2015-05-17 22:43:53 +02:00
#!/usr/bin/env bash
#
2015-06-19 20:36:32 +02:00
# vim:ts=5:sw=5
# use vim and you will see everything beautifully indented with a 5 char tab
2015-07-22 13:11:20 +02:00
[ -z " $BASH_VERSINFO " ] && echo " \n $( tput setaf 5) Please make sure you're using bash! Bye... $( tput sgr0) \n " && exit -10
2015-06-19 20:36:32 +02:00
2015-05-29 19:44:27 +02:00
# testssl.sh is a program for spotting weak SSL encryption, ciphers, version and some
2015-05-29 19:56:57 +02:00
# vulnerabilities or features
2015-05-17 22:43:53 +02:00
#
2015-05-27 11:19:30 +02:00
# 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
2015-05-17 22:43:53 +02:00
# Main author: Dirk Wetter, copyleft: 2007-2015, contributions so far see CREDIT.md
#
# License: GPLv2, see http://www.fsf.org/licensing/licenses/info/GPLv2.html
# and accompanying license "LICENSE.txt". Redistribution + modification under this
2015-05-29 19:44:27 +02:00
# license permitted.
2015-05-17 22:43:53 +02:00
# If you enclose this script or parts of it in your software, it has to
# be accompanied by the same license (see link) and the place where to get
# the recent version of this program. Don't violate the license!
#
# USAGE WITHOUT ANY WARRANTY, THE SOFTWARE IS PROVIDED "AS IS". USE IT AT
# your OWN RISK!
# HISTORY: I know this shell script is still on its way to be nice and readable. ;-) It
2015-06-16 14:04:44 +02:00
# all started with a few openssl commands around 2006 for me as a pentester.
# That's because openssl is a such a good swiss army knife (see e.g.
# wiki.openssl.org/index.php/Command_Line_Utilities) that it was difficult to resist
# wrapping some shell commands around it, which I used for my pen tests. This is how everything started.
2015-05-17 22:43:53 +02:00
# Now it has grown up, it has bash socket support for some features which basically replacing
# more and more functions of OpenSSL and will serve as some kind of library in the future.
# The socket checks in bash may sound cool and unique -- they are -- but probably you
2015-05-27 11:19:30 +02:00
# can achieve e.g. the same result with my favorite interactive shell: zsh (zmodload zsh/net/socket
2015-06-16 14:04:44 +02:00
# -- checkout zsh/net/tcp) too! /bin/bash though is way more often used within Linux and it's perfect
2015-05-29 19:56:57 +02:00
# for cross platform support, see MacOS X and also under Windows the MSYS2 extension.
2015-05-27 11:19:30 +02:00
# Cross-platform is one of the three ideas of this script. Second: Ease of installation.
# No compiling, install gems, go to CPAN, use pip etc. Third: Easy to use and to interpret
# the result.
#
# Did I mention it's open source?
2015-05-17 22:43:53 +02:00
#
2015-07-06 10:10:46 +02:00
# Q: So what's the difference to www.ssllabs.com/ssltesti/ or sslcheck.globalsign.com/ ?
# A: As of now ssllabs only check 1) webservers 2) on standard ports, 3) reachable from the
# internet. And the examples above 4) are 3rd parties. If those four restrictions are 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.
2015-05-17 22:43:53 +02:00
#
2015-05-27 11:19:30 +02:00
# Oh, and did I mention testssl.sh is open source?
#
2015-05-17 22:43:53 +02:00
# Note that for "standard" openssl binaries a lot of features (ciphers, protocols, vulnerabilities)
# are disabled as they'll impact security otherwise. For security testing though we need
2015-05-29 19:56:57 +02:00
# all broken features. testssl.sh will over time replace those checks with bash sockets --
2015-05-29 19:44:27 +02:00
# however it's still recommended to use the supplied binaries or cook your own, see
2015-05-17 22:43:53 +02:00
# https://github.com/drwetter/testssl.sh/blob/master/openssl-bins/openssl-1.0.2-chacha.pm/Readme.md
# Don't worry if feature X is not available you'll get a warning about this missing feature!
2015-06-16 14:04:44 +02:00
# The idea is if this script can't tell something for sure it speaks up so that you have
# a better picture.
2015-05-17 22:43:53 +02:00
2015-06-02 15:53:46 +02:00
# debugging help:
2015-07-14 17:13:58 +02:00
readonly PS4 = '${LINENO}> ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
2015-06-02 15:53:46 +02:00
2015-06-16 19:53:40 +02:00
# make sure that temporary files are cleaned up after use in ANY case
2015-06-02 15:53:46 +02:00
trap "cleanup" QUIT EXIT
2015-08-02 01:25:39 +02:00
readonly VERSION = "2.6rc1"
2015-06-16 19:53:40 +02:00
readonly SWCONTACT = "dirk aet testssl dot sh"
2015-08-02 01:25:39 +02:00
echo $VERSION | egrep -q "dev|rc" && \
2015-07-10 10:23:10 +02:00
SWURL = "https://testssl.sh/dev/" ||
SWURL = "https://testssl.sh "
2015-06-16 19:53:40 +02:00
2015-05-17 22:43:53 +02:00
readonly PROG_NAME = $( basename " $0 " )
readonly RUN_DIR = $( dirname $0 )
2015-06-02 22:13:19 +02:00
INSTALL_DIR = ""
MAP_RFC_FNAME = ""
2015-05-17 22:43:53 +02:00
2015-05-31 14:40:12 +02:00
which git & >/dev/null && readonly GIT_REL = $( git log --format= '%h %ci' -1 2>/dev/null | awk '{ print $1" "$2" "$3 }' )
readonly CVS_REL = $( tail -5 $0 | awk '/dirkw Exp/ { print $4" "$5" "$6}' )
readonly CVS_REL_SHORT = $( tail -5 $0 | awk '/dirkw Exp/ { print $4 }' )
2015-06-16 19:53:40 +02:00
readonly SYSTEM = $( uname -s)
date --help >/dev/null 2>& 1 && \
readonly HAS_GNUDATE = true || \
readonly HAS_GNUDATE = false
2015-06-17 11:33:29 +02:00
echo A | sed -E 's/A//' >/dev/null 2>& 1 && \
readonly HAS_SED_E = true || \
readonly HAS_SED_E = false
2015-06-16 19:53:40 +02:00
readonly ECHO = "/usr/bin/printf --" # works under Linux, BSD, MacOS.
TERM_DWITH = ${ COLUMNS :- $( tput cols) } # for future custom line wrapping
TERM_CURRPOS = 0 # ^^^ we also need to find out the length or current pos in the line
2015-05-31 14:40:12 +02:00
2015-05-17 22:43:53 +02:00
# 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
2015-06-16 19:53:40 +02:00
# we have tab indentation with 5 virtual chars!
2015-07-12 18:46:27 +02:00
declare -x OPENSSL
2015-06-16 19:53:40 +02:00
COLOR = ${ COLOR :- 2 } # 2: Full color, 1: b/w+positioning, 0: no ESC at all
2015-07-06 10:10:46 +02:00
SHOW_EACH_C = ${ SHOW_EACH_C :- 0 } # where individual ciphers are tested show just the positively ones tested #FIXME: upside down value
SNEAKY = ${ SNEAKY :- false } # is the referer and useragent we leave behind just usual?
2015-07-25 14:33:08 +02:00
QUIET = ${ QUIET :- false } # don't output the banner. By doing this yiu acknowledge usage term appearing in the banner
2015-07-06 10:10:46 +02:00
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
2015-06-16 19:53:40 +02:00
DEBUG = ${ DEBUG :- 0 } # if 1 the temp files won't be erased. 2: list more what's going on (formerly: eq VERBOSE=1),
# 3: slight hexdumps + other info, 4: send bytes via sockets, 5: received, 6: whole 9 yards
# FIXME: still to be filled with (more) sense or following to be included:
2015-07-06 10:10:46 +02:00
VERBERR = ${ VERBERR :- false } # true means to be more verbose (handshake errors to be displayed so that one can tell better
2015-06-16 19:53:40 +02:00
# whether handshake succeeded or not. While testing individual ciphers you also need to have SHOW_EACH_C=1
2015-07-06 10:10:46 +02:00
#FIXME: only a few functions support this
WIDE = ${ WIDE :- false } # whether to display for some options the cipher or the table with hexcode/KX,Enc,strength etc.
2015-05-17 22:43:53 +02:00
2015-06-16 19:53:40 +02:00
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
2015-05-25 15:10:09 +02:00
readonly CCS_MAX_WAITSOCK = 5 # for the two CCS payload (each)
readonly HEARTBLEED_MAX_WAITSOCK = 8 # for the heartbleed payload
2015-07-17 14:33:23 +02:00
readonly STARTTLS_SLEEP = ${ STARTTLS_SLEEP :- 1 } # max time to wait on a socket replay for STARTTLS
2015-07-08 21:30:31 +02:00
FAST_STARTTLS = ${ FAST_STARTTLS :- true } #at the cost of reliabilty decrese the handshakes for STARTTLS
2015-05-17 22:43:53 +02:00
USLEEP_SND = ${ USLEEP_SND :- 0 .1 } # sleep time for general socket send
2015-06-16 19:53:40 +02:00
USLEEP_REC = ${ USLEEP_REC :- 0 .2 } # sleep time for general socket receive
2015-05-17 22:43:53 +02:00
2015-06-16 19:53:40 +02:00
CAPATH = " ${ CAPATH :- /etc/ssl/certs/ } " # Does nothing yet (FC has only a CA bundle per default, ==> openssl version -d)
2015-08-12 00:17:28 +02:00
FNAME = ${ FNAME :- "" } # file name to read commands from
2015-08-01 23:11:27 +02:00
IKNOW_FNAME = false
2015-05-17 22:43:53 +02:00
readonly HSTS_MIN = 179 # >179 days is ok for HSTS
readonly HPKP_MIN = 30 # >=30 days should be ok for HPKP_MIN, practical hints?
readonly CLIENT_MIN_PFS = 5 # number of ciphers needed to run a test for PFS
readonly DAYS2WARN1 = 60 # days to warn before cert expires, threshold 1
readonly DAYS2WARN2 = 30 # days to warn before cert expires, threshold 2
2015-06-16 19:53:40 +02:00
# furher vars needed to follow
2015-05-17 22:43:53 +02:00
readonly NPN_PROTOs = "spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1"
TEMPDIR = ""
2015-05-18 21:51:45 +02:00
TMPFILE = ""
HOSTCERT = ""
HEADERFILE = ""
HEADERFILE_BREACH = ""
LOGFILE = ""
2015-05-17 22:43:53 +02:00
TLS_PROTO_OFFERED = ""
DETECTED_TLS_VERSION = ""
SOCKREPLY = ""
SOCK_REPLY_FILE = ""
HEXC = ""
NW_STR = ""
LEN_STR = ""
SNI = ""
2015-06-16 19:53:40 +02:00
OSSL_VER = "" # openssl version, will be auto-determined
2015-05-17 22:43:53 +02:00
OSSL_VER_MAJOR = 0
OSSL_VER_MINOR = 0
OSSL_VER_APPENDIX = "none"
2015-05-26 12:51:10 +02:00
HAS_DH_BITS = true
2015-07-16 17:58:03 +02:00
HAS_SSL2 = true #TODO: in the future we'll do the fastest possible test (openssl s_client -ssl2 is currently faster than sockets)
2015-07-13 23:24:23 +02:00
PORT = 443 # unless otherwise auto-determined, see below
2015-06-15 12:13:16 +02:00
NODE = ""
2015-05-17 22:43:53 +02:00
NODEIP = ""
2015-06-15 12:13:16 +02:00
IPADDRs = ""
IP46ADDRs = ""
2015-08-10 14:47:11 +02:00
LOCAL_A = false # does the $NODEIP ceom from /etc/hosts?
LOCAL_AAAA = false # does the IPv6 IP come from /etc/hosts?
2015-07-06 20:42:43 +02:00
XMPP_HOST = ""
2015-06-29 22:29:15 +02:00
PROXY = ""
PROXYIP = ""
PROXYPORT = ""
2015-05-17 22:43:53 +02:00
VULN_COUNT = 0
2015-06-16 19:53:40 +02:00
readonly VULN_THRESHLD = 1 # if bigger than this no we show a separate header in blue
2015-05-17 22:43:53 +02:00
IPS = ""
2015-06-16 19:53:40 +02:00
SERVICE = "" # is the server running an HTTP server, SMTP, POP or IMAP?
2015-05-17 22:43:53 +02:00
URI = ""
STARTTLS_PROTOCOL = ""
2015-06-16 19:53:40 +02:00
OPTIMAL_PROTO = "" # we need this for IIS6 (sigh) and OpenSSL 1.02, otherwise some handshakes
# will fail, see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892
2015-05-17 22:43:53 +02:00
TLS_TIME = ""
TLS_NOW = ""
HTTP_TIME = ""
GET_REQ11 = ""
HEAD_REQ10 = ""
readonly UA_SNEAKY = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"
readonly UA_STD = "Mozilla/5.0 (X11; Linux x86_64; rv:42.0) Gecko/19700101 Firefox/42.0"
# Devel stuff, see -q below
TLS_LOW_BYTE = ""
HEX_CIPHER = ""
2015-06-16 19:53:40 +02:00
# 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
2015-05-29 19:56:57 +02:00
HEXDUMP = ( hexdump -ve '16/1 "%02x " " \n"' ) # This is used to analyze the reply
2015-05-17 22:43:53 +02:00
HEXDUMPPLAIN = ( hexdump -ve '1/1 "%.2x"' ) # Replaces both xxd -p and tr -cd '[:print:]'
2015-06-16 19:53:40 +02:00
###### some hexbytes for bash network sockets follow ######
2015-05-17 22:43:53 +02:00
# 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,
2015-05-29 19:44:27 +02:00
00,08, 00,06, 00,03, 00,ff"
2015-05-17 22:43:53 +02:00
readonly SSLv2_CLIENT_HELLO = "
,80,34 # length (here: 52)
2015-05-29 19:44:27 +02:00
,01 # Client Hello
2015-05-17 22:43:53 +02:00
,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 ######
2015-05-29 19:44:27 +02:00
2015-08-05 11:31:55 +02:00
out( ) { $ECHO " $1 " ; }
outln( ) { [ [ -z " $1 " ] ] || $ECHO " $1 " ; $ECHO "\n" ; }
2015-05-17 22:43:53 +02:00
# color print functions, see also http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
2015-08-05 11:31:55 +02:00
pr_liteblue( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[0;34m $1 " || out " $1 " ; pr_off; }
pr_liteblueln( ) { pr_liteblue " $1 " ; outln; }
pr_blue( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[1;34m $1 " || out " $1 " ; pr_off; }
pr_blueln( ) { pr_blue " $1 " ; outln; }
pr_litered( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[0;31m $1 " || pr_bold " $1 " ; pr_off; }
pr_literedln( ) { pr_litered " $1 " ; outln; }
pr_red( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[1;31m $1 " || pr_bold " $1 " ; pr_off; }
pr_redln( ) { pr_red " $1 " ; outln; }
pr_litemagenta( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[0;35m $1 " || pr_underline " $1 " ; pr_off; }
pr_litemagentaln( ) { pr_litemagenta " $1 " ; outln; }
pr_magenta( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[1;35m $1 " || pr_underline " $1 " ; pr_off; }
pr_magentaln( ) { pr_magenta " $1 " ; outln; }
pr_litecyan( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[0;36m $1 " || out " $1 " ; pr_off; }
pr_litecyanln( ) { pr_litecyan " $1 " ; outln; }
pr_cyan( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[1;36m $1 " || out " $1 " ; pr_off; }
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_litegreen( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[0;32m $1 " || out " $1 " ; pr_off; }
pr_litegreenln( ) { pr_litegreen " $1 " ; outln; }
pr_green( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[1;32m $1 " || out " $1 " ; pr_off; }
pr_greenln( ) { pr_green " $1 " ; outln; }
pr_yellow( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[1;33m $1 " || out " $1 " ; pr_off; }
pr_yellowln( ) { pr_yellow " $1 " ; outln; }
pr_brown( ) { [ [ " $COLOR " -eq 2 ] ] && out " \033[0;33m $1 " || out " $1 " ; pr_off; }
pr_brownln( ) { pr_brown " $1 " ; outln; }
2015-08-02 00:03:30 +02:00
# color=1 functions
2015-08-05 11:31:55 +02:00
pr_off( ) { [ [ " $COLOR " -ne 0 ] ] && out "\033[m\c" ; }
pr_bold( ) { [ [ " $COLOR " -ne 0 ] ] && out " \033[1m $1 " || out " $1 " ; pr_off; }
2015-08-02 00:03:30 +02:00
pr_boldln( ) { pr_bold " $1 " ; outln; }
2015-08-05 11:31:55 +02:00
pr_underline( ) { [ [ " $COLOR " -ne 0 ] ] && out " \033[4m $1 " || out " $1 " ; pr_off; }
2015-05-17 22:43:53 +02:00
pr_reverse( ) { [ [ " $COLOR " -ne 0 ] ] && out " \033[7m $1 " || out " $1 " ; pr_off; }
2015-08-05 11:31:55 +02:00
### from here to the end of color_maker this is dead code, see
2015-08-02 00:03:30 +02:00
declare -A COLORS = ( [ pr_green] = '0\033[1;32m' [ pr_litegreen] = '0\033[0;32m'
[ pr_yellow] = '0\033[1;33m' [ pr_brown] = '0\033[0;33m'
[ pr_blue] = '0\033[1;34m' [ pr_liteblue] = '0\033[0;34m'
[ pr_red] = '0\033[1;31m' [ pr_litered] = '0\033[0;31m'
[ pr_magenta] = '0\033[1;35m' [ pr_litemagenta] = '0\033[0;35m'
[ pr_cyan] = '0\033[1;36m' [ pr_litecyan] = '0\033[0;36m'
[ pr_grey] = '0\033[1;30m' [ pr_litegrey] = '0\033[0;37m' )
# [pr_off]='0\033[m'
# [pr_bold]='0\033[1m'
# [pr_underline]='0\033[4m'
# [pr_reverse]='0\033[7m')
color_maker( ) {
local color_name
local color_code
for color_name in ${ !COLORS[*] } ; do
color_code = " ${ COLORS [ $color_name ] : 1 } "
# generate pr_* names from array
if [ [ " $COLOR " -eq 2 ] ] ; then
eval " ${ color_name } () { out \" ${ color_code } \$1\"; pr_off; } "
else
eval " ${ color_name } () { out \"\$1\"; } "
fi
# generate *ln functions
eval " ${ color_name } ln() { ${ color_name } \"\$1\"; outln; } "
done
}
2015-08-05 11:31:55 +02:00
2015-05-17 22:43:53 +02:00
### colorswitcher (see e.g. https://linuxtidbits.wordpress.com/2008/08/11/output-color-on-bash-scripts/
### http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
2015-06-28 13:52:42 +02:00
set_color_functions( ) {
# empty vars if we have no color:
red = ""
green = ""
brown = ""
blue = ""
magenta = ""
cyan = ""
grey = ""
yellow = ""
off = ""
bold = ""
underline = ""
if [ [ " $COLOR " -eq 2 ] ] ; then
red = $( tput setaf 1)
green = $( tput setaf 2)
brown = $( tput setaf 3)
blue = $( tput setaf 4)
magenta = $( tput setaf 5)
cyan = $( tput setaf 6)
grey = $( tput setaf 7)
yellow = $( tput setaf 3; tput bold)
fi
2015-05-17 22:43:53 +02:00
2015-06-28 13:52:42 +02:00
if [ [ " $COLOR " -ge 1 ] ] ; then
bold = $( tput bold)
underline = $( tput sgr 0 1)
off = $( tput sgr0)
fi
}
2015-05-17 22:43:53 +02:00
###### helper function definitions ######
debugme( ) {
2015-05-29 19:44:27 +02:00
[ [ $DEBUG -ge 2 ] ] && " $@ "
2015-05-17 22:43:53 +02:00
}
2015-06-23 21:54:47 +02:00
hex2dec( ) {
/usr/bin/printf -- "%d" 0x" $1 "
#echo $((16#$1))
}
dec2hex( ) {
/usr/bin/printf -- "%x" " $1 "
#echo $((0x$1))
}
# trim spaces for BSD and old sed
count_lines( ) {
echo " $1 " | wc -l | sed 's/ //g'
}
count_words( ) {
echo " $1 " | wc -w | sed 's/ //g'
}
newline_to_spaces( ) {
echo " $1 " | tr '\n' ' ' | sed 's/ $//'
}
2015-08-12 13:58:45 +02:00
strip_lf( ) {
echo " $1 " | tr -d '\n' | tr -d '\r'
}
toupper( ) {
printf " $1 " | tr '[a-z]' '[A-Z]'
2015-07-21 20:35:49 +02:00
}
2015-08-12 13:58:45 +02:00
2015-05-17 22:43:53 +02:00
tmpfile_handle( ) {
if [ [ " $DEBUG " -eq 0 ] ] ; then
rm $TMPFILE
else
2015-06-16 14:04:44 +02:00
mv $TMPFILE " $TEMPDIR / $NODEIP . $1 "
2015-05-17 22:43:53 +02:00
fi
}
2015-08-01 23:11:27 +02:00
# arg1: line with comment sign, tabs and so on
filter_input( ) {
echo " $1 " | sed -e 's/#.*$//' -e '/^$/d' | tr -d '\n' | tr -d '\t'
}
2015-05-17 22:43:53 +02:00
wait_kill( ) {
2015-07-07 22:59:31 +02:00
local pid = $1 # pid we wait for or kill
local maxsleep = $2 # how long we wait before killing
2015-05-17 22:43:53 +02:00
while true; do
2015-07-07 22:59:31 +02:00
[ [ " $DEBUG " -ge 6 ] ] && ps $pid
2015-05-17 22:43:53 +02:00
if ! ps $pid >/dev/null ; then
2015-07-07 22:59:31 +02:00
return 0 # process terminated before didn't reach $maxsleep
2015-05-17 22:43:53 +02:00
fi
sleep 1
maxsleep = $(( maxsleep - 1 ))
2015-07-07 22:59:31 +02:00
test $maxsleep -le 0 && break
done # needs to be killed:
2015-05-17 22:43:53 +02:00
kill $pid >& 2 2>/dev/null
2015-07-07 22:59:31 +02:00
wait $pid 2>/dev/null # make sure pid terminated, see wait(1p)
return 3 # means killed
2015-05-17 22:43:53 +02:00
}
###### check code starts here ######
# determines whether the port has an HTTP service running or not (plain TLS, no STARTTLS)
# arg1 could be the protocol determined as "working". IIS6 needs that
runs_HTTP( ) {
# SNI is nonsense for !HTTPS but fortunately other protocols don't seem to care
2015-06-29 22:29:15 +02:00
printf " $GET_REQ11 " | $OPENSSL s_client $1 -quiet -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE &
2015-05-17 22:43:53 +02:00
wait_kill $! $HEADER_MAXSLEEP
head $TMPFILE | grep -aq ^HTTP && SERVICE = HTTP
head $TMPFILE | grep -aq SMTP && SERVICE = SMTP
head $TMPFILE | grep -aq POP && SERVICE = POP
head $TMPFILE | grep -aq IMAP && SERVICE = IMAP
debugme head $TMPFILE
# $TMPFILE contains also a banner which we could use if there's a need for it
out " Service detected: "
case $SERVICE in
2015-05-29 19:44:27 +02:00
HTTP)
out " $SERVICE "
2015-05-17 22:43:53 +02:00
ret = 0 ; ;
2015-05-29 19:44:27 +02:00
IMAP| POP| SMTP)
out " $SERVICE , thus skipping HTTP specific checks "
2015-05-17 22:43:53 +02:00
ret = 0 ; ;
*) out " Couldn't determine what's running on port $PORT "
2015-07-06 10:10:46 +02:00
if $ASSUMING_HTTP ; then
2015-05-17 22:43:53 +02:00
SERVICE = HTTP
out " -- ASSUMING_HTTP set though"
ret = 0
else
2015-07-06 10:10:46 +02:00
out ", assuming no HTTP service, skipping HTTP checks"
2015-05-17 22:43:53 +02:00
ret = 1
fi
; ;
esac
outln
tmpfile_handle $FUNCNAME .txt
return $ret
}
#problems not handled: chunked
2015-07-22 13:11:20 +02:00
run_http_header( ) {
2015-06-16 23:00:47 +02:00
local header
local -i ret
local referer useragent
local url
2015-06-19 20:36:32 +02:00
outln; pr_blue "--> Testing HTTP header response" ; outln " @ \" $URL_PATH \"\n "
2015-05-17 22:43:53 +02:00
[ -z " $1 " ] && url = "/" || url = " $1 "
2015-07-06 10:10:46 +02:00
if $SNEAKY ; then
2015-05-17 22:43:53 +02:00
referer = "http://google.com/"
useragent = " $UA_SNEAKY "
else
referer = " TLS/SSL-Tester from $SWURL "
useragent = " $UA_STD "
fi
(
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $OPTIMAL_PROTO -quiet -connect $NODEIP :$PORT $PROXY $SNI << EOF
2015-05-17 22:43:53 +02:00
GET $url HTTP/1.1
Host: $NODE
Accept: text/html,application/xhtml+xml,application/xml; q = 0.9,*/*; q = 0.8
Accept-Language: en-us,en; q = 0.7,de-de; q = 0.3
User-Agent: $useragent
Referer: $referer
Connection: close
EOF
) & >$HEADERFILE &
2015-07-07 22:59:31 +02:00
if wait_kill $! $HEADER_MAXSLEEP ; then
2015-05-17 22:43:53 +02:00
if ! egrep -iaq "XML|HTML|DOCTYPE|HTTP|Connection" $HEADERFILE ; then
pr_litemagenta " likely HTTP header requests failed (#lines: $( wc -l < $HEADERFILE | sed 's/ //g' ) ). "
2015-07-22 13:11:20 +02:00
outln "Rerun with DEBUG=1 and inspect \"run_http_header.txt\"\n"
2015-05-17 22:43:53 +02:00
debugme cat $HEADERFILE
ret = 7
fi
sed -e '/^<HTML/,$d' -e '/^<html/,$d' -e '/^<XML /,$d' -e '/<?XML /,$d' \
-e '/^<xml /,$d' -e '/<?xml /,$d' -e '/^<\!DOCTYPE/,$d' -e '/^<\!doctype/,$d' $HEADERFILE >$HEADERFILE .2
#### ^^^ Attention: the filtering for the html body only as of now, doesn't work for other content yet
mv $HEADERFILE .2 $HEADERFILE # sed'ing in place doesn't work with BSD and Linux simultaneously
ret = 0
else
2015-06-19 20:36:32 +02:00
#TODO: attention: if this runs into a timeout, we're dead. Try again differently:
2015-07-27 12:16:03 +02:00
printf " $GET_REQ11 " | $OPENSSL s_client $OPTIMAL_PROTO -quiet -ign_eof -connect $NODEIP :$PORT $PROXY $SNI 1>$HEADERFILE 2>/dev/null
2015-06-19 20:36:32 +02:00
if [ $? -ne 0 ] ; then
2015-08-10 14:47:11 +02:00
pr_litemagentaln " failed (1st request stalled, 2nd erroneous)"
2015-06-19 20:36:32 +02:00
return 3
#ret=3
fi
2015-05-17 22:43:53 +02:00
fi
2015-07-27 12:16:03 +02:00
status_code = $( awk '/^HTTP\// { print $2 }' $HEADERFILE 2>/dev/null)
msg_thereafter = $( awk -F" $status_code " '/^HTTP\// { print $2 }' $HEADERFILE 2>/dev/null) # dirty trick to use the status code as a
2015-06-19 20:36:32 +02:00
msg_thereafter = $( strip_lf " $msg_thereafter " ) # field separator, otherwise we need a loop with awk
debugme echo " Status/MSG: $status_code $msg_thereafter "
pr_bold " HTTP Status Code "
[ -z " $status_code " ] && pr_litemagentaln "No status code" && return 3
out " $status_code $msg_thereafter "
case $status_code in
301| 302| 307| 308) out " , redirecting to \" $( grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n' ) \" " ; ;
200) ; ;
206) out " -- WTF?" ; ;
2015-07-08 11:34:45 +02:00
400) pr_litemagenta " (Hint: better try another URL)" ; ;
2015-06-19 20:36:32 +02:00
401) grep -aq "^WWW-Authenticate" $HEADERFILE && out " " ; strip_lf " $( grep -a "^WWW-Authenticate" $HEADERFILE ) "
; ;
403) ; ;
2015-07-08 11:34:45 +02:00
404) out " (Hint: supply a path which doesn't give a \" $status_code $msg_thereafter \") " ; ;
2015-06-19 20:36:32 +02:00
405) ; ;
*) pr_litemagenta " . Oh, didn't expect a $status_code $msg_thereafter " ; ;
esac
outln
2015-05-29 19:44:27 +02:00
2015-06-19 20:36:32 +02:00
# we don't call "tmpfile_handle $FUNCNAME.txt" as we need the header file in other functions!
2015-05-17 22:43:53 +02:00
return $ret
}
2015-06-16 23:00:47 +02:00
2015-07-22 13:11:20 +02:00
run_http_date( ) {
2015-06-19 20:36:32 +02:00
local now difftime
2015-06-16 19:53:40 +02:00
2015-06-19 20:36:32 +02:00
if [ ! -s $HEADERFILE ] ; then
2015-07-22 13:11:20 +02:00
run_http_header " $1 " || return 3 # this is just for the line "Testing HTTP header response"
2015-06-16 19:53:40 +02:00
fi
2015-06-19 20:36:32 +02:00
pr_bold " HTTP clock skew "
if [ [ $SERVICE != "HTTP" ] ] ; then
out "not tested as we're not targeting HTTP"
else
2015-06-29 22:29:15 +02:00
printf " $GET_REQ11 " | $OPENSSL s_client $OPTIMAL_PROTO -ign_eof -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE
2015-06-19 20:36:32 +02:00
now = $( date "+%s" ) # we need an ACCURATE date here and cannot rely on the headerfile!
HTTP_TIME = $( awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $TMPFILE )
if [ -n " $HTTP_TIME " ] ; then
if $HAS_GNUDATE ; then
HTTP_TIME = $( date --date= " $HTTP_TIME " "+%s" )
else
HTTP_TIME = $( date -j -f "%a, %d %b %Y %T %Z" " $HTTP_TIME " "+%s" 2>/dev/null) # the trailing \r confuses BSD flavors otherwise
fi
difftime = $(( $HTTP_TIME - $now ))
[ [ $difftime != "-" * ] ] && [ [ $difftime != "0" ] ] && difftime = " + $difftime "
out " $difftime sec from localtime " ;
else
out "Got no HTTP time, maybe try different URL?" ;
fi
2015-08-12 00:17:28 +02:00
debugme out " epoch: $HTTP_TIME "
2015-06-16 19:53:40 +02:00
fi
2015-06-19 20:36:32 +02:00
outln
detect_ipv4
2015-06-16 19:53:40 +02:00
}
2015-06-19 20:36:32 +02:00
# Borrowd from Glenn Jackman, see https://unix.stackexchange.com/users/4667/glenn-jackman
detect_ipv4( ) {
local octet = "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
local ipv4address = " $octet \\. $octet \\. $octet \\. $octet "
local your_ip_msg = " (check if it's yours or e.g. a cluster IP)"
local first = true
local spaces = " "
if [ ! -s $HEADERFILE ] ; then
http_header " $1 " || return 3
fi
2015-08-08 10:20:13 +02:00
# remove pagespeed header first as it is mistakenly identified as ipv4 address
# https://github.com/drwetter/testssl.sh/issues/158
if egrep -vi "pagespeed|page-speed" $HEADERFILE | grep -iqE $ipv4address ; then
2015-06-19 20:36:32 +02:00
pr_bold " IPv4 address in header "
cat $HEADERFILE | while read line; do
result = " $( echo -n " $line " | grep -E $ipv4address ) "
result = $( strip_lf " $result " )
if [ -n " $result " ] ; then
if ! $first ; then
out " $spaces "
your_ip_msg = ""
else
first = false
fi
# a little bit of sanitzing, otherwise printf will hiccup @ %
pr_litered " $( echo $result | sed 's/%/%%/g' ) "
outln " $your_ip_msg "
fi
done
fi
}
2015-05-17 22:43:53 +02:00
includeSubDomains( ) {
if grep -aiqw includeSubDomains " $1 " ; then
pr_litegreen ", includeSubDomains"
else
pr_litecyan ", just this domain"
fi
}
preload( ) {
grep -aiqw preload " $1 " && pr_litegreen ", preload"
}
2015-06-19 20:36:32 +02:00
2015-07-22 13:11:20 +02:00
run_hsts( ) {
2015-05-17 22:43:53 +02:00
local hsts_age_sec
local hsts_age_days
if [ ! -s $HEADERFILE ] ; then
http_header " $1 " || return 3
fi
2015-06-19 20:36:32 +02:00
#pr_bold " HSTS "
pr_bold " Strict Transport Security "
2015-05-17 22:43:53 +02:00
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)
2015-05-25 21:14:59 +02:00
#FIXME: test for number!
2015-05-17 22:43:53 +02:00
hsts_age_days = $(( hsts_age_sec / 86400 ))
if [ $hsts_age_days -gt $HSTS_MIN ] ; then
2015-05-25 21:14:59 +02:00
pr_litegreen " $hsts_age_days days " ; out " = $hsts_age_sec s "
2015-05-17 22:43:53 +02:00
else
2015-05-25 21:14:59 +02:00
out " $hsts_age_sec s = "
2015-06-17 11:33:29 +02:00
pr_brown " $hsts_age_days days, < $HSTS_MIN days is too short "
2015-05-17 22:43:53 +02:00
fi
includeSubDomains " $TMPFILE "
2015-05-29 19:44:27 +02:00
preload " $TMPFILE "
#FIXME: To be checked against e.g. https://dxr.mozilla.org/mozilla-central/source/security/manager/boot/src/nsSTSPreloadList.inc
2015-05-17 22:43:53 +02:00
# and https://chromium.googlesource.com/chromium/src/+/master/net/http/transport_security_state_static.json
else
out "--"
fi
outln
tmpfile_handle $FUNCNAME .txt
return $?
}
2015-07-21 20:35:49 +02:00
2015-07-22 13:11:20 +02:00
run_hpkp( ) {
2015-07-21 20:35:49 +02:00
local hpkp_age_sec
local hpkp_age_days
local hpkp_nr_keys
2015-05-18 21:51:45 +02:00
local hpkp_key hpkp_key_hostcert
2015-06-19 20:36:32 +02:00
local spaces = " "
2015-07-21 20:35:49 +02:00
local key_found = false
2015-05-29 19:44:27 +02:00
2015-05-17 22:43:53 +02:00
if [ ! -s $HEADERFILE ] ; then
http_header " $1 " || return 3
fi
2015-06-19 20:36:32 +02:00
#pr_bold " HPKP "
pr_bold " Public Key Pinning "
2015-05-17 22:43:53 +02:00
egrep -aiw '^Public-Key-Pins|Public-Key-Pins-Report-Only' $HEADERFILE >$TMPFILE
if [ $? -eq 0 ] ; then
2015-05-25 21:14:59 +02:00
egrep -aciw '^Public-Key-Pins|Public-Key-Pins-Report-Only' $HEADERFILE | egrep -waq "1" || out "(two HPKP headers, using 1st one) "
2015-07-21 20:35:49 +02:00
sed -e 's/Public-Key-Pins://g' -e s'/Public-Key-Pins-Report-Only://' $TMPFILE >$TMPFILE .2
mv $TMPFILE .2 $TMPFILE
2015-08-10 15:58:56 +02:00
# dirty trick so that grep -c really counts occurrences and not lines w/ occurrences:
hpkp_nr_keys = $( tr ' ' '\n' < $TMPFILE | grep -ac pin-sha)
out "# of keys: "
if [ $hpkp_nr_keys -eq 1 ] ; then
pr_litered "1 (NOT ok), "
else
out " $hpkp_nr_keys , "
fi
hpkp_age_sec = $( sed -e 's/^.*max-age=//' $TMPFILE | sed -E 's/[^[:digit:]]//g' )
2015-05-29 19:44:27 +02:00
hpkp_age_days = $(( hpkp_age_sec / 86400 ))
2015-05-17 22:43:53 +02:00
if [ $hpkp_age_days -ge $HPKP_MIN ] ; then
2015-05-25 21:14:59 +02:00
pr_litegreen " $hpkp_age_days days " ; out " = $hpkp_age_sec s "
2015-05-17 22:43:53 +02:00
else
2015-05-25 21:14:59 +02:00
out " $hpkp_age_sec s = "
2015-05-17 22:43:53 +02:00
pr_brown " $hpkp_age_days days (< $HPKP_MIN is not good enough) "
fi
2015-05-29 19:44:27 +02:00
2015-07-21 20:35:49 +02:00
2015-05-17 22:43:53 +02:00
includeSubDomains " $TMPFILE "
preload " $TMPFILE "
2015-05-18 21:51:45 +02:00
# get the key fingerprints
[ -s " $HOSTCERT " ] || get_host_cert
2015-05-18 23:10:34 +02:00
hpkp_key_hostcert = " $( $OPENSSL x509 -in $HOSTCERT -pubkey -noout | grep -v PUBLIC | \
$OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL base64) "
2015-05-17 22:43:53 +02:00
while read hpkp_key; do
2015-06-29 10:41:56 +02:00
if [ [ " $hpkp_key_hostcert " = = " $hpkp_key " ] ] || [ [ " $hpkp_key_hostcert " = = " $hpkp_key = " ] ] ; then
2015-08-10 15:58:56 +02:00
out " \n $spaces matching host key: "
2015-05-18 21:51:45 +02:00
pr_litegreen " $hpkp_key "
2015-07-21 20:35:49 +02:00
key_found = true
2015-05-18 21:51:45 +02:00
fi
2015-07-21 20:35:49 +02:00
debugme echo " $hpkp_key | $hpkp_key_hostcert "
done < <( tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }' )
2015-08-10 15:58:56 +02:00
if ! $key_found ; then
pr_litered "No matching key for pin found "
out "(CA pinned?)"
fi
2015-05-17 22:43:53 +02:00
else
out "--"
fi
outln
tmpfile_handle $FUNCNAME .txt
return $?
}
emphasize_stuff_in_headers( ) {
# see http://www.grymoire.com/Unix/Sed.html#uh-3
# outln "$1" | sed "s/[0-9]*/$brown&$off/g"
outln " $1 " | sed -e " s/\([0-9]\)/ $brown \1 $off /g " \
-e "s/Debian/" $yellow " \Debian $off /g " \
2015-05-25 15:10:09 +02:00
-e "s/Win32/" $yellow " \Win32 $off /g " \
-e "s/Win64/" $yellow " \Win64 $off /g " \
2015-05-17 22:43:53 +02:00
-e "s/Ubuntu/" $yellow " Ubuntu $off /g " \
-e "s/ubuntu/" $yellow " ubuntu $off /g " \
2015-05-18 23:10:34 +02:00
-e "s/jessie/" $yellow " jessie $off /g " \
2015-05-17 22:43:53 +02:00
-e "s/squeeze/" $yellow " squeeze $off /g " \
2015-05-18 23:10:34 +02:00
-e "s/wheezy/" $yellow " wheezy $off /g " \
2015-05-17 22:43:53 +02:00
-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 " \
2015-06-16 23:00:47 +02:00
-e "s/Via/" $yellow " Via $off /g " \
2015-08-08 13:42:31 +02:00
-e "s/X-Forwarded/" $yellow " X-Forwarded $off /g " \
2015-07-12 18:46:27 +02:00
-e "s/Liferay-Portal/" $yellow " Liferay-Portal $off /g " \
2015-06-16 23:00:47 +02:00
-e "s/X-Cache-Lookup/" $yellow " X-Cache-Lookup $off /g " \
-e "s/X-Cache/" $yellow " X-Cache $off /g " \
2015-06-19 20:36:32 +02:00
-e "s/X-Squid/" $yellow " X-Squid $off /g " \
2015-07-14 20:44:04 +02:00
-e "s/X-Server/" $yellow " X-Server $off /g " \
2015-06-19 20:36:32 +02:00
-e "s/X-Varnish/" $yellow " X-Varnish $off /g " \
2015-06-16 23:00:47 +02:00
-e "s/X-OWA-Version/" $yellow " X-OWA-Version $off /g " \
-e "s/X-Version/" $yellow " X-Version $off /g " \
2015-05-17 22:43:53 +02:00
-e "s/X-Powered-By/" $yellow " X-Powered-By $off /g " \
2015-07-14 20:44:04 +02:00
-e "s/X-UA-Compatible/" $yellow " X-UA-Compatible $off /g " \
2015-05-29 19:44:27 +02:00
-e "s/X-AspNet-Version/" $yellow " X-AspNet-Version $off /g "
2015-05-17 22:43:53 +02:00
}
2015-07-22 13:11:20 +02:00
run_server_banner( ) {
2015-06-16 23:00:47 +02:00
local serverbanner
2015-05-17 22:43:53 +02:00
if [ ! -s $HEADERFILE ] ; then
2015-07-22 13:11:20 +02:00
run_http_header " $1 " || return 3
2015-05-17 22:43:53 +02:00
fi
2015-06-19 20:36:32 +02:00
pr_bold " Server banner "
2015-05-17 22:43:53 +02:00
grep -ai '^Server' $HEADERFILE >$TMPFILE
if [ $? -eq 0 ] ; then
serverbanner = $( sed -e 's/^Server: //' -e 's/^server: //' $TMPFILE )
if [ x" $serverbanner " = = "x\n" -o x" $serverbanner " = = "x\n\r" -o x" $serverbanner " = = "x" ] ; then
outln "banner exists but empty string"
else
emphasize_stuff_in_headers " $serverbanner "
2015-05-18 23:10:34 +02:00
[ [ " $serverbanner " = *Microsoft-IIS/6.* ] ] && [ [ $OSSL_VER = = 1.0.2* ] ] && \
2015-07-08 11:34:45 +02:00
pr_litemagentaln " It's recommended to run another test w/ OpenSSL 1.01 !"
2015-05-18 23:10:34 +02:00
# see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892
2015-05-17 22:43:53 +02:00
fi
# mozilla.github.io/server-side-tls/ssl-config-generator/
# https://support.microsoft.com/en-us/kb/245030
else
2015-05-25 21:14:59 +02:00
outln "(no \"Server\" line in header, interesting!)"
2015-05-17 22:43:53 +02:00
fi
tmpfile_handle $FUNCNAME .txt
2015-06-16 23:00:47 +02:00
return 0
2015-05-17 22:43:53 +02:00
}
2015-07-22 13:11:20 +02:00
run_rp_banner( ) {
2015-06-16 23:00:47 +02:00
if [ ! -s $HEADERFILE ] ; then
2015-07-22 13:11:20 +02:00
run_http_header " $1 " || return 3
2015-06-16 23:00:47 +02:00
fi
2015-08-08 13:37:05 +02:00
pr_bold " Reverse Proxy banner "
2015-08-08 13:42:31 +02:00
egrep -ai '^Via:|^X-Cache|^X-Squid|^X-Varnish:|^X-Server-Name:|^X-Server-Port:|^x-forwarded' $HEADERFILE >$TMPFILE && \
2015-06-16 23:00:47 +02:00
emphasize_stuff_in_headers " $( sed 's/^/ /g' $TMPFILE | tr '\n\r' ' ' ) " || \
2015-06-19 20:36:32 +02:00
outln "--"
2015-06-16 23:00:47 +02:00
tmpfile_handle $FUNCNAME .txt
return 0
}
2015-07-22 13:11:20 +02:00
run_application_banner( ) {
2015-06-19 20:36:32 +02:00
local line
local first = true
local spaces = " "
2015-05-17 22:43:53 +02:00
if [ ! -s $HEADERFILE ] ; then
2015-07-22 13:11:20 +02:00
run_http_header " $1 " || return 3
2015-05-17 22:43:53 +02:00
fi
2015-06-19 20:36:32 +02:00
pr_bold " Application banner "
2015-07-14 20:44:04 +02:00
egrep -ai '^X-Powered-By|^X-AspNet-Version|^X-UA-Compatible|^X-Version|^Liferay-Portal|^X-OWA-Version' $HEADERFILE >$TMPFILE
2015-06-19 20:36:32 +02:00
if [ $? -ne 0 ] ; then
outln "--"
else
cat $TMPFILE | while read line; do
line = $( strip_lf " $line " )
if ! $first ; then
out " $spaces "
else
first = false
fi
emphasize_stuff_in_headers " $line "
done
fi
2015-05-17 22:43:53 +02:00
tmpfile_handle $FUNCNAME .txt
2015-06-16 23:00:47 +02:00
return 0
2015-05-17 22:43:53 +02:00
}
2015-07-22 13:11:20 +02:00
run_cookie_flags( ) { # ARG1: Path, ARG2: path
2015-06-16 23:00:47 +02:00
local -i nr_cookies
local nr_httponly nr_secure
local negative_word
2015-05-17 22:43:53 +02:00
if [ ! -s $HEADERFILE ] ; then
2015-07-22 13:11:20 +02:00
run_http_header " $1 " || return 3
2015-05-17 22:43:53 +02:00
fi
2015-06-19 20:36:32 +02:00
pr_bold " Cookie(s) "
2015-05-17 22:43:53 +02:00
grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE
if [ $? -eq 0 ] ; then
nr_cookies = $( wc -l < $TMPFILE | sed 's/ //g' )
out " $nr_cookies issued: "
if [ $nr_cookies -gt 1 ] ; then
negative_word = "NONE"
else
negative_word = "NOT"
fi
nr_secure = $( grep -iac secure $TMPFILE )
case $nr_secure in
0) pr_brown " $negative_word " ; ;
[ 123456789] ) pr_litegreen " $nr_secure / $nr_cookies " ; ;
esac
2015-05-25 21:14:59 +02:00
out " secure, "
2015-05-17 22:43:53 +02:00
nr_httponly = $( grep -cai httponly $TMPFILE )
case $nr_httponly in
0) pr_brown " $negative_word " ; ;
2015-05-29 19:44:27 +02:00
[ 123456789] ) pr_litegreen " $nr_httponly / $nr_cookies " ; ;
2015-05-17 22:43:53 +02:00
esac
2015-05-25 21:14:59 +02:00
out " HttpOnly"
2015-05-17 22:43:53 +02:00
else
2015-06-19 20:36:32 +02:00
out " (none issued at \" $1 \") "
2015-05-17 22:43:53 +02:00
fi
outln
tmpfile_handle $FUNCNAME .txt
return 0
}
2015-07-22 13:11:20 +02:00
run_more_flags( ) {
2015-06-19 20:36:32 +02:00
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"
2015-06-16 23:00:47 +02:00
local other_flags2test = "Access-Control-Allow-Origin Upgrade X-Served-By"
2015-05-17 22:43:53 +02:00
local egrep_pattern = ""
local f2t result_str
2015-06-19 20:36:32 +02:00
local first = true
local spaces = " "
2015-05-17 22:43:53 +02:00
if [ ! -s $HEADERFILE ] ; then
2015-07-22 13:11:20 +02:00
run_http_header " $1 " || return 3
2015-05-17 22:43:53 +02:00
fi
2015-06-19 20:36:32 +02:00
pr_bold " Security headers "
# convert spaces to | (for egrep)
egrep_pattern = $( echo " $good_flags2test $other_flags2test " | sed -e 's/ /|\^/g' -e 's/^/\^/g' )
2015-06-16 23:00:47 +02:00
egrep -ai " $egrep_pattern " $HEADERFILE >$TMPFILE
2015-05-17 22:43:53 +02:00
if [ $? -ne 0 ] ; then
2015-06-19 20:36:32 +02:00
outln "--"
2015-05-17 22:43:53 +02:00
ret = 1
else
ret = 0
for f2t in $good_flags2test ; do
2015-06-19 20:36:32 +02:00
debugme " ---> $f2t "
result_str = $( grep -wi " ^ $f2t " $TMPFILE | grep -vi " $f2t " -)
result_str = $( strip_lf " $result_str " )
2015-05-17 22:43:53 +02:00
[ -z " $result_str " ] && continue
if ! $first ; then
2015-06-19 20:36:32 +02:00
out " $spaces " # output leading spaces if the first header
2015-05-17 22:43:53 +02:00
else
first = false
fi
2015-06-19 20:36:32 +02:00
#if [ $(echo "$result_str" | wc -l | sed 's/ //g') -eq 1 ]; then
2015-05-17 22:43:53 +02:00
pr_litegreenln " $result_str "
2015-06-19 20:36:32 +02:00
#else # for the case we have two times the same header:
2015-05-29 19:56:57 +02:00
# exchange the line feeds between the two lines only:
2015-06-19 20:36:32 +02:00
#pr_litecyan "double -->" ; echo "$result_str" | tr '\n\r' ' | ' | sed 's/| $//g'
#pr_litecyanln "<-- double"
#fi
2015-05-17 22:43:53 +02:00
done
# now the same with other flags
for f2t in $other_flags2test ; do
result_str = $( grep -i " ^ $f2t " $TMPFILE )
[ -z " $result_str " ] && continue
2015-07-08 11:34:45 +02:00
if ! $first ; then
out " $spaces " # output leading spaces if the first header
2015-05-17 22:43:53 +02:00
else
2015-07-08 11:34:45 +02:00
first = false
2015-05-17 22:43:53 +02:00
fi
2015-07-08 11:34:45 +02:00
outln " $result_str "
2015-05-17 22:43:53 +02:00
done
fi
2015-06-23 21:54:47 +02:00
#TODO: I am not testing for the correctness or anything stupid yet, e.g. "X-Frame-Options: allowall"
2015-05-17 22:43:53 +02:00
tmpfile_handle $FUNCNAME .txt
return $ret
}
# #1: string with 2 opensssl codes, HEXC= same in NSS/ssllab terminology
normalize_ciphercode( ) {
part1 = $( echo " $1 " | awk -F',' '{ print $1 }' )
part2 = $( echo " $1 " | awk -F',' '{ print $2 }' )
part3 = $( echo " $1 " | awk -F',' '{ print $3 }' )
if [ " $part1 " = = "0x00" ] ; then # leading 0x00
HEXC = $part2
else
part2 = $( echo $part2 | sed 's/0x//g' )
if [ -n " $part3 " ] ; then # a SSLv2 cipher has three parts
part3 = $( echo $part3 | sed 's/0x//g' )
fi
HEXC = " $part1 $part2 $part3 "
fi
2015-06-16 14:04:44 +02:00
#TODO: we should just echo this and avoid the global var HEXC
2015-05-17 22:43:53 +02:00
HEXC = $( echo $HEXC | tr 'A-Z' 'a-z' | sed 's/0x/x/' ) #tolower + strip leading 0
return 0
}
prettyprint_local( ) {
2015-07-17 15:58:07 +02:00
local arg
local hexcode dash ciph sslvers kx auth enc mac export
local re = '^[0-9A-Fa-f]+$'
2015-05-29 19:44:27 +02:00
pr_blue "--> Displaying all local ciphers " ;
2015-07-14 17:13:58 +02:00
if [ [ -n " $1 " ] ] ; then
2015-07-17 15:58:07 +02:00
[ [ $1 = ~ $re ] ] && \
pr_blue " matching number pattern \" $1 \" " || \
pr_blue "matching word pattern " \" $1 \" " (ignore case)"
2015-05-17 22:43:53 +02:00
fi
outln "\n"
neat_header
2015-07-14 17:13:58 +02:00
if [ [ -z " $1 " ] ] ; then
2015-05-17 22:43:53 +02:00
$OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode dash ciph sslvers kx auth enc mac export ; do # -V doesn't work with openssl < 1.0
normalize_ciphercode $hexcode
2015-05-29 19:44:27 +02:00
neat_list $HEXC $ciph $kx $enc
2015-05-17 22:43:53 +02:00
outln
done
else
for arg in $( echo $@ | sed 's/,/ /g' ) ; do
$OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode dash ciph sslvers kx auth enc mac export ; do # -V doesn't work with openssl < 1.0
normalize_ciphercode $hexcode
2015-07-17 15:58:07 +02:00
# 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 "
2015-05-17 22:43:53 +02:00
done
done
fi
outln
return 0
}
# list ciphers (and makes sure you have them locally configured)
# arg[1]: cipher list (or anything else)
listciphers( ) {
2015-08-10 14:47:11 +02:00
local ret
local debugname = " $( sed -e s'/\!/not/g' -e 's/\:/_/g' <<< $1 ) "
$OPENSSL ciphers " $1 " & >$TMPFILE
2015-05-17 22:43:53 +02:00
ret = $?
2015-05-29 19:44:27 +02:00
debugme cat $TMPFILE
2015-05-17 22:43:53 +02:00
2015-08-10 14:47:11 +02:00
tmpfile_handle $FUNCNAME .$debugname .txt
2015-05-17 22:43:53 +02:00
return $ret
}
2015-05-29 19:44:27 +02:00
# argv[1]: cipher list to test
2015-05-17 22:43:53 +02:00
# argv[2]: string on console
# argv[3]: ok to offer? 0: yes, 1: no
std_cipherlists( ) {
2015-08-10 14:47:11 +02:00
local ret
local singlespaces
local debugname = " $( sed -e s'/\!/not/g' -e 's/\:/_/g' <<< $1 ) "
2015-06-19 20:36:32 +02:00
pr_bold " $2 " # indent in order to be in the same row as server preferences
2015-08-10 14:47:11 +02:00
if listciphers " $1 " ; then # is that locally available??
$OPENSSL s_client -cipher " $1 " $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI 2>$TMPFILE .2 >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
ret = $?
2015-08-10 14:47:11 +02:00
debugme cat $TMPFILE .2
2015-05-17 22:43:53 +02:00
case $3 in
0) # ok to offer
2015-05-27 14:28:18 +02:00
[ [ $ret -eq 0 ] ] && \
pr_greenln "offered (OK)" || \
2015-06-17 11:33:29 +02:00
pr_brownln "not offered (NOT ok)" ; ;
1) # the ugly ones
2015-05-27 14:28:18 +02:00
[ [ $ret -eq 0 ] ] && \
pr_redln "offered (NOT ok)" || \
pr_greenln "not offered (OK)" ; ;
2015-06-17 11:33:29 +02:00
2) # bad but not worst
[ [ $ret -eq 0 ] ] && \
pr_literedln "offered (NOT ok)" || \
pr_litegreenln "not offered (OK)" ; ;
3) # not totally bad
[ [ $ret -eq 0 ] ] && \
pr_brownln "offered (NOT ok)" || \
outln "not offered (OK)" ; ;
*) # we shouldn't reach this
pr_litemagenta "? (please report this)" ; ;
2015-05-17 22:43:53 +02:00
esac
2015-08-10 14:47:11 +02:00
debugme cat $TMPFILE .2 >>$TMPFILE
tmpfile_handle $FUNCNAME .$debugname .txt
2015-05-17 22:43:53 +02:00
else
singlespaces = $( echo " $2 " | sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/ //g' )
2015-05-29 19:44:27 +02:00
pr_magentaln " Local problem: No $singlespaces configured in $OPENSSL "
2015-05-17 22:43:53 +02:00
fi
# we need lf in those cases:
[ [ $DEBUG -ge 2 ] ] && echo
}
# sockets inspired by http://blog.chris007.de/?p=238
2015-05-29 19:56:57 +02:00
# ARG1: hexbyte with a leading comma (!!), separated by commas
2015-05-17 22:43:53 +02:00
# 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
2015-06-17 11:33:29 +02:00
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
2015-05-17 22:43:53 +02:00
[ [ $DEBUG -ge 4 ] ] && echo " \" $data \" "
printf -- " $data " >& 5 2>/dev/null &
sleep $2
}
2015-06-29 10:41:56 +02:00
#FIXME: This is only for HB and CCS, others use still sockread_serverhello()
2015-05-17 22:43:53 +02:00
sockread( ) {
2015-06-29 10:41:56 +02:00
[ [ " x $2 " = = "x" ] ] && maxsleep = $MAX_WAITSOCK || maxsleep = $2
2015-05-17 22:43:53 +02:00
ret = 0
ddreply = $( mktemp $TEMPDIR /ddreply.XXXXXX) || return 7
dd bs = $1 of = $ddreply count = 1 <& 5 2>/dev/null &
2015-07-07 22:59:31 +02:00
wait_kill $! $maxsleep
2015-05-17 22:43:53 +02:00
ret = $?
SOCKREPLY = $( cat $ddreply )
rm $ddreply
return $ret
}
show_rfc_style( ) {
[ ! -r " $MAP_RFC_FNAME " ] && return 1
2015-05-27 11:19:30 +02:00
RFCname = $( grep -iw " $1 " " $MAP_RFC_FNAME " | sed -e 's/^.*TLS/TLS/' -e 's/^.*SSL/SSL/' )
2015-05-29 19:44:27 +02:00
[ [ -n " $RFCname " ] ] && out " $RFCname "
2015-05-17 22:43:53 +02:00
return 0
}
neat_header( ) {
outln " Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits ${ MAP_RFC_FNAME : + Cipher Suite Name (RFC) } "
outln " %s------------------------------------------------------------------------- ${ MAP_RFC_FNAME : +---------------------------------------------- } "
}
2015-05-27 17:04:35 +02:00
# arg1: hexcode
# arg2: cipher in openssl notation
# arg3: keyexchange
# arg4: encryption (maybe included "export")
2015-05-17 22:43:53 +02:00
neat_list( ) {
2015-05-26 12:51:10 +02:00
kx = $( echo " $3 " | sed 's/Kx=//g' )
2015-05-29 10:36:14 +02:00
enc = $( echo " $4 " | sed 's/Enc=//g' )
2015-05-17 22:43:53 +02:00
strength = $( echo $enc | sed -e 's/.*(//' -e 's/)//' ) # strength = encryption bits
strength = $( echo $strength | sed -e 's/ChaCha20-Poly1305/ly1305/g' ) # workaround for empty bits ChaCha20-Poly1305
enc = $( echo $enc | sed -e 's/(.*)//g' -e 's/ChaCha20-Poly1305/ChaCha20-Po/g' ) # workaround for empty bits ChaCha20-Poly1305
echo " $export " | grep -iq export && strength = " $strength ,export "
2015-05-27 11:19:30 +02:00
# workaround for color escape codes:
if printf -- " $kx " | " ${ HEXDUMPVIEW [@] } " | grep -q 33 ; then # here's a color code
kx = " $kx " # one for color code if ECDH and three digits
[ [ " ${# kx } " -eq 18 ] ] && kx = " $kx " # 18 means DH, colored < 1000. Add another space
[ [ " ${# kx } " -eq 19 ] ] && kx = " $kx " # 19 means DH, colored >=1000. Add another space
#echo ${#kx} # should be always 20
fi
2015-07-14 17:13:58 +02:00
if [ [ -r " $MAP_RFC_FNAME " ] ] ; then
2015-05-17 22:43:53 +02:00
printf -- " %-7s %-30s %-10s %-11s%-11s ${ MAP_RFC_FNAME : + %-48s } ${ SHOW_EACH_C : + } " " $1 " " $2 " " $kx " " $enc " " $strength " " $( show_rfc_style $HEXC ) "
else
printf -- " %-7s %-30s %-10s %-11s%-11s ${ SHOW_EACH_C : + } " " $1 " " $2 " " $kx " " $enc " " $strength "
fi
}
test_just_one( ) {
2015-05-27 11:19:30 +02:00
local hexcode n ciph sslvers kx auth enc mac export
local dhlen
local ret
2015-07-17 15:58:07 +02:00
local re = '^[0-9A-Fa-f]+$'
2015-05-27 11:19:30 +02:00
2015-07-17 15:58:07 +02:00
pr_blue "--> Testing single cipher with "
[ [ $1 = ~ $re ] ] && \
pr_blue " matching number pattern \" $1 \" " || \
pr_blue "word pattern " \" $1 \" " (ignore case)"
outln
2015-07-16 17:58:03 +02:00
! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL cannot show DH/ECDH bits) "
2015-05-27 11:19:30 +02:00
outln
2015-05-17 22:43:53 +02:00
neat_header
2015-05-29 19:44:27 +02:00
for arg in $( echo $@ | sed 's/,/ /g' ) ; do
2015-05-17 22:43:53 +02:00
# 1st check whether openssl has cipher or not
$OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode dash ciph sslvers kx auth enc mac export ; do
2015-05-27 11:19:30 +02:00
# FIXME: e.g. OpenSSL < 1.0 doesn't understand "-V" --> we can't do anything about it!
2015-05-29 19:44:27 +02:00
normalize_ciphercode $hexcode
2015-07-17 15:58:07 +02:00
# 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
2015-07-14 17:13:58 +02:00
if [ [ $? -eq 0 ] ] ; then # string matches, so we can ssl to it:
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher $ciph $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
ret = $?
2015-07-14 17:13:58 +02:00
if [ [ $kx = = "Kx=ECDH" ] ] || [ [ $kx = = "Kx=DH" ] ] || [ [ $kx = = "Kx=EDH" ] ] ; then
2015-05-27 11:19:30 +02:00
if [ $ret -eq 0 ] ; then
dhlen = $( read_dhbits_from_file $TMPFILE quiet)
kx = " $kx $dhlen "
else
kx = " $kx $grey TBD $off "
fi
fi
2015-05-29 19:44:27 +02:00
neat_list $HEXC $ciph " $kx " $enc
2015-07-14 17:13:58 +02:00
if [ [ $ret -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
pr_cyan " available"
else
out " not a/v"
fi
outln
fi
done
done
outln
tmpfile_handle $FUNCNAME .txt
2015-07-22 13:11:20 +02:00
return 0 # this is a single test for a cipher
2015-05-17 22:43:53 +02:00
}
# test for all ciphers locally configured (w/o distinguishing whether they are good or bad
2015-07-22 13:11:20 +02:00
run_allciphers( ) {
2015-05-26 12:51:10 +02:00
local tmpfile
local nr_ciphers
2015-07-22 13:11:20 +02:00
local -i ret = 0
2015-05-26 12:51:10 +02:00
local hexcode n ciph sslvers kx auth enc mac export
local dhlen
2015-05-27 11:19:30 +02:00
nr_ciphers = $( $OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' | sed 's/:/ /g' | wc -w)
2015-05-26 12:51:10 +02:00
pr_blue " --> Testing all locally available $nr_ciphers ciphers against the server " ; outln ", ordered by encryption strength"
2015-07-16 17:58:03 +02:00
! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL cannot show DH/ECDH bits) "
2015-05-26 12:51:10 +02:00
outln
2015-05-17 22:43:53 +02:00
neat_header
$OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode n ciph sslvers kx auth enc mac export; do
# FIXME: e.g. OpenSSL < 1.0 doesn't understand "-V" --> we can't do anything about it!
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher $ciph $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
ret = $?
2015-07-14 17:13:58 +02:00
if [ [ $ret -ne 0 ] ] && [ [ " $SHOW_EACH_C " -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
continue # no successful connect AND not verbose displaying each cipher
fi
normalize_ciphercode $hexcode
2015-07-14 17:13:58 +02:00
if [ [ $kx = = "Kx=ECDH" ] ] || [ [ $kx = = "Kx=DH" ] ] || [ [ $kx = = "Kx=EDH" ] ] ; then
2015-05-26 12:51:10 +02:00
dhlen = $( read_dhbits_from_file $TMPFILE quiet)
kx = " $kx $dhlen "
fi
2015-05-29 19:44:27 +02:00
neat_list $HEXC $ciph " $kx " $enc
2015-05-17 22:43:53 +02:00
if [ " $SHOW_EACH_C " -ne 0 ] ; then
2015-07-14 17:13:58 +02:00
if [ [ $ret -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
pr_cyan " available"
else
out " not a/v"
fi
fi
outln
tmpfile_handle $FUNCNAME .txt
done
2015-05-27 11:19:30 +02:00
outln
2015-05-17 22:43:53 +02:00
return 0
}
# test for all ciphers per protocol locally configured (w/o distinguishing whether they are good or bad
2015-07-22 13:11:20 +02:00
run_cipher_per_proto( ) {
2015-05-17 22:43:53 +02:00
local proto proto_text
local hexcode n ciph sslvers kx auth enc mac export
2015-07-22 13:11:20 +02:00
local -i ret = 0
2015-05-26 12:51:10 +02:00
local dhlen
2015-05-17 22:43:53 +02:00
2015-05-26 12:51:10 +02:00
pr_blue "--> Testing all locally available ciphers per protocol against the server" ; outln ", ordered by encryption strength"
2015-07-16 17:58:03 +02:00
! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL cannot show DH/ECDH bits) "
2015-05-26 12:51:10 +02:00
outln
2015-05-17 22:43:53 +02:00
neat_header
outln " -ssl2 SSLv2\n -ssl3 SSLv3\n -tls1 TLS 1\n -tls1_1 TLS 1.1\n -tls1_2 TLS 1.2" | while read proto proto_text; do
locally_supported " $proto " " $proto_text " || continue
outln
$OPENSSL ciphers $proto -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode n ciph sslvers kx auth enc mac export; do # -V doesn't work with openssl < 1.0
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher $ciph $proto $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
ret = $?
2015-07-14 17:13:58 +02:00
if [ [ $ret -ne 0 ] ] && [ [ " $SHOW_EACH_C " -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
continue # no successful connect AND not verbose displaying each cipher
fi
normalize_ciphercode $hexcode
2015-07-14 17:13:58 +02:00
if [ [ $kx = = "Kx=ECDH" ] ] || [ [ $kx = = "Kx=DH" ] ] || [ [ $kx = = "Kx=EDH" ] ] ; then
2015-05-26 12:51:10 +02:00
dhlen = $( read_dhbits_from_file $TMPFILE quiet)
kx = " $kx $dhlen "
fi
2015-05-29 19:44:27 +02:00
neat_list $HEXC $ciph " $kx " $enc
2015-07-14 17:13:58 +02:00
if [ [ " $SHOW_EACH_C " -ne 0 ] ] ; then
if [ [ $ret -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
pr_cyan " available"
else
out " not a/v"
fi
fi
outln
tmpfile_handle $FUNCNAME .txt
done
done
return 0
}
locally_supported( ) {
local ret
[ -n " $2 " ] && out " $2 "
$OPENSSL s_client " $1 " 2>& 1 | grep -aq "unknown option"
2015-07-14 17:13:58 +02:00
if [ [ $? -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
pr_magentaln " Local problem: $OPENSSL doesn't support \"s_client $1 \" "
ret = 7
else
ret = 0
fi
return $ret
}
2015-06-28 13:52:42 +02:00
run_prototest_openssl( ) {
2015-08-10 14:47:11 +02:00
local sni = " $SNI "
2015-06-23 21:54:47 +02:00
local ret
2015-05-17 22:43:53 +02:00
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -state $1 $STARTTLS -connect $NODEIP :$PORT $PROXY $sni & >$TMPFILE </dev/null
ret = $?
# FIXME: here FreeBSD9 returns always 0 --> need to read the error
2015-07-06 10:10:46 +02:00
$VERBERR && egrep "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
2015-06-29 22:29:15 +02:00
2015-06-28 13:52:42 +02:00
if ! locally_supported " $1 " " $2 " ; then
return 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 -connect $NODEIP :$PORT $sni & >$TMPFILE </dev/null
ret = $? #TODO (maybe): here FreeBSD9 returns always 0 --> need to read the error
2015-07-06 10:10:46 +02:00
$VERBERR && \
2015-06-28 13:52:42 +02:00
egrep "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
grep -aq "no cipher list" $TMPFILE && ret = 5
fi
2015-05-29 19:44:27 +02:00
2015-06-28 13:52:42 +02:00
tmpfile_handle $FUNCNAME $1 .txt
2015-05-17 22:43:53 +02:00
return $ret
2015-06-28 13:52:42 +02:00
# 0: offered
# 1: not offered
# 5: protocol ok, but no cipher
# 7: no local support
2015-05-17 22:43:53 +02:00
}
2015-06-23 21:54:47 +02:00
run_protocols( ) {
local using_sockets = true
2015-06-19 20:36:32 +02:00
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"
2015-05-17 22:43:53 +02:00
2015-05-29 19:44:27 +02:00
pr_blue "--> Testing protocols " ;
2015-05-17 22:43:53 +02:00
2015-07-17 14:33:23 +02:00
if $SSL_NATIVE ; then
2015-06-23 21:54:47 +02:00
using_sockets = false
2015-07-17 14:33:23 +02:00
outln "(via native openssl)\n"
2015-05-17 22:43:53 +02:00
else
2015-07-17 14:33:23 +02:00
if [ [ -n " $STARTTLS " ] ] ; then
2015-07-23 17:11:33 +02:00
outln "(via openssl, SSLv2 via sockets)\n"
2015-07-17 14:33:23 +02:00
using_sockets = false
else
using_sockets = true
outln "(via sockets except TLS 1.2 and SPDY/NPN)\n"
fi
2015-05-17 22:43:53 +02:00
fi
2015-06-19 20:36:32 +02:00
pr_bold " SSLv2 " ;
2015-07-17 14:33:23 +02:00
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_redln "offered (NOT ok)" ; ;
1) pr_greenln "not offered (OK)" ; ;
5) pr_litered " $supported_no_ciph2 " ;
outln " (may need further attention)" ; ; # protocol ok, but no cipher
7) ; ; # no local support
esac
fi
2015-05-17 22:43:53 +02:00
2015-06-19 20:36:32 +02:00
pr_bold " SSLv3 " ;
2015-06-23 21:54:47 +02:00
if $using_sockets ; then
2015-05-17 22:43:53 +02:00
tls_sockets "00" " $TLS_CIPHER "
2015-06-23 21:54:47 +02:00
else
2015-06-28 13:52:42 +02:00
run_prototest_openssl "-ssl3"
2015-05-17 22:43:53 +02:00
fi
case $? in
2015-05-25 15:10:09 +02:00
0) pr_literedln "offered (NOT ok)" ; ;
1) pr_greenln "not offered (OK)" ; ;
2015-05-29 19:56:57 +02:00
2) pr_magentaln "#FIXME: downgraded. still missing a test case here" ; ;
2015-06-19 20:36:32 +02:00
5) pr_litered " $supported_no_ciph2 " ;
2015-06-28 13:52:42 +02:00
outln "(may need debugging)" ; ; # protocol ok, but no cipher
7) ; ; # no local support
2015-05-17 22:43:53 +02:00
esac
2015-06-19 20:36:32 +02:00
pr_bold " TLS 1 " ;
2015-06-23 21:54:47 +02:00
if $using_sockets ; then
2015-06-22 18:32:40 +02:00
tls_sockets "01" " $TLS_CIPHER "
2015-06-23 21:54:47 +02:00
else
2015-06-28 13:52:42 +02:00
run_prototest_openssl "-tls1"
2015-06-22 18:32:40 +02:00
fi
2015-05-17 22:43:53 +02:00
case $? in
2015-06-19 20:36:32 +02:00
0) outln "offered" ; ; # nothing wrong with it -- per se
1) outln "not offered" ; ; # neither good or bad
2015-06-23 21:54:47 +02:00
2) pr_brown "not offered (NOT ok)"
[ $DEBUG -eq 1 ] && out " -- downgraded"
outln ; ;
2015-06-19 20:36:32 +02:00
5) outln " $supported_no_ciph1 " ; ; # protocol ok, but no cipher
7) ; ; # no local support
2015-05-17 22:43:53 +02:00
esac
2015-06-19 20:36:32 +02:00
pr_bold " TLS 1.1 " ;
2015-06-23 21:54:47 +02:00
if $using_sockets ; then
2015-06-22 18:32:40 +02:00
tls_sockets "02" " $TLS_CIPHER "
2015-06-23 21:54:47 +02:00
else
2015-06-28 13:52:42 +02:00
run_prototest_openssl "-tls1_1"
2015-06-22 18:32:40 +02:00
fi
2015-05-17 22:43:53 +02:00
case $? in
2015-06-19 20:36:32 +02:00
0) outln "offered" ; ; # nothing wrong with it
1) outln "not offered" ; ; # neither good or bad
2015-06-23 21:54:47 +02:00
2) out "not offered"
[ $DEBUG -eq 1 ] && out " -- downgraded"
outln ; ;
2015-06-19 20:36:32 +02:00
5) outln " $supported_no_ciph1 " ; ; # protocol ok, but no cipher
7) ; ; # no local support
2015-05-17 22:43:53 +02:00
esac
2015-06-19 20:36:32 +02:00
pr_bold " TLS 1.2 " ;
2015-06-28 13:52:42 +02:00
if $using_sockets && [ [ $EXPERIMENTAL = = "yes" ] ] ; then #TODO: IIS servers do have a problem here with our handshake
2015-06-22 18:32:40 +02:00
tls_sockets "03" " $TLS12_CIPHER "
2015-06-23 21:54:47 +02:00
else
2015-06-28 13:52:42 +02:00
run_prototest_openssl "-tls1_2"
2015-06-22 18:32:40 +02:00
fi
2015-05-17 22:43:53 +02:00
case $? in
2015-06-19 20:36:32 +02:00
0) pr_greenln "offered (OK)" ; ; # GCM cipher in TLS 1.2: very good!
1) pr_brownln "not offered (NOT ok)" ; ; # no GCM, penalty
2015-06-23 21:54:47 +02:00
2) pr_brown "not offered (NOT ok)"
[ $DEBUG -eq 1 ] && out " -- downgraded"
outln ; ;
2015-06-19 20:36:32 +02:00
5) outln " $supported_no_ciph1 " ; ; # protocol ok, but no cipher
7) ; ; # no local support
2015-05-17 22:43:53 +02:00
esac
return 0
}
2015-06-17 11:33:29 +02:00
#TODO: work with a fixed list here
2015-05-17 22:43:53 +02:00
run_std_cipherlists( ) {
outln
2015-06-17 11:33:29 +02:00
pr_blue "--> Testing ~standard cipher lists" ; outln "\n"
2015-05-17 22:43:53 +02:00
# see ciphers(1ssl)
2015-08-10 14:47:11 +02:00
std_cipherlists 'NULL:eNULL' " Null Ciphers " 1
std_cipherlists 'aNULL' " Anonymous NULL Ciphers " 1
std_cipherlists 'ADH' " Anonymous DH Ciphers " 1
std_cipherlists 'EXPORT40' " 40 Bit encryption " 1
std_cipherlists 'EXPORT56' " 56 Bit encryption " 1
std_cipherlists 'EXPORT' " Export Ciphers (general) " 1
2015-06-17 11:33:29 +02:00
std_cipherlists 'LOW:!ADH' " Low (<=64 Bit) " 1
std_cipherlists 'DES:!ADH:!EXPORT:!aNULL' " DES Ciphers " 1
std_cipherlists 'MEDIUM:!NULL:!aNULL:!SSLv2' " Medium grade encryption " 2
std_cipherlists '3DES:!ADH:!aNULL' " Triple DES Ciphers " 3
2015-08-10 14:47:11 +02:00
std_cipherlists 'HIGH:!NULL:!aNULL:!DES:!3DES' " High grade encryption " 0
2015-05-17 22:43:53 +02:00
return 0
}
2015-05-26 12:51:10 +02:00
2015-05-25 15:10:09 +02:00
# arg1: file with input for grepping the bit length for ECDH/DHE
2015-05-26 12:51:10 +02:00
# arg2: whether to print sparse or not (empty: no)
2015-05-25 21:14:59 +02:00
read_dhbits_from_file( ) {
2015-05-26 12:51:10 +02:00
local bits what_dh
2015-05-27 11:19:30 +02:00
local add = ""
2015-07-16 17:58:03 +02:00
local old_fart = " (openssl cannot show DH bits)"
2015-05-25 15:10:09 +02:00
2015-05-26 12:51:10 +02:00
bits = $( awk -F': ' '/^Server Temp Key/ { print $2 }' " $1 " ) # extract line
bits = $( echo $bits | sed -e 's/, P-...//' -e 's/,//g' -e 's/bits//' -e 's/ //g' ) # now: ??DH [number] K??
what_dh = $( echo $bits | tr -d '[0-9]' )
bits = $( echo $bits | tr -d '[DHEC]' )
2015-05-25 15:10:09 +02:00
2015-07-14 17:13:58 +02:00
debugme echo " > $what_dh | $bits < "
2015-05-27 11:19:30 +02:00
2015-07-14 19:58:04 +02:00
if ! $HAS_DH_BITS && [ [ -z "what_dh" ] ] ; then
if [ [ -z " $2 " ] ] ; then
pr_litemagenta " $old_fart "
fi
return 0
fi
2015-07-14 17:13:58 +02:00
[ [ -n " $bits " ] ] && [ [ -z " $2 " ] ] && out ", "
2015-05-26 12:51:10 +02:00
if [ [ $what_dh = = "DH" ] ] || [ [ $what_dh = = "EDH" ] ] ; then
2015-07-14 17:13:58 +02:00
[ [ -z " $2 " ] ] && add = "bit DH"
2015-05-27 14:28:18 +02:00
if [ [ " $bits " -le 600 ] ] ; then
2015-05-29 19:44:27 +02:00
pr_red " $bits $add "
2015-05-27 14:28:18 +02:00
elif [ [ " $bits " -le 800 ] ] ; then
2015-05-29 19:44:27 +02:00
pr_litered " $bits $add "
2015-05-27 14:28:18 +02:00
elif [ [ " $bits " -le 1280 ] ] ; then
2015-05-29 19:44:27 +02:00
pr_brown " $bits $add "
2015-05-26 12:51:10 +02:00
elif [ [ " $bits " -ge 2048 ] ] ; then
pr_litegreen " $bits $add "
else
out " $bits $add "
fi
# https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography, http://www.keylength.com/en/compare/
elif [ [ $what_dh = = "ECDH" ] ] ; then
2015-07-14 17:13:58 +02:00
[ [ -z " $2 " ] ] && add = "bit ECDH"
2015-05-26 12:51:10 +02:00
if [ [ " $bits " -le 128 ] ] ; then # has that ever existed?
pr_red " $bits $add "
elif [ [ " $bits " -le 163 ] ] ; then
2015-05-29 19:44:27 +02:00
pr_litered " $bits $add "
2015-05-26 12:51:10 +02:00
elif [ [ " $bits " -ge 224 ] ] ; then
pr_litegreen " $bits $add "
else
out " $bits $add "
fi
fi
2015-05-29 19:44:27 +02:00
2015-05-25 15:10:09 +02:00
return 0
}
2015-07-22 13:11:20 +02:00
run_server_preference( ) {
2015-07-14 17:13:58 +02:00
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:AES256-SHA256"
local list_reverse = "AES256-SHA256: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" # offline via tac, see https://github.com/thomassa/testssl.sh/commit/7a4106e839b8c3033259d66697893765fc468393
2015-07-13 23:41:49 +02:00
2015-05-17 22:43:53 +02:00
outln;
pr_blue "--> Testing server preferences" ; outln "\n"
2015-06-19 20:36:32 +02:00
pr_bold " Has server cipher order? "
2015-07-13 23:41:49 +02:00
$OPENSSL s_client $STARTTLS -cipher $list_fwd -connect $NODEIP :$PORT $PROXY $SNI </dev/null 2>/dev/null >$TMPFILE
2015-05-17 22:43:53 +02:00
if [ $? -ne 0 ] ; then
pr_magenta "no matching cipher in this list found (pls report this): "
2015-07-13 23:41:49 +02:00
outln " $list_fwd . "
2015-05-17 22:43:53 +02:00
ret = 6
else
cipher1 = $( grep -wa Cipher $TMPFILE | egrep -avw "New|is" | sed -e 's/^ \+Cipher \+://' -e 's/ //g' )
2015-07-13 23:41:49 +02:00
$OPENSSL s_client $STARTTLS -cipher $list_reverse -connect $NODEIP :$PORT $PROXY $SNI </dev/null 2>/dev/null >$TMPFILE
2015-05-17 22:43:53 +02:00
cipher2 = $( grep -wa Cipher $TMPFILE | egrep -avw "New|is" | sed -e 's/^ \+Cipher \+://' -e 's/ //g' )
if [ [ " $cipher1 " != " $cipher2 " ] ] ; then
pr_litered "nope (NOT ok)"
remark4default_cipher = " (limited sense as client will pick)"
else
pr_green "yes (OK)"
remark4default_cipher = ""
fi
[ [ $DEBUG -ge 2 ] ] && out " $cipher1 | $cipher2 "
outln
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI </dev/null 2>/dev/null >$TMPFILE
2015-06-19 20:36:32 +02:00
pr_bold " Negotiated protocol "
2015-05-17 22:43:53 +02:00
default_proto = $( grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g' )
case " $default_proto " in
*TLSv1.2) pr_greenln $default_proto ; ;
*TLSv1.1) pr_litegreenln $default_proto ; ;
*TLSv1) outln $default_proto ; ;
*SSLv2) pr_redln $default_proto ; ;
*SSLv3) pr_redln $default_proto ; ;
2015-07-08 11:34:45 +02:00
"" ) pr_litemagenta "default proto empty" ; [ [ $OSSL_VER = = 1.0.2* ] ] && outln " (Hint: if IIS6 give OpenSSL 1.01 a try)" ; ;
2015-05-17 22:43:53 +02:00
*) outln " $default_proto " ; ;
esac
2015-05-29 19:44:27 +02:00
2015-06-19 20:36:32 +02:00
pr_bold " Negotiated cipher "
2015-05-17 22:43:53 +02:00
default_cipher = $( grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g' )
case " $default_cipher " in
*NULL*| *EXP*) pr_red " $default_cipher " ; ;
*RC4*) pr_litered " $default_cipher " ; ;
2015-05-27 11:19:30 +02:00
*CBC*) pr_brown " $default_cipher " ; ; # FIXME BEAST: We miss some CBC ciphers here, need to work w/ a list
2015-05-17 22:43:53 +02:00
*GCM*) pr_green " $default_cipher " ; ; # best ones
*CHACHA20*) pr_green " $default_cipher " ; ; # best ones
2015-05-27 11:19:30 +02:00
ECDHE*AES*) pr_yellow " $default_cipher " ; ; # it's CBC. --> lucky13
2015-07-08 11:34:45 +02:00
"" ) pr_litemagenta "default cipher empty" ; [ [ $OSSL_VER = = 1.0.2* ] ] && out " (Hint: if IIS6 give OpenSSL 1.01 a try)" ; ;
2015-05-17 22:43:53 +02:00
*) out " $default_cipher " ; ;
esac
2015-05-25 21:14:59 +02:00
read_dhbits_from_file " $TMPFILE "
2015-05-17 22:43:53 +02:00
outln " $remark4default_cipher "
if [ ! -z " $remark4default_cipher " ] ; then
2015-07-02 16:39:41 +02:00
pr_bold " Negotiated cipher per proto" ; outln " $remark4default_cipher "
2015-05-17 22:43:53 +02:00
i = 1
for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do
2015-07-16 17:58:03 +02:00
#locally_supported -"$p" " " || continue
2015-05-17 22:43:53 +02:00
locally_supported -" $p " || continue
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -" $p " -connect $NODEIP :$PORT $PROXY $SNI </dev/null 2>/dev/null >$TMPFILE
2015-07-14 17:13:58 +02:00
if [ [ $? -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
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' )
2015-07-02 16:39:41 +02:00
[ [ ${ cipher [i] } = = "0000" ] ] && cipher[ i] = "" # Hack!
2015-05-17 22:43:53 +02:00
[ [ $DEBUG -ge 2 ] ] && outln " Default cipher for ${ proto [i] } : ${ cipher [i] } "
else
proto[ i] = ""
cipher[ i] = ""
fi
i = $(( $i + 1 ))
done
2015-07-16 17:58:03 +02:00
[ [ -n " $PROXY " ] ] && arg = " SPDY/NPN is"
[ [ -n " $STARTTLS " ] ] && arg = " "
2015-07-02 16:39:41 +02:00
if spdy_pre " $arg " ; then # is NPN/SPDY supported and is this no STARTTLS? / no PROXY
2015-06-29 23:28:37 +02:00
$OPENSSL s_client -host $NODE -port $PORT -nextprotoneg " $NPN_PROTOs " </dev/null 2>/dev/null >$TMPFILE
2015-07-14 17:13:58 +02:00
if [ [ $? -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
proto[ i] = $( grep -aw "Next protocol" $TMPFILE | sed -e 's/^Next protocol://' -e 's/(.)//' -e 's/ //g' )
2015-07-14 17:13:58 +02:00
if [ [ -z " ${ proto [i] } " ] ] ; then
2015-05-17 22:43:53 +02:00
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
2015-07-16 17:58:03 +02:00
else
outln # we miss for STARTTLS 1x LF otherwise
2015-05-17 22:43:53 +02:00
fi
for i in 1 2 3 4 5 6; do
if [ [ -n " ${ cipher [i] } " ] ] ; then # cipher not empty
2015-05-29 19:44:27 +02:00
if [ [ -z " ${ cipher [i-1] } " ] ] ; then # previous one empty
2015-07-02 16:39:41 +02:00
#outln
2015-05-17 22:43:53 +02:00
printf -- " %-30s %s" " ${ cipher [i] } : " " ${ proto [i] } " # print out both
else # previous NOT empty
if [ [ " ${ cipher [i-1] } " = = " ${ cipher [i] } " ] ] ; then # and previous protocol same cipher
out " , ${ proto [i] } " # same cipher --> only print out protocol behind it
else
outln
printf -- " %-30s %s" " ${ cipher [i] } : " " ${ proto [i] } " # print out both
fi
fi
fi
done
fi
fi
tmpfile_handle $FUNCNAME .txt
if [ -z " $remark4default_cipher " ] ; then
cipher_pref_check
else
outln "\n No further cipher order check as order is determined by the client"
fi
return 0
}
cipher_pref_check( ) {
local p proto protos
local tested_cipher cipher
2015-06-19 20:36:32 +02:00
pr_bold " Cipher order"
2015-05-17 22:43:53 +02:00
for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -" $p " -connect $NODEIP :$PORT $PROXY $SNI </dev/null 2>/dev/null >$TMPFILE
2015-07-14 17:13:58 +02:00
if [ [ $? -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
tested_cipher = ""
proto = $( grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g' )
cipher = $( grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g' )
[ -z " $proto " ] && continue # for early openssl versions sometimes needed
outln
printf " %-10s %s " " $proto : " " $cipher "
tested_cipher = "-" $cipher
while true; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -" $p " -cipher " ALL: $tested_cipher " -connect $NODEIP :$PORT $PROXY $SNI </dev/null 2>/dev/null >$TMPFILE
2015-07-14 17:13:58 +02:00
[ [ $? -ne 0 ] ] && break
2015-05-17 22:43:53 +02:00
cipher = $( grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g' )
out " $cipher "
tested_cipher = " $tested_cipher :- $cipher "
done
fi
done
outln
2015-06-29 23:28:37 +02:00
if ! spdy_pre " SPDY/NPN: " ; then # is NPN/SPDY supported and is this no STARTTLS?
2015-05-27 11:19:30 +02:00
outln
2015-05-17 22:43:53 +02:00
else
2015-06-29 23:28:37 +02:00
protos = $( $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg \" \" </dev/null 2>/dev/null | grep -a "^Protocols " | sed -e 's/^Protocols.*server: //' -e 's/,//g' )
2015-05-17 22:43:53 +02:00
for p in $protos ; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -host $NODE -port $PORT -nextprotoneg " $p " $PROXY </dev/null 2>/dev/null >$TMPFILE
2015-05-17 22:43:53 +02:00
cipher = $( grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g' )
printf " %-10s %s " " $p : " " $cipher "
tested_cipher = "-" $cipher
while true; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher " ALL: $tested_cipher " -host $NODE -port $PORT -nextprotoneg " $p " $PROXY </dev/null 2>/dev/null >$TMPFILE
2015-07-14 17:13:58 +02:00
[ [ $? -ne 0 ] ] && break
2015-05-17 22:43:53 +02:00
cipher = $( grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g' )
out " $cipher "
tested_cipher = " $tested_cipher :- $cipher "
done
outln
done
fi
2015-05-29 19:44:27 +02:00
2015-05-17 22:43:53 +02:00
tmpfile_handle $FUNCNAME .txt
return 0
}
2015-05-18 21:51:45 +02:00
get_host_cert( ) {
# arg1 is proto or empty
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI $1 2>/dev/null </dev/null | \
2015-05-18 21:51:45 +02:00
awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT
return $?
}
2015-06-19 20:36:32 +02:00
tls_time( ) {
2015-05-17 22:43:53 +02:00
local now difftime
2015-07-16 17:58:03 +02:00
tls_sockets "01" " $TLS_CIPHER " # try first TLS 1.0 (mostfrequently used protocol)
[ [ -z " $TLS_TIME " ] ] && tls_sockets "03" " $TLS12_CIPHER " # TLS 1.2
[ [ -z " $TLS_TIME " ] ] && tls_sockets "02" " $TLS_CIPHER " # TLS 1.1
[ [ -z " $TLS_TIME " ] ] && tls_sockets "00" " $TLS_CIPHER " # SSL 3
if [ [ -n " $TLS_TIME " ] ] ; then # nothing returned a time!
difftime = $(( $TLS_TIME - $TLS_NOW )) # TLS_NOW 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
pr_bold " TLS timestamp" ; outln " random values, no fingerprinting possible "
2015-05-17 22:43:53 +02:00
else
2015-07-16 17:58:03 +02:00
[ [ $difftime != "-" * ] ] && [ [ $difftime != "0" ] ] && difftime = " + $difftime "
pr_bold " TLS clock skew" ; outln " $difftime sec from localtime " ;
2015-05-17 22:43:53 +02:00
fi
2015-07-16 17:58:03 +02:00
debugme out " $TLS_TIME "
outln
else
pr_bold " TLS timestamp" ; outln " " ; pr_litemagentaln "SSLv3 through TLS 1.2 didn't return a timestamp"
2015-05-17 22:43:53 +02:00
fi
2015-06-19 20:36:32 +02:00
}
2015-05-17 22:43:53 +02:00
2015-07-22 13:11:20 +02:00
run_server_defaults( ) {
2015-06-19 20:36:32 +02:00
local proto
local gost_status_problem = false
local extensions
2015-08-01 23:11:27 +02:00
local sessticket_str lifetime unit keysize sig_algo key_algo
2015-06-19 20:36:32 +02:00
local expire ocsp_uri crl savedir startdate enddate issuer_c issuer_o issuer sans san cn cn_nosni
2015-07-16 17:58:03 +02:00
local policy_oid
2015-05-28 17:51:33 +02:00
2015-05-17 22:43:53 +02:00
outln
2015-06-19 20:36:32 +02:00
pr_blue "--> Testing server defaults (Server Hello)" ; outln "\n"
2015-05-17 22:43:53 +02:00
#TLS extensions follow now
# throwing 1st every cipher/protocol at the server to know what works
for proto in tls1_2 tls1_1 tls1 ssl3; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI -$proto -tlsextdebug -status </dev/null 2>/dev/null >$TMPFILE
2015-05-17 22:43:53 +02:00
ret = $?
2015-05-18 21:51:45 +02:00
get_host_cert " - $proto "
2015-05-17 22:43:53 +02:00
[ $? -eq 0 ] && [ $ret -eq 0 ] && break
ret = 7
2015-05-18 23:10:34 +02:00
done # this loop is needed for IIS/6
2015-05-17 22:43:53 +02:00
if [ $ret -eq 7 ] ; then
# "-status" kills GOST only servers, so we do another test without it and see whether that works then:
2015-06-29 22:29:15 +02:00
if ! $OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI -$proto -tlsextdebug </dev/null 2>/dev/null >$TMPFILE ; then
2015-05-17 22:43:53 +02:00
pr_magentaln " $OPENSSL returned an error around line $LINENO " .
tmpfile_handle tlsextdebug+status.txt
return 7 # this is ugly, I know
else
gost_status_problem = true
fi
fi
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY -$proto 2>/dev/null </dev/null | awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT .nosni
2015-06-19 20:36:32 +02:00
pr_bold " TLS server extensions "
2015-05-17 22:43:53 +02:00
extensions = $( grep -aw "^TLS server extension" $TMPFILE | sed -e 's/^TLS server extension \"//' -e 's/\".*$/,/g' )
if [ -z " $extensions " ] ; then
outln "(none)"
else
echo $extensions | sed 's/,$//' # remove last comma
fi
2015-06-19 20:36:32 +02:00
pr_bold " Session Tickets RFC 5077 "
2015-05-17 22:43:53 +02:00
sessticket_str = $( grep -aw "session ticket" $TMPFILE | grep -a lifetime)
if [ -z " $sessticket_str " ] ; then
outln "(none)"
else
lifetime = $( echo $sessticket_str | grep -a lifetime | sed 's/[A-Za-z:() ]//g' )
unit = $( echo $sessticket_str | grep -a lifetime | sed -e 's/^.*' " $lifetime " '//' -e 's/[ ()]//g' )
outln " $lifetime $unit "
fi
2015-06-19 20:36:32 +02:00
pr_bold " Server key size "
2015-05-25 21:14:59 +02:00
keysize = $( grep -aw "^Server public key is" $TMPFILE | sed -e 's/^Server public key is //' -e 's/bit//' -e 's/ //' )
2015-08-01 23:11:27 +02:00
sig_algo = $( $OPENSSL x509 -in $HOSTCERT -noout -text | grep "Signature Algorithm" | sed 's/^.*Signature Algorithm: //' | sort -u )
key_algo = $( $OPENSSL x509 -in $HOSTCERT -noout -text | awk -F':' '/Public Key Algorithm:/ { print $2 }' | sort -u )
2015-07-10 10:23:10 +02:00
2015-05-17 22:43:53 +02:00
if [ -z " $keysize " ] ; then
outln "(couldn't determine)"
else
2015-05-25 21:14:59 +02:00
if [ " $keysize " -le 768 ] ; then
2015-08-01 23:11:27 +02:00
if [ [ $sig_algo = ~ "ecdsa" ] ] || [ [ $key_algo = ~ "ecPublicKey" ] ] ; then
2015-07-10 10:23:10 +02:00
pr_litegreen " EC $keysize "
else
pr_red " $keysize "
fi
2015-05-25 21:14:59 +02:00
elif [ " $keysize " -le 1024 ] ; then
pr_brown " $keysize "
elif [ " $keysize " -le 2048 ] ; then
2015-05-29 19:44:27 +02:00
out " $keysize "
2015-05-25 21:14:59 +02:00
elif [ " $keysize " -le 4096 ] ; then
pr_litegreen " $keysize "
else
2015-05-29 19:44:27 +02:00
out " weird keysize: $keysize "
2015-05-25 21:14:59 +02:00
fi
2015-05-17 22:43:53 +02:00
fi
2015-05-25 21:14:59 +02:00
outln " bit"
2015-05-17 22:43:53 +02:00
2015-06-19 20:36:32 +02:00
pr_bold " Signature Algorithm "
2015-08-01 23:11:27 +02:00
case $sig_algo in
2015-05-25 21:14:59 +02:00
sha1WithRSAEncryption) pr_brownln "SHA1 with RSA" ; ;
sha256WithRSAEncryption) pr_litegreenln "SHA256 with RSA" ; ;
sha512WithRSAEncryption) pr_litegreenln "SHA512 with RSA" ; ;
2015-07-10 10:23:10 +02:00
ecdsa-with-SHA256) pr_litegreenln "ECDSA with SHA256" ; ;
2015-05-17 22:43:53 +02:00
md5*) pr_redln "MD5" ; ;
*) outln " $algo " ; ;
esac
# old, but interesting: https://blog.hboeck.de/archives/754-Playing-with-the-EFF-SSL-Observatory.html
2015-06-19 20:36:32 +02:00
pr_bold " Fingerprint / Serial "
2015-05-17 22:43:53 +02:00
outln " $( $OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 | sed 's/Fingerprint=//' | sed 's/://g' ) / $( $OPENSSL x509 -noout -in $HOSTCERT -serial | sed 's/serial=//' ) "
outln " $( $OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 | sed 's/Fingerprint=//' | sed 's/://g' ) "
2015-06-19 20:36:32 +02:00
pr_bold " Common Name (CN) "
2015-08-10 15:17:42 +02:00
if $OPENSSL x509 -in $HOSTCERT -noout -subject | grep -wq CN; then
cn = $( $OPENSSL x509 -in $HOSTCERT -noout -subject | sed 's/subject= //' | sed -e 's/^.*CN=//' -e 's/\/emailAdd.*//' )
pr_underline " $cn "
else
cn = "(no CN field in subject)"
out " $cn "
fi
2015-05-17 22:43:53 +02:00
2015-07-10 10:23:10 +02:00
cn_nosni = ""
2015-08-10 15:17:42 +02:00
if [ [ -s $HOSTCERT .nosni ] ] ; then
if $OPENSSL x509 -in $HOSTCERT .nosni -noout -subject | grep -wq CN; then
cn_nosni = $( $OPENSSL x509 -in $HOSTCERT .nosni -noout -subject | sed 's/subject= //' | sed -e 's/^.*CN=//' -e 's/\/emailAdd.*//' )
else
cn_nosni = "no CN field in subject"
fi
fi
debugme out " \' $NODE \' | \' $cn \' | \' $cn_nosni \' "
2015-05-17 22:43:53 +02:00
if [ [ $NODE = = $cn_nosni ] ] ; then
2015-07-10 10:23:10 +02:00
if [ [ $SERVICE != "HTTP" ] ] ; then
2015-05-17 22:43:53 +02:00
outln " (matches certificate directly)"
else
outln " (works w/o SNI)"
fi
else
2015-07-10 10:23:10 +02:00
if [ [ $SERVICE != "HTTP" ] ] ; then
2015-05-17 22:43:53 +02:00
pr_brownln " (CN doesn't match but for non-HTTP services it might be ok)"
2015-07-10 10:23:10 +02:00
elif [ [ -z " $cn_nosni " ] ] ; then
out " (request w/o SNI didn't succeed" ;
[ [ $algo = ~ ecdsa ] ] && out ", usual for EC certificates"
outln ")"
2015-08-10 15:17:42 +02:00
elif [ [ " $cn_nosni " = ~ "no CN field" ] ] ; then
outln " , (request w/o SNI: $cn_nosni ) "
2015-05-17 22:43:53 +02:00
else
2015-07-10 10:23:10 +02:00
out " (CN in response to request w/o SNI: " ; pr_underline " $cn_nosni " ; outln ")"
2015-05-17 22:43:53 +02:00
fi
fi
sans = $( $OPENSSL x509 -in $HOSTCERT -noout -text | grep -A3 "Subject Alternative Name" | grep "DNS:" | \
sed -e 's/DNS://g' -e 's/ //g' -e 's/,/\n/g' -e 's/othername:<unsupported>//g' )
2015-06-19 20:36:32 +02:00
# ^^^ CACert
pr_bold " subjectAltName (SAN) "
2015-05-17 22:43:53 +02:00
if [ -n " $sans " ] ; then
sans = $( echo " $sans " | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g' ) # replace line feed by " "
for san in $sans ; do
out " $underline $san $off "
done
else
out "-- "
fi
outln
2015-06-19 20:36:32 +02:00
pr_bold " Issuer "
2015-05-17 22:43:53 +02:00
issuer = $( $OPENSSL x509 -in $HOSTCERT -noout -issuer | sed -e 's/^.*CN=//g' -e 's/\/.*$//g' )
issuer_o = $( $OPENSSL x509 -in $HOSTCERT -noout -issuer | sed 's/^.*O=//g' | sed 's/\/.*$//g' )
2015-05-29 19:44:27 +02:00
if $OPENSSL x509 -in $HOSTCERT -noout -issuer | grep -q 'C=' ; then
2015-05-17 22:43:53 +02:00
issuer_c = $( $OPENSSL x509 -in $HOSTCERT -noout -issuer | sed 's/^.*C=//g' | sed 's/\/.*$//g' )
else
issuer_c = "" # CACert would have 'issuer= ' here otherwise
fi
2015-06-17 11:33:29 +02:00
if [ " $issuer_o " = = "issuer=" ] || [ " $issuer_o " = = "issuer= " ] || [ " $issuer " = = " $CN " ] ; then
2015-05-17 22:43:53 +02:00
pr_redln "selfsigned (not OK)"
else
[ " $issuer_c " = = "" ] && \
2015-06-17 11:33:29 +02:00
outln " $underline $issuer $off ( $underline $issuer_o $off ) " || \
2015-05-17 22:43:53 +02:00
outln " $underline $issuer $off ( $underline $issuer_o $off from $underline $issuer_c $off ) "
fi
2015-07-16 17:58:03 +02:00
# http://events.ccc.de/congress/2010/Fahrplan/attachments/1777_is-the-SSLiverse-a-safe-place.pdf, see page 40pp
pr_bold " EV cert" ; out " (experimental) "
policy_oid = $( $OPENSSL x509 -in $HOSTCERT -text | awk '/ .Policy: / { print $2 }' )
if echo $issuer | egrep -q 'Extended Validation|Extended Validated|EV SSL|EV CA' || \
[ [ "2.16.840.1.114028.10.1.2" = = " $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 " ] ] || \
[ [ "2.16.578.1.26.1.3.3" = = " $policy_oid " ] ] ; then # entrust and Camerfirma (2x), Firmaprofesional bupass need an exception though:
out "yes "
else
out "no "
fi
[ [ $DEBUG = = 1 ] ] && out " ( $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
2015-06-19 20:36:32 +02:00
pr_bold " Certificate Expiration "
2015-05-17 22:43:53 +02:00
expire = $( $OPENSSL x509 -in $HOSTCERT -checkend 0)
if ! echo $expire | grep -qw not; then
2015-05-28 17:51:33 +02:00
pr_red "expired!"
2015-05-17 22:43:53 +02:00
else
SECS2WARN = $(( 24 * 60 * 60 * $DAYS2WARN2 )) # low threshold first
expire = $( $OPENSSL x509 -in $HOSTCERT -checkend $SECS2WARN )
if echo " $expire " | grep -qw not; then
2015-08-12 18:28:50 +02:00
SECS2WARN = $(( 24 * 60 * 60 * $DAYS2WARN1 ))
2015-05-17 22:43:53 +02:00
expire = $( $OPENSSL x509 -in $HOSTCERT -checkend $SECS2WARN )
if echo " $expire " | grep -qw not; then
pr_litegreen " >= $DAYS2WARN1 days "
else
2015-05-28 17:51:33 +02:00
pr_brown " expires < $DAYS2WARN1 days "
2015-05-17 22:43:53 +02:00
fi
else
2015-05-28 17:51:33 +02:00
pr_litered " expires < $DAYS2WARN2 days! "
2015-05-17 22:43:53 +02:00
fi
fi
2015-05-28 17:51:33 +02:00
2015-05-29 14:12:22 +02:00
if $HAS_GNUDATE ; then
2015-05-28 17:51:33 +02:00
enddate = $( date --date= " $( $OPENSSL x509 -in $HOSTCERT -noout -enddate | cut -d= -f 2) " +"%F %H:%M %z" )
startdate = $( date --date= " $( $OPENSSL x509 -in $HOSTCERT -noout -startdate | cut -d= -f 2) " +"%F %H:%M" )
else
enddate = $( date -j -f "%b %d %T %Y %Z" " $( $OPENSSL x509 -in $HOSTCERT -noout -enddate | cut -d= -f 2) " +"%F %H:%M %z" )
startdate = $( date -j -f "%b %d %T %Y %Z" " $( $OPENSSL x509 -in $HOSTCERT -noout -startdate | cut -d= -f 2) " +"%F %H:%M" )
fi
2015-05-17 22:43:53 +02:00
outln " ( $startdate --> $enddate ) "
savedir = $( pwd ) ; cd $TEMPDIR
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -showcerts $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI 2>/dev/null </dev/null | \
2015-05-28 17:51:33 +02:00
awk -v c = -1 '/-----BEGIN CERTIFICATE-----/{inc=1;c++} inc {print > ("level" c ".crt")} /---END CERTIFICATE-----/{inc=0}'
2015-05-17 22:43:53 +02:00
nrsaved = $( ls $TEMPDIR /level?.crt 2>/dev/null | wc -w | sed 's/^ *//' )
2015-06-19 20:36:32 +02:00
pr_bold " # of certificates provided" ; outln " $nrsaved "
2015-05-29 10:36:14 +02:00
cd " $savedir "
2015-05-17 22:43:53 +02:00
2015-06-19 20:36:32 +02:00
pr_bold " Certificate Revocation List "
2015-05-17 22:43:53 +02:00
crl = $( $OPENSSL x509 -in $HOSTCERT -noout -text | grep -A 4 "CRL Distribution" | grep URI | sed 's/^.*URI://' )
[ x" $crl " = = "x" ] && pr_literedln "--" || echo " $crl "
2015-06-19 20:36:32 +02:00
pr_bold " OCSP URI "
2015-05-17 22:43:53 +02:00
ocsp_uri = $( $OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri)
[ x" $ocsp_uri " = = "x" ] && pr_literedln "--" || echo " $ocsp_uri "
2015-06-19 20:36:32 +02:00
pr_bold " OCSP stapling "
2015-05-17 22:43:53 +02:00
if grep "OCSP response" $TMPFILE | grep -q "no response sent" ; then
out " not offered"
else
if grep "OCSP Response Status" $TMPFILE | grep -q successful; then
2015-06-19 20:36:32 +02:00
pr_litegreen " offered"
2015-05-17 22:43:53 +02:00
else
2015-06-29 10:41:56 +02:00
if $gost_status_problem ; then
2015-05-17 22:43:53 +02:00
outln " (GOST servers make problems here, sorry)"
ret = 0
else
outln " not sure what's going on here, debug:"
grep -A 20 "OCSP response" $TMPFILE
ret = 2
fi
fi
fi
outln
2015-06-19 20:36:32 +02:00
# if we call tls_time before tmpfile_handle it throws an error because the function tls_sockets removed $TMPFILE
# already -- and that was a different one -- means that would get overwritten anyway
2015-05-17 22:43:53 +02:00
tmpfile_handle tlsextdebug+status.txt
2015-06-19 20:36:32 +02:00
tls_time
2015-05-17 22:43:53 +02:00
return $ret
}
2015-05-29 19:44:27 +02:00
# FIXME: revoked, see checkcert.sh
2015-05-17 22:43:53 +02:00
# FIXME: Trust (only CN)
# http://www.heise.de/security/artikel/Forward-Secrecy-testen-und-einrichten-1932806.html
2015-07-22 13:11:20 +02:00
run_pfs( ) {
2015-05-29 10:36:14 +02:00
local ret ret2
local -i pfs_offered = 1
2015-05-26 12:51:10 +02:00
local tmpfile
local dhlen
2015-05-29 10:36:14 +02:00
local hexcode dash pfs_cipher sslvers kx auth enc mac
2015-05-17 22:43:53 +02:00
# 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'
2015-05-29 10:36:14 +02:00
#w/o RC4:
#local pfs_ciphers='EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EDH+aRSA EECDH !RC4-SHA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS:@STRENGTH'
#
# hardcoded: (the exclusion via ! doesn't work with libressl and openssl 0.9.8) and it's reproducible
local pfs_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-CAMELLIA128-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-CAMELLIA128-SHA256:DHE-RSA-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA"
local -i no_supported_ciphers = 0
2015-05-17 22:43:53 +02:00
outln
pr_blue "--> Testing (perfect) forward secrecy, (P)FS" ; outln " -- omitting 3DES, RC4 and Null Encryption here"
2015-07-16 17:58:03 +02:00
! $HAS_DH_BITS && $WIDE && pr_litemagentaln " (Your $OPENSSL cannot show DH/ECDH bits) "
2015-05-17 22:43:53 +02:00
2015-05-29 10:36:14 +02:00
no_supported_ciphers = $( count_ciphers $( actually_supported_ciphers $pfs_cipher_list ) )
if [ " $no_supported_ciphers " -le " $CLIENT_MIN_PFS " ] ; then
outln
pr_magentaln " Local problem: you only have $number_pfs PFS ciphers on the client side "
return 1
2015-05-17 22:43:53 +02:00
fi
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher 'ECDH:DH' $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
ret = $?
outln
if [ $ret -ne 0 ] || [ $( grep -ac "BEGIN CERTIFICATE" $TMPFILE ) -eq 0 ] ; then
pr_brownln "Not OK: No ciphers supporting Forward Secrecy offered"
else
2015-05-29 10:36:14 +02:00
pfs_offered = 0
2015-07-06 10:10:46 +02:00
if $WIDE ; then
2015-05-29 10:36:14 +02:00
pr_litegreen " PFS ciphers (OK): "
2015-06-16 19:53:40 +02:00
outln ", cipher follow (client/browser support is here specially important) \n"
2015-05-29 10:36:14 +02:00
neat_header
2015-07-06 10:10:46 +02:00
else
pr_litegreen " PFS is offered (OK) "
2015-05-29 10:36:14 +02:00
fi
while read hexcode dash pfs_cipher sslvers kx auth enc mac; do
2015-05-26 12:51:10 +02:00
tmpfile = $TMPFILE .$hexcode
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher $pfs_cipher $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI & >$tmpfile </dev/null
2015-05-17 22:43:53 +02:00
ret2 = $?
if [ [ $ret2 -ne 0 ] ] && [ [ " $SHOW_EACH_C " -eq 0 ] ] ; then
continue # no successful connect AND not verbose displaying each cipher
fi
2015-07-06 10:10:46 +02:00
if $WIDE ; then
2015-05-29 10:36:14 +02:00
normalize_ciphercode $hexcode
2015-07-14 17:13:58 +02:00
if [ [ $kx = = "Kx=ECDH" ] ] || [ [ $kx = = "Kx=DH" ] ] || [ [ $kx = = "Kx=EDH" ] ] ; then
2015-05-29 10:36:14 +02:00
dhlen = $( read_dhbits_from_file " $tmpfile " quiet)
kx = " $kx $dhlen "
fi
neat_list $HEXC $pfs_cipher " $kx " $enc $strength
let "pfs_offered++"
if [ [ " $SHOW_EACH_C " -ne 0 ] ] ; then
if [ [ $ret2 -eq 0 ] ] ; then
pr_green "works"
else
out "not a/v"
fi
2015-05-17 22:43:53 +02:00
fi
2015-05-29 10:36:14 +02:00
outln
else
out " $pfs_cipher "
2015-05-17 22:43:53 +02:00
fi
2015-05-29 10:36:14 +02:00
done < <( $OPENSSL ciphers -V " $pfs_cipher_list " ) # -V doesn't work with openssl < 1.0
2015-05-17 22:43:53 +02:00
# ^^^^^ posix redirect as shopt will either segfault or doesn't work with old bash versions
2015-05-29 10:36:14 +02:00
debugme echo $pfs_offered
2015-05-17 22:43:53 +02:00
2015-07-14 17:13:58 +02:00
if [ " $pfs_offered " -eq 1 ] ; then
2015-05-17 22:43:53 +02:00
pr_brown "no PFS ciphers found"
fi
fi
outln
2015-07-06 10:10:46 +02:00
$WIDE && outln
2015-05-29 10:36:14 +02:00
debugme echo $( actually_supported_ciphers $pfs_cipher_list )
debugme echo $no_supported_ciphers
2015-05-17 22:43:53 +02:00
tmpfile_handle $FUNCNAME .txt
2015-05-29 10:36:14 +02:00
return $pfs_offered
2015-05-17 22:43:53 +02:00
}
# 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
2015-06-29 23:28:37 +02:00
2015-05-17 22:43:53 +02:00
spdy_pre( ) {
if [ ! -z " $STARTTLS " ] ; then
2015-06-29 23:28:37 +02:00
[ -n " $1 " ] && out " $1 "
2015-05-27 11:19:30 +02:00
out "(SPDY is a HTTP protocol and thus not tested here)"
2015-05-17 22:43:53 +02:00
return 1
fi
2015-06-29 23:28:37 +02:00
if [ ! -z " $PROXY " ] ; then
[ -n " $1 " ] && pr_litemagenta " $1 "
2015-07-07 22:59:31 +02:00
pr_litemagenta "not tested as proxies do not support proxying it"
2015-06-29 23:28:37 +02:00
return 1
fi
2015-05-17 22:43:53 +02:00
# first, does the current openssl support it?
$OPENSSL s_client help 2>& 1 | grep -qw nextprotoneg
if [ $? -ne 0 ] ; then
2015-05-29 19:44:27 +02:00
pr_magentaln " Local problem: $OPENSSL doesn't support SPDY/NPN " ;
2015-05-17 22:43:53 +02:00
return 7
fi
return 0
}
2015-06-29 23:28:37 +02:00
run_spdy( ) {
2015-06-19 20:36:32 +02:00
pr_bold " SPDY/NPN "
2015-05-27 11:19:30 +02:00
if ! spdy_pre ; then
echo
return 0
fi
2015-06-29 23:28:37 +02:00
$OPENSSL s_client -host $NODE -port $PORT -nextprotoneg $NPN_PROTOs </dev/null 2>/dev/null >$TMPFILE
2015-05-17 22:43:53 +02:00
tmpstr = $( grep -a '^Protocols' $TMPFILE | sed 's/Protocols.*: //' )
2015-06-29 10:41:56 +02:00
if [ -z " $tmpstr " -o " $tmpstr " = = " " ] ; then
2015-05-17 22:43:53 +02:00
out "not offered"
ret = 1
else
# now comes a strange thing: "Protocols advertised by server:" is empty but connection succeeded
if echo $tmpstr | egrep -aq "spdy|http" ; then
2015-06-19 20:36:32 +02:00
out " $tmpstr " ; out " (advertised)"
2015-05-17 22:43:53 +02:00
ret = 0
else
pr_litemagenta "please check manually, server response was ambigious ..."
ret = 10
fi
fi
outln
# btw: nmap can do that too http://nmap.org/nsedoc/scripts/tls-nextprotoneg.html
# nmap --script=tls-nextprotoneg #NODE -p $PORT is your friend if your openssl doesn't want to test this
tmpfile_handle $FUNCNAME .txt
return $ret
}
2015-07-07 22:59:31 +02:00
# 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
debugme echo " ---> reply didn't match \" $2 \", see $TMPFILE "
2015-07-17 14:33:23 +02:00
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)"
2015-07-22 13:11:20 +02:00
exit -3
2015-07-07 22:59:31 +02:00
fi
fi
return 0
}
2015-07-08 21:30:31 +02:00
starttls_just_send( ) {
debugme echo -e " \n=== sending \" $1 \" ... "
echo -e " $1 " >& 5
}
2015-07-07 22:59:31 +02:00
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
}
2015-05-17 22:43:53 +02:00
# arg for a fd doesn't work here
fd_socket( ) {
2015-07-07 22:59:31 +02:00
local jabber = ""
local proyxline = ""
2015-06-29 23:28:37 +02:00
if [ [ -n " $PROXY " ] ] ; then
2015-06-29 22:29:15 +02:00
if ! exec 5<> /dev/tcp/${ PROXYIP } /${ PROXYPORT } ; then
outln
2015-06-29 23:28:37 +02:00
pr_magenta " $PROG_NAME : unable to open a socket to proxy $PROXYIP : $PROXYPORT "
2015-06-29 22:29:15 +02:00
return 6
fi
echo " CONNECT $NODEIP : $PORT " >& 5
while true ; do
2015-07-07 22:59:31 +02:00
read proyxline <& 5
if [ [ " ${ proyxline %/* } " = = "HTTP" ] ] ; then
proyxline = ${ proyxline #* }
if [ [ " ${ proyxline %% * } " != "200" ] ] ; then
2015-06-29 23:28:37 +02:00
[ [ " $PORT " != 443 ] ] && outln " Check whether your proxy supports port $PORT and the underlying protocol. "
pr_magenta "Unable to CONNECT via proxy. "
2015-06-29 22:29:15 +02:00
return 6
fi
fi
2015-07-07 22:59:31 +02:00
if [ [ " $proyxline " = = $'\r' ] ] ; then
2015-06-29 22:29:15 +02:00
break
fi
done
2015-07-07 22:59:31 +02:00
elif ! exec 5<>/dev/tcp/$NODEIP /$PORT ; then # 2>/dev/null would remove an error message, but disables debugging
2015-05-17 22:43:53 +02:00
outln
2015-07-07 22:59:31 +02:00
pr_magentaln " Unable to open a socket to $NODEIP : $PORT . "
2015-05-29 19:56:57 +02:00
# It can last ~2 minutes but for for those rare occasions we don't do a timeout handler here, KISS
2015-05-17 22:43:53 +02:00
return 6
2015-06-29 22:29:15 +02:00
fi
2015-07-07 22:59:31 +02:00
if [ [ -n " $STARTTLS " ] ] ; then
2015-08-12 13:58:45 +02:00
case " $STARTTLS_PROTOCOL " in # port
ftp) # https://tools.ietf.org/html/rfc4217
2015-07-08 21:30:31 +02:00
$FAST_STARTTLS || starttls_just_read
$FAST_STARTTLS || starttls_line "FEAT" "211" && starttls_just_send "FEAT"
2015-07-07 22:59:31 +02:00
starttls_line "AUTH TLS" "successful|234"
; ;
2015-08-12 13:58:45 +02:00
smtp) # SMTP, see https://tools.ietf.org/html/rfc4217
2015-07-08 21:30:31 +02:00
$FAST_STARTTLS || starttls_just_read
$FAST_STARTTLS || starttls_line "EHLO testssl.sh" "220|250" && starttls_just_send "EHLO testssl.sh"
2015-07-07 22:59:31 +02:00
starttls_line "STARTTLS" "220"
; ;
2015-08-12 13:58:45 +02:00
pop3) # POP, see https://tools.ietf.org/html/rfc2595
2015-07-08 21:30:31 +02:00
$FAST_STARTTLS || starttls_just_read
2015-07-07 22:59:31 +02:00
starttls_line "STLS" "OK"
; ;
2015-08-12 13:58:45 +02:00
nntp) # NNTP, see https://tools.ietf.org/html/rfc4642
2015-07-08 21:30:31 +02:00
$FAST_STARTTLS || starttls_just_read
$FAST_STARTTLS || starttls_line "CAPABILITIES" "101|200" && starttls_just_send "CAPABILITIES"
2015-07-07 22:59:31 +02:00
starttls_line "STARTTLS" "382"
; ;
2015-08-12 13:58:45 +02:00
imap) # IMAP, https://tools.ietf.org/html/rfc2595
2015-07-08 21:30:31 +02:00
$FAST_STARTTLS || starttls_just_read
$FAST_STARTTLS || starttls_line "a001 CAPABILITY" "OK" && starttls_just_send "a001 CAPABILITY"
2015-07-07 22:59:31 +02:00
starttls_line "a002 STARTTLS" "OK"
; ;
2015-08-12 13:58:45 +02:00
ldap) # LDAP, https://tools.ietf.org/html/rfc2830, https://tools.ietf.org/html/rfc4511
2015-07-07 22:59:31 +02:00
pr_magentaln "FIXME: LDAP/STARTTLS not yet supported"
2015-07-22 13:11:20 +02:00
exit -4
2015-07-07 22:59:31 +02:00
; ;
2015-08-12 13:58:45 +02:00
acap) # ACAP = Application Configuration Access Protocol, see https://tools.ietf.org/html/rfc2595
2015-07-07 22:59:31 +02:00
pr_magentaln "ACAP Easteregg: not implemented -- probably never will"
2015-07-22 13:11:20 +02:00
exit -4
2015-07-07 22:59:31 +02:00
; ;
2015-08-12 13:58:45 +02:00
xmpp) # XMPP, see https://tools.ietf.org/html/rfc6120
2015-07-07 22:59:31 +02:00
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 !
; ;
2015-08-12 13:58:45 +02:00
*) # we need to throw an error here -- otherwise testssl.sh treats the STARTTLS protocol as plain SSL/TLS which leads to FP
pr_magentaln " FIXME: STARTTLS protocol $STARTTLS_PROTOCOL is not yet supported "
exit -4
2015-07-07 22:59:31 +02:00
esac
fi
2015-06-29 22:29:15 +02:00
return 0
2015-05-17 22:43:53 +02:00
}
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' )
2015-08-01 23:11:27 +02:00
#TODO: just echo, no additional global var
2015-05-17 22:43:53 +02:00
}
len2twobytes( ) {
len_arg1 = $( echo ${# 1 } )
[ [ $len_arg1 -le 2 ] ] && LEN_STR = $( printf "00, %02s \n" $1 )
[ [ $len_arg1 -eq 3 ] ] && LEN_STR = $( printf "%02s, %02s \n" ${ 1 : 0 : 1 } ${ 1 : 1 : 2 } )
[ [ $len_arg1 -eq 4 ] ] && LEN_STR = $( printf "%02s, %02s \n" ${ 1 : 0 : 2 } ${ 1 : 2 : 2 } )
}
socksend_sslv2_clienthello( ) {
code2network " $1 "
data = $( echo $NW_STR )
[ [ " $DEBUG " -ge 4 ] ] && echo " \" $data \" "
printf -- " $data " >& 5 2>/dev/null &
sleep $USLEEP_SND
}
# for SSLv2 to TLS 1.2:
sockread_serverhello( ) {
[ [ -z " $2 " ] ] && maxsleep = $MAX_WAITSOCK || maxsleep = $2
SOCK_REPLY_FILE = $( mktemp $TEMPDIR /ddreply.XXXXXX) || return 7
dd bs = $1 of = $SOCK_REPLY_FILE count = 1 <& 5 2>/dev/null &
2015-07-07 22:59:31 +02:00
wait_kill $! $maxsleep
2015-05-17 22:43:53 +02:00
return $?
}
# arg1: name of file with socket reply
2015-06-23 12:58:40 +02:00
parse_sslv2_serverhello( ) {
2015-05-17 22:43:53 +02:00
# 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
2015-05-29 19:44:27 +02:00
# is the normal case)
2015-05-17 22:43:53 +02:00
# 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
2015-05-29 19:44:27 +02:00
# 12+13 connection id length
# [certificate length] ==> certificate
2015-05-17 22:43:53 +02:00
# [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
2015-06-23 12:58:40 +02:00
parse_tls_serverhello( ) {
2015-06-22 18:32:40 +02:00
local tls_hello_ascii = $( hexdump -v -e '16/1 "%02X"' $1 )
2015-06-22 23:19:08 +02:00
local tls_content_type tls_protocol tls_len_all
2015-06-22 18:32:40 +02:00
#TODO: all vars here
2015-06-23 12:58:40 +02:00
TLS_TIME = ""
DETECTED_TLS_VERSION = ""
2015-05-17 22:43:53 +02:00
# server hello, handshake details see http://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#TLS_record
2015-06-22 23:19:08 +02:00
# 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
2015-05-29 19:44:27 +02:00
# byte 3+4: length all
2015-06-22 18:32:40 +02:00
# byte 5: handshake type (2=hello) TLS alert: level (2=fatal), descr (0x28=handshake failure)
2015-05-29 19:44:27 +02:00
# byte 6+7+8: length server hello
2015-06-22 23:19:08 +02:00
# byte 9+10: 03, TLS version word see byte 1+2
# byte 11-14: TLS timestamp for OpenSSL <1.01f
2015-05-17 22:43:53 +02:00
# byte 15-42: random, 28 bytes
# byte 43: session id length
# byte 44+45+sid-len: cipher suite!
# byte 46+sid-len: compression method: 00: none, 01: deflate
# byte 47+48+sid-len: extension length
[ [ " $DEBUG " -eq 5 ] ] && echo $tls_hello_ascii # one line without any blanks
2015-06-23 12:58:40 +02:00
if [ [ -z $tls_hello_ascii ] ] ; then
debugme echo "server hello empty, TCP connection closed"
return 1 # no server hello received
fi
2015-05-17 22:43:53 +02:00
# now scrape two bytes out of the reply per byte
2015-06-23 12:58:40 +02:00
tls_content_type = " ${ tls_hello_ascii : 0 : 2 } " # normally this is x16 (Handshake) here
2015-06-22 23:19:08 +02:00
tls_protocol = " ${ tls_hello_ascii : 2 : 4 } "
DETECTED_TLS_VERSION = $tls_protocol
2015-05-17 22:43:53 +02:00
tls_len_all = ${ tls_hello_ascii : 6 : 4 }
2015-06-22 23:19:08 +02:00
sid_len_offset = 86
tls_hello = " ${ tls_hello_ascii : 10 : 2 } " # normally this is x02
tls_protocol2 = " ${ tls_hello_ascii : 18 : 4 } "
tls_hello_time = " ${ tls_hello_ascii : 22 : 8 } "
if [ [ $tls_content_type = = "15" ] ] ; then # TLS ALERT
2015-06-22 18:32:40 +02:00
tls_err_level = ${ tls_hello_ascii : 10 : 2 } # 1: warning, 2: fatal
tls_err_descr = ${ tls_hello_ascii : 12 : 2 } # 112/0x70: Unrecognized name, 111/0x6F: certificate_unobtainable,
# 113/0x71: bad_certificate_status_response, #114/0x72: bad_certificate_hash_value
2015-05-17 22:43:53 +02:00
if [ [ $DEBUG -ge 2 ] ] ; then
2015-06-22 23:19:08 +02:00
echo " tls_content_type: 0x $tls_content_type "
echo " tls_protocol: 0x $tls_protocol "
2015-06-22 18:32:40 +02:00
echo " tls_len_all: $tls_len_all "
2015-06-22 23:19:08 +02:00
echo " tls_err_descr: 0x ${ tls_err_descr } / = $( hex2dec ${ tls_err_descr } ) "
echo " tls_err_level: ${ tls_err_level } (warning:1, fatal:2) "
2015-06-22 18:32:40 +02:00
fi
2015-06-23 12:58:40 +02:00
# now, here comes a strange thing... -- on the first glance
2015-06-22 23:19:08 +02:00
# IF an apache 2.2/2.4 server e.g. has a default servername configured but we send SNI <myhostname>
# we get a TLS ALERT saying "unrecognized_name" (0x70) and a warning (0x1), see RFC https://tools.ietf.org/html/rfc6066#page-17
# note that RFC recommended to fail instead: https://tools.ietf.org/html/rfc6066#section-3
2015-06-22 18:32:40 +02:00
# we need to handle this properly -- otherwise we always return that the protocol or cipher is not available!
if [ [ " $tls_err_descr " = = 70 ] ] && [ [ " ${ tls_err_level } " = = "01" ] ] ; then
2015-06-22 23:19:08 +02:00
sid_len_offset = 100 # we are 2x7 bytes off (formerly: 86 instead of 100)
tls_hello = " ${ tls_hello_ascii : 24 : 2 } " # here, too (normally this is (02)
2015-06-23 12:58:40 +02:00
tls_protocol2 = " ${ tls_hello_ascii : 32 : 4 } " # here, too
tls_hello_time = " ${ tls_hello_ascii : 36 : 8 } " # and here, too
2015-06-22 18:32:40 +02:00
else
return 1
2015-05-17 22:43:53 +02:00
fi
fi
2015-06-22 23:19:08 +02:00
TLS_TIME = $( hex2dec $tls_hello_time )
tls_sid_len = $( hex2dec ${ tls_hello_ascii : $sid_len_offset : 2 } )
let sid_offset = $sid_len_offset +2+$tls_sid_len *2
2015-05-17 22:43:53 +02:00
tls_cipher_suite = " ${ tls_hello_ascii : $sid_offset : 4 } "
2015-06-22 23:19:08 +02:00
let sid_offset = $sid_len_offset +6++$tls_sid_len *2
2015-05-17 22:43:53 +02:00
tls_compression_method = " ${ tls_hello_ascii : $sid_offset : 2 } "
if [ [ $DEBUG -ge 2 ] ] ; then
2015-06-22 18:32:40 +02:00
echo " tls_hello: 0x $tls_hello "
2015-05-17 22:43:53 +02:00
if [ [ $DEBUG -ge 4 ] ] ; then
2015-06-22 23:19:08 +02:00
echo " tls_protocol2: 0x $tls_protocol2 "
echo " tls_sid_len: 0x $( dec2hex $tls_sid_len ) / = $tls_sid_len "
fi
echo -n " tls_hello_time: 0x $tls_hello_time "
if $HAS_GNUDATE ; then
echo $( date --date= " @ $TLS_TIME " "+%Y-%m-%d %r" )
else
echo $( date -j -f %s " $TLS_TIME " "+%Y-%m-%d %r" )
2015-05-17 22:43:53 +02:00
fi
2015-06-22 18:32:40 +02:00
echo " tls_cipher_suite: 0x $tls_cipher_suite "
2015-05-17 22:43:53 +02:00
echo " tls_compression_method: 0x $tls_compression_method "
outln
fi
return 0
}
sslv2_sockets( ) {
local ciphers_detected
fd_socket 5 || return 6
[ [ " $DEBUG " -ge 2 ] ] && outln "sending client hello... "
socksend_sslv2_clienthello " $SSLv2_CLIENT_HELLO "
2015-05-29 19:44:27 +02:00
sockread_serverhello 32768
2015-05-17 22:43:53 +02:00
[ [ " $DEBUG " -ge 2 ] ] && outln "reading server hello... "
if [ [ " $DEBUG " -ge 4 ] ] ; then
hexdump -C $SOCK_REPLY_FILE | head -6
outln
fi
2015-06-23 12:58:40 +02:00
parse_sslv2_serverhello " $SOCK_REPLY_FILE "
2015-05-17 22:43:53 +02:00
case $? in
7) # strange reply, couldn't convert the cipher spec length to a hex number
pr_litemagenta "strange v2 reply "
outln " (rerun with DEBUG >=2)"
[ [ $DEBUG -ge 3 ] ] && hexdump -C $SOCK_REPLY_FILE | head -1
ret = 7 ; ;
1) # no sslv2 server hello returned, like in openlitespeed which returns HTTP!
pr_greenln "not offered (OK)"
ret = 0 ; ;
0) # reset
pr_greenln "not offered (OK)"
ret = 0 ; ;
3) # everything else
lines = $( hexdump -C " $SOCK_REPLY_FILE " 2>/dev/null | wc -l | sed 's/ //g' )
[ [ " $DEBUG " -ge 2 ] ] && out " ( $lines lines) "
if [ [ " $lines " -gt 1 ] ] ; then
ciphers_detected = $(( $V2_HELLO_CIPHERSPEC_LENGTH / 3 ))
if [ 0 -eq " $ciphers_detected " ] ; then
2015-05-27 17:04:35 +02:00
pr_litered "supported but couldn't detect a cipher" ; outln " (may need further attention)"
2015-05-17 22:43:53 +02:00
else
pr_red "offered (NOT ok)" ; outln " -- $ciphers_detected ciphers "
fi
ret = 1
fi ; ;
2015-05-29 19:44:27 +02:00
esac
2015-05-17 22:43:53 +02:00
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( ) {
2015-06-28 13:52:42 +02:00
#FIXME: redo this with all extensions!
2015-06-22 18:32:40 +02:00
local tls_low_byte = " $1 "
2015-06-24 11:08:09 +02:00
local tls_low_byte1 = "01" # the first TLS version number is always 0301 -- except: SSLv3
2015-05-17 22:43:53 +02:00
local servername_hexstr len_servername len_servername_hex
local hexdump_format_str
2015-06-24 11:08:09 +02:00
local all_extensions
2015-05-17 22:43:53 +02:00
local len_sni_listlen len_sni_ext len_extension_hex
local cipher_suites len_ciph_suites len_ciph_suites_word
local len_client_hello_word len_all_word
len_servername = $( echo ${# NODE } )
hexdump_format_str = " $len_servername /1 \"%02x,\" "
servername_hexstr = $( printf $NODE | hexdump -v -e " ${ hexdump_format_str } " | sed 's/,$//' )
2015-06-24 11:08:09 +02:00
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
2015-05-17 22:43:53 +02:00
#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)) )
2015-06-24 11:08:09 +02:00
len_extension_hex = $( printf "%02x\n" $(( len_servername+9)) ) #FIXME: for TLS 1.2 and IIS servers we need extension_signature_algorithms!!
2015-05-17 22:43:53 +02:00
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" $(( 0 x$len_ciph_suites + 0 x27)) )
else
len2twobytes $( printf "%02x\n" $(( 0 x$len_ciph_suites + 0 x27 + 0 x$len_extension_hex + 0 x2)) )
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" $(( 0 x$len_ciph_suites + 0 x2b)) )
else
len2twobytes $( printf "%02x\n" $(( 0 x$len_ciph_suites + 0 x2b + 0 x$len_extension_hex + 0 x2)) )
fi
len_all_word = " $LEN_STR "
#[[ $DEBUG -ge 3 ]] && echo $len_all_word
2015-06-24 11:08:09 +02:00
# if we have SSLv3, the first occurence of TLS protocol is SSLv3, otherwise TLS 1.0
[ [ $tls_low_byte = = "00" ] ] && tls_low_byte1 = "00"
2015-05-17 22:43:53 +02:00
TLS_CLIENT_HELLO = "
# TLS header ( 5 bytes)
2015-06-24 11:08:09 +02:00
,16, 03, $tls_low_byte1 # TLS Version: in wireshark this is always 00 for TLS 1.0-1.2
2015-05-17 22:43:53 +02:00
,$len_all_word # Length <---
# Handshake header:
,01 # Type (x01 for ClientHello)
,00, $len_client_hello_word # Length ClientHello
,03, $tls_low_byte # TLS Version (again)
,54, 51, 1e, 7a # Unix time since see www.moserware.com/2009/06/first-few-milliseconds-of-https.html
,de, ad, be, ef # Random 28 bytes
,31, 33, 07, 00, 00, 00, 00, 00
,cf, bd, 39, 04, cc, 16, 0a, 85
,03, 90, 9f, 77, 04, 33, d4, de
,00 # Session ID length
,$len_ciph_suites_word # Cipher suites length
,$cipher_suites
,01 # Compression methods length
,00" # Compression method (x00 for NULL)
2015-06-22 18:32:40 +02:00
#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
2015-06-24 11:08:09 +02:00
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"
2015-06-22 18:32:40 +02:00
# Extension: Haertbeat 00 0f
# len 00 01
# peer allowed to send requests 01
2015-05-17 22:43:53 +02:00
if [ " $tls_low_byte " = = "00" ] ; then
2015-06-24 11:08:09 +02:00
all_extensions = ""
else #FIXME: we (probably) need extension_signature_algorithms here. TLS 1.2 fails on IIS otherwise
all_extensions = "
2015-05-17 22:43:53 +02:00
,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
2015-06-24 11:08:09 +02:00
2015-05-17 22:43:53 +02:00
fd_socket 5 || return 6
2015-06-24 11:08:09 +02:00
code2network " $TLS_CLIENT_HELLO $all_extensions "
2015-05-17 22:43:53 +02:00
data = $( echo $NW_STR )
[ [ " $DEBUG " -ge 4 ] ] && echo " \" $data \" "
printf -- " $data " >& 5 2>/dev/null &
sleep $USLEEP_SND
return 0
}
2015-06-22 18:32:40 +02:00
# arg1: TLS version low byte
# (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2)
2015-05-17 22:43:53 +02:00
tls_sockets( ) {
2015-07-22 13:11:20 +02:00
local -i ret = 0
local -i save = 0
2015-05-17 22:43:53 +02:00
local lines
local tls_low_byte
local cipher_list_2send
tls_low_byte = " $1 "
2015-06-24 11:08:09 +02:00
if [ -n " $2 " ] ; then # use supplied string in arg2 if there is one
2015-05-29 19:44:27 +02:00
cipher_list_2send = " $2 "
2015-05-17 22:43:53 +02:00
else # otherwise use std ciphers then
2015-06-29 10:41:56 +02:00
if [ [ " $tls_low_byte " = = "03" ] ] ; then
2015-05-17 22:43:53 +02:00
cipher_list_2send = " $TLS12_CIPHER "
else
cipher_list_2send = " $TLS_CIPHER "
fi
fi
[ [ " $DEBUG " -ge 2 ] ] && echo "sending client hello..."
socksend_tls_clienthello " $tls_low_byte " " $cipher_list_2send "
2015-06-24 11:08:09 +02:00
ret = $? # 6 means opening socket didn't succeed, e.g. timeout
2015-05-17 22:43:53 +02:00
# if sending didn't succeed we don't bother
if [ $ret -eq 0 ] ; then
2015-05-29 19:44:27 +02:00
sockread_serverhello 32768
2015-05-17 22:43:53 +02:00
TLS_NOW = $( date "+%s" )
[ [ " $DEBUG " -ge 2 ] ] && outln "reading server hello..."
if [ [ " $DEBUG " -ge 3 ] ] ; then
hexdump -C $SOCK_REPLY_FILE | head -6
echo
fi
2015-06-23 12:58:40 +02:00
parse_tls_serverhello " $SOCK_REPLY_FILE "
2015-05-17 22:43:53 +02:00
save = $?
# see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL
lines = $( hexdump -C " $SOCK_REPLY_FILE " 2>/dev/null | wc -l | sed 's/ //g' )
2015-05-29 19:44:27 +02:00
[ [ " $DEBUG " -ge 2 ] ] && out " (returned $lines lines) "
2015-05-17 22:43:53 +02:00
# determine the return value for higher level, so that they can tell what the result is
if [ [ $save -eq 1 ] ] || [ [ $lines -eq 1 ] ] ; then
2015-06-22 18:32:40 +02:00
ret = 1 # NOT available
2015-05-17 22:43:53 +02:00
else
if [ [ 03$tls_low_byte -eq $DETECTED_TLS_VERSION ] ] ; then
2015-06-22 18:32:40 +02:00
ret = 0 # protocol available, TLS version returned equal to the one send
2015-05-17 22:43:53 +02:00
else
2015-06-22 23:19:08 +02:00
[ [ $DEBUG -ge 2 ] ] && echo -n " protocol send: 0x03 $tls_low_byte , returned: 0x $DETECTED_TLS_VERSION "
2015-06-22 18:32:40 +02:00
ret = 2 # protocol NOT available, server downgraded to $DETECTED_TLS_VERSION
2015-05-17 22:43:53 +02:00
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 #######
2015-06-22 18:32:40 +02:00
# general overview which browser "supports" which vulnerability:
2015-05-17 22:43:53 +02:00
# http://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#Web_browsers
# mainly adapted from https://gist.github.com/takeshixx/10107280
2015-07-22 13:11:20 +02:00
run_heartbleed( ) {
2015-05-17 22:43:53 +02:00
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for heartbleed vulnerability" && outln "\n"
pr_bold " Heartbleed\c" ; out " (CVE-2014-0160) "
2015-07-08 21:30:31 +02:00
#if [[ -n "$STARTTLS" ]] && [[ $EXPERIMENTAL != "yes" ]] ; then
# outln "(not yet implemented for STARTTLS)"
# return 0
#fi
2015-05-17 22:43:53 +02:00
# determine TLS versions available:
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY -tlsextdebug & >$TMPFILE </dev/null
2015-05-29 19:44:27 +02:00
2015-06-17 11:33:29 +02:00
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
2015-05-17 22:43:53 +02:00
case $tls_proto_offered in
12) tls_hexcode = "x03, x03" ; ;
11) tls_hexcode = "x03, x02" ; ;
*) tls_hexcode = "x03, x01" ; ;
esac
heartbleed_payload = " , x18, $tls_hexcode , x00, x03, x01, x40, x00 "
client_hello = "
# TLS header ( 5 bytes)
,x16, # content type (x16 for handshake)
$tls_hexcode , # TLS version
x00, xdc, # length
# Handshake header
x01, # type (x01 for ClientHello)
x00, x00, xd8, # length
$tls_hexcode , # TLS version
# Random (32 byte)
x53, x43, x5b, x90, x9d, x9b, x72, x0b,
xbc, x0c, xbc, x2b, x92, xa8, x48, x97,
xcf, xbd, x39, x04, xcc, x16, x0a, x85,
x03, x90, x9f, x77, x04, x33, xd4, xde,
x00, # session ID length
x00, x66, # cipher suites length
# cipher suites (51 suites)
xc0, x14, xc0, x0a, xc0, x22, xc0, x21,
x00, x39, x00, x38, x00, x88, x00, x87,
xc0, x0f, xc0, x05, x00, x35, x00, x84,
xc0, x12, xc0, x08, xc0, x1c, xc0, x1b,
x00, x16, x00, x13, xc0, x0d, xc0, x03,
x00, x0a, xc0, x13, xc0, x09, xc0, x1f,
xc0, x1e, x00, x33, x00, x32, x00, x9a,
x00, x99, x00, x45, x00, x44, xc0, x0e,
xc0, x04, x00, x2f, x00, x96, x00, x41,
xc0, x11, xc0, x07, xc0, x0c, xc0, x02,
x00, x05, x00, x04, x00, x15, x00, x12,
x00, x09, x00, x14, x00, x11, x00, x08,
x00, x06, x00, x03, x00, xff,
x01, # compression methods length
x00, # compression method (x00 for NULL)
x00, x49, # extensions length
# extension: ec_point_formats
x00, x0b, x00, x04, x03, x00, x01, x02,
# extension: elliptic_curves
x00, x0a, x00, x34, x00, x32, x00, x0e,
x00, x0d, x00, x19, x00, x0b, x00, x0c,
x00, x18, x00, x09, x00, x0a, x00, x16,
x00, x17, x00, x08, x00, x06, x00, x07,
x00, x14, x00, x15, x00, x04, x00, x05,
x00, x12, x00, x13, x00, x01, x00, x02,
x00, x03, x00, x0f, x00, x10, x00, x11,
# extension: session ticket TLS
x00, x23, x00, x00,
# extension: heartbeat
x00, x0f, x00, x01, x01"
fd_socket 5 || return 6
[ [ $DEBUG -ge 2 ] ] && outln " \nsending client hello (TLS version $tls_hexcode ) "
socksend " $client_hello " 1
2015-05-29 19:44:27 +02:00
sockread 16384
2015-05-17 22:43:53 +02:00
[ [ $DEBUG -ge 2 ] ] && outln "\nreading server hello"
if [ [ $DEBUG -ge 3 ] ] ; then
echo " $SOCKREPLY " | " ${ HEXDUMPVIEW [@] } " | head -20
outln "[...]"
outln " \nsending payload with TLS version $tls_hexcode : "
fi
socksend " $heartbleed_payload " 1
sockread 16384 $HEARTBLEED_MAX_WAITSOCK
retval = $?
if [ [ $DEBUG -ge 3 ] ] ; then
outln "\nheartbleed reply: "
echo " $SOCKREPLY " | " ${ HEXDUMPVIEW [@] } "
outln
fi
lines_returned = $( echo " $SOCKREPLY " | " ${ HEXDUMP [@] } " | wc -l | sed 's/ //g' )
if [ $lines_returned -gt 1 ] ; then
pr_red "VULNERABLE (NOT ok)"
ret = 1
else
pr_green "not vulnerable (OK)"
ret = 0
fi
2015-05-25 21:14:59 +02:00
[ $retval -eq 3 ] && out " (timed out)"
2015-05-17 22:43:53 +02:00
outln
close_socket
tmpfile_handle $FUNCNAME .txt
return $ret
}
# helper function
ok_ids( ) {
pr_greenln "\n ok -- something resetted our ccs packets"
return 0
}
#FIXME: At a certain point heartbleed and ccs needs to be changed and make use of code2network using a file, then tls_sockets
2015-07-22 13:11:20 +02:00
run_ccs_injection( ) {
2015-05-17 22:43:53 +02:00
# see https://www.openssl.org/news/secadv_20140605.txt
# mainly adapted from Ramon de C Valle's C code from https://gist.github.com/rcvalle/71f4b027d61a78c42607
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for CCS injection vulnerability" && outln "\n"
2015-05-27 14:28:18 +02:00
pr_bold " CCS" ; out " (CVE-2014-0224) "
2015-05-17 22:43:53 +02:00
2015-07-08 21:30:31 +02:00
#if [[ -n "$STARTTLS" ]] && [[ $EXPERIMENTAL != "yes" ]] ; then
# outln "(not yet implemented for STARTTLS)"
# return 0
#fi
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY & >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
2015-06-17 11:33:29 +02:00
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
2015-05-17 22:43:53 +02:00
case $tls_proto_offered in
12) tls_hexcode = "x03, x03" ; ;
11) tls_hexcode = "x03, x02" ; ;
*) tls_hexcode = "x03, x01" ; ;
esac
ccs_message = " , x14, $tls_hexcode ,x00, x01, x01 "
client_hello = "
# TLS header (5 bytes)
,x16, # content type (x16 for handshake)
$tls_hexcode , # TLS version
x00, x93, # length
# Handshake header
x01, # type (x01 for ClientHello)
x00, x00, x8f, # length
$tls_hexcode , # TLS version
2015-05-29 19:44:27 +02:00
# Random (32 byte)
2015-05-17 22:43:53 +02:00
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
2015-07-07 22:59:31 +02:00
# we now make a standard handshake ...
debugme out "\nsending client hello, "
2015-05-17 22:43:53 +02:00
socksend " $client_hello " 1
2015-05-29 19:44:27 +02:00
sockread 16384
2015-05-17 22:43:53 +02:00
2015-07-07 22:59:31 +02:00
debugme outln "\nreading server hello"
2015-05-17 22:43:53 +02:00
if [ [ $DEBUG -ge 3 ] ] ; then
echo " $SOCKREPLY " | " ${ HEXDUMPVIEW [@] } " | head -20
outln "[...]"
outln " \npayload #1 with TLS version $tls_hexcode : "
fi
2015-07-07 22:59:31 +02:00
# ... and then send the a change cipher spec message
2015-05-17 22:43:53 +02:00
socksend " $ccs_message " 1 || ok_ids
sockread 2048 $CCS_MAX_WAITSOCK
if [ [ $DEBUG -ge 3 ] ] ; then
2015-05-29 19:44:27 +02:00
outln "\n1st reply: "
2015-05-17 22:43:53 +02:00
out " $SOCKREPLY " | " ${ HEXDUMPVIEW [@] } " | head -20
2015-07-07 22:59:31 +02:00
# ok: 15 | 0301 | 02 | 02 | 0a
# ALERT | TLS 1.0 | Length=2 | Unexpected Message (0a)
# or just timed out
2015-05-17 22:43:53 +02:00
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: "
2015-07-07 22:59:31 +02:00
printf -- " $SOCKREPLY " | " ${ HEXDUMPVIEW [@] } "
# not ok: 15 | 0301 | 02 | 02 | 15
# ALERT | TLS 1.0 | Length=2 | Decryption failed (21)
2015-05-17 22:43:53 +02:00
# ok: 0a or nothing: ==> RST
outln
fi
2015-07-07 22:59:31 +02:00
byte6 = $( echo " $SOCKREPLY " | " ${ HEXDUMPPLAIN [@] } " | sed 's/^..........//' )
lines = $( echo " $SOCKREPLY " | " ${ HEXDUMP [@] } " | count_lines )
debugme echo " lines: $lines , byte6: $byte6 "
2015-05-17 22:43:53 +02:00
2015-07-07 22:59:31 +02:00
if [ " $byte6 " = = "0a" ] || [ " $lines " -gt 1 ] ; then
2015-05-17 22:43:53 +02:00
pr_green "not vulnerable (OK)"
ret = 0
else
pr_red "VULNERABLE (NOT ok)"
ret = 1
fi
2015-05-25 21:14:59 +02:00
[ $retval -eq 3 ] && out " (timed out)"
2015-05-29 19:44:27 +02:00
outln
2015-05-17 22:43:53 +02:00
close_socket
tmpfile_handle $FUNCNAME .txt
return $ret
}
2015-07-22 13:11:20 +02:00
run_renego( ) {
2015-05-17 22:43:53 +02:00
# no SNI here. Not needed as there won't be two different SSL stacks for one IP
local legacycmd = ""
local insecure_renogo_str
local sec_renego sec_client_renego
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for Renegotiation vulnerability" && outln "\n"
pr_bold " Secure Renegotiation " ; out "(CVE 2009-3555) " # and RFC5746, OSVDB 59968-59974
# community.qualys.com/blogs/securitylabs/2009/11/05/ssl-and-tls-authentication-gap-vulnerability-discovered
insecure_renogo_str = "Secure Renegotiation IS NOT"
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY 2>& 1 </dev/null | grep -iaq " $insecure_renogo_str "
2015-05-17 22:43:53 +02:00
sec_renego = $? # 0= Secure Renegotiation IS NOT supported
#FIXME: didn't occur to me yet but why not also to check on "Secure Renegotiation IS supported"
case $sec_renego in
0) pr_redln "VULNERABLE (NOT ok)" ; ;
1) pr_greenln "not vulnerable (OK)" ; ;
*) pr_magentaln " FIXME (bug): $sec_renego " ; ;
esac
pr_bold " Secure Client-Initiated Renegotiation " # RFC 5746
# see: https://community.qualys.com/blogs/securitylabs/2011/10/31/tls-renegotiation-and-denial-of-service-attacks
# http://blog.ivanristic.com/2009/12/testing-for-ssl-renegotiation.html -- head/get doesn't seem to be needed though
case " $OSSL_VER " in
0.9.8*) # we need this for Mac OSX unfortunately
case " $OSSL_VER_APPENDIX " in
2015-05-27 23:31:25 +02:00
[ a-l] ) pr_magentaln " Local Problem: $OPENSSL cannot test this secure renegotiation vulnerability "
2015-05-17 22:43:53 +02:00
return 3 ; ;
[ m-z] ) ; ; # all ok
esac ; ;
1.0.1*| 1.0.2*) legacycmd = "-legacy_renegotiation" ; ;
2015-05-29 19:44:27 +02:00
0.9.9*| 1.0*) ; ; # all ok
2015-05-17 22:43:53 +02:00
esac
# We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background
2015-06-29 22:29:15 +02:00
echo R | $OPENSSL s_client $legacycmd $STARTTLS -msg -connect $NODEIP :$PORT $PROXY & >$TMPFILE & # msg enables us to look deeper into it while debugging
2015-05-17 22:43:53 +02:00
wait_kill $! $HEADER_MAXSLEEP
if [ $? -eq 3 ] ; then
2015-06-01 14:16:31 +02:00
pr_litegreen "likely not vulnerable (OK)" ; outln " (timed out)" # it hung
2015-05-17 22:43:53 +02:00
sec_client_renego = 1
else
# second try in the foreground as we are sure now it won't hang
2015-06-29 22:29:15 +02:00
echo R | $OPENSSL s_client $legacycmd $STARTTLS -msg -connect $NODEIP :$PORT $PROXY & >$TMPFILE
2015-05-17 22:43:53 +02:00
sec_client_renego = $? # 0=client is renegotiating & doesn't return an error --> vuln!
case $sec_client_renego in
0) pr_litered "VULNERABLE (NOT ok)" ; outln ", DoS threat" ; ;
1) pr_litegreenln "not vulnerable (OK)" ; ;
*) " FIXME (bug): $sec_client_renego " ; ;
esac
fi
#FIXME Insecure Client-Initiated Renegotiation is missing
tmpfile_handle $FUNCNAME .txt
return $(( $sec_renego + $sec_client_renego ))
#FIXME: the return value is wrong, should be 0 if all ok. But as the caller doesn't care we don't care either ... yet ;-)
}
2015-07-22 13:11:20 +02:00
run_crime( ) {
local -i ret = 0
local addcmd = ""
2015-05-17 22:43:53 +02:00
# in a nutshell: don't offer TLS/SPDY compression on the server side
# This tests for CRIME Vulnerability (www.ekoparty.org/2012/juliano-rizzo.php) on HTTPS, not SPDY (yet)
# Please note that it is an attack where you need client side control, so in regular situations this
# means anyway "game over", w/wo CRIME
# www.h-online.com/security/news/item/Vulnerability-in-SSL-encryption-is-barely-exploitable-1708604.html
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for CRIME vulnerability" && outln "\n"
pr_bold " CRIME, TLS " ; out "(CVE-2012-4929) "
# first we need to test whether OpenSSL binary has zlib support
2015-05-29 19:44:27 +02:00
$OPENSSL zlib -e -a -in /dev/stdin & >/dev/stdout </dev/null | grep -q zlib
2015-05-17 22:43:53 +02:00
if [ $? -eq 0 ] ; then
pr_magentaln " Local Problem: Your $OPENSSL lacks zlib support "
2015-05-29 19:44:27 +02:00
return 7
2015-05-17 22:43:53 +02:00
fi
2015-07-22 13:11:20 +02:00
[ [ " $OSSL_VER " = ~ "0.9.8" ] ] && addcmd = "-no_ssl2"
$OPENSSL s_client $addcmd $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI </dev/null & >$TMPFILE
2015-05-17 22:43:53 +02:00
if grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then
2015-06-11 21:41:25 +02:00
pr_litegreen "not vulnerable (OK)"
2015-05-17 22:43:53 +02:00
[ [ $SERVICE = = "HTTP" ] ] || out " (not using HTTP anyway)"
ret = 0
else
if [ [ $SERVICE = = "HTTP" ] ] ; then
2015-06-11 21:41:25 +02:00
pr_litered "VULNERABLE (NOT ok)"
2015-05-29 19:44:27 +02:00
else
2015-05-17 22:43:53 +02:00
pr_brown "VULNERABLE (NOT ok), but not using HTTP: probably no exploit known"
fi
ret = 1
fi
2015-05-29 19:44:27 +02:00
# not clear whether this is a protocol != HTTP as one needs to have the ability to modify the
2015-05-17 22:43:53 +02:00
# compression input which is done via javascript in the context of HTTP
outln
# this needs to be re-done i order to remove the redundant check for spdy
# weed out starttls, spdy-crime is a web thingy
# if [ "x$STARTTLS" != "x" ]; then
# echo
# return $ret
# fi
# weed out non-webports, spdy-crime is a web thingy. there's a catch thoug, you see it?
# case $PORT in
# 25|465|587|80|110|143|993|995|21)
# echo
# return $ret
# esac
# $OPENSSL s_client help 2>&1 | grep -qw nextprotoneg
# if [ $? -eq 0 ]; then
# $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg $NPN_PROTOs $SNI </dev/null 2>/dev/null >$TMPFILE
# if [ $? -eq 0 ]; then
# echo
# pr_bold "CRIME Vulnerability, SPDY \c" ; outln "(CVE-2012-4929): \c"
# STR=$(grep Compression $TMPFILE )
# if echo $STR | grep -q NONE >/dev/null; then
# pr_green "not vulnerable (OK)"
# ret=$(($ret + 0))
# else
# pr_red "VULNERABLE (NOT ok)"
# ret=$(($ret + 1))
# fi
# fi
# fi
2015-07-06 10:10:46 +02:00
$VERBERR && outln " $STR "
2015-05-17 22:43:53 +02:00
#echo
tmpfile_handle $FUNCNAME .txt
return $ret
}
# BREACH is a HTTP-level compression & an attack which works against any cipher suite and is agnostic
2015-05-29 19:56:57 +02:00
# to the version of TLS/SSL, more: http://www.breachattack.com/ . Foreign referrers are the important thing here!
2015-07-22 13:11:20 +02:00
run_breach( ) {
2015-06-19 20:36:32 +02:00
local header
2015-07-22 13:11:20 +02:00
local -i ret = 0
2015-06-19 20:36:32 +02:00
local referer useragent
local url
2015-05-17 22:43:53 +02:00
[ [ $SERVICE != "HTTP" ] ] && return 7
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for BREACH (HTTP compression) vulnerability" && outln "\n"
pr_bold " BREACH" ; out " (CVE-2013-3587) "
url = " $1 "
[ -z " $url " ] && url = "/"
2015-07-06 10:10:46 +02:00
if $SNEAKY ; then
2015-05-17 22:43:53 +02:00
# see https://community.qualys.com/message/20360
2015-05-29 19:44:27 +02:00
if [ [ " $NODE " = ~ google ] ] ; then
referer = "http://yandex.ru/" # otherwise we have a false positive for google.com
2015-05-17 22:43:53 +02:00
else
referer = "http://google.com/"
fi
useragent = " $UA_SNEAKY "
else
referer = " TLS/SSL-Tester from $SWURL "
useragent = " $UA_STD "
fi
(
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $OPTIMAL_PROTO -quiet -connect $NODEIP :$PORT $PROXY $SNI << EOF
2015-05-17 22:43:53 +02:00
GET $url HTTP/1.1
Host: $NODE
User-Agent: $useragent
Accept: text/*
Accept-Language: en-US,en
Accept-encoding: gzip,deflate,compress
Referer: $referer
Connection: close
EOF
) & >$HEADERFILE_BREACH &
2015-07-07 22:59:31 +02:00
if wait_kill $! $HEADER_MAXSLEEP ; then
2015-05-17 22:43:53 +02:00
result = $( grep -a '^Content-Encoding' $HEADERFILE_BREACH | sed -e 's/^Content-Encoding//' -e 's/://' -e 's/ //g' )
result = $( echo $result | tr -cd '\40-\176' )
if [ -z $result ] ; then
2015-05-29 19:44:27 +02:00
pr_green "no HTTP compression (OK) "
2015-05-17 22:43:53 +02:00
ret = 0
else
pr_litered " NOT ok: uses $result HTTP compression "
ret = 1
fi
# Catch: any URL can be vulnerable. I am testing now only the root. URL!
outln " (only \" $url \" tested) "
else
pr_litemagentaln "failed (HTTP header request stalled)"
ret = 3
fi
return $ret
}
2015-06-11 21:41:25 +02:00
### two helper functions for vulnerabilities follow
count_ciphers( ) {
2015-07-02 16:39:41 +02:00
printf " $1 " | sed 's/:/ /g' | wc -w | sed 's/ //g'
2015-06-11 21:41:25 +02:00
}
2015-06-10 18:38:39 +02:00
2015-06-11 21:41:25 +02:00
actually_supported_ciphers( ) {
2015-07-02 16:39:41 +02:00
$OPENSSL ciphers " $1 " 2>/dev/null || echo ""
2015-06-08 18:19:34 +02:00
}
2015-05-17 22:43:53 +02:00
2015-05-29 19:44:27 +02:00
# Padding Oracle On Downgraded Legacy Encryption, in a nutshell: don't use CBC Ciphers in SSLv3
2015-07-22 13:11:20 +02:00
run_ssl_poodle( ) {
local -i ret = 0
2015-05-17 22:43:53 +02:00
local cbc_ciphers
2015-07-17 14:33:23 +02:00
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"
2015-05-17 22:43:53 +02:00
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for SSLv3 POODLE (Padding Oracle On Downgraded Legacy Encryption)" && outln "\n"
pr_bold " POODLE, SSL" ; out " (CVE-2014-3566) "
cbc_ciphers = $( $OPENSSL ciphers -v 'ALL:eNULL' | awk '/CBC/ { print $1 }' | tr '\n' ':' )
#FIXME: even with worst openssl client (FreeBSD9) we have 17 reasonable ciphers but is that enough to check??
debugme echo $cbc_ciphers
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -ssl3 $STARTTLS -cipher $cbc_ciphers -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
ret = $?
2015-07-06 10:10:46 +02:00
$VERBERR && egrep -q "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
2015-05-17 22:43:53 +02:00
if [ $ret -eq 0 ] ; then
2015-06-11 21:41:25 +02:00
pr_litered "VULNERABLE (NOT ok)" ; out ", uses SSLv3+CBC (check TLS_FALLBACK_SCSV mitigation below)"
2015-05-17 22:43:53 +02:00
else
pr_green "not vulnerable (OK)"
fi
2015-05-29 19:44:27 +02:00
outln
2015-05-17 22:43:53 +02:00
tmpfile_handle $FUNCNAME .txt
2015-05-29 19:44:27 +02:00
return $ret
2015-05-17 22:43:53 +02:00
}
2015-05-29 19:56:57 +02:00
# for appliance which use padding, no fallback needed
2015-07-22 13:11:20 +02:00
run_tls_poodle( ) {
2015-05-17 22:43:53 +02:00
pr_bold " POODLE, SSL" ; out " CVE-2014-8730), experimental "
#FIXME
echo "#FIXME"
return 7
}
2015-07-22 13:11:20 +02:00
run_tls_fallback_scsv( ) {
local -i ret = 0
2015-05-27 23:31:25 +02:00
2015-06-11 21:41:25 +02:00
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for TLS_FALLBACK_SCSV Protection" && outln "\n"
pr_bold " TLS_FALLBACK_SCSV" ; out " (RFC 7507) "
# This isn't a vulnerability check per se, but checks for the existence of
# the countermeasure to protect against protocol downgrade attacks.
# First check we have support for TLS_FALLBACK_SCSV in our local OpenSSL
$OPENSSL s_client -h 2>& 1 | grep -q "\-fallback_scsv"
if [ $? -gt 0 ] ; then
pr_magentaln " Local Problem: Your $OPENSSL lacks TLS_FALLBACK_SCSV support "
return 4
fi
2015-05-29 19:44:27 +02:00
2015-06-11 21:41:25 +02:00
# ...and do the test
2015-07-16 17:58:03 +02:00
$OPENSSL s_client $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI -no_tls1_2 -fallback_scsv & >$TMPFILE </dev/null
2015-06-11 21:41:25 +02:00
if grep -q "CONNECTED(00" " $TMPFILE " ; then
2015-06-20 19:36:11 +02:00
if grep -qa "BEGIN CERTIFICATE" " $TMPFILE " ; then
2015-06-11 21:41:25 +02:00
pr_brown "Downgrade attack prevention NOT supported"
2015-06-20 19:36:11 +02:00
ret = 1
elif grep -qa "alert inappropriate fallback" " $TMPFILE " ; then
pr_litegreen "Downgrade attack prevention supported (OK)"
ret = 0
elif grep -qa "alert handshake failure" " $TMPFILE " ; then
# see RFC 7507, https://github.com/drwetter/testssl.sh/issues/121
2015-07-16 17:58:03 +02:00
pr_brown "\"handshake failure\" instead of \"inappropriate fallback\" (likely NOT ok)"
2015-06-11 21:41:25 +02:00
ret = 2
2015-07-16 17:58:03 +02:00
elif grep -qa "ssl handshake failure" " $TMPFILE " ; then
pr_brown "some enexpected \"handshake failure\" instead of \"inappropriate fallback\" (likely NOT ok)"
ret = 3
else
pr_magenta "test failed, unexpected result "
out " , run $PROG_NAME -Z --debug=1 and look at $TEMPDIR /*tls_fallback_scsv.txt "
2015-06-11 21:41:25 +02:00
fi
else
pr_magenta "test failed (couldn't connect)"
ret = 3
fi
outln
tmpfile_handle $FUNCNAME .txt
return $ret
}
2015-05-27 23:31:25 +02:00
2015-05-17 22:43:53 +02:00
# Factoring RSA Export Keys: don't use EXPORT RSA ciphers, see https://freakattack.com/
2015-07-22 13:11:20 +02:00
run_freak( ) {
local -i ret = 0
2015-05-27 23:31:25 +02:00
local -i no_supported_ciphers = 0
# with correct build it should list these 7 ciphers (plus the two latter as SSLv2 ciphers):
local exportrsa_cipher_list = "EXP1024-DES-CBC-SHA:EXP1024-RC4-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5:EXP-RC4-MD5:EXP-RC4-MD5"
2015-05-17 22:43:53 +02:00
local addtl_warning = ""
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for FREAK attack" && outln "\n"
2015-05-27 14:28:18 +02:00
pr_bold " FREAK" ; out " (CVE-2015-0204), experimental "
2015-05-27 23:31:25 +02:00
no_supported_ciphers = $( count_ciphers $( actually_supported_ciphers $exportrsa_cipher_list ) )
2015-07-02 16:39:41 +02:00
#echo "========= ${PIPESTATUS[*]}
2015-05-27 23:31:25 +02:00
case $no_supported_ciphers in
2015-05-29 19:44:27 +02:00
0) pr_magentaln " Local problem: your $OPENSSL doesn't have any EXPORT RSA ciphers configured "
2015-07-22 13:11:20 +02:00
return 7 ; ;
2015-05-29 19:44:27 +02:00
1| 2| 3)
2015-05-27 23:31:25 +02:00
addtl_warning = " ( $magenta " " tested only with $no_supported_ciphers out of 9 ciphers only! $off ) " ; ;
2015-05-27 14:28:18 +02:00
8| 9| 10| 11)
2015-05-17 22:43:53 +02:00
addtl_warning = "" ; ;
2015-05-29 19:44:27 +02:00
4| 5| 6| 7)
2015-05-27 23:31:25 +02:00
addtl_warning = " (tested with $no_supported_ciphers /9 ciphers) " ; ;
2015-05-17 22:43:53 +02:00
esac
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -cipher $exportrsa_cipher_list -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
ret = $?
2015-07-06 10:10:46 +02:00
$VERBERR && egrep -a "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
2015-05-17 22:43:53 +02:00
if [ $ret -eq 0 ] ; then
pr_red "VULNERABLE (NOT ok)" ; out ", uses EXPORT RSA ciphers"
else
pr_green "not vulnerable (OK)" ; out " $addtl_warning "
fi
2015-05-29 19:44:27 +02:00
outln
2015-05-17 22:43:53 +02:00
2015-05-27 23:31:25 +02:00
debugme echo $( actually_supported_ciphers $exportrsa_cipher_list )
debugme echo $no_supported_ciphers
2015-05-17 22:43:53 +02:00
tmpfile_handle $FUNCNAME .txt
2015-05-29 19:44:27 +02:00
return $ret
2015-05-17 22:43:53 +02:00
}
2015-05-27 14:28:18 +02:00
# see https://weakdh.org/logjam.html
2015-07-22 13:11:20 +02:00
run_logjam( ) {
local -i ret = 0
2015-05-27 23:31:25 +02:00
local exportdhe_cipher_list = "EXP1024-DHE-DSS-DES-CBC-SHA:EXP1024-DHE-DSS-RC4-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA"
local -i no_supported_ciphers = 0
2015-05-27 14:28:18 +02:00
local addtl_warning = ""
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for LOGJAM vulnerability" && outln "\n"
pr_bold " LOGJAM" ; out " (CVE-2015-4000), experimental "
2015-05-27 23:31:25 +02:00
no_supported_ciphers = $( count_ciphers $( actually_supported_ciphers $exportdhe_cipher_list ) )
case $no_supported_ciphers in
2015-05-29 19:44:27 +02:00
0) pr_magentaln " Local problem: your $OPENSSL doesn't have any DHE EXPORT ciphers configured "
2015-05-27 14:28:18 +02:00
return 3 ; ;
2015-05-27 23:31:25 +02:00
1| 2) addtl_warning = " ( $magenta " " tested w/ $no_supported_ciphers /4 ciphers only! $off ) " ; ;
3) addtl_warning = " (tested w/ $no_supported_ciphers /4 ciphers) " ; ;
2015-05-27 14:28:18 +02:00
4) ; ;
esac
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $STARTTLS -cipher $exportdhe_cipher_list -connect $NODEIP :$PORT $PROXY $SNI & >$TMPFILE </dev/null
2015-05-27 14:28:18 +02:00
ret = $?
2015-07-06 10:10:46 +02:00
$VERBERR && egrep -a "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
2015-06-19 20:36:32 +02:00
addtl_warning = " $addtl_warning , common primes not checked. \" $PROG_NAME -E\" spots candidates "
2015-05-27 14:28:18 +02:00
if [ $ret -eq 0 ] ; then
pr_red "VULNERABLE (NOT ok)" ; out ", uses DHE EXPORT ciphers"
else
pr_green "not vulnerable (OK)" ; out " $addtl_warning "
fi
outln
2015-05-27 23:31:25 +02:00
debugme echo $( actually_supported_ciphers $exportdhe_cipher_list )
debugme echo $no_supported_ciphers
2015-05-27 14:28:18 +02:00
tmpfile_handle $FUNCNAME .txt
return $ret
}
2015-06-28 13:52:42 +02:00
# TODO: perfect candidate for replacement by sockets, so is freak
2015-05-27 14:28:18 +02:00
2015-05-17 22:43:53 +02:00
# Browser Exploit Against SSL/TLS: don't use CBC Ciphers in SSLv3 TLSv1.0
2015-07-22 13:11:20 +02:00
run_beast( ) {
2015-05-17 22:43:53 +02:00
local hexcode dash cbc_cipher sslvers kx auth enc mac export
local detected_proto
2015-07-22 13:11:20 +02:00
local -i ret = 0
2015-05-27 23:31:25 +02:00
local detected_cbc_ciphers = ""
2015-05-17 22:43:53 +02:00
local higher_proto_supported = ""
2015-05-27 17:04:35 +02:00
local openssl_ret = 0
local vuln_beast = false
2015-05-17 22:43:53 +02:00
local spaces = " "
local cr = $'\n'
local first = true
2015-05-25 21:14:59 +02:00
local continued = false
2015-05-27 17:04:35 +02:00
local cbc_cipher_list = "SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-AES-256-CBC-SHA:RSA-PSK-AES256-CBC-SHA:PSK-AES256-CBC-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-AES-128-CBC-SHA:IDEA-CBC-SHA:IDEA-CBC-MD5:RC2-CBC-MD5:RSA-PSK-AES128-CBC-SHA:PSK-AES128-CBC-SHA:KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:DES-CBC3-MD5:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:DES-CBC-MD5:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5"
2015-05-17 22:43:53 +02:00
2015-07-06 10:10:46 +02:00
if [ $VULN_COUNT -le $VULN_THRESHLD ] || $WIDE ; then
2015-05-29 19:44:27 +02:00
outln
2015-05-27 17:04:35 +02:00
pr_blue "--> Testing for BEAST vulnerability" && outln "\n"
fi
2015-05-17 22:43:53 +02:00
pr_bold " BEAST" ; out " (CVE-2011-3389) "
2015-07-06 10:10:46 +02:00
$WIDE && outln
2015-05-17 22:43:53 +02:00
# 2) test handfull of common CBC ciphers
for proto in ssl3 tls1; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -" $proto " $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI >$TMPFILE 2>/dev/null </dev/null
2015-07-14 17:13:58 +02:00
if [ [ $? -ne 0 ] ] ; then # protocol supported?
2015-05-27 17:04:35 +02:00
if $continued ; then # second round: we hit TLS1:
2015-05-25 21:14:59 +02:00
pr_litegreenln "no SSL3 or TLS1"
return 0
2015-05-27 17:04:35 +02:00
else # protocol not succeeded but it';s the first time
2015-05-25 21:14:59 +02:00
continued = true
continue # protocol no supported, so we do not need to check each cipher with that protocol
fi
2015-05-27 17:04:35 +02:00
fi # protocol succeeded
# protocol with cbc_cipher check follows now
2015-07-06 10:10:46 +02:00
if $WIDE ; then
2015-05-27 17:04:35 +02:00
outln " \n $( echo $proto | tr '[a-z]' '[A-Z]' ) : " ;
neat_header # NOTTHATNICE: we display the header also if in the end no cbc cipher is available on the client side
2015-05-17 22:43:53 +02:00
fi
2015-05-27 17:04:35 +02:00
while read hexcode dash cbc_cipher sslvers kx auth enc mac; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher " $cbc_cipher " -" $proto " $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI >$TMPFILE 2>/dev/null </dev/null
2015-05-27 17:04:35 +02:00
openssl_ret = $?
[ [ $openssl_ret -eq 0 ] ] && vuln_beast = true
2015-07-06 10:10:46 +02:00
if $WIDE ; then
2015-05-27 17:04:35 +02:00
normalize_ciphercode $hexcode
if [ [ " $SHOW_EACH_C " -ne 0 ] ] ; then
neat_list $HEXC $cbc_cipher $kx $enc
if [ [ $openssl_ret -eq 0 ] ] ; then
pr_brownln "available"
else
outln "not a/v"
fi
else
[ [ $openssl_ret -eq 0 ] ] && neat_list $HEXC $cbc_cipher $kx $enc && outln
fi
else # short display:
2015-07-14 17:13:58 +02:00
if [ [ $openssl_ret -eq 0 ] ] ; then
2015-05-27 23:31:25 +02:00
detected_cbc_ciphers = " $detected_cbc_ciphers " " $( grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g' ) "
2015-05-27 17:04:35 +02:00
vuln_beast = true
fi
2015-05-17 22:43:53 +02:00
fi
done < <( $OPENSSL ciphers -V 'ALL:eNULL' | grep -a CBC) # -V doesn't work with openssl < 1.0
# ^^^^^ process substitution as shopt will either segfault or doesn't work with old bash versions
2015-07-06 10:10:46 +02:00
if ! $WIDE ; then
2015-07-14 17:13:58 +02:00
if [ [ -n " $detected_cbc_ciphers " ] ] ; then
2015-05-27 23:31:25 +02:00
detected_cbc_ciphers = $( echo " $detected_cbc_ciphers " | sed -e " s/ /\\ ${ cr } ${ spaces } /9 " -e " s/ /\\ ${ cr } ${ spaces } /6 " -e " s/ /\\ ${ cr } ${ spaces } /3 " )
2015-05-27 17:04:35 +02:00
! $first && out " $spaces "
2015-05-27 23:31:25 +02:00
out " $( echo $proto | tr '[a-z]' '[A-Z]' ) : " ; pr_brownln " $detected_cbc_ciphers "
detected_cbc_ciphers = "" # empty for next round
2015-05-27 17:04:35 +02:00
first = false
else
[ [ $proto = = "tls1" ] ] && ! $first && printf " $spaces "
pr_litegreenln " no CBC ciphers for $( echo $proto | tr '[a-z]' '[A-Z]' ) (OK) "
first = false
fi
2015-05-17 22:43:53 +02:00
else
2015-05-27 17:04:35 +02:00
$vuln_beast || pr_litegreenln " no CBC ciphers for $( echo $proto | tr '[a-z]' '[A-Z]' ) (OK) "
2015-05-17 22:43:53 +02:00
fi
2015-05-27 17:04:35 +02:00
done # for proto in ssl3 tls1
2015-05-17 22:43:53 +02:00
# 2) support for TLS 1.1+1.2?
for proto in tls1_1 tls1_2; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -state -" $proto " $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI 2>/dev/null >$TMPFILE </dev/null
2015-07-14 17:13:58 +02:00
if [ [ $? -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
higher_proto_supported = " $higher_proto_supported " " $( grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol .*://' -e 's/ //g' ) "
fi
done
2015-05-27 17:04:35 +02:00
if $vuln_beast ; then
2015-07-14 17:13:58 +02:00
if [ [ ! -z " $higher_proto_supported " ] ] ; then
2015-07-06 10:10:46 +02:00
if $WIDE ; then
2015-05-27 17:04:35 +02:00
outln
2015-05-29 19:44:27 +02:00
pr_brown "VULNERABLE"
2015-07-22 13:11:20 +02:00
ret = 1
2015-05-27 17:04:35 +02:00
outln " -- but also supports higher protocols (possible mitigation): $higher_proto_supported "
else
outln " ${ spaces } -- but also supports higher protocols (possible mitigation): $higher_proto_supported "
fi
fi
2015-05-17 22:43:53 +02:00
fi
# printf "For a full individual test of each CBC cipher suites support by your $OPENSSL run \"$0 -x CBC $NODE\"\n"
tmpfile_handle $FUNCNAME .txt
2015-05-29 19:44:27 +02:00
return
2015-05-17 22:43:53 +02:00
}
2015-07-22 13:11:20 +02:00
run_lucky13( ) {
2015-05-17 22:43:53 +02:00
#FIXME: to do . CVE-2013-0169
# in a nutshell: don't offer CBC suites (again). MAC as a fix for padding oracles is not enough. Best: TLS v1.2+ AES GCM
echo "FIXME"
return -1
}
# https://tools.ietf.org/html/rfc7465 REQUIRES that TLS clients and servers NEVER negotiate the use of RC4 cipher suites!
# https://en.wikipedia.org/wiki/Transport_Layer_Security#RC4_attacks
# http://blog.cryptographyengineering.com/2013/03/attack-of-week-rc4-is-kind-of-broken-in.html
2015-07-22 13:11:20 +02:00
run_rc4( ) {
local -i rc4_offered = 0
2015-05-27 23:31:25 +02:00
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"
2015-05-17 22:43:53 +02:00
2015-07-06 10:10:46 +02:00
if [ $VULN_COUNT -le $VULN_THRESHLD ] || $WIDE ; then
2015-05-29 19:44:27 +02:00
outln
2015-05-17 22:43:53 +02:00
pr_blue "--> Checking for vulnerable RC4 Ciphers" ; outln "\n"
fi
pr_bold " RC4" ; out " (CVE-2013-2566, CVE-2015-2808) "
2015-05-29 10:36:14 +02:00
$OPENSSL ciphers -V 'RC4:@STRENGTH' >$TMPFILE # -V doesn't work with openssl < 1.0, feeding this into the while loop below
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher $rc4_ciphers_list $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI & >/dev/null </dev/null
2015-05-17 22:43:53 +02:00
if [ $? -eq 0 ] ; then
2015-07-06 10:10:46 +02:00
# FF >=39 won't connect to them unless it's in this white list: http://mxr.mozilla.org/mozilla-central/source/security/manager/ssl/IntolerantFallbackList.inc
2015-05-25 21:14:59 +02:00
pr_litered "VULNERABLE (NOT ok): "
2015-07-06 10:10:46 +02:00
$WIDE && outln "\n"
2015-05-17 22:43:53 +02:00
rc4_offered = 1
2015-07-06 10:10:46 +02:00
$WIDE && neat_header
2015-05-27 23:31:25 +02:00
while read hexcode dash rc4_cipher sslvers kx auth enc mac; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -cipher $rc4_cipher $STARTTLS -connect $NODEIP :$PORT $PROXY $SNI </dev/null & >/dev/null
2015-05-17 22:43:53 +02:00
ret = $? # here we have a fp with openssl < 1.0
if [ [ $ret -ne 0 ] ] && [ [ " $SHOW_EACH_C " -eq 0 ] ] ; then
continue # no successful connect AND not verbose displaying each cipher
fi
2015-07-06 10:10:46 +02:00
if $WIDE ; then
2015-05-17 22:43:53 +02:00
normalize_ciphercode $hexcode
2015-05-29 19:44:27 +02:00
neat_list $HEXC $rc4_cipher $kx $enc
2015-05-17 22:43:53 +02:00
if [ [ " $SHOW_EACH_C " -ne 0 ] ] ; then
if [ [ $ret -eq 0 ] ] ; then
pr_litered "available"
else
out "not a/v"
fi
else
rc4_offered = 1
out
fi
outln
else
2015-05-27 23:31:25 +02:00
pr_litered " $rc4_cipher "
2015-05-17 22:43:53 +02:00
fi
done < $TMPFILE
# ^^^^^ posix redirect as shopt will either segfault or doesn't work with old bash versions
outln
else
pr_litegreenln "no RC4 ciphers detected (OK)"
rc4_offered = 0
fi
2015-05-29 10:36:14 +02:00
outln
2015-05-17 22:43:53 +02:00
tmpfile_handle $FUNCNAME .txt
return $rc4_offered
}
2015-07-22 13:11:20 +02:00
run_youknowwho( ) {
2015-05-29 19:44:27 +02:00
# CVE-2013-2566,
2015-05-17 22:43:53 +02:00
# 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
2015-07-22 13:11:20 +02:00
run_tls_truncation( ) {
2015-06-02 22:13:19 +02:00
#FIXME: difficult to test, is there any test available: pls let me know
2015-05-17 22:43:53 +02:00
:
}
old_fart( ) {
pr_magentaln " Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed. "
outln "Get precompiled bins or compile https://github.com/PeterMosmans/openssl ."
2015-07-22 13:11:20 +02:00
exit -2
2015-05-17 22:43:53 +02:00
}
2015-06-02 22:13:19 +02:00
# try very hard to determine th install path to get ahold of the mapping file
# it provides "keycode/ RFC style name", see RFCs, cipher(1), www.carbonwind.net/TLS_Cipher_Suites_Project/tls_ssl_cipher_suites_simple_table_all.htm
get_install_dir( ) {
#INSTALL_DIR=$(cd "$(dirname "$0")" && pwd)/$(basename "$0")
INSTALL_DIR = $( dirname ${ BASH_SOURCE [0] } )
[ -r " $RUN_DIR /mapping-rfc.txt " ] && MAP_RFC_FNAME = " $RUN_DIR /mapping-rfc.txt "
[ -r " $INSTALL_DIR /mapping-rfc.txt " ] && MAP_RFC_FNAME = " $INSTALL_DIR /mapping-rfc.txt "
# we haven't found the mapping file yet...
if [ ! -r " $MAP_RFC_FNAME " ] && which readlink & >/dev/null ; then
readlink -f ls & >/dev/null && \
INSTALL_DIR = $( readlink -f $( basename ${ BASH_SOURCE [0] } ) ) || \
INSTALL_DIR = $( readlink $( basename ${ BASH_SOURCE [0] } ) )
# not sure whether Darwin has -f
2015-07-23 17:11:33 +02:00
INSTALL_DIR = $( dirname $INSTALL_DIR 2>/dev/null)
2015-06-02 22:13:19 +02:00
[ -r " $INSTALL_DIR /mapping-rfc.txt " ] && MAP_RFC_FNAME = " $INSTALL_DIR /mapping-rfc.txt "
fi
# still no mapping file:
if [ ! -r " $MAP_RFC_FNAME " ] && which realpath & >/dev/null ; then
INSTALL_DIR = $( dirname $( realpath ${ BASH_SOURCE [0] } ) )
MAP_RFC_FNAME = " $INSTALL_DIR /mapping-rfc.txt "
fi
[ ! -r " $MAP_RFC_FNAME " ] && pr_magentaln "No mapping file found"
debugme echo " $MAP_RFC_FNAME "
}
2015-07-12 18:46:27 +02:00
test_openssl_suffix( ) {
2015-07-16 23:01:10 +02:00
local naming_ext = " $( uname) . $( uname -m) "
2015-07-12 18:46:27 +02:00
local uname_arch = $( uname -m)
[ [ $uname_arch = ~ "64" ] ] && myarch_suffix = 64 || myarch_suffix = 32
if [ [ -n " $1 /openssl " ] ] && [ [ -x " $1 /openssl " ] ] ; then
OPENSSL = " $1 /openssl "
return 0
2015-07-16 23:01:10 +02:00
elif [ [ -n " $1 /openssl. $naming_ext " ] ] && [ [ -x " $1 /openssl. $naming_ext " ] ] ; then
OPENSSL = " $1 /openssl. $naming_ext "
return 0
2015-07-12 18:46:27 +02:00
elif [ [ -n " $1 /openssl. $uname_arch " ] ] && [ [ -x " $1 /openssl. $uname_arch " ] ] ; then
OPENSSL = " $1 /openssl. $uname_arch "
return 0
fi
return 1
}
2015-05-17 22:43:53 +02:00
find_openssl_binary( ) {
2015-07-12 18:46:27 +02:00
local myarch_suffix = ""
local uname_arch = $( uname -m)
[ [ $uname_arch = ~ "64" ] ] && myarch_suffix = 64 || myarch_suffix = 32
# 0. check environment variable whether it's executable
if [ [ -n " $OPENSSL " ] ] && [ [ ! -x " $OPENSSL " ] ] ; then
pr_red " \ncannot find specified (\$OPENSSL= $OPENSSL ) binary. "
2015-06-01 12:01:38 +02:00
outln " Looking some place else ..."
2015-07-12 18:46:27 +02:00
elif [ [ -x " $OPENSSL " ] ] ; then
: # 1. all ok supplied $OPENSSL is excutable
elif test_openssl_suffix $RUN_DIR ; then
: # 2. otherwise try openssl in path of testssl.sh
2015-07-16 23:01:10 +02:00
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 [ [ -x " $RUN_DIR /openssl-bins/openssl-1.0.2-chacha.pm/openssl " $myarch_suffix "-1.0.2pm-krb5" ] ] ; then
OPENSSL = " $RUN_DIR /openssl-bins/openssl-1.0.2-chacha.pm/openssl " $myarch_suffix "-1.0.2pm-krb5"
# 4. legacy dirs follow, first kerberos binaries from pm (if executable)
2015-07-12 18:46:27 +02:00
elif [ [ -x " $RUN_DIR /openssl-bins/openssl-1.0.2-chacha.pm/openssl " $myarch_suffix "-1.0.2pm-static" ] ] ; then
2015-07-16 23:01:10 +02:00
# 5. otherwise default is trying the statically linked ones
2015-07-12 18:46:27 +02:00
OPENSSL = " $RUN_DIR /openssl-bins/openssl-1.0.2-chacha.pm/openssl " $myarch_suffix "-1.0.2pm-static"
elif test_openssl_suffix $( dirname $( which openssl) ) ; then
2015-07-16 23:01:10 +02:00
: # 5. we tried hard and failed, so now we use the system binaries
2015-05-17 22:43:53 +02:00
fi
2015-07-20 14:05:35 +02:00
$OPENSSL version -a 2>/dev/null >/dev/null
2015-05-17 22:43:53 +02:00
if [ $? -ne 0 ] || [ ! -x " $OPENSSL " ] ; then
outln
pr_magentaln "FATAL: cannot exec or find any openssl binary "
exit -1
fi
# http://www.openssl.org/news/openssl-notes.html
2015-07-20 14:05:35 +02:00
OSSL_VER = $( $OPENSSL version 2>/dev/null| awk -F' ' '{ print $2 }' )
2015-05-17 22:43:53 +02:00
OSSL_VER_MAJOR = $( echo " $OSSL_VER " | sed 's/\..*$//' )
2015-05-25 15:10:09 +02:00
OSSL_VER_MINOR = $( echo " $OSSL_VER " | sed -e 's/^.\.//' | tr -d '[a-zA-Z]-' )
2015-05-17 22:43:53 +02:00
OSSL_VER_APPENDIX = $( echo " $OSSL_VER " | tr -d '[0-9.]' )
2015-07-20 14:05:35 +02:00
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//' )
2015-05-29 19:44:27 +02:00
echo $OSSL_BUILD_DATE | grep -q "not available" && OSSL_BUILD_DATE = ""
2015-05-26 12:51:10 +02:00
2015-07-20 14:05:35 +02:00
if $OPENSSL version 2>/dev/null | grep -qi LibreSSL; then
2015-07-16 17:58:03 +02:00
HAS_DH_BITS = false # as of version 2.2.1
else
[ $OSSL_VER_MAJOR -ne 1 ] && HAS_DH_BITS = false
[ " $OSSL_VER_MINOR " = = "0.1" ] && HAS_DH_BITS = false
fi
2015-07-20 14:05:35 +02:00
if $OPENSSL version 2>/dev/null | grep -qi LibreSSL; then
outln
pr_litemagenta "Please note: LibreSSL is not a good choice for testing insecure features!"
fi
2015-07-16 23:01:10 +02:00
$OPENSSL s_client -ssl2 2>& 1 | grep -aq "unknown option" || \
HAS_SSL2 = true && \
HAS_SSL2 = false
2015-05-26 12:51:10 +02:00
2015-05-17 22:43:53 +02:00
return 0
}
openssl_age( ) {
case " $OSSL_VER " in
0.9.7*| 0.9.6*| 0.9.5*)
# 0.9.5a was latest in 0.9.5 an released 2000/4/1, that'll NOT suffice for this test
old_fart ; ;
0.9.8)
case $OSSL_VER_APPENDIX in
a| b| c| d| e) old_fart; ; # no SNI!
# other than that we leave this for MacOSX and FreeBSD but it's a pain and likely gives false negatives/positives
esac
; ;
esac
if [ $OSSL_VER_MAJOR -lt 1 ] ; then ## mm: Patch for libressl
pr_magentaln " Your \" $OPENSSL \" is way too old (<version 1.0) ! "
case $SYSTEM in
*BSD| Darwin)
outln " Please use openssl from ports/brew or compile from github.com/PeterMosmans/openssl" ; ;
*) outln " Update openssl binaries or compile from github.com/PeterMosmans/openssl" ; ;
esac
ignore_no_or_lame " Type \"yes\" to accept some false negatives or positives "
fi
}
help( ) {
cat << EOF
2015-05-29 19:44:27 +02:00
$PROG_NAME <options>
2015-05-17 22:43:53 +02:00
2015-06-23 12:58:40 +02:00
-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
2015-07-17 15:58:07 +02:00
-V, --local <pattern> which local ciphers with <pattern> are available?
( if pattern not a number: word match)
2015-05-17 22:43:53 +02:00
2015-06-23 07:56:56 +02:00
$PROG_NAME <options> URI ( " $PROG_NAME URI " does everything except -E)
2015-05-17 22:43:53 +02:00
2015-06-22 18:32:40 +02:00
-e, --each-cipher checks each local cipher remotely
-E, --cipher-per-proto checks those per protocol
-f, --ciphers checks common cipher suites
-p, --protocols checks TLS/SSL protocols
-S, --server_defaults displays the servers default picks and certificate info
-P, --preference displays the servers picks: protocol+cipher
-y, --spdy, --npn checks for SPDY/NPN
2015-07-17 15:58:07 +02:00
-x, --single-cipher <pattern> tests matched <pattern> of ciphers
( if <pattern> not a number: word match)
2015-06-22 18:32:40 +02:00
-U, --vulnerable tests all vulnerabilities
-B, --heartbleed tests for heartbleed vulnerability
2015-06-23 12:58:40 +02:00
-I, --ccs, --ccs-injection tests for CCS injection vulnerability
2015-06-22 18:32:40 +02:00
-R, --renegotiation tests for renegotiation vulnerabilities
-C, --compression, --crime tests for CRIME vulnerability
-T, --breach tests for BREACH vulnerability
-O, --poodle tests for POODLE ( SSL) vulnerability
-Z, --tls-fallback checks TLS_FALLBACK_SCSV mitigation
-F, --freak tests for FREAK vulnerability
-A, --beast tests for BEAST vulnerability
-J, --logjam tests for LOGJAM vulnerability
-s, --pfs, --fs,--nsa checks ( perfect) forward secrecy settings
-4, --rc4, --appelbaum which RC4 ciphers are being offered?
2015-06-23 12:58:40 +02:00
-H, --header, --headers tests HSTS, HPKP, server/app banner, security headers, cookie, reverse proxy, IPv4 address
2015-05-17 22:43:53 +02:00
special invocations:
2015-06-23 12:58:40 +02:00
-t, --starttls <protocol> does a default run against a STARTTLS enabled <protocol>
2015-07-06 20:42:43 +02:00
--xmpphost <to_domain> for STARTTLS enabled XMPP it supplies the XML stream to-'' domain -- sometimes needed
2015-06-22 18:32:40 +02:00
--mx <domain/host> tests MX records from high to low priority ( STARTTLS, port 25)
--ip <ipv4> a) tests the supplied <ipv4> instead of resolving host( s) in URI
2015-08-02 00:26:34 +02:00
b) arg "one" means: just test the first DNS returns ( useful for multiple IPs)
--file <file name> mass testing option: Just put multiple $PROG_NAME command lines in <file name>,
one line per instance. Comments via # allowed, EOF signals end of <file name>.
2015-05-17 22:43:53 +02:00
partly mandatory parameters:
2015-05-27 17:04:35 +02:00
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)
2015-05-17 22:43:53 +02:00
tuning options:
2015-07-20 14:05:35 +02:00
--assuming-http if protocol check fails it assumes HTTP protocol and enforces HTTP checks
2015-07-06 10:10:46 +02:00
--ssl-native <true| false> fallback to checks with OpenSSL where sockets are normally used
2015-06-22 18:32:40 +02:00
--openssl <PATH> use this openssl binary ( default: look in \$ PATH, \$ RUN_DIR of $PROG_NAME
2015-06-29 22:29:15 +02:00
--proxy <host>:<port> connect via the specified HTTP proxy
2015-07-20 14:05:35 +02:00
--sneaky be less verbose wrt referer headers
2015-07-25 14:33:08 +02:00
-q. --quiet don' t out put the banner. By doing this you acknowledge usage terms normally appearing in the banner
2015-07-20 14:05:35 +02:00
--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
2015-06-23 12:58:40 +02:00
--warnings <batch| off| false> "batch" doesn' t wait for keypress, "off" or "false" skips connection warning
2015-06-02 22:13:19 +02:00
--color <0| 1| 2> 0: no escape or other codes, 1: b/w escape codes, 2: color ( default)
2015-07-06 10:10:46 +02:00
--debug <0-6> 1: screen output normal but debug output in temp files. 2-6: see line ~105
2015-06-02 22:13:19 +02:00
2015-07-17 14:33:23 +02:00
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.
2015-05-29 19:44:27 +02:00
Need HTML output? Just pipe through "aha" ( Ansi HTML Adapter: github.com/theZiz/aha) like
2015-05-17 22:43:53 +02:00
" $PROG_NAME <options> <URI> | aha >output.html "
EOF
exit $1
}
mybanner( ) {
2015-05-31 14:40:12 +02:00
local nr_ciphers
local idtag
local bb
2015-07-17 13:25:39 +02:00
local openssl_location = $( which $OPENSSL )
local cwd = ""
2015-05-31 14:40:12 +02:00
2015-07-25 14:33:08 +02:00
$QUIET && return
2015-07-17 14:33:23 +02:00
nr_ciphers = $( count_ciphers $( $OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' ) )
2015-05-31 14:40:12 +02:00
[ -z " $GIT_REL " ] && \
idtag = " $CVS_REL " || \
idtag = " $GIT_REL -- $CVS_REL_SHORT "
2015-05-17 22:43:53 +02:00
[ " $COLOR " -ne 0 ] && idtag = " \033[1;30m $idtag \033[m\033[1m "
bb = $( cat <<EOF
2015-07-10 10:23:10 +02:00
###########################################################
$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!
2015-05-17 22:43:53 +02:00
2015-07-10 10:23:10 +02:00
Please file bugs @ https://testssl.sh/bugs/
2015-05-17 22:43:53 +02:00
2015-07-10 10:23:10 +02:00
###########################################################
2015-05-17 22:43:53 +02:00
EOF
)
2015-05-31 14:40:12 +02:00
pr_bold " $bb "
outln "\n"
2015-07-20 14:05:35 +02:00
outln " Using \" $( $OPENSSL version 2>/dev/null) \" [~ $nr_ciphers ciphers] on "
2015-07-17 13:25:39 +02:00
out " $( hostname) : "
[ -n " $GIT_REL " ] && \
cwd = $( /bin/pwd) || \
cwd = $RUN_DIR
2015-08-01 23:11:27 +02:00
if [ [ " $openssl_location " = ~ " $( /bin/pwd) /bin " ] ] ; then
echo " \$PWD/bin/ $( basename $openssl_location ) "
elif [ [ " $openssl_location " = ~ " $cwd " ] ] && [ [ " $cwd " != "." ] ] ; then
echo "\$INSTALL_DIR" " ${ openssl_location %% $cwd } "
else
2015-07-17 14:58:12 +02:00
echo " $openssl_location "
2015-08-01 23:11:27 +02:00
fi
2015-05-31 14:40:12 +02:00
outln " (built: \" $OSSL_BUILD_DATE \", platform: \" $OSSL_VER_PLATFORM \")\n "
2015-05-17 22:43:53 +02:00
}
2015-05-29 10:36:14 +02:00
2015-05-17 22:43:53 +02:00
maketempf( ) {
2015-07-22 13:11:20 +02:00
TEMPDIR = $( mktemp -d /tmp/ssltester.XXXXXX) || exit -6
TMPFILE = $TEMPDIR /tempfile.txt || exit -6
2015-05-17 22:43:53 +02:00
HOSTCERT = $TEMPDIR /host_certificate.txt
HEADERFILE = $TEMPDIR /http_header.txt
HEADERFILE_BREACH = $TEMPDIR /http_header_breach.txt
LOGFILE = $TEMPDIR /logfile.txt
2015-07-20 14:05:35 +02:00
initialize_engine
2015-05-17 22:43:53 +02:00
if [ $DEBUG -ne 0 ] ; then
cat >$TEMPDIR /environment.txt << EOF
2015-05-31 14:40:12 +02:00
CVS_REL: $CVS_REL
GIT_REL: $GIT_REL
2015-05-29 10:36:14 +02:00
2015-05-17 22:43:53 +02:00
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
2015-07-20 14:05:35 +02:00
$OPENSSL version -a:
$( $OPENSSL version -a)
2015-05-29 10:36:14 +02:00
OSSL_VER_MAJOR: $OSSL_VER_MAJOR
OSSL_VER_MINOR: $OSSL_VER_MINOR
OSSL_VER_APPENDIX: $OSSL_VER_APPENDIX
2015-05-29 19:44:27 +02:00
OSSL_BUILD_DATE: " $OSSL_BUILD_DATE "
2015-05-29 10:36:14 +02:00
OSSL_VER_PLATFORM: " $OSSL_VER_PLATFORM "
2015-05-17 22:43:53 +02:00
2015-07-20 14:05:35 +02:00
OPENSSL_CONF: $OPENSSL_CONF
2015-05-17 22:43:53 +02:00
PATH: $PATH
PROG_NAME: $PROG_NAME
2015-06-02 22:13:19 +02:00
INSTALL_DIR: $INSTALL_DIR
2015-05-17 22:43:53 +02:00
RUN_DIR: $RUN_DIR
2015-05-29 10:36:14 +02:00
MAP_RFC_FNAME: $MAP_RFC_FNAME
2015-05-17 22:43:53 +02:00
2015-05-29 14:12:22 +02:00
2015-05-17 22:43:53 +02:00
CAPATH: $CAPATH
ECHO: $ECHO
COLOR: $COLOR
TERM_DWITH: $TERM_DWITH
2015-05-29 14:12:22 +02:00
HAS_GNUDATE: $HAS_GNUDATE
2015-06-17 11:33:29 +02:00
HAS_SED_E: $HAS_SED_E
2015-05-17 22:43:53 +02:00
SHOW_EACH_C: $SHOW_EACH_C
SSL_NATIVE: $SSL_NATIVE
ASSUMING_HTTP $ASSUMING_HTTP
SNEAKY: $SNEAKY
2015-05-29 19:44:27 +02:00
VERBERR: $VERBERR
2015-05-17 22:43:53 +02:00
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
2015-07-20 14:05:35 +02:00
$OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL' & >$TEMPDIR /all_local_ciphers.txt
2015-05-17 22:43:53 +02:00
fi
}
cleanup ( ) {
if [ [ " $DEBUG " -ge 1 ] ] ; then
2015-06-16 19:53:40 +02:00
outln
2015-05-17 22:43:53 +02:00
pr_underline " DEBUG (level $DEBUG ): see files in $TEMPDIR "
2015-06-16 19:53:40 +02:00
outln
2015-05-17 22:43:53 +02:00
else
[ -d " $TEMPDIR " ] && rm -rf ${ TEMPDIR } ;
fi
outln
}
# for now only GOST engine
initialize_engine( ) {
2015-07-10 10:23:10 +02:00
grep -q '^# testssl config file' " $OPENSSL_CONF " 2>/dev/null && return 0 # have been here already
2015-05-17 22:43:53 +02:00
if ! $OPENSSL engine gost -vvvv -t -c >/dev/null 2>& 1; then
outln
2015-05-29 19:44:27 +02:00
pr_litemagenta " No engine or GOST support via engine with your $OPENSSL " ; outln
2015-05-17 22:43:53 +02:00
return 1
elif $OPENSSL engine gost -vvvv -t -c 2>& 1 | grep -iq "No such" ; then
outln
2015-05-29 19:44:27 +02:00
pr_litemagenta " No engine or GOST support via engine with your $OPENSSL " ; outln
2015-05-17 22:43:53 +02:00
return 1
2015-07-13 23:24:23 +02:00
else # we have engine support
if [ [ -n " $OPENSSL_CONF " ] ] ; then
pr_litemagentaln "For now I am providing the config file in to have GOST support"
2015-05-17 22:43:53 +02:00
else
2015-07-22 13:11:20 +02:00
OPENSSL_CONF = $TEMPDIR /gost.conf || exit -6
2015-05-17 22:43:53 +02:00
# see https://www.mail-archive.com/openssl-users@openssl.org/msg65395.html
cat >$OPENSSL_CONF << EOF
2015-07-13 23:24:23 +02:00
# testssl config file for openssl
2015-05-17 22:43:53 +02:00
2015-07-13 23:24:23 +02:00
openssl_conf = openssl_def
2015-07-02 16:39:41 +02:00
2015-05-17 22:43:53 +02:00
[ 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
2015-07-10 10:23:10 +02:00
2015-05-17 22:43:53 +02:00
return 0
}
ignore_no_or_lame( ) {
2015-06-29 22:29:15 +02:00
[ [ " $WARNINGS " = = "off" ] ] && return 0
[ [ " $WARNINGS " = = "false" ] ] && return 0
2015-06-29 10:41:56 +02:00
[ [ " $WARNINGS " = = "batch" ] ] && return 1
2015-05-17 22:43:53 +02:00
pr_magenta " $1 "
read a
case $a in
Y| y| Yes| YES| yes) return 0; ;
default) ; ;
esac
return 1
}
2015-06-15 12:13:16 +02:00
# arg1: URI
# arg2: protocol
2015-05-17 22:43:53 +02:00
parse_hn_port( ) {
NODE = " $1 "
# strip "https" and trailing urlpath supposed it was supplied additionally
2015-07-13 23:24:23 +02:00
echo " $NODE " | grep -q 'https://' && NODE = $( echo " $NODE " | sed -e 's/^https\:\/\///' )
2015-05-17 22:43:53 +02:00
# strip trailing urlpath
2015-07-13 23:24:23 +02:00
NODE = $( echo " $NODE " | sed -e 's/\/.*$//' )
2015-05-17 22:43:53 +02:00
# was the address supplied like [AA:BB:CC::]:port ?
2015-07-13 23:24:23 +02:00
if echo " $NODE " | grep -q ']' ; then
tmp_port = $( printf " $NODE " | sed 's/\[.*\]//' | sed 's/://' )
2015-05-17 22:43:53 +02:00
# determine v6 port, supposed it was supplied additionally
2015-07-13 23:24:23 +02:00
if [ [ -n " $tmp_port " ] ] ; then
2015-05-17 22:43:53 +02:00
PORT = $tmp_port
2015-07-13 23:24:23 +02:00
NODE = $( printf " $NODE " | sed " s/: $PORT // " )
2015-05-17 22:43:53 +02:00
fi
2015-07-13 23:24:23 +02:00
NODE = $( printf " $NODE " | sed -e 's/\[//' -e 's/\]//' )
2015-05-17 22:43:53 +02:00
else
# determine v4 port, supposed it was supplied additionally
2015-07-13 23:24:23 +02:00
echo " $NODE " | grep -q ':' && \
PORT = $( echo " $NODE " | sed 's/^.*\://' ) && NODE = $( echo " $NODE " | sed 's/\:.*$//' )
2015-05-17 22:43:53 +02:00
fi
2015-06-16 23:00:47 +02:00
debugme echo $NODE :$PORT
2015-05-29 19:44:27 +02:00
SNI = " -servername $NODE "
2015-05-17 22:43:53 +02:00
2015-06-16 23:00:47 +02:00
URL_PATH = $( echo $1 | sed 's/https:\/\///' | sed 's/' " ${ NODE } " '//' | sed 's/.*' " ${ PORT } " '//' ) # remove protocol and node part and port
2015-05-17 22:43:53 +02:00
URL_PATH = $( echo $URL_PATH | sed 's/\/\//\//g' ) # we rather want // -> /
2015-07-13 23:24:23 +02:00
[ [ -z " $URL_PATH " ] ] && URL_PATH = "/"
2015-06-16 23:00:47 +02:00
debugme echo $URL_PATH
2015-05-17 22:43:53 +02:00
2015-06-16 14:04:44 +02:00
return 0 # NODE, URL_PATH, PORT is set now
}
2015-07-13 23:24:23 +02:00
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
echo -n " $1 " | grep -Eq $ipv4address && \
return 0 || \
return 1
}
2015-08-01 23:11:27 +02:00
# 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 char we allow:
[ [ -n " $( echo -n | tr -d '0-9:a-fA-F ' | sed -e '/^$/d' ) " ] ] && \
return 1
return 0
}
2015-07-22 13:11:20 +02:00
# args: string containing ip addresses
2015-08-01 23:11:27 +02:00
filter_ip6_address( ) {
2015-07-22 13:11:20 +02:00
local a
for a in $@ ; do
2015-08-01 23:11:27 +02:00
if ! is_ipv6addr " $a " ; then
continue
fi
2015-07-22 13:11:20 +02:00
if $HAS_SED_E ; then
2015-08-01 23:11:27 +02:00
echo " $a " | sed -E 's/^abcdeABCDEFf0123456789:]//g' | sed -e '/^$/d' -e '/^;;/d'
2015-07-22 13:11:20 +02:00
else
2015-08-01 23:11:27 +02:00
echo " $a " | sed -r 's/[^abcdefABCDEF0123456789:]//g' | sed -e '/^$/d' -e '/^;;/d'
2015-07-22 13:11:20 +02:00
fi
done
}
2015-07-23 17:11:33 +02:00
2015-08-01 23:11:27 +02:00
filter_ip4_address( ) {
2015-07-22 13:11:20 +02:00
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
}
2015-07-13 23:24:23 +02:00
2015-08-12 00:17:28 +02:00
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
}
2015-07-23 17:11:33 +02:00
# 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 [ [ -z " $ip4 " ] ] ; then
which dig & > /dev/null && \
2015-08-01 23:11:27 +02:00
ip4 = $( filter_ip4_address $( dig +short -t a " $1 " 2>/dev/null | sed '/^;;/d' ) )
2015-07-23 17:11:33 +02:00
fi
if [ [ -z " $ip4 " ] ] ; then
which host & > /dev/null && \
2015-08-01 23:11:27 +02:00
ip4 = $( filter_ip4_address $( host -t a " $1 " 2>/dev/null | grep -v alias | sed 's/^.*address //' ) )
2015-07-23 17:11:33 +02:00
fi
if [ [ -z " $ip4 " ] ] ; then
if which nslookup & >/dev/null; then
# filtering from Name to EOF, remove iline with 'Name', the filter out non-numbers and ".'", and empty lines
2015-08-01 23:11:27 +02:00
ip4 = $( filter_ip4_address $( nslookup -querytype= a " $1 " 2>/dev/null | awk '/^Name/,/EOF/ { print $0 }' | grep -v Name) )
2015-07-23 17:11:33 +02:00
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
2015-08-01 23:11:27 +02:00
if 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 nslookup & >/dev/null; then
2015-07-23 17:11:33 +02:00
# same as above. Only we're using grep -A instead of awk
2015-08-01 23:11:27 +02:00
ip6 = $( filter_ip6_address $( nslookup -type= aaaa " $NODE " 2>/dev/null | grep -A10 Name | grep -v Name) )
2015-07-23 17:11:33 +02:00
fi
fi
OPENSSL_CONF = " $saved_openssl_conf " # see https://github.com/drwetter/testssl.sh/issues/134
echo " $ip6 "
}
2015-06-16 14:04:44 +02:00
# now get all IP addresses
determine_ip_addresses( ) {
local ip4 = ""
local ip6 = ""
2015-07-14 17:13:58 +02:00
if is_ipv4addr " $NODE " ; then
2015-08-01 23:11:27 +02:00
ip4 = " $NODE " # only an IPv4 address was supplied as an argument, no hostname
SNI = "" # override Server Name Indication as we test the IP only
2015-06-16 14:04:44 +02:00
else
2015-08-12 00:17:28 +02:00
ip4 = $( get_local_a $NODE ) # is there a local host entry?
if [ -z $ip4 ] ; then # empty: no (LOCAL_A is predefined as false)
ip4 = $( get_a_record $NODE )
else
LOCAL_A = true # we have the ip4 from local host entry and need to set this
fi
# same now for ipv6 (though not supported) <-- can't do this yet as it shows up under "further IP addresses"
# and we didn't bother to show the fact that it is local there
ip6 = $( get_local_aaaa $NODE )
#if [ -z $ip6 ]; then
ip6 = $( get_aaaa_record $NODE )
#else
# LOCAL_AAAA=true # we have the ip4 from local host entry and need to set this
#fi
2015-06-16 14:04:44 +02:00
fi
IPADDRs = $( newline_to_spaces " $ip4 " )
if [ [ -z " $IPADDRs " ] ] && [ [ -z " $CMDLINE_IP " ] ] ; then
2015-08-12 00:17:28 +02:00
pr_magenta " Can't proceed: No IPv4 address for \" $NODE \" available "
2015-06-16 14:04:44 +02:00
outln "\n"
2015-05-17 22:43:53 +02:00
exit -1
fi
2015-08-01 23:11:27 +02:00
[ [ -z " $ip6 " ] ] && IP46ADDRs = " $IPADDRs " || IP46ADDRs = " $ip4 $ip6 "
2015-06-19 20:36:32 +02:00
IP46ADDRs = $( newline_to_spaces " $IP46ADDRs " )
2015-07-13 23:24:23 +02:00
return 0 # IPADDR and IP46ADDR is set now
2015-06-16 14:04:44 +02:00
}
determine_rdns( ) {
2015-07-23 17:11:33 +02:00
local saved_openssl_conf = " $OPENSSL_CONF "
OPENSSL_CONF = "" # see https://github.com/drwetter/testssl.sh/issues/134
if which dig & > /dev/null; then
2015-08-10 14:47:11 +02:00
rDNS = $( dig -x $NODEIP +noall +answer | awk '/PTR/ { print $NF }' ) # +short returns also CNAME, e.g. openssl.org
2015-07-23 17:11:33 +02:00
elif which host & > /dev/null; then
2015-08-10 14:47:11 +02:00
rDNS = $( host -t PTR $NODEIP 2>/dev/null | awk '/pointer/ { print $NF }' )
2015-06-16 14:04:44 +02:00
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
2015-07-23 17:11:33 +02:00
OPENSSL_CONF = " $saved_openssl_conf " # see https://github.com/drwetter/testssl.sh/issues/134
rDNS = $( echo $rDNS )
2015-06-16 14:04:44 +02:00
[ -z " $rDNS " ] && rDNS = "--"
return 0
}
2015-05-17 22:43:53 +02:00
2015-07-23 17:11:33 +02:00
get_mx_record( ) {
local mx = ""
local saved_openssl_conf = " $OPENSSL_CONF "
2015-08-01 23:11:27 +02:00
OPENSSL_CONF = "" # see https://github.com/drwetter/testssl.sh/issues/134
2015-07-23 17:11:33 +02:00
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 nslookup & > /dev/null; then
mxs = $( nslookup -type= MX " $1 " 2>/dev/null | grep 'mail exchanger = ' | sed 's/^.*mail exchanger = //g' )
else
pr_magentaln 'No dig, host or nslookup'
exit -3
fi
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( ) {
2015-08-12 00:17:28 +02:00
local save_LOCAL_A = $LOCAL_A
local save_LOCAL_AAAA = $LOCAL_AAAA
2015-08-10 14:47:11 +02:00
2015-07-23 17:11:33 +02:00
if [ [ -n " $PROXY " ] ] ; then
if ! $OPENSSL s_client help 2>& 1 | grep -qw proxy; then
pr_magentaln " Local problem: Your $OPENSSL is too old to support the \"--proxy\" option "
exit -1
fi
PROXYNODE = ${ PROXY % : * }
PROXYPORT = ${ PROXY #* : }
2015-08-10 14:47:11 +02:00
2015-07-23 17:11:33 +02:00
PROXYIP = $( get_a_record $PROXYNODE 2>/dev/null | grep -v alias | sed 's/^.*address //' )
2015-08-12 00:17:28 +02:00
LOCAL_A = $save_LOCAL_A
LOCAL_AAAA = $save_LOCAL_AAAA
2015-07-23 17:11:33 +02:00
# no RFC 1918:
#if ! is_ipv4addr $PROXYIP ; then
if [ [ -z " $PROXYIP " ] ] ; then
pr_magentaln " Fatal error: Proxy IP cannot be determined from \" $PROXYNODE \" "
exit -3
fi
PROXY = " -proxy $PROXYIP : $PROXYPORT "
fi
}
2015-06-16 14:04:44 +02:00
# arg1: ftp smtp, pop3, imap, xmpp, telnet, ldap (maybe with trailing s)
determine_service( ) {
2015-06-16 23:00:47 +02:00
local all_failed
local ua
local protocol
2015-06-16 14:04:44 +02:00
if ! fd_socket; then # check if we can connect to $NODEIP:$PORT
2015-07-22 13:11:20 +02:00
pr_magentaln " Fatal error: can't connect to $NODEIP : $PORT "
outln "\nMake sure a firewall is not between you and your scanning target"
exit -2
2015-05-17 22:43:53 +02:00
fi
close_socket
datebanner "Testing"
2015-08-12 00:17:28 +02:00
if [ [ -z " $1 " ] ] ; then # for starttls we want another check
2015-05-29 19:44:27 +02:00
# determine protocol which works (needed for IIS6). If we don't have IIS6, 1st try will succeed --> better because we use the variable
2015-05-17 22:43:53 +02:00
# all over the place. Stupid thing that we need to do that stuff for IIS<=6
for OPTIMAL_PROTO in "" "-tls1_2" "-tls1" "-ssl3" "-tls1_1" "-ssl2" "" ; do
2015-06-29 22:29:15 +02:00
$OPENSSL s_client $OPTIMAL_PROTO -connect " $NODEIP : $PORT " $PROXY $SNI </dev/null & >/dev/null && all_failed = 1 && break
2015-05-17 22:43:53 +02:00
all_failed = 0
done
debugme echo " OPTIMAL_PROTO: $OPTIMAL_PROTO "
2015-07-14 17:13:58 +02:00
if [ [ $all_failed -eq 0 ] ] ; then
2015-05-17 22:43:53 +02:00
outln
2015-05-29 19:44:27 +02:00
pr_boldln " $NODEIP : $PORT doesn't seem a TLS/SSL enabled server or it requires a certificate " ;
2015-05-17 22:43:53 +02:00
ignore_no_or_lame " Note that the results might look ok but they are nonsense. Proceed ? "
2015-07-22 13:11:20 +02:00
[ $? -ne 0 ] && exit -2
2015-05-17 22:43:53 +02:00
fi
2015-07-06 10:10:46 +02:00
$SNEAKY && \
2015-06-16 23:00:47 +02:00
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 "
2015-05-17 22:43:53 +02:00
runs_HTTP $OPTIMAL_PROTO
else
2015-06-16 14:04:44 +02:00
protocol = $( echo " $1 " | sed 's/s$//' ) # strip trailing s in ftp(s), smtp(s), pop3(s), imap(s), ldap(s), telnet(s)
2015-05-17 22:43:53 +02:00
case " $protocol " in
ftp| smtp| pop3| imap| xmpp| telnet| ldap)
2015-07-06 20:42:43 +02:00
STARTTLS = " -starttls $protocol "
2015-06-16 14:04:44 +02:00
SNI = ""
2015-07-21 07:11:20 +02:00
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
outln
pr_magentaln " Local problem: Your $OPENSSL does not support the \"-xmpphost\" option "
2015-07-22 13:11:20 +02:00
exit -3
2015-07-21 07:11:20 +02:00
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
2015-07-06 20:42:43 +02:00
fi
fi
2015-06-29 22:29:15 +02:00
$OPENSSL s_client -connect $NODEIP :$PORT $PROXY $STARTTLS 2>/dev/null >$TMPFILE </dev/null
2015-05-17 22:43:53 +02:00
if [ $? -ne 0 ] ; then
pr_magentaln " $OPENSSL couldn't establish STARTTLS via $protocol to $NODEIP : $PORT "
debugme cat $TMPFILE
2015-07-22 13:11:20 +02:00
exit -2
2015-05-17 22:43:53 +02:00
fi
out " Service set: STARTTLS via "
2015-08-12 13:58:45 +02:00
toupper " $protocol "
2015-07-08 21:30:31 +02:00
[ [ -n " $XMPP_HOST " ] ] && printf " (XMPP domain=\' $XMPP_HOST \') "
2015-07-06 20:42:43 +02:00
outln
2015-05-17 22:43:53 +02:00
; ;
2015-07-21 07:11:20 +02:00
*)
pr_litemagentaln "momentarily only ftp, smtp, pop3, imap, xmpp, telnet and ldap allowed" >& 2
2015-07-22 13:11:20 +02:00
exit -1
2015-05-17 22:43:53 +02:00
; ;
esac
fi
outln
2015-06-16 14:04:44 +02:00
return 0 # OPTIMAL_PROTO, GET_REQ*/HEAD_REQ* is set now
2015-05-17 22:43:53 +02:00
}
display_rdns_etc( ) {
2015-06-15 12:13:16 +02:00
local i
2015-08-01 23:11:27 +02:00
if [ [ $( count_words " $( printf " $IP46ADDRs " ) " ) -gt 1 ] ] ; then
2015-05-17 22:43:53 +02:00
out " further IP addresses: "
2015-06-15 12:13:16 +02:00
for i in $IP46ADDRs ; do
2015-05-17 22:43:53 +02:00
[ " $i " = = " $NODEIP " ] && continue
out " $i "
done
outln
fi
2015-08-12 00:17:28 +02:00
[ [ -n " $rDNS " ] ] && printf " %-23s %s" " rDNS ( $NODEIP ): " " $rDNS "
if " $LOCAL_A " ; then
out " (A record via /etc/hosts) "
2015-05-17 22:43:53 +02:00
fi
}
datebanner( ) {
tojour = $( date +%F) " " $( date +%R)
outln
pr_reverse " $1 now ( $tojour ) ---> $NODEIP : $PORT ( $NODE ) <--- " ; outln "\n"
2015-07-13 23:24:23 +02:00
if [ [ " $1 " = = "Testing" ] ] ; then
2015-05-29 19:44:27 +02:00
display_rdns_etc
2015-05-17 22:43:53 +02:00
fi
outln
}
2015-08-02 01:16:27 +02:00
# one line with $1 over whole screen width
2015-05-31 14:40:12 +02:00
draw_dotted_line( ) {
printf -- " $1 " '%.s' $( eval "echo {1.." $(( $2 )) "}" )
}
2015-05-17 22:43:53 +02:00
2015-07-23 17:11:33 +02:00
2015-05-31 14:40:12 +02:00
mx_all_ips( ) {
2015-05-17 22:43:53 +02:00
local mxs mx
2015-06-16 14:04:44 +02:00
local mxport
2015-07-22 13:11:20 +02:00
local ret = 0
2015-06-16 14:04:44 +02:00
local starttls_proto = "smtp"
2015-05-31 14:40:12 +02:00
local ret = 0
2015-07-13 23:24:23 +02:00
2015-05-17 22:43:53 +02:00
# test first higher priority servers
2015-07-23 17:11:33 +02:00
mxs = $( get_mx_record " $1 " | sort -n | sed -e 's/^.* //' -e 's/\.$//' | tr '\n' ' ' )
2015-05-17 22:43:53 +02:00
mxport = ${ 2 :- 25 }
if [ -n " $mxs " ] && [ " $mxs " != ' ' ] ; then
2015-06-16 14:04:44 +02:00
[ [ $mxport = = "465" ] ] && \
starttls_proto = "" # no starttls for Port 465, on all other ports we speak starttls
2015-05-17 22:43:53 +02:00
pr_bold " Testing now all MX records (on port $mxport ): " ; outln " $mxs "
for mx in $mxs ; do
2015-08-02 01:16:27 +02:00
draw_dotted_line "-" $(( $TERM_DWITH * 2 / 3 ))
2015-05-29 10:36:14 +02:00
outln
2015-06-16 14:04:44 +02:00
parse_hn_port " $mx : $mxport "
2015-07-22 13:11:20 +02:00
determine_ip_addresses || continue
2015-08-02 01:16:27 +02:00
if [ [ $( printf " $IPADDRs " | wc -w | sed 's/ //g' ) -gt 1 ] ] ; then
pr_bold " Testing all IPv4 addresses (port $PORT ): " ; outln " $IPADDRs "
for ip in $IPADDRs ; do
NODEIP = " $ip "
lets_roll " ${ starttls_proto } "
done
else
NODEIP = " $IPADDRs "
lets_roll " ${ starttls_proto } "
fi
2015-07-22 13:11:20 +02:00
ret = $(( $? + ret))
2015-05-17 22:43:53 +02:00
done
2015-08-02 01:16:27 +02:00
draw_dotted_line "-" $(( $TERM_DWITH * 2 / 3 ))
2015-05-29 10:36:14 +02:00
outln
2015-06-16 14:04:44 +02:00
pr_bold " Done testing now all MX records (on port $mxport ): " ; outln " $mxs "
2015-05-17 22:43:53 +02:00
else
pr_boldln " $1 has no MX records(s) "
fi
2015-07-22 13:11:20 +02:00
return $ret
2015-05-17 22:43:53 +02:00
}
2015-07-22 13:11:20 +02:00
# This initializes boolean global do_* variables. They keep track of what to do!
2015-05-17 22:43:53 +02:00
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
2015-05-27 14:28:18 +02:00
do_logjam = false
2015-05-17 22:43:53 +02:00
do_header = false
do_heartbleed = false
2015-05-31 14:40:12 +02:00
do_mx_all_ips = false
2015-08-01 23:11:27 +02:00
do_read_from_file = false
2015-05-17 22:43:53 +02:00
do_pfs = false
do_protocols = false
do_rc4 = false
do_renego = false
2015-07-22 13:11:20 +02:00
do_std_cipherlists = false
2015-05-17 22:43:53 +02:00
do_server_defaults = false
do_server_preference = false
do_spdy = false
do_ssl_poodle = false
2015-06-08 18:19:34 +02:00
do_tls_fallback_scsv = false
2015-05-17 22:43:53 +02:00
do_test_just_one = false
do_tls_sockets = false
}
# Set default scanning options
set_scanning_defaults( ) {
2015-05-29 10:36:14 +02:00
do_allciphers = true
2015-05-17 22:43:53 +02:00
do_vulnerabilities = true
do_beast = true
do_breach = true
do_ccs_injection = true
do_crime = true
do_freak = true
2015-05-27 14:28:18 +02:00
do_logjam = true
2015-05-17 22:43:53 +02:00
do_header = true
do_heartbleed = true
do_pfs = true
do_protocols = true
do_rc4 = true
do_renego = true
2015-07-22 13:11:20 +02:00
do_std_cipherlists = true
2015-05-17 22:43:53 +02:00
do_server_defaults = true
do_server_preference = true
do_spdy = true
do_ssl_poodle = true
2015-06-08 18:19:34 +02:00
do_tls_fallback_scsv = true
2015-05-17 22:43:53 +02:00
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 \
2015-05-31 14:40:12 +02:00
do_freak do_logjam do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_renego \
2015-07-22 13:11:20 +02:00
do_std_cipherlists do_server_defaults do_server_preference do_spdy do_ssl_poodle do_tls_fallback_scsv \
2015-08-01 23:11:27 +02:00
do_test_just_one do_tls_sockets do_read_from_file; do
2015-05-17 22:43:53 +02:00
[ " ${ !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 \
2015-05-31 14:40:12 +02:00
do_freak do_logjam do_header do_heartbleed do_rc4 do_mx_all_ips do_pfs do_protocols do_rc4 do_renego \
2015-07-22 13:11:20 +02:00
do_std_cipherlists do_server_defaults do_server_preference do_spdy do_ssl_poodle do_tls_fallback_scsv \
2015-08-01 23:11:27 +02:00
do_test_just_one do_tls_sockets do_read_from_file; do
2015-05-29 19:44:27 +02:00
printf "%-22s = %s\n" $gbl " ${ !gbl } "
2015-05-17 22:43:53 +02:00
done
printf "%-22s : %s\n" URI: " $URI "
}
2015-06-01 12:01:38 +02:00
# arg1+2 are just the options
parse_opt_equal_sign( ) {
if [ [ " $1 " = = *= * ] ] ; then
echo " $1 " | awk -F'=' '{ print $2 }'
return 1 # = means we don't need to shift args!
else
echo $2
return 0 # we need to shift
fi
}
2015-06-28 13:52:42 +02:00
parse_cmd_line( ) {
2015-05-17 22:43:53 +02:00
# 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
2015-06-01 12:01:38 +02:00
-h| --help)
help 0
; ;
2015-05-17 22:43:53 +02:00
-b| --banner| -v| --version)
2015-05-30 11:13:57 +02:00
find_openssl_binary
2015-07-20 14:05:35 +02:00
maketempf
2015-05-30 11:13:57 +02:00
mybanner
2015-06-01 12:01:38 +02:00
exit 0
; ;
2015-05-17 22:43:53 +02:00
--mx)
2015-05-31 14:40:12 +02:00
do_mx_all_ips = true
2015-07-14 12:35:26 +02:00
PORT = 25
2015-06-01 12:01:38 +02:00
; ;
--mx465) # doesn't work with major ISPs
do_mx_all_ips = true
PORT = 465
; ;
--mx587) # doesn't work with major ISPs
2015-05-31 14:40:12 +02:00
do_mx_all_ips = true
2015-06-01 12:01:38 +02:00
PORT = 587
; ;
--ip| --ip= *)
CMDLINE_IP = $( parse_opt_equal_sign " $1 " " $2 " )
[ $? -eq 0 ] && shift
; ;
-V| -V= *| --local| --local= *) # this is only displaying local ciphers, thus we don't put it in the loop
2015-05-27 11:19:30 +02:00
find_openssl_binary
2015-07-20 14:05:35 +02:00
maketempf # for GOST support
2015-05-27 11:19:30 +02:00
mybanner
openssl_age
2015-06-01 12:01:38 +02:00
prettyprint_local $( parse_opt_equal_sign " $1 " " $2 " )
exit $?
; ;
-x| -x= *| --single[ -_] cipher| --single[ -_] cipher = *)
2015-05-17 22:43:53 +02:00
do_test_just_one = true
2015-06-01 12:01:38 +02:00
single_cipher = $( parse_opt_equal_sign " $1 " " $2 " )
2015-07-06 20:42:43 +02:00
[ [ $? -eq 0 ] ] && shift
2015-06-01 12:01:38 +02:00
; ;
-t| -t= *| --starttls| --starttls= *)
2015-05-17 22:43:53 +02:00
do_starttls = true
2015-06-01 12:01:38 +02:00
STARTTLS_PROTOCOL = $( parse_opt_equal_sign " $1 " " $2 " )
2015-07-06 20:42:43 +02:00
[ [ $? -eq 0 ] ] && shift
; ;
--xmpphost| --xmpphost= *)
XMPP_HOST = $( parse_opt_equal_sign " $1 " " $2 " )
[ [ $? -eq 0 ] ] && shift
2015-06-01 12:01:38 +02:00
; ;
2015-05-17 22:43:53 +02:00
-e| --each-cipher)
2015-06-01 12:01:38 +02:00
do_allciphers = true
; ;
2015-05-17 22:43:53 +02:00
-E| --cipher-per-proto| --cipher_per_proto)
2015-06-01 12:01:38 +02:00
do_cipher_per_proto = true
; ;
2015-05-17 22:43:53 +02:00
-p| --protocols)
do_protocols = true
2015-06-01 12:01:38 +02:00
do_spdy = true
; ;
2015-05-17 22:43:53 +02:00
-y| --spdy| --npn)
2015-06-01 12:01:38 +02:00
do_spdy = true
; ;
2015-05-17 22:43:53 +02:00
-f| --ciphers)
2015-07-22 13:11:20 +02:00
do_std_cipherlists = true
2015-06-01 12:01:38 +02:00
; ;
-S| --server[ -_] defaults)
do_server_defaults = true
; ;
-P| --server[ _-] preference)
do_server_preference = true
; ;
2015-05-17 22:43:53 +02:00
-H| --header| --headers)
2015-06-01 12:01:38 +02:00
do_header = true
; ;
2015-05-17 22:43:53 +02:00
-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
2015-06-08 18:19:34 +02:00
do_tls_fallback_scsv = true
2015-05-17 22:43:53 +02:00
do_freak = true
2015-07-22 13:11:20 +02:00
do_logjam = true
2015-05-17 22:43:53 +02:00
do_beast = true
do_rc4 = true
2015-06-01 12:01:38 +02:00
VULN_COUNT = 10
; ;
2015-05-17 22:43:53 +02:00
-B| --heartbleed)
do_heartbleed = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
-I| --ccs| --ccs[ -_] injection)
2015-05-17 22:43:53 +02:00
do_ccs_injection = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-05-17 22:43:53 +02:00
-R| --renegotiation)
do_renego = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-05-17 22:43:53 +02:00
-C| --compression| --crime)
do_crime = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-05-17 22:43:53 +02:00
-T| --breach)
do_breach = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-05-17 22:43:53 +02:00
-O| --poodle)
do_ssl_poodle = true
2015-06-08 18:19:34 +02:00
do_tls_fallback_scsv = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-06-11 18:33:06 +02:00
-Z| --tls[ _-] fallback| tls[ _-] fallback[ _-] scs)
do_tls_fallback_scsv = true
let "VULN_COUNT++"
; ;
2015-05-17 22:43:53 +02:00
-F| --freak)
do_freak = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-05-27 14:28:18 +02:00
-J| --logjam)
do_logjam = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-05-17 22:43:53 +02:00
-A| --beast)
do_beast = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-05-17 22:43:53 +02:00
-4| --rc4| --appelbaum)
2015-05-27 14:28:18 +02:00
do_rc4 = true
2015-06-01 12:01:38 +02:00
let "VULN_COUNT++"
; ;
2015-05-17 22:43:53 +02:00
-s| --pfs| --fs| --nsa)
2015-06-01 12:01:38 +02:00
do_pfs = true
; ;
2015-07-25 14:33:08 +02:00
--devel) ### this development feature will soon disappear
2015-06-22 18:32:40 +02:00
HEX_CIPHER = ""
2015-05-17 22:43:53 +02:00
# DEBUG=3 ./testssl.sh -q 03 "cc, 13, c0, 13" google.de
# DEBUG=3 ./testssl.sh -q 01 yandex.ru
2015-06-22 18:32:40 +02:00
TLS_LOW_BYTE = " $2 " ;
2015-05-17 22:43:53 +02:00
if [ $# -eq 4 ] ; then # protocol AND ciphers specified
HEX_CIPHER = " $3 "
2015-05-29 19:44:27 +02:00
shift
2015-05-17 22:43:53 +02:00
fi
shift
do_tls_sockets = true
2015-06-23 12:58:40 +02:00
outln " \nTLS_LOW_BYTE/HEX_CIPHER: ${ TLS_LOW_BYTE } / ${ HEX_CIPHER } "
2015-06-01 12:01:38 +02:00
; ;
--wide)
2015-07-06 10:10:46 +02:00
WIDE = true
2015-06-01 12:01:38 +02:00
; ;
--assuming[ _-] http| --assume[ -_] http)
2015-07-06 10:10:46 +02:00
ASSUMING_HTTP = true
2015-06-01 12:01:38 +02:00
; ;
2015-05-17 22:43:53 +02:00
--sneaky)
2015-07-06 10:10:46 +02:00
SNEAKY = true
2015-06-01 12:01:38 +02:00
; ;
2015-07-25 14:33:08 +02:00
-q| --quiet)
QUIET = true
; ;
2015-08-01 23:11:27 +02:00
--file| --file= *)
# no shift here as otherwise URI is empty and it bails out
FNAME = $( parse_opt_equal_sign " $1 " " $2 " )
IKNOW_FNAME = true
do_read_from_file = true
; ;
2015-06-01 12:01:38 +02:00
--warnings| --warnings= *)
WARNINGS = $( parse_opt_equal_sign " $1 " " $2 " )
2015-07-06 20:42:43 +02:00
[ [ $? -eq 0 ] ] && shift
2015-06-01 12:01:38 +02:00
case " $WARNING " in
batch| off| false ) ; ;
default) pr_magentaln "warnings can be either \"batch\", \"off\" or \"false\"" ; ;
2015-05-17 22:43:53 +02:00
esac
2015-06-01 12:01:38 +02:00
; ;
--show[ -_] each)
SHOW_EACH_C = 1 #FIXME: sense is vice versa
; ;
--debug| --debug= *)
DEBUG = $( parse_opt_equal_sign " $1 " " $2 " )
2015-07-06 20:42:43 +02:00
[ [ $? -eq 0 ] ] && shift
2015-06-01 12:01:38 +02:00
; ;
--color| --color= *)
COLOR = $( parse_opt_equal_sign " $1 " " $2 " )
2015-07-06 20:42:43 +02:00
[ [ $? -eq 0 ] ] && shift
2015-05-17 22:43:53 +02:00
if [ $COLOR -ne 0 ] && [ $COLOR -ne 1 ] && [ $COLOR -ne 2 ] ; then
COLOR = 2
pr_magentaln " $0 : unrecognized color: $2 " 1>& 2
help 1
fi
2015-06-01 12:01:38 +02:00
; ;
--openssl| --openssl= *)
OPENSSL = $( parse_opt_equal_sign " $1 " " $2 " )
2015-07-06 20:42:43 +02:00
[ [ $? -eq 0 ] ] && shift
2015-06-01 12:01:38 +02:00
; ;
2015-06-29 22:29:15 +02:00
--proxy| --proxy= *)
PROXY = $( parse_opt_equal_sign " $1 " " $2 " )
2015-07-06 20:42:43 +02:00
[ [ $? -eq 0 ] ] && shift
2015-06-29 22:29:15 +02:00
; ;
2015-05-17 22:43:53 +02:00
--ssl_native| --ssl-native)
2015-07-06 10:10:46 +02:00
SSL_NATIVE = true
2015-06-01 12:01:38 +02:00
; ;
2015-05-17 22:43:53 +02:00
( --) shift
2015-06-01 12:01:38 +02:00
break
; ;
2015-05-29 19:44:27 +02:00
( -*) pr_magentaln " $0 : unrecognized option $1 " 1>& 2;
2015-06-01 12:01:38 +02:00
help 1
; ;
( *) break
; ;
2015-05-17 22:43:53 +02:00
esac
shift
done
# Show usage if no options were specified
[ -z $1 ] && help 0
# left off here is the URI
URI = $1
[ " $DEBUG " -ge 4 ] && debug_globals
# if we have no "do_*" set here --> query_globals: we do a standard run -- otherwise just the one specified
query_globals && set_scanning_defaults
}
lets_roll( ) {
local ret
2015-07-22 13:11:20 +02:00
[ -z " $NODEIP " ] && pr_magentaln " $NODE doesn't resolve to an IP address " && exit -1
2015-06-16 14:04:44 +02:00
determine_rdns
determine_service " $1 " # any starttls service goes here
2015-06-23 12:58:40 +02:00
${ do_tls_sockets } && { tls_sockets " $TLS_LOW_BYTE " " $HEX_CIPHER " ; echo " $? " ; exit 0; }
2015-05-17 22:43:53 +02:00
${ do_test_just_one } && test_just_one ${ single_cipher }
2015-07-22 13:11:20 +02:00
# all top level functions now following have the prefix "run_"
2015-06-23 21:54:47 +02:00
${ do_protocols } && { run_protocols; ret = $(( $? + ret)) ; }
2015-06-29 23:28:37 +02:00
${ do_spdy } && { run_spdy; ret = $(( $? + ret)) ; }
2015-07-22 13:11:20 +02:00
${ 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)) ; }
2015-05-17 22:43:53 +02:00
if ${ do_header } ; then
#TODO: refactor this into functions
if [ [ $SERVICE = = "HTTP" ] ] ; then
2015-07-22 13:11:20 +02:00
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 "
2015-05-17 22:43:53 +02:00
fi
fi
# vulnerabilities
if [ $VULN_COUNT -gt $VULN_THRESHLD ] || ${ do_vulnerabilities } ; then
2015-05-29 19:44:27 +02:00
outln; pr_blue "--> Testing vulnerabilities"
2015-05-17 22:43:53 +02:00
outln "\n"
fi
2015-07-22 13:11:20 +02:00
${ 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_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)) ; }
2015-05-17 22:43:53 +02:00
2015-06-16 14:04:44 +02:00
datebanner "Done"
2015-05-17 22:43:53 +02:00
return $ret
}
################# main #################
2015-06-02 22:13:19 +02:00
get_install_dir
2015-05-17 22:43:53 +02:00
initialize_globals
2015-06-28 13:52:42 +02:00
parse_cmd_line " $@ "
2015-08-05 11:31:55 +02:00
#color_maker
2015-06-28 13:52:42 +02:00
set_color_functions
2015-05-26 12:51:10 +02:00
find_openssl_binary
2015-07-20 14:05:35 +02:00
maketempf
2015-05-26 12:51:10 +02:00
mybanner
2015-06-29 23:28:37 +02:00
check_proxy
2015-05-17 22:43:53 +02:00
openssl_age
2015-06-16 14:04:44 +02:00
# TODO: it's ugly to have those two vars here --> main()
ret = 0
ip = ""
2015-08-01 23:11:27 +02:00
if $do_read_from_file ; then
if [ [ ! -r " $FNAME " ] ] && $IKNOW_FNAME ; then
pr_magentaln " Fatal error: Can't read \" $FNAME \" "
exit -1
fi
pr_reverse " ====== Running in file batch mode with file=\" $FNAME \" ====== " ; outln "\n"
cat " $FNAME " | while read cmdline; do
cmdline = $( filter_input " $cmdline " )
[ [ -z " $cmdline " ] ] && continue
[ [ " $cmdline " = = "EOF" ] ] && break
echo " $0 -q $cmdline "
2015-08-12 13:58:45 +02:00
draw_dotted_line "=" $(( $TERM_DWITH / 2 )) ; outln;
2015-08-01 23:11:27 +02:00
$0 -q $cmdline
done
exit $?
fi
2015-06-16 14:04:44 +02:00
#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
2015-08-01 23:11:27 +02:00
if $do_mx_all_ips ; then
2015-06-16 14:04:44 +02:00
query_globals # if we have just 1x "do_*" --> we do a standard run -- otherwise just the one specified
2015-05-17 22:43:53 +02:00
[ $? -eq 1 ] && set_scanning_defaults
2015-05-31 14:40:12 +02:00
mx_all_ips " ${ URI } " $PORT
2015-05-17 22:43:53 +02:00
ret = $?
2015-06-16 14:04:44 +02:00
else
parse_hn_port " ${ URI } " # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now
2015-07-22 13:11:20 +02:00
if ! determine_ip_addresses && [ [ -z $CMDLINE_IP ] ] ; then
pr_magentaln "fatal error: No IP address could be determined"
fi
2015-06-19 20:36:32 +02:00
if [ [ -n " $CMDLINE_IP " ] ] ; then
[ [ " $CMDLINE_IP " = = "one" ] ] && \
2015-06-23 12:58:40 +02:00
CMDLINE_IP = $( echo -n " $IPADDRs " | awk '{ print $1 }' )
2015-06-28 13:52:42 +02:00
NODEIP = " $CMDLINE_IP " # specific ip address for NODE was supplied
2015-06-16 14:04:44 +02:00
lets_roll " ${ STARTTLS_PROTOCOL } "
ret = $?
else # no --ip was supplied
2015-07-17 11:05:07 +02:00
if [ [ $( printf " $IPADDRs " | wc -w | sed 's/ //g' ) -gt 1 ] ] ; then # we have more than one ipv4 address to check
2015-07-23 17:11:33 +02:00
pr_bold " Testing all IPv4 addresses (port $PORT ): " ; outln " $IPADDRs "
2015-06-16 14:04:44 +02:00
for ip in $IPADDRs ; do
2015-08-02 01:16:27 +02:00
draw_dotted_line "-" $(( $TERM_DWITH / 2 ))
2015-06-16 14:04:44 +02:00
outln
NODEIP = " $ip "
lets_roll " ${ STARTTLS_PROTOCOL } "
ret = $(( $? + ret))
done
2015-08-02 01:16:27 +02:00
draw_dotted_line "-" $(( $TERM_DWITH / 2 ))
2015-06-16 14:04:44 +02:00
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
2015-05-17 22:43:53 +02:00
fi
exit $ret
2015-06-16 14:04:44 +02:00
2015-08-12 13:58:45 +02:00
# $Id: testssl.sh,v 1.343 2015/08/12 11:58:44 dirkw Exp $