#!/bin/sh
#
# Copyright (c) 2020 Kungliga Tekniska Högskolan
# (Royal Institute of Technology, Stockholm, Sweden). 
# All rights reserved. 
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions 
# are met: 
#
# 1. Redistributions of source code must retain the above copyright 
#    notice, this list of conditions and the following disclaimer. 
#
# 2. Redistributions in binary form must reproduce the above copyright 
#    notice, this list of conditions and the following disclaimer in the 
#    documentation and/or other materials provided with the distribution. 
#
# 3. Neither the name of the Institute nor the names of its contributors 
#    may be used to endorse or promote products derived from this software 
#    without specific prior written permission. 
#
# THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
# ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
# SUCH DAMAGE. 

top_builddir="@top_builddir@"
env_setup="@env_setup@"
objdir="@objdir@"

testfailed="echo test failed; cat messages.log; exit 1"

. ${env_setup}

# If there is no useful db support compiled in, disable test
${have_db} || exit 77

if ! which curl > /dev/null; then
    echo "curl is not available -- not testing httpkadmind"
    exit 77
fi
if ! test -x ${objdir}/../../kdc/httpkadmind; then
    echo "Configured w/o libmicrohttpd -- not testing httpkadmind"
    exit 77
fi

R=TEST.H5L.SE
domain=test.h5l.se

port=@port@
admport=@admport@
admport1=@admport@
admport2=@admport2@
restport=@restport@
restport1=@restport@
restport2=@restport2@

server=datan.test.h5l.se
otherserver=other.test.h5l.se
cache="FILE:${objdir}/cache.krb5"
cache2="FILE:${objdir}/cache2.krb5"
admincache="FILE:${objdir}/cache3.krb5"
keyfile="${hx509_data}/key.der"
keyfile2="${hx509_data}/key2.der"
kt=${objdir}/kt
keytab=FILE:${kt}
ukt=${objdir}/ukt
ukeytab=FILE:${ukt}

kdc="${kdc} --addresses=localhost -P $port"
kadminr="${kadmin} -r $R -a $(uname -n)"
kadmin="${kadmin} -l -A -r $R"
kadmind2="${kadmind} --keytab=${keytab} --detach -p $admport2 --read-only"
kadmind="${kadmind} -A --keytab=${keytab} --detach -p $admport"
httpkadmind2="${httpkadmind} -A --reverse-proxied -T Negotiate -T Bearer -p $restport2"
httpkadmind="${httpkadmind} -A --reverse-proxied -T Negotiate -T Bearer -p $restport1"

kinit2="${kinit} -c $cache2 ${afs_no_afslog}"
kinit="${kinit} -c $cache ${afs_no_afslog}"
adminklist="${klist} --hidden -v -c $admincache"
klist2="${klist} --hidden -v -c $cache2"
klist="${klist} --hidden -v -c $cache"
kgetcred2="${kgetcred} -c $cache2"
kgetcred="${kgetcred} -c $cache"
kdestroy2="${kdestroy} -c $cache2 ${afs_no_unlog}"
kdestroy="${kdestroy} -c $cache ${afs_no_unlog}"
kx509="${kx509} -c $cache"

KRB5_CONFIG="${objdir}/krb5-httpkadmind.conf"
export KRB5_CONFIG
KRB5CCNAME=$cache
export KRB5CCNAME
HEIM_PIDFILE_DIR=$objdir
export HEIM_PIDFILE_DIR
HEIM_IPC_DIR=$objdir
export HEIM_IPC_DIR

rm -f current-db*
rm -f out-*
rm -f mkey.file*
rm -f *.pem *.crt *.der
rm -rf authz_dir
rm -f extracted_keytab*

mkdir -p authz_dir

> messages.log

# Generate JWT signing keys for Bearer token tests
echo "Generating JWT signing keys"
openssl genpkey -algorithm RSA -out ${objdir}/jwt_sign.pem -pkeyopt rsa_keygen_bits:2048 2>/dev/null ||
    { echo "Failed to generate JWT signing key"; exit 2; }
openssl pkey -in ${objdir}/jwt_sign.pem -pubout -out ${objdir}/jwt_sign_pub.pem 2>/dev/null ||
    { echo "Failed to extract JWT public key"; exit 2; }

# We'll avoid using a KDC for now.  For testing /httpkadmind we only need keys
# for Negotiate tokens, and we'll use ktutil and kimpersonate to make it
# possible to create and accept those without a KDC.

# grant ext-type value grantee_principal
grant() {
    mkdir -p "${objdir}/authz_dir/${3}"
    touch "${objdir}/authz_dir/${3}/${1}=${2}"
}

revoke() {
    rm -rf "${objdir}/authz_dir"
    mkdir -p "${objdir}/authz_dir"
}

if set -o|grep 'verbose.*on' > /dev/null ||
   set -o|grep 'xtrace.*on' > /dev/null; then
    verbose=-vvv
else
    verbose=
fi

# HTTP curl-opts
# Note: We use gss-token instead of curl's --negotiate because curl uses the
# system's GSS-API library which may do DNS lookups for principal name
# canonicalization, causing timeouts in test environments.  Heimdal's gss-token
# respects the krb5.conf name_canon_rules and rdns settings.
HTTP() {
    curl -g --resolve ${server}:${restport1}:127.0.0.1  \
            --resolve ${server}:${restport2}:127.0.0.1  \
         -H "Authorization: Negotiate $(${gsstoken} HTTP@${server})" \
         $verbose                                       \
         -D response-headers                            \
         "$@"
}

# HTTP_JWT jwt-token curl-opts
# Use Bearer token authentication instead of Negotiate
HTTP_JWT() {
    jwt_token="$1"
    shift
    curl -g --resolve ${server}:${restport1}:127.0.0.1  \
            --resolve ${server}:${restport2}:127.0.0.1  \
         -H "Authorization: Bearer ${jwt_token}"        \
         $verbose                                       \
         -D response-headers                            \
         "$@"
}

# get_config QPARAMS curl-opts
get_config() {
    url="http://${server}:${restport}/get-config?$1"
    shift
    HTTP $verbose "$@" "$url"
}

check_age() {
    set -- $(grep -i ^Cache-Control: response-headers)
    if [ $# -eq 0 ]; then
        return 1
    fi
    shift
    for param in "$@"; do
        case "$param" in
        no-store) true;;
        max-age=0) return 1;;
        max-age=*) true;;
        *) return 1;;
        esac
    done
    return 0;
}

# get_keytab QPARAMS curl-opts
get_keytab() {
    url="http://${server}:${restport}/get-keys?$1"
    shift
    HTTP $verbose "$@" "$url"
}

# get_keytab_POST QPARAMS curl-opts
get_keytab_POST() {
    # Curl is awful, so if you don't use -f, you don't get non-zero exit codes on
    # error responses, but if you do use -f then -D doesn't work.  Ugh.
    #
    # So first we check that POST w/o CSRF token fails:
    q=$1
    shift

    get_keytab "$q" -X POST --data-binary @/dev/null -f "$@" && 
        { echo "POST succeeded w/o CSRF token!"; return 1; }
    get_keytab "$q" -X POST --data-binary @/dev/null "$@"
    grep ^X-CSRF-Token: response-headers >/dev/null || return 1
    get_keytab "$q" -X POST --data-binary @/dev/null \
        -H "$(sed -e 's/\r//' response-headers | grep ^X-CSRF-Token:)" "$@"
    grep '^HTTP/1.1 200' response-headers >/dev/null || return $?
    return 0
}

get_keytab_POST_redir() {
    url="http://${server}:${restport}/get-keys?$1"
    shift
    > response-headers
    HTTP -X POST --data-binary @/dev/null "$@" "$url"
    grep ^X-CSRF-Token: response-headers >/dev/null ||
        { echo "POST w/o CSRF token had response w/o CSRF token!"; return 1; }
    HTTP -X POST --data-binary @/dev/null  -f                           \
        -H "$(sed -e 's/\r//' response-headers | grep ^X-CSRF-Token:)"  \
        "$@" "$url"
    grep '^HTTP/1.1 307 Temporary Redirect' response-headers ||
        { echo "POST to secondary did not yield a redirect to primary!"; return 1; }
    grep '^Location: '  response-headers ||
        { echo "POST to secondary did not yield a redirect to primary!"; return 1; }
    url=$(sed -e 's/\r//' response-headers | grep '^Location: ' | sed -e's/^Location:.//' )
    > response-headers
    HTTP -X POST --data-binary @/dev/null "$@" "$url"
    grep ^X-CSRF-Token: response-headers >/dev/null ||
        { echo "POST w/o CSRF token had response w/o CSRF token!"; return 1; }
    HTTP -X POST --data-binary @/dev/null  -f                           \
        -H "$(sed -e 's/\r//' response-headers | grep ^X-CSRF-Token:)"  \
        "$@" "$url"
}

kdcpid=
httpkadmindpid=
httpkadmind2pid=
test_csr_authorizer_pid=
kadmindpid=
kadmind2pid=
cleanup() {
    test -n "$kdcpid" &&
        { echo signal killing kdc; kill -9 "$kdcpid"; }
    test -n "$test_csr_authorizer_pid" &&
        { echo signal killing test_csr_authorizer; kill -9 "$test_csr_authorizer_pid"; }
    test -n "$httpkadmindpid" &&
        { echo signal killing httpkadmind; kill -9 "$httpkadmindpid"; }
    test -n "$httpkadmind2pid" &&
        { echo signal killing second httpkadmind; kill -9 "$httpkadmind2pid"; }
    test -n "$kadmindpid" &&
        { echo signal killing kadmind; kill -9 "$kadmindpid"; }
    test -n "$kadmind2pid" &&
        { echo signal killing kadmind; kill -9 "$kadmind2pid"; }
    trap '' EXIT INT TERM
}
trap cleanup EXIT INT TERM

rm -f extracted_keytab

echo "Creating database"
rm -f $kt $ukt
${kadmin} <<EOF || exit 1
init --realm-max-ticket-life=1day --realm-max-renewable-life=1month ${R}
add -r --use-defaults foo@${R}
add -r --use-defaults httpkadmind/admin@${R}
add -r --use-defaults WELLKNOWN/CSRFTOKEN@${R}
add -r --use-defaults HTTP/localhost@${R}
add -r --use-defaults host/xyz.${domain}@${R}
add -r --use-defaults HTTP/xyz.${domain}@${R}
add_ns --key-rotation-epoch=-1d --key-rotation-period=5m  \
       --max-ticket-life=1d --max-renewable-life=5d       \
       --attributes= HTTP/ns.${domain}@${R}
add_ns --key-rotation-epoch=-1d --key-rotation-period=5m  \
       --max-ticket-life=1d --max-renewable-life=5d       \
       --attributes=ok-as-delegate host/.ns2.${domain}@${R}
add -r --use-defaults HTTP/${server}@${R}
ext_keytab -r -k $keytab  kadmin/admin@${R}
ext_keytab -r -k $keytab  httpkadmind/admin@${R}
ext_keytab -r -k $keytab  HTTP/${server}@${R}
ext_keytab -r -k $keytab  HTTP/localhost@${R}
add -r --use-defaults HTTP/${otherserver}@${R}
ext_keytab -r -k $ukeytab foo@${R}
EOF
${kdestroy}

# For a while let's not bother with a KDC
$kimpersonate --ccache=$cache -k $keytab -R -t aes256-cts-hmac-sha1-96  \
   -c foo@${R} -s HTTP/datan.test.h5l.se@${R} ||
    { echo "failed to setup kimpersonate credentials"; exit 2; }
$kimpersonate -A --ccache=$cache -k $keytab -R -t aes256-cts-hmac-sha1-96  \
   -c foo@${R} -s HTTP/localhost@${R} ||
    { echo "failed to setup kimpersonate credentials"; exit 2; }
$klist -t >/dev/null ||
    { echo "failed to setup kimpersonate credentials"; exit 2; }

echo "Starting test_csr_authorizer"
${test_csr_authorizer} -A $objdir/authz_dir -S $objdir --server --daemon ||
    { echo "test_csr_authorizer failed to start"; exit 2; }
test_csr_authorizer_pid=`getpid test_csr_authorizer`
ec=0

echo "Starting httpkadmind"
${httpkadmind} -H $server -H localhost --local -t --daemon ||
    { echo "httpkadmind failed to start"; exit 2; }
httpkadmindpid=`getpid httpkadmind`
ec=0

echo "Checking that concrete principal exists"
${kadmin} get HTTP/xyz.${domain} > /dev/null ||
    { echo "Failed to create HTTP/xyz.${domain}"; exit 1; }
echo "Checking that virtual principal exists"
${kadmin} get HTTP/foo.ns.${domain} > /dev/null ||
    { echo "Virtual principals not working"; exit 1; }

hn=xyz.${domain}
p=HTTP/$hn
echo "Fetching krb5.conf for $p"
get_config "princ=$p" -sf -o "${objdir}/extracted_config" ||
    { echo "Failed to get config for $p"; exit 1; }
read config < "${objdir}/extracted_config"
test "$config" = "include /etc/krb5.conf" ||
    { echo "Got unexpected default config for $p"; exit 1; }
${kadmin} mod --krb5-config-file="$KRB5_CONFIG" $p ||
    { echo "Failed to set config for $p"; exit 1; }
get_config "princ=$p" -sf -o "${objdir}/extracted_config" ||
    { echo "Failed to get config for $p"; exit 1; }
cmp "${objdir}/extracted_config" "$KRB5_CONFIG" ||
    { echo "Got unexpected config for $p"; exit 1; }

hn=xyz.${domain}
p=HTTP/$hn
echo "Fetching keytab for concrete principal $p"
rm -f extracted_keytab*
grant san_dnsname $hn foo@${R}
${kadmin} ext_keytab -k extracted_keytab $p ||
    { echo "Failed to get a keytab for $p with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for $p"; exit 1; }
get_keytab "dNSName=${hn}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.rest ||
    { echo "Keytabs for $p don't match!"; exit 1; }

hn=foo.ns.${domain}
p=HTTP/$hn
echo "Fetching keytab for virtual principal $p"
rm -f extracted_keytab*
grant san_dnsname $hn foo@${R}
${kadmin} ext_keytab -k extracted_keytab $p ||
    { echo "Failed to get a keytab for $p with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for $p"; exit 1; }
get_keytab "dNSName=${hn}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
check_age
grep -i ^Cache-Control response-headers
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.rest ||
    { echo "Keytabs for $p don't match!"; exit 1; }

# JWT Bearer token tests
echo "Testing JWT Bearer token authentication"
hn=foo.ns.${domain}
p=HTTP/$hn
echo "Fetching keytab for virtual principal $p using JWT Bearer token"
rm -f extracted_keytab*
grant san_dnsname $hn foo@${R}

# Get expected keytab via kadmin for comparison
${kadmin} ext_keytab -k extracted_keytab $p ||
    { echo "Failed to get a keytab for $p with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for $p"; exit 1; }

# Generate a JWT token for foo@TEST.H5L.SE
jwt_token=$($hxtool jwt-sign -k ${objdir}/jwt_sign.pem -a RS256 \
    -s "foo@${R}" -i "test-issuer" -A "${server}" -l 3600 | tr -d '\n') ||
    { echo "Failed to generate JWT token"; exit 1; }

# Use JWT Bearer authentication to fetch keytab
rm -f extracted_keytab
url="http://${server}:${restport}/get-keys?dNSName=${hn}"
HTTP_JWT "$jwt_token" $verbose -sf -o "${objdir}/extracted_keytab" "$url" ||
    { echo "Failed to get a keytab for $p with JWT Bearer token"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.jwt ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.jwt ||
    { echo "Keytabs for $p don't match (JWT vs kadmin)!"; exit 1; }

echo "Testing JWT Bearer token with invalid signature fails"
# Create a token with wrong key (we'll just mangle the signature)
bad_jwt_token="${jwt_token}x"
url="http://${server}:${restport}/get-keys?dNSName=${hn}"
HTTP_JWT "$bad_jwt_token" $verbose -sf -o "${objdir}/extracted_keytab" "$url" &&
    { echo "Got keytab with invalid JWT token!"; exit 1; }

echo "Testing JWT Bearer token with wrong subject fails authorization"
revoke
jwt_token_other=$($hxtool jwt-sign -k ${objdir}/jwt_sign.pem -a RS256 \
    -s "other@${R}" -i "test-issuer" -A "${server}" -l 3600 | tr -d '\n') ||
    { echo "Failed to generate JWT token"; exit 1; }
# foo@R is not authorized, only granted to other@R, but we're using foo@R's JWT
grant san_dnsname $hn other@${R}
url="http://${server}:${restport}/get-keys?dNSName=${hn}"
HTTP_JWT "$jwt_token" $verbose -sf -o "${objdir}/extracted_keytab" "$url" &&
    { echo "Got keytab with unauthorized JWT subject!"; exit 1; }

# But other@R's JWT should work
HTTP_JWT "$jwt_token_other" $verbose -sf -o "${objdir}/extracted_keytab" "$url" ||
    { echo "Failed to get keytab with authorized JWT subject"; exit 1; }

# Restore authorization for foo@R for subsequent tests
revoke
grant san_dnsname $hn foo@${R}

hn1=foo.ns.${domain}
hn2=foobar.ns.${domain}
hn3=xyz.${domain}
p1=HTTP/$hn1
p2=HTTP/$hn2
p3=HTTP/$hn3
echo "Fetching keytabs for more than one principal"
rm -f extracted_keytab*
grant san_dnsname $hn1 foo@${R}
grant san_dnsname $hn2 foo@${R}
grant san_dnsname $hn3 foo@${R}
# Note that httpkadmind will first process dNSName q-params, then the spn
# q-params.
${kadmin} ext_keytab -k extracted_keytab $p1 ||
    { echo "Failed to get a keytab for $p1 with kadmin"; exit 1; }
${kadmin} ext_keytab -k extracted_keytab $p3 ||
    { echo "Failed to get a keytab for $p3 with kadmin"; exit 1; }
${kadmin} ext_keytab -k extracted_keytab $p2 ||
    { echo "Failed to get a keytab for $p2 with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for multiple principals"; exit 1; }
get_keytab "dNSName=${hn1}&spn=${p2}&dNSName=${hn3}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for multiple principals with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.rest ||
    { echo "Keytabs for $p don't match!"; exit 1; }
grep $hn1 extracted_keytab.rest > /dev/null ||
    { echo "Keytab does not include keys for $p1"; exit 1; }
grep $hn2 extracted_keytab.rest > /dev/null ||
    { echo "Keytab does not include keys for $p2"; exit 1; }
grep $hn3 extracted_keytab.rest > /dev/null ||
    { echo "Keytab does not include keys for $p3"; exit 1; }

p=host/foo.ns.${domain}
echo "Checking that $p doesn't exist (no namespace for host service)"
get_keytab "svc=host&dNSName=foo.ns.${domain}" -sf -o "${objdir}/extracted_keytab.rest" &&
    { echo "Got a keytab for host/foo.ns.${domain} when not namespaced!"; }

echo "Checking that authorization is enforced"
revoke
get_keytab "dNSName=xyz.${domain}" -sf -o "${objdir}/extracted_keytab" &&
    { echo "Got a keytab for HTTP/xyz.${domain} when not authorized!"; exit 1; }
get_keytab "dNSName=foo.ns.${domain}" -sf -o "${objdir}/extracted_keytab" &&
    { echo "Got a keytab for HTTP/foo.ns.${domain} when not authorized!"; exit 1; } 

echo "Checking that host service keys are not served"
hn=xyz.${domain}
p=host/$hn
echo "Fetching keytab for virtual principal $p"
rm -f extracted_keytab*
grant san_dnsname $hn foo@${R}
get_keytab "service=host&dNSName=xyz.${domain}" -sf -o "${objdir}/extracted_keytab" &&
    { echo "Got a keytab for $p even though it is a host service!"; exit 1; }
get_keytab "spn=host/xyz.${domain}" -sf -o "${objdir}/extracted_keytab" &&
    { echo "Got a keytab for $p even though it is a host service!"; exit 1; }
revoke

hn=xyz.${domain}
p=HTTP/$hn
echo "Checking key rotation for concrete principal $p"
rm -f extracted_keytab*
grant san_dnsname $hn foo@${R}
get_keytab "dNSName=${hn}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest1 ||
    { echo "Failed to list keytab for $p"; exit 1; }
test "$(grep $p extracted_keytab.rest1 | wc -l)" -eq 2 ||
    { echo "Wrong number of new keys!"; exit 1; }
get_keytab "dNSName=${hn}&rotate=true" -sf -o "${objdir}/extracted_keytab" &&
    { echo "GET succeeded for write operation!"; exit 1; }
get_keytab_POST "dNSName=${hn}&rotate=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to rotate keys for $p"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest2 ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.rest1 extracted_keytab.rest2 > /dev/null &&
    { echo "Keys for $p did not change!"; exit 1; }
test "$(grep $p extracted_keytab.rest2 | wc -l)" -eq 4 ||
    { echo "Wrong number of new keys!"; exit 1; }

hn=xyz.${domain}
p=HTTP/$hn
echo "Checking key rotation w/ revocation for concrete principal $p"
rm -f extracted_keytab*
grant san_dnsname $hn foo@${R}
get_keytab "dNSName=${hn}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest1 ||
    { echo "Failed to list keytab for $p"; exit 1; }
get_keytab "dNSName=${hn}&revoke=true" -sf -o "${objdir}/extracted_keytab" &&
    { echo "GET succeeded for write operation!"; exit 1; }
get_keytab_POST "dNSName=${hn}&revoke=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest2 ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.rest1 extracted_keytab.rest2 > /dev/null &&
    { echo "Keys for $p did not change!"; exit 1; }
test "$(grep $p extracted_keytab.rest2 | wc -l)" -eq 2 ||
    { echo "Wrong number of new keys!"; exit 1; }

hn=abc.${domain}
p=HTTP/$hn
echo "Checking concrete principal creation ($p)"
rm -f extracted_keytab
grant san_dnsname $hn foo@${R}
get_keytab "dNSName=${hn}&create=true" -sf -o "${objdir}/extracted_keytab" &&
    { echo "GET succeeded for write operation!"; exit 1; }
get_keytab_POST "dNSName=${hn}&create=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
    { echo "Failed to list keytab for $p"; exit 1; }
rm -f extracted_keytab
${kadmin} ext_keytab -k extracted_keytab $p ||
    { echo "Failed to get a keytab for $p with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.rest ||
    { echo "Keytabs for $p don't match!"; exit 1; }

hn=bar.ns.${domain}
p=HTTP/$hn
echo "Checking materialization of virtual principal ($p)"
rm -f extracted_keytab
grant san_dnsname $hn foo@${R}
get_keytab "dNSName=${hn}&materialize=true" -sf -o "${objdir}/extracted_keytab" &&
    { echo "GET succeeded for write operation!"; exit 1; }
get_keytab_POST "dNSName=${hn}&materialize=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to materialize and get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
    { echo "Failed to list keytab for $p"; exit 1; }
rm -f extracted_keytab
${kadmin} ext_keytab -k extracted_keytab $p ||
    { echo "Failed to get a keytab for $p with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.rest ||
    { echo "Keytabs for $p don't match!"; exit 1; }

echo "Starting secondary httpkadmind to test HTTP redirection"
${httpkadmind2} --primary-server-uri=http://${server}:$restport         \
    -H $server --local --local-read-only -t --daemon ||
    { echo "httpkadmind failed to start"; exit 2; }
httpkadmind2pid=`getpid httpkadmind`
ec=0

hn=def.${domain}
p=HTTP/$hn
restport=$restport2
echo "Checking principal creation at secondary yields redirect"
rm -f extracted_keytab
grant san_dnsname $hn foo@${R}
get_keytab_POST_redir "dNSName=${hn}&create=true"                           \
    -s -o "${objdir}/extracted_keytab"
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
    { echo "Failed to list keytab for $p"; exit 1; }
rm -f extracted_keytab
${kadmin} ext_keytab -k extracted_keytab $p ||
    { echo "Failed to get a keytab for $p with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.rest ||
    { echo "Keytabs for $p don't match!"; exit 1; }

echo "killing httpkadmind (${httpkadmindpid} ${httpkadmind2pid})"
sh ${leaks_kill} httpkadmind $httpkadmindpid || ec=1
sh ${leaks_kill} httpkadmind $httpkadmind2pid || ec=1
httpkadmindpid=
httpkadmind2pid=
test $ec = 1 &&
    { echo "Error killing httpkadmind instances or memory errors found"; exit 1; }

echo "Starting primary kadmind for testing httpkadmind with remote HDB"
${kadmind} ||
    { echo "Read-write kadmind failed to start"; exit 2; }
kadmindpid=`getpid kadmind`
echo "Starting secondray (read-only) kadmind for testing httpkadmind with remote HDB"
${kadmind2} ||
    { echo "Read-only kadmind failed to start"; exit 2; }
kadmind2pid=`getpid kadmind`

# Make a ccache for use with kadmin(1)
$kimpersonate --ticket-flags=initial --ccache=$admincache -k $keytab -t aes256-cts-hmac-sha1-96  \
   -c httpkadmind/admin@${R} -s kadmin/admin@${R} ||
    { echo "failed to setup kimpersonate credentials"; exit 2; }
$adminklist -t >/dev/null ||
    { echo "failed to setup kimpersonate credentials"; exit 2; }


echo "Making PKINIT certs for KDC"
${hxtool} issue-certificate \
          --self-signed \
          --issue-ca \
          --ca-private-key=FILE:${keyfile} \
          --subject="CN=CA,DC=test,DC=h5l,DC=se" \
          --certificate="FILE:ca.crt" || exit 1
${hxtool} request-create \
         --subject="CN=kdc,DC=test,DC=h5l,DC=se" \
         --key=FILE:${keyfile2} \
         req-kdc.der || exit 1
${hxtool} issue-certificate \
          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
          --type="pkinit-kdc" \
          --pk-init-principal="krbtgt/TEST.H5L.SE@TEST.H5L.SE" \
          --req="PKCS10:req-kdc.der" \
          --certificate="FILE:kdc.crt" || exit 1
${hxtool} request-create \
         --subject="CN=bar,DC=test,DC=h5l,DC=se" \
         --key=FILE:${keyfile2} \
         req-pkinit.der ||
         { echo "Failed to make CSR for PKINIT client cert"; exit 1; }
${hxtool} issue-certificate \
          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
          --type="pkinit-client" \
          --pk-init-principal="host/synthesized.${domain}@$R" \
          --req="PKCS10:req-pkinit.der" \
          --lifetime=7d \
          --certificate="FILE:pkinit-synthetic.crt" ||
         { echo "Failed to make PKINIT client cert"; exit 1; }

echo "Starting kdc needed for httpkadmind authentication to kadmind"
${kdc} --detach --testing || { echo "kdc failed to start"; cat messages.log; exit 1; }
kdcpid=`getpid kdc`

echo "Starting httpkadmind with remote HDBs only"
restport=$restport1
${httpkadmind} -H $server -H localhost -t --daemon              \
    --writable-admin-server=$(uname -n):$admport                \
    --read-only-admin-server=$(uname -n):$admport2              \
    --kadmin-client-name=httpkadmind/admin@${R}                 \
    --kadmin-client-keytab=$keytab ||
    { echo "httpkadmind failed to start"; exit 2; }
httpkadmindpid=`getpid httpkadmind`
ec=0

hn=xyz.${domain}
p=HTTP/$hn
echo "Fetching keytab for concrete principal $p using remote HDB"
rm -f extracted_keytab*
grant san_dnsname $hn httpkadmind/admin@${R}
KRB5CCNAME=$admincache ${kadmin} ext_keytab -k extracted_keytab $p ||
    { echo "Failed to get a keytab for $p with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for $p"; exit 1; }
get_keytab "spn=${p}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.rest ||
    { echo "Keytabs for $p don't match!"; exit 1; }

hn=xyz.${domain}
p=HTTP/$hn
echo "Checking key rotation for concrete principal $p using remote HDB"
rm -f extracted_keytab*
grant san_dnsname $hn foo@${R}
get_keytab "dNSName=${hn}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest1 ||
    { echo "Failed to list keytab for $p"; exit 1; }
test "$(grep $p extracted_keytab.rest1 | wc -l)" -eq 2 ||
    { echo "Wrong number of new keys!"; exit 1; }
get_keytab "dNSName=${hn}&rotate=true" -sf -o "${objdir}/extracted_keytab" &&
    { echo "GET succeeded for write operation!"; exit 1; }
get_keytab_POST "dNSName=${hn}&rotate=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to rotate keys for $p"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest2 ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.rest1 extracted_keytab.rest2 > /dev/null &&
    { echo "Keys for $p did not change!"; exit 1; }
test "$(grep $p extracted_keytab.rest2 | wc -l)" -eq 4 ||
    { echo "Wrong number of new keys!"; exit 1; }

sh ${leaks_kill} httpkadmind $httpkadmindpid || ec=1
httpkadmindpid=

echo "Starting httpkadmind with local read-only HDB and remote read-write HDB"
${httpkadmind} -H $server -H localhost -t --daemon              \
    --local-read-only                                           \
    --writable-admin-server=$(uname -n):$admport                \
    --read-only-admin-server=$(uname -n):$admport2              \
    --kadmin-client-name=httpkadmind/admin@${R}                 \
    --kadmin-client-keytab=$keytab ||
    { echo "httpkadmind failed to start"; exit 2; }
httpkadmindpid=`getpid httpkadmind`
ec=0

hn=xyz.${domain}
p=HTTP/$hn
echo "Fetching keytab for concrete principal $p using local read-only HDB"
rm -f extracted_keytab*
grant san_dnsname $hn httpkadmind/admin@${R}
KRB5CCNAME=$admincache ${kadmin} ext_keytab -k extracted_keytab $p ||
    { echo "Failed to get a keytab for $p with kadmin"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin ||
    { echo "Failed to list keytab for $p"; exit 1; }
get_keytab "spn=${p}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.kadmin extracted_keytab.rest ||
    { echo "Keytabs for $p don't match!"; exit 1; }

hn=xyz.${domain}
p=HTTP/$hn
echo "Checking key rotation for concrete principal $p using local read-only HDB and remote HDB"
rm -f extracted_keytab*
grant san_dnsname $hn foo@${R}
get_keytab "dNSName=${hn}" -sf -o "${objdir}/extracted_keytab" ||
    { echo "Failed to get a keytab for $p with curl"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest1 ||
    { echo "Failed to list keytab for $p"; exit 1; }
test "$(grep $p extracted_keytab.rest1 | wc -l)" -eq 4 ||
    { echo "Wrong number of new keys!"; exit 1; }
get_keytab "dNSName=${hn}&rotate=true" -sf -o "${objdir}/extracted_keytab" &&
    { echo "GET succeeded for write operation!"; exit 1; }
get_keytab_POST "dNSName=${hn}&rotate=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to rotate keys for $p"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest2 ||
    { echo "Failed to list keytab for $p"; exit 1; }
cmp extracted_keytab.rest1 extracted_keytab.rest2 > /dev/null &&
    { echo "Keys for $p did not change!"; exit 1; }
test "$(grep $p extracted_keytab.rest2 | wc -l)" -eq 6 ||
    { echo "Wrong number of new keys!"; exit 1; }

echo "Checking that host services as clients can self-create"
hn=synthesized.${domain}
p=host/$hn
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null &&
    { echo "Internal error -- $p exists too soon"; exit 1; }
${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
${kgetcred2} HTTP/localhost@$R || echo WAT
rm -f extracted_keytab*
KRB5CCNAME=$cache2 \
get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
    { echo "Failed to create and extract host keys for self"; exit 1; }

echo "Checking that host services can't get other host service principals"
hn=nonexistent.${domain}
p=host/$hn
KRB5CCNAME=$cache2 \
get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab2" &&
    { echo "Failed to fail to create and extract host keys for other!"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab2" list > /dev/null || true
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null &&
    { echo "Failed to fail to create and extract host keys for other!"; exit 1; }

echo "Checking that host services can't get keys for themselves and others"
hn=synthesized.${domain}
p=host/$hn
p2=host/nonexistent.${domain}
${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
${kgetcred2} HTTP/localhost@$R || echo WAT
rm -f extracted_keytab*
KRB5CCNAME=$cache2 \
get_keytab_POST "spn=$p&spn=$p2&create=true" -s -o "${objdir}/extracted_keytab" &&
    { echo "Failed to fail to create and extract host keys for other!"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab2" list > /dev/null || true
KRB5CCNAME=$admincache ${kadmin} get -s $p2 >/dev/null &&
    { echo "Failed to fail to create and extract host keys for other!"; exit 1; }

echo "Checking that attributes for new principals can be configured"
hn=a-particular-hostname.test.h5l.se
p=host/$hn
${hxtool} issue-certificate \
          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
          --type="pkinit-client" \
          --pk-init-principal="$p@$R" \
          --req="PKCS10:req-pkinit.der" \
          --lifetime=7d \
          --certificate="FILE:pkinit-synthetic.crt" ||
         { echo "Failed to make PKINIT client cert"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null 2>&1 &&
    { echo "Internal error -- $p exists too soon"; exit 1; }
${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
${kgetcred2} HTTP/localhost@$R || echo WAT
rm -f extracted_keytab*
KRB5CCNAME=$cache2 \
get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get $p |
    grep 'Attributes:.*ok-as-delegate' > /dev/null ||
    { echo "Failed to create with configured attributes"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get $p |
    grep 'Attributes:.*no-auth-data-reqd' > /dev/null ||
    { echo "Failed to create with configured attributes"; exit 1; }

hn=other-hostname.test.h5l.se
p=host/$hn
${hxtool} issue-certificate \
          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
          --type="pkinit-client" \
          --pk-init-principal="$p@$R" \
          --req="PKCS10:req-pkinit.der" \
          --lifetime=7d \
          --certificate="FILE:pkinit-synthetic.crt" ||
         { echo "Failed to make PKINIT client cert"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null 2>&1 &&
    { echo "Internal error -- $p exists too soon"; exit 1; }
${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
${kgetcred2} HTTP/localhost@$R || echo WAT
rm -f extracted_keytab*
KRB5CCNAME=$cache2 \
get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get $p |
    grep 'Attributes:.*ok-as-delegate' > /dev/null &&
    { echo "Create with unexpected attributes"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get $p |
    grep 'Attributes:.*no-auth-data-reqd' > /dev/null &&
    { echo "Create with unexpected attributes"; exit 1; }

hn=a-server.prod.test.h5l.se
p=host/$hn
${hxtool} issue-certificate \
          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
          --type="pkinit-client" \
          --pk-init-principal="$p@$R" \
          --req="PKCS10:req-pkinit.der" \
          --lifetime=7d \
          --certificate="FILE:pkinit-synthetic.crt" ||
         { echo "Failed to make PKINIT client cert"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null 2>&1 &&
    { echo "Internal error -- $p exists too soon"; exit 1; }
${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
${kgetcred2} HTTP/localhost@$R || echo WAT
rm -f extracted_keytab*
KRB5CCNAME=$cache2 \
get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get $p |
    grep 'Attributes:.*ok-as-delegate' > /dev/null ||
    { echo "Failed to create with configured attributes"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get $p |
    grep 'Attributes:.*no-auth-data-reqd' > /dev/null ||
    { echo "Failed to create with configured attributes"; exit 1; }

hn=a-host.ns2.test.h5l.se
p=host/$hn
${hxtool} issue-certificate \
          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
          --type="pkinit-client" \
          --pk-init-principal="$p@$R" \
          --req="PKCS10:req-pkinit.der" \
          --lifetime=7d \
          --certificate="FILE:pkinit-synthetic.crt" ||
         { echo "Failed to make PKINIT client cert"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null 2>&1 &&
    { echo "Internal error -- $p exists too soon"; exit 1; }
${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
${kgetcred2} HTTP/localhost@$R || echo WAT
rm -f extracted_keytab*
KRB5CCNAME=$cache2 \
get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
    { echo "Failed to create and extract host keys for self"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get $p |
    grep 'Attributes:.*ok-as-delegate' > /dev/null ||
    { echo "Failed to create with namespace attributes"; exit 1; }
KRB5CCNAME=$admincache ${kadmin} get $p |
    grep 'Attributes:.*no-auth-data-reqd' > /dev/null &&
    { echo "Create with unexpected attributes"; exit 1; }

grep 'Internal error' messages.log &&
    { echo "Internal errors in log"; exit 1; }

sh ${leaks_kill} test_csr_authorizer $test_csr_authorizer_pid || ec=1
sh ${leaks_kill} httpkadmind $httpkadmindpid || ec=1
sh ${leaks_kill} kadmind $kadmindpid || ec=1
sh ${leaks_kill} kadmind $kadmind2pid || ec=1
sh ${leaks_kill} kdc $kdcpid || ec=1

if [ $ec = 0 ]; then
    trap "" EXIT
    echo "Success"
fi

# TODO
#
#  - implement and test that we can materialize a principal yet leave it with
#    virtual keys
#  - test new key delay?  this one is tricky

exit $ec
