mirror of
https://github.com/mozilla/cipherscan.git
synced 2024-12-26 04:33:42 +01:00
limit number of forks needed to speed up execution
bash has a built in regular expression processor, we can match lines using =~ moreover, stuff that will match while being inside parentheses is later available in the BASH_REMATCH array the IFS (Internal Field Separator) by default includes space, tab and new line, as such we can use it to split longer lines to separate words, just as awk '{print $1}' can, just need to put the value to an array for that we also don't have to use $(echo $var) when assigning variables, $var is enough bash has also built in substitution engine, so we can do ${var/,/ & } to switch all commas to ampersands when using the variable
This commit is contained in:
parent
9f06829486
commit
c4a8495a54
217
cipherscan
217
cipherscan
@ -62,7 +62,9 @@ ratelimit() {
|
||||
}
|
||||
|
||||
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>
|
||||
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 it knows.
|
||||
@ -108,7 +110,7 @@ debug(){
|
||||
|
||||
c_hash() {
|
||||
local h=$(${OPENSSLBIN} x509 -hash -noout -in "$1/$2" 2>/dev/null)
|
||||
for num in $(seq 0 100); do
|
||||
for ((num=0; num<=100; num++)) ; do
|
||||
if [[ $1/${h}.${num} -ef $2 ]]; then
|
||||
# file already linked, ignore
|
||||
break
|
||||
@ -123,6 +125,106 @@ c_hash() {
|
||||
done
|
||||
}
|
||||
|
||||
parse_openssl_output() {
|
||||
# clear variables in case matching doesn't hit them
|
||||
current_ocspstaple="False"
|
||||
current_cipher=""
|
||||
current_pfs=""
|
||||
current_protocol=""
|
||||
current_tickethint="None"
|
||||
current_pubkey=0
|
||||
current_trusted="False"
|
||||
current_sigalg="None"
|
||||
|
||||
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
|
||||
|
||||
# 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 -gt 0 ]]; then
|
||||
local ossl_out=$(${OPENSSLBIN} x509 -noout -text 2>/dev/null <<<"${current_raw_certificates[0]}")
|
||||
while read data; do
|
||||
if [[ $data =~ Signature\ Algorithm ]]; then
|
||||
local match=($data)
|
||||
current_sigalg="${match[2]}"
|
||||
fi
|
||||
done <<<"$ossl_out"
|
||||
fi
|
||||
}
|
||||
|
||||
# Connect to a target host with the selected ciphersuite
|
||||
test_cipher_on_target() {
|
||||
local sslcommand=$@
|
||||
@ -138,60 +240,31 @@ test_cipher_on_target() {
|
||||
# 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
|
||||
cmnd=$(sed 's/-servername\ [^ ]*//'<<<$sslcommand)
|
||||
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)
|
||||
if grep 'OCSP Response Data' <<<"$tmp" >/dev/null; then
|
||||
current_ocspstaple="True"
|
||||
else
|
||||
current_ocspstaple="False"
|
||||
fi
|
||||
# filter out the OCSP server certificate
|
||||
tmp=$(awk 'BEGIN { pr="yes" } /^======================================/ { if ( pr=="yes" ) pr="no"; else pr="yes" } { if ( pr == "yes" ) print }' <<<"$tmp")
|
||||
|
||||
# session metadata
|
||||
current_cipher=$(awk '/New, / {print $5; exit}' <<<"$tmp")
|
||||
current_pfs=$(awk '/Server Temp Key/ {print $4$5$6$7; exit}' <<<"$tmp")
|
||||
current_protocol=$(awk '/^ +Protocol +:/ {print $3; exit}' <<<"$tmp")
|
||||
current_tickethint=$(awk '/ticket lifetime hint/ {print $6; exit}' <<<"$tmp")
|
||||
if [ -z $current_tickethint ]; then
|
||||
current_tickethint=None
|
||||
fi
|
||||
|
||||
# certificate metadata
|
||||
current_pubkey=$(awk '/Server public key is / {print $5;exit}' <<<"$tmp")
|
||||
if [ -z $current_pubkey ]; then
|
||||
current_pubkey=0
|
||||
fi
|
||||
current_sigalg=$(${OPENSSLBIN} x509 -noout -text 2>/dev/null <<<"$tmp"|\
|
||||
awk '/Signature Algorithm/ {print $3; exit}') || 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
|
||||
parse_openssl_output <<<"$tmp"
|
||||
verbose "selected cipher is '$current_cipher'"
|
||||
verbose "using protocol '$current_protocol'"
|
||||
|
||||
# collect certificate data
|
||||
current_certificates=""
|
||||
local certificate_count=$(grep --count -- '-----END CERTIFICATE-----'\
|
||||
<<<"$tmp")
|
||||
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=$(awk -v i=$i 'BEGIN { output=0;n=0 }
|
||||
/-----BEGIN CERTIFICATE-----/ { output=1 }
|
||||
output==1 { if (n==i) print }
|
||||
/-----END CERTIFICATE-----/ { output=0; n++ }' <<<"$tmp")
|
||||
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
|
||||
@ -205,9 +278,9 @@ test_cipher_on_target() {
|
||||
fi
|
||||
|
||||
# compute sha256 fingerprint of the certificate
|
||||
local sha256sum=$(${OPENSSLBIN} x509 -outform DER\
|
||||
local sha256sum=($(${OPENSSLBIN} x509 -outform DER\
|
||||
<<<"$cert" 2>/dev/null |\
|
||||
${OPENSSLBIN} dgst -sha256 -r 2>/dev/null| awk '{print $1}')
|
||||
${OPENSSLBIN} dgst -sha256 -r 2>/dev/null))
|
||||
|
||||
# check if it is a CA certificate
|
||||
local isCA="False"
|
||||
@ -229,7 +302,7 @@ test_cipher_on_target() {
|
||||
# signed ones)
|
||||
local saved="False"
|
||||
if ${OPENSSLBIN} verify "${trust_source[@]}" \
|
||||
-untrusted <(echo "$tmp") <(echo "$cert") 2>/dev/null | \
|
||||
-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
|
||||
@ -348,7 +421,7 @@ get_cipher_pref() {
|
||||
if [ $success -eq 0 ]; then
|
||||
cipherspref=("${cipherspref[@]}" "$result")
|
||||
ciphercertificates=("${ciphercertificates[@]}" "$certificates")
|
||||
pciph=($(echo $result))
|
||||
pciph=($result)
|
||||
get_cipher_pref "!$pciph:$ciphersuite"
|
||||
return 0
|
||||
fi
|
||||
@ -365,30 +438,32 @@ display_results_in_terminal() {
|
||||
local different=False
|
||||
echo "Target: $TARGET"; echo
|
||||
for cipher in "${cipherspref[@]}"; do
|
||||
pciph=($(echo $cipher))
|
||||
# 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
|
||||
pubkey=$(awk '{print $3}' <<<$cipher)
|
||||
sigalg=$(awk '{print $4}' <<<$cipher)
|
||||
trusted=$(awk '{print $5}' <<<$cipher)
|
||||
tickethint=$(awk '{print $6}' <<<$cipher)
|
||||
ocspstaple=$(awk '{print $7}' <<<$cipher)
|
||||
pubkey="${cipher_data[2]}"
|
||||
sigalg="${cipher_data[3]}"
|
||||
trusted="${cipher_data[4]}"
|
||||
tickethint="${cipher_data[5]}"
|
||||
ocspstaple="${cipher_data[6]}"
|
||||
else
|
||||
if [ "$pubkey" != "$(awk '{print $3}' <<<$cipher)" ]; then
|
||||
if [ "$pubkey" != "${cipher_data[2]}" ]; then
|
||||
different=True
|
||||
fi
|
||||
if [ "$sigalg" != "$(awk '{print $4}' <<<$cipher)" ]; then
|
||||
if [ "$sigalg" != "${cipher_data[3]}" ]; then
|
||||
different=True
|
||||
fi
|
||||
if [ "$trusted" != "$(awk '{print $5}' <<<$cipher)" ]; then
|
||||
if [ "$trusted" != "${cipher_data[4]}" ]; then
|
||||
different=True
|
||||
fi
|
||||
if [ "$tickethint" != "$(awk '{print $6}' <<<$cipher)" ]; then
|
||||
if [ "$tickethint" != "${cipher_data[5]}" ]; then
|
||||
different=True
|
||||
fi
|
||||
fi
|
||||
@ -448,18 +523,19 @@ display_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 -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')\","
|
||||
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\":\"$(echo $cipher|awk '{print $6}')\","
|
||||
echo -n "\"ocsp_stapling\":\"$(echo $cipher|awk '{print $7}')\","
|
||||
pfs=$(echo $cipher|awk '{print $8}')
|
||||
echo -n "\"ticket_hint\":\"${cipher_arr[5]}\","
|
||||
echo -n "\"ocsp_stapling\":\"${cipher_arr[6]}\","
|
||||
pfs="${cipher_arr[7]}"
|
||||
[ "$pfs" == "" ] && pfs="None"
|
||||
echo -n "\"pfs\":\"$pfs\"}"
|
||||
ctr=$((ctr+1))
|
||||
@ -477,25 +553,25 @@ test_serverside_ordering() {
|
||||
# server supports just two ciphers, so rotate them, that should be enough
|
||||
elif [[ ${#cipherspref[@]} -eq 2 ]]; then
|
||||
|
||||
local cipher=$(awk '{print $1}' <<< ${cipherspref[1]})
|
||||
local cipher=(${cipherspref[1]})
|
||||
prefered="$cipher"
|
||||
ciphersuite=$cipher
|
||||
|
||||
cipher=$(awk '{print $1}' <<< ${cipherspref[0]})
|
||||
cipher=(${cipherspref[0]})
|
||||
ciphersuite+=":$cipher"
|
||||
|
||||
# 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
|
||||
else
|
||||
local cipher=$(awk '{print $1}' <<< ${cipherspref[2]})
|
||||
local cipher=(${cipherspref[2]})
|
||||
prefered="$cipher"
|
||||
ciphersuite="$cipher"
|
||||
|
||||
cipher=$(awk '{print $1}' <<< ${cipherspref[1]})
|
||||
cipher=(${cipherspref[1]})
|
||||
ciphersuite+=":$cipher"
|
||||
|
||||
cipher=$(awk '{print $1}' <<< ${cipherspref[0]})
|
||||
cipher=(${cipherspref[0]})
|
||||
ciphersuite+=":$cipher"
|
||||
fi
|
||||
|
||||
@ -511,7 +587,7 @@ test_serverside_ordering() {
|
||||
if [ $? -ne 0 ]; then
|
||||
serverside="True"
|
||||
else
|
||||
local selected=$(awk '{print $1}' <<< $result)
|
||||
local selected=($result)
|
||||
if [[ $selected == $prefered ]]; then
|
||||
serverside="False"
|
||||
else
|
||||
@ -608,8 +684,7 @@ TARGET=$HOST:$PORT
|
||||
debug "target: $TARGET"
|
||||
|
||||
# test our openssl is usable
|
||||
tmp="$($OPENSSLBIN -h 2>&1 1>/dev/null)"
|
||||
if [ $? -gt 0 ]; then
|
||||
if [ ! -x $OPENSSLBIN ]; then
|
||||
OPENSSLBIN=$(which openssl)
|
||||
if [ "$OUTPUTFORMAT" == "terminal" ]; then
|
||||
echo "custom openssl not executable, falling back to system one from $OPENSSLBIN"
|
||||
|
Loading…
Reference in New Issue
Block a user