Reorder functions and some variables

For a fresh start it seemed a good idea to cleanup
the order of functions and some variables so that
those with the same functionality are somewhat grouped.

Some of the functions have now a header and a foooter
to make it easier to spot and use then. Also for added future
functions the hope is that they will be put where they better
fit
This commit is contained in:
Dirk Wetter 2020-01-24 13:58:05 +01:00
parent 67598e824f
commit d44a643fab

View File

@ -51,6 +51,8 @@
# cool and unique -- they are -- but probably you can achieve e.g. the same result with my favorite # cool and unique -- they are -- but probably you can achieve e.g. the same result with my favorite
# interactive shell: zsh (zmodload zsh/net/socket -- checkout zsh/net/tcp) too! Oh, and btw. # interactive shell: zsh (zmodload zsh/net/socket -- checkout zsh/net/tcp) too! Oh, and btw.
# ksh93 has socket support too. # ksh93 has socket support too.
# Also bash is quite powerful if you use it appropriately: It can operate on patterns, process lines
# and deal perfectly with regular expressions -- without external binaries.
# /bin/bash though is way more often used within Linux and it's perfect for cross platform support. # /bin/bash though is way more often used within Linux and it's perfect for cross platform support.
# MacOS X has it and also under Windows the MSYS2 extension or Cygwin as well as Bash on Windows (WSL) # MacOS X has it and also under Windows the MSYS2 extension or Cygwin as well as Bash on Windows (WSL)
# has /bin/bash. # has /bin/bash.
@ -135,62 +137,17 @@ declare -r PROG_NAME="$(basename "$0")"
declare -r RUN_DIR="$(dirname "$0")" declare -r RUN_DIR="$(dirname "$0")"
declare -r SYSTEM="$(uname -s)" declare -r SYSTEM="$(uname -s)"
declare -r SYSTEMREV="$(uname -r)" declare -r SYSTEMREV="$(uname -r)"
SYSTEM2="" # currently only being used for WSL = bash on windows
TESTSSL_INSTALL_DIR="${TESTSSL_INSTALL_DIR:-""}" # If you run testssl.sh and it doesn't find it necessary file automagically set TESTSSL_INSTALL_DIR
CA_BUNDLES_PATH="${CA_BUNDLES_PATH:-""}" # You can have your stores some place else
ADDITIONAL_CA_FILES="${ADDITIONAL_CA_FILES:-""}" # single file with a CA in PEM format or comma separated lists of them
CIPHERS_BY_STRENGTH_FILE=""
TLS_DATA_FILE="" # mandatory file for socket-based handshakes
OPENSSL_LOCATION=""
HNAME="$(hostname)" HNAME="$(hostname)"
HNAME="${HNAME%%.*}" HNAME="${HNAME%%.*}"
declare CMDLINE declare CMDLINE
CMDLINE_PARSED="" # This makes sure we don't let early fatal() write into files when files aren't created yet CMDLINE_PARSED="" # This makes sure we don't let early fatal() write into files when files aren't created yet
declare -r -a CMDLINE_ARRAY=("$@") # When performing mass testing, the child processes need to be sent the declare -r -a CMDLINE_ARRAY=("$@") # When performing mass testing, the child processes need to be sent the
declare -a MASS_TESTING_CMDLINE # command line in the form of an array (see #702 and http://mywiki.wooledge.org/BashFAQ/050). declare -a MASS_TESTING_CMDLINE # command line in the form of an array (see #702 and http://mywiki.wooledge.org/BashFAQ/050).
########### Some predefinitions: date, sed (we always use test and NOT try to determine
# capabilities by querying the OS)
#
HAS_GNUDATE=false
HAS_FREEBSDDATE=false
HAS_OPENBSDDATE=false
if date -d @735275209 >/dev/null 2>&1; then
if date -r @735275209 >/dev/null 2>&1; then
# It can't do any conversion from a plain date output.
HAS_OPENBSDDATE=true
else
HAS_GNUDATE=true
fi
fi
# FreeBSD and OS X date(1) accept "-f inputformat", so do newer OpenBSD versions >~ 6.6.
date -j -f '%s' 1234567 >/dev/null 2>&1 && \
HAS_FREEBSDDATE=true
echo A | sed -E 's/A//' >/dev/null 2>&1 && \
declare -r HAS_SED_E=true || \
declare -r HAS_SED_E=false
########### Terminal defintions
tty -s && \
declare -r INTERACTIVE=true || \
declare -r INTERACTIVE=false
if [[ -z $TERM_WIDTH ]]; then # no batch file and no otherwise predefined TERM_WIDTH
if ! tput cols &>/dev/null || ! "$INTERACTIVE";then # Prevent tput errors if running non interactive
export TERM_WIDTH=${COLUMNS:-80}
else
export TERM_WIDTH=${COLUMNS:-$(tput cols)} # for custom line wrapping and dashes
fi
fi
TERM_CURRPOS=0 # custom line wrapping needs alter the current horizontal cursor pos
########### Defining (and presetting) variables which can be changed ########### Defining (and presetting) variables which can be changed
# #
# Following variables make use of $ENV and can be used like "OPENSSL=<myprivate_path_to_openssl> ./testssl.sh <URI>" # Following variables make use of $ENV and can also be used like "<VAR>=<value> ./testssl.sh <URI>"
declare -x OPENSSL declare -x OPENSSL
OPENSSL_TIMEOUT=${OPENSSL_TIMEOUT:-""} # Default connect timeout with openssl before we call the server side unreachable OPENSSL_TIMEOUT=${OPENSSL_TIMEOUT:-""} # Default connect timeout with openssl before we call the server side unreachable
CONNECT_TIMEOUT=${CONNECT_TIMEOUT:-""} # Default connect timeout with sockets before we call the server side unreachable CONNECT_TIMEOUT=${CONNECT_TIMEOUT:-""} # Default connect timeout with sockets before we call the server side unreachable
@ -227,9 +184,12 @@ APPEND=${APPEND:-false} # append to csv/json file instead of ove
HAS_IPv6=${HAS_IPv6:-false} # if you have OpenSSL with IPv6 support AND IPv6 networking set it to yes HAS_IPv6=${HAS_IPv6:-false} # if you have OpenSSL with IPv6 support AND IPv6 networking set it to yes
ALL_CLIENTS=${ALL_CLIENTS:-false} # do you want to run all client simulation form all clients supplied by SSLlabs? ALL_CLIENTS=${ALL_CLIENTS:-false} # do you want to run all client simulation form all clients supplied by SSLlabs?
OFFENSIVE=${OFFENSIVE:-true} # do you want to include offensive vulnerability tests which may cause blocking by an IDS? OFFENSIVE=${OFFENSIVE:-true} # do you want to include offensive vulnerability tests which may cause blocking by an IDS?
ADDTL_CA_FILES="${ADDTL_CA_FILES:-""}" # single file with a CA in PEM format or comma separated lists of them
########### Tuning vars which cannot be set by a cmd line switch. Use instead e.g "HEADER_MAXSLEEP=10 ./testssl.sh <your_args_here>" ########### Tuning vars which cannot be set by a cmd line switch. Use instead e.g "HEADER_MAXSLEEP=10 ./testssl.sh <your_args_here>"
# #
TESTSSL_INSTALL_DIR="${TESTSSL_INSTALL_DIR:-""}" # If you run testssl.sh and it doesn't find it necessary file automagically set TESTSSL_INSTALL_DIR
CA_BUNDLES_PATH="${CA_BUNDLES_PATH:-""}" # You can have your CA stores some place else
EXPERIMENTAL=${EXPERIMENTAL:-false} # a development hook which allows us to disable code EXPERIMENTAL=${EXPERIMENTAL:-false} # a development hook which allows us to disable code
PROXY_WAIT=${PROXY_WAIT:-20} # waiting at max 20 seconds for socket reply through proxy PROXY_WAIT=${PROXY_WAIT:-20} # waiting at max 20 seconds for socket reply through proxy
DNS_VIA_PROXY=${DNS_VIA_PROXY:-true} # do DNS lookups via proxy. --ip=proxy reverses this DNS_VIA_PROXY=${DNS_VIA_PROXY:-true} # do DNS lookups via proxy. --ip=proxy reverses this
@ -273,7 +233,11 @@ declare -r UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Fi
########### Initialization part, further global vars just being declared here ########### Initialization part, further global vars just being declared here
# #
SYSTEM2="" # currently only being used for WSL = bash on windows
PRINTF="" # which external printf to use. Empty presets the internal one, see #1130 PRINTF="" # which external printf to use. Empty presets the internal one, see #1130
CIPHERS_BY_STRENGTH_FILE=""
TLS_DATA_FILE="" # mandatory file for socket-based handshakes
OPENSSL_LOCATION=""
IKNOW_FNAME=false IKNOW_FNAME=false
FIRST_FINDING=true # is this the first finding we are outputting to file? FIRST_FINDING=true # is this the first finding we are outputting to file?
JSONHEADER=true # include JSON headers and footers in HTML file, if one is being created JSONHEADER=true # include JSON headers and footers in HTML file, if one is being created
@ -438,6 +402,44 @@ declare TLS_CIPHER_EXPORT=()
declare TLS_CIPHER_OSSL_SUPPORTED=() declare TLS_CIPHER_OSSL_SUPPORTED=()
declare TLS13_OSSL_CIPHERS="TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256" declare TLS13_OSSL_CIPHERS="TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256"
########### Some predefinitions: date, sed (we always use tests for binaries and NOT try to determine
# capabilities by querying the OS)
#
HAS_GNUDATE=false
HAS_FREEBSDDATE=false
HAS_OPENBSDDATE=false
if date -d @735275209 >/dev/null 2>&1; then
if date -r @735275209 >/dev/null 2>&1; then
# It can't do any conversion from a plain date output.
HAS_OPENBSDDATE=true
else
HAS_GNUDATE=true
fi
fi
# FreeBSD and OS X date(1) accept "-f inputformat", so do newer OpenBSD versions >~ 6.6.
date -j -f '%s' 1234567 >/dev/null 2>&1 && \
HAS_FREEBSDDATE=true
echo A | sed -E 's/A//' >/dev/null 2>&1 && \
declare -r HAS_SED_E=true || \
declare -r HAS_SED_E=false
########### Terminal defintions
tty -s && \
declare -r INTERACTIVE=true || \
declare -r INTERACTIVE=false
if [[ -z $TERM_WIDTH ]]; then # No batch file and no otherwise predefined TERM_WIDTH
if ! tput cols &>/dev/null || ! "$INTERACTIVE";then # Prevent tput errors if running non interactive
export TERM_WIDTH=${COLUMNS:-80}
else
export TERM_WIDTH=${COLUMNS:-$(tput cols)} # For custom line wrapping and dashes
fi
fi
TERM_CURRPOS=0 # Custom line wrapping needs alter the current horizontal cursor pos
########### Severity functions and globals ########### Severity functions and globals
# #
INFO=0 INFO=0
@ -725,6 +727,94 @@ set_color_functions() {
# FreeBSD 10 understands ESC codes like 'echo -e "\e[3mfoobar\e[23m"', but also no tput for italics # FreeBSD 10 understands ESC codes like 'echo -e "\e[3mfoobar\e[23m"', but also no tput for italics
} }
###### START universal helper function definitions ######
if [[ "${BASH_VERSINFO[0]}" == 3 ]]; then
# older bash can do this only (MacOS X), even SLES 11, see #697
toupper() { tr 'a-z' 'A-Z' <<< "$1"; }
tolower() { tr 'A-Z' 'a-z' <<< "$1"; }
else
toupper() { echo -n "${1^^}"; }
tolower() { echo -n "${1,,}"; }
fi
get_last_char() {
echo "${1:~0}" # "${string: -1}" would work too (both also in bash 3.2)
}
# Checking for last char. If already a separator supplied, we don't need an additional one
debugme() {
[[ "$DEBUG" -ge 2 ]] && "$@"
return 0
}
hex2dec() {
echo $((16#$1))
}
# convert 414243 into ABC
hex2ascii() {
for (( i=0; i<${#1}; i+=2 )); do
# 2>/dev/null added because 'warning: command substitution: ignored null byte in input'
# --> didn't help though
printf "\x${1:$i:2}" 2>/dev/null
done
}
# convert decimal number < 256 to hex
dec02hex() {
printf "x%02x" "$1"
}
# convert decimal number between 256 and < 256*256 to hex
dec04hex() {
local a=$(printf "%04x" "$1")
printf "x%02s, x%02s" "${a:0:2}" "${a:2:2}"
}
# trim spaces for BSD and old sed
count_lines() {
echo $(wc -l <<< "$1")
}
count_words() {
echo $(wc -w <<< "$1")
}
count_ciphers() {
echo $(wc -w <<< "${1//:/ }")
}
newline_to_spaces() {
tr '\n' ' ' <<< "$1" | sed 's/ $//'
}
colon_to_spaces() {
echo "${1//:/ }"
}
strip_lf() {
tr -d '\n' <<< "$1" | tr -d '\r'
}
strip_spaces() {
echo "${1// /}"
}
# https://web.archive.org/web/20121022051228/http://codesnippets.joyent.com/posts/show/1816
strip_leading_space() {
printf "%s" "${1#"${1%%[![:space:]]*}"}"
}
strip_trailing_space() {
printf "%s" "${1%"${1##*[![:space:]]}"}"
}
is_number() {
[[ "$1" =~ ^[1-9][0-9]*$ ]] && \
return 0 || \
return 1
}
strip_quote() { strip_quote() {
# remove color codes (see http://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed) # remove color codes (see http://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed)
# \', leading and all trailing spaces # \', leading and all trailing spaces
@ -736,7 +826,171 @@ strip_quote() {
# " deconfuse vim\'s syntax highlighting ;-) # " deconfuse vim\'s syntax highlighting ;-)
#################### JSON FILE FORMATTING ####################
is_ipv4addr() {
local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
local ipv4address="$octet\\.$octet\\.$octet\\.$octet"
[[ -z "$1" ]] && return 1
# more than numbers, important for hosts like AAA.BBB.CCC.DDD.in-addr.arpa.DOMAIN.TLS
[[ -n $(tr -d '0-9\.' <<< "$1") ]] && return 1
grep -Eq "$ipv4address" <<< "$1" && \
return 0 || \
return 1
}
# a bit easier
is_ipv6addr() {
[[ -z "$1" ]] && return 1
# less than 2x ":"
[[ $(count_lines "$(tr ':' '\n' <<< "$1")") -le 1 ]] && \
return 1
# check which chars allowed:
[[ -n "$(tr -d '0-9:a-fA-F ' <<< "$1" | sed -e '/^$/d')" ]] && \
return 1
return 0
}
###### END universal helper function definitions ######
###### START ServerHello/OpenSSL/F5 function definitions ######
#arg1: TLS 1.2 and below ciphers
#arg2: TLS 1.3 ciphers
#arg3: options (e.g., -V)
actually_supported_osslciphers() {
local tls13_ciphers="$TLS13_OSSL_CIPHERS"
[[ "$2" != ALL ]] && tls13_ciphers="$2"
if "$HAS_CIPHERSUITES"; then
$OPENSSL ciphers $3 $OSSL_CIPHERS_S -ciphersuites "$tls13_ciphers" "$1" 2>/dev/null || echo ""
elif [[ -n "$tls13_ciphers" ]]; then
$OPENSSL ciphers $3 $OSSL_CIPHERS_S "$tls13_ciphers:$1" 2>/dev/null || echo ""
else
$OPENSSL ciphers $OSSL_CIPHERS_S $3 "$1" 2>/dev/null || echo ""
fi
}
# Given a protocol (arg1) and a list of ciphers (arg2) that is formatted as
# ", xx,xx, xx,xx, xx,xx, xx,xx" remove any TLSv1.3 ciphers if the protocol
# is less than 04 and remove any TLSv1.2-only ciphers if the protocol is less
# than 03.
strip_inconsistent_ciphers() {
local -i proto=0x$1
local cipherlist="$2"
[[ $proto -lt 4 ]] && cipherlist="${cipherlist//, 13,0[0-9a-fA-F]/}"
if [[ $proto -lt 3 ]]; then
cipherlist="${cipherlist//, 00,3[b-fB-F]/}"
cipherlist="${cipherlist//, 00,40/}"
cipherlist="${cipherlist//, 00,6[7-9a-dA-D]/}"
cipherlist="${cipherlist//, 00,9[c-fC-F]/}"
cipherlist="${cipherlist//, 00,[abAB][0-9a-fA-F]/}"
cipherlist="${cipherlist//, 00,[cC][0-5]/}"
cipherlist="${cipherlist//, 16,[bB][7-9aA]/}"
cipherlist="${cipherlist//, [cC]0,2[3-9a-fA-F]/}"
cipherlist="${cipherlist//, [cC]0,3[01278a-fA-F]/}"
cipherlist="${cipherlist//, [cC]0,[4-9aA][0-9a-fA-F]/}"
cipherlist="${cipherlist//, [cC][cC],1[345]/}"
cipherlist="${cipherlist//, [cC][cC],[aA][89a-eA-E]/}"
fi
echo "$cipherlist"
return 0
}
# retrieve cipher from ServerHello (via openssl)
get_cipher() {
local cipher=""
local server_hello="$(cat -v "$1")"
# This and two other following instances are not best practice and normally a useless use of "cat", see
# https://web.archive.org/web/20160711205930/http://porkmail.org/era/unix/award.html#uucaletter
# However there seem to be cases where the preferred $(< "$1") logic has a problem.
# Esepcially with bash 3.2 (Mac OS X) and when on the server side binary chars
# are returned, see https://stackoverflow.com/questions/7427262/how-to-read-a-file-into-a-variable-in-shell#22607352
# and https://github.com/drwetter/testssl.sh/issues/1292
# Performance measurements showed no to barely measureable penalty (1s displayed in 9 tries).
if [[ "$server_hello" =~ Cipher\ *:\ ([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]]; then
cipher="${BASH_REMATCH##* }"
elif [[ "$server_hello" =~ (New|Reused)", "(SSLv[23]|TLSv1(\.[0-3])?(\/SSLv3)?)", Cipher is "([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]]; then
cipher="${BASH_REMATCH##* }"
fi
tm_out "$cipher"
}
# retrieve protocol from ServerHello (via openssl)
get_protocol() {
local protocol=""
local server_hello="$(cat -v "$1")"
if [[ "$server_hello" =~ Protocol\ *:\ (SSLv[23]|TLSv1(\.[0-3])?) ]]; then
protocol="${BASH_REMATCH##* }"
elif [[ "$server_hello" =~ (New|Reused)", TLSv1.3, Cipher is "TLS_[A-Z0-9_]+ ]]; then
# Note: When OpenSSL prints "New, <protocol>, Cipher is <cipher>", <cipher> is the
# negotiated cipher, but <protocol> is not the negotiated protocol. Instead, it is
# the SSL/TLS protocol that first defined <cipher>. Since the ciphers that were
# first defined for TLSv1.3 may only be used with TLSv1.3, this line may be used
# to determine whether TLSv1.3 was negotiated, but if another protocol is specified
# on this line, then this line does not indicate the actual protocol negotiated. Also,
# only TLSv1.3 cipher suites have names that begin with TLS_, which provides additional
# assurance that the above match will only succeed if TLSv1.3 was negotiated.
protocol="TLSv1.3"
fi
tm_out "$protocol"
}
# now some function for the integrated BIGIP F5 Cookie detector (see https://github.com/drwetter/F5-BIGIP-Decoder)
f5_hex2ip() {
debugme echo "$1"
echo $((16#${1:0:2})).$((16#${1:2:2})).$((16#${1:4:2})).$((16#${1:6:2}))
}
f5_hex2ip6() {
debugme echo "$1"
echo "[${1:0:4}:${1:4:4}:${1:8:4}:${1:12:4}.${1:16:4}:${1:20:4}:${1:24:4}:${1:28:4}]"
}
f5_determine_routeddomain() {
local tmp
tmp="${1%%o*}"
echo "${tmp/rd/}"
}
f5_ip_oldstyle() {
local tmp
local a b c d
tmp="${1/%.*}" # until first dot
tmp="$(printf "%08x" "$tmp")" # convert the whole thing to hex, now back to ip (reversed notation:
tmp="$(f5_hex2ip $tmp)" # transform to ip with reversed notation
IFS="." read -r a b c d <<< "$tmp" # reverse it
echo $d.$c.$b.$a
}
f5_port_decode() {
local tmp
tmp="$(strip_lf "$1")" # remove lf if there is one
tmp="${tmp/.0000/}" # to be sure remove trailing zeros with a dot
tmp="${tmp#*.}" # get the port
tmp="$(printf "%04x" "${tmp}")" # to hex
if [[ ${#tmp} -eq 4 ]]; then
:
elif [[ ${#tmp} -eq 3 ]]; then # fill it up with leading zeros if needed
tmp=0${tmp}
elif [[ ${#tmp} -eq 2 ]]; then
tmp=00${tmp}
fi
echo $((16#${tmp:2:2}${tmp:0:2})) # reverse order and convert it from hex to dec
}
###### START ServerHello/OpenSSL/F5 function definitions ######
###### END helper function definitions ######
##################### START output file formatting functions #########################
#################### START JSON file functions ####################
fileout_json_footer() { fileout_json_footer() {
if "$do_json"; then if "$do_json"; then
@ -853,8 +1107,6 @@ fileout_json_finding() {
fi fi
} }
##################### FILE FORMATTING #########################
fileout_pretty_json_banner() { fileout_pretty_json_banner() {
local target local target
@ -1040,7 +1292,7 @@ csv_header() {
} }
################# JSON FILE FORMATTING END. HTML START #################### ################# END JSON file functions. START HTML functions ####################
html_header() { html_header() {
local fname_prefix local fname_prefix
@ -1111,7 +1363,7 @@ html_footer() {
return 0 return 0
} }
################# HTML FILE FORMATTING END #################### ################# END HTML file functions ####################
prepare_logging() { prepare_logging() {
# arg1: for testing mx records name we put a name of logfile in here, otherwise we get strange file names # arg1: for testing mx records name we put a name of logfile in here, otherwise we get strange file names
@ -1146,258 +1398,7 @@ prepare_logging() {
exec > >(tee -a -i "$LOGFILE") exec > >(tee -a -i "$LOGFILE")
} }
################### FILE FORMATTING END ######################### ################### END all file output functions #########################
###### START helper function definitions ######
if [[ "${BASH_VERSINFO[0]}" == 3 ]]; then
# older bash can do this only (MacOS X), even SLES 11, see #697
toupper() { tr 'a-z' 'A-Z' <<< "$1"; }
tolower() { tr 'A-Z' 'a-z' <<< "$1"; }
else
toupper() { echo -n "${1^^}"; }
tolower() { echo -n "${1,,}"; }
fi
get_last_char() {
echo "${1:~0}" # "${string: -1}" would work too (both also in bash 3.2)
}
# Checking for last char. If already a separator supplied, we don't need an additional one
debugme() {
[[ "$DEBUG" -ge 2 ]] && "$@"
return 0
}
hex2dec() {
echo $((16#$1))
}
# convert 414243 into ABC
hex2ascii() {
for (( i=0; i<${#1}; i+=2 )); do
# 2>/dev/null added because 'warning: command substitution: ignored null byte in input'
# --> didn't help though
printf "\x${1:$i:2}" 2>/dev/null
done
}
# convert decimal number < 256 to hex
dec02hex() {
printf "x%02x" "$1"
}
# convert decimal number between 256 and < 256*256 to hex
dec04hex() {
local a=$(printf "%04x" "$1")
printf "x%02s, x%02s" "${a:0:2}" "${a:2:2}"
}
# trim spaces for BSD and old sed
count_lines() {
#echo "${$(wc -l <<< "$1")// /}"
# ^^ bad substitution under bash, zsh ok. For some reason this does the trick:
echo $(wc -l <<< "$1")
}
count_words() {
#echo "${$(wc -w <<< "$1")// /}"
# ^^ bad substitution under bash, zsh ok. For some reason this does the trick:
echo $(wc -w <<< "$1")
}
count_ciphers() {
echo $(wc -w <<< "${1//:/ }")
}
#arg1: TLS 1.2 and below ciphers
#arg2: TLS 1.3 ciphers
#arg3: options (e.g., -V)
actually_supported_osslciphers() {
local tls13_ciphers="$TLS13_OSSL_CIPHERS"
[[ "$2" != ALL ]] && tls13_ciphers="$2"
if "$HAS_CIPHERSUITES"; then
$OPENSSL ciphers $3 $OSSL_CIPHERS_S -ciphersuites "$tls13_ciphers" "$1" 2>/dev/null || echo ""
elif [[ -n "$tls13_ciphers" ]]; then
$OPENSSL ciphers $3 $OSSL_CIPHERS_S "$tls13_ciphers:$1" 2>/dev/null || echo ""
else
$OPENSSL ciphers $OSSL_CIPHERS_S $3 "$1" 2>/dev/null || echo ""
fi
}
# Given a protocol (arg1) and a list of ciphers (arg2) that is formatted as
# ", xx,xx, xx,xx, xx,xx, xx,xx" remove any TLSv1.3 ciphers if the protocol
# is less than 04 and remove any TLSv1.2-only ciphers if the protocol is less
# than 03.
strip_inconsistent_ciphers() {
local -i proto=0x$1
local cipherlist="$2"
[[ $proto -lt 4 ]] && cipherlist="${cipherlist//, 13,0[0-9a-fA-F]/}"
if [[ $proto -lt 3 ]]; then
cipherlist="${cipherlist//, 00,3[b-fB-F]/}"
cipherlist="${cipherlist//, 00,40/}"
cipherlist="${cipherlist//, 00,6[7-9a-dA-D]/}"
cipherlist="${cipherlist//, 00,9[c-fC-F]/}"
cipherlist="${cipherlist//, 00,[abAB][0-9a-fA-F]/}"
cipherlist="${cipherlist//, 00,[cC][0-5]/}"
cipherlist="${cipherlist//, 16,[bB][7-9aA]/}"
cipherlist="${cipherlist//, [cC]0,2[3-9a-fA-F]/}"
cipherlist="${cipherlist//, [cC]0,3[01278a-fA-F]/}"
cipherlist="${cipherlist//, [cC]0,[4-9aA][0-9a-fA-F]/}"
cipherlist="${cipherlist//, [cC][cC],1[345]/}"
cipherlist="${cipherlist//, [cC][cC],[aA][89a-eA-E]/}"
fi
echo "$cipherlist"
return 0
}
newline_to_spaces() {
tr '\n' ' ' <<< "$1" | sed 's/ $//'
}
colon_to_spaces() {
echo "${1//:/ }"
}
strip_lf() {
tr -d '\n' <<< "$1" | tr -d '\r'
}
strip_spaces() {
echo "${1// /}"
}
# https://web.archive.org/web/20121022051228/http://codesnippets.joyent.com/posts/show/1816
strip_leading_space() {
printf "%s" "${1#"${1%%[![:space:]]*}"}"
}
strip_trailing_space() {
printf "%s" "${1%"${1##*[![:space:]]}"}"
}
# retrieve cipher from ServerHello (via openssl)
get_cipher() {
local cipher=""
local server_hello="$(cat -v "$1")"
# This and two other following instances are not best practice and normally a useless use of "cat", see
# https://web.archive.org/web/20160711205930/http://porkmail.org/era/unix/award.html#uucaletter
# However there seem to be cases where the preferred $(< "$1") logic has a problem.
# Esepcially with bash 3.2 (Mac OS X) and when on the server side binary chars
# are returned, see https://stackoverflow.com/questions/7427262/how-to-read-a-file-into-a-variable-in-shell#22607352
# and https://github.com/drwetter/testssl.sh/issues/1292
# Performance measurements showed no to barely measureable penalty (1s displayed in 9 tries).
if [[ "$server_hello" =~ Cipher\ *:\ ([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]]; then
cipher="${BASH_REMATCH##* }"
elif [[ "$server_hello" =~ (New|Reused)", "(SSLv[23]|TLSv1(\.[0-3])?(\/SSLv3)?)", Cipher is "([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]]; then
cipher="${BASH_REMATCH##* }"
fi
tm_out "$cipher"
}
# retrieve protocol from ServerHello (via openssl)
get_protocol() {
local protocol=""
local server_hello="$(cat -v "$1")"
if [[ "$server_hello" =~ Protocol\ *:\ (SSLv[23]|TLSv1(\.[0-3])?) ]]; then
protocol="${BASH_REMATCH##* }"
elif [[ "$server_hello" =~ (New|Reused)", TLSv1.3, Cipher is "TLS_[A-Z0-9_]+ ]]; then
# Note: When OpenSSL prints "New, <protocol>, Cipher is <cipher>", <cipher> is the
# negotiated cipher, but <protocol> is not the negotiated protocol. Instead, it is
# the SSL/TLS protocol that first defined <cipher>. Since the ciphers that were
# first defined for TLSv1.3 may only be used with TLSv1.3, this line may be used
# to determine whether TLSv1.3 was negotiated, but if another protocol is specified
# on this line, then this line does not indicate the actual protocol negotiated. Also,
# only TLSv1.3 cipher suites have names that begin with TLS_, which provides additional
# assurance that the above match will only succeed if TLSv1.3 was negotiated.
protocol="TLSv1.3"
fi
tm_out "$protocol"
}
is_number() {
[[ "$1" =~ ^[1-9][0-9]*$ ]] && \
return 0 || \
return 1
}
is_ipv4addr() {
local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
local ipv4address="$octet\\.$octet\\.$octet\\.$octet"
[[ -z "$1" ]] && return 1
# more than numbers, important for hosts like AAA.BBB.CCC.DDD.in-addr.arpa.DOMAIN.TLS
[[ -n $(tr -d '0-9\.' <<< "$1") ]] && return 1
grep -Eq "$ipv4address" <<< "$1" && \
return 0 || \
return 1
}
# a bit easier
is_ipv6addr() {
[[ -z "$1" ]] && return 1
# less than 2x ":"
[[ $(count_lines "$(tr ':' '\n' <<< "$1")") -le 1 ]] && \
return 1
#check on chars allowed:
[[ -n "$(tr -d '0-9:a-fA-F ' <<< "$1" | sed -e '/^$/d')" ]] && \
return 1
return 0
}
# now some function for the integrated BIGIP F5 Cookie detector (see https://github.com/drwetter/F5-BIGIP-Decoder)
f5_hex2ip() {
debugme echo "$1"
echo $((16#${1:0:2})).$((16#${1:2:2})).$((16#${1:4:2})).$((16#${1:6:2}))
}
f5_hex2ip6() {
debugme echo "$1"
echo "[${1:0:4}:${1:4:4}:${1:8:4}:${1:12:4}.${1:16:4}:${1:20:4}:${1:24:4}:${1:28:4}]"
}
f5_determine_routeddomain() {
local tmp
tmp="${1%%o*}"
echo "${tmp/rd/}"
}
f5_ip_oldstyle() {
local tmp
local a b c d
tmp="${1/%.*}" # until first dot
tmp="$(printf "%08x" "$tmp")" # convert the whole thing to hex, now back to ip (reversed notation:
tmp="$(f5_hex2ip $tmp)" # transform to ip with reversed notation
IFS="." read -r a b c d <<< "$tmp" # reverse it
echo $d.$c.$b.$a
}
f5_port_decode() {
local tmp
tmp="$(strip_lf "$1")" # remove lf if there is one
tmp="${tmp/.0000/}" # to be sure remove trailing zeros with a dot
tmp="${tmp#*.}" # get the port
tmp="$(printf "%04x" "${tmp}")" # to hex
if [[ ${#tmp} -eq 4 ]]; then
:
elif [[ ${#tmp} -eq 3 ]]; then # fill it up with leading zeros if needed
tmp=0${tmp}
elif [[ ${#tmp} -eq 2 ]]; then
tmp=00${tmp}
fi
echo $((16#${tmp:2:2}${tmp:0:2})) # reverse order and convert it from hex to dec
}
###### END helper function definitions ######
# prints out multiple lines in $1, left aligned by spaces in $2 # prints out multiple lines in $1, left aligned by spaces in $2
out_row_aligned() { out_row_aligned() {
@ -1744,9 +1745,9 @@ check_revocation_crl() {
return 1 return 1
fi fi
if grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem; then if grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem; then
$OPENSSL verify -crl_check -CAfile <(cat $ADDITIONAL_CA_FILES "$GOOD_CA_BUNDLE" "${tmpfile%%.crl}.pem") -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT &> "${tmpfile%%.crl}.err" $OPENSSL verify -crl_check -CAfile <(cat $ADDTL_CA_FILES "$GOOD_CA_BUNDLE" "${tmpfile%%.crl}.pem") -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT &> "${tmpfile%%.crl}.err"
else else
$OPENSSL verify -crl_check -CAfile <(cat $ADDITIONAL_CA_FILES "$GOOD_CA_BUNDLE" "${tmpfile%%.crl}.pem") $HOSTCERT &> "${tmpfile%%.crl}.err" $OPENSSL verify -crl_check -CAfile <(cat $ADDTL_CA_FILES "$GOOD_CA_BUNDLE" "${tmpfile%%.crl}.pem") $HOSTCERT &> "${tmpfile%%.crl}.err"
fi fi
if [[ $? -eq 0 ]]; then if [[ $? -eq 0 ]]; then
out ", " out ", "
@ -1797,7 +1798,7 @@ check_revocation_ocsp() {
asciihex_to_binary "$stapled_response" > "$TEMPDIR/stapled_ocsp_response.dd" asciihex_to_binary "$stapled_response" > "$TEMPDIR/stapled_ocsp_response.dd"
$OPENSSL ocsp -no_nonce -respin "$TEMPDIR/stapled_ocsp_response.dd" \ $OPENSSL ocsp -no_nonce -respin "$TEMPDIR/stapled_ocsp_response.dd" \
-issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \
-CAfile <(cat $ADDITIONAL_CA_FILES "$GOOD_CA_BUNDLE") -cert $HOSTCERT -text &> "$tmpfile" -CAfile <(cat $ADDTL_CA_FILES "$GOOD_CA_BUNDLE") -cert $HOSTCERT -text &> "$tmpfile"
else else
host_header=${uri##http://} host_header=${uri##http://}
host_header=${host_header%%/*} host_header=${host_header%%/*}
@ -1811,7 +1812,7 @@ check_revocation_ocsp() {
fi fi
$OPENSSL ocsp -no_nonce ${host_header} -url "$uri" \ $OPENSSL ocsp -no_nonce ${host_header} -url "$uri" \
-issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \
-CAfile <(cat $ADDITIONAL_CA_FILES "$GOOD_CA_BUNDLE") -cert $HOSTCERT -text &> "$tmpfile" -CAfile <(cat $ADDTL_CA_FILES "$GOOD_CA_BUNDLE") -cert $HOSTCERT -text &> "$tmpfile"
fi fi
if [[ $? -eq 0 ]] && grep -Fq "Response verify OK" "$tmpfile"; then if [[ $? -eq 0 ]] && grep -Fq "Response verify OK" "$tmpfile"; then
response="$(grep -F "$HOSTCERT: " "$tmpfile")" response="$(grep -F "$HOSTCERT: " "$tmpfile")"
@ -6971,9 +6972,9 @@ determine_trust() {
# in a subshell because that should be valid here only # in a subshell because that should be valid here only
(export SSL_CERT_DIR="/dev/null"; export SSL_CERT_FILE="/dev/null" (export SSL_CERT_DIR="/dev/null"; export SSL_CERT_FILE="/dev/null"
if [[ $certificates_provided -ge 2 ]]; then if [[ $certificates_provided -ge 2 ]]; then
$OPENSSL verify -purpose sslserver -CAfile <(cat $ADDITIONAL_CA_FILES "$bundle_fname") -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2 $OPENSSL verify -purpose sslserver -CAfile <(cat $ADDTL_CA_FILES "$bundle_fname") -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2
else else
$OPENSSL verify -purpose sslserver -CAfile <(cat $ADDITIONAL_CA_FILES "$bundle_fname") $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2 $OPENSSL verify -purpose sslserver -CAfile <(cat $ADDTL_CA_FILES "$bundle_fname") $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2
fi) fi)
verify_retcode[i]=$(awk '/error [1-9][0-9]? at [0-9]+ depth lookup:/ { if (!found) {print $2; found=1} }' $TEMPDIR/${certificate_file[i]}.1 $TEMPDIR/${certificate_file[i]}.2) verify_retcode[i]=$(awk '/error [1-9][0-9]? at [0-9]+ depth lookup:/ { if (!found) {print $2; found=1} }' $TEMPDIR/${certificate_file[i]}.1 $TEMPDIR/${certificate_file[i]}.2)
[[ -z "${verify_retcode[i]}" ]] && verify_retcode[i]=0 [[ -z "${verify_retcode[i]}" ]] && verify_retcode[i]=0
@ -19570,7 +19571,7 @@ parse_cmd_line() {
do_grease=true do_grease=true
;; ;;
--add-ca|--add-CA|--add-ca=*|--add-CA=*) --add-ca|--add-CA|--add-ca=*|--add-CA=*)
ADDITIONAL_CA_FILES="$(parse_opt_equal_sign "$1" "$2")" ADDTL_CA_FILES="$(parse_opt_equal_sign "$1" "$2")"
[[ $? -eq 0 ]] && shift [[ $? -eq 0 ]] && shift
;; ;;
--devel) ### this development feature will soon disappear --devel) ### this development feature will soon disappear
@ -19876,8 +19877,8 @@ parse_cmd_line() {
"$do_mx_all_ips" && [[ "$NODNS" == none ]] && fatal "\"--mx\" and \"--nodns=none\" don't work together" $ERR_CMDLINE "$do_mx_all_ips" && [[ "$NODNS" == none ]] && fatal "\"--mx\" and \"--nodns=none\" don't work together" $ERR_CMDLINE
[[ -n "$CONNECT_TIMEOUT" ]] && [[ "$MASS_TESTING_MODE" == parallel ]] && fatal "Parallel mass scanning and specifying connect timeouts currently don't work together" $ERR_CMDLINE [[ -n "$CONNECT_TIMEOUT" ]] && [[ "$MASS_TESTING_MODE" == parallel ]] && fatal "Parallel mass scanning and specifying connect timeouts currently don't work together" $ERR_CMDLINE
ADDITIONAL_CA_FILES="${ADDITIONAL_CA_FILES//,/ }" ADDTL_CA_FILES="${ADDTL_CA_FILES//,/ }"
for fname in $ADDITIONAL_CA_FILES; do for fname in $ADDTL_CA_FILES; do
[[ -s "$fname" ]] || fatal "CA file \"$fname\" does not exist" $ERR_RESOURCE [[ -s "$fname" ]] || fatal "CA file \"$fname\" does not exist" $ERR_RESOURCE
grep -q "BEGIN CERTIFICATE" "$fname" || fatal "\"$fname\" is not CA file in PEM format" $ERR_RESOURCE grep -q "BEGIN CERTIFICATE" "$fname" || fatal "\"$fname\" is not CA file in PEM format" $ERR_RESOURCE
done done