Newer
Older
invertedlogic / Scripts / Billing / dkim-verify.sh
#!/bin/bash
#
# DKIM verification script
# Written by John Ryland
# Copyright 2015
#
# Usage:  dkim-verify.sh raw-email.txt
#
# Returns:
#          0 if success and verifies OK
#          1 if has DKIM-signature and was processed but verification failed
#         -1 for other errors
#

PROGRAM_NAME=`basename $0`
INPUT=$1

if [ "$#" != "1" ]
then
	echo "$PROGRAM_NAME: Invalid number of parameters"
	exit -1
fi

if [ ! -f "$INPUT" ]
then
	echo "$PROGRAM_NAME: Invalid input file"
	exit -1
fi

mkdir -p $HOME/tmp
TEMPDIR=`mktemp -d -p $HOME/tmp`

if [ ! -d "$TEMPDIR" ]
then
	echo "$PROGRAM_NAME: Error creating temp directory"
	exit -1
fi

cat << EOF > $TEMPDIR/parameters.sh
DKIM_VERSION=0
DKIM_ALGORITHM=rsa-sha256
DKIM_CANONICALIZATION=relaxed/relaxed
DKIM_DOMAIN=domain
DKIM_SUBDOMAIN=sub
DKIM_TIMESTAMP=0
DKIM_BODY_HASH=0
DKIM_HEADERS=0
DKIM_BASE64_SIGNATURE=0
DKIM_SIGNATURE=0
EOF

# Split email text in to 2 parts, the email headers and the email body
cat $INPUT | tr -d '\r' | (
# cat $INPUT | (
  echo -n "" > $TEMPDIR/headers.txt
  while IFS= read -r line
  do
    echo "$line" >> $TEMPDIR/headers.txt
    if [ "$line" == '' ]
    then
	  break
    fi
  done
  echo -n "" > $TEMPDIR/body.txt
  while IFS= read -r line
  do
    echo "$line" >> $TEMPDIR/body.txt
  done
)

# Handles a single trailing empty line
# Perhaps this needs to remove all trailing empty lines?
LAST_LINE=`tail -n 1 $TEMPDIR/body.txt`
if [ "$LAST_LINE" == '' ]
then
	sed -i '$ d' $TEMPDIR/body.txt
fi

# Assuming dkim 'relaxed' canonicalization
cat $TEMPDIR/body.txt | sed 's/[ \t][ \t]*/ /g' | sed 's/[ \t]$//g' | unix2dos > $TEMPDIR/cbody.txt
cat $TEMPDIR/headers.txt | sed -e 's/\(.*\)[ \t]*:[ \t]\(.*\)/\L\1:\E\2/' | sed ':a;N;$!ba;s/[ \t]*\n[ \t][ \t]*/ /g' > $TEMPDIR/cheaders.txt

echo -n "dkim-signature:" > $TEMPDIR/signature.txt
cat $TEMPDIR/cheaders.txt | while read LINE
do
	HEADER_FIELD=`echo "$LINE" | cut -d ':' -f 1`
	HEADER_VALUE=`echo "$LINE" | cut -d ':' -f 2-`
	# echo "  field = $HEADER_FIELD"
	if [ "dkim-signature" == "$HEADER_FIELD" ]
	then
		# echo "  $HEADER_FIELD  =  $HEADER_VALUE"
		echo "$HEADER_VALUE" | sed 's/\;/\;\n/g' | while read ATTRIB_LINE
		do
			ATTRIB=`echo "$ATTRIB_LINE" | tr -d ' '`
			if [ "$ATTRIB" != "" ]
			then
				ATTRIB_NAME=`echo "$ATTRIB" | cut -d '=' -f 1`
				ATTRIB_VALUE=`echo "$ATTRIB" | cut -d '=' -f 2-`
				# echo "   ATTRIB:  $ATTRIB"
				# echo "   -$ATTRIB_NAME- = -$ATTRIB_VALUE-"
				[ "$ATTRIB_NAME" != "b"  ] && echo -n "$ATTRIB_LINE " >> $TEMPDIR/signature.txt
				[ "$ATTRIB_NAME" == "b"  ] && echo -n "$ATTRIB_LINE" | sed 's/b=[^;]*\(.*\)/b=\1/g' >> $TEMPDIR/signature.txt
				[ "$ATTRIB_NAME" == "v"  ] && echo "DKIM_VERSION=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
				[ "$ATTRIB_NAME" == "a"  ] && echo "DKIM_ALGORITHM=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
				[ "$ATTRIB_NAME" == "c"  ] && echo "DKIM_CANONICALIZATION=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
				[ "$ATTRIB_NAME" == "d"  ] && echo "DKIM_DOMAIN=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
				[ "$ATTRIB_NAME" == "s"  ] && echo "DKIM_SUBDOMAIN=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
				[ "$ATTRIB_NAME" == "t"  ] && echo "DKIM_TIMESTAMP=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
				[ "$ATTRIB_NAME" == "bh" ] && echo "DKIM_BODY_HASH=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
				[ "$ATTRIB_NAME" == "h"  ] && echo "DKIM_HEADERS=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
				[ "$ATTRIB_NAME" == "b"  ] && echo "DKIM_BASE64_SIGNATURE=$ATTRIB_VALUE" >> $TEMPDIR/parameters.sh
			fi
		done
	fi
done

if [ "`cat $TEMPDIR/signature.txt`" == "dkim-signature:" ]
then
	echo "$PROGRAM_NAME: No DKIM-Signature found"
	exit -1
fi

# echo -n "b=" >> $TEMPDIR/signature.txt

. ./$TEMPDIR/parameters.sh

if [ "$DKIM_VERSION" != "1" ]
then
	echo "$PROGRAM_NAME: Unsupported DKIM version: $DKIM_VERSION"
	exit -1
fi

BODY_HASH=0

if [ "$DKIM_ALGORITHM" == "rsa-sha256" ]
then
	BODY_HASH=`cat $TEMPDIR/cbody.txt | openssl dgst -sha256 -binary | openssl base64`
elif [ "$DKIM_ALGORITHM" == "rsa-sha1" ]
then
	BODY_HASH=`cat $TEMPDIR/cbody.txt | openssl dgst -sha1 -binary | openssl base64`
else
	echo "$PROGRAM_NAME: Unsupported DKIM signing algorithm: $DKIM_ALGORITHM"
	exit -1
fi

if [ "$DKIM_BODY_HASH" != "$BODY_HASH" ]
then
	echo "$PROGRAM_NAME: DKIM body hash incorrect ($DKIM_BODY_HASH != $BODY_HASH), possibly email body has been tampered with"
	# echo "   DKIM body hash field:  $DKIM_BODY_HASH"
	# echo "   Calculated body hash:  $BODY_HASH"
	# echo "Continuing"
	exit -1
fi
# rm $TEMPDIR/body.txt

# Lookup the public key for the domain
# PUBLIC_KEY=`dig ${DKIM_SUBDOMAIN}._domainkey.${DKIM_DOMAIN} TXT | grep rsa | cut -d '"' -f 2 | cut -d '=' -f 4`
DOMAIN_KEY_DNS_RECORD=`host -t txt ${DKIM_SUBDOMAIN}._domainkey.${DKIM_DOMAIN}`
RECORD_NOT_FOUND=`echo "$DOMAIN_KEY_DNS_RECORD" | grep -c -i "not found"`
if [ "$RECORD_NOT_FOUND" != "0" ]
then
	echo "$PROGRAM_NAME: DKIM domain key not found in DNS"
	# echo "Continuing"
# if TESTING
	exit -1
fi
#	echo "Copying over file"
#	rm $TEMPDIR/key.pub
#	cp misc/key.pub $TEMPDIR/key.pub
#	echo "Copied over file"
#else # !TESTING

# echo "DOMAIN_KEY_DNS_RECORD= -$DOMAIN_KEY_DNS_RECORD-"
# | tr -d '["\\ ]' | sed 's/.*;p=\(.*\)/\1/g' | cut -d ';' -f 1

# echo "DOMAIN_KEY_DNS_RECORD= -`echo "$DOMAIN_KEY_DNS_RECORD" | sed 's/.*[\073 \t\$]p=\(.*\)[\073 \t\$].*/\1/g' | tr -d ' \"'`-"
PUBLIC_KEY=`echo "$DOMAIN_KEY_DNS_RECORD" | tr -d '["\\ ]' | sed 's/.*;p=\(.*\)/\1/g' | cut -d ';' -f 1`
# | cut -d '"' -f 2,4 | tr -d '[" ]' | tr ';' '\n' | grep "p=" | cut -d '=' -f 2`

# TODO: Need to see if the 'test' flag is set eg:  't=y' is in the TXT of the DNS record

# echo PUBLIC_KEY=$PUBLIC_KEY

# echo " DOMAIN_KEY_DNS_RECORD = $DOMAIN_KEY_DNS_RECORD "

LAST_CHAR=`echo $PUBLIC_KEY | tail -c 2`
if [ "$LAST_CHAR" == "\\" ]
then
	# echo "fixing key"
	PUBLIC_KEY=`echo $PUBLIC_KEY | head -c -2`
fi

# Convert to a format that openssl can understand
echo -----BEGIN PUBLIC KEY----- > $TEMPDIR/key.pub
echo $PUBLIC_KEY | fold -w 64 >> $TEMPDIR/key.pub
echo -----END PUBLIC KEY----- >> $TEMPDIR/key.pub
# fi # End TESTING

# Dump the contents of the public key
# echo -n "PUBLIC_MODULUS=" > secret.sh
# openssl asn1parse -in key.pub -strparse 19 -offset 4 | cut -d ':' -f 4 | head -n 1 >> secret.sh
# echo -n "PUBLIC_EXPONENT=" >> secret.sh
# openssl asn1parse -in key.pub -strparse 19 -offset 4 | cut -d ':' -f 4 | tail -n 1 >> secret.sh


echo -n "" > $TEMPDIR/dkim-headers.txt

HEADER_INDEX=1

# echo HEADERS = "$DKIM_HEADERS"
while true
do
	CURRENT_HEADER=`echo "$DKIM_HEADERS :" | cut -d ':' -f $HEADER_INDEX | tr -d '[ \t]' | sed 's/\(.*\)/\L\1/g'`
	if [ "$CURRENT_HEADER" == "" ]
	then
		break
	fi
	# echo " header[$HEADER_INDEX] = -$CURRENT_HEADER-"
	cat $TEMPDIR/cheaders.txt | while read LINE
	do
		HEADER_FIELD=`echo "$LINE" | cut -d ':' -f 1`
		# echo "$HEADER_FIELD"
		if [ "$CURRENT_HEADER" == "$HEADER_FIELD" ]
		then
			echo "$LINE" >> $TEMPDIR/dkim-headers.txt
			# echo "$LINE"
			break
		fi
	done

	HEADER_INDEX=$((HEADER_INDEX + 1))
done

cat $TEMPDIR/signature.txt >> $TEMPDIR/dkim-headers.txt

# cat $TEMPDIR/dkim-headers.txt


LAST_CHAR=`echo $DKIM_BASE64_SIGNATURE | tail -c 2`
if [ "$LAST_CHAR" == "
" ]
then
	# echo "Fixing sig"
	DKIM_BASE64_SIGNATURE=`echo $DKIM_BASE64_SIGNATURE | head -c -2`
fi

# echo "  DKIM_BASE64_SIGNATURE=$DKIM_BASE64_SIGNATURE"

echo "$DKIM_BASE64_SIGNATURE" | fold -w 64 | base64 -d > $TEMPDIR/sign.bin
# cat $TEMPDIR/sign.txt | base64 -d > $TEMPDIR/sign.bin
# cat $TEMPDIR/dkim-headers.txt | head -c -1 | unix2dos > $TEMPDIR/dkim-headers.dos
uniq $TEMPDIR/dkim-headers.txt | head -c -1 | unix2dos > $TEMPDIR/dkim-headers.dos

if [ "$DKIM_ALGORITHM" == "rsa-sha256" ]
then
	echo -n "$PROGRAM_NAME: "
	cat $TEMPDIR/dkim-headers.dos | openssl dgst -keyform pem -sha256 -verify $TEMPDIR/key.pub -signature $TEMPDIR/sign.bin
	exit $?
	# echo "Ret: -$?-"
	# if verified ok, returns 0, else returns 1

	# echo "Hash DOS line endings"
	# # Convert raw hash to DER encoding by pre-pending something
	# (echo '3031300d060960864801650304020105000420' ; cat $TEMPDIR/dkim-headers.dos | sha256sum) | xxd -r -p | base64
	# echo "Verified Hash Method 1"
	# cat $TEMPDIR/sign.bin | openssl rsautl -verify -pkcs -pubin -inkey $TEMPDIR/key.pub | base64
    #
	# CALCULATED_HASH=`cat $TEMPDIR/dkim-headers.dos | sha256sum | tr [a-z] [A-Z] | tr -d ' -'`
	# DECRYPTED_HASH=`cat $TEMPDIR/sign.bin | openssl rsautl -verify -pkcs -pubin -inkey $TEMPDIR/key.pub | openssl asn1parse -inform der -offset 17 | cut -d ':' -f 4`
	# if [ "$CALCULATED_HASH" != "$DECRYPTED_HASH" ]
	# then
	# 	echo "$PROGRAM_NAME: Failure, mismatched hashes"
	#	echo "    CALCULATED_HASH = -$CALCULATED_HASH-"
	#	echo "    DECRYPTED_HASH  = -$DECRYPTED_HASH-"
	# fi

elif [ "$DKIM_ALGORITHM" == "rsa-sha1" ]
then
	echo -n "$PROGRAM_NAME: "
	cat $TEMPDIR/dkim-headers.dos | openssl dgst -keyform pem -sha1 -verify $TEMPDIR/key.pub -signature $TEMPDIR/sign.bin
	# echo "Ret: -$?-"
	# if verified ok, returns 0, else returns 1
	exit $?
else
	echo "$PROGRAM_NAME: Unsupported DKIM signing algorithm: $DKIM_ALGORITHM"
	exit -1
fi