2
0
mirror of https://github.com/mozilla/cipherscan.git synced 2024-12-26 12:43:42 +01:00
cipherscan/cipherscan
Hubert Kario 4d77c87494 properly detect ECDSA certs for keysize compare
since ECDSA certificates during the transition are likely to be
signed using RSA keys, we need to check the cipher rather than the
signature in the certificate to tell if the cert is ECDSA and as such
can have small key sizes
2015-11-17 15:31:46 +01:00

2138 lines
73 KiB
Bash
Executable File

#!/usr/bin/env bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Authors: Julien Vehent [:ulfr] - 201{3,4}
# Hubert Kario - 2014, 2015
# vim: autoindent tabstop=4 shiftwidth=4 expandtab softtabstop=4 filetype=sh
DOBENCHMARK=0
BENCHMARKITER=30
# cipherscan requires bash4, which doesn't come by default in OSX
if [[ ${BASH_VERSINFO[0]} -lt 4 ]]; then
echo "Bash version 4 is required to run cipherscan." 1>&2
echo "Please upgrade your version of bash (ex: brew install bash)." 1>&2
exit 1
fi
if [[ -n $NOAUTODETECT ]]; then
if ! [[ -f $TIMEOUTBIN && -x $TIMEOUTBIN ]]; then
echo "NOAUTODETECT set, but TIMEOUTBIN is not an executable file" 1>&2
exit 1
fi
if ! [[ -f $OPENSSLBIN && -x $OPENSSLBIN ]]; then
echo "NOAUTODETECT set, but OPENSSLBIN is not an executable file" 1>&2
exit 1
fi
else
case "$(uname -s)" in
Darwin)
opensslbin_name="openssl-darwin64"
READLINKBIN=$(which greadlink 2>/dev/null)
if [[ -z $READLINKBIN ]]; then
echo "greadlink not found. (try: brew install coreutils)" 1>&2
exit 1
fi
TIMEOUTBIN=$(which gtimeout 2>/dev/null)
if [[ -z $TIMEOUTBIN ]]; then
echo "gtimeout not found. (try: brew install coreutils)" 1>&2
exit 1
fi
;;
*)
opensslbin_name="openssl"
# test that readlink or greadlink (darwin) are present
READLINKBIN="$(which readlink)"
if [[ -z $READLINKBIN ]]; then
READLINKBIN="$(which greadlink)"
if [[ -z $READLINKBIN ]]; then
echo "neither readlink nor greadlink are present. install coreutils with {apt-get,yum,brew} install coreutils" 1>&2
exit 1
fi
fi
# test that timeout or gtimeout (darwin) are present
TIMEOUTBIN="$(which timeout)"
if [[ -z $TIMEOUTBIN ]]; then
TIMEOUTBIN="$(which gtimeout)"
if [[ -z $TIMEOUTBIN ]]; then
echo "neither timeout nor gtimeout are present. install coreutils with {apt-get,yum,brew} install coreutils" 1>&2
exit 1
fi
fi
# Check for busybox, which has different arguments
TIMEOUTOUTPUT="$($TIMEOUTBIN --help 2>&1)"
if [[ "$TIMEOUTOUTPUT" =~ BusyBox ]]; then
TIMEOUTBIN="$TIMEOUTBIN -t"
fi
;;
esac
fi
DIRNAMEPATH=$(dirname "$0")
join_array_by_char() {
# Two or less parameters (join + 0 or 1 value), then no need to set IFS because no join occurs.
if (( $# >= 3 )); then
# Three or more parameters (join + 2 values), then we need to set IFS for the join.
local IFS=$1
fi
# Discard the join string (usually ':', could be others).
shift
# Store the joined string in the result.
joined_array="$*"
}
# RSA ciphers are put at the end to force Google servers to accept ECDSA ciphers
# (probably a result of a workaround for the bug in Apple implementation of ECDSA)
CIPHERSUITE="ALL:COMPLEMENTOFALL:+aRSA"
# some servers are intolerant to large client hello, try a shorter list of
# ciphers with them
SHORTCIPHERSUITE=(
'ECDHE-ECDSA-AES128-GCM-SHA256'
'ECDHE-RSA-AES128-GCM-SHA256'
'ECDHE-RSA-AES256-GCM-SHA384'
'ECDHE-ECDSA-AES256-SHA'
'ECDHE-ECDSA-AES128-SHA'
'ECDHE-RSA-AES128-SHA'
'ECDHE-RSA-AES256-SHA'
'ECDHE-RSA-DES-CBC3-SHA'
'ECDHE-ECDSA-RC4-SHA'
'ECDHE-RSA-RC4-SHA'
'DHE-RSA-AES128-SHA'
'DHE-DSS-AES128-SHA'
'DHE-RSA-CAMELLIA128-SHA'
'DHE-RSA-AES256-SHA'
'DHE-DSS-AES256-SHA'
'DHE-RSA-CAMELLIA256-SHA'
'EDH-RSA-DES-CBC3-SHA'
'AES128-SHA'
'CAMELLIA128-SHA'
'AES256-SHA'
'CAMELLIA256-SHA'
'DES-CBC3-SHA'
'RC4-SHA'
'RC4-MD5'
)
join_array_by_char ':' "${SHORTCIPHERSUITE[@]}"
SHORTCIPHERSUITESTRING="$joined_array"
# as some servers are intolerant to large client hello's (or ones that have
# RC4 ciphers below position 64), use the following for cipher testing in case
# of problems
FALLBACKCIPHERSUITE=(
'ECDHE-RSA-AES128-GCM-SHA256'
'ECDHE-RSA-AES128-SHA256'
'ECDHE-RSA-AES128-SHA'
'ECDHE-RSA-DES-CBC3-SHA'
'ECDHE-RSA-RC4-SHA'
'DHE-RSA-AES128-SHA'
'DHE-DSS-AES128-SHA'
'DHE-RSA-CAMELLIA128-SHA'
'DHE-RSA-AES256-SHA'
'DHE-DSS-AES256-SHA'
'DHE-RSA-CAMELLIA256-SHA'
'EDH-RSA-DES-CBC3-SHA'
'AES128-SHA'
'CAMELLIA128-SHA'
'AES256-SHA'
'CAMELLIA256-SHA'
'DES-CBC3-SHA'
'RC4-SHA'
'RC4-MD5'
'SEED-SHA'
'IDEA-CBC-SHA'
'IDEA-CBC-MD5'
'RC2-CBC-MD5'
'DES-CBC3-MD5'
'EXP1024-DHE-DSS-DES-CBC-SHA'
'EDH-RSA-DES-CBC-SHA'
'EXP1024-DES-CBC-SHA'
'DES-CBC-MD5'
'EXP1024-RC4-SHA'
'EXP-EDH-RSA-DES-CBC-SHA'
'EXP-DES-CBC-SHA'
'EXP-RC2-CBC-MD5'
'EXP-RC4-MD5'
)
join_array_by_char ':' "${FALLBACKCIPHERSUITE[@]}"
FALLBACKCIPHERSUITESTRING="$joined_array"
DEBUG=0
VERBOSE=0
DELAY=0
ALLCIPHERS=""
OUTPUTFORMAT="terminal"
TIMEOUT=30
USECOLORS="auto"
# place where to put the found intermediate CA certificates and where
# trust anchors are stored
SAVECRT=""
TEST_CURVES="True"
has_curves="False"
TEST_TOLERANCE="True"
SNI="True"
# openssl formated list of curves that will cause server to select ECC suite
ecc_ciphers=""
TEST_KEX_SIGALG="False"
unset known_certs
declare -A known_certs
unset cert_checksums
declare -A cert_checksums
# array with results of tolerance scans (TLS version, extensions, etc.)
declare -A tls_tolerance
# array with info on type of fallback on unknown sigalgs (or required ones)
declare -A sigalgs_fallback
# array with preferred sigalgs for aRSA and aECDSA ciphers
declare -a sigalgs_preferred_rsa
declare -a sigalgs_preferred_ecdsa
renegotiation=""
compression=""
# because running external commands like sleep incurs a fork penalty, we
# first check if it is necessary
ratelimit() {
if [[ $DELAY != "0" ]]; then
sleep $DELAY
fi
}
usage() {
echo -e "usage: $0 [-a|--allciphers] [-b|--benchmark] [--capath directory]
[--saveca] [--savecrt directory] [-d|--delay seconds] [-D|--debug] [-j|--json]
[-v|--verbose] [-o|--openssl file] [openssl s_client args] <target:port>
usage: $0 -h|--help
$0 attempts to connect to a target site using all the ciphersuites known
to OpenSSL it is using.
Julien Vehent [:ulfr] and others (see README.md)
https://github.com/jvehent/cipherscan
Port defaults to 443
example: $ $0 www.google.com
Use one of the options below:
-a | --allciphers Test all known ciphers individually at the end.
-b | --benchmark Activate benchmark mode.
--capath use CAs from directory (must be in OpenSSL CAdir format)
--saveca save intermediate certificates in CA directory
-d | --delay Pause for n seconds between connections
-D | --debug Output ALL the information.
-h | --help Shows this help text.
-j | --json Output results in JSON format.
-o | --openssl path/to/your/openssl binary you want to use.
--savecrt path where to save untrusted and leaf certificates
--[no-]curves test ECC curves supported by server (req. OpenSSL 1.0.2)
--sigalg test signature algorithms used in TLSv1.2 ephemeral ciphers
(req. OpenSSL 1.0.2)
--[no-]tolerance test TLS tolerance
--no-sni don't use Server Name Indication
--colors force use of colors (autodetect by default)
--no-colors don't use terminal colors
-v | --verbose Increase verbosity.
The rest of the arguments will be interpreted as openssl s_client argument.
Some useful OpenSSL options:
-starttls [smtp|imap|pop3|ftp|xmpp] Enable support and testing of the protocols
that require turning TLS after initial protocol specific
hello
-servername name Request SNI support for connections
-verify_hostname name Request host name verification in connection
(req. OpenSSL 1.0.2)
-verify_ip ip Request host name verification for an IP address, usually
not specified in certificates (req. OpenSSL 1.0.2)
EXAMPLES:
$0 -starttls xmpp jabber.ccc.de:5222
$0 -servername youtube.com youtube.com:443
"
}
verbose() {
if [[ $VERBOSE != 0 ]]; then
echo "$@" >&2
fi
}
debug(){
if [[ $DEBUG == 1 ]]; then
echo Debug: "$@" >&2
set -evx
fi
}
# obtain an array of curves supported by openssl
CURVES=(
'sect163k1' # K-163
'sect163r1'
'sect163r2' # B-163
'sect193r1'
'sect193r2'
'sect233k1' # K-233
'sect233r1' # B-233
'sect239k1'
'sect283k1' # K-283
'sect283r1' # B-283
'sect409k1' # K-409
'sect409r1' # B-409
'sect571k1' # K-571
'sect571r1' # B-571
'secp160k1'
'secp160r1'
'secp160r2'
'secp192k1'
'prime192v1' # P-192 secp192r1
'secp224k1'
'secp224r1' # P-224
'secp256k1'
'prime256v1' # P-256 secp256r1
'secp384r1' # P-384
'secp521r1' # P-521
'brainpoolP256r1'
'brainpoolP384r1'
'brainpoolP512r1'
)
# many curves have alternative names, this array provides a mapping to find the IANA
# name of a curve using its alias
CURVES_MAP=(
'sect163k1 K-163'
'sect163r2 B-163'
'sect233k1 K-233'
'sect233r1 B-233'
'sect283k1 K-283'
'sect283r1 B-283'
'sect409k1 K-409'
'sect409r1 B-409'
'sect571k1 K-571'
'sect571r1 B-571'
'prime192v1 P-192 secp192r1'
'secp224r1 P-224'
'prime256v1 P-256 secp256r1'
'secp384r1 P-384'
'secp521r1 P-521'
)
get_curve_name() {
local identifier=$1
for c in "${CURVES_MAP[@]}"; do
if [[ "$c" =~ $identifier ]]; then
verbose "$c matches identifier $identifier"
echo "${c%% *}"
return
fi
done
echo "$identifier"
return
}
c_hash() {
local h=$(${OPENSSLBIN} x509 -hash -noout -in "$1/$2" 2>/dev/null)
for ((num=0; num<=100; num++)) ; do
if [[ $1/${h}.${num} -ef $2 ]]; then
# file already linked, ignore
break
fi
if [[ ! -e $1/${h}.${num} ]]; then
# file doesn't exist, create a link
if pushd "$1" > /dev/null; then
ln -s "$2" "${h}.${num}"
else
echo "'pushd $1' failed unexpectedly, refusing to proceed" 1>&2
exit 1
fi
popd > /dev/null
break
fi
done
}
check_option_support() {
[[ $OPENSSLBINHELP =~ "$1" ]]
}
parse_openssl_output() {
# clear variables in case matching doesn't hit them
current_ocspstaple="False"
current_cipher=""
current_kex_sigalg=""
current_pfs=""
current_protocol=""
current_tickethint="None"
current_pubkey=0
current_trusted="False"
current_sigalg="None"
current_renegotiation="False"
current_compression=""
certs_found=0
current_raw_certificates=()
while read line; do
# check if there isn't OCSP response data (response and responder cert)
if [[ $line =~ ^====================================== ]]; then
while read data; do
# check if there is a OCSP response in output
if [[ $data =~ OCSP\ Response\ Data ]]; then
current_ocspstaple="True"
continue
fi
# skip all data from a OCSP response
if [[ $data =~ ^====================================== ]]; then
break
fi
done
continue
fi
# extract selected cipher
if [[ $line =~ New,\ ]]; then
local match=($line)
current_cipher="${match[4]}"
continue
fi
# renegotiation support
if [[ $line =~ Secure\ Renegotiation\ IS\ supported ]]; then
current_renegotiation="secure"
fi
if [[ $line =~ Secure\ Renegotiation\ IS\ NOT\ supported ]]; then
current_renegotiation="insecure"
fi
# compression settings
if [[ $line =~ Compression:\ (.*) ]]; then
current_compression="${BASH_REMATCH[1]}"
fi
# extract the signing algorithm used in TLSv1.2 ephemeral kex
if [[ $line =~ Peer\ signing\ digest ]]; then
local match=($line)
current_kex_sigalg="${match[3]}"
continue
fi
# extract data about selected temporary key
if [[ $line =~ Server\ Temp\ Key ]]; then
local match=($line)
current_pfs="${match[3]}${match[4]}${match[5]}${match[6]}"
continue
fi
# extract used protocol
if [[ $line =~ ^Protocol\ + ]]; then
local match=($line)
current_protocol="${match[2]}"
continue
fi
# extract session ticket hint
if [[ $line =~ ticket\ lifetime\ hint ]]; then
local match=($line)
current_tickethint="${match[5]}"
continue
fi
# extract size of server public key
if [[ $line =~ Server\ public\ key\ is\ ]]; then
local match=($line)
current_pubkey="${match[4]}"
continue
fi
# check if connection used trused certificate
if [[ $line =~ Verify\ return\ code:\ 0 ]]; then
current_trusted="True"
continue
fi
# extract certificates
if [[ $line =~ -----BEGIN\ CERTIFICATE----- ]]; then
current_raw_certificates[$certs_found]="$line"$'\n'
while read data; do
current_raw_certificates[$certs_found]+="$data"$'\n'
if [[ $data =~ -----END\ CERTIFICATE----- ]]; then
break
fi
done
certs_found=$((certs_found+1))
continue
fi
done
# if we found any certs in output, process the first one and extract
# the signature algorithm on it (it's the server's certificate)
if (( certs_found > 0 )); then
local ossl_out=$(${OPENSSLBIN} x509 -noout -text 2>/dev/null <<<"${current_raw_certificates[0]}")
local regex='Signature Algorithm[^ ]+ +(.+$)'
while read data; do
if [[ $data =~ $regex ]]; then
current_sigalg="${BASH_REMATCH[1]// /_}"
fi
done <<<"$ossl_out"
fi
}
# Connect to a target host with the selected ciphersuite
test_cipher_on_target() {
local sslcommand="$*"
cipher=""
local cmnd=""
protocols=""
pfs=""
previous_cipher=""
certificates=""
for tls_version in "-ssl2" "-ssl3" "-tls1" "-tls1_1" "-tls1_2"
do
# sslv2 client hello doesn't support SNI extension
# in SSLv3 mode OpenSSL just ignores the setting so it's ok
# -status exception is ignored in SSLv2, go figure
if [[ "$tls_version" == "-ssl2" ]]; then
if [[ "$sslcommand" =~ (.*)(-servername\ [^ ]*)(.*) ]]; then
cmnd="${BASH_REMATCH[1]} ${BASH_REMATCH[3]}"
else
cmnd="$sslcommand"
fi
else
cmnd=$sslcommand
fi
ratelimit
debug echo \"Q\" \| $cmnd $tls_version
local tmp=$(echo "Q" | $cmnd $tls_version 1>/dev/stdout 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "selected cipher is '$current_cipher'"
verbose "using protocol '$current_protocol'"
# collect certificate data
current_certificates=""
local certificate_count=$certs_found
debug "server presented $certificate_count certificates"
local i
for ((i=0; i<certificate_count; i=i+1 )); do
# extract i'th certificate
local cert="${current_raw_certificates[$i]}"
# put the output to an array instead running awk '{print $1}'
local cksum=($(cksum <<<"$cert"))
# compare the values not just checksums so that eventual collision
# doesn't mess up results
if [[ ${known_certs[$cksum]} == "$cert" ]]; then
if [[ -n "${current_certificates}" ]]; then
current_certificates+=","
fi
current_certificates+="\"${cert_checksums[$cksum]}\""
continue
fi
# compute sha256 fingerprint of the certificate
local sha256sum=($(${OPENSSLBIN} x509 -outform DER\
<<<"$cert" 2>/dev/null |\
${OPENSSLBIN} dgst -sha256 -r 2>/dev/null))
# check if it is a CA certificate
local isCA="False"
if ${OPENSSLBIN} x509 -noout -text <<<"$cert" 2>/dev/null |\
grep 'CA:TRUE' >/dev/null; then
isCA="True"
fi
# build trust source for certificate verification
local trust_source=()
if [[ -n $CAPATH ]]; then
trust_source=("-CApath" "$CAPATH")
elif [[ -e $CACERTS ]]; then
trust_source=("-CAfile" "$CACERTS")
fi
# check if the certificate is actually trusted (server may present
# unrelated certificates that are not trusted (including self
# signed ones)
local saved="False"
if ${OPENSSLBIN} verify "${trust_source[@]}" \
-untrusted <(printf "%s" "${current_raw_certificates[@]}") <(echo "$cert") 2>/dev/null | \
grep ': OK$' >/dev/null; then
# if the certificate is an intermediate CA it may be useful
# for connecting to servers that are misconfigured so save it
if [[ -n $CAPATH ]] && [[ $SAVECA == "True" ]] && [[ $isCA == "True" ]]; then
if [[ ! -e "$CAPATH/${sha256sum}.pem" ]]; then
echo "$cert" > "$CAPATH/${sha256sum}.pem"
c_hash "$CAPATH" "${sha256sum}.pem"
fi
saved="True"
fi
fi
if [[ -n $SAVECRT ]] && [[ $saved == "False" ]]; then
if [[ ! -e $SAVECRT/${sha256sum}.pem ]]; then
echo "$cert" > "$SAVECRT/${sha256sum}.pem"
fi
fi
# save the sha sum for reporting
if [[ -n "${current_certificates}" ]]; then
current_certificates+=","
fi
current_certificates+="\"${sha256sum}\""
known_certs[$cksum]="$cert"
cert_checksums[$cksum]="$sha256sum"
done
debug "current_certificates: $current_certificates"
# parsing finished, report result
if [[ -z "$current_protocol" || "$current_cipher" == '(NONE)' ]]; then
# connection failed, try again with next TLS version
continue
else
verbose "connection successful; protocol: $current_protocol, cipher: $current_cipher, previous cipher: $previous_cipher"
fi
# handling of TLSv1.2 only cipher suites
if [[ ! -z "$previous_cipher" ]] && [[ "$previous_cipher" != "$current_cipher" ]] && [[ "$current_cipher" != "0000" ]]; then
unset protocols
fi
previous_cipher=$current_cipher
# connection succeeded, add TLS version to positive results
if [[ -z "$protocols" ]]; then
protocols=$current_protocol
else
protocols="$protocols,$current_protocol"
fi
cipher=$current_cipher
pfs=$current_pfs
[[ -z $pfs ]] && pfs="None"
pubkey=$current_pubkey
sigalg=$current_sigalg
trusted=$current_trusted
tickethint=$current_tickethint
ocspstaple=$current_ocspstaple
certificates="$current_certificates"
# grab the cipher and PFS key size
done
# if cipher is empty, that means none of the TLS version worked with
# the current cipher
if [[ -z "$cipher" ]]; then
verbose "handshake failed, no ciphersuite was returned"
result='ConnectionFailure'
return 2
# if cipher contains NONE, the cipher wasn't accepted
elif [[ "$cipher" == '(NONE) ' ]]; then
result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering"
verbose "handshake failed, server returned ciphersuite '$result'"
return 1
# the connection succeeded
else
current_curves="None"
# if pfs uses ECDH, test supported curves
if [[ $pfs =~ ECDH ]]; then
has_curves="True"
if [[ $TEST_CURVES == "True" ]]; then
test_curves
if [[ -n $ecc_ciphers ]]; then
ecc_ciphers+=":"
fi
ecc_ciphers+="$cipher"
else
# resolve the openssl curve to the proper IANA name
current_curves="$(get_curve_name "$(echo $pfs|cut -d ',' -f2)")"
fi
fi
result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering"
verbose "handshake succeeded, server returned ciphersuite '$result'"
return 0
fi
}
# Calculate the average handshake time for a specific ciphersuite
bench_cipher() {
local ciphersuite="$1"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client $SCLIENTARGS -connect $TARGET -cipher $ciphersuite"
local t="$(date +%s%N)"
verbose "Benchmarking handshake on '$TARGET' with ciphersuite '$ciphersuite'"
for i in $(seq 1 $BENCHMARKITER); do
debug "Connection $i"
(echo "Q" | $sslcommand 2>/dev/null 1>/dev/null)
if (( $? != 0 )); then
break
fi
done
# Time interval in nanoseconds
local t="$(($(date +%s%N) - t))"
verbose "Benchmarking done in $t nanoseconds"
# Microseconds
cipherbenchms="$((t/1000/BENCHMARKITER))"
}
# Connect to the target and retrieve the chosen cipher
# recursively until the connection fails
get_cipher_pref() {
[[ "$OUTPUTFORMAT" == "terminal" ]] && [[ $DEBUG -lt 1 ]] && echo -n '.'
local ciphersuite="$1"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [[ -n "$CAPATH" ]]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [[ -e $CACERTS ]]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" -status $SCLIENTARGS -connect $TARGET -cipher $ciphersuite"
verbose "Connecting to '$TARGET' with ciphersuite '$ciphersuite'"
# If the connection succeeded with the current cipher, benchmark and store
if test_cipher_on_target "$sslcommand"; then
cipherspref=("${cipherspref[@]}" "$result")
ciphercertificates=("${ciphercertificates[@]}" "$certificates")
pciph=($result)
get_cipher_pref "!$pciph:$ciphersuite"
return 0
fi
}
display_sigalgs_in_terminal() {
(echo "prio sigalg"
for sigalg in "$@"; do
if [[ $sigalg == "MD5" ]]; then
color="${c_red}"
elif [[ $sigalg == "SHA1" ]]; then
color="${c_yellow}"
else
color="${c_green}"
fi
echo -e "$cnt ${color}$sigalg${c_reset}"
cnt=$((cnt+1))
done )| column -t
}
display_results_in_terminal() {
# Display the results
ctr=1
local pubkey
local sigalg
local trusted
local tickethint
local ocspstaple
local curvesordering
local different=False
# Configure colors, if terminal supports them
if [[ $USECOLORS == "auto" ]]; then
if [[ -t 1 ]]; then
USECOLORS="True"
else
USECOLORS="False"
fi
fi
if [[ $USECOLORS == "True" ]]; then
c_blue="\033[0;34m"
c_green="\033[0;32m"
c_yellow="\033[0;33m"
c_red="\033[0;31m"
c_reset="\033[0m"
else
c_blue=
c_green=
c_yellow=
c_red=
c_reset=
fi
echo "Target: $TARGET"; echo
for cipher in "${cipherspref[@]}"; do
# get first in array
pciph=($cipher)
if [[ $DOBENCHMARK -eq 1 ]]; then
bench_cipher "$pciph"
r="$ctr $cipher $cipherbenchms"
else
r="$ctr $cipher"
fi
local cipher_data=($cipher)
if [[ $ctr -eq 1 ]]; then
cipher="${cipher_data[1]}"
pubkey="${cipher_data[2]}"
sigalg="${cipher_data[3]}"
trusted="${cipher_data[4]}"
tickethint="${cipher_data[5]}"
ocspstaple="${cipher_data[6]}"
if [[ $TEST_CURVES == "True" && -n ${cipher_data[9]} ]]; then
curvesordering="${cipher_data[9]}"
fi
else
if [[ "$pubkey" != "${cipher_data[2]}" ]]; then
different=True
fi
if [[ "$sigalg" != "${cipher_data[3]}" ]]; then
different=True
fi
if [[ "$trusted" != "${cipher_data[4]}" ]]; then
different=True
fi
if [[ "$tickethint" != "${cipher_data[5]}" ]]; then
different=True
fi
if [[ "$ocspstaple" != "${cipher_data[6]}" ]]; then
different=True
fi
if [[ -z $curvesordering && -n "${cipher_data[9]}" ]]; then
curvesordering="${cipher_data[9]}"
fi
if [[ -n $curvesordering && "$curvesordering" != "${cipher_data[9]}" ]]; then
different=True
fi
fi
results=("${results[@]}" "$r")
ctr=$((ctr+1))
done
header="prio ciphersuite protocols"
if [[ $different == "True" ]]; then
header+=" pubkey_size signature_algoritm trusted ticket_hint ocsp_staple"
fi
header+=" pfs"
if [[ $has_curves == "True" ]]; then
header+=" curves"
if [[ $TEST_CURVES == "True" && $different == "True" ]]; then
header+=" curves_ordering"
fi
fi
if [[ $DOBENCHMARK -eq 1 ]]; then
header+=" avg_handshake_microsec"
fi
ctr=0
for result in "${results[@]}"; do
if [[ $ctr -eq 0 ]]; then
echo "$header"
ctr=$((ctr+1))
fi
if [[ $different == "True" ]]; then
echo "$result"|grep -v '(NONE)'
else
# prints priority, ciphersuite, protocols and pfs
awk '!/(NONE)/{print $1 " " $2 " " $3 " " $9 " " $10}' <<<"$result"
fi
done|column -t
echo
if [[ ($sigalg =~ RSA && $pubkey -ge 2047) || ($cipher =~ ECDSA && $pubkey -gt 255) ]]; then
pubkey="${c_green}${pubkey}${c_reset}"
else
pubkey="${c_red}${pubkey}${c_reset}"
fi
if [[ $sigalg =~ md5|sha1 ]]; then
sigalg="${c_red}${sigalg}${c_reset}"
else
sigalg="${c_green}${sigalg}${c_reset}"
fi
if [[ $trusted == "True" ]]; then
trusted="${c_green}trusted${c_reset}"
else
trusted="${c_red}untrusted${c_reset}"
fi
if [[ $different != "True" ]]; then
echo -e "Certificate: $trusted, $pubkey bits, $sigalg signature"
echo "TLS ticket lifetime hint: $tickethint"
fi
if [[ $ocspstaple == "True" ]]; then
echo -e "OCSP stapling: ${c_green}supported${c_reset}"
else
echo -e "OCSP stapling: ${c_red}not supported${c_reset}"
fi
if [[ $serverside == "True" ]]; then
echo -e "Cipher ordering: ${c_green}server${c_reset}"
else
echo -e "Cipher ordering: ${c_red}client${c_reset}"
fi
if [[ $TEST_CURVES == "True" ]]; then
if [[ $curvesordering == "server" ]]; then
curvesordering="${c_green}${curvesordering}${c_reset}"
else
if [[ $curvesordering == "" ]]; then
curvesordering="none"
fi
curvesordering="${c_red}${curvesordering}${c_reset}"
fi
if [[ $fallback_supported == "True" ]]; then
fallback_supported="${c_green}yes${c_reset}"
else
fallback_supported="${c_red}no${c_reset}"
fi
echo -e "Curves ordering: $curvesordering - fallback: $fallback_supported"
fi
if [[ $renegotiation ]]; then
if [[ $renegotiation == "secure" ]]; then
echo -e "Server ${c_green}supports${c_reset} secure renegotiation"
else
echo -e "Server ${c_red}DOESN'T${c_reset} support secure renegotiation"
fi
else
echo "Renegotiation test error"
fi
if [[ $compression ]]; then
if [[ $compression != "NONE" ]]; then
color="${c_red}"
else
color="${c_green}"
fi
echo -e "Server supported compression methods:" \
"${color}$compression${c_reset}"
else
echo -e "Supported compression methods ${c_red}test error${c_reset}"
fi
if [[ $TEST_KEX_SIGALG == "True" ]]; then
echo
echo "TLSv1.2 ephemeral sigalgs:"
for auth in "ECDSA" "RSA"; do
# not colored as neither of that results alone is good or bad
if [[ -z ${sigalgs_fallback[$auth]} ]]; then
echo "no PFS $auth ciphers detected"
elif [[ ${sigalgs_fallback[$auth]} == "False" ]]; then
echo "no PFS $auth fallback"
elif [[ ${sigalgs_fallback[$auth]} == "intolerant" ]]; then
echo "$auth test: intolerant of sigalg removal"
elif [[ ${sigalgs_fallback[$auth]} =~ "pfs-" ]]; then
echo "PFS $auth fallbacks to ${sigalgs_fallback[$auth]}"
else
echo "server forces ${sigalgs_fallback[$auth]} with $auth"
fi
done
if [[ ${sigalgs_ordering} == "server" ]]; then
echo -e "${c_green}Server side sigalg ordering${c_reset}"
elif [[ ${sigalgs_ordering} == "client" ]]; then
echo -e "${c_red}Client side sigalg ordering${c_reset}"
elif [[ ${sigalgs_ordering} == "unsupported" ]]; then
# do nothing - messages above will report that it's unsupported
echo -n
else
echo "Ordering test failure: ${sigalgs_ordering}"
fi
if [[ ${#sigalgs_preferred_ecdsa[@]} -gt 0 ]]; then
echo
if [[ ${sigalgs_preferred_ecdsa[0]} == "Fail" ]]; then
echo -e "${c_red}ECDSA test failed${c_reset}"
else
local cnt=1
echo "Supported PFS ECDSA signature algorithms"
display_sigalgs_in_terminal "${sigalgs_preferred_ecdsa[@]}"
fi
fi
if [[ ${#sigalgs_preferred_rsa[@]} -gt 0 ]]; then
echo
if [[ ${sigalgs_preferred_rsa[0]} == "Fail" ]]; then
echo -e "${c_red}RSA test failed${c_reset}"
else
local cnt=1
echo "Supported PFS RSA signature algorithms"
display_sigalgs_in_terminal "${sigalgs_preferred_rsa[@]}"
fi
fi
echo
fi
if [[ $TEST_TOLERANCE == "True" ]]; then
if [[ ${tls_tolerance['big-TLSv1.2']} =~ TLSv1\.2 ]]; then
echo -e "TLS Tolerance: ${c_green}yes${c_reset}"
else
echo
echo -e "TLS Tolerance: ${c_red}no${c_reset}"
echo "Fallbacks required:"
for test_name in "${!tls_tolerance[@]}"; do
if [[ ${tls_tolerance[$test_name]} == "False" ]]; then
echo "$test_name config not supported, connection failed"
else
local res=(${tls_tolerance[$test_name]})
echo "$test_name no fallback req, connected: ${res[1]} ${res[2]}"
fi
done | sort
fi
fi
}
display_results_in_json() {
# Display the results in json
ctr=0
echo -n "{\"target\":\"$TARGET\",\"utctimestamp\":\"$(date -u '+%FT%T.0Z')\",\"serverside\":\"${serverside}\",\"ciphersuite\": ["
for cipher in "${cipherspref[@]}"; do
local cipher_arr=($cipher)
(( ctr > 0 )) && echo -n ','
echo -n "{\"cipher\":\"${cipher_arr[0]}\","
echo -n "\"protocols\":[\"${cipher_arr[1]//,/\",\"}\"],"
echo -n "\"pubkey\":[\"${cipher_arr[2]//,/\",\"}\"],"
echo -n "\"sigalg\":[\"${cipher_arr[3]//,/\",\"}\"],"
echo -n "\"trusted\":\"${cipher_arr[4]//,/\",\"}\","
if [[ -n $CAPATH ]]; then
echo -n "\"certificates\":[${ciphercertificates[$ctr]}],"
fi
echo -n "\"ticket_hint\":\"${cipher_arr[5]}\","
echo -n "\"ocsp_stapling\":\"${cipher_arr[6]}\","
pfs="${cipher_arr[7]}"
[[ -z $pfs ]] && pfs="None"
echo -n "\"pfs\":\"$pfs\""
if [[ "${cipher_arr[0]}" =~ ECDH ]]; then
echo -n ","
echo -n "\"curves\":[\"${cipher_arr[8]//,/\",\"}\"]"
if [[ $TEST_CURVES == "True" ]]; then
echo -n ","
echo -n "\"curves_ordering\":\"${cipher_arr[9]}\""
fi
fi
echo -n "}"
ctr=$((ctr+1))
done
echo -n ']'
if [[ $TEST_CURVES == "True" ]]; then
echo -n ",\"curves_fallback\":\"$fallback_supported\""
fi
if [[ $renegotiation ]]; then
echo -n ",\"renegotiation\":\"$renegotiation\""
else
echo -n ",\"renegotiation\":\"False\""
fi
if [[ $compression ]]; then
echo -n ",\"compression\":\"$compression\""
else
echo -n ",\"compression\":\"False\""
fi
if [[ $TEST_KEX_SIGALG == "True" ]]; then
echo -n ',"sigalgs":{'
echo -n "\"ordering\":\"${sigalgs_ordering}\","
echo -n "\"ECDSA-fallback\":\"${sigalgs_fallback[ECDSA]}\","
echo -n "\"RSA-fallback\":\"${sigalgs_fallback[RSA]}\""
if [[ ${#sigalgs_preferred_ecdsa[@]} -gt 0 ]]; then
echo -n ","
echo -n '"ECDSA":['
local cnt=0
for sigalg in "${sigalgs_preferred_ecdsa[@]}"; do
if [[ $cnt -gt 0 ]]; then
echo -n ','
fi
echo -n "\"$sigalg\""
cnt=$((cnt+1))
done
echo -n ']'
fi
if [[ ${#sigalgs_preferred_rsa[@]} -gt 0 ]]; then
echo -n ","
echo -n '"RSA":['
local cnt=0
for sigalg in "${sigalgs_preferred_rsa[@]}"; do
if [[ $cnt -gt 0 ]]; then
echo -n ','
fi
echo -n "\"$sigalg\""
cnt=$((cnt+1))
done
echo -n ']'
fi
echo -n '}'
fi
echo -n ',"configs":{'
ctr=0
for test_name in "${!tls_tolerance[@]}"; do
local result=(${tls_tolerance[$test_name]})
(( ctr > 0 )) && echo -n ","
echo -n "\"$test_name\":{"
if [[ ${result[0]} == "False" ]]; then
echo -n "\"tolerant\":\"False\""
else
echo -n "\"tolerant\":\"True\",\"proto\":\"${result[1]}\","
echo -n "\"cipher\":\"${result[2]}\",\"trusted\":\"${result[3]}\""
fi
echo -n "}"
ctr=$((ctr+1))
done
echo '}}'
}
test_serverside_ordering() {
local -a ciphersuites=()
local ciphersuite=""
local prefered=""
# server supports only one cipher or no ciphers, so it effectively uses server side ordering...
if (( ${#cipherspref[@]} < 2 )); then
serverside="True"
return 0
fi
local cipher=""
if (( ${#cipherspref[@]} > 2 )); then
# server supports 3 or more ciphers, rotate all three. This is necessary because google does
# select first client provided cipher, if it is either CDHE-RSA-AES128-GCM-SHA256 or
# ECDHE-RSA-CHACHA20-POLY1305
ciphersuites+=("${cipherspref[2]%% *}")
fi
# else, server supports just two ciphers, so rotate them, that should be enough
ciphersuites+=("${cipherspref[1]%% *}")
ciphersuites+=("${cipherspref[0]%% *}")
prefered="${ciphersuites[0]%% *}"
join_array_by_char ':' "${ciphersuites[@]}"
ciphersuite="$joined_array"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [[ -n "$CAPATH" ]]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [[ -e "$CACERTS" ]]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" -status $SCLIENTARGS -connect $TARGET -cipher $ciphersuite"
test_cipher_on_target "$sslcommand"
if (( $? != 0 )); then
serverside="True"
else
if [[ ${result%% *} == "$prefered" ]]; then
serverside="False"
else
serverside="True"
fi
fi
}
test_curves() {
# return variable: list of curves supported by server, in order
current_curves=""
# return variable: check if server uses server side or client side ordering
# for curves
curves_ordering="server"
local curves=(${CURVES[*]})
join_array_by_char ':' "${curves[@]}"
verbose "Will test following curves: $joined_array"
# prepare the ssl command we'll be using
local sslcommand=""
sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [[ -n "$CAPATH" ]]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [[ -e "$CACERTS" ]]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" -status $SCLIENTARGS -connect $TARGET -cipher $current_cipher"
# force the TLS to send a TLS1.0 client hello at least, as with SSLv2
# ciphers present it will try to send a SSLv2 compatible client hello
sslcommand+=" -no_ssl2 -no_ssl3"
#
# here we use the same logic as with detecting cipher suites: first
# advertise all curves as supported, then remove curves one by one until we
# either get a fallback to a non ECC cipher, we run of curves or server
# tries to negotiate a curve we didn't advertise
#
while (( ${#curves[@]} > 0 )); do
join_array_by_char ':' "${curves[@]}"
local test_curves="$joined_array"
verbose "Testing $test_curves with command $sslcommand"
ratelimit
local tmp=$(echo Q | $sslcommand -curves "$test_curves" 2>/dev/null)
parse_openssl_output <<<"$tmp"
if [[ -z $current_protocol || $current_cipher == "(NONE)" || $current_cipher == '0000' ]]; then
break
else
# server accepted connection
local ephem_data=(${current_pfs//,/ })
local cname=""
if [[ ${ephem_data[0]} =~ ECDH ]]; then
if [[ -n $current_curves ]]; then
current_curves+=","
fi
cname="$(get_curve_name "${ephem_data[1]}")"
verbose "Server selected ${ephem_data[1]}, a.k.a $cname"
current_curves+="$cname"
fi
for id in "${!curves[@]}"; do
if [[ $cname == "${curves[$id]}" ]]; then
# we know it's supported, remove it from set of offered ones
unset curves[$id]
break
fi
done
fi
[[ "$OUTPUTFORMAT" == "terminal" ]] && [[ $DEBUG -lt 1 ]] && echo -n '.'
done
# don't penalize servers that will negotiate all curves we know of...
if [[ ${#curves[@]} -eq 0 ]]; then
fallback_supported="unknown"
fi
#
# check if curves ordering is server of client side
#
local tmp_curves=(${current_curves//,/ })
verbose "Server supported curves: ${tmp_curves[*]}"
# server supports just one or none, so it effectively uses server side
# ordering (as it dictates what curves client must support)
if (( ${#tmp_curves[@]} < 2 )); then
curves_ordering="server"
else
# server supports at least 2 curves, rotate their order, see if
# selected changes
test_curves=""
most_wanted="${tmp_curves[${#tmp_curves[@]}-1]}"
for (( i=${#tmp_curves[@]}-1; i>0; i--)); do
test_curves+="${tmp_curves[$i]}:"
done
test_curves+="${tmp_curves[0]}"
verbose "Testing ordering with $sslcommand -curves $test_curves"
ratelimit
local tmp=$(echo Q | $sslcommand -curves "$test_curves" 2>/dev/null)
parse_openssl_output <<<"$tmp"
if [[ -z $current_protocol || $current_cipher == "(NONE)" || $current_cipher == '0000' ]]; then
fallback_supported="order-specific"
verbose "Server aborted connection"
else
local ephem_data=(${current_pfs//,/ })
verbose "Server selected $current_cipher with $current_pfs"
verbose "ephem_data: ${ephem_data[*]}"
if [[ ${ephem_data[0]} =~ ECDH ]]; then
verbose "Server did select ${ephem_data[1]} curve"
curves_ordering="inconclusive-${ephem_data[1]}"
local cname="$(get_curve_name "${ephem_data[1]}")"
if [[ "$cname" == "$most_wanted" ]]; then
curves_ordering="client"
else
curves_ordering="server"
fi
else
# some servers downgrade to non ECDH when curve order is changed
curves_ordering="inconclusive-noecc"
fi
fi
fi
}
test_curves_fallback() {
# return variable: whatever a server will fall back to non ECC suite when
# client doesn't advertise support for curves the server needs
fallback_supported="unknown"
if [[ -z $ecc_ciphers ]]; then
verbose "No ECC cipher found, can't test curve fallback"
return
fi
# prepare the ssl command we'll be using
local sslcommand=""
sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [[ -n "$CAPATH" ]]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [[ -e "$CACERTS" ]]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" -status $SCLIENTARGS -connect $TARGET -cipher $ecc_ciphers"
# force the TLS to send a TLS1.0 client hello at least, as with SSLv2
# ciphers present it will try to send a SSLv2 compatible client hello
sslcommand+=" -no_ssl2 -no_ssl3"
#
# here we use the same logic as with detecting cipher suites: first
# advertise all curves as supported, then remove curves one by one until we
# either get a fallback to a non ECC cipher, we run of curves or server
# tries to negotiate a curve we didn't advertise
#
local curves=(${CURVES[*]})
while (( ${#curves[@]} > 0 )); do
join_array_by_char ':' "${curves[@]}"
local test_curves="$joined_array"
verbose "Testing $sslcommand -curves $test_curves"
ratelimit
local tmp=$(echo Q | $sslcommand -curves "$test_curves" 2>/dev/null)
parse_openssl_output <<<"$tmp"
if [[ -z $current_protocol || $current_cipher == "(NONE)" || $current_cipher == '0000' ]]; then
verbose "Curve fallback failed, server refused connection"
fallback_supported="False"
break
else
# server accepted connection
local ephem_data=(${current_pfs//,/ })
if [[ ${ephem_data[0]} =~ ECDH ]]; then
# we got an ecc connection, remove the curve from the list of testable curves
local cname="$(get_curve_name "${ephem_data[1]}")"
verbose "Server selected curve $cname"
for id in "${!curves[@]}"; do
if [[ "${curves[id]}" == "$cname" ]]; then
unset curves[$id]
break
fi
done
else
verbose "Server fell back to $current_cipher"
# ok, we got a fallback
fallback_supported="True"
break
fi
fi
done
}
test_tls_tolerance() {
#
# first test general version tolerance with all we've got (full list of
# curves, full list of ciphers, NPN, ALPN
#
declare -A tls_vers_tests
tls_vers_tests['big-TLSv1.2']=""
tls_vers_tests['big-TLSv1.1']="-no_tls1_2"
tls_vers_tests['big-TLSv1.0']="-no_tls1_2 -no_tls1_1"
tls_vers_tests['big-SSLv3']="-no_tls1_2 -no_tls1_1 -no_tls1"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
sslcommand+=" -status -nextprotoneg 'http/1.1'"
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $CIPHERSUITE"
for version in "${!tls_vers_tests[@]}"; do
ratelimit
verbose "Testing fallback with $sslcommand ${tls_vers_tests[$version]}"
local tmp=$(echo Q | $sslcommand ${tls_vers_tests[$version]} 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance[$version]="False"
else
tls_tolerance[$version]="True $current_protocol $current_cipher $current_trusted"
# collect renegotiation info
if [[ $current_renegotiation != "False" ]]; then
renegotiation="$current_renegotiation"
fi
# collect compression info
if [[ $version == "big-TLSv1.2" || -z $compression ]]; then
compression="$current_compression"
fi
fi
done
# if TLS1.2 didn't succeeded, try different fallbacks
if [[ ${tls_tolerance['big-TLSv1.2']} == "False" ]]; then
#
# Try big client hello, but with a version 2 compatible format
# (openssl automatically does that when there are SSLv2 ciphers in
# cipher string and no options are specified)
#
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [[ -n "$CAPATH" ]]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [[ -e "$CACERTS" ]]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" -connect $TARGET -cipher $CIPHERSUITE"
ratelimit
verbose "Testing fallback with $sslcommand"
local tmp=$(echo Q | $sslcommand 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-big-TLSv1.2']="False"
else
tls_tolerance['v2-big-TLSv1.2']="True $current_protocol $current_cipher $current_trusted"
fi
#
# try a smaller, but still v2 compatible Client Hello
#
local ciphers="$SHORTCIPHERSUITESTRING"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [[ -n "$CAPATH" ]]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [[ -e "$CACERTS" ]]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" -connect $TARGET -cipher $ciphers"
ratelimit
verbose "Testing fallback with $sslcommand"
local tmp=$(echo Q | $sslcommand 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-small-TLSv1.2']="False"
else
tls_tolerance['v2-small-TLSv1.2']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v2, small but with TLS1.1 as max version
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2"
local tmp=$(echo Q | $sslcommand -no_tls1_2 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-small-TLSv1.1']="False"
else
tls_tolerance['v2-small-TLSv1.1']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v2, small but with TLS1.0 as max version
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-small-TLSv1.0']="False"
else
tls_tolerance['v2-small-TLSv1.0']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v2, small but with SSLv3 as max version
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1 -no_tls1"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 -no_tls1 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-small-SSLv3']="False"
else
tls_tolerance['v2-small-SSLv3']="True $current_protocol $current_cipher $current_trusted"
fi
#
# use v3 format TLSv1.2 hello, small cipher list
#
local ciphers="$SHORTCIPHERSUITESTRING"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [[ -n "$CAPATH" ]]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [[ -e "$CACERTS" ]]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $ciphers:!SSLv2"
ratelimit
verbose "Testing fallback with $sslcommand"
local tmp=$(echo Q | $sslcommand 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-TLSv1.2']="False"
else
tls_tolerance['small-TLSv1.2']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v3 format TLSv1.1 hello, small cipher list
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2"
local tmp=$(echo Q | $sslcommand -no_tls1_2 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-TLSv1.1']="False"
else
tls_tolerance['small-TLSv1.1']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v3 format TLSv1.0 hello, small cipher list
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-TLSv1.0']="False"
else
tls_tolerance['small-TLSv1.0']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v3 format TLSv1.0 hello, small cipher list, no extensions
#
if check_option_support "-no_tlsext"; then
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1 -no_tlsext"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 -no_tlsext 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-TLSv1.0-notlsext']="False"
else
tls_tolerance['small-TLSv1.0-notlsext']="True $current_protocol $current_cipher $current_trusted"
fi
fi
#
# v3 format SSLv3 hello, small cipher list
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1 -no_tls1"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 -no_tls1 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-SSLv3']="False"
else
tls_tolerance['small-SSLv3']="True $current_protocol $current_cipher $current_trusted"
fi
fi
}
test_kex_sigalgs() {
local ecdsa_sigalgs=("ECDSA+SHA512" "ECDSA+SHA384" "ECDSA+SHA256"
"ECDSA+SHA224" "ECDSA+SHA1" "ECDSA+MD5")
local rsa_sigalgs=("RSA+SHA512" "RSA+SHA384" "RSA+SHA256"
"RSA+SHA224" "RSA+SHA1" "RSA+MD5")
local supported_rsa_ciphers=""
local supported_ecdsa_ciphers=""
local supported_ciphers=()
# check if TLS1.2 is supported by server, as tests needs it
# collect ciphers
local tls12="False"
for cipher in "${cipherspref[@]}"; do
local ciph_data=($cipher)
if [[ ${ciph_data[1]} =~ TLSv1.2 ]]; then
tls12="True"
fi
supported_ciphers+=(${ciph_data[0]})
done
if [[ $tls12 == "False" ]]; then
return
fi
# create cipher list for ecdsa and rsa tests that include non ephemeral
# ciphers for fallback
for cipher in "${supported_ciphers[@]}"; do
if [[ $cipher =~ DHE-ECDSA ]]; then
if [[ $supported_ecdsa_ciphers ]]; then
supported_ecdsa_ciphers+=":"
fi
supported_ecdsa_ciphers+="$cipher"
elif [[ ${supported_ecdsa_ciphers} ]]; then
supported_ecdsa_ciphers+=":$cipher"
fi
if [[ $cipher =~ DHE-RSA ]]; then
if [[ $supported_rsa_ciphers ]]; then
supported_rsa_ciphers+=":"
fi
supported_rsa_ciphers+="$cipher"
elif [[ ${supported_rsa_ciphers} ]]; then
supported_rsa_ciphers+=":$cipher"
fi
done
#
# Test default sigalgs for aECDSA ciphers
#
if [[ $supported_ecdsa_ciphers ]]; then
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $supported_ecdsa_ciphers"
# since some ciphers supported by server may be SSLv2 only, we need to
# force use of TLSv1.2, otherwise openssl will send a SSLv2 compatible
# client hello
sslcommand+=" -no_ssl2 -no_ssl3"
local test_ecdsa_sigalgs=("${ecdsa_sigalgs[@]}")
local test_rsa_sigalgs=("${rsa_sigalgs[@]}")
while true; do
join_array_by_char ":" "${test_ecdsa_sigalgs[@]}" \
"${test_rsa_sigalgs[@]}"
local test_sigalgs="$joined_array"
ratelimit
verbose "Testing default ECDSA sig algs with $sslcommand -sigalgs $test_sigalgs"
local tmp=$(echo Q | $sslcommand -sigalgs $test_sigalgs 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "server selected $current_cipher, $current_protocol, $current_kex_sigalg"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == "0000" ]]; then
if [[ ${#test_ecdsa_sigalgs[@]} -eq 0 ]]; then
sigalgs_fallback["ECDSA"]="False"
else
sigalgs_fallback["ECDSA"]="intolerant"
fi
break
fi
if [[ $current_cipher =~ DHE-ECDSA ]]; then
if [[ -z $current_kex_sigalg ]]; then
# if we didn't get a sigalg that means the test failed
sigalgs_preferred_ecdsa=("Fail")
break
fi
if [[ "${test_ecdsa_sigalgs[*]}" =~ "ECDSA+$current_kex_sigalg" ]]; then
# save sigalg for reporting
sigalgs_preferred_ecdsa+=("$current_kex_sigalg")
# remove it from offered
local id
for id in "${!test_ecdsa_sigalgs[@]}"; do
if [[ ${test_ecdsa_sigalgs[$id]} =~ $current_kex_sigalg ]]; then
unset test_ecdsa_sigalgs[$id]
break
fi
done
# continue testing
else
# server selected sigalg we didn't offer
sigalgs_fallback["ECDSA"]="$current_kex_sigalg"
break
fi
elif [[ $current_cipher =~ DHE-RSA ]]; then
# we got a fallback to a RSA based cipher
if [[ -z $current_kex_sigalg ]]; then
sigalgs_fallback["ECDSA"]="pfs-rsa"
else
sigalgs_fallback["ECDSA"]="pfs-rsa-${current_kex_sigalg}"
fi
break
else
# we got a fallback to a non PFS cipher, that's good too
sigalgs_fallback["ECDSA"]="soft-nopfs"
break
fi
[ "$OUTPUTFORMAT" == "terminal" ] && [ $DEBUG -lt 1 ] && echo -n '.'
done
fi
#
# Test default sigalgs for aRSA ciphers
#
if [[ ${supported_rsa_ciphers} ]]; then
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $supported_rsa_ciphers"
# since some ciphers supported by server may be SSLv2 only, we need to
# force use of TLSv1.2, otherwise openssl will send a SSLv2 compatible
# client hello
sslcommand+=" -no_ssl2 -no_ssl3"
local test_ecdsa_sigalgs=("${ecdsa_sigalgs[@]}")
local test_rsa_sigalgs=("${rsa_sigalgs[@]}")
while true; do
join_array_by_char ":" "${test_rsa_sigalgs[@]}" \
"${test_ecdsa_sigalgs[@]}"
local test_sigalgs="$joined_array"
ratelimit
verbose "Testing default RSA sig algs with $sslcommand -sigalgs $test_sigalgs"
local tmp=$(echo Q | $sslcommand -sigalgs $test_sigalgs 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "server selected $current_cipher, $current_protocol, $current_kex_sigalg"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == "0000" ]]; then
if [[ ${#test_rsa_sigalgs[@]} -eq 0 ]]; then
sigalgs_fallback["RSA"]="False"
else
sigalgs_fallback["RSA"]="intolerant"
fi
break
fi
if [[ $current_cipher =~ DHE-RSA ]]; then
if [[ -z $current_kex_sigalg ]]; then
# if we didn't get a sigalg that means the test failed
sigalgs_preferred_rsa=("Fail")
break
fi
if [[ "${test_rsa_sigalgs[*]}" =~ "RSA+$current_kex_sigalg" ]]; then
# save sigalg for reporting
sigalgs_preferred_rsa+=("$current_kex_sigalg")
# remove it from offered
local id
for id in "${!test_rsa_sigalgs[@]}"; do
if [[ ${test_rsa_sigalgs[$id]} =~ $current_kex_sigalg ]]; then
unset test_rsa_sigalgs[$id]
break
fi
done
# continue testing
else
# server selected sigalg we didn't offer
sigalgs_fallback["RSA"]="$current_kex_sigalg"
break
fi
elif [[ $current_cipher =~ DHE-ECDSA ]]; then
# we got a fallback to an ECDSA based cipher
if [[ -z $current_kex_sigalg ]]; then
sigalgs_fallback["RSA"]="pfs-ecdsa"
else
sigalgs_fallback["RSA"]="pfs-ecdsa-${current_kex_sigalg}"
fi
break
else
# we got a fallback to a non PFS cipher, that's good too
sigalgs_fallback["RSA"]="soft-nopfs"
break
fi
[ "$OUTPUTFORMAT" == "terminal" ] && [ $DEBUG -lt 1 ] && echo -n '.'
done
fi
#
# test which ordering is preferred, server or client
#
if [[ ${#sigalgs_preferred_rsa[@]} -eq 0 \
&& ${#sigalgs_preferred_ecdsa[@]} -eq 0 ]]; then
sigalgs_ordering="unsupported"
elif [[ ${#sigalgs_preferred_rsa[@]} -le 1 \
&& ${#sigalgs_preferred_ecdsa[@]} -le 1 ]]; then
# if there is just one hash for each signature algorithm, that means
# the server essentially forces the signature algorithm on client
sigalgs_ordering="server"
elif [[ ${#sigalgs_preferred_ecdsa[@]} -gt 1 ]]; then
# in case server supports multiple ECDSA sigalgs, test just those,
# even if it supports RSA (since those are more important)
# completely rotate order check if negotiated changes
local test_sigalgs=""
local i
for ((i=${#sigalgs_preferred_ecdsa[@]}-1; i>0; i--)); do
test_sigalgs+="ECDSA+${sigalgs_preferred_ecdsa[$i]}:"
done
test_sigalgs+="ECDSA+${sigalgs_preferred_ecdsa[0]}"
# prepare the command to run
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $supported_ecdsa_ciphers"
# since some ciphers supported by server may be SSLv2 only, we need to
# force use of TLSv1.2, otherwise openssl will send a SSLv2 compatible
# client hello
sslcommand+=" -no_ssl2 -no_ssl3"
ratelimit
verbose "Test ordering of sigalgs with $sslcommand -sigalgs $test_sigalgs"
local tmp=$(echo Q | $sslcommand -sigalgs $test_sigalgs 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "server selected $current_cipher, $current_protocol, $current_kex_sigalg"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == "0000" ]]; then
sigalgs_ordering="intolerant"
elif [[ -z $current_kex_sigalg ]] || [[ ! $current_cipher =~ DHE-ECDSA ]]; then
sigalgs_ordering="order-fallback"
else
if [[ ${sigalgs_preferred_ecdsa[0]} == $current_kex_sigalg ]]; then
sigalgs_ordering="server"
elif [[ ${sigalgs_preferred_ecdsa[${#sigalgs_preferred_ecdsa[@]}-1]} \
== $current_kex_sigalg ]]; then
sigalgs_ordering="client"
else
sigalgs_ordering="indeterminate"
fi
fi
else
# test ordering with RSA ciphers
# completely rotate order check if negotiated changes
local test_sigalgs=""
local i
for ((i=${#sigalgs_preferred_rsa[@]}-1; i>0; i--)); do
test_sigalgs+="RSA+${sigalgs_preferred_rsa[$i]}:"
done
test_sigalgs+="RSA+${sigalgs_preferred_rsa[0]}"
# prepare the command to run
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $supported_rsa_ciphers"
# since some ciphers supported by server may be SSLv2 only, we need to
# force use of TLSv1.2, otherwise openssl will send a SSLv2 compatible
# client hello
sslcommand+=" -no_ssl2 -no_ssl3"
ratelimit
verbose "Test ordering of sigalgs with $sslcommand -sigalgs $test_sigalgs"
local tmp=$(echo Q | $sslcommand -sigalgs $test_sigalgs 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "server selected $current_cipher, $current_protocol, $current_kex_sigalg"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == "0000" ]]; then
sigalgs_ordering="intolerant"
elif [[ -z $current_kex_sigalg ]] || [[ ! $current_cipher =~ DHE-RSA ]]; then
sigalgs_ordering="order-fallback"
else
if [[ ${sigalgs_preferred_rsa[0]} == $current_kex_sigalg ]]; then
sigalgs_ordering="server"
elif [[ ${sigalgs_preferred_rsa[${#sigalgs_preferred_rsa[@]}-1]} \
== $current_kex_sigalg ]]; then
sigalgs_ordering="client"
else
sigalgs_ordering="indeterminate"
fi
fi
fi
}
# If no options are given, give usage information and exit (with error code)
if (( $# == 0 )); then
usage
exit 1
fi
# UNKNOWNOPTIONS=""
while :
do
case $1 in
-h | --help | -\?)
usage
exit 0 # This is not an error, User asked help. Don't do "exit 1"
;;
-o | --openssl)
OPENSSLBIN=$2 # You might want to check if you really got FILE
shift 2
;;
-a | --allciphers)
ALLCIPHERS=1
shift
;;
-v | --verbose)
# Each instance of -v adds 1 to verbosity
VERBOSE=$((VERBOSE+1))
shift
;;
-j | -json | --json | --JSON)
OUTPUTFORMAT="json"
shift
;;
-b | --benchmark)
DOBENCHMARK=1
shift
;;
-D | --debug)
DEBUG=1
shift
;;
-d | --delay)
DELAY=$2
shift 2
;;
--cafile)
CACERTS="$2"
shift 2
# We need to bypass autodetection if this is provided.
CACERTS_ARG_SET=1
;;
--capath)
CAPATH="$2"
shift 2
;;
--saveca)
SAVECA="True"
shift 1
;;
--savecrt)
SAVECRT="$2"
shift 2
;;
--curves)
TEST_CURVES="True"
shift 1
;;
--no-curves)
TEST_CURVES="False"
shift 1
;;
--sigalg)
TEST_KEX_SIGALG="True"
shift 1
;;
--tolerance)
TEST_TOLERANCE="True"
shift 1
;;
--no-tolerance)
TEST_TOLERANCE="False"
shift 1
;;
--colors)
USECOLORS="True"
shift 1
;;
--no-colors)
USECOLORS="False"
shift 1
;;
--no-sni)
SNI="False"
shift 1
;;
--) # End of all options
shift
break
;;
# -*)
# UNKNOWNOPTIONS=$((UNKNOWNOPTIONS+$1))
# # echo "WARN: Unknown option (ignored): $1" >&2
# shift
# ;;
*) # no more options we understand.
break
;;
esac
done
if [[ -z $OPENSSLBIN ]]; then
readlink_result=$("$READLINKBIN" -f "$0")
if [[ -z $readlink_result ]]; then
echo "$READLINKBIN -f $0 failed, aborting." 1>&2
exit 1
fi
REALPATH=$(dirname "$readlink_result")
if [[ -z $REALPATH ]]; then
echo "dirname $REALPATH failed, aborting." 1>&2
exit 1
fi
OPENSSLBIN="${REALPATH}/${opensslbin_name}"
if ! [[ -x "${OPENSSLBIN}" ]]; then
OPENSSLBIN="$(which openssl)" # fallback to generic openssl
fi
fi
# use custom config file to enable GOST ciphers
if [[ -e $DIRNAMEPATH/openssl.cnf ]]; then
export OPENSSL_CONF="$DIRNAMEPATH/openssl.cnf"
fi
OPENSSLBINHELP="$($OPENSSLBIN s_client -help 2>&1)"
if [[ $OPENSSLBINHELP =~ :error: ]]; then
verbose "$OPENSSLBIN can't handle GOST config, disabling"
unset OPENSSL_CONF
OPENSSLBINHELP="$($OPENSSLBIN s_client -help 2>&1)"
fi
if ! [[ $OPENSSLBINHELP =~ -connect ]]; then
echo "$OPENSSLBIN s_client doesn't accept the -connect parameter, which is extremely strange; refusing to proceed." 1>&2
exit 1
fi
if [[ -n $CAPATH && -n $CACERTS ]]; then
echo "Both directory and file with CA certificates specified" 1>&2
exit 1
fi
if [[ -n $ALLCIPHERS && $OUTPUTFORMAT == "json" ]]; then
echo "--allciphers cannot produce JSON output, aborting." 1>&2
exit 1
fi
# echo parameters left: $@
if (( $# < 1 )); then
echo "The final argument must be a valid HOST[:PORT], but none was provided." 1>&2
exit 1
fi
PARAMS=("$@")
last_element="$(( $# - 1 ))"
TARGET=${PARAMS[$last_element]}
unset PARAMS[$last_element]
# Refuse to proceed if the hostname starts with a hyphen, since hostnames can't
# begin with a hyphen and this likely means we accidentally parsed an option as
# a hostname.
if [[ -z $TARGET || $TARGET =~ ^[-:] ]]; then
echo "The final argument '$TARGET' is not a valid HOST[:PORT]." 1>&2
exit 1
fi
# Handle Targets that are URIs
if [[ $TARGET =~ /([^/]+)(/|$) ]]; then
TARGET="${BASH_REMATCH[1]}"
fi
if [[ $TARGET =~ :.*[^0-9] ]]; then
echo "Final argument is not a valid HOST[:PORT]" >&2
exit 1
fi
if ! [[ $TARGET =~ : ]]; then
sni_target=$TARGET
TARGET="${TARGET}:443"
else
# strip the port for the sni_target
if [[ "$TARGET" =~ (.*):([0-9]{1,5}) ]]; then
sni_target="${BASH_REMATCH[1]}"
fi
fi
debug "target: $TARGET"
# test our openssl is usable
if [[ ! -x $OPENSSLBIN ]]; then
OPENSSLBIN=$(which openssl)
if [[ "$OUTPUTFORMAT" == "terminal" ]]; then
echo "custom openssl not executable, falling back to system one from $OPENSSLBIN" 1>&2
fi
fi
if [[ $TEST_CURVES == "True" ]]; then
if [[ ! -z "$($OPENSSLBIN s_client -curves 2>&1|head -1|grep 'unknown option')" ]]; then
echo "curves testing not available with your version of openssl, disabling it" 1>&2
TEST_CURVES="False"
fi
fi
if [[ -z $CACERTS ]] && ! [[ -n $CACERTS_ARG_SET ]]; then
# find a list of trusted CAs on the local system, or use the provided list
for f in /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt; do
if [[ -e "$f" ]]; then
CACERTS="$f"
break
fi
done
if [[ ! -e "$CACERTS" ]]; then
CACERTS="$DIRNAMEPATH/ca-bundle.crt"
fi
fi
if ! [[ -e $CACERTS && -r $CACERTS ]]; then
echo "--cafile $CACERTS is not a readable file, aborting." 1>&2
exit 1
fi
if [[ -n $CAPATH ]] && ! [[ -d $CAPATH ]]; then
echo "--capath $CAPATH is not a directory, aborting." 1>&2
exit 1
fi
if [[ $VERBOSE != 0 ]] ; then
[[ -n "$CACERTS" ]] && echo "Using trust anchors from $CACERTS"
echo "Loading $($OPENSSLBIN ciphers -v $CIPHERSUITE 2>/dev/null|grep Kx|wc -l) ciphersuites from $(echo -n $($OPENSSLBIN version 2>/dev/null))"
$OPENSSLBIN ciphers ALL 2>/dev/null
fi
SCLIENTARGS="${PARAMS[*]}"
# only append the SNI:
# if the target is a hostname by validating the tld
# if -servername was not supplied by the user
if [[ $SNI == "True" && ! $SCLIENTARGS =~ servername ]]; then
if [[ $sni_target =~ \.[a-zA-Z]{1,20}$ ]]; then
SCLIENTARGS="$SCLIENTARGS -servername $sni_target"
else
echo "Warning: target is not a FQDN. SNI was disabled. Use a FQDN or '-servername <fqdn>'" 1>&2
fi
fi
debug "sclientargs: $SCLIENTARGS"
cipherspref=()
ciphercertificates=()
results=()
# Call to the recursive loop that retrieves the cipher preferences
get_cipher_pref $CIPHERSUITE
# in case the server is intolerant to our big hello, try again with
# a smaller one
# do that either when the normal scan returns no ciphers or just SSLv2
# ciphers (where it's likely that the limiting by OpenSSL worked)
pref=(${cipherspref[0]})
if (( ${#cipherspref[@]} == 0 )) || [[ ${pref[1]} == "SSLv2" ]]; then
cipherspref=()
ciphercertificates=()
results=()
get_cipher_pref "$FALLBACKCIPHERSUITESTRING"
fi
if [[ $TEST_TOLERANCE == "True" ]]; then
test_tls_tolerance
fi
test_serverside_ordering
if [[ $TEST_KEX_SIGALG == "True" ]]; then
test_kex_sigalgs
fi
if [[ $TEST_CURVES == "True" ]]; then
test_curves_fallback
fi
if [[ "$OUTPUTFORMAT" == "json" ]]; then
display_results_in_json
else
echo
display_results_in_terminal
fi
# If asked, test every single cipher individually
if [[ -n $ALLCIPHERS ]]; then
echo; echo "All accepted ciphersuites"
for c in $($OPENSSLBIN ciphers -v ALL:COMPLEMENTOFALL 2>/dev/null |awk '{print $1}'|sort -u); do
osslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client $SCLIENTARGS -connect $TARGET -cipher $c"
if test_cipher_on_target "$osslcommand"; then
r="pass"
else
r="fail"
fi
printf "%-35s %s\n" "$c" "$r"
done
fi