#! /bin/bash

set -e

DOMAIN=example.com
HOST=mail.${DOMAIN}
TEST=./tls_dane

key() {
    local key=$1; shift

    if [ ! -f "${key}.pem" ]; then
    	openssl genpkey 2>/dev/null \
	    -paramfile <(openssl ecparam -name prime256v1) \
	    -out "${key}.pem"
    fi
}

req() {
    local key=$1; shift
    local cn=$1; shift

    key "$key"
    openssl req -new -sha256 -key "${key}.pem" 2>/dev/null \
	-config <(printf "[req]\n%s\n%s\n[dn]\nCN=%s\n" \
		   "prompt = no" "distinguished_name = dn" "${cn}")
}

req_nocn() {
    local key=$1; shift

    key "$key"
    openssl req -new -sha256 -subj / -key "${key}.pem" 2>/dev/null \
	-config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \
		   "distinguished_name = dn")
}

cert() {
    local cert=$1; shift
    local exts=$1; shift

    openssl x509 -req -sha256 -out "${cert}.pem" 2>/dev/null \
	-extfile <(printf "%s\n" "$exts") "$@"
}

genroot() {
    local cn=$1; shift
    local key=$1; shift
    local cert=$1; shift
    local skid=$1; shift
    local akid=$1; shift

    exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true")
    key "$key"
    req "$key" "$cn" |
    	cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days 30
}

genca() {
    local cn=$1; shift
    local key=$1; shift
    local cert=$1; shift
    local skid=$1; shift
    local akid=$1; shift
    local ca=$1; shift
    local cakey=$1; shift

    exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true")
    key "$key"
    req "$key" "$cn" |
    	cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
	    -set_serial 2 -days 30 "$@"
}

genee() {
    local cn=$1; shift
    local key=$1; shift
    local cert=$1; shift
    local ca=$1; shift
    local cakey=$1; shift

    exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
	    "subjectKeyIdentifier = hash" \
	    "authorityKeyIdentifier = keyid, issuer" \
	    "basicConstraints = CA:false" \
	    "extendedKeyUsage = serverAuth" \
	    "subjectAltName = @alts" "DNS=${cn}")
    key "$key"
    req "$key" "$cn" |
    	cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
	    -set_serial 2 -days 30 "$@"
}

genss() {
    local cn=$1; shift
    local key=$1; shift
    local cert=$1; shift

    exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
	    "subjectKeyIdentifier   = hash" \
	    "authorityKeyIdentifier = keyid, issuer" \
	    "basicConstraints = CA:true" \
	    "extendedKeyUsage = serverAuth" \
	    "subjectAltName = @alts" "DNS=${cn}")
    key "$key"
    req "$key" "$cn" |
	cert "$cert" "$exts" -set_serial 1 -days 30 -signkey "${key}.pem" "$@"
}

gennocn() {
    local key=$1; shift
    local cert=$1; shift

    key "$key"
    req_nocn "$key" |
	cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days 0 "$@"
}

runtest() {
    local desc=$1; shift
    local usage=$1; shift
    local selector=$1; shift
    local mtype=$1; shift
    local tlsa=$1; shift
    local ca=$1; shift
    local chain=$1; shift
    local digest

    case $mtype in
    0) digest="";;
    1) digest=sha256;;
    2) digest=sha512;;
    *) echo "bad mtype: $mtype"; exit 1;;
    esac

    printf "%d %d %d %-24s %s: " "$usage" "$selector" "$mtype" "$tlsa" "$desc"

    if [ -n "$ca" ]; then ca="$ca.pem"; fi
    "$TEST" "$usage" "$selector" "$mtype" "$tlsa.pem" "$ca" "$chain.pem" \
    	"$@" > /dev/null
}

checkpass() { runtest "$@" && { echo pass; } || { echo fail; exit 1; }; }
checkfail() { runtest "$@" && { echo fail; exit 1; } || { echo pass; }; }

#---------

genss "$HOST" sskey sscert
gennocn akey acert

# Tests that might depend on akid/skid chaining
#
for rakid in "" \
	"authorityKeyIdentifier  = keyid,issuer" \
	"authorityKeyIdentifier  = issuer" \
	"authorityKeyIdentifier  = keyid"
do
for cakid in "" \
	"authorityKeyIdentifier  = keyid,issuer" \
	"authorityKeyIdentifier  = issuer" \
	"authorityKeyIdentifier  = keyid"
do
for rskid in "" "subjectKeyIdentifier = hash"
do
for caskid in "" "subjectKeyIdentifier = hash"
do

genroot "Root CA" rootkey rootcert "$rskid" "$rakid"
genca "CA 1" cakey1 cacert1 "$caskid" "$cakid" rootcert rootkey
genca "CA 2" cakey2 cacert2 "$caskid" "$cakid" cacert1 cakey1
genee "$HOST" eekey eecert cacert2 cakey2
cat eecert.pem cacert2.pem cacert1.pem rootcert.pem > chain.pem
cat eecert.pem cacert2.pem cacert1.pem > chain1.pem

for s in 0 1
do
  checkpass "OOB root TA" 2 "$s" 0 rootcert "" chain1 "$HOST"
  checkpass "OOB TA" 2 "$s" 0 cacert2 "" eecert "$HOST"
  checkpass "in-chain root TA" 2 "$s" 1 rootcert "" chain "$HOST"

  for m in 0 1 2
  do
    checkpass "valid TA" 2 "$s" "$m" rootcert "" chain "$HOST"
    for ca in "cacert1" "cacert2"; do
	checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain "$HOST"
	checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain1 "$HOST"
	checkpass "valid TA+CA" 2 "$s" "$m" "$ca" rootcert chain1 "$HOST"
	checkpass "sub-domain" 2 "$s" "$m" "$ca" "" chain1 whatever ".$DOMAIN"
	checkfail "wrong name" 2 "$s" "$m" "$ca" "" chain1 "whatever"
    done
  done
done

done
done
done
done

# These tests don't depend on the akid/skid chaining:
#
for s in 0 1
do
  checkfail "missing TA" 2 "$s" 1 rootcert "" chain1 "$HOST"
  for m in 0 1 2
  do
    # Special-case "2 1 0" TLSA records. These match as issuer public keys of a
    # self-signed EE cert.  The same key as part of a "2 0 0" certificate would
    # not match.
    #
    if [ "$s" -ne 1 -o "$m" -ne 0 ]; then
        checkfail "depth 0 TA" 2 "$s" "$m" sscert "" sscert "$HOST"
    fi
    checkfail "non-TA" 2 "$s" "$m" eecert rootcert chain "$HOST"

    checkpass "valid EE" 3 "$s" "$m" eecert "" chain whatever
    checkpass "key-only EE" 3 "$s" "$m" acert "" acert whatever
    checkfail "wrong EE" 3 "$s" "$m" cacert2 "" chain whatever
  done
done

rm -f acert.pem akey.pem \
    eecert.pem eekey.pem \
    cacert?.pem cakey?.pem \
    rootcert.pem rootkey.pem \
    sscert.pem sskey.pem \
    chain.pem chain1.pem
