From f0747dd2fcfc58d447bf9841d027d8a85bbdf9d1 Mon Sep 17 00:00:00 2001 From: Dirk Date: Sat, 10 Jan 2015 22:08:11 +0100 Subject: [PATCH] now checker fo SSLv3 to TLSV1.2 (SNI missing for now) --- utils/prototype.tls-protocol-checker.bash | 355 ++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100755 utils/prototype.tls-protocol-checker.bash diff --git a/utils/prototype.tls-protocol-checker.bash b/utils/prototype.tls-protocol-checker.bash new file mode 100755 index 0000000..734061b --- /dev/null +++ b/utils/prototype.tls-protocol-checker.bash @@ -0,0 +1,355 @@ +#!/usr/bin/env bash + +# bash socket implementation of checking the availability of TLS, (SSLv3 to TLS 1.2) +# Based on my bash-heartbleed (loosely based on my bash-heartbleed implementation) +# +# Author: Dirk Wetter, GPLv2 see https://testssl.sh/LICENSE.txt + +# it helps to wireshark: +# //openssl s_client -state -ssl3 -connect AA.BB.YYY.XXX:443 -servername target +# //openssl s_client -state -tls1 -connect AA.BB.YYY.XXX:443 -servername target +# //openssl s_client -state -tls1_1 -connect AA.BB.YYY.XXX:443 -servername target +# //openssl s_client -state -tls1_2 -connect AA.BB.YYY.XXX:443 -servername target +# +# debug is easier for response: +# //openssl s_client -tls1 -debug -connect target:443 /openssl s_client -host target -port 443 -nextprotoneg 'spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1' +# todo: TLS 1.3 (https://tools.ietf.org/html/draft-ietf-tls-tls13-03#section-7.4) +# todo: DTLS (https://tools.ietf.org/html/rfc4347#section-4.2.2) + +IFILE=./mapping-rfc.txt +NODE="" +COL_WIDTH=32 +DEBUG=${DEBUG:-0} +USLEEP_REC=${USLEEP_REC:-0.2} +USLEEP_SND=${USLEEP_SND:-0.1} # 1 second wait until otherwise specified +MAX_WAITSOCK=2 +SOCK_REPLY_FILE="" +NW_STR="" +LEN_STR="" +DETECTED_TLS_VERSION="" + +# spdy, TLS 1.2, 133 cipher: +TLS12_CIPHER=" +cc, 14, cc, 13, cc, 15, c0, 30, c0, 2c, c0, 28, c0, 24, c0, 14, +c0, 0a, c0, 22, c0, 21, c0, 20, 00, a5, 00, a3, 00, a1, 00, 9f, +00, 6b, 00, 6a, 00, 69, 00, 68, 00, 39, 00, 38, 00, 37, 00, 36, +c0, 77, c0, 73, 00, c4, 00, c3, 00, c2, 00, c1, 00, 88, 00, 87, +00, 86, 00, 85, c0, 32, c0, 2e, c0, 2a, c0, 26, c0, 0f, c0, 05, +c0, 79, c0, 75, 00, 9d, 00, 3d, 00, 35, 00, c0, 00, 84, c0, 2f, +c0, 2b, c0, 27, c0, 23, c0, 13, c0, 09, c0, 1f, c0, 1e, c0, 1d, +00, a4, 00, a2, 00, a0, 00, 9e, 00, 67, 00, 40, 00, 3f, 00, 3e, +00, 33, 00, 32, 00, 31, 00, 30, c0, 76, c0, 72, 00, be, 00, bd, +00, bc, 00, bb, 00, 9a, 00, 99, 00, 98, 00, 97, 00, 45, 00, 44, +00, 43, 00, 42, c0, 31, c0, 2d, c0, 29, c0, 25, c0, 0e, c0, 04, +c0, 78, c0, 74, 00, 9c, 00, 3c, 00, 2f, 00, ba, 00, 96, 00, 41, +00, 07, c0, 11, c0, 07, 00, 66, c0, 0c, c0, 02, 00, 05, 00, 04, +c0, 12, c0, 08, c0, 1c, c0, 1b, c0, 1a, 00, 16, 00, 13, 00, 10, +00, 0d, c0, 0d, c0, 03, 00, 0a, 00, 63, 00, 15, 00, 12, 00, 0f, +00, 0c, 00, 62, 00, 09, 00, 65, 00, 64, 00, 14, 00, 11, 00, 0e, +00, 0b, 00, 08, 00, 06, 00, 03, 00, ff +" + +# 76 cipher fuer SSLv3, TLS 1, TLS 1.1: +TLS_CIPHER=" +c0, 14, c0, 0a, c0, 22, c0, 21, c0, 20, 00, 39, 00, 38, 00, 37, +00, 36, 00, 88, 00, 87, 00, 86, 00, 85, c0, 0f, c0, 05, 00, 35, +00, 84, c0, 13, c0, 09, c0, 1f, c0, 1e, c0, 1d, 00, 33, 00, 32, +00, 31, 00, 30, 00, 9a, 00, 99, 00, 98, 00, 97, 00, 45, 00, 44, +00, 43, 00, 42, c0, 0e, c0, 04, 00, 2f, 00, 96, 00, 41, 00, 07, +c0, 11, c0, 07, 00, 66, c0, 0c, c0, 02, 00, 05, 00, 04, c0, 12, +c0, 08, c0, 1c, c0, 1b, c0, 1a, 00, 16, 00, 13, 00, 10, 00, 0d, +c0, 0d, c0, 03, 00, 0a, 00, 63, 00, 15, 00, 12, 00, 0f, 00, 0c, +00, 62, 00, 09, 00, 65, 00, 64, 00, 14, 00, 11, 00, 0e, 00, 0b, +00, 08, 00, 06, 00, 03, 00, ff" + + +# example 1: for SNI +#00 00 # extention server_name +#00 0b # length +#00 09 # server_name list_length +#00 # server_name type (hostname) +#00 06 # server_name length +#66 66 66 66 66 66 # server_name target + +# example 2: for SNI +#00 00 00 1a 00 18 00 00 15 66 66 66 66 66 66 2e .........target. +#66 66 66 66 66 66 66 66 66 66 2e 66 66 66 mydomain1.tld + +#formatted: +#00 00 +#00 1a +#00 18 +#00 +#00 15 +#66 66 66 66 66 66 2e 66 66 66 66 66 66 66 66 66 66 2e 66 66 66 target.mydomain1.tld + + +help() { + echo "Syntax $0 [[TLS lsb]]" + echo + echo "example: $0 google.com " + echo + exit 1 +} + + +parse_hn_port() { + PORT=443 # unless otherwise auto-determined, see below + NODE="$1" + + # strip "https", supposed it was supplied additionally + echo $NODE | grep -q 'https://' && NODE=`echo $NODE | sed -e 's/https\:\/\///' ` + + # strip trailing urlpath + NODE=`echo $NODE | sed -e 's/\/.*$//'` + + # determine port, supposed it was supplied additionally + echo $NODE | grep -q ':' && PORT=`echo $NODE | sed 's/^.*\://'` && NODE=`echo $NODE | sed 's/\:.*$//'` + + # servername to network bytes: + SNIHEX=`printf $NODE | hexdump -v -e '16/1 "%02X"'` +} + +# arg1: formatted string here in the code +code2network() { + NW_STR=`echo "$1" | sed -e 's/,/\\\x/g' | sed -e 's/# .*$//g' -e 's/ //g' -e '/^$/d' | tr -d '\n' | tr -d '\t'` +} + +len2twobytes() { + len_arg1=`echo ${#1}` + [[ $len_arg1 -le 2 ]] && LEN_STR=`printf "00, %02s \n" $1` + [[ $len_arg1 -eq 3 ]] && LEN_STR=`printf "%02s, %02s \n" ${1:0:1} ${1:1:2}` + [[ $len_arg1 -eq 4 ]] && LEN_STR=`printf "%02s, %02s \n" ${1:0:2} ${1:2:2}` +} + + +# arg1: TLS_VER_LSB +# arg2: CIPHER_SUITES string +# arg3: SERVERNAME +# ??? more extensions? +socksend_clienthello() { + + if [[ "$1" != "ff" ]]; then # internally we use 00 to indicate SSLv2 + len_sni=`echo ${#3}` + #len_ciph_suites=`echo ${#SNIHEX}` + + code2network "$2" + cipher_suites="$NW_STR" # we don't have the leading \x here so string length is two byte less, see next + + len_ciph_suites_byte=`echo ${#cipher_suites}` + let "len_ciph_suites_byte += 2" + + # we have additional 2 chars \x in each 2 byte string and 2 byte ciphers, so we need to divide by 4: + len_ciph_suites=`printf "%X\n" $(($len_ciph_suites_byte / 4 ))` + len2twobytes "$len_ciph_suites" + len_ciph_suites_word="$LEN_STR" + [[ $DEBUG -ge 4 ]] && echo $len_ciph_suites_word + + len2twobytes `printf "%X\n" $((0x$len_ciph_suites + 0x27))` + len_c_hello_word="$LEN_STR" + [[ $DEBUG -ge 4 ]] && echo $len_c_hello_word + + len2twobytes `printf "%X\n" $((0x$len_ciph_suites + 0x2b))` + len_all_word="$LEN_STR" + [[ $DEBUG -ge 4 ]] && echo $len_all_word + + TLS_CLIENT_HELLO=" + # TLS header ( 5 bytes) + ,16, 03, $1 # TLS Version + ,$len_all_word # Length + # Handshake header: + ,01 # Type (x01 for ClientHello) + ,00, $len_c_hello_word # Length ClientHello + ,03, $1 # TLS Version (again) + ,54, 51, 1e, 7a # Unix time since see www.moserware.com/2009/06/first-few-milliseconds-of-https.html + ,de, ad, be, ef # Random 28 bytes + ,31, 33, 07, 00, 00, 00, 00, 00 + ,cf, bd, 39, 04, cc, 16, 0a, 85 + ,03, 90, 9f, 77, 04, 33, d4, de + ,00 # Session ID length + ,$len_ciph_suites_word # Cipher suites length + # Cipher suites + ,$cipher_suites + ,01 # Compression methods length + ,00 # Compression method (x00 for NULL) + " + fi + + code2network "$TLS_CLIENT_HELLO" + data=`echo $NW_STR` + + [[ "$DEBUG" -ge 3 ]] && echo "\"$data\"" + printf -- "$data" >&5 2>/dev/null & + sleep $USLEEP_SND + echo +} + +sockread_serverhello() { + [[ "x$2" = "x" ]] && maxsleep=$MAX_WAITSOCK || maxsleep=$2 + ret=0 + + SOCK_REPLY_FILE=`mktemp /tmp/ddreply.XXXXXX` || exit 7 + dd bs=$1 of=$SOCK_REPLY_FILE count=1 <&5 2>/dev/null & + pid=$! + + while true; do + if ! ps ax | grep -v grep | grep -q $pid; then + break # didn't reach maxsleep yet + kill $pid >&2 2>/dev/null + fi + sleep $USLEEP_REC + maxsleep=$(($maxsleep - 1)) + [[ $maxsleep -le 0 ]] && break + done + + if ps ax | grep -v grep | grep -q $pid; then + # time's up and dd is still alive --> timeout + kill $pid >&2 2>/dev/null + wait $pid 2>/dev/null + ret=3 # means killed + fi + + return $ret +} + +# arg1: name of file with socket reply +display_tls_serverhello() { + # server hello: + # byte 0: 0x16=TLS, 0x15= TLS alert + # byte 1+2: 03, TLS version + # byte 3+4: length all + # byte 5: handshake type (2=hello) TLS alert: level (2=fatal), descr (0x28=handshake failure) + # byte 6+7+8: length server hello + # byte 9+10: 03, TLS version (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2) + # byte 11-14: TLS timestamp + # byte 15-42: random (28 bytes) + # byte 43 : session id length + # byte 44+45+sid-len: cipher suite! + # byte 46+sid-len: compression method: 00: none, 01: deflate + # byte 47+48+sid-len: extension length + + tls_hello_ascii=`hexdump -v -e '16/1 "%02X"' $1` + [[ "$DEBUG" -eq 5 ]] && echo $tls_hello_ascii # one line without any blanks + [[ -z $tls_hello_ascii ]] && return 0 # no server hello received + + # now scrape two bytes out of the reply per byte + tls_hello_initbyte="${tls_hello_ascii:0:2}" # normally this is x16 + tls_hello_protocol="${tls_hello_ascii:2:4}" + tls_len_all=`printf "%d\n" ${tls_hello_ascii:6:4}` + + if [[ $tls_hello_initbyte != "16" ]] ; then + [[ $DEBUG -ge 1 ]] && echo "tls_hello_initbyte: 0x$tls_hello_initbyte" + if [[ $DEBUG -ge 2 ]]; then + echo "tls_hello_protocol: 0x$tls_hello_protocol" + echo "tls_len_all: $tls_len_all" + echo "tls_err_level: ${tls_hello_ascii:10:2}" + echo "tls_err_descr: 0x${tls_hello_ascii:12:2}" + fi + return 1 + fi + + DETECTED_TLS_VERSION=$tls_hello_protocol + + tls_hello="${tls_hello_ascii:10:2}" # normally this is x02 + tls_hello_protocol2="${tls_hello_ascii:18:4}" + tls_hello_time="${tls_hello_ascii:22:8}" + tls_time=`printf "%d\n" 0x$tls_hello_time` + tls_time=`date --date="@$tls_time" "+%Y-%m-%d %r"` + tls_sid_len=`printf "%d\n" 0x${tls_hello_ascii:86:2}` + let sid_offset=88+$tls_sid_len*2 + tls_cipher_suite="${tls_hello_ascii:$sid_offset:4}" + let sid_offset=92+$tls_sid_len*2 + tls_compression_method="${tls_hello_ascii:$sid_offset:2}" + + if [[ $DEBUG -ge 2 ]]; then + echo "tls_hello_initbyte: 0x$tls_hello_initbyte" + echo "tls_hello: 0x$tls_hello" + echo "tls_hello_protocol: 0x$tls_hello_protocol" + echo "tls_hello_protocol2: 0x$tls_hello_protocol2" + echo "tls_len_all: $tls_len_all" + echo "tls_sid_len: $tls_sid_len" + echo "tls_hello_time: 0x$tls_hello_time ($tls_time)" + echo "tls_cipher_suite: 0x$tls_cipher_suite" + echo "tls_compression_method: 0x$tls_compression_method" + fi + + return 0 +} + + +#### main + +[[ -z "$1" ]] && help # hostname + +parse_hn_port "$1" +echo + +for tls_low_byte in "00" "01" "02" "03"; do + + if ! exec 5<> /dev/tcp/$NODE/$PORT; then + echo "`basename $0`: unable to connect to $NODE:$PORT" + exit 2 + fi + + [[ "$DEBUG" -ge 1 ]] && printf "sending client hello...\n" + if [[ "$tls_low_byte" == "03" ]] ; then + socksend_clienthello $tls_low_byte "$TLS12_CIPHER" $SNIHEX + else + socksend_clienthello $tls_low_byte "$TLS_CIPHER" $SNIHEX + fi + + sockread_serverhello 32768 0 + [[ "$DEBUG" -ge 1 ]] && printf "reading server hello...\n" + if [[ "$DEBUG" -eq 3 ]]; then + #xxd -c$COL_WIDTH $SOCK_REPLY_FILE | head -3 + #hexdump -v -e '"%04_ax: " 32/1 "%02X " "\n"' $SOCK_REPLY_FILE | head -6 + hexdump -C $SOCK_REPLY_FILE | head -6 + echo + fi + + display_tls_serverhello "$SOCK_REPLY_FILE" + ret=$? + + # see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL + lines=`cat "$SOCK_REPLY_FILE" 2>/dev/null | hexdump -v -e '"%04_ax: " 32/1 "%02X " "\n"' | wc -l` + + printf "Protokoll "; tput bold; printf "$tls_low_byte"; tput sgr0; printf ": " + + if [[ $ret -eq 1 ]] || [[ $lines -eq 1 ]] ; then + tput setaf 3; echo "NOT available" + ret=1 + else + if [[ 03$tls_low_byte -eq $DETECTED_TLS_VERSION ]]; then + tput setaf 2; echo "available" + ret=0 + else + tput setaf 3; echo -n "NOT available " + [[ $DEBUG -ge 1 ]] && echo -n "send: 0x03$tls_low_byte, returned: 0x$DETECTED_TLS_VERSION" + echo + fi + fi + tput sgr0 + + [[ "$DEBUG" -ge 2 ]] && printf " ($lines lines)" && echo + echo + + # closing fd: + exec 5<&- + exec 5>&- + + rm $SOCK_REPLY_FILE + + echo "--------------------------------------------" + +done + + +echo +exit 0 + +# vim:tw=110:ts=5:sw=5 +# $Id: prototype.tls-protocol-checker.bash,v 1.8 2015/01/08 08:57:26 dirkw Exp $