2
0
mirror of https://github.com/mozilla/cipherscan.git synced 2024-11-26 07:53:41 +01:00
cipherscan/cipherscan
Hubert Kario 5dfa3c444e put ECDSA ciphers before RSA ciphers
Google servers (like youtube) negotiate ECDSA variant
of ciphersuite only if the RSA variant is also present,
so to return more comple cipher listing, we need to move
ECDSA ciphers before RSA ciphers
2014-05-13 13:41:16 +02:00

385 lines
12 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/.
# Author: Julien Vehent [:ulfr] - 2013
DOBENCHMARK=0
BENCHMARKITER=30
OPENSSLBIN="$(dirname $0)/openssl"
CACERTS=${CACERTS:-/etc/pki/tls/certs/ca-bundle.crt}
if [ ! -e "$CACERTS" ]; then
CACERTS="/etc/ssl/certs/ca-certificates.crt"
fi
if [ ! -e "$CACERTS" ]; then
echo "Warning: CA Certificates not found at $CACERTS, export CACERTS variable with location of your trust anchors" 1>&2
fi
CIPHERSUITE="ALL:COMPLEMENTOFALL:+aRSA"
DEBUG=0
VERBOSE=0
DELAY=0
ALLCIPHERS=0
OUTPUTFORMAT="terminal"
TIMEOUT=10
usage() {
echo -e "usage: $0 [-a|--allciphers] [-b|--benchmark] [-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 it knowns.
Julien Vehent [:ulfr] - https://github.com/jvehent/cipherscan
Port defaults to 443
example: $ $0 www.google.com:443
Use one of the options below:
-a | --allciphers Test all known ciphers individually at the end.
-b | --benchmark Activate benchmark mode.
-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.
-v | --verbose Increase verbosity.
The rest of the arguments will be interpreted as openssl s_client argument.
This enables checking smtp/imap/pop3/ftp/xmpp via -starttls
EXAMPLES: $0 -starttls xmpp jabber.ccc.de:5222
"
}
verbose() {
if [ $VERBOSE != 0 ]; then
echo "$@" >&2
fi
}
debug(){
if [ $DEBUG == 1 ]; then
echo Debug: "$@" >&2
fi
}
# Connect to a target host with the selected ciphersuite
test_cipher_on_target() {
local sslcommand=$@
cipher=""
protocols=""
pfs=""
previous_cipher=""
for tls_version in "-ssl2" "-ssl3" "-tls1" "-tls1_1" "-tls1_2"
do
debug echo \"Q\" \| $sslcommand $tls_version
local tmp=$(echo "Q" | $sslcommand $tls_version 1>/dev/stdout 2>/dev/null)
current_cipher=$(grep "New, " <<<"$tmp"|awk '{print $5}')
current_pfs=$(grep 'Server Temp Key' <<<"$tmp"|awk '{print $4$5$6$7}')
current_protocol=$(egrep "^\s+Protocol\s+:" <<<"$tmp"|awk '{print $3}')
current_pubkey=$(grep 'Server public key is ' <<<"$tmp"|awk '{print $5}')
if [ -z $current_pubkey ]; then
current_pubkey=0
fi
current_sigalg=$(openssl x509 -noout -text 2>/dev/null <<<"$tmp"|grep Signature\ Algorithm | head -n 1 | awk '{print $3}') || current_sigalg="None"
grep 'Verify return code: 0 ' <<<"$tmp" >/dev/null
if [ $? -eq 0 ]; then
current_trusted="True"
else
current_trusted="False"
fi
if [ -z $current_sigalg ]; then
current_sigalg=None
fi
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
pubkey=$current_pubkey
sigalg=$current_sigalg
trusted=$current_trusted
# 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 $pfs"
verbose "handshake failed, server returned ciphersuite '$result'"
return 1
# the connection succeeded
else
result="$cipher $protocols $pubkey $sigalg $trusted $pfs"
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="timeout $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 [ $? -gt 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"
if [ -e $CACERTS ]; then
local sslcommand="timeout $TIMEOUT $OPENSSLBIN s_client -CAfile $CACERTS $SCLIENTARGS -connect $TARGET -cipher $ciphersuite"
else
local sslcommand="timeout $TIMEOUT $OPENSSLBIN s_client $SCLIENTARGS -connect $TARGET -cipher $ciphersuite"
fi
verbose "Connecting to '$TARGET' with ciphersuite '$ciphersuite'"
test_cipher_on_target "$sslcommand"
local success=$?
# If the connection succeeded with the current cipher, benchmark and store
if [ $success -eq 0 ]; then
cipherspref=("${cipherspref[@]}" "$result")
pciph=$(echo $result|awk '{print $1}')
get_cipher_pref "!$pciph:$ciphersuite"
return 0
fi
sleep $DELAY
}
display_results_in_terminal() {
# Display the results
ctr=1
local pubkey
local sigalg
local trusted
local different=False
for cipher in "${cipherspref[@]}"; do
pciph=$(echo $cipher|awk '{print $1}')
if [ $DOBENCHMARK -eq 1 ]; then
bench_cipher "$pciph"
r="$ctr $cipher $cipherbenchms"
else
r="$ctr $cipher"
fi
if [ $ctr -eq 1 ]; then
pubkey=$(awk '{print $3}' <<<$cipher)
sigalg=$(awk '{print $4}' <<<$cipher)
trusted=$(awk '{print $5}' <<<$cipher)
else
if [ "$pubkey" != "$(awk '{print $3}' <<<$cipher)" ]; then
different=True
fi
if [ "$sigalg" != "$(awk '{print $4}' <<<$cipher)" ]; then
different=True
fi
if [ "$trusted" != "$(awk '{print $5}' <<<$cipher)" ]; then
different=True
fi
fi
results=("${results[@]}" "$r")
ctr=$((ctr+1))
done
if [ $DOBENCHMARK -eq 1 ]; then
if [ $different == "True" ]; then
header="prio ciphersuite protocols pubkey_size signature_algoritm trusted pfs_keysize avg_handshake_microsec"
else
header="prio ciphersuite protocols pfs_keysize avg_handshake_microsec"
fi
else
if [ $different == "True" ]; then
header="prio ciphersuite protocols pubkey_size signature_algorithm trusted pfs_keysize"
else
header="prio ciphersuite protocols pfs_keysize"
fi
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
echo $result|grep -v '(NONE)'|awk '{print $1 " " $2 " " $3 " " $7}'
fi
done|column -t
echo
if [ $different != "True" ]; then
if [ "$trusted" == "True" ]; then
echo "Certificate: trusted, $pubkey bit, $sigalg signature"
else
echo "Certificate: UNTRUSTED, $pubkey bit, $sigalg signature"
fi
fi
}
display_results_in_json() {
# Display the results in json
ctr=0
echo -n "{\"target\":\"$TARGET\",\"date\":\"$(date -R)\",\"ciphersuite\": ["
for cipher in "${cipherspref[@]}"; do
[ $ctr -gt 0 ] && echo -n ','
echo -n "{\"cipher\":\"$(echo $cipher|awk '{print $1}')\","
echo -n "\"protocols\":[\"$(echo $cipher|awk '{print $2}'|sed 's/,/","/g')\"],"
echo -n "\"pubkey\":[\"$(echo $cipher|awk '{print $3}'|sed 's/,/","/g')\"],"
echo -n "\"sigalg\":[\"$(echo $cipher|awk '{print $4}'|sed 's/,/","/g')\"],"
echo -n "\"trusted\":\"$(echo $cipher|awk '{print $5}'|sed 's/,/","/g')\","
pfs=$(echo $cipher|awk '{print $6}')
[ "$pfs" == "" ] && pfs="None"
echo -n "\"pfs\":\"$pfs\"}"
ctr=$((ctr+1))
done
echo ']}'
}
# 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
;;
--) # 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 [ $VERBOSE != 0 ] ; then
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
# echo paramters left: $@
TEMPTARGET=$(sed -e 's/^.* //'<<<"${@}")
HOST=$(sed -e 's/:.*//'<<<"${TEMPTARGET}")
PORT=$(sed -e 's/.*://'<<<"${TEMPTARGET}")
# Default to https if no port given
if [ "$HOST" = "$PORT" ]; then
PORT=443
fi
debug "host: $HOST"
debug "Port: $PORT"
TARGET=$HOST:$PORT
debug "target: $TARGET"
SCLIENTARGS=$(sed -e s,${TEMPTARGET},,<<<"${@}")
debug "sclientargs: $SCLIENTARGS"
cipherspref=();
results=()
# Call to the recursive loop that retrieves the cipher preferences
get_cipher_pref $CIPHERSUITE
if [ "$OUTPUTFORMAT" == "json" ]; then
display_results_in_json
else
echo
display_results_in_terminal
fi
# If asked, test every single cipher individually
if [ $ALLCIPHERS -gt 0 ]; then
echo; echo "All accepted ciphersuites"
for c in $($OPENSSLBIN ciphers -v ALL:COMPLEMENTOFALL 2>/dev/null |awk '{print $1}'|sort|uniq); do
r="fail"
osslcommand="timeout $TIMEOUT $OPENSSLBIN s_client $SCLIENTARGS -connect $TARGET -cipher $c"
test_cipher_on_target "$osslcommand"
if [ $? -eq 0 ]; then
r="pass"
fi
echo "$c $r"|awk '{printf "%-35s %s\n",$1,$2}'
debug "Sleeping for $DELAY."
sleep $DELAY
done
fi