From 5b96f8fb4726827f2e937a5c72cd516ec757a75a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 18 Oct 2014 17:20:20 +0200 Subject: [PATCH] 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 --- cipherscan | 217 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 71 deletions(-) diff --git a/cipherscan b/cipherscan index 016c5a4..233b655 100755 --- a/cipherscan +++ b/cipherscan @@ -56,7 +56,9 @@ unset ok_protocols declare -A ok_protocols 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] + 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] usage: $0 -h|--help $0 attempts to connect to a target site using all the ciphersuites it knows. @@ -102,7 +104,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 @@ -117,6 +119,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=$@ @@ -135,59 +237,30 @@ 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 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 @@ -201,9 +274,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" @@ -225,7 +298,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 @@ -345,7 +418,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 @@ -362,30 +435,32 @@ display_results_in_terminal() { local ocspstaple local different=False 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 @@ -445,18 +520,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)) @@ -474,25 +550,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 @@ -508,7 +584,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 @@ -605,8 +681,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"