#!/bin/sh
#
# Copyright (c) 2019 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}

srcdir="${top_srcdir}/tests/kdc"

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

umask 077

R=TEST.H5L.SE
DCs="DC=test,DC=h5l,DC=se"

port=@port@
bx509port=@bx509port@

server=datan.test.h5l.se
otherserver=other.test.h5l.se

kadmin="${kadmin} -l -A -r $R"
bx509d="${bx509d} --allow-GET --reverse-proxied -p $bx509port -H $server --cert=${objdir}/bx509.pem -t"
kdc="${kdc} --addresses=localhost -P $port"

cachefile="${objdir}/cache.krb5"
cache="FILE:${cachefile}"
cachefile2="${objdir}/cache2.krb5"
cache2="FILE:${cachefile2}"
keyfile="${hx509_data}/key.der"
keyfile2="${hx509_data}/key2.der"
kt=${objdir}/kt
keytab=FILE:${kt}
ukt=${objdir}/ukt
ukeytab=FILE:${ukt}

kinit="${kinit} -c $cache ${afs_no_afslog}"
klist2="${klist} --hidden -v -c $cache2"
klistjson="${klist} --json -c $cache"
klist="${klist} --hidden -v -c $cache"
kgetcred="${kgetcred} -c $cache"
kdestroy="${kdestroy} -c $cache ${afs_no_unlog}"
test_csr_authorizer="$test_csr_authorizer -A $objdir/authz_dir -S $objdir"
kx509="${kx509} -c $cache"

KRB5_CONFIG="${objdir}/krb5-bx509.conf"
export KRB5_CONFIG

HEIM_PIDFILE_DIR="${objdir}/"
export HEIM_PIDFILE_DIR

HEIM_IPC_DIR=$objdir
export HEIM_IPC_DIR

rsa=yes
pkinit=no
if ${hxtool} info | grep 'rsa: hx509 null RSA' > /dev/null ; then
    rsa=no
fi
if ${hxtool} info | grep 'rand: not available' > /dev/null ; then
    rsa=no
fi

if ${kinit} --help 2>&1 | grep "CA certificates" > /dev/null; then
    pkinit=yes
fi

# If we doesn't support pkinit and have RSA, give up
if test "$pkinit" != yes -o "$rsa" != yes ; then
    exit 77
fi


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

mkdir -p authz_dir

# Generate JWT signing keys upfront (needed before config file is read)
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; }

> messages.log

kdcpid=
bx509pid=
test_csr_authorizer_pid=
cleanup() {
    echo signal killing kdc, bx509d, and test_csr_authorizer
    kill -9 ${kdcpid} ${bx509pid} ${test_csr_authorizer_pid} 2>/dev/null
    trap '' EXIT INT TERM
    cat messages.log
    exit 1
}
trap cleanup EXIT INT TERM

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

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

# get_cert ""         curl-opts
# get_cert "&qparams" curl-opts
get_cert() {
    url="http://${server}:${bx509port}/bx509?csr=$csr${1}"
    shift
    curl -g --resolve ${server}:${bx509port}:127.0.0.1                  \
         -H "Authorization: Negotiate $token"                           \
         "$@" "$url"
}

get_with_token() {
    if [ -n "$csr" ]; then
        url="http://${server}:${bx509port}/${1}?csr=$csr${2}"
    else
        url="http://${server}:${bx509port}/${1}?${2}"
    fi
    shift 2

    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
         -H "Authorization: Negotiate $token"                           \
         -D response-headers                                            \
         "$@" "$url"                                                    &&
        { echo "GET w/o CSRF token succeeded!"; exit 2; }
    curl -g --resolve ${server}:${bx509port}:127.0.0.1                  \
         -H "Authorization: Negotiate $token"                           \
         -D response-headers                                            \
         "$@" "$url"
    grep ^X-CSRF-Token: response-headers >/dev/null ||
        { echo "GET w/o CSRF token did not output a CSRF token!"; exit 2; }
    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
         -H "Authorization: Negotiate $token"                           \
         -H "$(sed -e 's/\r//' response-headers | grep ^X-CSRF-Token:)" \
         "$@" "$url"                                                    ||
        { echo "GET w/ CSRF failed"; exit 2; }
}

get_via_POST() {
    endpoint=$1
    shift

    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
         -H "Authorization: Negotiate $token"                           \
         -X POST -D response-headers                                    \
         "$@" "http://${server}:${bx509port}/${endpoint}" &&
        { echo "POST w/o CSRF token succeeded!"; exit 2; }
    curl -g --resolve ${server}:${bx509port}:127.0.0.1                  \
         -H "Authorization: Negotiate $token"                           \
         -X POST -D response-headers                                    \
         "$@" "http://${server}:${bx509port}/${endpoint}"
    grep ^X-CSRF-Token: response-headers >/dev/null ||
        { echo "POST w/o CSRF token did not output a CSRF token!"; exit 2; }
    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
         -H "Authorization: Negotiate $token"                           \
         -H "$(sed -e 's/\r//' response-headers | grep ^X-CSRF-Token:)" \
         -X POST                                                        \
         "$@" "http://${server}:${bx509port}/${endpoint}" ||
        { echo "POST w/ CSRF failed"; exit 2; }
}

rm -f $kt $ukt
$ktutil -k $keytab add -r -V 1 -e aes256-cts-hmac-sha1-96               \
    -p HTTP/datan.test.h5l.se@${R} ||
    { echo "failed to setup kimpersonate credentials"; exit 2; }
$ktutil -k $keytab list ||
    { echo "failed to setup kimpersonate credentials"; exit 2; }
$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; }
$klist ||
    { echo "failed to setup kimpersonate credentials"; exit 2; }

echo "Setting up certificates"
# We need:
#
#  - a CA certificate for issuing client certificates
#  - a CA certificate for issuing server certificates
#  - a CA certificate for issuing mixed  certificates
#  - a certificate for bx509 itself (well, not in reverse proxy mode, but we'll
#    make one anyways)

# Make the realm's user cert issuer CA certificate.
#
# NOTE WELL: We need all three KeyUsage values listed below!
#            We also need this to be of type "pkinit-kdc",
#            which means we'll get an appropriate EKU OID as
#            well.
$hxtool ca  --issue-ca --self-signed --type=pkinit-kdc          \
            --ku=digitalSignature --ku=keyCertSign --ku=cRLSign \
            --pk-init-principal=krbtgt/${R}@${R}                \
            --generate-key=rsa --key-bits=1024                  \
            --subject="OU=Users,CN=KDC,${DCs}"                  \
            --certificate=PEM-FILE:"${objdir}/user-issuer.pem" ||
    { echo "failed to setup CA certificate"; exit 2; }

# We'll use the user cert issuer as the PKINIT anchor, allowing bx509-issued
# certificates to be used for PKINIT.  Though we won't be testing PKINIT here
# -- we test kx509->PKINIT in check-pkinit.
cp ${objdir}/user-issuer.pem ${objdir}/pkinit-anchor.pem

# Put the cert alone in the trust anchors file
ex "${objdir}/pkinit-anchor.pem" <<"EOF"
/-----BEGIN CERTIFICATE-----
1,.-1 d
wq
EOF

$hxtool ca  --issue-ca --self-signed                                \
            --ku=digitalSignature --ku=keyCertSign --ku=cRLSign     \
            --generate-key=rsa --key-bits=1024                      \
            --subject="OU=Servers,CN=KDC,${DCs}"                    \
            --certificate=PEM-FILE:"${objdir}/server-issuer.pem" ||
    { echo "failed to setup CA certificate"; exit 2; }

$hxtool ca  --issue-ca --self-signed                                \
            --ku=digitalSignature --ku=keyCertSign --ku=cRLSign     \
            --generate-key=rsa --key-bits=1024                      \
            --subject="OU=Users,CN=KDC,${DCs}"                      \
            --certificate=PEM-FILE:"${objdir}/mixed-issuer.pem" ||
    { echo "failed to setup CA certificate"; exit 2; }

$hxtool ca  --issue-ca --type=https-negotiate-server                \
            --ca-certificate=PEM-FILE:"${objdir}/server-issuer.pem" \
            --ku=digitalSignature --pk-init-principal=HTTP/${server}@${R}\
            --generate-key=rsa --key-bits=1024 --subject=""         \
            --certificate=PEM-FILE:"${objdir}/bx509.pem" ||
    { echo "failed to setup CA certificate"; exit 2; }

# XXX Before starting bx509d let us use kdc test programs to check that:
#
#  - the negotiate token validator plugin works
#  - the authz_dir CSR authorizer plugin works
#  - the KDC CA tester program works

echo "Check gss-token and Negotiate token validator plugin"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server | tr A B)
$test_token_validator -a datan.test.h5l.se Negotiate "$token" &&
    { echo "Negotiate token validator accepted invalid token"; exit 2; }
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
$test_token_validator -a datan.test.h5l.se Negotiate "$token" ||
    { echo "Negotiate token validator failed to validate valid token"; exit 2; }

echo "Testing JWT validator with RFC test vectors"
${objdir}/../../kdc/test_jwt_validator ||
    { echo "JWT validator RFC test vectors failed"; exit 2; }

echo "Check Bearer/JWT token validator"
# JWT signing key was already generated at start of test

# Generate a valid JWT using hxtool
jwt_token=$($hxtool jwt-sign -k ${objdir}/jwt_sign.pem -a RS256 \
    -s "foo@${R}" -i "test-issuer" -A "datan.test.h5l.se" -l 3600 | tr -d '\n')

echo "Testing Bearer token validator with valid JWT"
$test_token_validator -a datan.test.h5l.se Bearer "$jwt_token" ||
    { echo "Bearer token validator failed to validate valid JWT"; exit 2; }

echo "Testing Bearer token validator with wrong audience"
$test_token_validator -a wrong.audience.example Bearer "$jwt_token" &&
    { echo "Bearer token validator accepted JWT with wrong audience"; exit 2; }

echo "Testing Bearer token validator with tampered JWT"
tampered_jwt=$(echo "$jwt_token" | sed 's/eyJ/eXJ/')
$test_token_validator -a datan.test.h5l.se Bearer "$tampered_jwt" &&
    { echo "Bearer token validator accepted tampered JWT"; exit 2; }

echo "Testing Bearer token validator with expired JWT"
# Use negative lifetime to create an already-expired token
expired_jwt=$($hxtool jwt-sign -k ${objdir}/jwt_sign.pem -a RS256 \
    -s "foo@${R}" -i "test-issuer" -A "datan.test.h5l.se" -l -3600 | tr -d '\n')
$test_token_validator -a datan.test.h5l.se Bearer "$expired_jwt" &&
    { echo "Bearer token validator accepted expired JWT"; exit 2; }

# Note: ES256 and EdDSA signature verification is tested by the RFC test vectors
# in test_jwt_validator. The Bearer token tests use only RS256 because the
# configuration only supports a single key file.

echo "JWT/Bearer token validator tests passed"


echo "Starting CSR authorizer IPC service"
$test_csr_authorizer --server --daemon ||
    { echo "Failed to start test_csr_authorizer service"; exit 2; }
test_csr_authorizer_pid=`getpid test_csr_authorizer`

# Make a CSR for foo@$R
$hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
                        --key=FILE:"${objdir}/k.der" --kerberos=foo@$R \
                        ${objdir}/req ||
    { echo "Failed to make a CSR"; exit 2; }

echo "Test CSR authorizer IPC service (deny foo@$R to san_pkinit=foo@$R)"
csr_revoke
$test_csr_authorizer PKCS10:${objdir}/req foo@$R &&
    { echo "CSR authorizer IPC service granted foo@$R"; exit 2; }

echo "Test CSR authorizer IPC service (grant foo@$R to san_pkinit=foo@$R)"
csr_grant san_pkinit foo@$R foo@${R}
$test_csr_authorizer PKCS10:${objdir}/req foo@$R ||
    { echo "CSR authorizer IPC service rejected foo@$R"; exit 2; }

# Make a CSR for bar@$R
$hxtool request-create  --subject='' --key-bits=1024 \
                        --key=FILE:"${objdir}/k.der" --kerberos=bar@$R \
                        ${objdir}/req ||
    { echo "Failed to make a CSR"; exit 2; }

echo "Test CSR authorizer IPC service (deny foo@$R to san_pkinit=bar@$R)"
$test_csr_authorizer PKCS10:${objdir}/req foo@$R &&
    { echo "CSR authorizer IPC service accepted foo@$R"; exit 2; }

echo "Test CSR authorizer IPC service (grant foo@$R to san_pkinit=bar@$R)"
csr_grant san_pkinit foo@$R bar@${R}
$test_csr_authorizer PKCS10:${objdir}/req foo@$R &&
    { echo "CSR authorizer IPC service accepted foo@$R"; exit 2; }

# Make a CSR for foo@$R and bar@$R
$hxtool request-create  --subject='' --key-bits=1024 \
                        --key=FILE:"${objdir}/k.der" \
                        --kerberos=foo@$R --kerberos=bar@$R \
                        ${objdir}/req ||
    { echo "Failed to make a CSR"; exit 2; }

# Check that the authorizer does mark foo@$R as approved even though it denies
# the overall request because it rejects bar@$R
echo "Test CSR authorizer IPC service (partial authz)"
csr_revoke
csr_grant san_pkinit foo@$R foo@${R}
# Check that the authorizer grants foo@$R
$test_csr_authorizer PKCS10:${objdir}/req foo@$R san_pkinit=foo@$R ||
    { echo "CSR authorizer IPC service partial approval check fail"; exit 2; }
# Check that the authorizer rejects bar@$R
$test_csr_authorizer PKCS10:${objdir}/req foo@$R san_pkinit=bar@$R &&
    { echo "CSR authorizer IPC service partial approval check fail"; exit 2; }
$test_csr_authorizer PKCS10:${objdir}/req foo@$R san_pkinit=foo@$R san_pkinit=bar@$R &&
    { echo "CSR authorizer IPC service partial approval check fail"; exit 2; }

echo "Making a plain CSR"
$hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
                        --key=FILE:"${objdir}/k.der" "${objdir}/req" ||
    { echo "Failed to make a CSR"; exit 2; }

rm -f trivial.pem server.pem email.pem

echo "Testing plain user cert issuance KDC CA"
$test_kdc_ca -a bx509 -A foo@${R} PKCS10:${objdir}/req       \
             PEM-FILE:${objdir}/trivial.pem ||
    { echo "Trivial offline CA test failed"; exit 2; }
$hxtool print --content PEM-FILE:${objdir}/trivial.pem ||
    { echo "Trivial offline CA test failed"; exit 2; }
$hxtool acert --end-entity                                              \
              --expr="%{certificate.subject} == \"CN=foo,$DCs\""        \
              -P "foo@${R}" "FILE:${objdir}/trivial.pem" ||
    { echo "Trivial offline CA test failed"; exit 2; }
$hxtool acert --expr="%{certificate.subject} == \"OU=Users,CN=KDC,$DCs\""   \
              --lacks-private-key "FILE:${objdir}/trivial.pem" ||
    { echo "Trivial offline CA test failed (issuer private keys included!!)"; exit 2; }

echo "Testing other cert issuance KDC CA"
csr_revoke
# https server cert
$hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
                        --key=FILE:"${objdir}/k.der"                    \
                        --eku=id_pkix_kp_serverAuth                     \
                        --dnsname=foo.test.h5l.se "${objdir}/req" ||
    { echo "Failed to make a CSR with a dNSName SAN request"; exit 2; }
$test_kdc_ca -a bx509 foo@${R} PKCS10:${objdir}/req              \
             PEM-FILE:${objdir}/server.pem &&
    { echo "Trivial offline CA test failed: unauthorized issuance (dNSName)"; exit 2; }
csr_grant san_dnsname foo.test.h5l.se foo@${R}
csr_grant eku 1.3.6.1.5.5.7.3.1 foo@${R}
$test_kdc_ca -a bx509 foo@${R} PKCS10:${objdir}/req              \
             PEM-FILE:${objdir}/server.pem ||
    { echo "Offline CA test failed for explicitly authorized dNSName"; exit 2; }
$hxtool print --content PEM-FILE:${objdir}/server.pem ||
    { echo "Offline CA test failed for explicitly authorized dNSName"; exit 2; }
$hxtool acert --expr="%{certificate.subject} == \"OU=Servers,CN=KDC,$DCs\""   \
              --lacks-private-key "FILE:${objdir}/server.pem" ||
    { echo "Trivial offline CA test failed (issuer private keys included!!)"; exit 2; }
# email cert
$hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
                        --key=FILE:"${objdir}/k.der"                    \
                        --eku=id_pkix_kp_clientAuth                     \
                        --email=foo@test.h5l.se "${objdir}/req" ||
    { echo "Failed to make a CSR with an rfc822Name SAN request"; exit 2; }
$test_kdc_ca -a bx509 foo@${R} PKCS10:${objdir}/req              \
             PEM-FILE:${objdir}/email.pem &&
    { echo "Offline CA test failed: unauthorized issuance (dNSName)"; exit 2; }
csr_grant san_email foo@test.h5l.se foo@${R}
csr_grant eku 1.3.6.1.5.5.7.3.2 foo@${R}
$test_kdc_ca -a bx509 foo@${R} PKCS10:${objdir}/req              \
             PEM-FILE:${objdir}/email.pem ||
    { echo "Offline CA test failed for explicitly authorized dNSName"; exit 2; }
$hxtool print --content PEM-FILE:${objdir}/email.pem ||
    { echo "Offline CA test failed for explicitly authorized dNSName"; exit 2; }
$hxtool acert --expr="%{certificate.subject} == \"OU=Users,CN=KDC,$DCs\""   \
              --lacks-private-key "FILE:${objdir}/email.pem" ||
    { echo "Offline CA test failed (issuer private keys included!!)"; exit 2; }

if ! which curl; then
    echo "curl is not available -- not testing bx509d"
    sh ${leaks_kill} test_csr_authorizer $test_csr_authorizer_pid || ec=1
    trap '' EXIT
    exit 77
fi

if ! test -x ${objdir}/../../kdc/bx509d; then
    echo "Configured w/o libmicrohttpd -- not testing bx509d"
    sh ${leaks_kill} test_csr_authorizer $test_csr_authorizer_pid || ec=1
    trap '' EXIT
    exit 77
fi

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 bar@${R}
add -r --use-defaults baz@${R}
add -r --use-defaults raz@${R}
modify --pkinit-acl="CN=foo,DC=test,DC=h5l,DC=se" foo@${R}
add -r --use-defaults HTTP/${server}@${R}
ext_keytab -r -k $keytab  HTTP/${server}@${R}
add -r --use-defaults HTTP/${otherserver}@${R}
ext_keytab -r -k $ukeytab foo@${R}
EOF

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

${kdestroy}
${kinit} -kt $ukeytab foo@${R} || exit 1
$klist || { echo "failed to kinit"; exit 2; }


echo "Starting bx509d"
${bx509d} --daemon || { echo "bx509 failed to start"; exit 2; }
bx509pid=`getpid bx509d`

ec=0

rm -f trivial.pem server.pem email.pem

echo "Making a plain CSR (with BasicConstraints requesting CA cert)"
csr_revoke
$hxtool request-create  --subject='CN=H5LCA' --generate-key=rsa --key-bits=1024 \
                        --ca --key=FILE:"${objdir}/k.der" "${objdir}/req" ||
    { echo "Failed to make a CSR"; exit 2; }

# XXX Add autoconf check for curl?
#     Create a barebones bx509 HTTP/1.1 client test program?

echo "Fail to get a certificate using a CSR requesting a CA cert"
# Encode the CSR in base64, then URL-encode it
csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx; get_cert '' -sf -o "${objdir}/trivial.pem"); then
    echo 'Issued a certificate for a CSR that requested a CA cert!'
    exit 1
else
    echo 'CSRs requesting CA certs properly rejected'
fi

echo "Making a plain CSR (with BasicConstraints requesting EE cert)"
csr_revoke
$hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
                        --ee --key=FILE:"${objdir}/k.der" "${objdir}/req" ||
    { echo "Failed to make a CSR"; exit 2; }

echo "Fetching a trivial user certificate"
# Encode the CSR in base64, then URL-encode it
csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx; get_cert '' -sf -o "${objdir}/trivial.pem"); then
    $hxtool print --content "FILE:${objdir}/trivial.pem"
    if $hxtool acert --end-entity                                            \
                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
                    -P "foo@${R}" "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate with a CSR w/ BasicConstraints requesting EE cert!'
    else
        echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
        exit 1
    fi
    if $hxtool acert --expr="%{certificate.subject} == \"OU=Users,$DCs\""   \
                     --has-private-key "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    fi
else
    echo 'Failed to get a certificate with a CSR w/ BasicConstraints requesting EE cert!'
    exit 1
fi

echo "Making a plain CSR"
csr_revoke
$hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
                        --key=FILE:"${objdir}/k.der" "${objdir}/req" ||
    { echo "Failed to make a CSR"; exit 2; }

echo "Fetching a trivial user certificate (no authentication, must fail)"
# Encode the CSR in base64, then URL-encode it
csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
if (set -vx;
    curl -g --resolve ${server}:${bx509port}:127.0.0.1                  \
         -sf -o "${objdir}/trivial.pem"                                 \
         "http://${server}:${bx509port}/bx509?csr=$csr"); then
    $hxtool print --content "FILE:${objdir}/trivial.pem"
    echo 'Got a certificate without authenticating!'
    exit 1
fi

echo "Fetching a trivial user certificate"
# Encode the CSR in base64, then URL-encode it
csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx; get_cert '' -sf -o "${objdir}/trivial.pem"); then
    $hxtool print --content "FILE:${objdir}/trivial.pem"
    if $hxtool acert --end-entity                                            \
                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
                    -P "foo@${R}" "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    else
        echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
        exit 1
    fi
    if $hxtool acert --expr="%{certificate.subject} == \"OU=Users,$DCs\""   \
                     --has-private-key "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    fi
else
    echo 'Failed to get a certificate!'
    exit 1
fi

echo "Fetching a trivial user certificate (with POST, no auth, must fail)"
# Encode the CSR in base64; curl will URL-encode it for us
csr=$($rkbase64 -- ${objdir}/req)
if (set -vx;
    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
         -X POST -D response-headers                                    \
         -F csr="$csr" -o "${objdir}/trivial.pem"                       \
         "http://${server}:${bx509port}/bx509" ); then
    $hxtool print --content "FILE:${objdir}/trivial.pem"
    echo 'Got a certificate without authenticating!'
    exit 1
fi

echo "Fetching a trivial user certificate (with POST)"
# Encode the CSR in base64; curl will URL-encode it for us
csr=$($rkbase64 -- ${objdir}/req)
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx;
    get_via_POST bx509 -F csr="$csr" -o "${objdir}/trivial.pem"); then
    $hxtool print --content "FILE:${objdir}/trivial.pem"
    if $hxtool acert --end-entity                                            \
                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
                    -P "foo@${R}" "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    else
        echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
        exit 1
    fi
    if $hxtool acert --expr="%{certificate.subject} == \"OU=Users,$DCs\""   \
                     --has-private-key "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    fi
else
    echo 'Failed to get a certificate!'
    exit 1
fi

echo "Checking that authorization is enforced"
csr_revoke
get_cert '&rfc822Name=foo@bar.example' -vvv -o "${objdir}/bad1.pem"
if (set -vx; get_cert '&rfc822Name=foo@bar.example' -sf -o "${objdir}/trivial.pem"); then
    $hxtool print --content "FILE:${objdir}/bad1.pem"
    echo 'Obtained a client certificate for a non-granted name!'
    exit 1
else
    echo 'Correctly failed to get a client certificate for a non-granted name'
fi

if (set -vx; get_cert "&dNSName=$server" -sf -o "${objdir}/bad2.pem"); then
    $hxtool print --content "FILE:${objdir}/bad2.pem"
    echo 'Obtained a server certificate for a non-granted name!'
    exit 1
else
    echo 'Correctly failed to get a server certificate for a non-granted name'
fi

echo "Fetching a server certificate with one dNSName SAN"
csr_grant san_dnsname $server foo@${R}
if (set -vx; get_cert "&dNSName=$server" -sf -o "${objdir}/server.pem"); then
    $hxtool print --content "FILE:${objdir}/server.pem"
    if (set -vx; $hxtool acert --expr="%{certificate.subject} == \"\""             \
                    --end-entity -P foo@${R}               \
                    "FILE:${objdir}/server.pem"); then
        echo 'Got a broken server certificate (has PKINIT SAN)'
        exit 1
    elif $hxtool acert --end-entity -D $server "FILE:${objdir}/server.pem"; then
        echo 'Successfully obtained a server certificate!'
    else
        echo 'Got a broken server certificate'
        exit 1
    fi
else
    echo 'Failed to get a server certificate!'
    exit 1
fi

echo "Fetching a server certificate with two dNSName SANs"
csr_grant san_dnsname "second-$server" foo@${R}
if (set -vx;
    get_cert "&dNSName=${server}&dNSName=second-$server" -sf \
        -o "${objdir}/server2.pem"); then
    $hxtool print --content "FILE:${objdir}/server2.pem"
    if $hxtool acert --expr="%{certificate.subject} == \"\""             \
                    --end-entity -P foo@${R}               \
                    "FILE:${objdir}/server2.pem"; then
        echo 'Got a broken server certificate (has PKINIT SAN)'
        exit 1
    elif $hxtool acert --end-entity -D "$server"                      \
                                   -D "second-$server"               \
                                   "FILE:${objdir}/server2.pem"; then
        echo 'Successfully obtained a server certificate with two dNSName SANs!'
    else
        echo 'Got a broken server certificate (wanted two dNSName SANs)'
        exit 1
    fi
else
    echo 'Failed to get a server certificate with two dNSName SANs!'
    exit 1
fi

echo "Fetching an email certificate"
csr_grant san_email foo@bar.example foo@${R}
if (set -vx; get_cert "&rfc822Name=foo@bar.example" -sf -o "${objdir}/email.pem"); then
    $hxtool print --content "FILE:${objdir}/email.pem"
    if $hxtool acert --end-entity -P "foo@${R}" "FILE:${objdir}/email.pem"; then
        echo 'Got a broken email certificate (has PKINIT SAN)'
        exit 1
    elif $hxtool acert --expr="%{certificate.subject} == \"\""           \
                      --end-entity -M foo@bar.example                   \
                      "FILE:${objdir}/email.pem"; then
        echo 'Successfully obtained a email certificate!'
    else
        echo 'Got a broken email certificate'
        exit 1
    fi
else
    echo 'Failed to get an email certificate!'
    exit 1
fi

echo "Making an Ed25519 CSR"
csr_revoke
$hxtool request-create  --subject='' --generate-key=ed25519 \
                        --key=FILE:"${objdir}/k-ed25519.der" "${objdir}/req-ed25519" ||
    { echo "Failed to make an Ed25519 CSR"; exit 2; }

echo "Fetching a trivial user certificate with Ed25519 key"
csr=$($rkbase64 -- ${objdir}/req-ed25519 | $rkvis -h --stdin)
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx; get_cert '' -sf -o "${objdir}/trivial-ed25519.pem"); then
    $hxtool print --content "FILE:${objdir}/trivial-ed25519.pem"
    # Check that the certificate has an Ed25519 public key
    if openssl x509 -in "${objdir}/trivial-ed25519.pem" -noout -text | grep -q "Public Key Algorithm: ED25519"; then
        echo 'Certificate has Ed25519 public key!'
    else
        echo 'FAIL: Certificate does not have Ed25519 public key'
        exit 1
    fi
    if $hxtool acert --end-entity                                            \
                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
                    -P "foo@${R}" "FILE:${objdir}/trivial-ed25519.pem"; then
        echo 'Successfully obtained an Ed25519 client certificate!'
    else
        echo 'FAIL: Obtained an Ed25519 certificate w/o expected PKINIT SAN)'
        exit 1
    fi
else
    echo 'Failed to get an Ed25519 certificate!'
    exit 1
fi

echo "Fetching an Ed25519 server certificate with one dNSName SAN"
csr_grant san_dnsname $server foo@${R}
csr=$($rkbase64 -- ${objdir}/req-ed25519 | $rkvis -h --stdin)
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx; get_cert "&dNSName=$server" -sf -o "${objdir}/server-ed25519.pem"); then
    $hxtool print --content "FILE:${objdir}/server-ed25519.pem"
    # Check that the certificate has an Ed25519 public key
    if openssl x509 -in "${objdir}/server-ed25519.pem" -noout -text | grep -q "Public Key Algorithm: ED25519"; then
        echo 'Server certificate has Ed25519 public key!'
    else
        echo 'FAIL: Server certificate does not have Ed25519 public key'
        exit 1
    fi
    if $hxtool acert --end-entity -D $server "FILE:${objdir}/server-ed25519.pem"; then
        echo 'Successfully obtained an Ed25519 server certificate!'
    else
        echo 'Got a broken Ed25519 server certificate'
        exit 1
    fi
else
    echo 'Failed to get an Ed25519 server certificate!'
    exit 1
fi

echo "Fetch TGT (not granted for other)"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx;
    curl -o "${cachefile2}" -Lgsf                                       \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgt?cname=bar@${R}&address=8.8.8.8"); then
    echo "Got a TGT with /get-tgt end-point when not granted!"
    exit 2
fi

echo "Fetch TGT"
csr_grant san_pkinit foo@${R} foo@${R}
csr_grant eku 1.3.6.1.5.2.3.4 foo@${R}
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if ! (set -vx;
    curl -o "${cachefile2}" -Lgsf                                       \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgt?address=8.8.8.8"); then
    echo "Failed to get a TGT with /get-tgt end-point"
    exit 2
fi

${klist2} | grep Addresses:.IPv4:8.8.8.8 ||
    { echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; }

echo "Fetch TGT (inception)"
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -o "${cachefile}" -Lgsf                                        \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgt?address=8.8.8.8"); then
    echo "Failed to get a TGT with /get-tgt end-point"
    exit 2
fi
${kgetcred} -H HTTP/${server}@${R} ||
    { echo "Fetched TGT didn't work"; exit 2; }
${klist} | grep Addresses:.IPv4:8.8.8.8 ||
    { echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; }

echo "Fetch TGT (for other)"
csr_grant san_pkinit bar@${R} foo@${R}
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -o "${cachefile}" -Lgsf                                        \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgt?cname=bar@${R}&address=8.8.8.8"); then
    echo "Failed to get a TGT with /get-tgt end-point"
    exit 2
fi
${kgetcred} -H HTTP/${server}@${R} ||
    { echo "Fetched TGT didn't work"; exit 2; }
${klist} | grep Addresses:.IPv4:8.8.8.8 ||
    { echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; }

echo "Fetch TGT (for other, w/ lifetime req under max)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
csr_grant san_pkinit bar@${R} foo@${R}
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -o "${cachefile}" -Lgsf                                        \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgt?cname=bar@${R}&address=8.8.8.8&lifetime=3d"); then
    echo "Failed to get a TGT with /get-tgt end-point"
    exit 2
fi
${kgetcred} -H HTTP/${server}@${R} ||
    { echo "Fetched TGT didn't work"; exit 2; }
if which jq >/dev/null; then
    if ! ${klistjson} | jq -e '
            (reduce (.tickets[0]|(.Issued,.Expires)|
                    strptime("%b %e %H:%M:%S %Y")|mktime) as $t
                (0; if .==0 then $t else $t - . end) / 86400) | floor |
            . == 3'; then
        echo "Incorrect lifetime"
        exit 2
    fi
fi

echo "Fetch TGT (for other, w/ lifetime req over max)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
csr_grant san_pkinit bar@${R} foo@${R}
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -o "${cachefile}" -Lgsf                                        \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgt?cname=bar@${R}&address=8.8.8.8&lifetime=10d"); then
    echo "Failed to get a TGT with /get-tgt end-point"
    exit 2
fi
${kgetcred} -H HTTP/${server}@${R} ||
    { echo "Fetched TGT didn't work"; exit 2; }
if which jq >/dev/null; then
    if ! ${klistjson} | jq -e '
        (.tickets[0].Issued  | strptime("%b %e %H:%M:%S %Y")|mktime) as $iat
      | (.tickets[0].Expires | strptime("%b %e %H:%M:%S %Y")|mktime) as $exp
      | (($exp - $iat) / 86400) as $life_days
      | ($life_days > 4 and $life_days <= 5)'; then
        echo "Incorrect lifetime"
        exit 2
    fi
fi

echo "Fetch TGT (for other, w/ lifetime req under max)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
csr_grant san_pkinit bar@${R} foo@${R}
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -o "${cachefile}" -Lgsf                                        \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgt?cname=bar@${R}&address=8.8.8.8&address=8.9.10.11&address=11.11.11.11&address=12.12.12.12&lifetime=5d"); then
    echo "Failed to get a TGT with /get-tgt end-point"
    exit 2
fi
${kgetcred} -H HTTP/${server}@${R} ||
    { echo "Fetched TGT didn't work"; exit 2; }
if which jq >/dev/null; then
    if ! ${klistjson} | jq -e '
            (reduce (.tickets[0]|(.Issued,.Expires)|
                    strptime("%b %e %H:%M:%S %Y")|mktime) as $t
                (0; if .==0 then $t else $t - . end) / 86400) |
            . >= 4'; then
        echo "Failed to get a TGT with /get-tgt end-point with addresses"
        exit 2
    fi
fi

echo "Fetch TGTs (batch, authz fail)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
csr_revoke
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
rm -f "${cachefile}.json"
if (set -vx;
    curl -o "${cachefile}.json" -Lgsf                                   \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgts?cname=bar@${R}&cname=baz@${R}"); then
    # 200 Ok is not a problem.  We have to check that the result is sane.
    true
else
    if grep ccache "${cachefile}.json"; then
        echo "Got TGTs with /get-tgts end-point that should have been denied"
        exit 2;
    fi
    if ! grep error_code "${cachefile}.json" > /dev/null; then
        cat "${cachefile}.json"
        echo "Request failed w/o error information"
        exit 2;
    fi
fi
cat "${cachefile}.json"
if grep ccache "${cachefile}.json"; then
    echo "Got TGTs with /get-tgts end-point that should have been denied"
    exit 2;
fi

echo "Fetch TGTs (batch, partial authz with IPC authorizer)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
csr_revoke
csr_grant san_pkinit bar@${R} foo@${R}
csr_grant san_pkinit baz@${R} foo@${R}
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -vvvo "${cachefile}.json" -Lgsf                                \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgts?cname=bar@${R}&cname=raz@${R}&cname=baz@${R}"); then
    echo "Failed to get TGTs batch including non-existent principal"
    exit 2
fi
if which jq >/dev/null; then
    set -vx
    jq -e . "${cachefile}.json" > /dev/null ||
        { echo "/get-tgts produced non-JSON"; exit 2; }
    jq -es '.[]|select(.name|startswith("raz@"))|(.error_code//empty)' "${cachefile}.json" > /dev/null ||
        { echo "No error was reported for raz@${R}!"; exit 2; }
    jq -es '.[]|select(.name|startswith("raz@"))|(.ccache//"")|(length==0)' "${cachefile}.json" > /dev/null ||
        { echo "Non-empty ccache included for raz@${R}!"; exit 2; }

    # Check bar@$R's tickets:
    jq -r 'select(.name|startswith("bar@")).ccache' "${cachefile}.json" |
        $rkbase64 -d -- - > "${cachefile}"
    ${kgetcred} -H HTTP/${server}@${R} ||
        { echo "Fetched TGT didn't work"; exit 2; }
    ${klistjson} | jq -e --arg p bar@$R '.principal == $p' > /dev/null ||
        { echo "/get-tgts produced wrong TGTs"; exit 2; }

    # Check baz@$R's tickets:
    jq -r 'select(.name|startswith("baz@")).ccache' "${cachefile}.json" |
        $rkbase64 -d -- - > "${cachefile}"
    ${kgetcred} -H HTTP/${server}@${R} ||
        { echo "Fetched TGT didn't work"; exit 2; }
    ${klistjson} | jq -e --arg p baz@$R '.principal == $p' > /dev/null ||
        { echo "/get-tgts produced wrong TGTs"; exit 2; }
fi

echo "Fetch TGTs (batch, partial authz)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
csr_revoke
csr_grant san_pkinit bar@${R} foo@${R}
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -vvvo "${cachefile}.json" -Lgsf                                \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgts?cname=not@${R}&cname=bar@${R}&cname=baz@${R}"); then
    echo "Failed to get TGTs batch including non-existent principal"
    exit 2
fi
if which jq >/dev/null; then
    set -vx
    jq -e . "${cachefile}.json" > /dev/null ||
        { echo "/get-tgts produced non-JSON"; exit 2; }
    jq -es '.[]|select(.name|startswith("not@"))|(.error_code//empty)' "${cachefile}.json" > /dev/null ||
        { echo "No error was reported for not@${R}!"; exit 2; }
    jq -es '.[]|select(.name|startswith("not@"))|(.ccache//"")|(length==0)' "${cachefile}.json" > /dev/null ||
        { echo "Non-empty ccache included for not@${R}!"; exit 2; }
    jq -es '.[]|select(.name|startswith("baz@"))|(.error_code//empty)' "${cachefile}.json" > /dev/null ||
        { echo "No error was reported for baz@${R}!"; exit 2; }
    jq -es '.[]|select(.name|startswith("baz@"))|(.ccache//"")|(length==0)' "${cachefile}.json" > /dev/null ||
        { echo "Non-empty ccache included for baz@${R}!"; exit 2; }

    # Check bar@$R's tickets:
    jq -r 'select(.name|startswith("bar@")).ccache' "${cachefile}.json" |
        $rkbase64 -d -- - > "${cachefile}"
    ${kgetcred} -H HTTP/${server}@${R} ||
        { echo "Fetched TGT didn't work"; exit 2; }
    ${klistjson} | jq -e --arg p bar@$R '.principal == $p' > /dev/null ||
        { echo "/get-tgts produced wrong TGTs"; exit 2; }
fi

echo "Fetch TGTs (batch, authz pass)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
csr_grant san_pkinit bar@${R} foo@${R}
csr_grant san_pkinit baz@${R} foo@${R}
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -vvvo "${cachefile}.json" -Lgsf                                \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgts?cname=bar@${R}&cname=baz@${R}"); then
    echo "Failed to get TGTs batch"
    exit 2
fi
if which jq >/dev/null; then
    jq -e . "${cachefile}.json" > /dev/null ||
        { echo "/get-tgts produced non-JSON"; exit 2; }

    # Check bar@$R's tickets:
    jq -r 'select(.name|startswith("bar@")).ccache' "${cachefile}.json" |
        $rkbase64 -d -- - > "${cachefile}"
    ${kgetcred} -H HTTP/${server}@${R} ||
        { echo "Fetched TGT didn't work"; exit 2; }
    ${klistjson} | jq -e --arg p bar@$R '.principal == $p' > /dev/null ||
        { echo "/get-tgts produced wrong TGTs"; exit 2; }

    # Check baz@$R's tickets:
    jq -r 'select(.name|startswith("baz@")).ccache' "${cachefile}.json" |
        $rkbase64 -d -- - > "${cachefile}"
    ${kgetcred} -H HTTP/${server}@${R} ||
        { echo "Fetched TGT didn't work"; exit 2; }
    ${klistjson} | jq -e --arg p baz@$R '.principal == $p' > /dev/null ||
        { echo "/get-tgts produced wrong TGTs"; exit 2; }
fi

echo "Fetch TGTs (batch, authz pass, one non-existent principal)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
csr_grant san_pkinit bar@${R} foo@${R}
csr_grant san_pkinit baz@${R} foo@${R}
csr_grant san_pkinit not@${R} foo@${R}
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
if ! (set -vx;
    curl -vvvo "${cachefile}.json" -Lgsf                                \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
         -H "Authorization: Negotiate $token"                           \
         "http://${server}:${bx509port}/get-tgts?cname=not@${R}&cname=bar@${R}&cname=baz@${R}"); then
    echo "Failed to get TGTs batch including non-existent principal"
    exit 2
fi
if which jq >/dev/null; then
    set -vx
    jq -e . "${cachefile}.json" > /dev/null ||
        { echo "/get-tgts produced non-JSON"; exit 2; }
    jq -es '.[]|select(.name|startswith("not@"))|(.error_code//empty)' "${cachefile}.json" > /dev/null ||
        { echo "No error was reported for not@${R}!"; exit 2; }

    # Check bar@$R's tickets:
    jq -r 'select(.name|startswith("bar@")).ccache' "${cachefile}.json" |
        $rkbase64 -d -- - > "${cachefile}"
    ${kgetcred} -H HTTP/${server}@${R} ||
        { echo "Fetched TGT didn't work"; exit 2; }
    ${klistjson} | jq -e --arg p bar@$R '.principal == $p' > /dev/null ||
        { echo "/get-tgts produced wrong TGTs"; exit 2; }

    # Check baz@$R's tickets:
    jq -r 'select(.name|startswith("baz@")).ccache' "${cachefile}.json" |
        $rkbase64 -d -- - > "${cachefile}"
    ${kgetcred} -H HTTP/${server}@${R} ||
        { echo "Fetched TGT didn't work"; exit 2; }
    ${klistjson} | jq -e --arg p baz@$R '.principal == $p' > /dev/null ||
        { echo "/get-tgts produced wrong TGTs"; exit 2; }
fi

echo "killing bx509d (${bx509pid})"
sh ${leaks_kill} bx509d $bx509pid || ec=1

echo "Starting bx509d (csrf-protection-type=GET-with-token, POST-with-header)"
${bx509d} --csrf-protection-type=GET-with-token \
          --csrf-protection-type=POST-with-header --daemon || {
    echo "bx509 failed to start"
    exit 2
}
bx509pid=`getpid bx509d`

${kinit} -kt $ukeytab foo@${R} || exit 1
$klist || { echo "failed to kinit"; exit 2; }

echo "Fetching a trivial user certificate (GET with CSRF token)"
csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx; get_with_token get-cert '' -o "${objdir}/trivial.pem"); then
    $hxtool print --content "FILE:${objdir}/trivial.pem"
    if $hxtool acert --end-entity                                            \
                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
                    -P "foo@${R}" "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    else
        echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
        exit 1
    fi
    if $hxtool acert --expr="%{certificate.subject} == \"OU=Users,$DCs\""   \
                     --has-private-key "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    fi
else
    echo 'Failed to get a certificate!'
    exit 1
fi

echo "Fetching a trivial user certificate (POST with X-CSRF header, no token)"
# Encode the CSR in base64, then URL-encode it
csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx; get_cert '' -H 'X-CSRF: junk' -X POST -sf -o "${objdir}/trivial.pem"); then
    $hxtool print --content "FILE:${objdir}/trivial.pem"
    if $hxtool acert --end-entity                                            \
                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
                    -P "foo@${R}" "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    else
        echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
        exit 1
    fi
    if $hxtool acert --expr="%{certificate.subject} == \"OU=Users,$DCs\""   \
                     --has-private-key "FILE:${objdir}/trivial.pem"; then
        echo 'Successfully obtained a trivial client certificate!'
    fi
else
    echo 'Failed to get a certificate!'
    exit 1
fi

echo "Fetch negotiate token (pre-test)"
# Do what /bnegotiate does, roughly, prior to testing /bnegotiate
$hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
                        --key=PEM-FILE:"${objdir}/k.pem" "${objdir}/req" ||
    { echo "Failed to make a CSR"; exit 2; }
$test_kdc_ca -a bx509 -A foo@${R} PKCS10:${objdir}/req       \
             PEM-FILE:${objdir}/pkinit-test.pem ||
    { echo "Trivial offline CA test failed (CA)"; exit 2; }
cat ${objdir}/k.pem >> ${objdir}/pkinit-test.pem
${kinit} -C PEM-FILE:${objdir}/pkinit-test.pem foo@${R} ||
    { echo "Trivial offline CA test failed (PKINIT)"; exit 2; }
${kgetcred} -H HTTP/${server}@${R} ||
    { echo "Trivial offline CA test failed (TGS)"; exit 2; }
KRB5CCNAME=$cache $gsstoken HTTP@$server | KRB5_KTNAME="$keytab" $gsstoken -r ||
    { echo "Trivial offline CA test failed (gss-token)"; exit 2; }

# Check that we get up to three tixaddrs k/v in the log
grep 'REQ.*wrongaddr=true' ${objdir}/messages.log |
    grep 'tixaddrs=IPv4:11.11.11.11' ||
    { echo "KDC not warning about requests from wrong address"; exit 2; }

echo "Fetching a Negotiate token"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
csr=
if (set -vx;
    get_with_token get-negotiate-token "target=HTTP%40${server}" -o "${objdir}/negotiate-token"); then
    # bx509 sends us a token w/o a newline for now; we add one because
    # gss-token expects it.
    test -s negotiate-token && echo >> negotiate-token
    if test -s negotiate-token && KRB5_KTNAME="$keytab" $gsstoken -Nr < negotiate-token; then
        echo 'Successfully obtained a Negotiate token!'
    else
        echo 'Failed to get a Negotiate token (got an unacceptable token)!'
        exit 1
    fi
else
    echo 'Failed to get a Negotiate token!'
    exit 1
fi

referer=https://${otherserver}/blah
redirect=$(${rkvis} -h https://${otherserver}/blah?q=whatever)
if (set -vx;
    curl -o negotiate-token -Lgsf                                       \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
        -H "Authorization: Negotiate $token"                            \
        "http://${server}:${bx509port}/bnegotiate?target=HTTP%40${server}&redirect=${redirect}"); then
    echo "Error: /bnegotiate with target and redirect succeeded"
    exit 1
fi

if (set -vx;
    curl -o negotiate-token -Lgsf                                       \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
        -H "Authorization: Negotiate $token"                            \
        "http://${server}:${bx509port}/bnegotiate?redirect=${redirect}"); then
    echo "Error: /bnegotiate with redirect but no Referer succeeded"
    exit 1
fi

referer=http://${otherserver}/blah
redirect=$(${rkvis} -h http://${otherserver}/blah?q=whatever)
if (set -vx;
    curl -gsf                                                           \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
        -H "Authorization: Negotiate $token"                            \
        -H "Referer: $referer"                                          \
        "http://${server}:${bx509port}/bnegotiate?redirect=${redirect}"); then
    echo "Error: redirect for non-https referer"
    exit 1
fi

referer=https://${otherserver}/blah
redirect=$(${rkvis} -h https://${otherserver}/blah?q=whatever)
if (set -vx;
    curl -gfs -D curlheaders                                            \
         --resolve ${server}:${bx509port}:127.0.0.1                     \
        -H "Authorization: Negotiate $token"                            \
        -H "Referer: $referer"                                          \
        "http://${server}:${bx509port}/bnegotiate?redirect=${redirect}"); then
    read junk code junk < curlheaders
    if test "$code" = 307; then
        echo "Got a proper redirect"
    else
        echo "Error: unexpected status code $code (wanted 307)"
    fi
else
    echo "Error: no redirect"
    exit 1
fi

echo "killing kdc (${kdcpid}) and bx509d (${bx509pid}) and test_csr_authorizer (${test_csr_authorizer_pid})"
sh ${leaks_kill} kdc $kdcpid || ec=1
sh ${leaks_kill} bx509d $bx509pid || ec=1
sh ${leaks_kill} test_csr_authorizer $test_csr_authorizer_pid || ec=1

trap "" EXIT

exit $ec
