7181 lines
327 KiB
Bash
Executable File
7181 lines
327 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# vim:ts=5:sw=5:expandtab
|
|
# we have a spaces softtab, that ensures readability with other editors too
|
|
|
|
[ -z "$BASH_VERSINFO" ] && printf "\n\033[1;35m Please make sure you're using \"bash\"! Bye...\033[m\n\n" >&2 && exit 245
|
|
[ $(kill -l | grep -c SIG) -eq 0 ] && printf "\n\033[1;35m Please make sure you're calling me without leading \"sh\"! Bye...\033[m\n\n" >&2 && exit 245
|
|
|
|
# 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-today, contributions so far see CREDITS.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. Do not violate the license and if
|
|
# you do not agree to all of these terms, do not use it in the first place.
|
|
#
|
|
# OpenSSL, which is being used and maybe distributed via one of this projects'
|
|
# web sites, is subject to their licensing: https://www.openssl.org/source/license.txt
|
|
#
|
|
# The client simulation data comes from SSLlabs and is licensed to the 'Qualys SSL Labs
|
|
# Terms of Use' (v2.2), see https://www.ssllabs.com/downloads/Qualys_SSL_Labs_Terms_of_Use.pdf,
|
|
# stating a CC BY 3.0 US license: https://creativecommons.org/licenses/by/3.0/us/
|
|
#
|
|
# Please note: USAGE WITHOUT ANY WARRANTY, THE SOFTWARE IS PROVIDED "AS IS".
|
|
#
|
|
# USE IT AT your OWN RISK!
|
|
# Seriously! The threat is you run this code on your computer and input could be /
|
|
# is being supplied via untrusted sources.
|
|
|
|
# HISTORY:
|
|
# Back in 2006 it all started with a few openssl commands...
|
|
# That's because openssl is a such a good swiss army knife (see e.g.
|
|
# 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 is 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 or Cygwin.
|
|
# Cross-platform is one of the three main goals of this script. Second: Ease of installation.
|
|
# No compiling, install gems, go to CPAN, use pip etc. Third: Easy to use and to interpret
|
|
# the results.
|
|
|
|
# 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 1) webservers 2) on standard ports, 3) reachable from the
|
|
# internet. And those examples above 4) are 3rd parties. If these restrictions are all fine
|
|
# with you and you need a management compatible rating -- go ahead and use those.
|
|
|
|
# But also if your fine with those restrictions: testssl.sh is meant as a tool in your hand
|
|
# and it's way more flexible.
|
|
#
|
|
# Oh, and did I mention testssl.sh is open source?
|
|
|
|
# Note that up to today there were a lot changes 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/bin/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 clear 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.7dev"
|
|
readonly SWCONTACT="dirk aet testssl dot sh"
|
|
egrep -q "dev|rc" <<< "$VERSION" && \
|
|
SWURL="https://testssl.sh/dev/" ||
|
|
SWURL="https://testssl.sh/ "
|
|
|
|
readonly PROG_NAME=$(basename "$0")
|
|
readonly RUN_DIR=$(dirname "$0")
|
|
INSTALL_DIR=""
|
|
MAPPING_FILE_RFC=""
|
|
OPENSSL_LOCATION=""
|
|
HNAME="$(hostname)"
|
|
HNAME="${HNAME%%.*}"
|
|
|
|
readonly CMDLINE="$@"
|
|
|
|
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 }')
|
|
if git log &>/dev/null; then
|
|
readonly GIT_REL=$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $1" "$2" "$3 }')
|
|
readonly GIT_REL_SHORT=$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $1 }')
|
|
readonly REL_DATE=$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $2 }')
|
|
else
|
|
readonly REL_DATE=$(tail -5 "$0" | awk '/dirkw Exp/ { print $5 }')
|
|
fi
|
|
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
|
|
|
|
tty -s && \
|
|
readonly INTERACTIVE=true || \
|
|
readonly INTERACTIVE=false
|
|
|
|
if ! tput cols &>/dev/null || ! $INTERACTIVE; then # Prevent tput errors if running non interactive
|
|
TERM_DWITH=${COLUMNS:-80}
|
|
else
|
|
TERM_DWITH=${COLUMNS:-$(tput cols)} # for custom line wrapping and dashes
|
|
fi
|
|
TERM_CURRPOS=0 # custom line wrapping needs alter the current horizontal cursor pos
|
|
|
|
# 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
|
|
# most of them can be set also by a cmd line switch
|
|
|
|
declare -x OPENSSL
|
|
COLOR=${COLOR:-2} # 2: Full color, 1: b/w+positioning, 0: no ESC at all
|
|
COLORBLIND=${COLORBLIND:-false} # if true, swap blue and green in the output
|
|
SHOW_EACH_C=${SHOW_EACH_C:-false} # where individual ciphers are tested show just the positively ones tested
|
|
SHOW_SIGALGO=${SHOW_SIGALGO:-false} # "secret" switch whether testssl.sh shows the signature algorithm for -E / -e
|
|
SNEAKY=${SNEAKY:-false} # is the referer and useragent we leave behind just usual?
|
|
QUIET=${QUIET:-false} # don't output the banner. By doing this yiu acknowledge usage term appearing in the banner
|
|
SSL_NATIVE=${SSL_NATIVE:-false} # we do per default bash sockets where possible "true": switch back to "openssl native"
|
|
ASSUMING_HTTP=${ASSUMING_HTTP:-false} # in seldom cases (WAF, old servers, grumpy SSL) service detection fails. "True" enforces HTTP checks
|
|
BUGS=${BUGS:-""} # -bugs option from openssl, needed for some BIG IP F5
|
|
DEBUG=${DEBUG:-0} # 1.: the temp files won't be erased.
|
|
# 2: list more what's going on (formerly: eq VERBOSE=1, VERBERR=true), lists some errors of connections
|
|
# 3: slight hexdumps + other info,
|
|
# 4: display bytes sent via sockets, 5: display bytes received via sockets, 6: whole 9 yards
|
|
WIDE=${WIDE:-false} # whether to display for some options the cipher or the table with hexcode/KX,Enc,strength etc.
|
|
LOGFILE=${LOGFILE:-""} # logfile if used
|
|
JSONFILE=${JSONFILE:-""} # jsonfile if used
|
|
CSVFILE=${CSVFILE:-""} # csvfile if used
|
|
HAS_IPv6=${HAS_IPv6:-false} # if you have OPENSSL with IPv6 support AND IPv6 networking set it to yes and testssl.sh works!
|
|
|
|
# tuning vars, can not be set by a cmd line switch
|
|
EXPERIMENTAL=${EXPERIMENTAL:-false}
|
|
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
|
|
STARTTLS_SLEEP=${STARTTLS_SLEEP:-1} # max time to wait on a socket replay for STARTTLS
|
|
FAST_STARTTLS=${FAST_STARTTLS:-true} #at the cost of reliabilty decrease the handshakes for STARTTLS
|
|
USLEEP_SND=${USLEEP_SND:-0.1} # sleep time for general socket send
|
|
USLEEP_REC=${USLEEP_REC:-0.2} # sleep time for general socket receive
|
|
HSTS_MIN=${HSTS_MIN:-179} # >179 days is ok for HSTS
|
|
HPKP_MIN=${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
|
|
DAYS2WARN1=${DAYS2WARN1:-60} # days to warn before cert expires, threshold 1
|
|
DAYS2WARN2=${DAYS2WARN2:-30} # days to warn before cert expires, threshold 2
|
|
VULN_THRESHLD=${VULN_THRESHLD:-1} # if vulnerabilities to check >$VULN_THRESHLD we DON'T show a separate header line in the output each vuln. check
|
|
|
|
HAD_SLEPT=0
|
|
CAPATH="${CAPATH:-/etc/ssl/certs/}" # Does nothing yet (FC has only a CA bundle per default, ==> openssl version -d)
|
|
FNAME=${FNAME:-""} # file name to read commands from
|
|
IKNOW_FNAME=false
|
|
|
|
# further global vars just declared here
|
|
readonly NPN_PROTOs="spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1"
|
|
# alpn_protos needs to be space-separated, not comma-seperated
|
|
readonly ALPN_PROTOs="h2 h2-17 h2-16 h2-15 h2-14 spdy/3.1 http/1.1"
|
|
TEMPDIR=""
|
|
TMPFILE=""
|
|
ERRFILE=""
|
|
CLIENT_AUTH=false
|
|
NO_SSL_SESSIONID=false
|
|
HOSTCERT=""
|
|
HEADERFILE=""
|
|
PROTOS_OFFERED=""
|
|
TLS_EXTENSIONS=""
|
|
GOST_STATUS_PROBLEM=false
|
|
DETECTED_TLS_VERSION=""
|
|
PATTERN2SHOW=""
|
|
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=${HAS_DH_BITS:-false} # initialize openssl variables
|
|
HAS_SSL2=false
|
|
HAS_SSL3=false
|
|
HAS_ALPN=false
|
|
HAS_SPDY=false
|
|
ADD_RFC_STR="rfc" # display RFC ciphernames
|
|
PORT=443 # unless otherwise auto-determined, see below
|
|
NODE=""
|
|
NODEIP=""
|
|
CORRECT_SPACES="" # used for IPv6 and proper output formatting
|
|
IPADDRs=""
|
|
IP46ADDRs=""
|
|
LOCAL_A=false # does the $NODEIP come from /etc/hosts?
|
|
LOCAL_AAAA=false # does the IPv6 IP come from /etc/hosts?
|
|
XMPP_HOST=""
|
|
PROXY=""
|
|
PROXYIP=""
|
|
PROXYPORT=""
|
|
VULN_COUNT=0
|
|
IPS=""
|
|
SERVICE="" # is the server running an HTTP server, SMTP, POP or IMAP?
|
|
URI=""
|
|
CERT_FINGERPRINT_SHA2=""
|
|
SHOW_CENSYS_LINK=${SHOW_CENSYS_LINK:-true}
|
|
STARTTLS_PROTOCOL=""
|
|
OPTIMAL_PROTO="" # we need this for IIS6 (sigh) and OpenSSL 1.0.2, otherwise some handshakes
|
|
# will fail, see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892
|
|
STARTTLS_OPTIMAL_PROTO="" # same for STARTTLS, see https://github.com/drwetter/testssl.sh/issues/188
|
|
TLS_TIME=""
|
|
TLS_NOW=""
|
|
NOW_TIME=""
|
|
HTTP_TIME=""
|
|
GET_REQ11=""
|
|
HEAD_REQ10=""
|
|
readonly UA_STD="TLS tester from $SWURL"
|
|
readonly UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0"
|
|
FIRST_FINDING=true # Is this the first finding we are outputting to file?
|
|
|
|
# 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 ######
|
|
# a little bit of sanitzing with bash internal search&replace -- otherwise printf will hiccup at '%' and '--' does the rest.
|
|
out(){
|
|
# if [[ "$BASH_VERSINFO" -eq 4 ]]; then
|
|
printf -- "%b" "${1//%/%%}"
|
|
# else
|
|
# /usr/bin/printf -- "${1//%/%%}"
|
|
# fi
|
|
}
|
|
outln() { out "$1\n"; }
|
|
#TODO: Still no shell injection safe but if just run it from the cmd line: that's fine
|
|
|
|
# color print functions, see also http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
|
|
pr_liteblue() { [[ "$COLOR" -eq 2 ]] && ( "$COLORBLIND" && out "\033[0;32m$1" || out "\033[0;34m$1" ) || out "$1"; pr_off; } # not yet used
|
|
pr_liteblueln() { pr_liteblue "$1"; outln; }
|
|
pr_blue() { [[ "$COLOR" -eq 2 ]] && ( "$COLORBLIND" && out "\033[1;32m$1" || out "\033[1;34m$1" ) || out "$1"; pr_off; } # used for head lines of single tests
|
|
pr_blueln() { pr_blue "$1"; outln; }
|
|
|
|
pr_warning() { [[ "$COLOR" -eq 2 ]] && out "\033[0;35m$1" || pr_underline "$1"; pr_off; } # litemagentai | local problem: one test cannot be done
|
|
pr_warningln() { pr_warning "$1"; outln; }
|
|
pr_magenta() { [[ "$COLOR" -eq 2 ]] && out "\033[1;35m$1" || pr_underline "$1"; pr_off; } # Fatal error: quitting because of this!
|
|
pr_magentaln() { pr_magenta "$1"; outln; }
|
|
|
|
pr_litecyan() { [[ "$COLOR" -eq 2 ]] && out "\033[0;36m$1" || out "$1"; pr_off; } # not yet used
|
|
pr_litecyanln() { pr_litecyan "$1"; outln; }
|
|
pr_cyan() { [[ "$COLOR" -eq 2 ]] && out "\033[1;36m$1" || out "$1"; pr_off; } # additional hint
|
|
pr_cyanln() { pr_cyan "$1"; outln; }
|
|
|
|
pr_litegreyln() { pr_litegrey "$1"; outln; }
|
|
pr_litegrey() { [[ "$COLOR" -eq 2 ]] && out "\033[0;37m$1" || out "$1"; pr_off; }
|
|
pr_grey() { [[ "$COLOR" -eq 2 ]] && out "\033[1;30m$1" || out "$1"; pr_off; }
|
|
pr_greyln() { pr_grey "$1"; outln; }
|
|
|
|
|
|
pr_done_good() { [[ "$COLOR" -eq 2 ]] && ( "$COLORBLIND" && out "\033[0;34m$1" || out "\033[0;32m$1" ) || out "$1"; pr_off; } # litegreen (liteblue), This is good
|
|
pr_done_goodln() { pr_done_good "$1"; outln; }
|
|
pr_done_best() { [[ "$COLOR" -eq 2 ]] && ( "$COLORBLIND" && out "\033[1;34m$1" || out "\033[1;32m$1" ) || out "$1"; pr_off; } # green (blue), This is the best
|
|
pr_done_bestln() { pr_done_best "$1"; outln; }
|
|
|
|
pr_svrty_minor() { [[ "$COLOR" -eq 2 ]] && out "\033[1;33m$1" || out "$1"; pr_off; } # yellow brown | academic or minor problem
|
|
pr_svrty_minorln() { pr_svrty_minor "$1"; outln; }
|
|
pr_svrty_medium() { [[ "$COLOR" -eq 2 ]] && out "\033[0;33m$1" || out "$1"; pr_off; } # brown | it is not a bad problem but you shouldn't do this
|
|
pr_svrty_mediumln() { pr_svrty_medium "$1"; outln; }
|
|
|
|
pr_svrty_high() { [[ "$COLOR" -eq 2 ]] && out "\033[0;31m$1" || pr_bold "$1"; pr_off; } # litered
|
|
pr_svrty_highln() { pr_svrty_high "$1"; outln; }
|
|
pr_svrty_critical() { [[ "$COLOR" -eq 2 ]] && out "\033[1;31m$1" || pr_bold "$1"; pr_off; } # red
|
|
pr_svrty_criticalln(){ pr_svrty_critical "$1"; outln; }
|
|
|
|
|
|
# color=1 functions
|
|
pr_off() { [[ "$COLOR" -ne 0 ]] && out "\033[m\c"; }
|
|
pr_bold() { [[ "$COLOR" -ne 0 ]] && out "\033[1m$1" || out "$1"; pr_off; }
|
|
pr_boldln() { pr_bold "$1" ; outln; }
|
|
pr_italic() { [[ "$COLOR" -ne 0 ]] && out "\033[3m$1" || out "$1"; pr_off; }
|
|
pr_underline() { [[ "$COLOR" -ne 0 ]] && out "\033[4m$1" || out "$1"; pr_off; }
|
|
pr_reverse() { [[ "$COLOR" -ne 0 ]] && out "\033[7m$1" || out "$1"; pr_off; }
|
|
pr_reverse_bold() { [[ "$COLOR" -ne 0 ]] && out "\033[7m\033[1m$1" || out "$1"; pr_off; }
|
|
|
|
#pr_headline() { pr_blue "$1"; }
|
|
#http://misc.flogisoft.com/bash/tip_colors_and_formatting
|
|
|
|
#pr_headline() { [[ "$COLOR" -eq 2 ]] && out "\033[1;30m\033[47m$1" || out "$1"; pr_off; }
|
|
pr_headline() { [[ "$COLOR" -ne 0 ]] && out "\033[1m\033[4m$1" || out "$1"; pr_off; }
|
|
pr_headlineln() { pr_headline "$1" ; outln; }
|
|
|
|
pr_squoted() { out "'$1'"; }
|
|
pr_dquoted() { out "\"$1\""; }
|
|
|
|
local_problem_ln() { pr_warningln "Local problem: $1"; }
|
|
local_problem() { pr_warning "Local problem: $1"; }
|
|
|
|
### color switcher (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
|
|
set_color_functions() {
|
|
local ncurses_tput=true
|
|
|
|
# empty vars if we have COLOR=0 equals no escape code:
|
|
red=""
|
|
green=""
|
|
brown=""
|
|
blue=""
|
|
magenta=""
|
|
cyan=""
|
|
grey=""
|
|
yellow=""
|
|
off=""
|
|
bold=""
|
|
underline=""
|
|
italic=""
|
|
|
|
which tput &>/dev/null || return 0 # Hey wait, do we actually have tput / ncurses ?
|
|
tput cols &>/dev/null || return 0 # tput under BSDs and GNUs doesn't work either (TERM undefined?)
|
|
tput sgr0 &>/dev/null || ncurses_tput=false
|
|
if [[ "$COLOR" -eq 2 ]]; then
|
|
if $ncurses_tput; then
|
|
red=$(tput setaf 1)
|
|
green=$(tput setaf 2)
|
|
brown=$(tput setaf 3)
|
|
blue=$(tput setaf 4)
|
|
magenta=$(tput setaf 5)
|
|
cyan=$(tput setaf 6)
|
|
grey=$(tput setaf 7)
|
|
yellow=$(tput setaf 3; tput bold)
|
|
else # this is a try for old BSD, see terminfo(5)
|
|
red=$(tput AF 1)
|
|
green=$(tput AF 2)
|
|
brown=$(tput AF 3)
|
|
blue=$(tput AF 4)
|
|
magenta=$(tput AF 5)
|
|
cyan=$(tput AF 6)
|
|
grey=$(tput AF 7)
|
|
yellow=$(tput AF 3; tput md)
|
|
fi
|
|
fi
|
|
|
|
if [[ "$COLOR" -ge 1 ]]; then
|
|
if $ncurses_tput; then
|
|
bold=$(tput bold)
|
|
underline=$(tput sgr 0 1)
|
|
italic=$(tput sitm)
|
|
italic_end=$(tput ritm)
|
|
off=$(tput sgr0)
|
|
else # this is a try for old BSD, see terminfo(5)
|
|
bold=$(tput md)
|
|
underline=$(tput us)
|
|
italic=$(tput ZH) # that doesn't work on FreeBSD 9+10.x
|
|
italic_end=$(tput ZR) # here too. Probably entry missing in /etc/termcap
|
|
reverse=$(tput mr)
|
|
off=$(tput me)
|
|
fi
|
|
fi
|
|
}
|
|
|
|
strip_quote() {
|
|
# remove color codes (see http://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed)
|
|
# \', leading and all trailing spaces
|
|
sed -e "s,\x1B\[[0-9;]*[a-zA-Z],,g" \
|
|
-e "s/\"/\\'/g" \
|
|
-e 's/^ *//g' \
|
|
-e 's/ *$//g' <<< "$1"
|
|
}
|
|
|
|
fileout_header() {
|
|
"$do_json" && printf "[\n" > "$JSONFILE"
|
|
"$do_csv" && echo "\"id\",\"fqdn/ip\",\"port\",\"severity\",\"finding\"" > "$CSVFILE"
|
|
}
|
|
|
|
fileout_footer() {
|
|
"$do_json" && printf "]\n" >> "$JSONFILE"
|
|
}
|
|
|
|
fileout() { # ID, SEVERITY, FINDING
|
|
local finding=$(strip_lf "$(newline_to_spaces "$(strip_quote "$3")")")
|
|
|
|
if "$do_json"; then
|
|
"$FIRST_FINDING" || echo "," >> $JSONFILE
|
|
echo -e "
|
|
{
|
|
\"id\" : \"$1\",
|
|
\"ip\" : \"$NODE/$NODEIP\",
|
|
\"port\" : \"$PORT\",
|
|
\"severity\" : \"$2\",
|
|
\"finding\" : \"$finding\"
|
|
}" >> $JSONFILE
|
|
fi
|
|
# does the following do any sanitization?
|
|
if "$do_csv"; then
|
|
echo -e \""$1\"",\"$NODE/$NODEIP\",\"$PORT"\",\""$2"\",\""$finding"\"" >>$CSVFILE
|
|
fi
|
|
"$FIRST_FINDING" && FIRST_FINDING=false
|
|
}
|
|
|
|
###### helper function definitions ######
|
|
|
|
debugme() {
|
|
[[ "$DEBUG" -ge 2 ]] && "$@"
|
|
}
|
|
|
|
hex2dec() {
|
|
#/usr/bin/printf -- "%d" 0x"$1"
|
|
echo $((16#$1))
|
|
}
|
|
|
|
# trim spaces for BSD and old sed
|
|
count_lines() {
|
|
wc -l <<<"$1" | sed 's/ //g'
|
|
}
|
|
count_words() {
|
|
wc -w <<<"$1" | sed 's/ //g'
|
|
}
|
|
|
|
count_ciphers() {
|
|
echo -n "$1" | sed 's/:/ /g' | wc -w | sed 's/ //g'
|
|
}
|
|
|
|
actually_supported_ciphers() {
|
|
$OPENSSL ciphers "$1" 2>/dev/null || echo ""
|
|
}
|
|
|
|
newline_to_spaces() {
|
|
tr '\n' ' ' <<< "$1" | sed 's/ $//'
|
|
}
|
|
|
|
colon_to_spaces() {
|
|
echo "${1//:/ }"
|
|
}
|
|
|
|
strip_lf() {
|
|
echo "$1" | tr -d '\n' | tr -d '\r'
|
|
}
|
|
|
|
strip_spaces() {
|
|
echo "${1// /}"
|
|
}
|
|
|
|
toupper() {
|
|
echo -n "$1" | tr 'a-z' 'A-Z'
|
|
}
|
|
|
|
is_number() {
|
|
[[ "$1" =~ ^[1-9][0-9]*$ ]] && \
|
|
return 0 || \
|
|
return 1
|
|
}
|
|
|
|
is_ipv4addr() {
|
|
local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
|
|
local ipv4address="$octet\\.$octet\\.$octet\\.$octet"
|
|
|
|
[[ -z "$1" ]] && return 1
|
|
# more than numbers, important for hosts like AAA.BBB.CCC.DDD.in-addr.arpa.DOMAIN.TLS
|
|
[[ -n $(tr -d '0-9\.' <<< "$1") ]] && return 1
|
|
|
|
echo -n "$1" | grep -Eq "$ipv4address" && \
|
|
return 0 || \
|
|
return 1
|
|
}
|
|
|
|
# a bit easier
|
|
is_ipv6addr() {
|
|
[[ -z "$1" ]] && return 1
|
|
# less than 2x ":"
|
|
[[ $(count_lines "$(echo -n "$1" | tr ':' '\n')") -le 1 ]] && \
|
|
return 1
|
|
#check on chars allowed:
|
|
[[ -n "$(tr -d '0-9:a-fA-F ' <<< "$1" | sed -e '/^$/d')" ]] && \
|
|
return 1
|
|
return 0
|
|
}
|
|
|
|
|
|
|
|
# prints out multiple lines in $1, left aligned by spaces in $2
|
|
out_row_aligned() {
|
|
local first=true
|
|
|
|
echo "$1" | while read line; do
|
|
if $first; then
|
|
first=false
|
|
else
|
|
out "$2"
|
|
fi
|
|
outln "$line"
|
|
done
|
|
}
|
|
|
|
|
|
tmpfile_handle() {
|
|
mv $TMPFILE "$TEMPDIR/$NODEIP.$1" 2>/dev/null
|
|
[[ $ERRFILE =~ dev.null ]] && return 0 || \
|
|
mv $ERRFILE "$TEMPDIR/$NODEIP.$(sed 's/\.txt//g' <<<"$1").errorlog" 2>/dev/null
|
|
}
|
|
|
|
# arg1: line with comment sign, tabs and so on
|
|
filter_input() {
|
|
echo "$1" | sed -e 's/#.*$//' -e '/^$/d' | tr -d '\n' | tr -d '\t'
|
|
}
|
|
|
|
|
|
wait_kill(){
|
|
local pid=$1 # pid we wait for or kill
|
|
local maxsleep=$2 # how long we wait before killing
|
|
|
|
HAD_SLEPT=0
|
|
while true; do
|
|
if ! ps $pid >/dev/null ; then
|
|
return 0 # process terminated before didn't reach $maxsleep
|
|
fi
|
|
[[ "$DEBUG" -ge 6 ]] && ps $pid
|
|
sleep 1
|
|
maxsleep=$((maxsleep - 1))
|
|
HAD_SLEPT=$((HAD_SLEPT + 1))
|
|
test $maxsleep -le 0 && break
|
|
done # needs to be killed:
|
|
kill $pid >&2 2>/dev/null
|
|
wait $pid 2>/dev/null # make sure pid terminated, see wait(1p)
|
|
return 3 # means killed
|
|
}
|
|
|
|
|
|
###### 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() {
|
|
local -i ret=0
|
|
local -i was_killed
|
|
|
|
if ! $CLIENT_AUTH; then
|
|
# SNI is nonsense for !HTTPS but fortunately for other protocols s_client doesn't seem to care
|
|
printf "$GET_REQ11" | $OPENSSL s_client $1 -quiet $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE &
|
|
wait_kill $! $HEADER_MAXSLEEP
|
|
was_killed=$?
|
|
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
|
|
head $TMPFILE | egrep -aqw "Jive News|InterNetNews|NNRP|INN" && SERVICE=NNTP
|
|
debugme head -50 $TMPFILE
|
|
fi
|
|
# FIXME: we can guess ports by port number if not properly recognized (and label it as guessed)
|
|
|
|
out " Service detected: $CORRECT_SPACES"
|
|
case $SERVICE in
|
|
HTTP)
|
|
out " $SERVICE"
|
|
fileout "service" "INFO" "Service detected: $SERVICE"
|
|
ret=0 ;;
|
|
IMAP|POP|SMTP|NNTP)
|
|
out " $SERVICE, thus skipping HTTP specific checks"
|
|
fileout "service" "INFO" "Service detected: $SERVICE, thus skipping HTTP specific checks"
|
|
ret=0 ;;
|
|
*) if $CLIENT_AUTH; then
|
|
out "certificate based authentication => skipping all HTTP checks"
|
|
echo "certificate based authentication => skipping all HTTP checks" >$TMPFILE
|
|
fileout "client_auth" "WARN" "certificate based authentication => skipping all HTTP checks"
|
|
else
|
|
out " Couldn't determine what's running on port $PORT"
|
|
if $ASSUMING_HTTP; then
|
|
SERVICE=HTTP
|
|
out " -- ASSUMING_HTTP set though"
|
|
fileout "service" "WARN" "Couldn't determine service, --ASSUMING_HTTP set"
|
|
ret=0
|
|
else
|
|
out ", assuming no HTTP service => skipping all HTTP checks"
|
|
fileout "service" "WARN" "Couldn't determine service, skipping all HTTP checks"
|
|
ret=1
|
|
fi
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
outln "\n"
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $ret
|
|
}
|
|
|
|
|
|
#problems not handled: chunked
|
|
run_http_header() {
|
|
local header
|
|
local -i ret
|
|
local referer useragent
|
|
local url redirect
|
|
|
|
HEADERFILE=$TEMPDIR/$NODEIP.http_header.txt
|
|
outln; pr_headlineln " Testing HTTP header response @ \"$URL_PATH\" "
|
|
outln
|
|
|
|
[[ -z "$1" ]] && url="/" || url="$1"
|
|
printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI >$HEADERFILE 2>$ERRFILE &
|
|
wait_kill $! $HEADER_MAXSLEEP
|
|
if [[ $? -eq 0 ]]; then
|
|
# we do the get command again as it terminated within $HEADER_MAXSLEEP. Thus it didn't hang, we do it
|
|
# again in the foreground ito get an ccurate header time!
|
|
printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI >$HEADERFILE 2>$ERRFILE
|
|
NOW_TIME=$(date "+%s")
|
|
HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $HEADERFILE)
|
|
HAD_SLEPT=0
|
|
else
|
|
# GET request needed to be killed before, try, whether it succeeded:
|
|
if egrep -iaq "XML|HTML|DOCTYPE|HTTP|Connection" $HEADERFILE; then
|
|
NOW_TIME=$(($(date "+%s") - HAD_SLEPT)) # correct by seconds we slept
|
|
HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $HEADERFILE)
|
|
else
|
|
pr_warning " likely HTTP header requests failed (#lines: $(wc -l < $HEADERFILE | sed 's/ //g'))."
|
|
outln "Rerun with DEBUG=1 and inspect \"run_http_header.txt\"\n"
|
|
debugme cat $HEADERFILE
|
|
return 7
|
|
fi
|
|
fi
|
|
# populate vars for HTTP time
|
|
|
|
debugme echo "$NOW_TIME: $HTTP_TIME"
|
|
|
|
# delete from pattern til the end. We ignore any leading spaces (e.g. www.amazon.de)
|
|
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
|
|
|
|
status_code=$(awk '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE)
|
|
msg_thereafter=$(awk -F"$status_code" '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE) # dirty trick to use the status code as a
|
|
msg_thereafter=$(strip_lf "$msg_thereafter") # field separator, otherwise we need a loop with awk
|
|
debugme echo "Status/MSG: $status_code $msg_thereafter"
|
|
|
|
pr_bold " HTTP Status Code "
|
|
[[ -z "$status_code" ]] && pr_cyan "No status code" && return 3
|
|
|
|
out " $status_code$msg_thereafter"
|
|
case $status_code in
|
|
301|302|307|308)
|
|
redirect=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n')
|
|
out ", redirecting to \"$redirect\""
|
|
if [[ $redirect == "http://"* ]]; then
|
|
pr_svrty_high " -- Redirect to insecure URL (NOT ok)"
|
|
fileout "status_code" "NOT ok" \, "Redirect to insecure URL (NOT ok). Url: \"$redirect\""
|
|
fi
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter, redirecting to \"$redirect\""
|
|
;;
|
|
200)
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter"
|
|
;;
|
|
204)
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter"
|
|
;;
|
|
206)
|
|
out " -- WTF?"
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter -- WTF?"
|
|
;;
|
|
400)
|
|
pr_cyan " (Hint: better try another URL)"
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter (Hint: better try another URL)"
|
|
;;
|
|
401)
|
|
grep -aq "^WWW-Authenticate" $HEADERFILE && out " "; strip_lf "$(grep -a "^WWW-Authenticate" $HEADERFILE)"
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter $(grep -a "^WWW-Authenticate" $HEADERFILE)"
|
|
;;
|
|
403)
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter"
|
|
;;
|
|
404)
|
|
out " (Hint: supply a path which doesn't give a \"$status_code$msg_thereafter\")"
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter (Hint: supply a path which doesn't give a \"$status_code$msg_thereafter\")"
|
|
;;
|
|
405)
|
|
fileout "status_code" "INFO" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter"
|
|
;;
|
|
*)
|
|
pr_warning ". Oh, didn't expect \"$status_code$msg_thereafter\""
|
|
fileout "status_code" "WARN" \
|
|
"Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter. 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
|
|
}
|
|
|
|
# Borrowed 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 whitelisted_header="pagespeed|page-speed|^Content-Security-Policy|^MicrosoftSharePointTeamServices|^X-OWA-Version"
|
|
local your_ip_msg="(check if it's your IP address or e.g. a cluster IP)"
|
|
local result
|
|
local first=true
|
|
local spaces=" "
|
|
local count
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_http_header "$1" || return 3
|
|
fi
|
|
|
|
# white list some headers as they are mistakenly identified as ipv4 address. Issues 158, 323,o facebook has a CSP rule for 127.0.0.1
|
|
if egrep -vi "$whitelisted_header" $HEADERFILE | grep -iqE "$ipv4address"; then
|
|
pr_bold " IPv4 address in header "
|
|
count=0
|
|
while read line; do
|
|
result="$(grep -E "$ipv4address" <<< "$line")"
|
|
result=$(strip_lf "$result")
|
|
if [[ -n "$result" ]]; then
|
|
if ! $first; then
|
|
out "$spaces"
|
|
your_ip_msg=""
|
|
else
|
|
first=false
|
|
fi
|
|
pr_svrty_high "$result"
|
|
outln "\n$spaces$your_ip_msg"
|
|
fileout "ip_in_header_$count" "NOT ok" "IPv4 address in header $result $your_ip_msg"
|
|
fi
|
|
count=$count+1
|
|
done < $HEADERFILE
|
|
fi
|
|
}
|
|
|
|
|
|
run_http_date() {
|
|
local now difftime
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_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
|
|
if [[ -n "$HTTP_TIME" ]]; then
|
|
if "$HAS_GNUDATE"; then
|
|
HTTP_TIME=$(date --date="$HTTP_TIME" "+%s")
|
|
else
|
|
HTTP_TIME=$(LC_ALL=C date -j -f "%a, %d %b %Y %T %Z" "$HTTP_TIME" "+%s" 2>>$ERRFILE) # the trailing \r confuses BSD flavors otherwise
|
|
fi
|
|
|
|
difftime=$((HTTP_TIME - $NOW_TIME))
|
|
[[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
|
|
# process was killed, so we need to add an error:
|
|
[[ $HAD_SLEPT -ne 0 ]] && difftime="$difftime (± 1.5)"
|
|
out "$difftime sec from localtime";
|
|
fileout "http_clock_skew" "INFO" "HTTP clock skew $difftime sec from localtime"
|
|
else
|
|
out "Got no HTTP time, maybe try different URL?";
|
|
fileout "http_clock_skew" "INFO" "HTTP clock skew not measured. Got no HTTP time, maybe try different URL?"
|
|
fi
|
|
debugme out ", epoch: $HTTP_TIME"
|
|
fi
|
|
outln
|
|
detect_ipv4
|
|
}
|
|
|
|
includeSubDomains() {
|
|
if grep -aiqw includeSubDomains "$1"; then
|
|
pr_done_good ", includeSubDomains"
|
|
return 1
|
|
else
|
|
pr_litecyan ", just this domain"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
preload() {
|
|
if grep -aiqw preload "$1"; then
|
|
pr_done_good ", preload"
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
|
|
run_hsts() {
|
|
local hsts_age_sec
|
|
local hsts_age_days
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_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_done_good "$hsts_age_days days" ; out "=$hsts_age_sec s"
|
|
fileout "hsts_time" "OK" "HSTS timeout $hsts_age_days days (=$hsts_age_sec seconds) > $HSTS_MIN days"
|
|
else
|
|
out "$hsts_age_sec s = "
|
|
pr_svrty_medium "$hsts_age_days days, <$HSTS_MIN days is too short"
|
|
fileout "hsts_time" "NOT ok" "HSTS timeout too short. $hsts_age_days days (=$hsts_age_sec seconds) < $HSTS_MIN days"
|
|
fi
|
|
if includeSubDomains "$TMPFILE"; then
|
|
fileout "hsts_subdomains" "OK" "HSTS includes subdomains"
|
|
else
|
|
fileout "hsts_subdomains" "WARN" "HSTS only for this domain, consider to include subdomains as well"
|
|
fi
|
|
if preload "$TMPFILE"; then
|
|
fileout "hsts_preload" "OK" "HSTS domain is marked for preloading"
|
|
else
|
|
fileout "hsts_preload" "INFO" "HSTS domain is NOT marked for preloading"
|
|
fi
|
|
#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 "--"
|
|
fileout "hsts" "NOT ok" "No support for HTTP Strict Transport Security"
|
|
fi
|
|
outln
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $?
|
|
}
|
|
|
|
|
|
run_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 key_found=false
|
|
local i
|
|
local hpkp_headers
|
|
local first_hpkp_header
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_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
|
|
if egrep -aciw '^Public-Key-Pins|Public-Key-Pins-Report-Only' $HEADERFILE | egrep -waq "1" ; then
|
|
:
|
|
else
|
|
hpkp_headers=""
|
|
pr_svrty_medium "multiple HPKP headers: "
|
|
# https://scotthelme.co.uk is a candidate
|
|
#FIXME: should display both Public-Key-Pins+Public-Key-Pins-Report-Only --> egrep -ai -w
|
|
for i in $(newline_to_spaces "$(egrep -ai '^Public-Key-Pins' $HEADERFILE | awk -F':' '/Public-Key-Pins/ { print $1 }')"); do
|
|
pr_italic $i
|
|
hpkp_headers="$hpkp_headers$i "
|
|
out " "
|
|
done
|
|
out "\n$spaces Examining first one: "
|
|
first_hpkp_header=$(awk -F':' '/Public-Key-Pins/ { print $1 }' $HEADERFILE | head -1)
|
|
pr_italic "$first_hpkp_header, "
|
|
fileout "hpkp_multiple" "WARN" "Multiple HPKP headershpkp_headers. Using first header: $first_hpkp_header"
|
|
fi
|
|
|
|
# remove leading Public-Key-Pins*, any colons, double quotes and trailing spaces and taking the first -- whatever that is
|
|
sed -e 's/Public-Key-Pins://g' -e s'/Public-Key-Pins-Report-Only://' $TMPFILE | \
|
|
sed -e 's/;//g' -e 's/\"//g' -e 's/^ //' | head -1 > $TMPFILE.2
|
|
# BSD lacks -i, otherwise we would have done it inline
|
|
# now separate key value and other stuff per line:
|
|
tr ' ' '\n' < $TMPFILE.2 >$TMPFILE
|
|
|
|
hpkp_nr_keys=$(grep -ac pin-sha $TMPFILE)
|
|
out "# of keys: "
|
|
if [[ $hpkp_nr_keys -eq 1 ]]; then
|
|
pr_svrty_high "1 (NOT ok), "
|
|
fileout "hpkp_keys" "NOT ok" "Only one key pinned in HPKP header, this means the site may become unavailable if the key is revoked"
|
|
else
|
|
out "$hpkp_nr_keys, "
|
|
fileout "hpkp_keys" "OK" "$hpkp_nr_keys keys pinned in HPKP header, additional keys are available if the current key is revoked"
|
|
fi
|
|
|
|
# print key=value pair with awk, then strip non-numbers, to be improved with proper parsing of key-value with awk
|
|
hpkp_age_sec=$(awk -F= '/max-age/{max_age=$2; print max_age}' $TMPFILE | sed -E 's/[^[:digit:]]//g')
|
|
hpkp_age_days=$((hpkp_age_sec / 86400))
|
|
if [[ $hpkp_age_days -ge $HPKP_MIN ]]; then
|
|
pr_done_good "$hpkp_age_days days" ; out "=$hpkp_age_sec s"
|
|
fileout "hpkp_age" "OK" "HPKP age is set to $hpkp_age_days days ($hpkp_age_sec sec)"
|
|
else
|
|
out "$hpkp_age_sec s = "
|
|
pr_svrty_medium "$hpkp_age_days days (<$HPKP_MIN days is not good enough)"
|
|
fileout "hpkp_age" "NOT ok" "HPKP age is set to $hpkp_age_days days ($hpkp_age_sec sec) < $HPKP_MIN days is not good enough."
|
|
fi
|
|
|
|
if includeSubDomains "$TMPFILE"; then
|
|
fileout "hpkp_subdomains" "INFO" "HPKP header is valid for subdomains as well"
|
|
else
|
|
fileout "hpkp_subdomains" "INFO" "HPKP header is valid for this domain only"
|
|
fi
|
|
if preload "$TMPFILE"; then
|
|
fileout "hpkp_preload" "INFO" "HPKP header is marked for browser preloading"
|
|
else
|
|
fileout "hpkp_preload" "INFO" "HPKP header is NOT marked for browser preloading"
|
|
fi
|
|
|
|
if [[ ! -s "$HOSTCERT" ]]; then
|
|
get_host_cert || return 1
|
|
fi
|
|
# get the key fingerprint from the host certificate
|
|
hpkp_key_hostcert="$($OPENSSL x509 -in $HOSTCERT -pubkey -noout | grep -v PUBLIC | \
|
|
$OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL base64)"
|
|
# compare it with the ones provided in the header
|
|
while read hpkp_key; do
|
|
if [[ "$hpkp_key_hostcert" == "$hpkp_key" ]] || [[ "$hpkp_key_hostcert" == "$hpkp_key=" ]]; then
|
|
out "\n$spaces matching host key: "
|
|
pr_done_good "$hpkp_key"
|
|
fileout "hpkp_keymatch" "OK" "Key matches a key pinned in the HPKP header"
|
|
key_found=true
|
|
fi
|
|
debugme out "\n $hpkp_key | $hpkp_key_hostcert"
|
|
done < <(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }')
|
|
if ! $key_found ; then
|
|
out "\n$spaces"
|
|
pr_svrty_high " No matching key for pins found "
|
|
out "(CAs pinned? -- not checked for yet)"
|
|
fileout "hpkp_keymatch" "WARN" "The TLS key does not match any key pinned in the HPKP header. If you pinned a CA key you can ignore this"
|
|
fi
|
|
else
|
|
out "--"
|
|
fileout "hpkp" "INFO" "No support for HTTP Public Key Pinning"
|
|
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-Forwarded/"$yellow"X-Forwarded$off/g" \
|
|
-e "s/Liferay-Portal/"$yellow"Liferay-Portal$off/g" \
|
|
-e "s/X-Cache-Lookup/"$yellow"X-Cache-Lookup$off/g" \
|
|
-e "s/X-Cache/"$yellow"X-Cache$off/g" \
|
|
-e "s/X-Squid/"$yellow"X-Squid$off/g" \
|
|
-e "s/X-Server/"$yellow"X-Server$off/g" \
|
|
-e "s/X-Varnish/"$yellow"X-Varnish$off/g" \
|
|
-e "s/X-OWA-Version/"$yellow"X-OWA-Version$off/g" \
|
|
-e "s/MicrosoftSharePointTeamServices/"$yellow"MicrosoftSharePointTeamServices$off/g" \
|
|
-e "s/X-Version/"$yellow"X-Version$off/g" \
|
|
-e "s/X-Powered-By/"$yellow"X-Powered-By$off/g" \
|
|
-e "s/X-UA-Compatible/"$yellow"X-UA-Compatible$off/g" \
|
|
-e "s/X-AspNet-Version/"$yellow"X-AspNet-Version$off/g"
|
|
}
|
|
|
|
run_server_banner() {
|
|
local serverbanner
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_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" ]] || [[ x"$serverbanner" == "x\n\r" ]] || [[ -z "$serverbanner" ]]; then
|
|
outln "banner exists but empty string"
|
|
fileout "serverbanner" "INFO" "Server banner exists but empty string"
|
|
else
|
|
emphasize_stuff_in_headers "$serverbanner"
|
|
fileout "serverbanner" "INFO" "Server banner identified: $serverbanner"
|
|
if [[ "$serverbanner" = *Microsoft-IIS/6.* ]] && [[ $OSSL_VER == 1.0.2* ]]; then
|
|
pr_warningln " It's recommended to run another test w/ OpenSSL 1.0.1 !"
|
|
# see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892
|
|
fileout "IIS6_openssl_mismatch" "WARN" "It is recommended to rerun this test w/ OpenSSL 1.0.1\nSee https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892"
|
|
fi
|
|
fi
|
|
# mozilla.github.io/server-side-tls/ssl-config-generator/
|
|
# https://support.microsoft.com/en-us/kb/245030
|
|
else
|
|
outln "(no \"Server\" line in header, interesting!)"
|
|
fileout "serverbanner" "WARN" "No Server banner in header, interesting!"
|
|
fi
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
run_rp_banner() {
|
|
local line
|
|
local first=true
|
|
local spaces=" "
|
|
local rp_banners=""
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_http_header "$1" || return 3
|
|
fi
|
|
pr_bold " Reverse Proxy banner "
|
|
egrep -ai '^Via:|^X-Cache|^X-Squid|^X-Varnish:|^X-Server-Name:|^X-Server-Port:|^x-forwarded' $HEADERFILE >$TMPFILE
|
|
if [[ $? -ne 0 ]]; then
|
|
outln "--"
|
|
fileout "rp_header" "INFO" "No reverse proxy banner found"
|
|
else
|
|
while read line; do
|
|
line=$(strip_lf "$line")
|
|
if ! $first; then
|
|
out "$spaces"
|
|
else
|
|
first=false
|
|
fi
|
|
emphasize_stuff_in_headers "$line"
|
|
rp_banners="$rp_bannersline"
|
|
done < $TMPFILE
|
|
fileout "rp_header" "INFO" "Reverse proxy banner(s) found: $rp_banners"
|
|
fi
|
|
outln
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
# emphasize_stuff_in_headers "$(sed 's/^/ /g' $TMPFILE | tr '\n\r' ' ')" || \
|
|
}
|
|
|
|
run_application_banner() {
|
|
local line
|
|
local first=true
|
|
local spaces=" "
|
|
local app_banners=""
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_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^|^MicrosoftSharePointTeamServices' $HEADERFILE >$TMPFILE
|
|
if [[ $? -ne 0 ]]; then
|
|
outln "--"
|
|
fileout "app_banner" "INFO" "No Application Banners found"
|
|
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"
|
|
app_banners="$app_bannersline"
|
|
done
|
|
fileout "app_banner" "WARN" "Application Banners found: $app_banners"
|
|
fi
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
run_cookie_flags() { # ARG1: Path, ARG2: path
|
|
local -i nr_cookies
|
|
local nr_httponly nr_secure
|
|
local negative_word
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_http_header "$1" || return 3
|
|
fi
|
|
pr_bold " Cookie(s) "
|
|
grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE
|
|
if [[ $? -eq 0 ]]; then
|
|
nr_cookies=$(count_lines "$TMPFILE")
|
|
out "$nr_cookies issued: "
|
|
fileout "cookie_count" "INFO" "$nr_cookies cookie(s) issued at \"$1\""
|
|
if [[ $nr_cookies -gt 1 ]]; then
|
|
negative_word="NONE"
|
|
else
|
|
negative_word="NOT"
|
|
fi
|
|
nr_secure=$(grep -iac secure $TMPFILE)
|
|
case $nr_secure in
|
|
0) pr_svrty_medium "$negative_word" ;;
|
|
[123456789]) pr_done_good "$nr_secure/$nr_cookies";;
|
|
esac
|
|
out " secure, "
|
|
if [[ $nr_cookies == $nr_secure ]]; then
|
|
fileout "cookie_secure" "OK" "All $nr_cookies cookie(s) issued at \"$1\" marked as secure"
|
|
else
|
|
fileout "cookie_secure" "WARN" "$nr_secure/$nr_cookies cookie(s) issued at \"$1\" marked as secure"
|
|
fi
|
|
nr_httponly=$(grep -cai httponly $TMPFILE)
|
|
case $nr_httponly in
|
|
0) pr_svrty_medium "$negative_word" ;;
|
|
[123456789]) pr_done_good "$nr_httponly/$nr_cookies";;
|
|
esac
|
|
out " HttpOnly"
|
|
if [[ $nr_cookies == $nr_httponly ]]; then
|
|
fileout "cookie_httponly" "OK" "All $nr_cookies cookie(s) issued at \"$1\" marked as HttpOnly"
|
|
else
|
|
fileout "cookie_httponly" "WARN" "$nr_secure/$nr_cookies cookie(s) issued at \"$1\" marked as HttpOnly"
|
|
fi
|
|
else
|
|
out "(none issued at \"$1\")"
|
|
fileout "cookie_count" "INFO" "No cookies issued at \"$1\""
|
|
fi
|
|
outln
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
|
|
run_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 X-UA-Compatible"
|
|
local egrep_pattern=""
|
|
local f2t result_str
|
|
local first=true
|
|
local spaces=" "
|
|
|
|
if [[ ! -s $HEADERFILE ]]; then
|
|
run_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 "--"
|
|
fileout "sec_headers" "WARN" "No security (or other interesting) headers detected"
|
|
ret=1
|
|
else
|
|
ret=0
|
|
for f2t in $good_flags2test; do
|
|
debugme echo "---> $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
|
|
# extract and print key(=flag) in green:
|
|
pr_done_good "${result_str%%:*}:"
|
|
#pr_done_good "$(sed 's/:.*$/:/' <<< "$result_str")"
|
|
# print value in plain text:
|
|
outln "${result_str#*:}"
|
|
fileout "${result_str%%:*}" "OK" "${result_str%%:*}: ${result_str#*:}"
|
|
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
|
|
out "$spaces" # output leading spaces if the first header
|
|
else
|
|
first=false
|
|
fi
|
|
# extract and print key(=flag) underlined
|
|
pr_litecyan "${result_str%%:*}:"
|
|
# print value in plain text:
|
|
outln "${result_str#*:}"
|
|
fileout "${result_str%%:*}" "WARN" "${result_str%%:*}: ${result_str#*:}"
|
|
done
|
|
fi
|
|
#TODO: 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/ssllabs 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')
|
|
part2=${part2//0x/}
|
|
if [[ -n "$part3" ]]; then # a SSLv2 cipher has three parts
|
|
#part3=$(echo $part3 | sed 's/0x//g')
|
|
part3=${part3//0x/}
|
|
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() {
|
|
local arg
|
|
local hexcode dash ciph sslvers kx auth enc mac export
|
|
local re='^[0-9A-Fa-f]+$'
|
|
|
|
if [[ "$1" == 0x* ]] || [[ "$1" == 0X* ]]; then
|
|
fatal "pls supply x<number> instead" 2
|
|
fi
|
|
|
|
pr_headline " Displaying all local ciphers ";
|
|
if [[ -n "$1" ]]; then
|
|
# pattern provided; which one?
|
|
[[ $1 =~ $re ]] && \
|
|
pr_headline "matching number pattern \"$1\" " || \
|
|
pr_headline "matching word pattern "\"$1\"" (ignore case) "
|
|
fi
|
|
outln "\n"
|
|
neat_header
|
|
|
|
if [[ -z "$1" ]]; then
|
|
$OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE | 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
|
|
for arg in ${*//,/ /}; do
|
|
$OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE | while read hexcode dash ciph sslvers kx auth enc mac export ; do # -V doesn't work with openssl < 1.0
|
|
normalize_ciphercode $hexcode
|
|
# for numbers we don't do word matching:
|
|
[[ $arg =~ $re ]] && \
|
|
neat_list "$HEXC" "$ciph" "$kx" "$enc" | grep -ai "$arg" || \
|
|
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() {
|
|
local -i ret
|
|
local debugname="$(sed -e s'/\!/not/g' -e 's/\:/_/g' <<< "$1")"
|
|
|
|
$OPENSSL ciphers "$1" &>$TMPFILE
|
|
ret=$?
|
|
debugme cat $TMPFILE
|
|
|
|
tmpfile_handle $FUNCNAME.$debugname.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() {
|
|
local -i sclient_success
|
|
local singlespaces
|
|
local debugname="$(sed -e s'/\!/not/g' -e 's/\:/_/g' <<< "$1")"
|
|
|
|
pr_bold "$2 " # indent in order to be in the same row as server preferences
|
|
if listciphers "$1"; then # is that locally available??
|
|
$OPENSSL s_client -cipher "$1" $BUGS $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI 2>$ERRFILE >$TMPFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$?
|
|
debugme cat $ERRFILE
|
|
case $3 in
|
|
0) # ok to offer
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_done_bestln "offered (OK)"
|
|
fileout "std_$4" "OK" "$2 offered (OK)"
|
|
else
|
|
pr_svrty_mediumln "not offered (NOT ok)"
|
|
fileout "std_$4" "NOT ok" "$2 not offered (NOT ok)"
|
|
fi
|
|
;;
|
|
1) # the ugly ones
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_svrty_criticalln "offered (NOT ok)"
|
|
fileout "std_$4" "NOT ok" "$2 offered (NOT ok) - ugly"
|
|
else
|
|
pr_done_bestln "not offered (OK)"
|
|
fileout "std_$4" "OK" "$2 not offered (OK)"
|
|
fi
|
|
;;
|
|
2) # bad but not worst
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_svrty_highln "offered (NOT ok)"
|
|
fileout "std_$4" "NOT ok" "$2 offered (NOT ok) - bad"
|
|
else
|
|
pr_done_goodln "not offered (OK)"
|
|
fileout "std_$4" "OK" "$2 not offered (OK)"
|
|
fi
|
|
;;
|
|
3) # not totally bad
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_svrty_mediumln "offered (NOT ok)"
|
|
fileout "std_$4" "NOT ok" "$2 offered (NOT ok) - not too bad"
|
|
else
|
|
outln "not offered (OK)"
|
|
fileout "std_$4" "OK" "$2 not offered (OK)"
|
|
fi
|
|
;;
|
|
*) # we shouldn't reach this
|
|
pr_warning "?: $3 (please report this)"
|
|
fileout "std_$4" "WARN" "return condition $3 unclear"
|
|
;;
|
|
esac
|
|
tmpfile_handle $FUNCNAME.$debugname.txt
|
|
else
|
|
singlespaces=$(echo "$2" | sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/ //g')
|
|
local_problem_ln "No $singlespaces configured in $OPENSSL"
|
|
fileout "std_$4" "WARN" "Cipher $2 ($1) not supported by local OpenSSL ($OPENSSL)"
|
|
fi
|
|
# we need 1xlf in those cases:
|
|
debugme 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 still sockread_serverhello()
|
|
sockread() {
|
|
local -i ret=0
|
|
local ddreply
|
|
|
|
[[ "x$2" == "x" ]] && maxsleep=$MAX_WAITSOCK || maxsleep=$2
|
|
|
|
ddreply=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7
|
|
dd bs=$1 of=$ddreply count=1 <&5 2>/dev/null &
|
|
wait_kill $! $maxsleep
|
|
ret=$?
|
|
SOCKREPLY=$(cat $ddreply)
|
|
rm $ddreply
|
|
return $ret
|
|
}
|
|
|
|
#FIXME: fill the following two:
|
|
openssl2rfc() {
|
|
:
|
|
}
|
|
|
|
rfc2openssl() {
|
|
:
|
|
}
|
|
|
|
|
|
show_rfc_style(){
|
|
[[ -z "$ADD_RFC_STR" ]] && return 1
|
|
#[[ -z "$1" ]] && return 0
|
|
|
|
local rfcname
|
|
rfcname="$(grep -iw "$1" "$MAPPING_FILE_RFC" | sed -e 's/^.*TLS/TLS/' -e 's/^.*SSL/SSL/')"
|
|
[[ -n "$rfcname" ]] && out "$rfcname"
|
|
return 0
|
|
}
|
|
|
|
neat_header(){
|
|
printf -- "Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits${ADD_RFC_STR:+ Cipher Suite Name (RFC)}\n"
|
|
printf -- "%s-------------------------------------------------------------------------${ADD_RFC_STR:+-------------------------------------------------}\n"
|
|
}
|
|
|
|
|
|
# arg1: hexcode
|
|
# arg2: cipher in openssl notation
|
|
# arg3: keyexchange
|
|
# arg4: encryption (maybe included "export")
|
|
neat_list(){
|
|
local hexcode="$1"
|
|
local ossl_cipher="$2"
|
|
local kx enc strength
|
|
|
|
kx="${3//Kx=/}"
|
|
enc="${4//Enc=/}"
|
|
strength=$(sed -e 's/.*(//' -e 's/)//' <<< "$enc") # strength = encryption bits
|
|
strength="${strength//ChaCha20-Poly1305/ly1305}"
|
|
enc=$(sed -e 's/(.*)//g' -e 's/ChaCha20-Poly1305/ChaCha20-Po/g' <<< "$enc") # workaround for empty bits ChaCha20-Poly1305
|
|
echo "$export" | grep -iq export && strength="$strength,export"
|
|
|
|
#printf -- "%q" "$kx" | xxd | head -1
|
|
# length correction for color escape codes (printf counts the escape color codes!!)
|
|
if printf -- "%q" "$kx" | egrep -aq '.;3.m|E\[1m' ; then # here's a color code which screws up the formatting with printf below
|
|
while [[ ${#kx} -lt 20 ]]; do
|
|
kx="$kx "
|
|
done
|
|
elif printf -- "%q" "$kx" | grep -aq 'E\[m' ; then # for color=1/0 we have the pr_off which screws up the formatting
|
|
while [[ ${#kx} -lt 13 ]]; do # so it'll be filled up ok
|
|
kx="$kx "
|
|
done
|
|
fi
|
|
#echo "${#kx}" # should be always 20 / 13
|
|
printf -- " %-7s %-30s %-10s %-11s%-11s${ADD_RFC_STR:+ %-48s}${SHOW_EACH_C:+ %-0s}" "$hexcode" "$ossl_cipher" "$kx" "$enc" "$strength" "$(show_rfc_style "$hexcode")"
|
|
}
|
|
|
|
test_just_one(){
|
|
local hexcode n ciph sslvers kx auth enc mac export
|
|
local dhlen
|
|
local sclient_success
|
|
local re='^[0-9A-Fa-f]+$'
|
|
|
|
pr_headline " Testing single cipher with "
|
|
if [[ $1 =~ $re ]]; then
|
|
pr_headline "matching number pattern \"$1\" "
|
|
tjolines="$tjolines matching number pattern \"$1\"\n\n"
|
|
else
|
|
pr_headline "word pattern "\"$1\"" (ignore case) "
|
|
tjolines="$tjolines word pattern \"$1\" (ignore case)\n\n"
|
|
fi
|
|
outln
|
|
! "$HAS_DH_BITS" && pr_warningln " (Your $OPENSSL cannot show DH/ECDH bits)"
|
|
outln
|
|
neat_header
|
|
#for arg in $(echo $@ | sed 's/,/ /g'); do
|
|
for arg in ${*//, /}; do
|
|
# 1st check whether openssl has cipher or not
|
|
$OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE | 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
|
|
# is argument a number?
|
|
if [[ $arg =~ $re ]]; then
|
|
neat_list $HEXC $ciph $kx $enc | grep -qai "$arg"
|
|
else
|
|
neat_list $HEXC $ciph $kx $enc | grep -qwai "$arg"
|
|
fi
|
|
if [[ $? -eq 0 ]]; then # string matches, so we can ssl to it:
|
|
$OPENSSL s_client -cipher $ciph $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI 2>$ERRFILE >$TMPFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$?
|
|
if [[ $kx == "Kx=ECDH" ]] || [[ $kx == "Kx=DH" ]] || [[ $kx == "Kx=EDH" ]]; then
|
|
if [[ $sclient_success -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 [[ $sclient_success -eq 0 ]]; then
|
|
pr_cyan " available"
|
|
fileout "cipher_$HEXC" "INFO" "$(neat_header)(neat_list $HEXC $ciph "$kx" $enc) available"
|
|
else
|
|
out " not a/v"
|
|
fileout "cipher_$HEXC" "INFO" "$(neat_header)(neat_list $HEXC $ciph "$kx" $enc) not a/v"
|
|
fi
|
|
outln
|
|
fi
|
|
done
|
|
exit
|
|
done
|
|
outln
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0 # this is a single test for a cipher
|
|
}
|
|
|
|
|
|
# test for all ciphers locally configured (w/o distinguishing whether they are good or bad)
|
|
run_allciphers() {
|
|
local tmpfile
|
|
local -i nr_ciphers=0
|
|
local n sslvers auth mac export
|
|
local -a hexcode ciph kx enc export2
|
|
local -i i j parent child end_of_bundle round_num bundle_size num_bundles mod_check
|
|
local -a ciphers_found
|
|
local dhlen
|
|
local available
|
|
local ciphers_to_test
|
|
|
|
# get a list of all the cipher suites to test (only need the hexcode, ciph, kx, enc, and export values)
|
|
while read hexcode[nr_ciphers] n ciph[nr_ciphers] sslvers kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do
|
|
nr_ciphers=$nr_ciphers+1
|
|
done < <($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>>$ERRFILE)
|
|
|
|
outln
|
|
pr_headlineln " Testing all $nr_ciphers locally available ciphers against the server, ordered by encryption strength "
|
|
"$HAS_DH_BITS" || pr_warningln " (Your $OPENSSL cannot show DH/ECDH bits)"
|
|
outln
|
|
neat_header
|
|
|
|
# Split ciphers into bundles of size 4**n, starting with an "n" that
|
|
# splits the ciphers into 4 bundles, and then reducing "n" by one in each
|
|
# round. Only test a bundle of 4**n ciphers against the server if it was
|
|
# part of a bundle of 4**(n+1) ciphers that included a cipher supported by
|
|
# the server. Continue until n=0.
|
|
|
|
# Determine bundle size that will result in their being exactly four bundles.
|
|
for (( bundle_size=1; bundle_size < nr_ciphers; bundle_size*=4 )); do
|
|
:
|
|
done
|
|
|
|
# set ciphers_found[1] so that all bundles will be tested in round 0.
|
|
ciphers_found[1]=true
|
|
round_num=0
|
|
|
|
for (( bundle_size/=4; bundle_size>=1; bundle_size/=4 )); do
|
|
# Note that since the number of ciphers isn't a power of 4, the number
|
|
# of bundles may be may be less than 4**(round_num+1), and the final
|
|
# bundle may have fewer than bundle_size ciphers.
|
|
num_bundles=$nr_ciphers/$bundle_size
|
|
mod_check=$nr_ciphers%$bundle_size
|
|
[[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1
|
|
for ((i=0;i<num_bundles;i++)); do
|
|
# parent=index of bundle from previous round that includes this bundle of ciphers
|
|
parent=4**$round_num+$i/4
|
|
# child=index for this bundle of ciphers
|
|
child=4*4**$round_num+$i
|
|
if ${ciphers_found[parent]}; then
|
|
ciphers_to_test=""
|
|
end_of_bundle=$i*$bundle_size+$bundle_size
|
|
[[ $end_of_bundle -gt $nr_ciphers ]] && end_of_bundle=$nr_ciphers
|
|
for ((j=i*bundle_size;j<end_of_bundle;j++)); do
|
|
ciphers_to_test="${ciphers_to_test}:${ciph[j]}"
|
|
done
|
|
ciphers_found[child]=false
|
|
$OPENSSL s_client -cipher "${ciphers_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE </dev/null
|
|
sclient_connect_successful "$?" "$TMPFILE"
|
|
[[ "$?" -eq 0 ]] && ciphers_found[child]=true
|
|
else
|
|
# No need to test, since test of parent demonstrated none of these ciphers work.
|
|
ciphers_found[child]=false
|
|
fi
|
|
|
|
# If this is a "leaf" of the test tree, then print out the results.
|
|
if [[ $bundle_size -eq 1 ]] && ( ${ciphers_found[child]} || "$SHOW_EACH_C"); then
|
|
export=${export2[i]}
|
|
normalize_ciphercode "${hexcode[i]}"
|
|
if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then
|
|
if ${ciphers_found[child]}; then
|
|
dhlen=$(read_dhbits_from_file "$TMPFILE" quiet)
|
|
kx[i]="${kx[i]} $dhlen"
|
|
fi
|
|
fi
|
|
neat_list "$HEXC" "${ciph[i]}" "${kx[i]}" "${enc[i]}"
|
|
available=""
|
|
if "$SHOW_EACH_C"; then
|
|
if ${ciphers_found[child]}; then
|
|
available="available"
|
|
pr_cyan " available"
|
|
else
|
|
out " not a/v"
|
|
available="not a/v"
|
|
fi
|
|
fi
|
|
if "$SHOW_SIGALGO" && ${ciphers_found[child]}; then
|
|
$OPENSSL x509 -noout -text -in $TMPFILE | awk -F':' '/Signature Algorithm/ { print $2 }' | head -1
|
|
else
|
|
outln
|
|
fi
|
|
fileout "cipher_$HEXC" "INFO" "$(neat_list "$HEXC" "${ciph[i]}" "${kx[i]}" "${enc[i]}") $available"
|
|
fi
|
|
done
|
|
round_num=round_num+1
|
|
done
|
|
|
|
outln
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
# test for all ciphers per protocol locally configured (w/o distinguishing whether they are good or bad)
|
|
run_cipher_per_proto() {
|
|
local proto proto_text
|
|
local -i nr_ciphers
|
|
local n sslvers auth mac export
|
|
local -a hexcode ciph kx enc export2
|
|
local -i i j parent child end_of_bundle round_num bundle_size num_bundles mod_check
|
|
local -a ciphers_found
|
|
local dhlen
|
|
local available
|
|
local id
|
|
|
|
pr_headlineln " Testing all locally available ciphers per protocol against the server, ordered by encryption strength "
|
|
! "$HAS_DH_BITS" && pr_warningln " (Your $OPENSSL cannot 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
|
|
|
|
# get a list of all the cipher suites to test (only need the hexcode, ciph, kx, enc, and export values)
|
|
nr_ciphers=0
|
|
while read hexcode[nr_ciphers] n ciph[nr_ciphers] sslvers kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do
|
|
nr_ciphers=$nr_ciphers+1
|
|
done < <($OPENSSL ciphers $proto -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE)
|
|
|
|
# Split ciphers into bundles of size 4**n, starting with the smallest
|
|
# "n" that leaves the ciphers in one bundle, and then reducing "n" by
|
|
# one in each round. Only test a bundle of 4**n ciphers against the
|
|
# server if it was part of a bundle of 4**(n+1) ciphers that included
|
|
# a cipher supported by the server. Continue until n=0.
|
|
|
|
# Determine the smallest bundle size that will result in their being one bundle.
|
|
for(( bundle_size=1; bundle_size < nr_ciphers; bundle_size*=4 )); do
|
|
:
|
|
done
|
|
|
|
# set ciphers_found[1] so that the complete bundle will be tested in round 0.
|
|
ciphers_found[1]=true
|
|
for (( round_num=0; bundle_size>=1; bundle_size/=4 )); do
|
|
# Note that since the number of ciphers isn't a power of 4, the number
|
|
# of bundles may be may be less than 4**(round_num+1), and the final
|
|
# bundle may have fewer than bundle_size ciphers.
|
|
num_bundles=$nr_ciphers/$bundle_size
|
|
mod_check=$nr_ciphers%$bundle_size
|
|
[[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1
|
|
for (( i=0; i<num_bundles; i++ )); do
|
|
# parent=index of bundle from previous round that includes this bundle of ciphers
|
|
parent=4**$round_num+$i/4
|
|
# child=index for this bundle of ciphers
|
|
child=4*4**$round_num+$i
|
|
if ${ciphers_found[parent]}; then
|
|
ciphers_to_test=""
|
|
end_of_bundle=$i*$bundle_size+$bundle_size
|
|
[[ $end_of_bundle -gt $nr_ciphers ]] && end_of_bundle=$nr_ciphers
|
|
for (( j=i*bundle_size; j<end_of_bundle; j++ )); do
|
|
ciphers_to_test="${ciphers_to_test}:${ciph[j]}"
|
|
done
|
|
ciphers_found[child]=false
|
|
$OPENSSL s_client -cipher "${ciphers_to_test:1}" $proto $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE </dev/null
|
|
sclient_connect_successful "$?" "$TMPFILE"
|
|
[[ "$?" -eq 0 ]] && ciphers_found[child]=true
|
|
else
|
|
# No need to test, since test of parent demonstrated none of these ciphers work.
|
|
ciphers_found[child]=false
|
|
fi
|
|
|
|
# If this is a "leaf" of the test tree, then print out the results.
|
|
if [[ $bundle_size -eq 1 ]] && ( ${ciphers_found[child]} || "$SHOW_EACH_C"); then
|
|
export=${export2[i]}
|
|
normalize_ciphercode "${hexcode[i]}"
|
|
if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then
|
|
if ${ciphers_found[child]}; then
|
|
dhlen=$(read_dhbits_from_file "$TMPFILE" quiet)
|
|
kx[i]="${kx[i]} $dhlen"
|
|
fi
|
|
fi
|
|
neat_list "$HEXC" "${ciph[i]}" "${kx[i]}" "${enc[i]}"
|
|
available="available"
|
|
if "$SHOW_EACH_C"; then
|
|
if ${ciphers_found[child]}; then
|
|
pr_cyan " available"
|
|
else
|
|
out " not a/v"
|
|
available="not a/v"
|
|
fi
|
|
fi
|
|
if "$SHOW_SIGALGO" && ${ciphers_found[child]}; then
|
|
$OPENSSL x509 -noout -text -in $TMPFILE | awk -F':' '/Signature Algorithm/ { print $2 }' | head -1
|
|
else
|
|
outln
|
|
fi
|
|
id="cipher$proto"
|
|
id+="_$HEXC"
|
|
fileout "$id" "INFO" "$proto_text $(neat_list "$HEXC" "${ciph[i]}" "${kx[i]}" "${enc[i]}") $available"
|
|
fi
|
|
done
|
|
round_num=round_num+1
|
|
done
|
|
done
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
run_client_simulation() {
|
|
# Runs browser simulations. Browser capabilities gathered from:
|
|
# https://www.ssllabs.com/ssltest/clients.html on 10 jan 2016
|
|
local names=()
|
|
local short=()
|
|
local protos=()
|
|
local ciphers=()
|
|
local tlsvers=()
|
|
local sni=()
|
|
local warning=()
|
|
local i=0
|
|
|
|
# doesn't make sense for other services
|
|
if [[ $SERVICE != "HTTP" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# FIXME: At a certain time we should put the following to an external file
|
|
names+=("Android 2.3.7 ")
|
|
short+=("android_237")
|
|
protos+=("-no_tls1_2 -no_tls1_1 -no_ssl2")
|
|
ciphers+=("RC4-MD5:RC4-SHA:AES128-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:EXP-RC4-MD5:EXP-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA")
|
|
tlsvers+=("-tls1")
|
|
sni+=("")
|
|
warning+=("")
|
|
|
|
names+=("Android 4.0.4 ")
|
|
short+=("android_404")
|
|
protos+=("-no_tls1_2 -no_tls1_1 -no_ssl2")
|
|
ciphers+=("CDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Android 4.1.1 ")
|
|
short+=("android_411")
|
|
protos+=("-no_tls1_2 -no_tls1_1 -no_ssl2")
|
|
ciphers+=("ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Android 4.2.2 ")
|
|
short+=("android_422")
|
|
protos+=("-no_tls1_2 -no_tls1_1 -no_ssl2")
|
|
ciphers+=("ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Android 4.3 ")
|
|
short+=("android_43")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Android 4.4.2 ")
|
|
short+=("android_442")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("CDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tl1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Android 5.0.0 ")
|
|
short+=("android_500")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:AES128-GCM-SHA256:AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Baidu Jan 2015 ")
|
|
short+=("baidu_jan_2015")
|
|
protos+=("-no_tls1_2 -no_tls1_1 -no_ssl2")
|
|
ciphers+=("ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:CAMELLIA256-SHA:AES256-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:ECDH-RSA-RC4-SHA:ECDH-RSA-AES128-SHA:ECDH-ECDSA-RC4-SHA:ECDH-ECDSA-AES128-SHA:SEED-SHA:CAMELLIA128-SHA:RC4-MD5:RC4-SHA:AES128-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("BingPreview Jan 2015 ")
|
|
short+=("bingpreview_jan_2015")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:ECDH-RSA-AES256-GCM-SHA384:ECDH-ECDSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:ECDH-RSA-AES128-GCM-SHA256:ECDH-ECDSA-AES128-GCM-SHA256:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Chrome 47 / OSX ")
|
|
short+=("chrome_47_osx")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:AES128-GCM-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Firefox 31.3.0ESR / Win7 ")
|
|
short+=("firefox_3130esr_win7")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:EDH-RSA-DES-CBC3-SHA:AES128-SHA:CAMELLIA128-SHA:AES256-SHA:CAMELLIA256-SHA:DES-CBC3-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Firefox 42 / OSX ")
|
|
short+=("firefox_42_osx")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("GoogleBot Feb 2015 ")
|
|
short+=("googlebot_feb_2015")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:RC4-SHA:RC4-MD5:AES128-SHA:DES-CBC3-SHA:AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("IE6 / XP ")
|
|
short+=("ie6_xp")
|
|
protos+=("-no_tls1")
|
|
tlsvers+=("")
|
|
ciphers+=("RC4-MD5:RC4-SHA:DES-CBC3-SHA:RC4-MD5:DES-CBC3-MD5:RC2-CBC-MD5:DES-CBC-SHA:DES-CBC-MD5:EXP1024-RC4-SHA:EXP1024-DES-CBC-SHA:EXP-RC4-MD5:EXP-RC2-CBC-MD5:EXP-RC4-MD5:EXP-RC2-CBC-MD5:EDH-DSS-DES-CBC3-SHA:EDH-DSS-DES-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA")
|
|
sni+=("")
|
|
warning+=("")
|
|
|
|
names+=("IE7 / Vista ")
|
|
short+=("ie7_vista")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:EDH-DSS-DES-CBC3-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("IE8 / XP ")
|
|
short+=("ie8_xp")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("RC4-MD5:RC4-SHA:DES-CBC3-SHA:DES-CBC-SHA:EXP1024-RC4-SHA:EXP1024-DES-CBC-SHA:EXP-RC4-MD5:EXP-RC2-CBC-MD5:EDH-DSS-DES-CBC3-SHA:EDH-DSS-DES-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA")
|
|
tlsvers+=("-tls1")
|
|
sni+=("")
|
|
warning+=("")
|
|
|
|
names+=("IE8-10 / Win7 ")
|
|
short+=("ie10_win7")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-SHA:AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:DHE-DSS-AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("IE11 / Win7 ")
|
|
short+=("ie11_win7")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("IE11 / Win8.1 ")
|
|
short+=("ie11_win81")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("IE10 / Win Phone 8.0 ")
|
|
short+=("ie10_winphone_80")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:EDH-DSS-DES-CBC3-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("IE11 / Win Phone 8.1 ")
|
|
short+=("ie10_winphone_81")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("AES128-SHA256:AES128-SHA:AES256-SHA256:AES256-SHA:DES-CBC3-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA256:DHE-DSS-AES256-SHA:EDH-DSS-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("IE11 / Win Phone 8.1 Update")
|
|
short+=("ie10_winphone_81_update")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("IE11 / Win10 ")
|
|
short+=("ie11_win10")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Edge 13 / Win10 ")
|
|
short+=("edge13_win10")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-AES128-SHA:EDH-DSS-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Edge 12 / Win Phone 10 ")
|
|
short+=("edge13_winphone10")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-AES128-SHA:EDH-DSS-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Java 6u45 ")
|
|
short+=("java6u45")
|
|
protos+=("-no_tls1_2 -no_tls1_1")
|
|
ciphers+=("RC4-MD5:RC4-MD5:RC4-SHA:AES128-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA:DES-CBC3-MD5:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DES-CBC-SHA:DES-CBC-MD5:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:EXP-RC4-MD5:EXP-RC4-MD5:EXP-DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA")
|
|
tlsvers+=("-tls1")
|
|
sni+=("")
|
|
warning+=("")
|
|
|
|
names+=("Java 7u25 ")
|
|
short+=("java7u25")
|
|
protos+=("-no_ssl2 -no_tls1_2 -no_tls1_1")
|
|
ciphers+=("ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:AES128-SHA:ECDH-ECDSA-AES128-SHA:ECDH-RSA-AES128-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Java 8u31 ")
|
|
short+=("java8u31")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:AES128-SHA:ECDH-ECDSA-AES128-SHA:ECDH-RSA-AES128-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:ECDH-ECDSA-AES128-GCM-SHA256:ECDH-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("OpenSSL 0.9.8y ")
|
|
short+=("openssl098y")
|
|
protos+=("-no_ssl2 -no_tls1_2 -no_tls1_1")
|
|
ciphers+=("DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DES-CBC3-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:AES128-SHA:IDEA-CBC-SHA:RC4-SHA:RC4-MD5:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("OpenSSL 1.0.1l ")
|
|
short+=("openssl101l")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:ECDH-RSA-AES256-GCM-SHA384:ECDH-ECDSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:ECDH-RSA-AES128-GCM-SHA256:ECDH-ECDSA-AES128-GCM-SHA256:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("OpenSSL 1.0.2e ")
|
|
short+=("openssl102")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DH-DSS-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DH-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:ECDH-RSA-AES256-GCM-SHA384:ECDH-ECDSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DH-DSS-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DH-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DH-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:ECDH-RSA-AES128-GCM-SHA256:ECDH-ECDSA-AES128-GCM-SHA256:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
#warning+=("Tests are based on OpenSSL 1.0.1, therefore ciphers 0xe and 0xb are missing")
|
|
warning+=("")
|
|
|
|
names+=("Safari 5.1.9/ OSX 10.6.8 ")
|
|
short+=("safari519_osx1068")
|
|
protos+=("-no_ssl2 -no_tls1_2 -no_tls1_1")
|
|
ciphers+=("ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-RC4-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-AES256-SHA:ECDH-ECDSA-RC4-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDH-RSA-RC4-SHA:ECDH-RSA-DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5:AES256-SHA:DES-CBC3-SHA:DES-CBC-SHA:EXP-RC4-MD5:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:DHE-DSS-AES128-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:EDH-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC3-SHA:EDH-DSS-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA")
|
|
tlsvers+=("-tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Safari 6 / iOS 6.0.1 ")
|
|
short+=("safari6_ios601")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDH-ECDSA-AES256-SHA384:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES256-SHA384:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-AES256-SHA:ECDH-ECDSA-RC4-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDH-RSA-RC4-SHA:ECDH-RSA-DES-CBC3-SHA:AES256-SHA256:AES128-SHA256:AES128-SHA:RC4-SHA:RC4-MD5:AES256-SHA:DES-CBC3-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:EDH-RSA-DES-CBC3-SHA:ECDHE-ECDSA-NULL-SHA:ECDHE-RSA-NULL-SHA:ECDH-ECDSA-NULL-SHA:ECDH-RSA-NULL-SHA:NULL-SHA256:NULL-SHA:NULL-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Safari 6.0.4/ OS X 10.8.4 ")
|
|
short+=("safari604_osx1084")
|
|
protos+=("-no_ssl2 -no_tls1_2 -no_tls1_1")
|
|
ciphers+=("ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-AES256-SHA:ECDH-ECDSA-RC4-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDH-RSA-RC4-SHA:ECDH-RSA-DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5:AES256-SHA:DES-CBC3-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:EDH-RSA-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Safari 7 / iOS 7.1 ")
|
|
short+=("safari7_ios71")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDH-ECDSA-AES256-SHA384:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES256-SHA384:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES256-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-RC4-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-AES256-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-RC4-SHA:ECDH-RSA-DES-CBC3-SHA:AES256-SHA256:AES128-SHA256:AES128-SHA:RC4-SHA:RC4-MD5:AES256-SHA:DES-CBC3-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:EDH-RSA-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Safari 7 / OS X 10.9 ")
|
|
short+=("safari7_osx109")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDH-ECDSA-AES256-SHA384:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES256-SHA384:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES256-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-RC4-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-AES256-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-RC4-SHA:ECDH-RSA-DES-CBC3-SHA:AES256-SHA256:AES128-SHA256:AES128-SHA:RC4-SHA:RC4-MD5:AES256-SHA:DES-CBC3-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:EDH-RSA-DES-CBC3-SHA")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Safari 8 / iOS 8.4 ")
|
|
short+=("safari8_ios84")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDH-ECDSA-AES256-SHA384:ECDH-ECDSA-AES128-SHA256:ECDH-ECDSA-AES256-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-AES256-SHA384:ECDH-RSA-AES128-SHA256:ECDH-RSA-AES256-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-DES-CBC3-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Safari 8 / OS X 10.10 ")
|
|
short+=("safari8_osx1010")
|
|
protos+=("-no_ssl2")
|
|
ciphers+=("ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDH-ECDSA-AES256-SHA384:ECDH-ECDSA-AES128-SHA256:ECDH-ECDSA-AES256-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-RSA-AES256-SHA384:ECDH-RSA-AES128-SHA256:ECDH-RSA-AES256-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-DES-CBC3-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Safari 9 / iOS 9 ")
|
|
short+=("safari9_ios9")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
names+=("Safari 9 / OS X 10.11 ")
|
|
short+=("safari9_osx1011")
|
|
protos+=("-no_ssl2 -no_ssl3")
|
|
ciphers+=("ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:RC4-SHA:RC4-MD5")
|
|
tlsvers+=("-tls1_2 -tls1_1 -tls1")
|
|
sni+=("$SNI")
|
|
warning+=("")
|
|
|
|
outln
|
|
pr_headlineln " Running browser simulations (experimental) "
|
|
outln
|
|
|
|
debugme outln
|
|
for name in "${short[@]}"; do
|
|
#FIXME: printf formatting would look better, especially if we want a wide option here
|
|
out " ${names[i]} "
|
|
$OPENSSL s_client -cipher ${ciphers[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]} </dev/null >$TMPFILE 2>$ERRFILE
|
|
debugme echo "$OPENSSL s_client -cipher ${ciphers[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]} </dev/null"
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$?
|
|
if [[ $sclient_success -ne 0 ]]; then
|
|
outln "No connection"
|
|
fileout "client_${short[i]}" "INFO" "$(strip_spaces "${names[i]}") client simulation: No connection"
|
|
else
|
|
#FIXME: awk
|
|
proto=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g')
|
|
if [[ "$proto" == TLSv1.2 ]]; then
|
|
# OpenSSL reports TLS1.2 even if the connection is TLS1.1 or TLS1.0. Need to figure out which one it is...
|
|
for tls in ${tlsvers[i]}; do
|
|
$OPENSSL s_client $tls -cipher ${ciphers[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]} </dev/null >$TMPFILE 2>$ERRFILE
|
|
debugme echo "$OPENSSL s_client $tls -cipher ${ciphers[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]} </dev/null"
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$?
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
case "$tls" in
|
|
"-tls1_2")
|
|
break
|
|
;;
|
|
"-tls1_1")
|
|
proto="TLSv1.1"
|
|
break
|
|
;;
|
|
"-tls1")
|
|
proto="TLSv1.0"
|
|
break
|
|
;;
|
|
esac
|
|
fi
|
|
done
|
|
fi
|
|
#FiXME: awk
|
|
cipher=$(grep -wa Cipher $TMPFILE | egrep -avw "New|is" | sed -e 's/ //g' -e 's/^Cipher://')
|
|
outln "$proto $cipher"
|
|
if [[ -n "${warning[i]}" ]]; then
|
|
out " "
|
|
outln "${warning[i]}"
|
|
fi
|
|
fileout "client_${short[i]}" "INFO" \
|
|
"$(strip_spaces "${names[i]}") client simulation: $proto $cipher ${warning[i]}"
|
|
debugme cat $TMPFILE
|
|
fi
|
|
i=$((i+1))
|
|
done
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
# generic function whether $1 is supported by s_client ($2: string to display)
|
|
locally_supported() {
|
|
[[ -n "$2" ]] && out "$2 "
|
|
if $OPENSSL s_client "$1" 2>&1 | grep -aq "unknown option"; then
|
|
local_problem_ln "$OPENSSL doesn't support \"s_client $1\""
|
|
return 7
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
|
|
# the protocol check needs to be revamped. It sucks.
|
|
# 1) we need to have a variable where the results are being stored so that every other test doesn't have to do this again.
|
|
# 2) the code is too old and one can do that way better
|
|
# 3) HAS_SSL3/2 does already exist
|
|
# we should do what's available and faster (openssl vs. sockets). Keep in mind that the socket reply for SSLv2 returns the number # of ciphers!
|
|
#
|
|
# arg1: -ssl2|-ssl3|-tls1
|
|
# arg2: doesn't seem to be used in calling, seems to be a textstring with the protocol though
|
|
run_prototest_openssl() {
|
|
local sni="$SNI"
|
|
local -i ret=0
|
|
|
|
$OPENSSL s_client -state $1 $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $sni >$TMPFILE 2>$ERRFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
ret=$?
|
|
[[ $DEBUG -eq 2 ]] && egrep "error|failure" $ERRFILE | egrep -av "unable to get local|verify error"
|
|
if ! locally_supported "$1" "$2" ; then
|
|
ret=7
|
|
else # we remove SNI for SSLv2 and v3:
|
|
[[ "$1" =~ ssl ]] && sni="" # newer openssl throw an error if SNI is supplied with SSLv2,
|
|
# SSLv3 doesn't have SNI (openssl doesn't complain though -- yet)
|
|
$OPENSSL s_client -state $1 $STARTTLS $BUGS -connect $NODEIP:$PORT $sni >$TMPFILE 2>$ERRFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
ret=$?
|
|
[[ $DEBUG -eq 2 ]] && egrep "error|failure" $ERRFILE | egrep -av "unable to get local|verify error"
|
|
grep -aq "no cipher list" $TMPFILE && ret=5 # <--- important indicator for SSL2 (maybe others, too)
|
|
fi
|
|
tmpfile_handle $FUNCNAME$1.txt
|
|
return $ret
|
|
|
|
# 0: offered
|
|
# 1: not offered
|
|
# 5: protocol ok, but no cipher
|
|
# 7: no local support
|
|
}
|
|
|
|
|
|
# the protocol check needs to be revamped. It sucks, see above
|
|
run_protocols() {
|
|
local using_sockets=true
|
|
local supported_no_ciph1="supported but couldn't detect a cipher (may need debugging)"
|
|
local supported_no_ciph2="supported but couldn't detect a cipher"
|
|
local via=""
|
|
|
|
outln; pr_headline " Testing protocols "
|
|
via="Protocol tested "
|
|
|
|
#FIXME: use PROTOS_OFFERED here
|
|
|
|
if $SSL_NATIVE; then
|
|
using_sockets=false
|
|
pr_headlineln "(via native openssl)"
|
|
via+="via native openssl"
|
|
else
|
|
if [[ -n "$STARTTLS" ]]; then
|
|
pr_headlineln "(via openssl, SSLv2 via sockets) "
|
|
via+="via openssl, SSLv2 via sockets"
|
|
using_sockets=false
|
|
else
|
|
using_sockets=true
|
|
pr_headlineln "(via sockets except TLS 1.2, SPDY+HTTP2) "
|
|
via+="via sockets except for TLS1.2, SPDY+HTTP2"
|
|
fi
|
|
fi
|
|
outln
|
|
|
|
pr_bold " SSLv2 ";
|
|
if ! $SSL_NATIVE; then
|
|
sslv2_sockets #FIXME: messages need to be moved to this higher level
|
|
else
|
|
run_prototest_openssl "-ssl2"
|
|
case $? in
|
|
0)
|
|
pr_svrty_criticalln "offered (NOT ok)"
|
|
fileout "sslv2" "NOT ok" "SSLv2 is offered (NOT ok)"
|
|
;;
|
|
1)
|
|
pr_done_bestln "not offered (OK)"
|
|
fileout "sslv2" "OK" "SSLv2 is not offered (OK)"
|
|
;;
|
|
5)
|
|
pr_svrty_high "CVE-2015-3197: $supported_no_ciph2";
|
|
fileout "sslv2" "WARN" "CVE-2015-3197: SSLv2 is $supported_no_ciph2"
|
|
;;
|
|
7)
|
|
fileout "sslv2" "INFO" "SSLv2 is not tested due to lack of local support"
|
|
;; # no local support
|
|
esac
|
|
fi
|
|
|
|
pr_bold " SSLv3 ";
|
|
if $using_sockets; then
|
|
tls_sockets "00" "$TLS_CIPHER"
|
|
else
|
|
run_prototest_openssl "-ssl3"
|
|
fi
|
|
case $? in
|
|
0)
|
|
pr_svrty_highln "offered (NOT ok)"
|
|
fileout "sslv3" "NOT ok" "SSLv3 is offered (NOT ok)"
|
|
;;
|
|
1)
|
|
pr_done_bestln "not offered (OK)"
|
|
fileout "sslv3" "OK" "SSLv3 is not offered (OK)"
|
|
;;
|
|
2)
|
|
pr_warningln "#FIXME: downgraded. still missing a test case here"
|
|
fileout "sslv3" "WARN" "SSLv3: #FIXME: downgraded. still missing a test case here"
|
|
;;
|
|
5)
|
|
fileout "sslv3" "WARN" "SSLv3 is $supported_no_ciph1"
|
|
pr_svrty_high "$supported_no_ciph2"
|
|
outln "(may need debugging)"
|
|
;; # protocol ok, but no cipher
|
|
7)
|
|
fileout "sslv3" "INFO" "SSLv3 is not tested due to lack of local support"
|
|
;; # no local support
|
|
esac
|
|
|
|
pr_bold " TLS 1 ";
|
|
if $using_sockets; then
|
|
tls_sockets "01" "$TLS_CIPHER"
|
|
else
|
|
run_prototest_openssl "-tls1"
|
|
fi
|
|
case $? in
|
|
0)
|
|
outln "offered"
|
|
fileout "tls1" "INFO" "TLSv1.0 is offered"
|
|
;; # nothing wrong with it -- per se
|
|
1)
|
|
outln "not offered"
|
|
fileout "tls1" "INFO" "TLSv1.0 is not offered"
|
|
;; # neither good or bad
|
|
2)
|
|
pr_svrty_medium "not offered (NOT ok)"
|
|
[[ $DEBUG -eq 1 ]] && out " -- downgraded"
|
|
outln
|
|
fileout "tls1" "NOT ok" "TLSv1.0 is not offered, and downgraded to SSL (NOT ok)"
|
|
;;
|
|
5)
|
|
outln "$supported_no_ciph1" # protocol ok, but no cipher
|
|
fileout "tls1" "WARN" "TLSv1.0 is $supported_no_ciph1"
|
|
;;
|
|
7)
|
|
fileout "tlsv1" "INFO" "TLSv1.0 is not tested due to lack of local support"
|
|
;; # no local support
|
|
esac
|
|
|
|
pr_bold " TLS 1.1 ";
|
|
if $using_sockets; then
|
|
tls_sockets "02" "$TLS_CIPHER"
|
|
else
|
|
run_prototest_openssl "-tls1_1"
|
|
fi
|
|
case $? in
|
|
0)
|
|
outln "offered"
|
|
fileout "tls1_1" "INFO" "TLSv1.1 is offered"
|
|
;; # nothing wrong with it
|
|
1)
|
|
outln "not offered"
|
|
fileout "tls1_1" "INFO" "TLSv1.1 is not offered"
|
|
;; # neither good or bad
|
|
2)
|
|
out "not offered"
|
|
[[ $DEBUG -eq 1 ]] && out " -- downgraded"
|
|
outln
|
|
fileout "tls1_1" "NOT ok" "TLSv1.1 is not offered, and downgraded to a weaker protocol (NOT ok)"
|
|
;;
|
|
5)
|
|
outln "$supported_no_ciph1"
|
|
fileout "tls1_1" "WARN" "TLSv1.1 is $supported_no_ciph1"
|
|
;; # protocol ok, but no cipher
|
|
7)
|
|
fileout "tls1_1" "INFO" "TLSv1.1 is not tested due to lack of local support"
|
|
;; # no local support
|
|
esac
|
|
|
|
pr_bold " TLS 1.2 ";
|
|
if $using_sockets && $EXPERIMENTAL; then #TODO: IIS servers do have a problem here with our handshake
|
|
tls_sockets "03" "$TLS12_CIPHER"
|
|
else
|
|
run_prototest_openssl "-tls1_2"
|
|
fi
|
|
case $? in
|
|
0)
|
|
pr_done_bestln "offered (OK)"
|
|
fileout "tls1_2" "OK" "TLSv1.2 is offered (OK)"
|
|
;; # GCM cipher in TLS 1.2: very good!
|
|
1)
|
|
pr_svrty_mediumln "not offered (NOT ok)"
|
|
fileout "tls1_2" "NOT ok" "TLSv1.2 is not offered (NOT ok)"
|
|
;; # no GCM, penalty
|
|
2)
|
|
pr_svrty_medium "not offered (NOT ok)"
|
|
[[ $DEBUG -eq 1 ]] && out " -- downgraded"
|
|
outln
|
|
fileout "tls1_2" "NOT ok" "TLSv1.2 is not offered and downgraded to a weaker protocol (NOT ok)"
|
|
;;
|
|
5)
|
|
outln "$supported_no_ciph1"
|
|
fileout "tls1_2" "WARN" "TLSv1.2 is $supported_no_ciph1"
|
|
;; # protocol ok, but no cipher
|
|
7)
|
|
fileout "tls1_2" "INFO" "TLSv1.2 is not tested due to lack of local support"
|
|
;; # no local support
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
#TODO: work with fixed lists here
|
|
run_std_cipherlists() {
|
|
outln
|
|
pr_headlineln " Testing ~standard cipher lists "
|
|
outln
|
|
# see ciphers(1ssl)
|
|
std_cipherlists 'NULL:eNULL' " Null Ciphers " 1 "NULL"
|
|
std_cipherlists 'aNULL' " Anonymous NULL Ciphers " 1 "aNULL"
|
|
std_cipherlists 'ADH' " Anonymous DH Ciphers " 1 "ADH"
|
|
std_cipherlists 'EXPORT40' " 40 Bit encryption " 1 "EXPORT40"
|
|
std_cipherlists 'EXPORT56' " 56 Bit encryption " 1 "EXPORT56"
|
|
std_cipherlists 'EXPORT' " Export Ciphers (general) " 1 "EXPORT"
|
|
std_cipherlists 'LOW:!ADH' " Low (<=64 Bit) " 1 "LOW"
|
|
std_cipherlists 'DES:!ADH:!EXPORT:!aNULL' " DES Ciphers " 1 "DES"
|
|
std_cipherlists 'MEDIUM:!NULL:!aNULL:!SSLv2' " Medium grade encryption " 2 "MEDIUM"
|
|
std_cipherlists '3DES:!ADH:!aNULL' " Triple DES Ciphers " 3 "3DES"
|
|
std_cipherlists 'HIGH:!NULL:!aNULL:!DES:!3DES' " High grade encryption " 0 "HIGH"
|
|
outln
|
|
return 0
|
|
}
|
|
|
|
|
|
# arg1: file with input for grepping the bit length for ECDH/DHE
|
|
# arg2: whether to print warning "old fart" or not (empty: no)
|
|
read_dhbits_from_file() {
|
|
local bits what_dh temp
|
|
local add=""
|
|
local old_fart=" (openssl cannot show DH bits)"
|
|
|
|
temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$1") # extract line
|
|
what_dh=$(awk -F',' '{ print $1 }' <<< $temp)
|
|
bits=$(awk -F',' '{ print $3 }' <<< $temp)
|
|
# RH's backport has the DH bits in second arg after comma
|
|
grep -q bits <<< $bits || bits=$(awk -F',' '{ print $2 }' <<< $temp)
|
|
bits=$(tr -d ' bits' <<< $bits)
|
|
|
|
debugme echo ">$HAS_DH_BITS|$what_dh|$bits<"
|
|
|
|
[[ -n "$what_dh" ]] && HAS_DH_BITS=true # FIX 190
|
|
if [[ -z "$what_dh" ]] && ! "$HAS_DH_BITS"; then
|
|
if [[ -z "$2" ]]; then
|
|
pr_warning "$old_fart"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
[[ -n "$bits" ]] && [[ -z "$2" ]] && out ", "
|
|
if [[ $what_dh == "DH" ]] || [[ $what_dh == "EDH" ]]; then
|
|
[[ -z "$2" ]] && add="bit DH"
|
|
if [[ "$bits" -le 600 ]]; then
|
|
pr_svrty_critical "$bits $add"
|
|
elif [[ "$bits" -le 800 ]]; then
|
|
pr_svrty_high "$bits $add"
|
|
elif [[ "$bits" -le 1280 ]]; then
|
|
pr_svrty_medium "$bits $add"
|
|
elif [[ "$bits" -ge 2048 ]]; then
|
|
pr_done_good "$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_svrty_critical "$bits $add"
|
|
elif [[ "$bits" -le 163 ]]; then
|
|
pr_svrty_high "$bits $add"
|
|
elif [[ "$bits" -ge 224 ]]; then
|
|
pr_done_good "$bits $add"
|
|
else
|
|
out "$bits $add"
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
run_server_preference() {
|
|
local cipher1 cipher2
|
|
local default_cipher default_proto
|
|
local remark4default_cipher
|
|
local -a cipher proto
|
|
local p i
|
|
local -i ret=0
|
|
local list_fwd="DES-CBC3-SHA:RC4-MD5:DES-CBC-SHA:RC4-SHA:AES128-SHA:AES128-SHA256:AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:AES256-SHA256"
|
|
# now reversed offline via tac, see https://github.com/thomassa/testssl.sh/commit/7a4106e839b8c3033259d66697893765fc468393 :
|
|
local list_reverse="AES256-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:AES256-SHA:AES128-SHA256:AES128-SHA:RC4-SHA:DES-CBC-SHA:RC4-MD5:DES-CBC3-SHA"
|
|
local has_cipher_order=true
|
|
local isok
|
|
|
|
outln
|
|
pr_headlineln " Testing server preferences "
|
|
outln
|
|
|
|
pr_bold " Has server cipher order? "
|
|
$OPENSSL s_client $STARTTLS -cipher $list_fwd $BUGS -connect $NODEIP:$PORT $PROXY $SNI </dev/null 2>$ERRFILE >$TMPFILE
|
|
if ! sclient_connect_successful $? $TMPFILE && [[ -z "$STARTTLS_PROTOCOL" ]]; then
|
|
pr_warning "no matching cipher in this list found (pls report this): "
|
|
outln "$list_fwd . "
|
|
has_cipher_order=false
|
|
ret=6
|
|
fileout "order_bug" "WARN" "Could not determine server cipher order, no matching cipher in this list found (pls report this): $list_fwd"
|
|
elif [[ -n "$STARTTLS_PROTOCOL" ]]; then
|
|
# now it still could be that we hit this bug: https://github.com/drwetter/testssl.sh/issues/188
|
|
# workaround is to connect with a protocol
|
|
debugme out "(workaround #188) "
|
|
determine_optimal_proto $STARTTLS_PROTOCOL
|
|
$OPENSSL s_client $STARTTLS $STARTTLS_OPTIMAL_PROTO -cipher $list_fwd $BUGS -connect $NODEIP:$PORT $PROXY $SNI </dev/null 2>$ERRFILE >$TMPFILE
|
|
if ! sclient_connect_successful $? $TMPFILE; then
|
|
pr_warning "no matching cipher in this list found (pls report this): "
|
|
outln "$list_fwd . "
|
|
has_cipher_order=false
|
|
ret=6
|
|
fileout "order_bug" "WARN" "Could not determine server cipher order, no matching cipher in this list found (pls report this): $list_fwd"
|
|
fi
|
|
fi
|
|
|
|
if $has_cipher_order; then
|
|
cipher1=$(grep -wa Cipher $TMPFILE | egrep -avw "New|is" | sed -e 's/^ \+Cipher \+://' -e 's/ //g')
|
|
$OPENSSL s_client $STARTTLS $STARTTLS_OPTIMAL_PROTO -cipher $list_reverse $BUGS -connect $NODEIP:$PORT $PROXY $SNI </dev/null 2>>$ERRFILE >$TMPFILE
|
|
# that worked above so no error handling here
|
|
cipher2=$(grep -wa Cipher $TMPFILE | egrep -avw "New|is" | sed -e 's/^ \+Cipher \+://' -e 's/ //g')
|
|
|
|
if [[ "$cipher1" != "$cipher2" ]]; then
|
|
pr_svrty_high "nope (NOT ok)"
|
|
remark4default_cipher=" (limited sense as client will pick)"
|
|
fileout "order" "NOT ok" "Server does NOT set a cipher order (NOT ok)"
|
|
else
|
|
pr_done_best "yes (OK)"
|
|
remark4default_cipher=""
|
|
fileout "order" "OK" "Server sets a cipher order (OK)"
|
|
fi
|
|
debugme out " $cipher1 | $cipher2"
|
|
outln
|
|
|
|
pr_bold " Negotiated protocol "
|
|
$OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI </dev/null 2>>$ERRFILE >$TMPFILE
|
|
if ! sclient_connect_successful $? $TMPFILE; then
|
|
# 2 second try with $OPTIMAL_PROTO especially for intolerant IIS6 servers:
|
|
$OPENSSL s_client $STARTTLS $OPTIMAL_PROTO $BUGS -connect $NODEIP:$PORT $PROXY $SNI </dev/null 2>>$ERRFILE >$TMPFILE
|
|
sclient_connect_successful $? $TMPFILE || pr_warning "Handshake error!"
|
|
fi
|
|
default_proto=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g')
|
|
case "$default_proto" in
|
|
*TLSv1.2)
|
|
pr_done_bestln $default_proto
|
|
fileout "order_proto" "OK" "Default protocol TLS1.2 (OK)"
|
|
;;
|
|
*TLSv1.1)
|
|
pr_done_goodln $default_proto
|
|
fileout "order_proto" "OK" "Default protocol TLS1.1 (OK)"
|
|
;;
|
|
*TLSv1)
|
|
outln $default_proto
|
|
fileout "order_proto" "INFO" "Default protocol TLS1.0"
|
|
;;
|
|
*SSLv2)
|
|
pr_svrty_criticalln $default_proto
|
|
fileout "order_proto" "NOT ok" "Default protocol SSLv2"
|
|
;;
|
|
*SSLv3)
|
|
pr_svrty_criticalln $default_proto
|
|
fileout "order_proto" "NOT ok" "Default protocol SSLv3"
|
|
;;
|
|
"")
|
|
pr_warning "default proto empty"
|
|
if [[ $OSSL_VER == 1.0.2* ]]; then
|
|
outln " (Hint: if IIS6 give OpenSSL 1.0.1 a try)"
|
|
fileout "order_proto" "WARN" "Default protocol empty (Hint: if IIS6 give OpenSSL 1.0.1 a try)"
|
|
else
|
|
fileout "order_proto" "WARN" "Default protocol empty"
|
|
fi
|
|
;;
|
|
*)
|
|
pr_warning "FIXME line $LINENO: $default_proto"
|
|
fileout "order_proto" "WARN" "FIXME line $LINENO: $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_svrty_critical "$default_cipher"
|
|
|
|
fileout "order_cipher" "NOT ok" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (NOT ok) $remark4default_cipher"
|
|
;;
|
|
*RC4*)
|
|
pr_svrty_high "$default_cipher"
|
|
fileout "order_cipher" "NOT ok" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (NOT ok) remark4default_cipher"
|
|
;;
|
|
*CBC*)
|
|
pr_svrty_medium "$default_cipher"
|
|
fileout "order_cipher" "NOT ok" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (NOT ok) $remark4default_cipher"
|
|
;; # FIXME BEAST: We miss some CBC ciphers here, need to work w/ a list
|
|
*GCM*|*CHACHA20*)
|
|
pr_done_best "$default_cipher"
|
|
fileout "order_cipher" "OK" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (OK) $remark4default_cipher"
|
|
;; # best ones
|
|
ECDHE*AES*)
|
|
pr_svrty_minor "$default_cipher"
|
|
fileout "order_cipher" "WARN" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (cbc) $remark4default_cipher"
|
|
;; # it's CBC. --> lucky13
|
|
"")
|
|
pr_warning "default cipher empty" ;
|
|
if [[ $OSSL_VER == 1.0.2* ]]; then
|
|
out " (Hint: if IIS6 give OpenSSL 1.0.1 a try)"
|
|
fileout "order_cipher" "WARN" "Default cipher empty (Hint: if IIS6 give OpenSSL 1.0.1 a try) $remark4default_cipher"
|
|
else
|
|
fileout "order_cipher" "WARN" "Default cipher empty $remark4default_cipher"
|
|
fi
|
|
;;
|
|
*)
|
|
out "$default_cipher"
|
|
fileout "order_cipher" "INFO" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") $remark4default_cipher"
|
|
;;
|
|
esac
|
|
read_dhbits_from_file "$TMPFILE"
|
|
outln "$remark4default_cipher"
|
|
|
|
if [[ ! -z "$remark4default_cipher" ]]; then
|
|
# no cipher order
|
|
pr_bold " Negotiated cipher per proto"; outln " $remark4default_cipher"
|
|
i=1
|
|
for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do
|
|
if [[ $p == ssl2 ]] && ! "$HAS_SSL2"; then
|
|
out " (SSLv2: "; local_problem "$OPENSSL doesn't support \"s_client -ssl2\""; outln ")";
|
|
continue
|
|
fi
|
|
if [[ $p == ssl3 ]] && ! "$HAS_SSL3"; then
|
|
out " (SSLv3: "; local_problem "$OPENSSL doesn't support \"s_client -ssl3\"" ; outln ")";
|
|
continue
|
|
fi
|
|
$OPENSSL s_client $STARTTLS -"$p" $BUGS -connect $NODEIP:$PORT $PROXY $SNI </dev/null 2>>$ERRFILE >$TMPFILE
|
|
if sclient_connect_successful $? $TMPFILE; 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
|
|
|
|
[[ -n "$PROXY" ]] && arg=" SPDY/NPN is"
|
|
[[ -n "$STARTTLS" ]] && arg=" "
|
|
if spdy_pre " $arg" ; then # is NPN/SPDY supported and is this no STARTTLS? / no PROXY
|
|
$OPENSSL s_client -connect $NODEIP:$PORT $BUGS -nextprotoneg "$NPN_PROTOs" </dev/null 2>>$ERRFILE >$TMPFILE
|
|
if sclient_connect_successful $? $TMPFILE; 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
|
|
else
|
|
outln # we miss for STARTTLS 1x LF otherwise
|
|
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
|
|
fileout "order_${proto[i]}_cipher" "INFO" "Default cipher on ${proto[i]}: ${cipher[i]}remark4default_cipher"
|
|
done
|
|
fi
|
|
fi
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
if [[ -z "$remark4default_cipher" ]]; then
|
|
cipher_pref_check
|
|
else
|
|
outln "\n No further cipher order check has been done as order is determined by the client"
|
|
outln
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
cipher_pref_check() {
|
|
local p proto protos npn_protos
|
|
local tested_cipher cipher order
|
|
|
|
pr_bold " Cipher order"
|
|
|
|
for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do
|
|
order=""
|
|
if [[ $p == ssl2 ]] && ! "$HAS_SSL2"; then
|
|
out "\n SSLv2: "; local_problem "$OPENSSL doesn't support \"s_client -ssl2\"";
|
|
continue
|
|
fi
|
|
if [[ $p == ssl3 ]] && ! "$HAS_SSL3"; then
|
|
out "\n SSLv3: "; local_problem "$OPENSSL doesn't support \"s_client -ssl3\"";
|
|
continue
|
|
fi
|
|
$OPENSSL s_client $STARTTLS -"$p" $BUGS -connect $NODEIP:$PORT $PROXY $SNI </dev/null 2>$ERRFILE >$TMPFILE
|
|
if sclient_connect_successful $? $TMPFILE; 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
|
|
order="$cipher"
|
|
while true; do
|
|
$OPENSSL s_client $STARTTLS -"$p" $BUGS -cipher "ALL:$tested_cipher" -connect $NODEIP:$PORT $PROXY $SNI </dev/null 2>>$ERRFILE >$TMPFILE
|
|
sclient_connect_successful $? $TMPFILE || break
|
|
cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g')
|
|
out "$cipher "
|
|
order+=" $cipher"
|
|
tested_cipher="$tested_cipher:-$cipher"
|
|
done
|
|
fi
|
|
[[ -z "$order" ]] || fileout "order_$p" "INFO" "Default cipher order for protocol $p: $order"
|
|
done
|
|
outln
|
|
|
|
if ! spdy_pre " SPDY/NPN: "; then # is NPN/SPDY supported and is this no STARTTLS?
|
|
outln
|
|
else
|
|
npn_protos=$($OPENSSL s_client -host $NODE -port $PORT $BUGS -nextprotoneg \"\" </dev/null 2>>$ERRFILE | grep -a "^Protocols " | sed -e 's/^Protocols.*server: //' -e 's/,//g')
|
|
for p in $npn_protos; do
|
|
order=""
|
|
$OPENSSL s_client -host $NODE -port $PORT $BUGS -nextprotoneg "$p" $PROXY </dev/null 2>>$ERRFILE >$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
|
|
order="$cipher"
|
|
while true; do
|
|
$OPENSSL s_client -cipher "ALL:$tested_cipher" -host $NODE -port $PORT $BUGS -nextprotoneg "$p" $PROXY </dev/null 2>>$ERRFILE >$TMPFILE
|
|
sclient_connect_successful $? $TMPFILE || break
|
|
cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g')
|
|
out "$cipher "
|
|
tested_cipher="$tested_cipher:-$cipher"
|
|
order+=" $cipher"
|
|
done
|
|
outln
|
|
[[ -z $order ]] || fileout "order_spdy_$p" "INFO" "Default cipher order for SPDY protocol $p:order"
|
|
done
|
|
fi
|
|
|
|
outln
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
|
|
# arg1 is proto or empty
|
|
get_host_cert() {
|
|
local tmpvar=$TEMPDIR/$FUNCNAME.txt # change later to $TMPFILE
|
|
|
|
$OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $1 2>/dev/null </dev/null >$tmpvar
|
|
if sclient_connect_successful $? $tmpvar; then
|
|
awk '/-----BEGIN/,/-----END/ { print $0 }' $tmpvar >$HOSTCERT
|
|
return 0
|
|
else
|
|
pr_warningln "could not retrieve host certificate!"
|
|
#fileout "host_certificate" "WARN" "Could not retrieve host certificate!"
|
|
return 1
|
|
fi
|
|
#tmpfile_handle $FUNCNAME.txt
|
|
#return $((${PIPESTATUS[0]} + ${PIPESTATUS[1]}))
|
|
}
|
|
|
|
verify_retcode_helper() {
|
|
local ret=0
|
|
local -i retcode=$1
|
|
|
|
case $retcode in
|
|
# codes from ./doc/apps/verify.pod | verify(1ssl)
|
|
24) out "(certificate unreadable)" ;; # X509_V_ERR_INVALID_CA
|
|
23) out "(certificate revoked)" ;; # X509_V_ERR_CERT_REVOKED
|
|
21) out "(chain incomplete, only 1 cert provided)" ;; # X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE
|
|
20) out "(chain incomplete)" ;; # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
|
|
19) out "(self signed CA in chain)" ;; # X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN
|
|
18) out "(self signed)" ;; # X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
|
|
10) out "(expired)" ;; # X509_V_ERR_CERT_HAS_EXPIRED
|
|
9) out "(not yet valid)" ;; # X509_V_ERR_CERT_NOT_YET_VALID
|
|
2) out "(issuer cert missing)" ;; # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT
|
|
*) ret=1 ; pr_warning " (unknown, pls report) $1" ;;
|
|
esac
|
|
return $ret
|
|
}
|
|
|
|
# arg1: number of certificate if provided >1
|
|
determine_trust() {
|
|
local json_prefix=$1
|
|
local -i i=1
|
|
local -i num_ca_bundles=0
|
|
local bundle_fname=""
|
|
local -a certificate_file verify_retcode trust
|
|
local ok_was=""
|
|
local notok_was=""
|
|
local all_ok=true
|
|
local some_ok=false
|
|
local code
|
|
local ca_bundles="$INSTALL_DIR/etc/*.pem"
|
|
local spaces=" "
|
|
local -i certificates_provided=1+$(grep -c "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem)
|
|
local addtl_warning
|
|
|
|
# If $json_prefix is not empty, then there is more than one certificate
|
|
# and the output should should be indented by two more spaces.
|
|
[[ -n $json_prefix ]] && spaces=" "
|
|
|
|
if [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != "1.0.2" ]] && [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != "1.1.0" ]]; then
|
|
addtl_warning="(Your openssl <= 1.0.2 might be too unreliable to determine trust)"
|
|
fileout "${json_prefix}trust_warn" "WARN" "$addtl_warning"
|
|
fi
|
|
debugme outln
|
|
for bundle_fname in $ca_bundles; do
|
|
certificate_file[i]=$(basename ${bundle_fname//.pem})
|
|
if [[ ! -r $bundle_fname ]]; then
|
|
pr_warningln "\"$bundle_fname\" cannot be found / not readable"
|
|
return 7
|
|
fi
|
|
debugme printf -- " %-12s" "${certificate_file[i]}"
|
|
# set SSL_CERT_DIR to /dev/null so that $OPENSSL verify will only use certificates in $bundle_fname
|
|
(export SSL_CERT_DIR="/dev/null; export SSL_CERT_FILE=/dev/null"
|
|
if [[ $certificates_provided -ge 2 ]]; then
|
|
$OPENSSL verify -purpose sslserver -CAfile "$bundle_fname" -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2
|
|
else
|
|
$OPENSSL verify -purpose sslserver -CAfile "$bundle_fname" $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2
|
|
fi)
|
|
verify_retcode[i]=$(awk '/error [1-9][0-9]? at [0-9]+ depth lookup:/ { if (!found) {print $2; found=1} }' $TEMPDIR/${certificate_file[i]}.1)
|
|
[[ -z "${verify_retcode[i]}" ]] && verify_retcode[i]=0
|
|
if [[ ${verify_retcode[i]} -eq 0 ]]; then
|
|
trust[i]=true
|
|
some_ok=true
|
|
debugme pr_done_good "Ok "
|
|
debugme outln "${verify_retcode[i]}"
|
|
else
|
|
trust[i]=false
|
|
all_ok=false
|
|
debugme pr_svrty_high "not trusted "
|
|
debugme outln "${verify_retcode[i]}"
|
|
fi
|
|
i=$((i + 1))
|
|
done
|
|
num_ca_bundles=$((i - 1))
|
|
debugme out " "
|
|
if $all_ok; then
|
|
# all stores ok
|
|
pr_done_good "Ok "; pr_warning "$addtl_warning"
|
|
# we did to stdout the warning above already, so we could stay here with INFO:
|
|
fileout "${json_prefix}trust" "OK" "All certificate trust checks passed. $addtl_warning"
|
|
else
|
|
# at least one failed
|
|
pr_svrty_critical "NOT ok"
|
|
if ! $some_ok; then
|
|
# all failed (we assume with the same issue), we're displaying the reason
|
|
out " "
|
|
verify_retcode_helper "${verify_retcode[2]}"
|
|
fileout "${json_prefix}trust" "NOT ok" "All certificate trust checks failed: $(verify_retcode_helper "${verify_retcode[2]}"). $addtl_warning"
|
|
else
|
|
# is one ok and the others not ==> display the culprit store
|
|
if $some_ok ; then
|
|
pr_svrty_critical ":"
|
|
for ((i=1;i<=num_ca_bundles;i++)); do
|
|
if ${trust[i]}; then
|
|
ok_was="${certificate_file[i]} $ok_was"
|
|
else
|
|
#code="$(verify_retcode_helper ${verify_retcode[i]})"
|
|
#notok_was="${certificate_file[i]} $notok_was"
|
|
pr_svrty_high " ${certificate_file[i]} "
|
|
verify_retcode_helper "${verify_retcode[i]}"
|
|
notok_was="${certificate_file[i]} $(verify_retcode_helper "${verify_retcode[i]}") $notok_was"
|
|
fi
|
|
done
|
|
#pr_svrty_high "$notok_was "
|
|
#outln "$code"
|
|
outln
|
|
# lf + green ones
|
|
[[ "$DEBUG" -eq 0 ]] && out "$spaces"
|
|
pr_done_good "OK: $ok_was"
|
|
fi
|
|
fileout "${json_prefix}trust" "NOT ok" "Some certificate trust checks failed : OK : $ok_was NOT ok: $notok_was $addtl_warning"
|
|
fi
|
|
[[ -n "$addtl_warning" ]] && out "\n$spaces" && pr_warning "$addtl_warning"
|
|
fi
|
|
outln
|
|
return 0
|
|
}
|
|
|
|
# not handled: Root CA supplied (contains anchor)
|
|
|
|
tls_time() {
|
|
local now difftime
|
|
local spaces=" "
|
|
|
|
tls_sockets "01" "$TLS_CIPHER" # try first TLS 1.0 (most frequently used protocol)
|
|
[[ -z "$TLS_TIME" ]] && tls_sockets "03" "$TLS12_CIPHER" # TLS 1.2
|
|
[[ -z "$TLS_TIME" ]] && tls_sockets "02" "$TLS_CIPHER" # TLS 1.1
|
|
[[ -z "$TLS_TIME" ]] && tls_sockets "00" "$TLS_CIPHER" # SSL 3
|
|
|
|
pr_bold " TLS clock skew" ; out "$spaces"
|
|
if [[ -n "$TLS_TIME" ]]; then # nothing returned a time!
|
|
difftime=$(($TLS_TIME - $TLS_NOW)) # TLS_NOW is being set in tls_sockets()
|
|
if [[ "${#difftime}" -gt 5 ]]; then
|
|
# openssl >= 1.0.1f fills this field with random values! --> good for possible fingerprint
|
|
out "random values, no fingerprinting possible "
|
|
fileout "tls_time" "INFO" "Your TLS time seems to be filled with random values to prevent fingerprinting"
|
|
else
|
|
[[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
|
|
out "$difftime"; out " sec from localtime";
|
|
fileout "tls_time" "INFO" "Your TLS time is skewed from your localtime by $difftime seconds"
|
|
fi
|
|
debugme out "$TLS_TIME"
|
|
outln
|
|
else
|
|
pr_warning "SSLv3 through TLS 1.2 didn't return a timestamp"
|
|
fileout "tls_time" "INFO" "No TLS timestamp returned by SSLv3 through TLSv1.2"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# core function determining whether handshake succeded or not
|
|
sclient_connect_successful() {
|
|
[[ $1 -eq 0 ]] && return 0
|
|
[[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]] && return 0
|
|
# second check saved like
|
|
# fgrep 'Cipher is (NONE)' "$2" &> /dev/null && return 1
|
|
# what's left now is: master key empty and Session-ID not empty ==> probably client based auth with x509 certificate
|
|
return 1
|
|
}
|
|
|
|
# arg1 is "-cipher <OpenSSL cipher>" or empty
|
|
determine_tls_extensions() {
|
|
local proto
|
|
local success
|
|
local alpn=""
|
|
local savedir
|
|
local nrsaved
|
|
|
|
"$HAS_ALPN" && alpn="h2-14,h2-15,h2"
|
|
|
|
# throwing 1st every cipher/protocol at the server to know what works
|
|
success=7
|
|
for proto in tls1_2 tls1_1 tls1 ssl3; do
|
|
# alpn: echo | openssl s_client -connect google.com:443 -tlsextdebug -alpn h2-14 -servername google.com <-- suport needs to be checked b4 -- see also: ssl/t1_trce.c
|
|
$OPENSSL s_client $STARTTLS $BUGS $1 -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug -nextprotoneg $alpn -status </dev/null 2>$ERRFILE >$TMPFILE
|
|
sclient_connect_successful $? $TMPFILE && success=0 && break
|
|
done # this loop is needed for IIS6 and others which have a handshake size limitations
|
|
if [[ $success -eq 7 ]]; then
|
|
# "-status" above doesn't work for GOST only servers, so we do another test without it and see whether that works then:
|
|
$OPENSSL s_client $STARTTLS $BUGS $1 -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug </dev/null 2>>$ERRFILE >$TMPFILE
|
|
if ! sclient_connect_successful $? $TMPFILE; then
|
|
if [ -z "$1" ]; then
|
|
pr_warningln "Strange, no SSL/TLS protocol seems to be supported (error around line $((LINENO - 6)))"
|
|
fi
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 7 # this is ugly, I know
|
|
else
|
|
GOST_STATUS_PROBLEM=true
|
|
fi
|
|
fi
|
|
#TLS_EXTENSIONS=$(awk -F'"' '/TLS server extension / { printf "\""$2"\" " }' $TMPFILE)
|
|
#
|
|
# this is not beautiful (grep+sed)
|
|
# but maybe we should just get the ids and do a private matching, according to
|
|
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml <-- ALPN is missing
|
|
TLS_EXTENSIONS=$(grep -a 'TLS server extension ' $TMPFILE | sed -e 's/TLS server extension //g' -e 's/\" (id=/\/#/g' -e 's/,.*$/,/g' -e 's/),$/\"/g')
|
|
TLS_EXTENSIONS=$(echo $TLS_EXTENSIONS) # into one line
|
|
|
|
# Place the server's certificate in $HOSTCERT and any intermediate
|
|
# certificates that were provided in $TEMPDIR/intermediatecerts.pem
|
|
savedir=$(pwd); cd $TEMPDIR
|
|
# http://backreference.org/2010/05/09/ocsp-verification-with-openssl/
|
|
awk -v n=-1 '/Certificate chain/ {start=1}
|
|
/-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} }
|
|
inc { print > ("level" n ".crt") }
|
|
/---END CERTIFICATE-----/{ inc=0 }' $TMPFILE
|
|
nrsaved=$(count_words "$(echo level?.crt 2>/dev/null)")
|
|
if [[ $nrsaved -eq 0 ]]; then
|
|
success=1
|
|
else
|
|
success=0
|
|
mv level0.crt $HOSTCERT
|
|
if [[ $nrsaved -eq 1 ]]; then
|
|
echo "" > $TEMPDIR/intermediatecerts.pem
|
|
else
|
|
cat level?.crt > $TEMPDIR/intermediatecerts.pem
|
|
rm level?.crt
|
|
fi
|
|
fi
|
|
cd "$savedir"
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $success
|
|
}
|
|
|
|
# arg1: path to certificate
|
|
# returns CN
|
|
get_cn_from_cert() {
|
|
local subject
|
|
|
|
# attention! openssl 1.0.2 doesn't properly handle online output from certifcates from trustwave.com/github.com
|
|
#FIXME: use -nameopt oid for robustness
|
|
|
|
# for e.g. russian sites -esc_msb,utf8 works in an UTF8 terminal -- any way to check platform indepedent?
|
|
# see x509(1ssl):
|
|
subject="$($OPENSSL x509 -in $1 -noout -subject -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)"
|
|
echo "$(awk -F'=' '/CN=/ { print $2 }' <<< "$subject")"
|
|
return $?
|
|
}
|
|
|
|
|
|
certificate_info() {
|
|
local proto
|
|
local -i certificate_number=$1
|
|
local -i number_of_certificates=$2
|
|
local cipher=$3
|
|
local cert_keysize=$4
|
|
local ocsp_response=$5
|
|
local ocsp_response_status=$6
|
|
local cert_sig_algo cert_key_algo
|
|
local expire days2expire secs2warn ocsp_uri crl startdate enddate issuer_C issuer_O issuer sans san cn cn_nosni
|
|
local cert_fingerprint_sha1 cert_fingerprint_sha2 cert_fingerprint_serial
|
|
local policy_oid
|
|
local spaces=""
|
|
local wildcard=false
|
|
local -i certificates_provided
|
|
local cnfinding
|
|
local cnok="OK"
|
|
local expfinding expok="OK"
|
|
local json_prefix="" # string to place at beginng of JSON IDs when there is more than one certificate
|
|
local indent=""
|
|
|
|
if [[ $number_of_certificates -gt 1 ]]; then
|
|
[[ $certificate_number -eq 1 ]] && outln
|
|
indent=" "
|
|
out "$indent"
|
|
pr_headlineln "Server Certificate #$certificate_number"
|
|
json_prefix="Server Certificate #$certificate_number "
|
|
spaces=" "
|
|
else
|
|
spaces=" "
|
|
fi
|
|
|
|
cert_sig_algo=$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep "Signature Algorithm" | sed 's/^.*Signature Algorithm: //' | sort -u )
|
|
cert_key_algo=$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | awk -F':' '/Public Key Algorithm:/ { print $2 }' | sort -u )
|
|
|
|
out "$indent" ; pr_bold " Signature Algorithm "
|
|
case $cert_sig_algo in
|
|
sha1WithRSAEncryption)
|
|
pr_svrty_mediumln "SHA1 with RSA"
|
|
fileout "${json_prefix}algorithm" "WARN" "Signature Algorithm: SHA1 with RSA (warning)"
|
|
;;
|
|
sha256WithRSAEncryption)
|
|
pr_done_goodln "SHA256 with RSA"
|
|
fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: SHA256 with RSA (OK)"
|
|
;;
|
|
sha384WithRSAEncryption)
|
|
pr_done_goodln "SHA384 with RSA"
|
|
fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: SHA384 with RSA (OK)"
|
|
;;
|
|
sha512WithRSAEncryption)
|
|
pr_done_goodln "SHA512 with RSA"
|
|
fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: SHA512 with RSA (OK)"
|
|
;;
|
|
ecdsa-with-SHA256)
|
|
pr_done_goodln "ECDSA with SHA256"
|
|
fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: ECDSA with SHA256 (OK)"
|
|
;;
|
|
md5*)
|
|
pr_svrty_criticalln "MD5"
|
|
fileout "${json_prefix}algorithm" "NOT ok" "Signature Algorithm: MD5 (NOT ok)"
|
|
;;
|
|
*)
|
|
out "$cert_sig_algo ("
|
|
pr_warning "Unknown"
|
|
outln ")"
|
|
fileout "${json_prefix}algorithm" "WARN" "Signature Algorithm: $sign_algo"
|
|
;;
|
|
esac
|
|
# old, but interesting: https://blog.hboeck.de/archives/754-Playing-with-the-EFF-SSL-Observatory.html
|
|
|
|
out "$indent"; pr_bold " Server key size "
|
|
if [[ -z "$cert_keysize" ]]; then
|
|
outln "(couldn't determine)"
|
|
fileout "${json_prefix}key_size" "WARN" "Server keys size cannot be determined"
|
|
else
|
|
# https://tools.ietf.org/html/rfc4492, http://www.keylength.com/en/compare/
|
|
# http://infoscience.epfl.ch/record/164526/files/NPDF-22.pdf
|
|
# see http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf
|
|
# Table 2 @ chapter 5.6.1 (~ p64)
|
|
if [[ $cert_sig_algo =~ ecdsa ]] || [[ $cert_key_algo =~ ecPublicKey ]]; then
|
|
if [[ "$cert_keysize" -le 110 ]]; then # a guess
|
|
pr_svrty_critical "$cert_keysize"
|
|
fileout "${json_prefix}key_size" "NOT ok" "Server keys $cert_keysize EC bits (NOT ok)"
|
|
elif [[ "$cert_keysize" -le 123 ]]; then # a guess
|
|
pr_svrty_high "$cert_keysize"
|
|
fileout "${json_prefix}key_size" "NOT ok" "Server keys $cert_keysize EC bits (NOT ok)"
|
|
elif [[ "$cert_keysize" -le 163 ]]; then
|
|
pr_svrty_medium "$cert_keysize"
|
|
fileout "${json_prefix}key_size" "NOT ok" "Server keys $cert_keysize EC bits (NOT ok)"
|
|
elif [[ "$cert_keysize" -le 224 ]]; then
|
|
out "$cert_keysize"
|
|
fileout "${json_prefix}key_size" "INFO" "Server keys $cert_keysize EC bits"
|
|
elif [[ "$cert_keysize" -le 533 ]]; then
|
|
pr_done_good "$cert_keysize"
|
|
fileout "${json_prefix}key_size" "OK" "Server keys $cert_keysize EC bits (OK)"
|
|
else
|
|
out "keysize: $cert_keysize (not expected, FIXME)"
|
|
fileout "${json_prefix}key_size" "WARN" "Server keys $cert_keysize bits (not expected)"
|
|
fi
|
|
outln " bits"
|
|
elif [[ $cert_sig_algo = *RSA* ]]; then
|
|
if [[ "$cert_keysize" -le 512 ]]; then
|
|
pr_svrty_critical "$cert_keysize"
|
|
outln " bits"
|
|
fileout "${json_prefix}key_size" "NOT ok" "Server keys $cert_keysize bits (NOT ok)"
|
|
elif [[ "$cert_keysize" -le 768 ]]; then
|
|
pr_svrty_high "$cert_keysize"
|
|
outln " bits"
|
|
fileout "${json_prefix}key_size" "NOT ok" "Server keys $cert_keysize bits (NOT ok)"
|
|
elif [[ "$cert_keysize" -le 1024 ]]; then
|
|
pr_svrty_medium "$cert_keysize"
|
|
outln " bits"
|
|
fileout "${json_prefix}key_size" "NOT ok" "Server keys $cert_keysize bits (NOT ok)"
|
|
elif [[ "$cert_keysize" -le 2048 ]]; then
|
|
outln "$cert_keysize bits"
|
|
fileout "${json_prefix}key_size" "INFO" "Server keys $cert_keysize bits"
|
|
elif [[ "$cert_keysize" -le 4096 ]]; then
|
|
pr_done_good "$cert_keysize"
|
|
fileout "${json_prefix}key_size" "OK" "Server keys $cert_keysize bits (OK)"
|
|
outln " bits"
|
|
else
|
|
pr_magenta "weird key size: $cert_keysize bits"; outln " (could cause compatibility problems)"
|
|
fileout "${json_prefix}key_size" "WARN" "Server keys $cert_keysize bits (Odd)"
|
|
fi
|
|
else
|
|
out "$cert_keysize bits ("
|
|
pr_warning "can't tell whether $cert_keysize bits is good or not"
|
|
outln ")"
|
|
fileout "${json_prefix}key_size" "WARN" "Server keys $cert_keysize bits (unknown signature algorithm)"
|
|
fi
|
|
fi
|
|
|
|
out "$indent"; pr_bold " Fingerprint / Serial "
|
|
cert_fingerprint_sha1="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g')"
|
|
cert_fingerprint_serial="$($OPENSSL x509 -noout -in $HOSTCERT -serial 2>>$ERRFILE | sed 's/serial=//')"
|
|
cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g' )"
|
|
outln "$cert_fingerprint_sha1 / $cert_fingerprint_serial"
|
|
outln "$spaces$cert_fingerprint_sha2"
|
|
fileout "${json_prefix}fingerprint" "INFO" "Fingerprints / Serial: $cert_fingerprint_sha1 / $cert_fingerprint_serial, $cert_fingerprint_sha2"
|
|
[[ -z $CERT_FINGERPRINT_SHA2 ]] && \
|
|
CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2" ||
|
|
CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2 $CERT_FINGERPRINT_SHA2"
|
|
|
|
out "$indent"; pr_bold " Common Name (CN) "
|
|
cnfinding="Common Name (CN) : "
|
|
cn="$(get_cn_from_cert $HOSTCERT)"
|
|
if [[ -n "$cn" ]]; then
|
|
pr_dquoted "$cn"
|
|
cnfinding="$cn"
|
|
if echo -n "$cn" | grep -q '^*.' ; then
|
|
out " (wildcard certificate"
|
|
cnfinding+="(wildcard certificate "
|
|
if [[ "$cn" == "*.$(echo -n "$cn" | sed 's/^\*.//')" ]]; then
|
|
out " match)"
|
|
cnfinding+=" match)"
|
|
wildcard=true
|
|
else
|
|
cnfinding+=" NO match)"
|
|
cnok="INFO"
|
|
#FIXME: we need to test also the SANs as they can contain a wild card (google.de .e.g) ==> 2.7dev
|
|
fi
|
|
fi
|
|
else
|
|
cn="no CN field in subject"
|
|
pr_warning "($cn)"
|
|
cnfinding="$cn"
|
|
cnok="INFO"
|
|
fi
|
|
|
|
# no cipher suites specified here. We just want the default vhost subject
|
|
$OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $OPTIMAL_PROTO 2>>$ERRFILE </dev/null | awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT.nosni
|
|
cn_nosni="$(get_cn_from_cert "$HOSTCERT.nosni")"
|
|
[[ -z "$cn_nosni" ]] && cn_nosni="no CN field in subject"
|
|
|
|
#FIXME: check for SSLv3/v2 and look whether it goes to a different CN (probably not polite)
|
|
|
|
debugme out "\"$NODE\" | \"$cn\" | \"$cn_nosni\""
|
|
if [[ "$cn_nosni" == "$cn" ]]; then
|
|
outln " (works w/o SNI)"
|
|
cnfinding+=" (works w/o SNI)"
|
|
elif [[ $NODE == "$cn_nosni" ]]; then
|
|
if [[ $SERVICE == "HTTP" ]] || $CLIENT_AUTH ; then
|
|
outln " (works w/o SNI)"
|
|
cnfinding+=" (works w/o SNI)"
|
|
else
|
|
outln " (matches certificate directly)"
|
|
cnfinding+=" (matches certificate directly)"
|
|
# for services != HTTP it depends on the protocol, server and client but it is not named "SNI"
|
|
fi
|
|
else
|
|
if [[ $SERVICE != "HTTP" ]]; then
|
|
outln
|
|
cnfinding+="\n"
|
|
#pr_svrty_mediumln " (non-SNI clients don't match CN but for non-HTTP services it might be ok)"
|
|
#FIXME: this is irritating and needs to be redone. Then also the wildcard match needs to be tested against "$cn_nosni"
|
|
elif [[ -z "$cn_nosni" ]]; then
|
|
out " (request w/o SNI didn't succeed";
|
|
cnfinding+=" (request w/o SNI didn't succeed"
|
|
if [[ $cert_sig_algo =~ ecdsa ]]; then
|
|
out ", usual for EC certificates"
|
|
cnfinding+=", usual for EC certificates"
|
|
fi
|
|
outln ")"
|
|
cnfinding+=")"
|
|
elif [[ "$cn_nosni" == *"no CN field"* ]]; then
|
|
outln ", (request w/o SNI: $cn_nosni)"
|
|
cnfinding+=", (request w/o SNI: $cn_nosni)"
|
|
else
|
|
out " (CN in response to request w/o SNI: "; pr_dquoted "$cn_nosni"; outln ")"
|
|
cnfinding+=" (CN in response to request w/o SNI: \"$cn_nosni\")"
|
|
fi
|
|
fi
|
|
fileout "${json_prefix}cn" "$cnok" "$cnfinding"
|
|
|
|
sans=$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A3 "Subject Alternative Name" | grep "DNS:" | \
|
|
sed -e 's/DNS://g' -e 's/ //g' -e 's/,/ /g' -e 's/othername:<unsupported>//g')
|
|
# ^^^ CACert
|
|
out "$indent"; pr_bold " subjectAltName (SAN) "
|
|
if [[ -n "$sans" ]]; then
|
|
for san in $sans; do
|
|
pr_dquoted "$san"
|
|
out " "
|
|
done
|
|
fileout "${json_prefix}san" "INFO" "subjectAltName (SAN) : $sans"
|
|
else
|
|
out "-- "
|
|
fileout "${json_prefix}san" "INFO" "subjectAltName (SAN) : --"
|
|
fi
|
|
outln
|
|
out "$indent"; pr_bold " Issuer "
|
|
#FIXME: oid would be better maybe (see above)
|
|
issuer="$($OPENSSL x509 -in $HOSTCERT -noout -issuer -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)"
|
|
issuer_CN="$(awk -F'=' '/CN=/ { print $2 }' <<< "$issuer")"
|
|
issuer_O="$(awk -F'=' '/O=/ { print $2 }' <<< "$issuer")"
|
|
issuer_C="$(awk -F'=' '/C=/ { print $2 }' <<< "$issuer")"
|
|
|
|
if [[ "$issuer_O" == "issuer=" ]] || [[ "$issuer_O" == "issuer= " ]] || [[ "$issuer_CN" == "$CN" ]]; then
|
|
pr_svrty_criticalln "self-signed (NOT ok)"
|
|
fileout "${json_prefix}issuer" "NOT ok" "Issuer: selfsigned (NOT ok)"
|
|
else
|
|
pr_dquoted "$issuer_CN"
|
|
out " ("
|
|
pr_dquoted "$issuer_O"
|
|
if [[ -n "$issuer_C" ]]; then
|
|
out " from "
|
|
pr_dquoted "$issuer_C"
|
|
fileout "${json_prefix}issuer" "INFO" "Issuer: \"$issuer\" ( \"$issuer_O\" from \"$issuer_C\")"
|
|
else
|
|
fileout "${json_prefix}issuer" "INFO" "Issuer: \"$issuer\" ( \"$issuer_O\" )"
|
|
fi
|
|
outln ")"
|
|
fi
|
|
|
|
# http://events.ccc.de/congress/2010/Fahrplan/attachments/1777_is-the-SSLiverse-a-safe-place.pdf, see page 40pp
|
|
out "$indent"; pr_bold " EV cert"; out " (experimental) "
|
|
# only the first one, seldom we have two
|
|
policy_oid=$($OPENSSL x509 -in $HOSTCERT -text 2>>$ERRFILE | awk '/ .Policy: / { print $2 }' | awk 'NR < 2')
|
|
if echo "$issuer" | egrep -q 'Extended Validation|Extended Validated|EV SSL|EV CA' || \
|
|
[[ 2.16.840.1.114028.10.1.2 == "$policy_oid" ]] || \
|
|
[[ 2.16.840.1.114412.1.3.0.2 == "$policy_oid" ]] || \
|
|
[[ 2.16.840.1.114412.2.1 == "$policy_oid" ]] || \
|
|
[[ 2.16.578.1.26.1.3.3 == "$policy_oid" ]] || \
|
|
[[ 1.3.6.1.4.1.17326.10.14.2.1.2 == "$policy_oid" ]] || \
|
|
[[ 1.3.6.1.4.1.17326.10.8.12.1.2 == "$policy_oid" ]] || \
|
|
[[ 1.3.6.1.4.1.13177.10.1.3.10 == "$policy_oid" ]] ; then
|
|
out "yes "
|
|
fileout "${json_prefix}ev" "OK" "Extended Validation (EV) (experimental) : yes"
|
|
else
|
|
out "no "
|
|
fileout "${json_prefix}ev" "INFO" "Extended Validation (EV) (experimental) : no"
|
|
fi
|
|
debugme echo "($(newline_to_spaces "$policy_oid"))"
|
|
outln
|
|
#TODO: use browser OIDs:
|
|
# https://mxr.mozilla.org/mozilla-central/source/security/certverifier/ExtendedValidation.cpp
|
|
# http://src.chromium.org/chrome/trunk/src/net/cert/ev_root_ca_metadata.cc
|
|
# https://certs.opera.com/03/ev-oids.xml
|
|
|
|
out "$indent"; pr_bold " Certificate Expiration "
|
|
|
|
if "$HAS_GNUDATE"; then
|
|
enddate=$(date --date="$($OPENSSL x509 -in $HOSTCERT -noout -enddate 2>>$ERRFILE | cut -d= -f 2)" +"%F %H:%M %z")
|
|
startdate=$(date --date="$($OPENSSL x509 -in $HOSTCERT -noout -startdate 2>>$ERRFILE | cut -d= -f 2)" +"%F %H:%M")
|
|
days2expire=$(( $(date --date="$enddate" "+%s") - $(date "+%s") )) # in seconds
|
|
else
|
|
enddate=$(LC_ALL=C date -j -f "%b %d %T %Y %Z" "$($OPENSSL x509 -in $HOSTCERT -noout -enddate 2>>$ERRFILE | cut -d= -f 2)" +"%F %H:%M %z")
|
|
startdate=$(LC_ALL=C date -j -f "%b %d %T %Y %Z" "$($OPENSSL x509 -in $HOSTCERT -noout -startdate 2>>$ERRFILE | cut -d= -f 2)" +"%F %H:%M")
|
|
LC_ALL=C days2expire=$(( $(date -j -f "%F %H:%M %z" "$enddate" "+%s") - $(date "+%s") )) # in seconds
|
|
fi
|
|
days2expire=$((days2expire / 3600 / 24 ))
|
|
|
|
expire=$($OPENSSL x509 -in $HOSTCERT -checkend 1 2>>$ERRFILE)
|
|
if ! echo $expire | grep -qw not; then
|
|
pr_svrty_critical "expired!"
|
|
expfinding="expired!"
|
|
expok="NOT ok"
|
|
else
|
|
secs2warn=$((24 * 60 * 60 * DAYS2WARN2)) # low threshold first
|
|
expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE)
|
|
if echo "$expire" | grep -qw not; then
|
|
secs2warn=$((24 * 60 * 60 * DAYS2WARN1))
|
|
expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE)
|
|
if echo "$expire" | grep -qw not; then
|
|
pr_done_good "$days2expire >= $DAYS2WARN1 days"
|
|
expfinding+="$days2expire >= $DAYS2WARN1 days"
|
|
else
|
|
pr_svrty_medium "expires < $DAYS2WARN1 days ($days2expire)"
|
|
expfinding+="expires < $DAYS2WARN1 days ($days2expire)"
|
|
expok="WARN"
|
|
fi
|
|
else
|
|
pr_svrty_high "expires < $DAYS2WARN2 days ($days2expire) !"
|
|
expfinding+="expires < $DAYS2WARN2 days ($days2expire) !"
|
|
expok="NOT ok"
|
|
fi
|
|
fi
|
|
outln " ($startdate --> $enddate)"
|
|
fileout "${json_prefix}expiration" "$expok" "Certificate Expiration : $expfinding ($startdate --> $enddate)"
|
|
|
|
certificates_provided=1+$(grep -c "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem)
|
|
out "$indent"; pr_bold " # of certificates provided"; outln " $certificates_provided"
|
|
fileout "${json_prefix}certcount" "INFO" "# of certificates provided : $certificates_provided"
|
|
|
|
|
|
out "$indent"; pr_bold " Chain of trust"; out " (experim.) "
|
|
determine_trust "$json_prefix" # Also handles fileout
|
|
|
|
out "$indent"; pr_bold " Certificate Revocation List "
|
|
crl="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A 4 "CRL Distribution" | grep URI | sed 's/^.*URI://')"
|
|
if [[ -z "$crl" ]]; then
|
|
pr_svrty_highln "--"
|
|
fileout "${json_prefix}crl" "NOT ok" "No CRL provided (NOT ok)"
|
|
elif grep -q http <<< "$crl"; then
|
|
if [[ $(count_lines "$crl") -eq 1 ]]; then
|
|
outln "$crl"
|
|
fileout "${json_prefix}crl" "INFO" "Certificate Revocation List : $crl"
|
|
else # more than one CRL
|
|
out_row_aligned "$crl" "$spaces"
|
|
fileout "${json_prefix}crl" "INFO" "Certificate Revocation List : $crl"
|
|
fi
|
|
else
|
|
pr_warningln "no parsable output \"$crl\", pls report"
|
|
fileout "${json_prefix}crl" "WARN" "Certificate Revocation List : no parsable output \"$crl\", pls report"
|
|
fi
|
|
|
|
out "$indent"; pr_bold " OCSP URI "
|
|
ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri 2>>$ERRFILE)
|
|
if [[ -z "$ocsp_uri" ]]; then
|
|
pr_svrty_highln "--"
|
|
fileout "${json_prefix}ocsp_uri" "NOT ok" "OCSP URI : -- (NOT ok)"
|
|
else
|
|
outln "$ocsp_uri"
|
|
fileout "${json_prefix}ocsp_uri" "INFO" "OCSP URI : $ocsp_uri"
|
|
fi
|
|
|
|
out "$indent"; pr_bold " OCSP stapling "
|
|
if grep -a "OCSP response" <<<"$ocsp_response" | grep -q "no response sent" ; then
|
|
pr_svrty_minor "--"
|
|
fileout "${json_prefix}ocsp_stapling" "INFO" "OCSP stapling : not offered"
|
|
else
|
|
if grep -a "OCSP Response Status" <<<"$ocsp_response_status" | grep -q successful; then
|
|
pr_done_good "offered"
|
|
fileout "${json_prefix}ocsp_stapling" "OK" "OCSP stapling : offered"
|
|
else
|
|
if $GOST_STATUS_PROBLEM; then
|
|
outln "(GOST servers make problems here, sorry)"
|
|
fileout "${json_prefix}ocsp_stapling" "OK" "OCSP stapling : (GOST servers make problems here, sorry)"
|
|
ret=0
|
|
else
|
|
out "(response status unknown)"
|
|
fileout "${json_prefix}ocsp_stapling" "OK" "OCSP stapling : not sure what's going on here, debug: $ocsp_response"
|
|
debugme grep -a -A20 -B2 "OCSP response" <<<"$ocsp_response"
|
|
ret=2
|
|
fi
|
|
fi
|
|
fi
|
|
outln "\n"
|
|
|
|
return $ret
|
|
}
|
|
# FIXME: revoked, see checkcert.sh
|
|
# FIXME: Trust (only CN)
|
|
|
|
|
|
|
|
|
|
run_server_defaults() {
|
|
local ciph match_found newhostcert
|
|
local sessticket_str=""
|
|
local lifetime unit
|
|
local line
|
|
local -i i n
|
|
local all_tls_extensions=""
|
|
local -i certs_found=0
|
|
local -a previous_hostcert previous_intermediates keysize cipher ocsp_response ocsp_response_status
|
|
local -a ciphers_to_test
|
|
|
|
# Try each public key type once:
|
|
# ciphers_to_test[1]: cipher suites using certificates with RSA signature public keys
|
|
# ciphers_to_test[2]: cipher suites using certificates with RSA key encipherment public keys
|
|
# ciphers_to_test[3]: cipher suites using certificates with DSA signature public keys
|
|
# ciphers_to_test[4]: cipher suites using certificates with DH key agreement public keys
|
|
# ciphers_to_test[5]: cipher suites using certificates with ECDH key agreement public keys
|
|
# ciphers_to_test[6]: cipher suites using certificates with ECDSA signature public keys
|
|
# ciphers_to_test[7]: cipher suites using certificates with GOST R 34.10 (either 2001 or 94) public keys
|
|
ciphers_to_test[1]=""
|
|
ciphers_to_test[2]=""
|
|
for ciph in $(colon_to_spaces $($OPENSSL ciphers "aRSA")); do
|
|
if grep -q "\-RSA\-" <<<$ciph; then
|
|
ciphers_to_test[1]="${ciphers_to_test[1]}:$ciph"
|
|
else
|
|
ciphers_to_test[2]="${ciphers_to_test[2]}:$ciph"
|
|
fi
|
|
done
|
|
[[ -n "${ciphers_to_test[1]}" ]] && ciphers_to_test[1]="${ciphers_to_test[1]:1}"
|
|
[[ -n "${ciphers_to_test[2]}" ]] && ciphers_to_test[2]="${ciphers_to_test[2]:1}"
|
|
ciphers_to_test[3]="aDSS"
|
|
ciphers_to_test[4]="aDH"
|
|
ciphers_to_test[5]="aECDH"
|
|
ciphers_to_test[6]="aECDSA"
|
|
ciphers_to_test[7]="aGOST"
|
|
|
|
for n in 1 2 3 4 5 6 7 ; do
|
|
if [[ -n "${ciphers_to_test[n]}" ]] && [[ $(count_ciphers $($OPENSSL ciphers "${ciphers_to_test[n]}" 2>>$ERRFILE)) -ge 1 ]]; then
|
|
determine_tls_extensions "-cipher ${ciphers_to_test[n]}"
|
|
if [[ $? -eq 0 ]]; then
|
|
# check to see if any new TLS extensions were returned and add any new ones to all_tls_extensions
|
|
while read -d "\"" -r line; do
|
|
if [[ $line != "" ]] && ! grep -q "$line" <<< "$all_tls_extensions"; then
|
|
all_tls_extensions="${all_tls_extensions} \"${line}\""
|
|
fi
|
|
done <<<$TLS_EXTENSIONS
|
|
|
|
cp "$TEMPDIR/$NODEIP.determine_tls_extensions.txt" $TMPFILE
|
|
>$ERRFILE
|
|
if [[ -z "$sessticket_str" ]]; then
|
|
sessticket_str=$(grep -aw "session ticket" $TMPFILE | grep -a lifetime)
|
|
fi
|
|
|
|
# check whether the host's certificate has been seen before
|
|
match_found=false
|
|
i=1
|
|
newhostcert=$(cat $HOSTCERT)
|
|
while [[ $i -le $certs_found ]]; do
|
|
if [ "$newhostcert" == "${previous_hostcert[i]}" ]; then
|
|
match_found=true
|
|
break;
|
|
fi
|
|
i=$((i + 1))
|
|
done
|
|
if ! $match_found ; then
|
|
certs_found=$(($certs_found + 1))
|
|
cipher[certs_found]=${ciphers_to_test[n]}
|
|
keysize[certs_found]=$(grep -aw "^Server public key is" $TMPFILE | sed -e 's/^Server public key is //' -e 's/bit//' -e 's/ //')
|
|
ocsp_response[certs_found]=$(grep -aA 20 "OCSP response" $TMPFILE)
|
|
ocsp_response_status[certs_found]=$(grep -a "OCSP Response Status" $TMPFILE)
|
|
previous_hostcert[certs_found]=$newhostcert
|
|
previous_intermediates[certs_found]=$(cat $TEMPDIR/intermediatecerts.pem)
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ $certs_found -eq 0 ]]; then
|
|
[[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions
|
|
[[ -n "$TLS_EXTENSIONS" ]] && all_tls_extensions=" $TLS_EXTENSIONS"
|
|
|
|
cp "$TEMPDIR/$NODEIP.determine_tls_extensions.txt" $TMPFILE
|
|
>$ERRFILE
|
|
|
|
sessticket_str=$(grep -aw "session ticket" $TMPFILE | grep -a lifetime)
|
|
fi
|
|
|
|
outln
|
|
pr_headlineln " Testing server defaults (Server Hello) "
|
|
outln
|
|
|
|
pr_bold " TLS extensions (standard) "
|
|
if [[ -z "$all_tls_extensions" ]]; then
|
|
outln "(none)"
|
|
fileout "tls_extensions" "INFO" "TLS server extensions (std): (none)"
|
|
else
|
|
all_tls_extensions="${all_tls_extensions:1}"
|
|
outln "$all_tls_extensions"
|
|
fileout "tls_extensions" "INFO" "TLS server extensions (std): $all_tls_extensions"
|
|
fi
|
|
TLS_EXTENSIONS="$all_tls_extensions"
|
|
|
|
pr_bold " Session Tickets RFC 5077 "
|
|
if [[ -z "$sessticket_str" ]]; then
|
|
outln "(none)"
|
|
fileout "session_ticket" "INFO" "TLS session tickes RFC 5077 not supported"
|
|
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')
|
|
out "$lifetime $unit "
|
|
pr_svrty_minorln "(PFS requires session ticket keys to be rotated <= daily)"
|
|
fileout "session_ticket" "INFO" "TLS session tickes RFC 5077 valid for $lifetime $unit (PFS requires session ticket keys to be rotated at least daily)"
|
|
fi
|
|
|
|
pr_bold " SSL Session ID support "
|
|
if "$NO_SSL_SESSIONID"; then
|
|
outln "no"
|
|
fileout "session_id" "INFO" "SSL session ID support: no"
|
|
else
|
|
outln "yes"
|
|
fileout "session_id" "INFO" "SSL session ID support: yes"
|
|
fi
|
|
|
|
tls_time
|
|
|
|
i=1
|
|
while [[ $i -le $certs_found ]]; do
|
|
echo "${previous_hostcert[i]}" > $HOSTCERT
|
|
echo "${previous_intermediates[i]}" > $TEMPDIR/intermediatecerts.pem
|
|
certificate_info "$i" "$certs_found" "${cipher[i]}" "${keysize[i]}" "${ocsp_response[i]}" "${ocsp_response_status[i]}"
|
|
i=$((i + 1))
|
|
done
|
|
}
|
|
|
|
# http://www.heise.de/security/artikel/Forward-Secrecy-testen-und-einrichten-1932806.html
|
|
run_pfs() {
|
|
local -i sclient_success
|
|
local pfs_offered=false
|
|
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'
|
|
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"
|
|
local -i nr_supported_ciphers=0
|
|
local pfs_ciphers
|
|
|
|
outln
|
|
pr_headlineln " Testing (perfect) forward secrecy, (P)FS -- omitting 3DES, RC4 and Null Encryption here "
|
|
if ! "$HAS_DH_BITS" && "$WIDE"; then
|
|
pr_warningln " (Your $OPENSSL cannot show DH/ECDH bits)"
|
|
fi
|
|
|
|
nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $pfs_cipher_list))
|
|
debugme echo $nr_supported_ciphers
|
|
debugme echo $(actually_supported_ciphers $pfs_cipher_list)
|
|
if [[ "$nr_supported_ciphers" -le "$CLIENT_MIN_PFS" ]]; then
|
|
outln
|
|
local_problem_ln "You only have $nr_supported_ciphers PFS ciphers on the client side "
|
|
fileout "pfs" "WARN" "(Perfect) Forward Secrecy tests: Skipped. You only have $nr_supported_ciphers PFS ciphers on the client site. ($CLIENT_MIN_PFS are required)"
|
|
return 1
|
|
fi
|
|
|
|
$OPENSSL s_client -cipher 'ECDH:DH' $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
if [[ $? -ne 0 ]] || [[ $(grep -ac "BEGIN CERTIFICATE" $TMPFILE) -eq 0 ]]; then
|
|
outln
|
|
pr_svrty_mediumln "NOT ok: No ciphers supporting Forward Secrecy offered"
|
|
fileout "pfs" "NOT ok" "(Perfect) Forward Secrecy : NOT ok: No ciphers supporting Forward Secrecy offered"
|
|
else
|
|
outln
|
|
pfs_offered=true
|
|
pfs_ciphers=""
|
|
pr_done_good " PFS is offered (OK)"
|
|
fileout "pfs" "OK" "(Perfect) Forward Secrecy : PFS is offered (OK)"
|
|
if "$WIDE"; then
|
|
outln ", ciphers follow (client/browser support is important here) \n"
|
|
neat_header
|
|
else
|
|
out " "
|
|
fi
|
|
while read hexcode dash pfs_cipher sslvers kx auth enc mac; do
|
|
tmpfile=$TMPFILE.$hexcode
|
|
$OPENSSL s_client -cipher $pfs_cipher $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI &>$tmpfile </dev/null
|
|
sclient_connect_successful $? $tmpfile
|
|
sclient_success=$?
|
|
if [[ "$sclient_success" -ne 0 ]] && ! "$SHOW_EACH_C"; then
|
|
continue # no successful connect AND not verbose displaying each cipher
|
|
fi
|
|
|
|
if "$WIDE"; 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
|
|
if "$SHOW_EACH_C"; then
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_done_best "available"
|
|
else
|
|
out "not a/v"
|
|
fi
|
|
else
|
|
pfs_offered=true
|
|
fi
|
|
outln
|
|
else
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
out "$pfs_cipher "
|
|
fi
|
|
fi
|
|
pfs_ciphers+="$pfs_cipher "
|
|
debugme rm $tmpfile
|
|
done < <($OPENSSL ciphers -V "$pfs_cipher_list" 2>$ERRFILE) # -V doesn't work with openssl < 1.0
|
|
debugme echo $pfs_offered
|
|
"$WIDE" || outln
|
|
|
|
if ! "$pfs_offered"; then
|
|
pr_svrty_medium "no PFS ciphers found"
|
|
fileout "pfs_ciphers" "NOT ok" "(Perfect) Forward Secrecy Ciphers: no PFS ciphers found (NOT ok)"
|
|
else
|
|
fileout "pfs_ciphers" "INFO" "(Perfect) Forward Secrecy Ciphers: $pfs_ciphers"
|
|
fi
|
|
fi
|
|
outln
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
# sub1_curves
|
|
if "$pfs_offered"; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
|
|
# 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 [[ -n "$STARTTLS" ]]; then
|
|
[[ -n "$1" ]] && out "$1"
|
|
out "(SPDY is an HTTP protocol and thus not tested here)"
|
|
fileout "spdy_npn" "INFO" "SPDY/NPN : (SPY is an HTTP protocol and thus not tested here)"
|
|
return 1
|
|
fi
|
|
if [[ -n "$PROXY" ]]; then
|
|
[[ -n "$1" ]] && pr_warning " $1 "
|
|
pr_warning "not tested as proxies do not support proxying it"
|
|
fileout "spdy_npn" "WARN" "SPDY/NPN : not tested as proxies do not support proxying it"
|
|
return 1
|
|
fi
|
|
if ! "$HAS_SPDY"; then
|
|
local_problem "$OPENSSL doesn't support SPDY/NPN";
|
|
fileout "spdy_npn" "WARN" "SPDY/NPN : not tested $OPENSSL doesn't support SPDY/NPN"
|
|
return 7
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
http2_pre(){
|
|
if [[ -n "$STARTTLS" ]]; then
|
|
[[ -n "$1" ]] && out "$1"
|
|
outln "(HTTP/2 is a HTTP protocol and thus not tested here)"
|
|
fileout "https_alpn" "INFO" "HTTP2/ALPN : HTTP/2 is and HTTP protocol and thus not tested"
|
|
return 1
|
|
fi
|
|
if [[ -n "$PROXY" ]]; then
|
|
[[ -n "$1" ]] && pr_warning " $1 "
|
|
pr_warning "not tested as proxies do not support proxying it"
|
|
fileout "https_alpn" "WARN" "HTTP2/ALPN : HTTP/2 was not tested as proxies do not support proxying it"
|
|
return 1
|
|
fi
|
|
if ! "$HAS_ALPN"; then
|
|
local_problem_ln "$OPENSSL doesn't support HTTP2/ALPN";
|
|
fileout "https_alpn" "WARN" "HTTP2/ALPN : HTTP/2 was not tested as $OPENSSL does not support it"
|
|
return 7
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
run_spdy() {
|
|
local tmpstr
|
|
local -i ret=0
|
|
|
|
pr_bold " SPDY/NPN "
|
|
if ! spdy_pre ; then
|
|
outln
|
|
return 0
|
|
fi
|
|
$OPENSSL s_client -connect $NODEIP:$PORT $BUGS $SNI -nextprotoneg $NPN_PROTOs </dev/null 2>$ERRFILE >$TMPFILE
|
|
tmpstr=$(grep -a '^Protocols' $TMPFILE | sed 's/Protocols.*: //')
|
|
if [[ -z "$tmpstr" ]] || [[ "$tmpstr" == " " ]]; then
|
|
outln "not offered"
|
|
fileout "spdy_npn" "INFO" "SPDY/NPN : not offered"
|
|
ret=1
|
|
else
|
|
# now comes a strange thing: "Protocols advertised by server:" is empty but connection succeeded
|
|
if echo $tmpstr | egrep -aq "h2|spdy|http" ; then
|
|
out "$tmpstr"
|
|
outln " (advertised)"
|
|
fileout "spdy_npn" "INFO" "SPDY/NPN : $tmpstr (advertised)"
|
|
ret=0
|
|
else
|
|
pr_cyanln "please check manually, server response was ambiguous ..."
|
|
fileout "spdy_npn" "INFO" "SPDY/NPN : please check manually, server response was ambiguous ..."
|
|
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
|
|
}
|
|
|
|
|
|
run_http2() {
|
|
local tmpstr
|
|
local -i ret=0
|
|
local had_alpn_proto=false
|
|
local alpn_finding=""
|
|
|
|
pr_bold " HTTP2/ALPN "
|
|
if ! http2_pre ; then
|
|
outln
|
|
return 0
|
|
fi
|
|
for proto in $ALPN_PROTOs; do
|
|
# for some reason OpenSSL doesn't list the advertised protocols, so instead try common protocols
|
|
$OPENSSL s_client -connect $NODEIP:$PORT $BUGS $SNI -alpn $proto </dev/null 2>$ERRFILE >$TMPFILE
|
|
#tmpstr=$(grep -a '^ALPN protocol' $TMPFILE | sed 's/ALPN protocol.*: //')
|
|
#tmpstr=$(awk '/^ALPN protocol*:/ { print $2 }' $TMPFILE)
|
|
tmpstr=$(awk -F':' '/^ALPN protocol*:/ { print $2 }' $TMPFILE)
|
|
if [[ "$tmpstr" == *"$proto" ]]; then
|
|
if ! $had_alpn_proto; then
|
|
out "$proto"
|
|
alpn_finding+="$proto"
|
|
had_alpn_proto=true
|
|
else
|
|
out ", $proto"
|
|
alpn_finding+=", $proto"
|
|
fi
|
|
fi
|
|
done
|
|
if $had_alpn_proto; then
|
|
outln " (offered)"
|
|
fileout "https_alpn" "INFO" "HTTP2/ALPN : offered; Protocols: $alpn_finding"
|
|
ret=0
|
|
else
|
|
outln "not offered"
|
|
fileout "https_alpn" "INFO" "HTTP2/ALPN : not offered"
|
|
ret=1
|
|
fi
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $ret
|
|
}
|
|
|
|
# arg1: string to send
|
|
# arg2: possible success strings a egrep pattern, needed!
|
|
starttls_line() {
|
|
debugme echo -e "\n=== sending \"$1\" ..."
|
|
echo -e "$1" >&5
|
|
|
|
# we don't know how much to read and it's blocking! So we just put a cat into the
|
|
# background and read until $STARTTLS_SLEEP and: cross our fingers
|
|
cat <&5 >$TMPFILE &
|
|
wait_kill $! $STARTTLS_SLEEP
|
|
debugme echo "... received result: "
|
|
debugme cat $TMPFILE
|
|
if [[ -n "$2" ]]; then
|
|
if egrep -q "$2" $TMPFILE; then
|
|
debugme echo "---> reply matched \"$2\""
|
|
else
|
|
# slow down for exim and friends who need a proper handshake:, see
|
|
# https://github.com/drwetter/testssl.sh/issues/218
|
|
FAST_STARTTLS=false
|
|
debugme echo -e "\n=== sending with automated FAST_STARTTLS=false \"$1\" ..."
|
|
echo -e "$1" >&5
|
|
cat <&5 >$TMPFILE &
|
|
debugme echo "... received result: "
|
|
debugme cat $TMPFILE
|
|
if [[ -n "$2" ]]; then
|
|
debugme echo "---> reply with automated FAST_STARTTLS=false matched \"$2\""
|
|
else
|
|
debugme echo "---> reply didn't match \"$2\", see $TMPFILE"
|
|
pr_magenta "STARTTLS handshake problem. "
|
|
outln "Either switch to native openssl (--ssl-native), "
|
|
outln " give the server more time to reply (STARTTLS_SLEEP=<seconds> ./testssh.sh ..) -- "
|
|
outln " or debug what happened (add --debug=2)"
|
|
return 3
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
starttls_just_send(){
|
|
debugme echo -e "\n=== sending \"$1\" ..."
|
|
echo -e "$1" >&5
|
|
}
|
|
|
|
starttls_just_read(){
|
|
debugme echo "=== just read banner ==="
|
|
if [[ "$DEBUG" -ge 2 ]]; then
|
|
cat <&5 &
|
|
wait_kill $! $STARTTLS_SLEEP
|
|
else
|
|
dd of=/dev/null count=8 <&5 2>/dev/null &
|
|
wait_kill $! $STARTTLS_SLEEP
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# arg for a fd doesn't work here
|
|
fd_socket() {
|
|
local jabber=""
|
|
local proyxline=""
|
|
local nodeip="$(tr -d '[]' <<< $NODEIP)" # sockets do not need the square brackets we have of IPv6 addresses
|
|
# we just need do it here, that's all!
|
|
if [[ -n "$PROXY" ]]; then
|
|
if ! exec 5<> /dev/tcp/${PROXYIP}/${PROXYPORT}; then
|
|
outln
|
|
pr_magenta "$PROG_NAME: unable to open a socket to proxy $PROXYIP:$PROXYPORT"
|
|
return 6
|
|
fi
|
|
echo "CONNECT $nodeip:$PORT" >&5
|
|
while true ; do
|
|
read proyxline <&5
|
|
if [[ "${proyxline%/*}" == "HTTP" ]]; then
|
|
proyxline=${proyxline#* }
|
|
if [[ "${proyxline%% *}" != "200" ]]; then
|
|
pr_magenta "Unable to CONNECT via proxy. "
|
|
[[ "$PORT" != 443 ]] && pr_magentaln "Check whether your proxy supports port $PORT and the underlying protocol."
|
|
return 6
|
|
fi
|
|
fi
|
|
if [[ "$proyxline" == $'\r' ]]; then
|
|
break
|
|
fi
|
|
done
|
|
elif ! exec 5<>/dev/tcp/$nodeip/$PORT; then # 2>/dev/null would remove 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
|
|
|
|
if [[ -n "$STARTTLS" ]]; then
|
|
case "$STARTTLS_PROTOCOL" in # port
|
|
ftp|ftps) # https://tools.ietf.org/html/rfc4217
|
|
$FAST_STARTTLS || starttls_just_read
|
|
$FAST_STARTTLS || starttls_line "FEAT" "211" && starttls_just_send "FEAT"
|
|
starttls_line "AUTH TLS" "successful|234"
|
|
;;
|
|
smtp|smtps) # SMTP, see https://tools.ietf.org/html/rfc4217
|
|
$FAST_STARTTLS || starttls_just_read
|
|
$FAST_STARTTLS || starttls_line "EHLO testssl.sh" "220|250" && starttls_just_send "EHLO testssl.sh"
|
|
starttls_line "STARTTLS" "220"
|
|
;;
|
|
pop3|pop3s) # POP, see https://tools.ietf.org/html/rfc2595
|
|
$FAST_STARTTLS || starttls_just_read
|
|
starttls_line "STLS" "OK"
|
|
;;
|
|
nntp|nntps) # NNTP, see https://tools.ietf.org/html/rfc4642
|
|
$FAST_STARTTLS || starttls_just_read
|
|
$FAST_STARTTLS || starttls_line "CAPABILITIES" "101|200" && starttls_just_send "CAPABILITIES"
|
|
starttls_line "STARTTLS" "382"
|
|
;;
|
|
imap|imaps) # IMAP, https://tools.ietf.org/html/rfc2595
|
|
$FAST_STARTTLS || starttls_just_read
|
|
$FAST_STARTTLS || starttls_line "a001 CAPABILITY" "OK" && starttls_just_send "a001 CAPABILITY"
|
|
starttls_line "a002 STARTTLS" "OK"
|
|
;;
|
|
ldap|ldaps) # LDAP, https://tools.ietf.org/html/rfc2830, https://tools.ietf.org/html/rfc4511
|
|
fatal "FIXME: LDAP+STARTTLS over sockets not yet supported (try \"--ssl-native\")" -4
|
|
;;
|
|
acap|acaps) # ACAP = Application Configuration Access Protocol, see https://tools.ietf.org/html/rfc2595
|
|
fatal "ACAP Easteregg: not implemented -- probably never will" -4
|
|
;;
|
|
xmpp|xmpps) # XMPP, see https://tools.ietf.org/html/rfc6120
|
|
starttls_just_read
|
|
[[ -z $XMPP_HOST ]] && XMPP_HOST="$NODE"
|
|
jabber=$(cat <<EOF
|
|
<?xml version='1.0' ?>
|
|
<stream:stream
|
|
xmlns:stream='http://etherx.jabber.org/streams'
|
|
xmlns='jabber:client'
|
|
to='$XMPP_HOST'
|
|
xml:lang='en'
|
|
version='1.0'>
|
|
EOF
|
|
)
|
|
starttls_line "$jabber"
|
|
starttls_line "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" "proceed"
|
|
# BTW: https://xmpp.net !
|
|
;;
|
|
*) # we need to throw an error here -- otherwise testssl.sh treats the STARTTLS protocol as plain SSL/TLS which leads to FP
|
|
fatal "FIXME: STARTTLS protocol $STARTTLS_PROTOCOL is not yet supported" -4
|
|
esac
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
close_socket(){
|
|
exec 5<&-
|
|
exec 5>&-
|
|
return 0
|
|
}
|
|
|
|
|
|
# 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')
|
|
#TODO: just echo, no additional global var
|
|
}
|
|
|
|
len2twobytes() {
|
|
local len_arg1=${#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() {
|
|
local data=""
|
|
|
|
code2network "$1"
|
|
data="$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 &
|
|
wait_kill $! $maxsleep
|
|
|
|
return $?
|
|
}
|
|
|
|
# arg1: name of file with socket reply
|
|
parse_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
|
|
parse_tls_serverhello() {
|
|
local tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$1")
|
|
local tls_handshake_ascii="" tls_alert_ascii=""
|
|
local -i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len msg_len
|
|
local tls_serverhello_ascii=""
|
|
local -i tls_serverhello_ascii_len=0
|
|
local tls_alert_descrip tls_sid_len_hex
|
|
local -i tls_sid_len offset
|
|
local tls_msg_type tls_content_type tls_protocol tls_protocol2 tls_hello_time
|
|
local tls_err_level tls_err_descr tls_cipher_suite tls_compression_method
|
|
local -i i
|
|
|
|
TLS_TIME=""
|
|
DETECTED_TLS_VERSION=""
|
|
|
|
# $tls_hello_ascii may contain trailing whitespace. Remove it:
|
|
tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}"
|
|
[[ "$DEBUG" -eq 5 ]] && echo $tls_hello_ascii # one line without any blanks
|
|
|
|
# Client messages, including handshake messages, are carried by the record layer.
|
|
# First, extract the handshake and alert messages.
|
|
# see 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: fragment length
|
|
# bytes 5...: message fragment
|
|
tls_hello_ascii_len=${#tls_hello_ascii}
|
|
if [[ $DEBUG -ge 2 ]] && [[ $tls_hello_ascii_len -gt 0 ]]; then
|
|
echo "TLS message fragments:"
|
|
fi
|
|
for (( i=0; i<tls_hello_ascii_len; i=i+msg_len )); do
|
|
if [[ $tls_hello_ascii_len-$i -lt 10 ]]; then
|
|
# This could just be a result of the server's response being
|
|
# split across two or more packets.
|
|
continue
|
|
fi
|
|
tls_content_type="${tls_hello_ascii:i:2}"
|
|
i=$i+2
|
|
tls_protocol="${tls_hello_ascii:i:4}"
|
|
i=$i+4
|
|
msg_len=2*$(hex2dec "${tls_hello_ascii:i:4}")
|
|
i=$i+4
|
|
|
|
if [[ $DEBUG -ge 2 ]]; then
|
|
echo " tls_protocol (reclyr): 0x$tls_protocol"
|
|
out " tls_content_type: 0x$tls_content_type"
|
|
case $tls_content_type in
|
|
15) outln " (alert)" ;;
|
|
16) outln " (handshake)" ;;
|
|
*) outln ;;
|
|
esac
|
|
echo " msg_len: $((msg_len/2))"
|
|
outln
|
|
fi
|
|
if [[ $tls_content_type != "15" ]] && [[ $tls_content_type != "16" ]]; then
|
|
debugme pr_svrty_criticalln "Content type other than alert or handshake detected."
|
|
return 1
|
|
elif [[ "${tls_protocol:0:2}" != "03" ]]; then
|
|
debugme pr_svrty_criticalln "Protocol record_version.major is not 03."
|
|
return 1
|
|
fi
|
|
DETECTED_TLS_VERSION=$tls_protocol
|
|
|
|
if [[ $msg_len -gt $tls_hello_ascii_len-$i ]]; then
|
|
# This could just be a result of the server's response being
|
|
# split across two or more packets. Just grab the part that
|
|
# is available.
|
|
msg_len=$tls_hello_ascii_len-$i
|
|
fi
|
|
|
|
if [[ $tls_content_type == "16" ]]; then
|
|
tls_handshake_ascii="$tls_handshake_ascii${tls_hello_ascii:i:msg_len}"
|
|
elif [[ $tls_content_type == "15" ]]; then # TLS ALERT
|
|
tls_alert_ascii="$tls_alert_ascii${tls_hello_ascii:i:msg_len}"
|
|
fi
|
|
done
|
|
|
|
# Now check the alert messages.
|
|
tls_alert_ascii_len=${#tls_alert_ascii}
|
|
if [[ $tls_alert_ascii_len -gt 0 ]]; then
|
|
debugme echo "TLS alert messages:"
|
|
for (( i=0; i+3 < tls_alert_ascii_len; i=i+4 )); do
|
|
tls_err_level=${tls_alert_ascii:i:2} # 1: warning, 2: fatal
|
|
j=$i+2
|
|
tls_err_descr=${tls_alert_ascii:j:2} # 112/0x70: Unrecognized name, 111/0x6F: certificate_unobtainable,
|
|
# 113/0x71: bad_certificate_status_response, #114/0x72: bad_certificate_hash_value
|
|
|
|
debugme out " tls_err_descr: 0x${tls_err_descr} / = $(hex2dec ${tls_err_descr})"
|
|
case $tls_err_descr in
|
|
00) tls_alert_descrip="close notify" ;;
|
|
0A) tls_alert_descrip="unexpected message" ;;
|
|
14) tls_alert_descrip="bad record mac" ;;
|
|
15) tls_alert_descrip="decryption failed" ;;
|
|
16) tls_alert_descrip="record overflow" ;;
|
|
1E) tls_alert_descrip="decompression failure" ;;
|
|
28) tls_alert_descrip="handshake failure" ;;
|
|
29) tls_alert_descrip="no certificate RESERVED" ;;
|
|
2A) tls_alert_descrip="bad certificate" ;;
|
|
2B) tls_alert_descrip="unsupported certificate" ;;
|
|
2C) tls_alert_descrip="certificate revoked" ;;
|
|
2D) tls_alert_descrip="certificate expired" ;;
|
|
2E) tls_alert_descrip="certificate unknown" ;;
|
|
2F) tls_alert_descrip="illegal parameter" ;;
|
|
30) tls_alert_descrip="unknown ca" ;;
|
|
31) tls_alert_descrip="access denied" ;;
|
|
32) tls_alert_descrip="decode error" ;;
|
|
33) tls_alert_descrip="decrypt error" ;;
|
|
3C) tls_alert_descrip="export restriction RESERVED" ;;
|
|
46) tls_alert_descrip="protocol version" ;;
|
|
47) tls_alert_descrip="insufficient security" ;;
|
|
50) tls_alert_descrip="internal error" ;;
|
|
56) tls_alert_descrip="inappropriate fallback" ;;
|
|
5A) tls_alert_descrip="user canceled" ;;
|
|
64) tls_alert_descrip="no renegotiation" ;;
|
|
6E) tls_alert_descrip="unsupported extension" ;;
|
|
6F) tls_alert_descrip="certificate unobtainable" ;;
|
|
70) tls_alert_descrip="unrecognized name" ;;
|
|
71) tls_alert_descrip="bad certificate status response" ;;
|
|
72) tls_alert_descrip="bad certificate hash value" ;;
|
|
73) tls_alert_descrip="unknown psk identity" ;;
|
|
78) tls_alert_descrip="no application protocol" ;;
|
|
*) tls_alert_descrip="$(hex2dec "$tls_err_descr")";;
|
|
esac
|
|
|
|
if [[ $DEBUG -ge 2 ]]; then
|
|
outln " ($tls_alert_descrip)"
|
|
out " tls_err_level: ${tls_err_level}"
|
|
case $tls_err_level in
|
|
01) outln " (warning)" ;;
|
|
02) outln " (fatal)" ;;
|
|
*) outln ;;
|
|
esac
|
|
outln
|
|
fi
|
|
if [[ "$tls_err_level" != "01" ]] && [[ "$tls_err_level" != "02" ]]; then
|
|
debugme pr_svrty_criticalln "Unexpected AlertLevel (0x$tls_err_level)."
|
|
return 1
|
|
elif [[ "$tls_err_level" == "02" ]]; then
|
|
# Fatal alert
|
|
return 1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Now extract just the server hello handshake message.
|
|
tls_handshake_ascii_len=${#tls_handshake_ascii}
|
|
if [[ $DEBUG -ge 2 ]] && [[ $tls_handshake_ascii_len -gt 0 ]]; then
|
|
echo "TLS handshake messages:"
|
|
fi
|
|
for (( i=0; i<tls_handshake_ascii_len; i=i+msg_len )); do
|
|
if [[ $tls_handshake_ascii_len-$i -lt 8 ]]; then
|
|
# This could just be a result of the server's response being
|
|
# split across two or more packets.
|
|
continue
|
|
fi
|
|
tls_msg_type="${tls_handshake_ascii:i:2}"
|
|
i=$i+2
|
|
msg_len=2*$(hex2dec "${tls_handshake_ascii:i:6}")
|
|
i=$i+6
|
|
|
|
if [[ $DEBUG -ge 2 ]]; then
|
|
out " handshake type: 0x${tls_msg_type}"
|
|
case $tls_msg_type in
|
|
00) outln " (hello_request)" ;;
|
|
01) outln " (client_hello)" ;;
|
|
02) outln " (server_hello)" ;;
|
|
03) outln " (hello_verify_request)" ;;
|
|
04) outln " (NewSessionTicket)" ;;
|
|
0B) outln " (certificate)" ;;
|
|
0C) outln " (server_key_exchange)" ;;
|
|
0D) outln " (certificate_request)" ;;
|
|
0E) outln " (server_hello_done)" ;;
|
|
0F) outln " (certificate_verify)" ;;
|
|
10) outln " (client_key_exchange)" ;;
|
|
14) outln " (finished)" ;;
|
|
15) outln " (certificate_url)" ;;
|
|
16) outln " (certificate_status)" ;;
|
|
17) outln " (supplemental_data)" ;;
|
|
*) outln ;;
|
|
esac
|
|
echo " msg_len: $((msg_len/2))"
|
|
outln
|
|
fi
|
|
if [[ $msg_len -gt $tls_handshake_ascii_len-$i ]]; then
|
|
# This could just be a result of the server's response being
|
|
# split across two or more packets. Just grab the part that
|
|
# is available.
|
|
msg_len=$tls_handshake_ascii_len-$i
|
|
fi
|
|
|
|
if [[ "$tls_msg_type" == "02" ]]; then
|
|
if [[ -n "$tls_serverhello_ascii" ]]; then
|
|
debugme pr_svrty_criticalln "Response contained more than one ServerHello handshake message."
|
|
return 1
|
|
fi
|
|
tls_serverhello_ascii="${tls_handshake_ascii:i:msg_len}"
|
|
tls_serverhello_ascii_len=$msg_len
|
|
fi
|
|
done
|
|
|
|
if [[ $tls_serverhello_ascii_len -eq 0 ]]; then
|
|
debugme echo "server hello empty, TCP connection closed"
|
|
return 1 # no server hello received
|
|
elif [[ $tls_serverhello_ascii_len -lt 76 ]]; then
|
|
debugme echo "Malformed response"
|
|
return 1
|
|
elif [[ "${tls_handshake_ascii:0:2}" != "02" ]]; then
|
|
# the ServerHello MUST be the first handshake message
|
|
debugme pr_svrty_criticalln "The first handshake protocol message is not a ServerHello."
|
|
return 1
|
|
fi
|
|
|
|
# Parse the server hello handshake message
|
|
# byte 0+1: 03, TLS version word see byte 1+2
|
|
# byte 2-5: TLS timestamp for OpenSSL <1.01f
|
|
# byte 6-33: random, 28 bytes
|
|
# byte 34: session id length
|
|
# byte 35+36+sid-len: cipher suite!
|
|
# byte 37+sid-len: compression method: 00: none, 01: deflate, 64: LZS
|
|
# byte 38+39+sid-len: extension length
|
|
tls_protocol2="${tls_serverhello_ascii:0:4}"
|
|
if [[ "${tls_protocol2:0:2}" != "03" ]]; then
|
|
debugme pr_svrty_criticalln "server_version.major in ServerHello is not 03."
|
|
return 1
|
|
fi
|
|
DETECTED_TLS_VERSION="$tls_protocol2"
|
|
|
|
tls_hello_time="${tls_serverhello_ascii:4:8}"
|
|
TLS_TIME=$(hex2dec "$tls_hello_time")
|
|
|
|
tls_sid_len_hex="${tls_serverhello_ascii:68:2}"
|
|
tls_sid_len=2*$(hex2dec "$tls_sid_len_hex")
|
|
let offset=70+$tls_sid_len
|
|
|
|
if [[ $tls_serverhello_ascii_len -lt 76+$tls_sid_len ]]; then
|
|
debugme echo "Malformed response"
|
|
return 1
|
|
fi
|
|
|
|
tls_cipher_suite="${tls_serverhello_ascii:$offset:4}"
|
|
|
|
let offset=74+$tls_sid_len
|
|
tls_compression_method="${tls_serverhello_ascii:$offset:2}"
|
|
|
|
if [[ $DEBUG -ge 2 ]]; then
|
|
echo "TLS server hello message:"
|
|
if [[ $DEBUG -ge 4 ]]; then
|
|
echo " tls_protocol: 0x$tls_protocol2"
|
|
echo " tls_sid_len: 0x$tls_sid_len_hex / = $((tls_sid_len/2))"
|
|
fi
|
|
echo -n " tls_hello_time: 0x$tls_hello_time "
|
|
if "$HAS_GNUDATE"; then
|
|
date --date="@$TLS_TIME" "+%Y-%m-%d %r"
|
|
else
|
|
LC_ALL=C date -j -f %s "$TLS_TIME" "+%Y-%m-%d %r"
|
|
fi
|
|
echo " tls_cipher_suite: 0x$tls_cipher_suite"
|
|
echo -n " tls_compression_method: 0x$tls_compression_method "
|
|
case $tls_compression_method in
|
|
00) echo "(NONE)" ;;
|
|
01) echo "(zlib compression)" ;;
|
|
40) echo "(LZS compression)" ;;
|
|
*) echo "(unrecognized compression method)" ;;
|
|
esac
|
|
outln
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
|
|
sslv2_sockets() {
|
|
local nr_ciphers_detected
|
|
|
|
fd_socket 5 || return 6
|
|
debugme outln "sending client hello... "
|
|
socksend_sslv2_clienthello "$SSLv2_CLIENT_HELLO"
|
|
|
|
sockread_serverhello 32768
|
|
debugme outln "reading server hello... "
|
|
if [[ "$DEBUG" -ge 4 ]]; then
|
|
hexdump -C "$SOCK_REPLY_FILE" | head -6
|
|
outln
|
|
fi
|
|
|
|
parse_sslv2_serverhello "$SOCK_REPLY_FILE"
|
|
case $? in
|
|
7) # strange reply, couldn't convert the cipher spec length to a hex number
|
|
pr_cyan "strange v2 reply "
|
|
outln " (rerun with DEBUG >=2)"
|
|
[[ $DEBUG -ge 3 ]] && hexdump -C "$SOCK_REPLY_FILE" | head -1
|
|
ret=7
|
|
fileout "sslv2" "WARN" "SSLv2: received a strange SSLv2 reply (rerun with DEBUG>=2)"
|
|
;;
|
|
1) # no sslv2 server hello returned, like in openlitespeed which returns HTTP!
|
|
pr_done_bestln "not offered (OK)"
|
|
ret=0
|
|
fileout "sslv2" "OK" "SSLv2 not offered (OK)"
|
|
;;
|
|
0) # reset
|
|
pr_done_bestln "not offered (OK)"
|
|
ret=0
|
|
fileout "sslv2" "OK" "SSLv2 not offered (OK)"
|
|
;;
|
|
3) # everything else
|
|
lines=$(count_lines "$(hexdump -C "$SOCK_REPLY_FILE" 2>/dev/null)")
|
|
[[ "$DEBUG" -ge 2 ]] && out " ($lines lines) "
|
|
if [[ "$lines" -gt 1 ]]; then
|
|
nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3))
|
|
if [[ 0 -eq "$nr_ciphers_detected" ]]; then
|
|
pr_svrty_highln "supported but couldn't detect a cipher and vulnerable to CVE-2015-3197 ";
|
|
fileout "sslv2" "NOT ok" "SSLv2 offered (NOT ok), vulnerable to CVE-2015-3197"
|
|
else
|
|
pr_svrty_critical "offered (NOT ok), also VULNERABLE to DROWN attack";
|
|
outln " -- $nr_ciphers_detected ciphers"
|
|
fileout "sslv2" "NOT ok" "SSLv2 offered (NOT ok), vulnerable to DROWN attack. Detected ciphers: $nr_ciphers_detected"
|
|
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() {
|
|
#FIXME: redo this with all extensions!
|
|
local tls_low_byte="$1"
|
|
local tls_word_reclayer="03, 01" # the first TLS version number is the record layer and always 0301 -- except: SSLv3
|
|
local servername_hexstr len_servername len_servername_hex
|
|
local hexdump_format_str
|
|
local all_extensions
|
|
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})
|
|
len_servername=${#NODE}
|
|
hexdump_format_str="$len_servername/1 \"%02x,\""
|
|
servername_hexstr=$(printf $NODE | hexdump -v -e "${hexdump_format_str}" | sed 's/,$//')
|
|
|
|
code2network "$2" # convert 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))) #FIXME: for TLS 1.2 and IIS servers we need extension_signature_algorithms!!
|
|
|
|
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
|
|
|
|
# if we have SSLv3, the first occurence of TLS protocol -- record layer -- is SSLv3, otherwise TLS 1.0
|
|
[[ $tls_low_byte == "00" ]] && tls_word_reclayer="03, 00"
|
|
|
|
TLS_CLIENT_HELLO="
|
|
# TLS header ( 5 bytes)
|
|
,16, $tls_word_reclayer # TLS Version: in wireshark this is always 01 for TLS 1.0-1.2
|
|
,$len_all_word # Length <---
|
|
# Handshake header:
|
|
,01 # Type (x01 for ClientHello)
|
|
,00, $len_client_hello_word # Length ClientHello
|
|
,03, $tls_low_byte # TLS version ClientHello
|
|
,54, 51, 1e, 7a # Unix time since see www.moserware.com/2009/06/first-few-milliseconds-of-https.html
|
|
,de, ad, be, ef # Random 28 bytes
|
|
,31, 33, 07, 00, 00, 00, 00, 00
|
|
,cf, bd, 39, 04, cc, 16, 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, # Type: signature_algorithms , see RFC 5246
|
|
00, 20, # len
|
|
00,1e, 06,01, 06,02, 06,03, 05,01, 05,02, 05,03,
|
|
04,01, 04,02, 04,03, 03,01, 03,02, 03,03, 02,01, 02,02, 02,03"
|
|
|
|
# Extension: Haertbeat 00 0f
|
|
# len 00 01
|
|
# peer allowed to send requests 01
|
|
|
|
if [[ "$tls_low_byte" == "00" ]]; then
|
|
all_extensions=""
|
|
else #FIXME: we (probably) need extension_signature_algorithms here. TLS 1.2 fails on IIS otherwise
|
|
all_extensions="
|
|
,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$all_extensions"
|
|
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 -i ret=0
|
|
local -i save=0
|
|
local lines
|
|
local tls_low_byte
|
|
local cipher_list_2send
|
|
|
|
tls_low_byte="$1"
|
|
if [[ -n "$2" ]]; then # use supplied string in arg2 if there is one
|
|
cipher_list_2send="$2"
|
|
else # otherwise use std ciphers then
|
|
if [[ "$tls_low_byte" == "03" ]]; then
|
|
cipher_list_2send="$TLS12_CIPHER"
|
|
else
|
|
cipher_list_2send="$TLS_CIPHER"
|
|
fi
|
|
fi
|
|
|
|
debugme 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=$(LC_ALL=C date "+%s")
|
|
debugme outln "reading server hello..."
|
|
if [[ "$DEBUG" -ge 4 ]]; then
|
|
hexdump -C $SOCK_REPLY_FILE | head -6
|
|
echo
|
|
fi
|
|
|
|
parse_tls_serverhello "$SOCK_REPLY_FILE"
|
|
save=$?
|
|
|
|
# see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL
|
|
lines=$(count_lines "$(hexdump -C "$SOCK_REPLY_FILE" 2>$ERRFILE)")
|
|
debugme 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
|
|
run_heartbleed(){
|
|
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for heartbleed vulnerability " && outln
|
|
pr_bold " Heartbleed\c"; out " (CVE-2014-0160) "
|
|
|
|
[[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions
|
|
if ! grep -q heartbeat <<< "$TLS_EXTENSIONS"; then
|
|
pr_done_best "not vulnerable (OK)"
|
|
outln " (no heartbeat extension)"
|
|
fileout "heartbleed" "OK" "Heartbleed (CVE-2014-0160): not vulnerable (OK) (no heartbeat extension)"
|
|
return 0
|
|
fi
|
|
|
|
# determine TLS versions offered <-- needs to come from another place
|
|
$OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -tlsextdebug >$TMPFILE 2>$ERRFILE </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
|
|
#FIXME: for SSLv3 only we need to set tls_hexcode and the record layer TLS version correctly
|
|
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)
|
|
x03, x01, # TLS record layer 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
|
|
|
|
debugme outln "\nsending client hello (TLS version $tls_hexcode)"
|
|
socksend "$client_hello" 1
|
|
|
|
debugme outln "\nreading server hello"
|
|
sockread 32768
|
|
if [[ $DEBUG -ge 4 ]]; 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_svrty_critical "VULNERABLE (NOT ok)"
|
|
if [[ $retval -eq 3 ]]; then
|
|
fileout "heartbleed" "NOT ok" "Heartbleed (CVE-2014-0160): VULNERABLE (NOT ok) (timed out)"
|
|
else
|
|
fileout "heartbleed" "NOT ok" "Heartbleed (CVE-2014-0160): VULNERABLE (NOT ok)"
|
|
fi
|
|
ret=1
|
|
else
|
|
pr_done_best "not vulnerable (OK)"
|
|
if [[ $retval -eq 3 ]]; then
|
|
fileout "heartbleed" "OK" "Heartbleed (CVE-2014-0160): not vulnerable (OK) (timed out)"
|
|
else
|
|
fileout "heartbleed" "OK" "Heartbleed (CVE-2014-0160): not vulnerable (OK)"
|
|
fi
|
|
ret=0
|
|
fi
|
|
[[ $retval -eq 3 ]] && out " (timed out)"
|
|
outln
|
|
|
|
close_socket
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $ret
|
|
}
|
|
|
|
# helper function
|
|
ok_ids(){
|
|
pr_done_bestln "\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
|
|
run_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_headlineln " Testing for CCS injection vulnerability " && outln
|
|
pr_bold " CCS"; out " (CVE-2014-0224) "
|
|
|
|
# determine TLS versions offered <-- needs to come from another place
|
|
$OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY >$TMPFILE 2>$ERRFILE </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" ;;
|
|
#FIXME: for SSLv3 only we need to set tls_hexcode and the record layer TLS version correctly
|
|
esac
|
|
ccs_message=", x14, $tls_hexcode ,x00, x01, x01"
|
|
|
|
client_hello="
|
|
# TLS header (5 bytes)
|
|
,x16, # content type (x16 for handshake)
|
|
x03, x01, # TLS version in record layer is always TLS 1.0 (except SSLv3)
|
|
x00, x93, # length
|
|
# Handshake header
|
|
x01, # type (x01 for ClientHello)
|
|
x00, x00, x8f, # length
|
|
$tls_hexcode, # TLS version
|
|
# Random (32 byte)
|
|
x53, x43, x5b, x90, x9d, x9b, x72, x0b,
|
|
xbc, x0c, xbc, x2b, x92, xa8, x48, x97,
|
|
xcf, xbd, x39, x04, xcc, x16, 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
|
|
|
|
# we now make a standard handshake ...
|
|
debugme out "\nsending client hello, "
|
|
socksend "$client_hello" 1
|
|
|
|
debugme outln "\nreading server hello"
|
|
sockread 32768
|
|
if [[ $DEBUG -ge 4 ]]; then
|
|
echo "$SOCKREPLY" | "${HEXDUMPVIEW[@]}" | head -20
|
|
outln "[...]"
|
|
outln "\npayload #1 with TLS version $tls_hexcode:"
|
|
fi
|
|
|
|
# ... and then send the a change cipher spec message
|
|
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)
|
|
# or just timed out
|
|
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: "
|
|
printf -- "$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
|
|
|
|
byte6=$(echo "$SOCKREPLY" | "${HEXDUMPPLAIN[@]}" | sed 's/^..........//')
|
|
lines=$(echo "$SOCKREPLY" | "${HEXDUMP[@]}" | count_lines )
|
|
debugme echo "lines: $lines, byte6: $byte6"
|
|
|
|
if [[ "$byte6" == "0a" ]] || [[ "$lines" -gt 1 ]]; then
|
|
pr_done_best "not vulnerable (OK)"
|
|
if [[ $retval -eq 3 ]]; then
|
|
fileout "ccs" "OK" "CCS (CVE-2014-0224): not vulnerable (OK) (timed out)"
|
|
else
|
|
fileout "ccs" "OK" "CCS (CVE-2014-0224): not vulnerable (OK)"
|
|
fi
|
|
ret=0
|
|
else
|
|
pr_svrty_critical "VULNERABLE (NOT ok)"
|
|
if [[ $retval -eq 3 ]]; then
|
|
fileout "ccs" "NOT ok" "CCS (CVE-2014-0224): VULNERABLE (NOT ok) (timed out)"
|
|
else
|
|
fileout "ccs" "NOT ok" "CCS (CVE-2014-0224): VULNERABLE (NOT ok)"
|
|
fi
|
|
ret=1
|
|
fi
|
|
[[ $retval -eq 3 ]] && out " (timed out)"
|
|
outln
|
|
|
|
close_socket
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $ret
|
|
}
|
|
|
|
run_renego() {
|
|
# no SNI here. Not needed as there won't be two different SSL stacks for one IP
|
|
local legacycmd=""
|
|
local insecure_renogo_str="Secure Renegotiation IS NOT"
|
|
local sec_renego sec_client_renego
|
|
|
|
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Renegotiation vulnerabilities " && outln
|
|
|
|
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
|
|
$OPENSSL s_client $OPTIMAL_PROTO $STARTTLS $BUGS -connect $NODEIP:$PORT $SNI $PROXY 2>&1 </dev/null >$TMPFILE 2>$ERRFILE
|
|
if sclient_connect_successful $? $TMPFILE; then
|
|
grep -iaq "$insecure_renogo_str" $TMPFILE
|
|
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_svrty_criticalln "VULNERABLE (NOT ok)"
|
|
fileout "secure_renego" "NOT ok" "Secure Renegotiation (CVE-2009-3555) : VULNERABLE (NOT ok)"
|
|
;;
|
|
1)
|
|
pr_done_bestln "not vulnerable (OK)"
|
|
fileout "secure_renego" "OK" "Secure Renegotiation (CVE-2009-3555) : not vulnerable (OK)"
|
|
;;
|
|
*)
|
|
pr_warningln "FIXME (bug): $sec_renego"
|
|
fileout "secure_renego" "WARN" "Secure Renegotiation (CVE-2009-3555) : FIXME (bug) $sec_renego"
|
|
;;
|
|
esac
|
|
else
|
|
pr_warningln "handshake didn't succeed"
|
|
fileout "secure_renego" "WARN" "Secure Renegotiation (CVE-2009-3555) : handshake didn't succeed"
|
|
fi
|
|
|
|
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])
|
|
local_problem_ln "$OPENSSL cannot test this secure renegotiation vulnerability"
|
|
fileout "sec_client_renego" "WARN" "Secure Client-Initiated Renegotiation : $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
|
|
|
|
if "$CLIENT_AUTH"; then
|
|
pr_warningln "client authentication prevents this from being tested"
|
|
fileout "sec_client_renego" "WARN" "Secure Client-Initiated Renegotiation : client authentication prevents this from being tested"
|
|
sec_client_renego=1
|
|
else
|
|
# We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background
|
|
# msg enables us to look deeper into it while debugging
|
|
echo R | $OPENSSL s_client $OPTIMAL_PROTO $BUGS $legacycmd $STARTTLS -msg -connect $NODEIP:$PORT $SNI $PROXY >$TMPFILE 2>>$ERRFILE &
|
|
wait_kill $! $HEADER_MAXSLEEP
|
|
if [[ $? -eq 3 ]]; then
|
|
pr_done_good "likely not vulnerable (OK)"; outln " (timed out)" # it hung
|
|
fileout "sec_client_renego" "OK" "Secure Client-Initiated Renegotiation : likely not vulnerable (OK) (timed out)"
|
|
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 $BUGS -msg -connect $NODEIP:$PORT $SNI $PROXY >$TMPFILE 2>>$ERRFILE
|
|
sec_client_renego=$? # 0=client is renegotiating & doesn't return an error --> vuln!
|
|
case "$sec_client_renego" in
|
|
0)
|
|
pr_svrty_high "VULNERABLE (NOT ok)"; outln ", DoS threat"
|
|
fileout "sec_client_renego" "NOT ok" "Secure Client-Initiated Renegotiation : VULNERABLE (NOT ok), DoS threat"
|
|
;;
|
|
1)
|
|
pr_done_goodln "not vulnerable (OK)"
|
|
fileout "sec_client_renego" "OK" "Secure Client-Initiated Renegotiation : not vulnerable (OK)"
|
|
;;
|
|
*)
|
|
pr_warningln "FIXME (bug): $sec_client_renego"
|
|
fileout "sec_client_renego" "WARN" "Secure Client-Initiated Renegotiation : FIXME (bug) $sec_client_renego - Please report"
|
|
;;
|
|
esac
|
|
fi
|
|
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 ;-)
|
|
}
|
|
|
|
run_crime() {
|
|
local -i ret=0
|
|
local addcmd=""
|
|
# 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_headlineln " Testing for CRIME vulnerability " && outln
|
|
pr_bold " CRIME, TLS " ; out "(CVE-2012-4929) "
|
|
|
|
# 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
|
|
local_problem_ln "$OPENSSL lacks zlib support"
|
|
fileout "crime" "WARN" "CRIME, TLS (CVE-2012-4929) : Not tested. $OPENSSL lacks zlib support"
|
|
return 7
|
|
fi
|
|
|
|
[[ "$OSSL_VER" == "0.9.8"* ]] && addcmd="-no_ssl2"
|
|
$OPENSSL s_client $OPTIMAL_PROTO $BUGS $addcmd $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI </dev/null &>$TMPFILE
|
|
if grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then
|
|
pr_done_good "not vulnerable (OK)"
|
|
if [[ $SERVICE != "HTTP" ]] && ! $CLIENT_AUTH; then
|
|
out " (not using HTTP anyway)"
|
|
fileout "crime" "OK" "CRIME, TLS (CVE-2012-4929) : Not vulnerable (OK) (not using HTTP anyway)"
|
|
else
|
|
fileout "crime" "OK" "CRIME, TLS (CVE-2012-4929) : Not vulnerable (OK)"
|
|
fi
|
|
ret=0
|
|
else
|
|
if [[ $SERVICE == "HTTP" ]]; then
|
|
pr_svrty_high "VULNERABLE (NOT ok)"
|
|
fileout "crime" "NOT ok" "CRIME, TLS (CVE-2012-4929) : VULNERABLE (NOT ok)"
|
|
else
|
|
pr_svrty_medium "VULNERABLE (NOT ok), but not using HTTP: probably no exploit known"
|
|
fileout "crime" "NOT ok" "CRIME, TLS (CVE-2012-4929) : 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 repeatedly modify the input
|
|
# which is done e.g. 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_done_best "not vulnerable (OK)"
|
|
# ret=$((ret + 0))
|
|
# else
|
|
# pr_svrty_critical "VULNERABLE (NOT ok)"
|
|
# ret=$((ret + 1))
|
|
# fi
|
|
# fi
|
|
# fi
|
|
# [[ $DEBUG -eq 2 ]] outln "$STR"
|
|
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!
|
|
# Mitigation: see https://community.qualys.com/message/20360
|
|
run_breach() {
|
|
local header
|
|
local -i ret=0
|
|
local -i was_killed=0
|
|
local referer useragent
|
|
local url
|
|
local spaces=" "
|
|
local disclaimer=""
|
|
local when_makesense=" Can be ignored for static pages or if no secrets in the page"
|
|
|
|
[[ $SERVICE != "HTTP" ]] && return 7
|
|
|
|
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln
|
|
pr_bold " BREACH"; out " (CVE-2013-3587) "
|
|
|
|
url="$1"
|
|
[[ -z "$url" ]] && url="/"
|
|
disclaimer=" - only supplied \"$url\" tested"
|
|
|
|
referer="https://google.com/"
|
|
[[ "$NODE" =~ google ]] && referer="https://yandex.ru/" # otherwise we have a false positive for google.com
|
|
|
|
useragent="$UA_STD"
|
|
$SNEAKY && useragent="$UA_SNEAKY"
|
|
|
|
printf "GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: gzip,deflate,compress\r\nAccept: text/*\r\n\r\n" | $OPENSSL s_client $OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI 1>$TMPFILE 2>$ERRFILE &
|
|
wait_kill $! $HEADER_MAXSLEEP
|
|
was_killed=$? # !=0 was killed
|
|
result=$(awk '/^Content-Encoding/ { print $2 }' $TMPFILE)
|
|
result=$(strip_lf "$result")
|
|
debugme grep '^Content-Encoding' $TMPFILE
|
|
if [[ ! -s $TMPFILE ]]; then
|
|
pr_warning "failed (HTTP header request stalled"
|
|
if [[ $was_killed -ne 0 ]]; then
|
|
pr_warning " and was terminated"
|
|
fileout "breach" "WARN" "BREACH (CVE-2013-3587) : Test failed (HTTP request stalled and was terminated)"
|
|
else
|
|
fileout "breach" "WARN" "BREACH (CVE-2013-3587) : Test failed (HTTP request stalled)"
|
|
fi
|
|
pr_warningln ") "
|
|
ret=3
|
|
elif [[ -z $result ]]; then
|
|
pr_done_best "no HTTP compression (OK) "
|
|
outln "$disclaimer"
|
|
fileout "breach" "OK" "BREACH (CVE-2013-3587) : no HTTP compression (OK) $disclaimer"
|
|
ret=0
|
|
else
|
|
pr_svrty_high "potentially NOT ok, uses $result HTTP compression."
|
|
outln "$disclaimer"
|
|
outln "$spaces$when_makesense"
|
|
fileout "breach" "NOT ok" "BREACH (CVE-2013-3587) : potentially VULNERABLE, uses $result HTTP compression. $disclaimer ($when_makesense)"
|
|
ret=1
|
|
fi
|
|
# Any URL can be vulnerable. I am testing now only the given URL!
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $ret
|
|
}
|
|
|
|
# Padding Oracle On Downgraded Legacy Encryption, in a nutshell: don't use CBC Ciphers in SSLv3
|
|
run_ssl_poodle() {
|
|
local -i sclient_success=0
|
|
local cbc_ciphers
|
|
local cbc_ciphers="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: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:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:EXP1024-RC2-CBC-MD5:DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5"
|
|
local cbc_ciphers_krb="KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5"
|
|
|
|
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for SSLv3 POODLE (Padding Oracle On Downgraded Legacy Encryption) " && outln
|
|
pr_bold " POODLE, SSL"; out " (CVE-2014-3566) "
|
|
#nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $cbc_ciphers:cbc_ciphers_krb))
|
|
cbc_ciphers=$($OPENSSL ciphers -v 'ALL:eNULL' 2>$ERRFILE | awk '/CBC/ { print $1 }' | tr '\n' ':')
|
|
|
|
debugme echo $cbc_ciphers
|
|
$OPENSSL s_client -ssl3 $STARTTLS $BUGS -cipher $cbc_ciphers -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$?
|
|
[[ "$DEBUG" -eq 2 ]] && egrep -q "error|failure" $ERRFILE | egrep -av "unable to get local|verify error"
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_svrty_high "VULNERABLE (NOT ok)"; out ", uses SSLv3+CBC (check TLS_FALLBACK_SCSV mitigation below)"
|
|
fileout "poodle_ssl" "NOT ok" "POODLE, SSL (CVE-2014-3566) : VULNERABLE (NOT ok), uses SSLv3+CBC (check if TLS_FALLBACK_SCSV mitigation is used)"
|
|
else
|
|
pr_done_best "not vulnerable (OK)"
|
|
fileout "poodle_ssl" "OK" "POODLE, SSL (CVE-2014-3566) : not vulnerable (OK)"
|
|
fi
|
|
outln
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $sclient_success
|
|
}
|
|
|
|
# for appliance which use padding, no fallback needed
|
|
run_tls_poodle() {
|
|
pr_bold " POODLE, TLS"; out " (CVE-2014-8730), experimental "
|
|
#FIXME
|
|
echo "#FIXME"
|
|
fileout "poodle_tls" "WARN" "POODLE, TLS (CVE-2014-8730) : Not tested. Not yet implemented #FIXME"
|
|
return 7
|
|
}
|
|
|
|
run_tls_fallback_scsv() {
|
|
local -i ret=0
|
|
|
|
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for TLS_FALLBACK_SCSV Protection " && outln
|
|
pr_bold " TLS_FALLBACK_SCSV"; out " (RFC 7507), experim. "
|
|
# 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
|
|
if ! $OPENSSL s_client -help 2>&1 | grep -q "\-fallback_scsv"; then
|
|
local_problem_ln "$OPENSSL lacks TLS_FALLBACK_SCSV support"
|
|
return 4
|
|
fi
|
|
#TODO: this need some tuning: a) if one protocol is supported only it has practcally no value (theoretical it's interesting though)
|
|
# b) for IIS6 + openssl 1.0.2 this won't work
|
|
# c) best to make sure that we hit a specific protocol, see https://alpacapowered.wordpress.com/2014/10/20/ssl-poodle-attack-what-is-this-scsv-thingy/
|
|
# d) minor: we should do "-state" here
|
|
|
|
# first: make sure we have tls1_2:
|
|
$OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI -no_tls1_2 >$TMPFILE 2>$ERRFILE </dev/null
|
|
if ! sclient_connect_successful $? $TMPFILE; then
|
|
pr_done_good "No fallback possible, TLS 1.2 is the only protocol (OK)"
|
|
ret=7
|
|
else
|
|
# ...and do the test (we need to parse the error here!)
|
|
$OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI -no_tls1_2 -fallback_scsv &>$TMPFILE </dev/null
|
|
if grep -q "CONNECTED(00" "$TMPFILE"; then
|
|
if grep -qa "BEGIN CERTIFICATE" "$TMPFILE"; then
|
|
pr_svrty_medium "Downgrade attack prevention NOT supported"
|
|
fileout "fallback_scsv" "NOT ok" "TLS_FALLBACK_SCSV (RFC 7507) (experimental) : Downgrade attack prevention NOT supported"
|
|
ret=1
|
|
elif grep -qa "alert inappropriate fallback" "$TMPFILE"; then
|
|
pr_done_good "Downgrade attack prevention supported (OK)"
|
|
fileout "fallback_scsv" "OK" "TLS_FALLBACK_SCSV (RFC 7507) (experimental) : 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_svrty_medium "\"handshake failure\" instead of \"inappropriate fallback\" (likely NOT ok)"
|
|
fileout "fallback_scsv" "NOT ok" "TLS_FALLBACK_SCSV (RFC 7507) (experimental) : \"handshake failure\" instead of \"inappropriate fallback\" (likely NOT ok)"
|
|
ret=2
|
|
elif grep -qa "ssl handshake failure" "$TMPFILE"; then
|
|
pr_svrty_medium "some unexpected \"handshake failure\" instead of \"inappropriate fallback\" (likely NOT ok)"
|
|
ret=3
|
|
else
|
|
pr_warning "Check failed, unexpected result "
|
|
out ", run $PROG_NAME -Z --debug=1 and look at $TEMPDIR/*tls_fallback_scsv.txt"
|
|
fileout "fallback_scsv" "WARN" "TLS_FALLBACK_SCSV (RFC 7507) (experimental) : Check failed, unexpected result, run $PROG_NAME -Z --debug=1 and look at $TEMPDIR/*tls_fallback_scsv.txt"
|
|
fi
|
|
else
|
|
pr_warning "test failed (couldn't connect)"
|
|
fileout "fallback_scsv" "WARN" "TLS_FALLBACK_SCSV (RFC 7507) (experimental) : Check failed. (couldn't connect)"
|
|
ret=7
|
|
fi
|
|
fi
|
|
|
|
outln
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $ret
|
|
}
|
|
|
|
|
|
# Factoring RSA Export Keys: don't use EXPORT RSA ciphers, see https://freakattack.com/
|
|
run_freak() {
|
|
local -i sclient_success=0
|
|
local -i nr_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_headlineln " Testing for FREAK attack " && outln
|
|
pr_bold " FREAK"; out " (CVE-2015-0204) "
|
|
|
|
nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $exportrsa_cipher_list))
|
|
#echo "========= ${PIPESTATUS[*]}
|
|
|
|
case $nr_supported_ciphers in
|
|
0)
|
|
local_problem_ln "$OPENSSL doesn't have any EXPORT RSA ciphers configured"
|
|
fileout "freak" "WARN" "FREAK (CVE-2015-0204) : Not tested. $OPENSSL doesn't have any EXPORT RSA ciphers configured"
|
|
return 7
|
|
;;
|
|
1|2|3)
|
|
addtl_warning=" ($magenta""tested only with $nr_supported_ciphers out of 9 ciphers only!$off)" ;;
|
|
8|9|10|11)
|
|
addtl_warning="" ;;
|
|
4|5|6|7)
|
|
addtl_warning=" (tested with $nr_supported_ciphers/9 ciphers)" ;;
|
|
esac
|
|
$OPENSSL s_client $STARTTLS $BUGS -cipher $exportrsa_cipher_list -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$?
|
|
[[ $DEBUG -eq 2 ]] && egrep -a "error|failure" $ERRFILE | egrep -av "unable to get local|verify error"
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_svrty_critical "VULNERABLE (NOT ok)"; out ", uses EXPORT RSA ciphers"
|
|
fileout "freak" "NOT ok" "FREAK (CVE-2015-0204) : VULNERABLE (NOT ok), uses EXPORT RSA ciphers"
|
|
else
|
|
pr_done_best "not vulnerable (OK)"; out "$addtl_warning"
|
|
fileout "freak" "OK" "FREAK (CVE-2015-0204) : not vulnerable (OK) $addtl_warning"
|
|
fi
|
|
outln
|
|
|
|
debugme echo $(actually_supported_ciphers $exportrsa_cipher_list)
|
|
debugme echo $nr_supported_ciphers
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $ret
|
|
}
|
|
|
|
|
|
# see https://weakdh.org/logjam.html
|
|
run_logjam() {
|
|
local -i sclient_success=0
|
|
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 nr_supported_ciphers=0
|
|
local addtl_warning=""
|
|
|
|
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for LOGJAM vulnerability " && outln
|
|
pr_bold " LOGJAM"; out " (CVE-2015-4000), experimental "
|
|
|
|
nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $exportdhe_cipher_list))
|
|
|
|
case $nr_supported_ciphers in
|
|
0)
|
|
local_problem_ln "$OPENSSL doesn't have any DHE EXPORT ciphers configured"
|
|
fileout "logjam" "WARN" "LOGJAM (CVE-2015-4000) : Not tested. $OPENSSL doesn't have any DHE EXPORT ciphers configured"
|
|
return 3
|
|
;;
|
|
1|2) addtl_warning=" ($magenta""tested w/ $nr_supported_ciphers/4 ciphers only!$off)" ;;
|
|
3) addtl_warning=" (tested w/ $nr_supported_ciphers/4 ciphers)" ;;
|
|
4) ;;
|
|
esac
|
|
$OPENSSL s_client $STARTTLS $BUGS -cipher $exportdhe_cipher_list -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$?
|
|
debugme egrep -a "error|failure" $ERRFILE | egrep -av "unable to get local|verify error"
|
|
addtl_warning="$addtl_warning, common primes not checked."
|
|
if "$HAS_DH_BITS"; then
|
|
if ! "$do_allciphers" && ! "$do_cipher_per_proto" && "$HAS_DH_BITS"; then
|
|
addtl_warning="$addtl_warning \"$PROG_NAME -E/-e\" spots candidates"
|
|
else
|
|
"$HAS_DH_BITS" && addtl_warning="$addtl_warning See below for any DH ciphers + bit size"
|
|
fi
|
|
fi
|
|
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_svrty_critical "VULNERABLE (NOT ok)"; out ", uses DHE EXPORT ciphers, common primes not checked."
|
|
fileout "logjam" "NOT ok" "LOGJAM (CVE-2015-4000) : VULNERABLE (NOT ok), uses DHE EXPORT ciphers, common primes not checked."
|
|
else
|
|
pr_done_best "not vulnerable (OK)"; out "$addtl_warning"
|
|
fileout "logjam" "OK" "LOGJAM (CVE-2015-4000) : not vulnerable (OK) $addtl_warning"
|
|
fi
|
|
outln
|
|
|
|
debugme echo $(actually_supported_ciphers $exportdhe_cipher_list)
|
|
debugme echo $nr_supported_ciphers
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $sclient_success
|
|
}
|
|
# TODO: perfect candidate for replacement by sockets, so is freak
|
|
|
|
|
|
run_drown() {
|
|
local nr_ciphers_detected
|
|
local spaces=" "
|
|
local cert_fingerprint_sha2=""
|
|
|
|
#FIXME: test for iexistence of RSA key exchange
|
|
if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then
|
|
outln
|
|
pr_headlineln " Testing for DROWN vulnerability "
|
|
outln
|
|
fi
|
|
# if we want to use OPENSSL: check for < openssl 1.0.2g, openssl 1.0.1s if native openssl
|
|
pr_bold " DROWN"; out " (2016-0800, CVE-2016-0703), exper. "
|
|
fd_socket 5 || return 6
|
|
debugme outln "sending client hello... "
|
|
socksend_sslv2_clienthello "$SSLv2_CLIENT_HELLO"
|
|
sockread_serverhello 32768
|
|
debugme outln "reading server hello... "
|
|
if [[ "$DEBUG" -ge 4 ]]; then
|
|
hexdump -C "$SOCK_REPLY_FILE" | head -6
|
|
outln
|
|
fi
|
|
parse_sslv2_serverhello "$SOCK_REPLY_FILE"
|
|
case $? in
|
|
7) # strange reply, couldn't convert the cipher spec length to a hex number
|
|
pr_cyan "strange v2 reply "
|
|
outln " (rerun with DEBUG >=2)"
|
|
[[ $DEBUG -ge 3 ]] && hexdump -C "$SOCK_REPLY_FILE" | head -1
|
|
ret=7
|
|
fileout "DROWN" "MINOR_ERROR" "SSLv2: received a strange SSLv2 reply (rerun with DEBUG>=2)"
|
|
;;
|
|
3) # vulnerable
|
|
lines=$(count_lines "$(hexdump -C "$SOCK_REPLY_FILE" 2>/dev/null)")
|
|
debugme out " ($lines lines) "
|
|
if [[ "$lines" -gt 1 ]]; then
|
|
nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3))
|
|
if [[ 0 -eq "$nr_ciphers_detected" ]]; then
|
|
pr_svrty_highln "CVE-2015-3197: SSLv2 supported but couldn't detect a cipher (NOT ok)";
|
|
fileout "DROWN" "NOT ok" "SSLv2 offered (NOT ok), CVE-2015-3197: but could not detect a cipher"
|
|
else
|
|
pr_svrty_criticalln "vulnerable (NOT ok), SSLv2 offered with $nr_ciphers_detected ciphers";
|
|
fileout "DROWN" "NOT ok" "vulnerable (NOT ok), SSLv2 offered with $nr_ciphers_detected ciphers"
|
|
fi
|
|
fi
|
|
ret=1
|
|
;;
|
|
*) pr_done_bestln "not vulnerable on this port (OK)"
|
|
fileout "DROWN" "OK" "not vulnerable to DROWN"
|
|
outln "$spaces make sure you don't use this certificate elsewhere with SSLv2 enabled services"
|
|
if [[ "$DEBUG" -ge 1 ]] || "$SHOW_CENSYS_LINK"; then
|
|
# not advertising it as it after 5 tries and account is needed
|
|
if [[ -z "$CERT_FINGERPRINT_SHA2" ]]; then
|
|
get_host_cert || return 7
|
|
cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed -e 's/^.*Fingerprint=//' -e 's/://g' )"
|
|
else
|
|
cert_fingerprint_sha2="$CERT_FINGERPRINT_SHA2"
|
|
fi
|
|
cert_fingerprint_sha2=${cert_fingerprint_sha2/SHA256 /}
|
|
outln "$spaces https://censys.io/ipv4?q=$cert_fingerprint_sha2 could help you to find out"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
return $?
|
|
}
|
|
|
|
|
|
|
|
# Browser Exploit Against SSL/TLS: don't use CBC Ciphers in SSLv3 TLSv1.0
|
|
run_beast(){
|
|
local hexcode dash cbc_cipher sslvers kx auth enc mac export
|
|
local detected_proto
|
|
local -i sclient_success=0
|
|
local detected_cbc_ciphers=""
|
|
local higher_proto_supported=""
|
|
local -i sclient_success=0
|
|
local vuln_beast=false
|
|
local spaces=" "
|
|
local cr=$'\n'
|
|
local first=true
|
|
local continued=false
|
|
local cbc_cipher_list="EXP-RC2-CBC-MD5:IDEA-CBC-SHA:EXP-DES-CBC-SHA:DES-CBC-SHA:DES-CBC3-SHA:EXP-DH-DSS-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:DH-DSS-DES-CBC3-SHA:EXP-DH-RSA-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-RSA-DES-CBC3-SHA:EXP-EDH-DSS-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:EDH-DSS-DES-CBC3-SHA:EXP-EDH-RSA-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EXP-ADH-DES-CBC-SHA:ADH-DES-CBC-SHA:ADH-DES-CBC3-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-IDEA-CBC-SHA:KRB5-DES-CBC-MD5:KRB5-DES-CBC3-MD5:KRB5-IDEA-CBC-MD5:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-MD5:EXP-KRB5-RC2-CBC-MD5:AES128-SHA:DH-DSS-AES128-SHA:DH-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DHE-RSA-AES128-SHA:ADH-AES128-SHA:AES256-SHA:DH-DSS-AES256-SHA:DH-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ADH-AES256-SHA:AES128-SHA256:AES256-SHA256:DH-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DHE-RSA-CAMELLIA128-SHA:ADH-CAMELLIA128-SHA:EXP1024-DES-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:DHE-RSA-AES128-SHA256:DH-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA256:ADH-AES128-SHA256:ADH-AES256-SHA256:CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DHE-RSA-CAMELLIA256-SHA:ADH-CAMELLIA256-SHA:PSK-3DES-EDE-CBC-SHA:PSK-AES128-CBC-SHA:PSK-AES256-CBC-SHA:SEED-SHA:DH-DSS-SEED-SHA:DH-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DHE-RSA-SEED-SHA:ADH-SEED-SHA:ECDH-ECDSA-DES-CBC3-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AECDH-DES-CBC3-SHA:AECDH-AES128-SHA:AECDH-AES256-SHA:SRP-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-DSS-AES-256-CBC-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDH-ECDSA-AES128-SHA256:ECDH-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDH-RSA-AES128-SHA256:ECDH-RSA-AES256-SHA384:RC2-CBC-MD5:EXP-RC2-CBC-MD5:IDEA-CBC-MD5:DES-CBC-MD5:DES-CBC3-MD5"
|
|
|
|
if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then
|
|
outln
|
|
pr_headlineln " Testing for BEAST vulnerability "
|
|
fi
|
|
if [[ $VULN_COUNT -le $VULN_THRESHLD ]] || "$WIDE"; then
|
|
outln
|
|
fi
|
|
pr_bold " BEAST"; out " (CVE-2011-3389) "
|
|
# output in wide mode if cipher doesn't exist is not ok
|
|
|
|
>$ERRFILE
|
|
|
|
# first determine whether it's mitigated by higher protocols
|
|
for proto in tls1_1 tls1_2; do
|
|
$OPENSSL s_client -state -"$proto" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI 2>>$ERRFILE >$TMPFILE </dev/null
|
|
if sclient_connect_successful $? $TMPFILE; then
|
|
higher_proto_supported="$higher_proto_supported ""$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol .*://' -e 's/ //g')"
|
|
fi
|
|
done
|
|
|
|
for proto in ssl3 tls1; do
|
|
$OPENSSL s_client -"$proto" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>>$ERRFILE </dev/null
|
|
if ! sclient_connect_successful $? $TMPFILE; then # protocol supported?
|
|
if "$continued"; then # second round: we hit TLS1
|
|
pr_done_goodln "no SSL3 or TLS1 (OK)"
|
|
fileout "beast" "OK" "BEAST (CVE-2011-3389) : not vulnerable (OK) no SSL3 or TLS1"
|
|
return 0
|
|
else # protocol not succeeded but it's the first time
|
|
continued=true
|
|
continue # protocol not supported, so we do not need to check each cipher with that protocol
|
|
"$WIDE" && outln
|
|
fi
|
|
fi # protocol succeeded
|
|
|
|
|
|
# now we test in one shot with the precompiled ciphers
|
|
$OPENSSL s_client -"$proto" -cipher "$cbc_cipher_list" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>>$ERRFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE || continue
|
|
|
|
if "$WIDE"; then
|
|
out "\n "; pr_underline "$(toupper $proto):\n";
|
|
if "$first"; then
|
|
neat_header # NOT_THAT_NICE: we display the header also if in the end no cbc cipher is available on the client side
|
|
fi
|
|
fi
|
|
for ciph in $(colon_to_spaces "$cbc_cipher_list"); do
|
|
read hexcode dash cbc_cipher sslvers kx auth enc mac < <($OPENSSL ciphers -V "$ciph" 2>>$ERRFILE) # -V doesn't work with openssl < 1.0
|
|
# ^^^^^ process substitution as shopt will either segfault or doesn't work with old bash versions
|
|
$OPENSSL s_client -cipher "$cbc_cipher" -"$proto" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>>$ERRFILE </dev/null
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$?
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
vuln_beast=true
|
|
"$WIDE" && first=false
|
|
fi
|
|
if "$WIDE"; then
|
|
normalize_ciphercode "$hexcode"
|
|
if "$SHOW_EACH_C"; then
|
|
[[ -z "$hexcode" ]] && continue
|
|
neat_list "$HEXC" "$cbc_cipher" "$kx" "$enc" #why this is needed?
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
if [[ -n "$higher_proto_supported" ]]; then
|
|
pr_svrty_minorln "available"
|
|
else
|
|
pr_svrty_mediumln "available"
|
|
fi
|
|
|
|
else
|
|
outln "not a/v"
|
|
fi
|
|
else
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
neat_list "$HEXC" "$cbc_cipher" "$kx" "$enc"
|
|
outln
|
|
fi
|
|
fi
|
|
else # short display:
|
|
if [[ $sclient_success -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
|
|
|
|
if ! "$WIDE"; then
|
|
if [[ -n "$detected_cbc_ciphers" ]]; then
|
|
detected_cbc_ciphers=$(echo "$detected_cbc_ciphers" | \
|
|
sed -e "s/ /\\${cr} ${spaces}/12" \
|
|
-e "s/ /\\${cr} ${spaces}/9" \
|
|
-e "s/ /\\${cr} ${spaces}/6" \
|
|
-e "s/ /\\${cr} ${spaces}/3")
|
|
fileout "cbc_$proto" "NOT ok" "BEAST (CVE-2011-3389) : CBC ciphers for $(toupper $proto): $detected_cbc_ciphers"
|
|
! "$first" && out "$spaces"
|
|
out "$(toupper $proto):"
|
|
[[ -n "$higher_proto_supported" ]] && \
|
|
pr_svrty_minorln "$detected_cbc_ciphers" || \
|
|
pr_svrty_mediumln "$detected_cbc_ciphers"
|
|
detected_cbc_ciphers="" # empty for next round
|
|
first=false
|
|
else
|
|
[[ $proto == "tls1" ]] && ! $first && echo -n "$spaces "
|
|
pr_done_goodln "no CBC ciphers for $(toupper $proto) (OK)"
|
|
first=false
|
|
fi
|
|
else
|
|
if ! "$vuln_beast" ; then
|
|
pr_done_goodln " no CBC ciphers for $(toupper $proto) (OK)"
|
|
fileout "cbc_$proto" "OK" "BEAST (CVE-2011-3389) : No CBC ciphers for $(toupper $proto) (OK)"
|
|
fi
|
|
fi
|
|
done # for proto in ssl3 tls1
|
|
|
|
if "$vuln_beast"; then
|
|
if [[ -n "$higher_proto_supported" ]]; then
|
|
if "$WIDE"; then
|
|
outln
|
|
# NOT ok seems too harsh for me if we have TLS >1.0
|
|
pr_svrty_minor "VULNERABLE"
|
|
outln " -- but also supports higher protocols (possible mitigation):$higher_proto_supported"
|
|
else
|
|
out "$spaces"
|
|
pr_svrty_minor "VULNERABLE"
|
|
outln " -- but also supports higher protocols (possible mitigation):$higher_proto_supported"
|
|
fi
|
|
fileout "beast" "NOT ok" "BEAST (CVE-2011-3389) : VULNERABLE -- but also supports higher protocols (possible mitigation):$higher_proto_supported"
|
|
else
|
|
if "$WIDE"; then
|
|
outln
|
|
else
|
|
out "$spaces"
|
|
fi
|
|
pr_svrty_medium "VULNERABLE (NOT ok)"
|
|
outln " -- and no higher protocols as mitigation supported"
|
|
fileout "beast" "NOT ok" "BEAST (CVE-2011-3389) : VULNERABLE -- and no higher protocols as mitigation supported"
|
|
fi
|
|
fi
|
|
"$first" && ! "$vuln_beast" && pr_done_goodln "no CBC ciphers found for any protocol (OK)"
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
run_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"
|
|
fileout "lucky13" "WARN" "LUCKY13 (CVE-2013-0169) : No tested. Not implemented. #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
|
|
run_rc4() {
|
|
local -i rc4_offered=0
|
|
local -i sclient_success
|
|
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"
|
|
local rc4_detected=""
|
|
local available=""
|
|
|
|
if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then
|
|
outln
|
|
pr_headlineln " Checking for vulnerable RC4 Ciphers "
|
|
fi
|
|
if [[ $VULN_COUNT -le $VULN_THRESHLD ]] || "$WIDE"; then
|
|
outln
|
|
fi
|
|
pr_bold " RC4"; out " (CVE-2013-2566, CVE-2015-2808) "
|
|
|
|
$OPENSSL s_client -cipher $rc4_ciphers_list $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE </dev/null
|
|
if sclient_connect_successful $? $TMPFILE; then
|
|
"$WIDE" || pr_svrty_high "VULNERABLE (NOT ok): "
|
|
rc4_offered=1
|
|
if "$WIDE"; then
|
|
outln "\n"
|
|
neat_header
|
|
fi
|
|
while read hexcode dash rc4_cipher sslvers kx auth enc mac; do
|
|
$OPENSSL s_client -cipher $rc4_cipher $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI </dev/null >$TMPFILE 2>$ERRFILE
|
|
sclient_connect_successful $? $TMPFILE
|
|
sclient_success=$? # here we may have a fp with openssl < 1.0, TBC
|
|
if [[ $sclient_success -ne 0 ]] && ! "$SHOW_EACH_C"; then
|
|
continue # no successful connect AND not verbose displaying each cipher
|
|
fi
|
|
if "$WIDE"; then
|
|
#FIXME: JSON+CSV in wide mode is missing
|
|
normalize_ciphercode "$hexcode"
|
|
neat_list "$HEXC" "$rc4_cipher" "$kx" "$enc"
|
|
if "$SHOW_EACH_C"; then
|
|
if [[ $sclient_success -eq 0 ]]; then
|
|
pr_svrty_high "available"
|
|
else
|
|
out "not a/v"
|
|
fi
|
|
else
|
|
rc4_offered=1
|
|
out
|
|
fi
|
|
outln
|
|
else
|
|
pr_svrty_high "$rc4_cipher "
|
|
fi
|
|
rc4_detected+="$rc4_cipher "
|
|
done < <($OPENSSL ciphers -V $rc4_ciphers_list:@STRENGTH)
|
|
outln
|
|
"$WIDE" && pr_svrty_high "VULNERABLE (NOT ok)"
|
|
fileout "rc4" "NOT ok" "RC4 (CVE-2013-2566, CVE-2015-2808) : VULNERABLE (NOT ok) Detected ciphers: $rc4_detected"
|
|
else
|
|
pr_done_goodln "no RC4 ciphers detected (OK)"
|
|
fileout "rc4" "OK" "RC4 (CVE-2013-2566, CVE-2015-2808) : not vulnerable (OK)"
|
|
rc4_offered=0
|
|
fi
|
|
outln
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return $rc4_offered
|
|
}
|
|
|
|
|
|
run_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
|
|
run_tls_truncation() {
|
|
#FIXME: difficult to test, is there any test available: pls let me know
|
|
:
|
|
}
|
|
|
|
|
|
old_fart() {
|
|
outln "Get precompiled bins or compile https://github.com/PeterMosmans/openssl ."
|
|
fileout "old_fart" "WARN" "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed. Get precompiled bins or compile https://github.com/PeterMosmans/openssl ."
|
|
fatal "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed." -2
|
|
}
|
|
|
|
# try very hard to determine the 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/etc/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$RUN_DIR/etc/mapping-rfc.txt"
|
|
[[ -r "$INSTALL_DIR/etc/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$INSTALL_DIR/etc/mapping-rfc.txt"
|
|
if [[ ! -r "$MAPPING_FILE_RFC" ]]; then
|
|
# those will disapper:
|
|
[[ -r "$RUN_DIR/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$RUN_DIR/mapping-rfc.txt"
|
|
[[ -r "$INSTALL_DIR/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$INSTALL_DIR/mapping-rfc.txt"
|
|
fi
|
|
|
|
# we haven't found the mapping file yet...
|
|
if [[ ! -r "$MAPPING_FILE_RFC" ]] && 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 2>/dev/null)
|
|
[[ -r "$INSTALL_DIR/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$INSTALL_DIR/mapping-rfc.txt"
|
|
[[ -r "$INSTALL_DIR/etc/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$INSTALL_DIR/etc/mapping-rfc.txt"
|
|
# will disappear:
|
|
fi
|
|
|
|
# still no mapping file:
|
|
if [[ ! -r "$MAPPING_FILE_RFC" ]] && which realpath &>/dev/null ; then
|
|
INSTALL_DIR=$(dirname $(realpath ${BASH_SOURCE[0]}))
|
|
MAPPING_FILE_RFC="$INSTALL_DIR/etc/mapping-rfc.txt"
|
|
# will disappear
|
|
[[ -r "$INSTALL_DIR/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$INSTALL_DIR/mapping-rfc.txt"
|
|
fi
|
|
|
|
[[ ! -r "$MAPPING_FILE_RFC" ]] && unset MAPPING_FILE_RFC && unset ADD_RFC_STR && pr_warningln "\nNo mapping file found"
|
|
debugme echo "$MAPPING_FILE_RFC"
|
|
}
|
|
|
|
|
|
test_openssl_suffix() {
|
|
local naming_ext="$(uname).$(uname -m)"
|
|
local uname_arch="$(uname -m)"
|
|
local myarch_suffix=""
|
|
|
|
[[ $uname_arch =~ 64 ]] && myarch_suffix=64 || myarch_suffix=32
|
|
if [[ -f "$1/openssl" ]] && [[ -x "$1/openssl" ]]; then
|
|
OPENSSL="$1/openssl"
|
|
return 0
|
|
elif [[ -f "$1/openssl.$naming_ext" ]] && [[ -x "$1/openssl.$naming_ext" ]]; then
|
|
OPENSSL="$1/openssl.$naming_ext"
|
|
return 0
|
|
elif [[ -f "$1/openssl.$uname_arch" ]] && [[ -x "$1/openssl.$uname_arch" ]]; then
|
|
OPENSSL="$1/openssl.$uname_arch"
|
|
return 0
|
|
elif [[ -f "$1/openssl$myarch_suffix" ]] && [[ -x "$1/openssl$myarch_suffix" ]]; then
|
|
OPENSSL="$1/openssl$myarch_suffix"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
|
|
find_openssl_binary() {
|
|
# 0. check environment variable whether it's executable
|
|
if [[ -n "$OPENSSL" ]] && [[ ! -x "$OPENSSL" ]]; then
|
|
pr_warningln "\ncannot find specified (\$OPENSSL=$OPENSSL) binary."
|
|
outln " Looking some place else ..."
|
|
elif [[ -x "$OPENSSL" ]]; then
|
|
: # 1. all ok supplied $OPENSSL was found and has excutable bit set -- testrun comes below
|
|
elif test_openssl_suffix $RUN_DIR; then
|
|
: # 2. otherwise try openssl in path of testssl.sh
|
|
elif test_openssl_suffix $RUN_DIR/bin; then
|
|
: # 3. otherwise here, this is supposed to be the standard --platform independed path in the future!!!
|
|
elif test_openssl_suffix "$(dirname "$(which openssl)")"; then
|
|
: # 5. we tried hard and failed, so now we use the system binaries
|
|
fi
|
|
|
|
# no ERRFILE initialized yet, thus we use /dev/null for stderr directly
|
|
$OPENSSL version -a 2>/dev/null >/dev/null
|
|
if [[ $? -ne 0 ]] || [[ ! -x "$OPENSSL" ]]; then
|
|
fatal "\ncannot exec or find any openssl binary" -1
|
|
fi
|
|
|
|
# http://www.openssl.org/news/openssl-notes.html
|
|
OSSL_VER=$($OPENSSL version 2>/dev/null | 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 2>/dev/null | sed 's/^platform: //')
|
|
OSSL_BUILD_DATE=$($OPENSSL version -a 2>/dev/null | grep '^built' | sed -e 's/built on//' -e 's/: ... //' -e 's/: //' -e 's/ UTC//' -e 's/ +0000//' -e 's/.000000000//')
|
|
echo $OSSL_BUILD_DATE | grep -q "not available" && OSSL_BUILD_DATE=""
|
|
|
|
# see #190, reverting logic: unless otherwise proved openssl has no dh bits
|
|
case "$OSSL_VER_MAJOR.$OSSL_VER_MINOR" in
|
|
1.0.2|1.1.0) HAS_DH_BITS=true ;;
|
|
esac
|
|
# libressl does not have "Server Temp Key" (SSL_get_server_tmp_key)
|
|
|
|
if $OPENSSL version 2>/dev/null | grep -qi LibreSSL; then
|
|
outln
|
|
pr_warning "Please note: LibreSSL is not a good choice for testing INSECURE features!"
|
|
fi
|
|
|
|
$OPENSSL s_client -ssl2 2>&1 | grep -aq "unknown option" || \
|
|
HAS_SSL2=true
|
|
|
|
$OPENSSL s_client -ssl3 2>&1 | grep -aq "unknown option" || \
|
|
HAS_SSL3=true
|
|
|
|
$OPENSSL s_client -help 2>&1 | grep -qw '\-alpn' && \
|
|
HAS_ALPN=true
|
|
|
|
$OPENSSL s_client -help 2>&1 | grep -qw '\-nextprotoneg' && \
|
|
HAS_SPDY=true
|
|
|
|
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 binary provided in \$INSTALLDIR/bin/ or 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
|
|
outln
|
|
}
|
|
|
|
|
|
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> which local ciphers with <pattern> are available?
|
|
(if pattern not a number: word match)
|
|
|
|
$PROG_NAME <options> URI ("$PROG_NAME URI" does everything except -E)
|
|
|
|
-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 (including SPDY/HTTP2)
|
|
-y, --spdy, --npn checks for SPDY/NPN
|
|
-Y, --http2, --alpn checks for HTTP2/ALPN
|
|
-S, --server-defaults displays the server's default picks and certificate info
|
|
-P, --server-preference displays the server's picks: protocol+cipher
|
|
-x, --single-cipher <pattern> tests matched <pattern> of ciphers
|
|
(if <pattern> not a number: word match)
|
|
-c, --client-simulation test client simulations, see which client negotiates with cipher and protocol
|
|
-H, --header, --headers tests HSTS, HPKP, server/app banner, security headers, cookie, reverse proxy, IPv4 address
|
|
|
|
-U, --vulnerable tests all vulnerabilities
|
|
-B, --heartbleed tests for heartbleed vulnerability
|
|
-I, --ccs, --ccs-injection 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
|
|
-D, --drown tests for DROWN vulnerability
|
|
-s, --pfs, --fs, --nsa checks (perfect) forward secrecy settings
|
|
-4, --rc4, --appelbaum which RC4 ciphers are being offered?
|
|
|
|
special invocations:
|
|
-t, --starttls <protocol> does a default run against a STARTTLS enabled <protocol>
|
|
--xmpphost <to_domain> for STARTTLS enabled XMPP it supplies the XML stream to-'' domain -- sometimes needed
|
|
--mx <domain/host> tests MX records from high to low priority (STARTTLS, port 25)
|
|
--ip <ip> a) tests the supplied <ip> v4 or v6 address instead of resolving host(s) in URI
|
|
b) arg "one" means: just test the first DNS returns (useful for multiple IPs)
|
|
--file <fname> mass testing option: Reads command lines from <fname>, one line per instance.
|
|
Comments via # allowed, EOF signals end of <fname>. Implicitly turns on "--warnings batch"
|
|
|
|
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 (can also be preset via environment variables):
|
|
--bugs enables the "-bugs" option of s_client, needed e.g. for some buggy F5s
|
|
--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
|
|
--proxy <host>:<port> connect via the specified HTTP proxy
|
|
-6 use also IPv6. Works only with supporting OpenSSL version and IPv6 connectivity
|
|
--sneaky leave less traces in target logs: user agent, referer
|
|
|
|
output options (can also be preset via environment variables):
|
|
--warnings <batch|off|false> "batch" doesn't wait for keypress, "off" or "false" skips connection warning
|
|
--quiet don't output the banner. By doing this you acknowledge usage terms normally appearing in the banner
|
|
--wide wide output for tests like RC4, BEAST. PFS also with hexcode, kx, strength, RFC name
|
|
--show-each for wide outputs: display all ciphers tested -- not only succeeded ones
|
|
--mapping <no-rfc> don't display the RFC Cipher Suite Name
|
|
--color <0|1|2> 0: no escape or other codes, 1: b/w escape codes, 2: color (default)
|
|
--colorblind swap green and blue in the output
|
|
--debug <0-6> 1: screen output normal but debug output in temp files. 2-6: see line ~120
|
|
|
|
file output options (can also be preset via environment variables):
|
|
--log, --logging logs stdout to <NODE-YYYYMMDD-HHMM.log> in current working directory
|
|
--logfile <file> logs stdout to <file/NODE-YYYYMMDD-HHMM.log> if file is a dir or to specified file
|
|
--json additional output of findings to JSON file <NODE-YYYYMMDD-HHMM.json> in cwd (experimental)
|
|
--jsonfile <file> additional output to JSON and output JSON to the specified file (experimental)
|
|
--csv additional output of findings to CSV file <NODE-YYYYMMDD-HHMM.csv> in cwd (experimental)
|
|
--csvfile <file> set output to CSV and output CSV to the specified file (experimental)
|
|
|
|
All options requiring a value can also be called with '=' e.g. testssl.sh -t=smtp --wide --openssl=/usr/bin/openssl <URI>.
|
|
|
|
<URI> is always the last parameter.
|
|
|
|
Need HTML output? Just pipe through "aha" (ANSI HTML Adapter: github.com/theZiz/aha) like
|
|
|
|
"$PROG_NAME <options> <URI> | aha >output.html"
|
|
EOF
|
|
#' Fix syntax highlight on sublime
|
|
exit $1
|
|
}
|
|
|
|
maketempf() {
|
|
TEMPDIR=$(mktemp -d /tmp/ssltester.XXXXXX) || exit -6
|
|
TMPFILE=$TEMPDIR/tempfile.txt || exit -6
|
|
if [[ "$DEBUG" -eq 0 ]]; then
|
|
ERRFILE="/dev/null"
|
|
else
|
|
ERRFILE=$TEMPDIR/errorfile.txt || exit -6
|
|
fi
|
|
HOSTCERT=$TEMPDIR/host_certificate.txt
|
|
initialize_engine
|
|
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
|
|
|
|
$OPENSSL version -a:
|
|
$($OPENSSL version -a)
|
|
OSSL_VER_MAJOR: $OSSL_VER_MAJOR
|
|
OSSL_VER_MINOR: $OSSL_VER_MINOR
|
|
OSSL_VER_APPENDIX: $OSSL_VER_APPENDIX
|
|
OSSL_BUILD_DATE: $OSSL_BUILD_DATE
|
|
OSSL_VER_PLATFORM: $OSSL_VER_PLATFORM
|
|
|
|
OPENSSL_CONF: $OPENSSL_CONF
|
|
|
|
HAS_IPv6: $HAS_IPv6
|
|
HAS_SSL2: $HAS_SSL2
|
|
HAS_SSL3: $HAS_SSL3
|
|
HAS_SPDY: $HAS_SPDY
|
|
HAS_ALPN: $HAS_ALPN
|
|
|
|
PATH: $PATH
|
|
PROG_NAME: $PROG_NAME
|
|
INSTALL_DIR: $INSTALL_DIR
|
|
RUN_DIR: $RUN_DIR
|
|
MAPPING_FILE_RFC: $MAPPING_FILE_RFC
|
|
|
|
CAPATH: $CAPATH
|
|
ECHO: $ECHO
|
|
COLOR: $COLOR
|
|
COLORBLIND: $COLORBLIND
|
|
TERM_DWITH: $TERM_DWITH
|
|
INTERACTIVE: $INTERACTIVE
|
|
HAS_GNUDATE: $HAS_GNUDATE
|
|
HAS_SED_E: $HAS_SED_E
|
|
|
|
SHOW_EACH_C: $SHOW_EACH_C
|
|
SSL_NATIVE: $SSL_NATIVE
|
|
ASSUMING_HTTP $ASSUMING_HTTP
|
|
SNEAKY: $SNEAKY
|
|
|
|
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 'ALL:COMPLEMENTOFALL' &>$TEMPDIR/all_local_ciphers.txt
|
|
fi
|
|
}
|
|
|
|
|
|
mybanner() {
|
|
local nr_ciphers
|
|
local idtag
|
|
local bb
|
|
local openssl_location="$(which $OPENSSL)"
|
|
local cwd=""
|
|
|
|
$QUIET && return
|
|
nr_ciphers=$(count_ciphers "$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>/dev/null)")
|
|
[[ -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 from $SWURL
|
|
($idtag)
|
|
|
|
This program is free software. Distribution and
|
|
modification under GPLv2 permitted.
|
|
USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK!
|
|
|
|
Please file bugs @ https://testssl.sh/bugs/
|
|
|
|
###########################################################
|
|
EOF
|
|
)
|
|
pr_bold "$bb"
|
|
outln "\n"
|
|
outln " Using \"$($OPENSSL version 2>/dev/null)\" [~$nr_ciphers ciphers]"
|
|
out " on $HNAME:"
|
|
|
|
[[ -n "$GIT_REL" ]] && \
|
|
cwd=$(/bin/pwd) || \
|
|
cwd=$RUN_DIR
|
|
if [[ "$openssl_location" =~ $(/bin/pwd)/bin ]]; then
|
|
OPENSSL_LOCATION="\$PWD/bin/$(basename "$openssl_location")"
|
|
elif [[ "$openssl_location" =~ $cwd ]] && [[ "$cwd" != '.' ]]; then
|
|
OPENSSL_LOCATION="${openssl_location%%$cwd}"
|
|
else
|
|
OPENSSL_LOCATION="$openssl_location"
|
|
fi
|
|
echo "$OPENSSL_LOCATION"
|
|
outln " (built: \"$OSSL_BUILD_DATE\", platform: \"$OSSL_VER_PLATFORM\")\n"
|
|
}
|
|
|
|
|
|
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
|
|
fileout_footer
|
|
}
|
|
|
|
fatal() {
|
|
pr_magentaln "Fatal error: $1" >&2
|
|
exit $2
|
|
}
|
|
|
|
|
|
# for now only GOST engine
|
|
initialize_engine(){
|
|
grep -q '^# testssl config file' "$OPENSSL_CONF" 2>/dev/null && return 0 # have been here already
|
|
|
|
if ! $OPENSSL engine gost -vvvv -t -c 2>/dev/null >/dev/null; then
|
|
outln
|
|
pr_warning "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_warning "No engine or GOST support via engine with your $OPENSSL"; outln
|
|
return 1
|
|
else # we have engine support
|
|
if [[ -n "$OPENSSL_CONF" ]]; then
|
|
pr_warningln "For now I am providing the config file to have GOST support"
|
|
else
|
|
OPENSSL_CONF=$TEMPDIR/gost.conf || exit -6
|
|
# see https://www.mail-archive.com/openssl-users@openssl.org/msg65395.html
|
|
cat >$OPENSSL_CONF << EOF
|
|
# testssl config file for openssl
|
|
|
|
openssl_conf = openssl_def
|
|
|
|
[ openssl_def ]
|
|
engines = engine_section
|
|
|
|
[ engine_section ]
|
|
gost = gost_section
|
|
|
|
[ gost_section ]
|
|
engine_id = gost
|
|
default_algorithms = ALL
|
|
CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet
|
|
|
|
EOF
|
|
export OPENSSL_CONF
|
|
fi
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
|
|
ignore_no_or_lame() {
|
|
local a
|
|
|
|
[[ "$WARNINGS" == "off" ]] && return 0
|
|
[[ "$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() {
|
|
local tmp_port
|
|
|
|
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 [[ -n "$tmp_port" ]]; then
|
|
PORT=$tmp_port
|
|
NODE=$(sed "s/:$PORT//" <<< "$NODE")
|
|
fi
|
|
NODE=$(sed -e 's/\[//' -e 's/\]//' <<< "$NODE")
|
|
else
|
|
# determine v4 port, supposed it was supplied additionally
|
|
echo "$NODE" | grep -q ':' && \
|
|
PORT=$(echo "$NODE" | sed 's/^.*\://') && NODE=$(echo "$NODE" | sed 's/\:.*$//')
|
|
fi
|
|
debugme echo $NODE:$PORT
|
|
SNI="-servername $NODE"
|
|
|
|
# now do logging if instructed
|
|
if "$do_logging"; then
|
|
if [[ -z "$LOGFILE" ]]; then
|
|
LOGFILE=$NODE-$(date +"%Y%m%d-%H%M".log)
|
|
elif [[ -d "$LOGFILE" ]]; then
|
|
# actually we were instructed to place all files in a DIR instead of the current working dir
|
|
LOGFILE=$LOGFILE/$NODE-$(date +"%Y%m%d-%H%M".log)
|
|
else
|
|
: # just for clarity: a log file was specified, no need to do anything else
|
|
fi
|
|
>$LOGFILE
|
|
outln "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE}
|
|
outln "## ($VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE, at $HNAME:$OPENSSL_LOCATION)\n" >>${LOGFILE}
|
|
exec > >(tee -a ${LOGFILE})
|
|
# not decided yet. Maybe good to have a separate file or none at all
|
|
#exec 2> >(tee -a ${LOGFILE} >&2)
|
|
fi
|
|
|
|
if "$do_json"; then
|
|
if [[ -z "$JSONFILE" ]]; then
|
|
JSONFILE=$NODE-$(date +"%Y%m%d-%H%M".json)
|
|
elif [[ -d "$JSONFILE" ]]; then
|
|
# actually we were instructed to place all files in a DIR instead of the current working dir
|
|
JSONFILE=$JSONFILE/$NODE-$(date +"%Y%m%d-%H%M".json)
|
|
fi
|
|
fi
|
|
|
|
if "$do_csv"; then
|
|
if [[ -z "$CSVFILE" ]]; then
|
|
CSVFILE=$NODE-$(date +"%Y%m%d-%H%M".csv)
|
|
elif [[ -d "$CSVFILE" ]]; then
|
|
# actually we were instructed to place all files in a DIR instead of the current working dir
|
|
CSVFILE=$CSVFILE/$NODE-$(date +"%Y%m%d-%H%M".csv)
|
|
fi
|
|
fi
|
|
|
|
fileout_header # write out any CSV/JSON header line
|
|
|
|
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
|
|
}
|
|
|
|
|
|
# args: string containing ip addresses
|
|
filter_ip6_address() {
|
|
local a
|
|
|
|
for a in "$@"; do
|
|
if ! is_ipv6addr "$a"; then
|
|
continue
|
|
fi
|
|
if "$HAS_SED_E"; then
|
|
echo "$a" | sed -E 's/^abcdeABCDEFf0123456789:]//g' | sed -e '/^$/d' -e '/^;;/d'
|
|
else
|
|
echo "$a" | sed -r 's/[^abcdefABCDEF0123456789:]//g' | sed -e '/^$/d' -e '/^;;/d'
|
|
fi
|
|
done
|
|
}
|
|
|
|
filter_ip4_address() {
|
|
local a
|
|
|
|
for a in "$@"; do
|
|
if ! is_ipv4addr "$a"; then
|
|
continue
|
|
fi
|
|
if "$HAS_SED_E"; then
|
|
echo "$a" | sed -E 's/[^[:digit:].]//g' | sed -e '/^$/d'
|
|
else
|
|
echo "$a" | sed -r 's/[^[:digit:].]//g' | sed -e '/^$/d'
|
|
fi
|
|
done
|
|
}
|
|
|
|
get_local_aaaa() {
|
|
local ip6=""
|
|
local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts"
|
|
|
|
# for security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution
|
|
ip6=$(grep -wh "$NODE" $etchosts 2>/dev/null | grep ':' | grep -v '^#' | egrep "[[:space:]]$NODE" | awk '{ print $1 }')
|
|
if is_ipv6addr "$ip6"; then
|
|
echo "$ip6"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
get_local_a() {
|
|
local ip4=""
|
|
local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts"
|
|
|
|
# for security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution
|
|
ip4=$(grep -wh "$1" $etchosts 2>/dev/null | egrep -v ':|^#' | egrep "[[:space:]]$1" | awk '{ print $1 }')
|
|
if is_ipv4addr "$ip4"; then
|
|
echo "$ip4"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
check_resolver_bins() {
|
|
if ! which dig &> /dev/null && ! which host &> /dev/null && ! which drill &> /dev/null && ! which nslookup &>/dev/null; then
|
|
fatal "Neither \"dig\", \"host\", \"drill\" or \"nslookup\" is present" "-3"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# arg1: a host name. Returned will be 0-n IPv4 addresses
|
|
get_a_record() {
|
|
local ip4=""
|
|
local saved_openssl_conf="$OPENSSL_CONF"
|
|
|
|
OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134
|
|
if [[ "$NODE" == *.local ]]; then
|
|
if which avahi-resolve &>/dev/null; then
|
|
ip4=$(filter_ip4_address $(avahi-resolve -4 -n "$1" 2>/dev/null | awk '{ print $2 }'))
|
|
elif which dig &>/dev/null; then
|
|
ip4=$(filter_ip4_address $(dig @224.0.0.251 -p 5353 +short -t a +notcp "$1" 2>/dev/null | sed '/^;;/d'))
|
|
else
|
|
fatal "Local hostname given but no 'avahi-resolve' or 'dig' avaliable."
|
|
fi
|
|
fi
|
|
if [[ -z "$ip4" ]]; then
|
|
which dig &> /dev/null && \
|
|
ip4=$(filter_ip4_address $(dig +short -t a "$1" 2>/dev/null | sed '/^;;/d'))
|
|
fi
|
|
if [[ -z "$ip4" ]]; then
|
|
which host &> /dev/null && \
|
|
ip4=$(filter_ip4_address $(host -t a "$1" 2>/dev/null | grep -v alias | sed 's/^.*address //'))
|
|
fi
|
|
if [[ -z "$ip4" ]]; then
|
|
which drill &> /dev/null && \
|
|
ip4=$(filter_ip4_address $(drill a "$1" 2>/dev/null | awk '/^\;\;\sANSWER\sSECTION\:$/,/\;\;\sAUTHORITY\sSECTION\:$/ { print $5,$6 }' | sed '/^\s$/d'))
|
|
fi
|
|
if [[ -z "$ip4" ]]; then
|
|
if which nslookup &>/dev/null; then
|
|
ip4=$(filter_ip4_address $(nslookup -querytype=a "$1" 2>/dev/null | awk '/^Name/,/EOF/ { print $0 }' | grep -v Name))
|
|
fi
|
|
fi
|
|
OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134
|
|
echo "$ip4"
|
|
}
|
|
|
|
# arg1: a host name. Returned will be 0-n IPv6 addresses
|
|
get_aaaa_record() {
|
|
local ip6=""
|
|
local saved_openssl_conf="$OPENSSL_CONF"
|
|
|
|
OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134
|
|
if [[ -z "$ip6" ]]; then
|
|
if [[ "$NODE" == *.local ]]; then
|
|
if which avahi-resolve &>/dev/null; then
|
|
ip6=$(filter_ip6_address $(avahi-resolve -6 -n "$NODE" 2>/dev/null | awk '{ print $2 }'))
|
|
elif which dig &>/dev/null; then
|
|
ip6=$(filter_ip6_address $(dig @ff02::fb -p 5353 -t aaaa +short +notcp "$NODE"))
|
|
else
|
|
fatal "Local hostname given but no 'avahi-resolve' or 'dig' avaliable."
|
|
fi
|
|
elif which host &> /dev/null ; then
|
|
ip6=$(filter_ip6_address $(host -t aaaa "$NODE" | grep -v alias | grep -v "no AAAA record" | sed 's/^.*address //'))
|
|
elif which dig &> /dev/null; then
|
|
ip6=$(filter_ip6_address $(dig +short -t aaaa "$NODE" 2>/dev/null))
|
|
elif which drill &> /dev/null; then
|
|
ip6=$(filter_ip6_address $(drill aaaa "$NODE" 2>/dev/null | awk '/^\;\;\sANSWER\sSECTION\:$/,/^\;\;\sAUTHORITY\sSECTION\:$/ { print $5,$6 }' | sed '/^\s$/d'))
|
|
elif which nslookup &>/dev/null; then
|
|
ip6=$(filter_ip6_address $(nslookup -type=aaaa "$NODE" 2>/dev/null | grep -A10 Name | grep -v Name))
|
|
fi
|
|
fi
|
|
OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134
|
|
echo "$ip6"
|
|
}
|
|
|
|
|
|
# now get all IP addresses
|
|
determine_ip_addresses() {
|
|
local ip4=""
|
|
local ip6=""
|
|
|
|
if is_ipv4addr "$NODE"; then
|
|
ip4="$NODE" # only an IPv4 address was supplied as an argument, no hostname
|
|
SNI="" # override Server Name Indication as we test the IP only
|
|
else
|
|
ip4=$(get_local_a $NODE) # is there a local host entry?
|
|
if [[ -z $ip4 ]]; then # empty: no (LOCAL_A is predefined as false)
|
|
check_resolver_bins
|
|
ip4=$(get_a_record $NODE)
|
|
else
|
|
LOCAL_A=true # we have the ip4 from local host entry and need to signal this to testssl
|
|
fi
|
|
# same now for ipv6
|
|
ip6=$(get_local_aaaa $NODE)
|
|
if [[ -z $ip6 ]]; then
|
|
check_resolver_bins
|
|
ip6=$(get_aaaa_record $NODE)
|
|
else
|
|
LOCAL_AAAA=true # we have a local ipv6 entry and need to signal this to testssl
|
|
fi
|
|
fi
|
|
if [[ -z "$ip4" ]]; then # IPv6 only address
|
|
if "$HAS_IPv6"; then
|
|
IPADDRs=$(newline_to_spaces "$ip6")
|
|
IP46ADDRs="$IPADDRs" # IP46ADDRs are the ones to display, IPADDRs the ones to test
|
|
fi
|
|
else
|
|
if "$HAS_IPv6" && [[ -n "$ip6" ]]; then
|
|
IPADDRs=$(newline_to_spaces "$ip4 $ip6")
|
|
IP46ADDRs="$IPADDRs"
|
|
else
|
|
IPADDRs=$(newline_to_spaces "$ip4")
|
|
IP46ADDRs=$(newline_to_spaces "$ip4 $ip6")
|
|
fi
|
|
fi
|
|
if [[ -z "$IPADDRs" ]] && [[ -z "$CMDLINE_IP" ]]; then
|
|
fatal "No IPv4 address for \"$NODE\" available" -1
|
|
fi
|
|
return 0 # IPADDR and IP46ADDR is set now
|
|
}
|
|
|
|
determine_rdns() {
|
|
local saved_openssl_conf="$OPENSSL_CONF"
|
|
OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134
|
|
|
|
if [[ "$NODE" == *.local ]]; then
|
|
if which avahi-resolve &>/dev/null; then
|
|
rDNS=$(avahi-resolve -a $NODEIP 2>/dev/null | awk '{ print $2 }')
|
|
elif which dig &>/dev/null; then
|
|
rDNS=$(dig -x $NODEIP @224.0.0.251 -p 5353 +notcp +noall +answer | awk '/PTR/ { print $NF }')
|
|
fi
|
|
elif which dig &> /dev/null; then
|
|
rDNS=$(dig -x $NODEIP +noall +answer | awk '/PTR/ { print $NF }') # +short returns also CNAME, e.g. openssl.org
|
|
elif which host &> /dev/null; then
|
|
rDNS=$(host -t PTR $NODEIP 2>/dev/null | awk '/pointer/ { print $NF }')
|
|
elif which drill &> /dev/null; then
|
|
rDNS=$(drill -x ptr $NODEIP 2>/dev/null | awk '/^\;\;\sANSWER\sSECTION\:$/,/\;\;\sAUTHORITY\sSECTION\:$/ { print $5,$6 }' | sed '/^\s$/d')
|
|
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
|
|
OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134
|
|
rDNS="$(echo $rDNS)"
|
|
[[ -z "$rDNS" ]] && rDNS=" --"
|
|
return 0
|
|
}
|
|
|
|
get_mx_record() {
|
|
local mx=""
|
|
local saved_openssl_conf="$OPENSSL_CONF"
|
|
|
|
OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134
|
|
check_resolver_bins
|
|
if which host &> /dev/null; then
|
|
mxs=$(host -t MX "$1" 2>/dev/null | grep 'handled by' | sed -e 's/^.*by //g' -e 's/\.$//')
|
|
elif which dig &> /dev/null; then
|
|
mxs=$(dig +short -t MX "$1" 2>/dev/null)
|
|
elif which drill &> /dev/null; then
|
|
mxs=$(drill mx "$1" 2>/dev/null | awk '/^\;\;\sANSWER\sSECTION\:$/,/\;\;\sAUTHORITY\sSECTION\:$/ { print $5,$6 }' | sed '/^\s$/d')
|
|
elif which nslookup &> /dev/null; then
|
|
mxs=$(nslookup -type=MX "$1" 2>/dev/null | grep 'mail exchanger = ' | sed 's/^.*mail exchanger = //g')
|
|
else
|
|
fatal "No dig, host, drill or nslookup" -3
|
|
fi
|
|
OPENSSL_CONF="$saved_openssl_conf"
|
|
echo "$mxs"
|
|
}
|
|
|
|
# We need to get the IP address of the proxy so we can use it in fd_socket
|
|
#
|
|
check_proxy() {
|
|
if [[ -n "$PROXY" ]]; then
|
|
if ! $OPENSSL s_client -help 2>&1 | grep -qw proxy; then
|
|
fatal "Your $OPENSSL is too old to support the \"--proxy\" option" -1
|
|
fi
|
|
PROXYNODE=${PROXY%:*}
|
|
PROXYPORT=${PROXY#*:}
|
|
is_number "$PROXYPORT" || fatal "Proxy port cannot be determined from \"$PROXY\"" "-3"
|
|
|
|
#if is_ipv4addr "$PROXYNODE" || is_ipv6addr "$PROXYNODE" ; then
|
|
# IPv6 via openssl -proxy: that doesn't work. Sockets does
|
|
#FIXME: to finish this with LibreSSL which supports an IPv6 proxy
|
|
if is_ipv4addr "$PROXYNODE"; then
|
|
PROXYIP="$PROXYNODE"
|
|
else
|
|
check_resolver_bins
|
|
PROXYIP=$(get_a_record $PROXYNODE 2>/dev/null | grep -v alias | sed 's/^.*address //')
|
|
[[ -z "$PROXYIP" ]] && fatal "Proxy IP cannot be determined from \"$PROXYNODE\"" "-3"
|
|
fi
|
|
PROXY="-proxy $PROXYIP:$PROXYPORT"
|
|
fi
|
|
}
|
|
|
|
|
|
# this is only being called from determine_optimal_proto in order to check whether we have a server
|
|
# with client authentication, a server with no SSL session ID switched off
|
|
#
|
|
sclient_auth() {
|
|
[[ $1 -eq 0 ]] && return 0 # no client auth (CLIENT_AUTH=false is preset globally)
|
|
if [[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]]; then # connect succeeded
|
|
if grep -q '^<<< .*CertificateRequest' "$2"; then # CertificateRequest message in -msg
|
|
CLIENT_AUTH=true
|
|
return 0
|
|
fi
|
|
if [[ -z $(awk '/Session-ID: / { print $2 }' "$2") ]]; then # probably no SSL session
|
|
if [[ 2 -eq $(grep -c CERTIFICATE "$2") ]]; then # do another sanity check to be sure
|
|
CLIENT_AUTH=false
|
|
NO_SSL_SESSIONID=true # NO_SSL_SESSIONID is preset globally to false for all other cases
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
# what's left now is: master key empty, handshake returned not successful, session ID empty --> not sucessful
|
|
return 1
|
|
}
|
|
|
|
|
|
# this function determines OPTIMAL_PROTO. It is a workaround function as under certain circumstances
|
|
# (e.g. IIS6.0 and openssl 1.0.2 as opposed to 1.0.1) needs a protocol otherwise s_client -connect will fail!
|
|
# Circumstances observed so far: 1.) IIS 6 2.) starttls + dovecot imap
|
|
# The first try in the loop is empty as we prefer not to specify always a protocol if it works w/o.
|
|
#
|
|
determine_optimal_proto() {
|
|
local all_failed
|
|
local addcmd=""
|
|
|
|
#TODO: maybe query known openssl version before this workaround. 1.0.1 doesn't need this
|
|
|
|
>$ERRFILE
|
|
if [[ -n "$1" ]]; then
|
|
# starttls workaround needed see https://github.com/drwetter/testssl.sh/issues/188
|
|
# kind of odd
|
|
for STARTTLS_OPTIMAL_PROTO in -tls1_2 -tls1 -ssl3 -tls1_1 -ssl2; do
|
|
$OPENSSL s_client $STARTTLS_OPTIMAL_PROTO $BUGS -connect "$NODEIP:$PORT" $PROXY -msg -starttls $1 </dev/null >$TMPFILE 2>>$ERRFILE
|
|
if sclient_auth $? $TMPFILE; then
|
|
all_failed=1
|
|
break
|
|
fi
|
|
all_failed=0
|
|
done
|
|
debugme echo "STARTTLS_OPTIMAL_PROTO: $STARTTLS_OPTIMAL_PROTO"
|
|
else
|
|
for OPTIMAL_PROTO in '' -tls1_2 -tls1 -ssl3 -tls1_1 -ssl2 ''; do
|
|
$OPENSSL s_client $OPTIMAL_PROTO $BUGS -connect "$NODEIP:$PORT" -msg $PROXY $SNI </dev/null >$TMPFILE 2>>$ERRFILE
|
|
if sclient_auth $? $TMPFILE; then
|
|
all_failed=1
|
|
break
|
|
fi
|
|
all_failed=0
|
|
done
|
|
debugme echo "OPTIMAL_PROTO: $OPTIMAL_PROTO"
|
|
fi
|
|
grep -q '^Server Temp Key' $TMPFILE && HAS_DH_BITS=true # FIX #190
|
|
|
|
if [[ $all_failed -eq 0 ]]; then
|
|
outln
|
|
if "$HAS_IPv6"; then
|
|
pr_bold " Your $OPENSSL is not IPv6 aware, or $NODEIP:$PORT "
|
|
else
|
|
pr_bold " $NODEIP:$PORT "
|
|
fi
|
|
tmpfile_handle $FUNCNAME.txt
|
|
pr_boldln "doesn't seem a TLS/SSL enabled server";
|
|
ignore_no_or_lame " Note that the results might look ok but they are nonsense. Proceed ? "
|
|
[[ $? -ne 0 ]] && exit -2
|
|
fi
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0
|
|
}
|
|
|
|
|
|
# arg1: ftp smtp, pop3, imap, xmpp, telnet, ldap (maybe with trailing s)
|
|
determine_service() {
|
|
local ua
|
|
local protocol
|
|
|
|
if ! fd_socket; then # check if we can connect to $NODEIP:$PORT
|
|
[[ -n "$PROXY" ]] && \
|
|
fatal "You're sure $PROXYNODE:$PROXYPORT allows tunneling here? Can't connect to \"$NODEIP:$PORT\"" -2 || \
|
|
fatal "Can't connect to \"$NODEIP:$PORT\"\nMake sure a firewall is not between you and your scanning target!" -2
|
|
fi
|
|
close_socket
|
|
|
|
datebanner " Start"
|
|
outln
|
|
if [[ -z "$1" ]]; then
|
|
# no STARTTLS.
|
|
determine_optimal_proto "$1"
|
|
$SNEAKY && \
|
|
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
|
|
# STARTTLS
|
|
protocol=${1%s} # strip trailing 's' in ftp(s), smtp(s), pop3(s), etc
|
|
case "$protocol" in
|
|
ftp|smtp|pop3|imap|xmpp|telnet|ldap)
|
|
STARTTLS="-starttls $protocol"
|
|
SNI=""
|
|
if [[ $protocol == "xmpp" ]]; then
|
|
# for XMPP, openssl has a problem using -connect $NODEIP:$PORT. thus we use -connect $NODE:$PORT instead!
|
|
NODEIP="$NODE"
|
|
if [[ -n "$XMPP_HOST" ]]; then
|
|
if ! $OPENSSL s_client --help 2>&1 | grep -q xmpphost; then
|
|
fatal "Your $OPENSSL does not support the \"-xmpphost\" option" -3
|
|
fi
|
|
STARTTLS="$STARTTLS -xmpphost $XMPP_HOST" # it's a hack -- instead of changing calls all over the place
|
|
# see http://xmpp.org/rfcs/rfc3920.html
|
|
fi
|
|
fi
|
|
$OPENSSL s_client -connect $NODEIP:$PORT $PROXY $BUGS $STARTTLS 2>$ERRFILE >$TMPFILE </dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
debugme cat $TMPFILE
|
|
outln
|
|
fatal " $OPENSSL couldn't establish STARTTLS via $protocol to $NODEIP:$PORT" -2
|
|
fi
|
|
grep -q '^Server Temp Key' $TMPFILE && HAS_DH_BITS=true # FIX #190
|
|
out " Service set:$CORRECT_SPACES STARTTLS via "
|
|
toupper "$protocol"
|
|
[[ -n "$XMPP_HOST" ]] && echo -n " (XMPP domain=\'$XMPP_HOST\')"
|
|
outln
|
|
;;
|
|
*) outln
|
|
fatal "momentarily only ftp, smtp, pop3, imap, xmpp, telnet and ldap allowed" -1
|
|
;;
|
|
esac
|
|
fi
|
|
#outln
|
|
|
|
tmpfile_handle $FUNCNAME.txt
|
|
return 0 # OPTIMAL_PROTO, GET_REQ*/HEAD_REQ* is set now
|
|
}
|
|
|
|
|
|
display_rdns_etc() {
|
|
local ip
|
|
|
|
if [[ -n "$PROXY" ]]; then
|
|
out " Via Proxy: $CORRECT_SPACES"
|
|
outln "$PROXYIP:$PROXYPORT "
|
|
fi
|
|
if [[ $(count_words "$IP46ADDRs") -gt 1 ]]; then
|
|
out " further IP addresses: $CORRECT_SPACES"
|
|
for ip in $IP46ADDRs; do
|
|
if [[ "$ip" == "$NODEIP" ]] || [[ "[$ip]" == "$NODEIP" ]]; then
|
|
continue
|
|
else
|
|
out " $ip"
|
|
fi
|
|
done
|
|
outln
|
|
fi
|
|
if "$LOCAL_A"; then
|
|
outln " A record via /etc/hosts "
|
|
elif [[ -n "$CMDLINE_IP" ]]; then
|
|
outln " A record via supplied IP \"$CMDLINE_IP\""
|
|
fi
|
|
if [[ -n "$rDNS" ]]; then
|
|
if "$HAS_IPv6"; then
|
|
printf " %-23s %s" "rDNS $NODEIP:" "$rDNS"
|
|
else
|
|
printf " %-23s %s" "rDNS ($NODEIP):" "$rDNS"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
datebanner() {
|
|
pr_reverse "$1 $(date +%F) $(date +%T) -->> $NODEIP:$PORT ($NODE) <<--"
|
|
outln "\n"
|
|
[[ "$1" =~ Start ]] && display_rdns_etc
|
|
}
|
|
|
|
# one line with char $1 over screen width $2
|
|
draw_line() {
|
|
printf -- "$1"'%.s' $(eval "echo {1.."$(($2))"}")
|
|
}
|
|
|
|
|
|
mx_all_ips() {
|
|
local mxs mx
|
|
local mxport
|
|
local -i ret=0
|
|
|
|
STARTTLS_PROTOCOL="smtp"
|
|
|
|
# test first higher priority servers
|
|
mxs=$(get_mx_record "$1" | sort -n | sed -e 's/^.* //' -e 's/\.$//' | tr '\n' ' ')
|
|
mxport=${2:-25}
|
|
if [[ -n "$mxs" ]] && [[ "$mxs" != ' ' ]]; then
|
|
[[ $mxport == "465" ]] && \
|
|
STARTTLS_PROTOCOL="" # 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_line "-" $((TERM_DWITH * 2 / 3))
|
|
outln
|
|
parse_hn_port "$mx:$mxport"
|
|
determine_ip_addresses || continue
|
|
if [[ $(count_words "$(echo -n "$IPADDRs")") -gt 1 ]]; then # we have more than one ipv4 address to check
|
|
pr_bold "Testing all IPv4 addresses (port $PORT): "; outln "$IPADDRs"
|
|
for ip in $IPADDRs; do
|
|
NODEIP="$ip"
|
|
lets_roll "${STARTTLS_PROTOCOL}"
|
|
done
|
|
else
|
|
NODEIP="$IPADDRs"
|
|
lets_roll "${STARTTLS_PROTOCOL}"
|
|
fi
|
|
ret=$(($? + ret))
|
|
done
|
|
draw_line "-" $((TERM_DWITH * 2 / 3))
|
|
outln
|
|
pr_bold "Done testing now all MX records (on port $mxport): "; outln "$mxs"
|
|
else
|
|
pr_boldln " $1 has no MX records(s)"
|
|
fi
|
|
return $ret
|
|
}
|
|
|
|
run_mass_testing_parallel() {
|
|
local cmdline=""
|
|
local global_cmdline=${CMDLINE%%--file*}
|
|
|
|
if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then
|
|
fatal "Can't read file \"$FNAME\"" "-1"
|
|
fi
|
|
pr_reverse "====== Running in parallel file batch mode with file=\"$FNAME\" ======"; outln
|
|
outln "(output is in ....\n)"
|
|
while read cmdline; do
|
|
cmdline=$(filter_input "$cmdline")
|
|
[[ -z "$cmdline" ]] && continue
|
|
[[ "$cmdline" == "EOF" ]] && break
|
|
cmdline="$0 $global_cmdline --warnings=batch -q $cmdline"
|
|
draw_line "=" $((TERM_DWITH / 2)); outln;
|
|
determine_logfile
|
|
outln "$cmdline"
|
|
$cmdline >$LOGFILE &
|
|
sleep $PARALLEL_SLEEP
|
|
done < "$FNAME"
|
|
return $?
|
|
}
|
|
|
|
|
|
run_mass_testing() {
|
|
local cmdline=""
|
|
local global_cmdline=${CMDLINE%%--file*}
|
|
|
|
if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then
|
|
fatal "Can't read file \"$FNAME\"" "-1"
|
|
fi
|
|
|
|
pr_reverse "====== Running in file batch mode with file=\"$FNAME\" ======"; outln "\n"
|
|
while read cmdline; do
|
|
cmdline=$(filter_input "$cmdline")
|
|
[[ -z "$cmdline" ]] && continue
|
|
[[ "$cmdline" == "EOF" ]] && break
|
|
cmdline="$0 $global_cmdline --warnings=batch -q $cmdline"
|
|
draw_line "=" $((TERM_DWITH / 2)); outln;
|
|
outln "$cmdline"
|
|
$cmdline
|
|
done < "${FNAME}"
|
|
return $?
|
|
}
|
|
|
|
|
|
|
|
# This initializes boolean global do_* variables. They keep track of what to do
|
|
# -- as the name insinuates
|
|
initialize_globals() {
|
|
do_allciphers=false
|
|
do_vulnerabilities=false
|
|
do_beast=false
|
|
do_breach=false
|
|
do_ccs_injection=false
|
|
do_cipher_per_proto=false
|
|
do_crime=false
|
|
do_freak=false
|
|
do_logjam=false
|
|
do_drown=false
|
|
do_header=false
|
|
do_heartbleed=false
|
|
do_mx_all_ips=false
|
|
do_mass_testing=false
|
|
do_logging=false
|
|
do_json=false
|
|
do_csv=false
|
|
do_pfs=false
|
|
do_protocols=false
|
|
do_rc4=false
|
|
do_renego=false
|
|
do_std_cipherlists=false
|
|
do_server_defaults=false
|
|
do_server_preference=false
|
|
do_spdy=false
|
|
do_http2=false
|
|
do_ssl_poodle=false
|
|
do_tls_fallback_scsv=false
|
|
do_test_just_one=false
|
|
do_tls_sockets=false
|
|
do_client_simulation=false
|
|
do_display_only=false
|
|
}
|
|
|
|
|
|
# Set default scanning options for the boolean global do_* variables.
|
|
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_drown=true
|
|
do_header=true
|
|
do_heartbleed=true
|
|
do_pfs=true
|
|
do_protocols=true
|
|
do_rc4=true
|
|
do_renego=true
|
|
do_std_cipherlists=true
|
|
do_server_defaults=true
|
|
do_server_preference=true
|
|
do_spdy=true
|
|
do_http2=true
|
|
do_ssl_poodle=true
|
|
do_tls_fallback_scsv=true
|
|
do_client_simulation=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_drown do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_renego \
|
|
do_std_cipherlists do_server_defaults do_server_preference do_spdy do_http2 do_ssl_poodle do_tls_fallback_scsv \
|
|
do_client_simulation do_test_just_one do_tls_sockets do_mass_testing do_display_only; do
|
|
[[ "${!gbl}" == "true" ]] && let true_nr++
|
|
done
|
|
return $true_nr
|
|
}
|
|
|
|
|
|
debug_globals() {
|
|
local gbl
|
|
|
|
for gbl in do_allciphers do_vulnerabilities do_beast do_breach do_ccs_injection do_cipher_per_proto do_crime \
|
|
do_freak do_logjam do_drown do_header do_heartbleed do_rc4 do_mx_all_ips do_pfs do_protocols do_rc4 do_renego \
|
|
do_std_cipherlists do_server_defaults do_server_preference do_spdy do_http2 do_ssl_poodle do_tls_fallback_scsv \
|
|
do_client_simulation do_test_just_one do_tls_sockets do_mass_testing do_display_only; do
|
|
printf "%-22s = %s\n" $gbl "${!gbl}"
|
|
done
|
|
printf "%-22s : %s\n" URI: "$URI"
|
|
}
|
|
|
|
|
|
# arg1: either switch+value (=) or switch
|
|
# arg2: value (if no = provided)
|
|
parse_opt_equal_sign() {
|
|
if [[ "$1" == *=* ]]; then
|
|
echo ${1#*=}
|
|
return 1 # = means we don't need to shift args!
|
|
else
|
|
echo $2
|
|
return 0 # we need to shift
|
|
fi
|
|
}
|
|
|
|
|
|
parse_cmd_line() {
|
|
# 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
|
|
maketempf
|
|
mybanner
|
|
exit 0
|
|
;;
|
|
--mx)
|
|
do_mx_all_ips=true
|
|
PORT=25
|
|
;;
|
|
--mx465) # doesn't work with major ISPs
|
|
do_mx_all_ips=true
|
|
PORT=465
|
|
;;
|
|
--mx587) # doesn't work with major ISPs
|
|
do_mx_all_ips=true
|
|
PORT=587
|
|
;;
|
|
--ip|--ip=*)
|
|
CMDLINE_IP=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
;;
|
|
-V|-V=*|--local|--local=*) # attention, this could have a value or not!
|
|
do_display_only=true
|
|
PATTERN2SHOW="$(parse_opt_equal_sign "$1" "$2")"
|
|
retval=$?
|
|
if [[ "$PATTERN2SHOW" == -* ]]; then
|
|
unset PATTERN2SHOW # we hit the next command ==> not our value
|
|
else # it was ours, point to next arg
|
|
[[ $retval -eq 0 ]] && shift
|
|
fi
|
|
;;
|
|
-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
|
|
case $STARTTLS_PROTOCOL in
|
|
ftp|smtp|pop3|imap|xmpp|telnet|ldap|nntp) ;;
|
|
ftps|smtps|pop3s|imaps|xmpps|telnets|ldaps|nntps) ;;
|
|
*) pr_magentaln "\nunrecognized STARTTLS protocol \"$1\", see help" 1>&2
|
|
help 1 ;;
|
|
esac
|
|
;;
|
|
--xmpphost|--xmpphost=*)
|
|
XMPP_HOST=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
;;
|
|
-e|--each-cipher)
|
|
do_allciphers=true
|
|
;;
|
|
-E|--cipher-per-proto|--cipher_per_proto)
|
|
do_cipher_per_proto=true
|
|
;;
|
|
-p|--protocols)
|
|
do_protocols=true
|
|
do_spdy=true
|
|
do_http2=true
|
|
;;
|
|
-y|--spdy|--npn)
|
|
do_spdy=true
|
|
;;
|
|
-Y|--http2|--alpn)
|
|
do_http2=true
|
|
;;
|
|
-f|--ciphers)
|
|
do_std_cipherlists=true
|
|
;;
|
|
-S|--server[-_]defaults)
|
|
do_server_defaults=true
|
|
;;
|
|
-P|--server[_-]preference|--preference)
|
|
do_server_preference=true
|
|
;;
|
|
-H|--header|--headers)
|
|
do_header=true
|
|
;;
|
|
-c|--client-simulation)
|
|
do_client_simulation=true
|
|
;;
|
|
-U|--vulnerable)
|
|
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_drown=true
|
|
do_logjam=true
|
|
do_beast=true
|
|
do_rc4=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++"
|
|
;;
|
|
-D|--drown)
|
|
do_drown=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
|
|
;;
|
|
--devel) ### this development feature will soon disappear
|
|
HEX_CIPHER=""
|
|
# DEBUG=3 ./testssl.sh --devel 03 "cc, 13, c0, 13" google.de --> TLS 1.2, old CHACHA/POLY
|
|
# DEBUG=3 ./testssl.sh --devel 03 "cc,a8, cc,a9, cc,aa, cc,ab, cc,ac" blog.cloudflare.com --> new CHACHA/POLY
|
|
# DEBUG=3 ./testssl.sh --devel 01 yandex.ru --> TLS 1.0
|
|
# DEBUG=3 ./testssl.sh --devel 00 <host which supports SSLv3>
|
|
# DEBUG=3 ./testssl.sh --devel 22 <host which still supports SSLv2>
|
|
TLS_LOW_BYTE="$2";
|
|
if [[ $# -eq 4 ]]; then # protocol AND ciphers specified
|
|
HEX_CIPHER="$3"
|
|
shift
|
|
fi
|
|
shift
|
|
do_tls_sockets=true
|
|
outln "\nTLS_LOW_BYTE/HEX_CIPHER: ${TLS_LOW_BYTE}/${HEX_CIPHER}"
|
|
;;
|
|
--wide)
|
|
WIDE=true
|
|
;;
|
|
--assuming[_-]http|--assume[-_]http)
|
|
ASSUMING_HTTP=true
|
|
;;
|
|
--sneaky)
|
|
SNEAKY=true
|
|
;;
|
|
-q|--quiet)
|
|
QUIET=true
|
|
;;
|
|
--file|--file=*)
|
|
# no shift here as otherwise URI is empty and it bails out
|
|
FNAME=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
IKNOW_FNAME=true
|
|
WARNINGS=batch # set this implicitly!
|
|
do_mass_testing=true
|
|
;;
|
|
--warnings|--warnings=*)
|
|
WARNINGS=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
case "$WARNINGS" in
|
|
batch|off|false) ;;
|
|
*) pr_magentaln "\nwarnings can be either \"batch\", \"off\" or \"false\""
|
|
help 1
|
|
esac
|
|
;;
|
|
--show[-_]each)
|
|
SHOW_EACH_C=true
|
|
;;
|
|
--bugs)
|
|
BUGS="-bugs"
|
|
;;
|
|
--debug|--debug=*)
|
|
DEBUG=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
case $DEBUG in
|
|
[0-6]) ;;
|
|
*) pr_magentaln "\nunrecognized debug value \"$1\", must be between 0..6" 1>&2
|
|
help 1
|
|
esac
|
|
;;
|
|
--color|--color=*)
|
|
COLOR=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
case $COLOR in
|
|
[0-2]) ;;
|
|
*) COLOR=2
|
|
pr_magentaln "\nunrecognized color: \"$1\", must be between 0..2" 1>&2
|
|
help 1
|
|
esac
|
|
;;
|
|
--colorblind)
|
|
COLORBLIND=true
|
|
;;
|
|
--log|--logging)
|
|
do_logging=true
|
|
;; # DEFINITION of LOGFILE if no arg specified: automagically in parse_hn_port()
|
|
# following does the same but we can specify a log location additionally
|
|
--logfile|--logfile=*)
|
|
LOGFILE=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
do_logging=true
|
|
;;
|
|
--json)
|
|
do_json=true
|
|
;; # DEFINITION of JSONFILE is not arg specified: automagically in parse_hn_port()
|
|
# following does the same but we can specify a log location additionally
|
|
--jsonfile|--jsonfile=*)
|
|
JSONFILE=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
do_json=true
|
|
;;
|
|
--csv)
|
|
do_csv=true
|
|
;; # DEFINITION of CSVFILE is not arg specified: automagically in parse_hn_port()
|
|
# following does the same but we can specify a log location additionally
|
|
--csvfile|--csvfile=*)
|
|
CSVFILE=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
do_csv=true
|
|
;;
|
|
--openssl|--openssl=*)
|
|
OPENSSL=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
;;
|
|
--mapping|--mapping=*)
|
|
local cipher_mapping
|
|
cipher_mapping=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
case "$cipher_mapping" in
|
|
no-rfc) unset ADD_RFC_STR;;
|
|
*) pr_magentaln "\nmapping can only be \"no-rfc\""
|
|
help 1 ;;
|
|
esac
|
|
;;
|
|
--proxy|--proxy=*)
|
|
PROXY=$(parse_opt_equal_sign "$1" "$2")
|
|
[[ $? -eq 0 ]] && shift
|
|
;;
|
|
-6) # doesn't work automagically. My versions have -DOPENSSL_USE_IPV6, CentOS/RHEL/FC do not
|
|
HAS_IPv6=true
|
|
;;
|
|
--has[-_]dhbits|--has[_-]dh[-_]bits) # For CentOS, RHEL and FC with openssl server temp key backport on version 1.0.1, see #190. But should work automagically
|
|
HAS_DH_BITS=true
|
|
;;
|
|
--ssl_native|--ssl-native)
|
|
SSL_NATIVE=true
|
|
;;
|
|
(--) shift
|
|
break
|
|
;;
|
|
(-*) pr_magentaln "0: unrecognized option \"$1\"" 1>&2;
|
|
help 1
|
|
;;
|
|
(*) break
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Show usage if no options were specified
|
|
if [[ -z "$1" ]] && [[ -z "$FNAME" ]] && ! $do_display_only; then
|
|
help 0
|
|
else
|
|
# left off here is the URI
|
|
URI="$1"
|
|
# parameter after URI supplied:
|
|
[[ -n "$2" ]] && echo && fatal "URI comes last" "1"
|
|
fi
|
|
|
|
[[ "$DEBUG" -ge 5 ]] && 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
|
|
}
|
|
|
|
|
|
# connect call from openssl needs ipv6 in square brackets
|
|
nodeip_to_proper_ip6() {
|
|
local len_nodeip=0
|
|
|
|
if is_ipv6addr $NODEIP; then
|
|
NODEIP="[$NODEIP]"
|
|
len_nodeip=${#NODEIP}
|
|
CORRECT_SPACES="$(draw_line " " "$((len_nodeip - 16))" )"
|
|
# IPv6 addresses are longer, this varaible takes care that "further IP" and "Service" is properly aligned
|
|
fi
|
|
}
|
|
|
|
|
|
reset_hostdepended_vars() {
|
|
TLS_EXTENSIONS=""
|
|
PROTOS_OFFERED=""
|
|
OPTIMAL_PROTO=""
|
|
}
|
|
|
|
|
|
lets_roll() {
|
|
local ret
|
|
|
|
[[ -z "$NODEIP" ]] && fatal "$NODE doesn't resolve to an IP address" -1
|
|
nodeip_to_proper_ip6
|
|
reset_hostdepended_vars
|
|
determine_rdns
|
|
determine_service "$1" # any starttls service goes here
|
|
|
|
$do_tls_sockets && { [[ $TLS_LOW_BYTE -eq 22 ]] && \
|
|
sslv2_sockets || \
|
|
tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER"; echo "$?" ; exit 0; }
|
|
$do_test_just_one && test_just_one ${single_cipher}
|
|
|
|
# all top level functions now following have the prefix "run_"
|
|
$do_protocols && { run_protocols; ret=$(($? + ret)); }
|
|
$do_spdy && { run_spdy; ret=$(($? + ret)); }
|
|
$do_http2 && { run_http2; ret=$(($? + ret)); }
|
|
$do_std_cipherlists && { run_std_cipherlists; ret=$(($? + ret)); }
|
|
$do_pfs && { run_pfs; ret=$(($? + ret)); }
|
|
$do_server_preference && { run_server_preference; ret=$(($? + ret)); }
|
|
$do_server_defaults && { run_server_defaults; ret=$(($? + ret)); }
|
|
|
|
if $do_header; then
|
|
#TODO: refactor this into functions
|
|
if [[ $SERVICE == "HTTP" ]]; then
|
|
run_http_header "$URL_PATH"
|
|
run_http_date "$URL_PATH"
|
|
run_hsts "$URL_PATH"
|
|
run_hpkp "$URL_PATH"
|
|
run_server_banner "$URL_PATH"
|
|
run_application_banner "$URL_PATH"
|
|
run_cookie_flags "$URL_PATH"
|
|
run_more_flags "$URL_PATH"
|
|
run_rp_banner "$URL_PATH"
|
|
fi
|
|
fi
|
|
|
|
# vulnerabilities
|
|
if [[ $VULN_COUNT -gt $VULN_THRESHLD ]] || $do_vulnerabilities; then
|
|
outln; pr_headlineln " Testing vulnerabilities "
|
|
outln
|
|
fi
|
|
$do_heartbleed && { run_heartbleed; ret=$(($? + ret)); }
|
|
$do_ccs_injection && { run_ccs_injection; ret=$(($? + ret)); }
|
|
$do_renego && { run_renego; ret=$(($? + ret)); }
|
|
$do_crime && { run_crime; ret=$(($? + ret)); }
|
|
$do_breach && { run_breach "$URL_PATH" ; ret=$(($? + ret)); }
|
|
$do_ssl_poodle && { run_ssl_poodle; ret=$(($? + ret)); }
|
|
$do_tls_fallback_scsv && { run_tls_fallback_scsv; ret=$(($? + ret)); }
|
|
$do_freak && { run_freak; ret=$(($? + ret)); }
|
|
$do_drown && { run_drown ret=$(($? + ret)); }
|
|
$do_logjam && { run_logjam; ret=$(($? + ret)); }
|
|
$do_beast && { run_beast; ret=$(($? + ret)); }
|
|
$do_rc4 && { run_rc4; ret=$(($? + ret)); }
|
|
|
|
$do_allciphers && { run_allciphers; ret=$(($? + ret)); }
|
|
$do_cipher_per_proto && { run_cipher_per_proto; ret=$(($? + ret)); }
|
|
$do_client_simulation && { run_client_simulation; ret=$(($? + ret)); }
|
|
|
|
outln
|
|
datebanner " Done"
|
|
|
|
return $ret
|
|
}
|
|
|
|
|
|
|
|
################# main #################
|
|
|
|
get_install_dir
|
|
|
|
initialize_globals
|
|
parse_cmd_line "$@"
|
|
set_color_functions
|
|
find_openssl_binary
|
|
maketempf
|
|
mybanner
|
|
check_proxy
|
|
openssl_age
|
|
|
|
# TODO: it is ugly to have those two vars here --> main()
|
|
ret=0
|
|
ip=""
|
|
|
|
if $do_display_only; then
|
|
prettyprint_local "$PATTERN2SHOW"
|
|
exit $?
|
|
fi
|
|
|
|
if $do_mass_testing; then
|
|
run_mass_testing
|
|
exit $?
|
|
fi
|
|
|
|
#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
|
|
mx_all_ips "${URI}" $PORT
|
|
ret=$?
|
|
else
|
|
parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now
|
|
if ! determine_ip_addresses && [[ -z "$CMDLINE_IP" ]]; then
|
|
fatal "No IP address could be determined"
|
|
fi
|
|
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 [[ $(count_words "$(echo -n "$IPADDRs")") -gt 1 ]]; then # we have more than one ipv4 address to check
|
|
pr_bold "Testing all IPv4 addresses (port $PORT): "; outln "$IPADDRs"
|
|
for ip in $IPADDRs; do
|
|
draw_line "-" $((TERM_DWITH * 2 / 3))
|
|
outln
|
|
NODEIP="$ip"
|
|
lets_roll "${STARTTLS_PROTOCOL}"
|
|
ret=$(($? + ret))
|
|
done
|
|
draw_line "-" $((TERM_DWITH * 2 / 3))
|
|
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 $?
|
|
|
|
|
|
# $Id: testssl.sh,v 1.481 2016/04/21 16:44:56 dirkw Exp $
|