#!/usr/bin/env bash
# x509 -- show information about an X.509 certificate

. lib.bash || exit

usage() {
	echo "Usage: $progname [-LRgnos] [-v] <file>"
	echo
	echo_opt "-L" "input is a certificate revocation list (CRL)"
	echo_opt "-R" "input is a certificate request (CSR)"
	echo_opt "-g" "display using GnuTLS 'certtool'"
	echo_opt "-n" "display using NSS 'certutil'"
	echo_opt "-o" "display using OpenSSL 'x509'"
	echo_opt "-s" "display using Step"
	echo_opt "-v" "show some normally-hidden details"
}

highlight() {
	if [ -t 1 ]; then
		sed "$1 s/.*/"$'\e[1m&\e[m'"/"
	else
		cat
	fi
}

mode=""
opt_verbose=0
opt_certreq=0
opt_crl=0

while getopts :LRgnosv OPT; do
	case $OPT in
	L) opt_crl=1;;
	R) opt_certreq=1;;
	g) mode=certtool;;
	n) mode=nsscertutil;;
	o) mode=openssl;;
	s) mode=step;;
	v) let opt_verbose++;;
	*) lib:die_getopts;;
	esac
done; shift $[OPTIND-1]

if (( ! $# )); then
	set -- /dev/stdin
fi

if [ "$mode" ]; then
	:
elif have openssl; then
	mode=openssl
elif have certtool; then
	mode=certtool
elif have step; then
	mode=step
elif have certutil; then
	mode=nsscertutil
else
	vdie "no supported tool found"
fi

for file; do
	tempfile=""
	if [[ $file == - || $file == /dev/stdin ]]; then
		tempfile=$(mktemp /tmp/x509.XXXXXXXX)
		file=$tempfile
		cat > "$tempfile"
	fi

	der=0
	if [[ "$(head -c 2 "$file")" == $'\x30\x82' ]]; then
		debug "- is probably in DER format"
		der=1
	fi
	if grep -Eqs -- "-----BEGIN (NEW )?CERTIFICATE REQUEST-----" "$file"; then
		opt_certreq=1
	fi

	case $mode in
	certtool)
		arg=""
		if (( der )); then
			arg="--inder"
		fi
		if (( opt_crl )); then
			arg+=" --crl-info"
		elif (( opt_certreq )); then
			arg+=" --crq-info"
		else
			arg+=" --certificate-info"
		fi
		certtool $arg < "$file" |
			sed -r '/^-----BEGIN/,/^-----END/d' |
			awk '
				BEGIN {m=1}
				{c=0}
				/^\t*([0-9a-f][0-9a-f]:)*[0-9a-f][0-9a-f]$/ {c=1}
				# Remember indentation of the line
				c {ws=$0; sub(/[^ \t].*/, "", ws)}
				c {n++; p=$0}
				# Exactly 1 more line? Print it directly
				!c && n==m {print p}
				!c && n>m {print ws "\033[3m(" n-m+1 " more lines)\033[m"}
				!c {n=0}
				n<m {print}
			' |
			if (( opt_verbose )); then
				cat
			else
				awk '/^Other Information:/ {m=1} /^$/ {m=0} !m {print}'
			fi |
			highlight '/^X\.509 Certificate /'
		;;
	openssl)
		arg=""
		if (( der )); then
			arg+=" -inform DER"
		fi
		arg+=" -nameopt RFC2253"
		arg+=" -nameopt sep_comma_plus_space"
		if (( opt_crl )); then
			openssl crl -noout -text $arg < "$file"
		elif (( opt_certreq )); then
			openssl req -noout -text -reqopt no_sigdump $arg < "$file"
		else
			openssl x509 -noout -text -certopt no_sigdump $arg < "$file"
		fi |
		awk '
			/Exponent:|X509v3 extensions:/ {m=0}
			!m {print}
			/Modulus:|Public-Key:/ {m=1}
		' |
		highlight '/^Certificate/'
		;;
	nsscertutil)
		if (( opt_crl )); then
			vdie "don't know how to dump revocation lists using NSS"
		elif (( opt_certreq )); then
			vdie "don't know how to dump certificate requests using NSS"
		fi
		tempdb=$(mktemp -d /tmp/x509.XXXXXXXX)
		name="Temporary X.509 certificate"
		certutil -A -d "$tempdb" -i "$file" -n "$name" -t ",,"
		certutil -L -d "$tempdb" -n "$name" |
			awk '/Exponent:/ {m=0} !m {print} /Modulus:/ {m=1}' |
			awk '!m {print} /Signature:/ {m=1} !/:$/ {m=0}' |
			awk '/Certificate Trust Flags:/ {m=1} !m {print} /^$/ {m=0}' |
			highlight '/^Certificate/'
		rm -rf "$tempdb"
		;;
	step)
		step certificate inspect < "$file" | awk '
			/Curve:|Exponent:|X509v3 extensions:/ {m=0}
			!m {print}
			/Modulus:|Public-Key:/ {m=1}
			/^    Signature Algorithm:/ {m=1}
		'
		;;
	esac

	if [[ $tempfile ]]; then
		rm -f "$tempfile"
	fi
done
