#!/bin/sh
# shell shock all-clear: compatible to ash dash ksh93 lksh mksh pdksh shish zsh
# needs bc or bash for calculation

# ssltest.sh / ssltest-slow.sh
# Frank Bergmann, www.tuxad.com, 10/2014 - 5/2016, GNU GPL v2
# Use openssl to test peer for supporting ssl ciphers

# RHEL 5 postfix defaults:
#   [postfix-2.3.3]$ grep -r DEF src/global/mail_params.h|grep MAND_CIPH
#   #define DEF_SMTPD_TLS_MAND_CIPH "medium"
#   #define DEF_SMTP_TLS_MAND_CIPH  "medium"
#   #define DEF_LMTP_TLS_MAND_CIPH  "medium"
#   [postfix-2.3.3]$ grep -r DEF_TLS_.*_CLIST src/global/mail_params.h
#   #define DEF_TLS_HIGH_CLIST      "!EXPORT:!LOW:!MEDIUM:ALL:+RC4:@STRENGTH"
#   #define DEF_TLS_MEDIUM_CLIST    "!EXPORT:!LOW:ALL:+RC4:@STRENGTH"
#   #define DEF_TLS_LOW_CLIST       "!EXPORT:ALL:+RC4:@STRENGTH"
#   #define DEF_TLS_EXPORT_CLIST    "ALL:+RC4:@STRENGTH"
#   #define DEF_TLS_NULL_CLIST      "!aNULL:eNULL+kRSA"
# e.g. simulate default RHEL 5 postfix client:
# SSLCipherSuite=$(openssl ciphers '!EXPORT:!LOW:ALL:+RC4:@STRENGTH') ./ssltest.sh 127.0.0.1 25 smtp

[ $# -lt 2 ] && {
  echo "ssltest.sh: show supported ciphers of remote server"
  echo "usage: [SSLCipherSuite='ECDHE...:!aNULL'] ssltest.sh ip-address port [protocol]   # protocol is i.e. smtp for STARTTLS"
  echo 'examples:'
  echo "ssltest.sh 127.0.0.1 443"
  echo "ssltest-slow.sh 127.0.0.1 443   # symlinked to ssltest.sh; for slow, broken or tarpitting clients"
  echo 'SSLCipherSuite=ALL '$0' 127.0.0.1 993'
  echo 'SSLCipherSuite=$(openssl ciphers '\''!EXPORT:!LOW:ALL:+RC4:@STRENGTH'\''|tr ":" " ") '$0' 127.0.0.1 25 smtp'
  exit 1
}

# configurable basic DHE ciphers for getting DH params:
DHE_CIPHERS="
DHE-RSA-AES256-GCM-SHA384
DHE-RSA-AES256-SHA256
DHE-RSA-AES256-SHA
DHE-RSA-CAMELLIA256-SHA
DHE-RSA-AES128-GCM-SHA256
DHE-RSA-AES128-SHA256
DHE-RSA-AES128-SHA
DHE-RSA-CAMELLIA128-SHA
EDH-RSA-DES-CBC3-SHA
DHE-RSA-SEED-SHA
EDH-RSA-DES-CBC-SHA
"

#
# setup
#
exec 2>/dev/null
OPENSSL=openssl
#type openssl1 &>/dev/null && OPENSSL=openssl1
openssl1 -h >/dev/null && OPENSSL=openssl1
export OPENSSL

SLOW=NO
echo $0|grep -q slow && SLOW=YES
SLEEP1=.1
SLEEP2=.5
if [ $SLOW = "YES" ]
then
  SLEEP1=2
  SLEEP2=31
fi
SERVER=$1
[ "$SERVER" ] || usage
PORT=$2
[ "$PORT" ] || usage
CONNECT_STRING=$SERVER:$PORT
if [ "$3" ]
then
  STARTTLS=-starttls
  PROT=$3
fi
DATE=`date -R`
if [ "$SSLCipherSuite" ]
then
  CIPHER_SUITE=`echo $SSLCipherSuite|tr ' ' ':'`
  echo $DATE, Testing: $SERVER $PORT $PROT with ciphers $CIPHER_SUITE
else
  CIPHER_SUITE="`$OPENSSL ciphers 'ALL:eNULL' | tr ' ' ':'`"
  echo $DATE, Testing: $SERVER $PORT $PROT
fi

#
# protocols
#
echo Testing protocols:
for f in `$OPENSSL s_client --help 2>&1|awk '/just use/{if($1!~/dtls/)print$1" "$5}'|env -i sort|tr ' ' ':'`
do
  OPTION=`echo $f|cut -d: -f1`
  SSLPROTOCOL=`echo $f|cut -d: -f2`
  if [ $SSLPROTOCOL = "SSLv2" ]
  then
    OUTPUT="`sh -c '( sleep 5; kill $$; ) & exec '$OPENSSL' s_client -cipher '\''ALL:!NULL'\'' '$STARTTLS' '$PROT' -connect '$CONNECT_STRING' '$OPTION' 2>&1 </dev/null'|grep -A1 Protocol|grep Cipher`"
  else
    OUTPUT="`$OPENSSL s_client -cipher 'ALL:!NULL' $STARTTLS $PROT -connect $CONNECT_STRING $OPTION 2>&1 </dev/null|grep -A1 Protocol|grep Cipher`"
  fi
  ACT_CIPHER=`echo $OUTPUT|sed 's,.* ,,'|grep -v '0000'|tr -d '()'`
  if [ "$ACT_CIPHER" ]
  then
    echo $SSLPROTOCOL: YES
    LAST_SUPP_PROTOCOL=$SSLPROTOCOL
    LAST_SUPP_OPTION=$OPTION
  else
    echo $SSLPROTOCOL: NO
  fi
  sleep $SLEEP1
done
SSLPROTOCOL=$LAST_SUPP_PROTOCOL
SSLPROTOCOLOPTION=$LAST_SUPP_OPTION
[ "$SSLPROTOCOL" != "" ] || exit 0

#
# hostname (no check of: validity, CRL, chain and more)
#
echo "Checking names (CN/SAN):"
NAMES1=`$OPENSSL s_client -cipher $CIPHER_SUITE $STARTTLS $PROT -connect $CONNECT_STRING </dev/null 2>&1|$OPENSSL x509 -text -noout|egrep -A1 'Subject:.*CN=|Subject Alternative Name:'|egrep "CN=|DNS:"|sed 's,.*CN=,,;s,DNS:,,g'|tr -d ,`
NAMES2=`for f in $NAMES1;do echo $f;done|sort -u`
CN_SAN_NAMES=`echo $NAMES2`
MATCH="NO MATCH"
for f in $CN_SAN_NAMES
do
  if echo $f|grep -q ^$SERVER'$'
  then
    MATCH=MATCH
  else
    if echo $f|fgrep -q '*'
    then
      REGEX=.`echo $f|sed 's,\.,\\\\.,g'`
      echo $SERVER|grep -q "$REGEX" && MATCH="MATCH WILDCARD"
      break
    fi
  fi
done
echo "CN/SAN names: $CN_SAN_NAMES ($MATCH)"
sleep $SLEEP1

#
# ciphers
#
echo Testing ciphers using protocol $SSLPROTOCOL:
while true
do
  # don't specify protocol and don't do separate checks on every protocol, use "highest" protocol
  OUTPUT="`$OPENSSL s_client -cipher $CIPHER_SUITE $STARTTLS $PROT $SSLPROTOCOLOPTION -pause -bugs -connect $CONNECT_STRING 2>&1 </dev/null|egrep -A1 'Server Temp Key|Protocol'|egrep 'Server Temp Key|Cipher'|sed 's,.*Server Temp Key *:* *,,;s, *Cipher *:* *, / ,'`"
  ACT_CIPHER=`echo $OUTPUT|sed 's,.* ,,'|grep -v '0000'|tr -d '()'`
  TEMP_KEY=`echo $OUTPUT|sed 's, *\/ .*,,'`
  sleep $SLEEP2
  if [ "$ACT_CIPHER" ]
  then
    echo $TEMP_KEY|grep -q ^DH, && DHE_CIPHERS="$DHE_CIPHERS $ACT_CIPHER"
    echo `$OPENSSL ciphers $ACT_CIPHER -v|head -n 1``[ "$TEMP_KEY" ] && echo ' TempKey: '$TEMP_KEY`
    NEWSUITE=`$OPENSSL ciphers $CIPHER_SUITE -v|grep -v "^$ACT_CIPHER "|awk '{print$1}'`
    CIPHER_SUITE=`echo $NEWSUITE|tr ' ' ':'`
  else
    break
  fi
done

#
# DH params
#
DHE_CIPHER_LIST=`echo $DHE_CIPHERS|tr ' ' :`
KEY_EXCHANGE=`$OPENSSL s_client $STARTTLS $PROT -connect $CONNECT_STRING -msg -cipher $DHE_CIPHER_LIST </dev/null 2>/dev/null|sed -n '/<<<.*ServerKeyExchange/,/<<<.*ServerHelloDone/p'|grep -v '^<<<'|tr '[a-z]' '[A-Z]'`
if [ x"$KEY_EXCHANGE"x != "xx" ] 
then
echo Getting DH param modulus:
LEN_STRING=`echo $KEY_EXCHANGE|cut -d" " -f5-6|tr -d ' '`
# len calculation
if bc -h >/dev/null
then
  # compatible
  LEN_BYTES=`echo "ibase=16;$LEN_STRING"|bc`
  LEN=`echo '8*'$LEN_BYTES|bc`
  COLLIMIT=`echo 6+$LEN_BYTES|bc`
  DH_BYTES=`echo $KEY_EXCHANGE|cut -d" " -f7-$COLLIMIT`
else
  # fallback to bash internal function
  LEN_BYTES=$((16#$LEN_STRING))
  LEN=$((8*$LEN_BYTES))
  DH_BYTES=$(echo $KEY_EXCHANGE|cut -d" " -f7-$((6+$LEN_BYTES)))
fi
# print it as C code (modulus of "openssl dhparam -C XXX")
echo 'static unsigned char dh'$LEN'_p[]={'
POS=0
COUNT=0
for f in $DH_BYTES
do
  let COUNT=COUNT+1
  [ $POS -eq 0 ] && echo -e '    \c'
  echo -e "0x$f\c"
  [ $COUNT -ne $LEN_BYTES ] && echo -e ', \c'
  let POS=POS+1
  if [ $POS -eq 12 ]
  then
    POS=0
    echo
  fi
done
echo ' };'
fi

exit 0
