#!/bin/bash
# Quick 'n' dirty generator for extending wireshark cipher suites
# Author: Peter Wu <lekensteyn@gmail.com>

set -u

warn() {
	local cb= ce=

	# add color only if printing to terminal
	if [ -t 2 ]; then
		cb='\e[1;91m' # bright red
		ce='\e[m'
	fi

	printf "$cb%s$ce\n" "$*" >&2
}

# printf conversions with padding
_KEX=%-$(wc -L <<<KEX_RSA_PSK,)s
_ENC=%-$(wc -L <<<ENC_CAMELLIA256,)s
_IV_SZ=%2d
_KEYSZ=%3d
_EXP_KEYSZ=%3d
_DIG=%-$(wc -L <<<DIG_SHA384,)s
_MODE=%-$(wc -L <<<STREAM)s

p() {
	local tmp kex keysize exp_keysize=0 dig diglen mode us_export blocksize hexid
	[ $# -gt 0 ] || return
	num=$(($2*0x100 + $3))
	hexid=000$(echo "obase=16;$num" | bc)
	hexid=0x${hexid: -4}

	# ignore TLS_NULL_WITH_NULL_NULL and TLS_EMPTY_RENEGOTIATION_INFO_SCSV
	case $hexid in
	0x0000|0x00FF) return ;;
	esac

	tmp=${1%%_WITH_*}
	tmp=${tmp%_EXPORT}
	tmp=${tmp%_EXPORT1024}
	tmp=${tmp#TLS_}
	case $tmp in
	PSK)		kex=PSK ;;
	RSA_PSK)	kex=RSA_PSK ;;
	RSA)		kex=RSA ;;
	DH_*|DHE_*)	kex=DH ;;
	ECDH_*|ECDHE_*)	kex=ECDH ;;
	*)
		warn "Unknown kex in $hexid $1 (tmp=$tmp)"
		return
		;;
	esac

	# HACK HACK HACK
	tmp=$1
	# prevent seeing 56-bit export cipher as 64-bit DES
	tmp=${tmp/EXPORT1024_WITH_DES_CBC_/EXPORT1024_WITH_DES_56_CBC_}
	tmp=${tmp#*WITH_}
	cipher=${tmp%%_*}
	tmp=${tmp/_CBC_/_}
	tmp=${tmp#${cipher}_} # now continue for keysize
	keysize=${tmp%%_*}
	[[ $keysize != [0-9]* ]] || cipher=$cipher$keysize
	case $cipher in
	RC[24]40)	keysize=128; exp_keysize=40 ;;
	RC[24]56)	keysize=128; exp_keysize=56 ;;
	*128|*256) ;;
	SEED|IDEA)	keysize=128 ;;
	NULL)	keysize=0 ;;
	DES)	keysize=64 ;;
	DES40)	keysize=64; exp_keysize=40 ;;
	DES56)	keysize=64; exp_keysize=56 ;;
	3DES)
		if [[ $keysize == EDE ]]; then
			keysize=192
		else
			warn "Invalid keysize in $hexid $1 (cipher=$cipher, keysize=$keysize)"
			#return
		fi
		;;
	*)
		warn "Invalid keysize in $hexid $1 (cipher=$cipher, keysize=$keysize)"
		return
		;;
	esac
	# assume same size for actual and algorithm key size
	[ $exp_keysize -gt 0 ] || exp_keysize=$keysize

	case $cipher in
	AES128)
		cipher=AES
		;;
	DES|3DES|RC4|RC2|IDEA|AES256|CAMELLIA128|CAMELLIA256|NULL|IDEA) ;;
	DES40|DES56)	cipher=DES ;;
	SEED*)	cipher=SEED ;;
	RC240|RC256)	cipher=RC2 ;;
	RC440|RC4128|RC456)	cipher=RC4 ;;
	*)
		warn "Unknown cipher $cipher in $hexid $1"
		return
		;;
	esac

	# For block sizes, see:
	# SSL 3.0: http://tools.ietf.org/html/rfc6101#page-55
	# TLS 1.0: http://tools.ietf.org/html/rfc2246#page-62
	# TLS 1.1: http://tools.ietf.org/html/rfc4346#page-69
	# TLS 1.2: http://tools.ietf.org/html/rfc5246#page-84
	# GCM's IV size is always 4 regardless of underlying block cipher
	case $1 in
	*_GCM_*|*_CCM|*_CCM_8)
		blocksize=4 ;;
	*)
		case $cipher in
		AES|AES256|CAMELLIA128|CAMELLIA256|SEED)
			blocksize=16 ;;
		DES|3DES|IDEA)
			blocksize=8 ;;
		RC2)
			blocksize=8 ;;
		RC4|NULL)
			# N/A for stream cipher RC4
			blocksize=0 ;;
		*)
			warn "Unknown cipher $cipher in $hexid $1"
			return
			;;
		esac
		;;
	esac

	# diglen is unused here, but shown for clarity (when a new digest is
	# added to packet-ssl-utils)
	dig=${1##*_}
	case $dig in
	MD5)	diglen=16 ;;
	SHA)	diglen=20 ;;
	SHA256)	diglen=32 ;;
	SHA384) diglen=48 ;;
	*)
		# CCM (counter with CBC-MAC) includes MAC already.
		if [[ $1 == *_CCM_8 ]] || [[ $1 == *_CCM ]]; then
			dig=NA
		else
			warn "Unknown dig in $hexid $1 (dig=$dig)"
			return
		fi
		;;
	esac

	us_export=0
	if [[ $1 == *_EXPORT_* ]]; then
		us_export=1
		[ $exp_keysize -lt $keysize ] || \
		warn "Export cipher, actual keysize may not be accurate: $hexid $1"
	fi

	case $1 in
	*_GCM_*)
		mode=GCM ;;
	*_CCM)
		mode=CCM ;;
	*_CCM_8)
		mode=CCM_8 ;;
	*)
		case $cipher in
		AES|AES256|DES|3DES|CAMELLIA128|CAMELLIA256|SEED|IDEA|RC2)
			mode=CBC ;;
		RC4|NULL)
			mode=STREAM ;;
		*)
			warn "Unknown mode in $hexid $1 (cipher=$cipher)"
			return
			;;
		esac
		;;
	esac

	printf \
"    {$hexid,${_KEX}${_ENC}${_IV_SZ},${_KEYSZ},${_EXP_KEYSZ},${_DIG} MODE_${_MODE}},   /* $1 */\n" \
	"KEX_$kex," \
	"ENC_$cipher," \
	$blocksize \
	$keysize \
	$exp_keysize \
	"DIG_$dig," \
	"$mode"
}

# expects a line like:
#  CipherSuite TLS_RSA_WITH_CAMELLIA_128_CBC_SHA         = { 0x00,0x41 };
sed 's/CipherSuite//;s/,/ /g' | grep -v '^[ \t]*$' | tr -d '={};' | while read name n1 n2 rem; do
	# for <number> <name>, like suites.txt
	if [ -z "$n2$rem" ] && [[ $name =~ ^[0-9]+|0[Xx][0-9a-fA-F]$ ]]; then
		p "$n1" 0 "$name"
		continue
	fi

	if [ -n "$rem" ]; then
		warn "Error! Invalid line: $name $n1 $n2 $rem"
		continue
	fi
	p "$name" "$n1" "$n2"
done
exit

# from http://tools.ietf.org/html/rfc5932, Proposed Cipher Suites

p TLS_RSA_WITH_CAMELLIA_128_CBC_SHA         0x00 0x41
p TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA      0x00 0x42
p TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA      0x00 0x43
p TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA     0x00 0x44
p TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA     0x00 0x45
p TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA     0x00 0x46
p 
p TLS_RSA_WITH_CAMELLIA_256_CBC_SHA         0x00 0x84
p TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA      0x00 0x85
p TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA      0x00 0x86
p TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA     0x00 0x87
p TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA     0x00 0x88
p TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA     0x00 0x89
p 
p 
p TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256      0x00 0xBA
p TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256   0x00 0xBB
p TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256   0x00 0xBC
p TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256  0x00 0xBD
p TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256  0x00 0xBE
p TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256  0x00 0xBF
p 
p TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256      0x00 0xC0
p TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256   0x00 0xC1
p TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256   0x00 0xC2
p TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256  0x00 0xC3
p TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256  0x00 0xC4
p TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256  0x00 0xC5
