From 0676866e91d7b0499e59ed97553f22729bca7f15 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Wed, 28 Sep 2016 17:15:37 -0400 Subject: [PATCH] Add option for extract data from SSLv2 ServerHello This PR adds the option for `parse_sslv2_serverhello()` to extract information from the ServerHello (server key size and cipher suites supported) and write the information to `$TMPFILE` as well as to write the server's certificate to `$HOSTCERT`. --- testssl.sh | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 2 deletions(-) diff --git a/testssl.sh b/testssl.sh index c06dc0e..1356e12 100755 --- a/testssl.sh +++ b/testssl.sh @@ -637,6 +637,38 @@ else } fi +# arg1: An ASCII-HEX string +# arg2: file name +# Append $arg1 in binary format to $arg2 +asciihex_to_binary_file(){ + local string="$1" + local file="$2" + local -i len + local -i i ip2 ip4 ip6 ip8 ip10 ip12 ip14 + local -i remainder + + len=${#string} + [[ $len%2 -ne 0 ]] && return 1 + + for (( i=0; i <= len-16 ; i=i+16 )); do + ip2=$i+2; ip4=$i+4; ip6=$i+6; ip8=$i+8; ip10=$i+10; ip12=$i+12; ip14=$i+14 + echo -e -n "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}\x${string:ip12:2}\x${string:ip14:2}" >> "$file" + done + + ip2=$i+2; ip4=$i+4; ip6=$i+6; ip8=$i+8; ip10=$i+10; ip12=$i+12; ip14=$i+14 + remainder=$len-$i + case $remainder in + 2) echo -e -n "\x${string:i:2}" >> "$file" ;; + 4) echo -e -n "\x${string:i:2}\x${string:ip2:2}" >> "$file" ;; + 6) echo -e -n "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}" >> "$file" ;; + 8) echo -e -n "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}" >> "$file" ;; + 10) echo -e -n "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}" >> "$file" ;; + 12) echo -e -n "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}" >> "$file" ;; + 14) echo -e -n "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}\x${string:ip12:2}" >> "$file" ;; + esac + return 0 +} + ###### check code starts here ###### # determines whether the port has an HTTP service running or not (plain TLS, no STARTTLS) @@ -5401,8 +5433,114 @@ sockread_serverhello() { return $? } +get_pub_key_size() { + local pubkey pubkeybits + local -i i len1 len + local tmppubkeyfile + + # OpenSSL displays the number of bits for RSA and ECC + pubkeybits=$($OPENSSL x509 -noout -pubkey -in $HOSTCERT | $OPENSSL pkey -pubin -text | grep -aw "Public-Key:" | sed -e 's/.*(//' -e 's/)//') + if [[ -n $pubkeybits ]]; then + echo "Server public key is $pubkeybits" >> $TMPFILE + else + # This extracts the public key for DSA, DH, and GOST + tmppubkeyfile=$(mktemp $TEMPDIR/pubkey.XXXXXX) || return 7 + $OPENSSL x509 -noout -pubkey -in $HOSTCERT | $OPENSSL pkey -pubin -outform DER -out "$tmppubkeyfile" + pubkey=$(hexdump -v -e '16/1 "%02X"' "$tmppubkeyfile") + rm $tmppubkeyfile + # Skip over tag and length of subjectPublicKeyInfo + i=2 + len1="0x${pubkey:i:2}" + if [[ $len1 -lt 0x80 ]]; then + i=$i+2 + else + len1=$len1-0x80 + i=$i+2*$len1+2 + fi + + # Skip over algorithm field + i=$i+2 + len1="0x${pubkey:i:2}" + i=$i+2 + if [[ $len1 -lt 0x80 ]]; then + i=$i+2*$len1 + else + case $len1 in + 129) len="0x${pubkey:i:2}" ;; + 130) len="0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + ;; + 131) len="0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + ;; + 132) len="0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + ;; + esac + i=$i+2+2*$len + fi + + # Next is the public key BIT STRING. Skip over tag, length, and number of unused bits. + i=$i+2 + len1="0x${pubkey:i:2}" + if [[ $len1 -lt 0x80 ]]; then + i=$i+4 + else + len1=$len1-0x80 + i=$i+2*$len1+4 + fi + + # Now get the length of the public key + i=$i+2 + len1="0x${pubkey:i:2}" + i=$i+2 + if [[ $len1 -lt 0x80 ]]; then + len=$len1 + else + case $len1 in + 129) len="0x${pubkey:i:2}" ;; + 130) len="0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + ;; + 131) len="0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + i=$i+2 + len=256*$len+"0x${pubkey:i:2}" + ;; + 132) len="0x${pubkey:i:2}" + i=$i+2 + len=256*"0x${pubkey:i:2}" + i=$i+2 + len=256*"0x${pubkey:i:2}" + i=$i+2 + len=256*"0x${pubkey:i:2}" + ;; + esac + fi + len=8*$len # convert from bytes to bits + pubkeybits="$(printf "%d" $len)" + echo "Server public key is $pubkeybits bit" >> $TMPFILE + fi +} + # arg1: name of file with socket reply +# arg2: true if entire server hello should be parsed parse_sslv2_serverhello() { + local ret v2_hello_ascii v2_hello_initbyte v2_hello_length + local v2_hello_handshake v2_cert_type v2_hello_cert_length + local v2_hello_cipherspec_length tmp_der_certfile + local -i certificate_len nr_ciphers_detected offset i # server hello: in hex representation, see below # byte 1+2: length of server hello 0123 # 3: 04=Handshake message, server hello 45 @@ -5417,6 +5555,9 @@ parse_sslv2_serverhello() { # [cipher spec length] ==> ciphers GOOD: HERE ARE ALL CIPHERS ALREADY! local ret=3 + if [[ "$2" == "true" ]]; then + echo "======================================" > $TMPFILE + fi v2_hello_ascii=$(hexdump -v -e '16/1 "%02X"' $1) [[ "$DEBUG" -ge 5 ]] && echo "$v2_hello_ascii" @@ -5428,6 +5569,7 @@ parse_sslv2_serverhello() { v2_hello_initbyte="${v2_hello_ascii:0:1}" # normally this belongs to the next, should be 8! v2_hello_length="${v2_hello_ascii:1:3}" # + 0x8000 see above v2_hello_handshake="${v2_hello_ascii:4:2}" + v2_cert_type="${v2_hello_ascii:8:2}" v2_hello_cert_length="${v2_hello_ascii:14:4}" v2_hello_cipherspec_length="${v2_hello_ascii:18:4}" @@ -5445,10 +5587,36 @@ parse_sslv2_serverhello() { if [[ $DEBUG -ge 3 ]]; then echo "SSLv2 server hello length: 0x0$v2_hello_length" + echo "SSLv2 certificate type: 0x$v2_cert_type" echo "SSLv2 certificate length: 0x$v2_hello_cert_length" echo "SSLv2 cipher spec length: 0x$v2_hello_cipherspec_length" fi fi + + certificate_len=2*$(hex2dec "$v2_hello_cert_length") + [[ -e $HOSTCERT ]] && rm $HOSTCERT + [[ -e $TEMPDIR/intermediatecerts.pem ]] && rm $TEMPDIR/intermediatecerts.pem + if [[ "$2" == "true" ]] && [[ "$v2_cert_type" == "01" ]] && [[ "$v2_hello_cert_length" != "00" ]]; then + tmp_der_certfile=$(mktemp $TEMPDIR/der_cert.XXXXXX) || return $ret + asciihex_to_binary_file "${v2_hello_ascii:26:certificate_len}" "$tmp_der_certfile" + $OPENSSL x509 -inform DER -in $tmp_der_certfile -outform PEM -out $HOSTCERT + rm $tmp_der_certfile + get_pub_key_size + echo "======================================" >> $TMPFILE + fi + + # Output list of supported ciphers + if [[ "$2" == "true" ]]; then + let offset=26+$certificate_len + nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3)) + for (( i=0 ; i> $TMPFILE + let offset=$offset+6 + done + echo "======================================" >> $TMPFILE + + tmpfile_handle $FUNCNAME.txt + fi return $ret } @@ -5740,6 +5908,7 @@ parse_tls_serverhello() { #arg1: list of ciphers suites or empty +#arg2: "true" if full server response should be parsed. sslv2_sockets() { local ret local client_hello cipher_suites len_client_hello @@ -5790,7 +5959,7 @@ sslv2_sockets() { outln fi - parse_sslv2_serverhello "$SOCK_REPLY_FILE" + parse_sslv2_serverhello "$SOCK_REPLY_FILE" "$2" ret=$? close_socket @@ -9149,7 +9318,7 @@ lets_roll() { determine_rdns determine_service "$1" # any starttls service goes here - $do_tls_sockets && [[ $TLS_LOW_BYTE -eq 22 ]] && { sslv2_sockets; echo "$?" ; exit 0; } + $do_tls_sockets && [[ $TLS_LOW_BYTE -eq 22 ]] && { sslv2_sockets "" "true"; echo "$?" ; exit 0; } $do_tls_sockets && [[ $TLS_LOW_BYTE -ne 22 ]] && { tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER"; echo "$?" ; exit 0; } $do_test_just_one && test_just_one ${single_cipher}