bx509: CSRF protection for /bnegotiate

This commit is contained in:
Nicolas Williams
2019-12-05 19:52:47 -06:00
parent 0a0a27ccec
commit d1a2652090
4 changed files with 350 additions and 126 deletions

View File

@@ -172,6 +172,7 @@ validate_token(struct MHD_Connection *connection,
krb5_data tok;
size_t host_len, brk, i;
*cprinc_from_token = NULL;
memset(token_times, 0, sizeof(*token_times));
ret = get_krb5_context(&context);
if (ret)
@@ -257,8 +258,7 @@ generate_key(hx509_context context,
if (ret == 0)
ret = hx509_certs_add(context, certs, cert);
if (ret == 0)
ret = hx509_certs_store(context, certs,
HX509_CERTS_STORE_NO_PRIVATE_KEYS, NULL);
ret = hx509_certs_store(context, certs, 0, NULL);
if (ret)
hx509_err(context, 1, ret, "Could not generate and save private key "
"for %s", key_name);
@@ -630,16 +630,33 @@ update_and_authorize_CSR(krb5_context context,
return ret;
}
/*
* hx509_certs_iter_f() callback to assign a private key to the first cert in a
* store.
*/
static int HX509_LIB_CALL
set_priv_key(hx509_context context, void *d, hx509_cert c)
{
(void) _hx509_cert_assign_key(c, (hx509_private_key)d);
return -1; /* stop iteration */
}
static krb5_error_code
store_certs(hx509_context context, const char *store, hx509_certs store_these)
store_certs(hx509_context context,
const char *store,
hx509_certs store_these,
hx509_private_key key)
{
krb5_error_code ret;
hx509_certs certs = NULL;
ret = hx509_certs_init(context, store, HX509_CERTS_CREATE, NULL,
&certs);
if (ret == 0)
if (ret == 0) {
if (key)
(void) hx509_certs_iter_f(context, store_these, set_priv_key, key);
hx509_certs_merge(context, certs, store_these);
}
if (ret == 0)
hx509_certs_store(context, certs, 0, NULL);
hx509_certs_free(&certs);
@@ -716,7 +733,7 @@ do_CA(krb5_context context,
return bad_500(connection, ret,
"Could not create PEM store for issued certificate");
ret = store_certs(context->hx509ctx, *pkix_store, certs);
ret = store_certs(context->hx509ctx, *pkix_store, certs, NULL);
hx509_certs_free(&certs);
if (ret) {
(void) unlink(strchr(*pkix_store, ':') + 1);
@@ -873,11 +890,13 @@ find_ccache(krb5_context context, const char *princ, char **ccname)
/* Check if we have a good enough credential */
if (ret == 0 &&
(ret = krb5_cc_get_lifetime(context, cc, &life)) == 0 && life > 60)
(ret = krb5_cc_get_lifetime(context, cc, &life)) == 0 && life > 60) {
krb5_cc_close(context, cc);
return 0;
}
if (cc)
krb5_cc_close(context, cc);
return ret;
return ret ? ret : ENOENT;
}
/*
@@ -897,7 +916,7 @@ do_pkinit(krb5_context context,
{
krb5_get_init_creds_opt *opt = NULL;
krb5_init_creds_context ctx = NULL;
krb5_error_code ret = ENOMEM;
krb5_error_code ret = 0;
krb5_ccache temp_cc = NULL;
krb5_ccache cc = NULL;
krb5_principal p = NULL;
@@ -908,12 +927,21 @@ do_pkinit(krb5_context context,
char *temp_ccname = NULL;
int fd = -1;
if ((ret = krb5_cc_resolve(context, ccname, &cc)))
return ret;
/*
* Avoid nasty race conditions and ccache file corruption, take an flock on
* temp_ccname and do the cleanup dance.
* Open and lock a .new ccache file. Use .new to avoid garbage files on
* crash.
*
* We can race with other threads to do this, so we loop until we
* definitively win or definitely lose the race. We win when we have a) an
* open FD that is b) flock'ed, and c) we observe with lstat() that the
* file we opened and locked is the same as on disk after locking.
*
* We don't close the FD until we're done.
*
* If we had a proper anon MEMORY ccache, we could instead use that for a
* temporary ccache, and then the initialization of and move to the final
* FILE ccache would take care to mkstemp() and rename() into place.
* fcc_open() basically does a similar thing.
*/
if (asprintf(&temp_ccname, "%s.ccnew", ccname) == -1 ||
temp_ccname == NULL)
@@ -922,7 +950,7 @@ do_pkinit(krb5_context context,
fn = temp_ccname + sizeof("FILE:") - 1;
if (ret == 0) do {
/*
* Open and flock the file.
* Open and flock the temp ccache file.
*
* XXX We should really a) use _krb5_xlock(), or move that into
* lib/roken anyways, b) abstract this loop into a utility function in
@@ -952,9 +980,9 @@ do_pkinit(krb5_context context,
if (ret == 0)
ret = krb5_cc_resolve(context, temp_ccname, &temp_cc);
if (ret == 0)
ret = krb5_cc_get_lifetime(context, cc, &life);
ret = krb5_cc_get_lifetime(context, temp_cc, &life);
if (ret == 0 && life > 60)
goto out; /* We lost the race, we get to do less work */
goto out; /* We lost the race, but we win: we get to do less work */
/*
* We won the race. Setup to acquire Kerberos creds with PKINIT.
@@ -968,7 +996,6 @@ do_pkinit(krb5_context context,
if (ret == 0)
crealm = krb5_principal_get_realm(context, p);
if (ret == 0 &&
(ret = krb5_cc_initialize(context, temp_cc, p)) == 0 &&
(ret = krb5_get_init_creds_opt_alloc(context, &opt)) == 0)
krb5_get_init_creds_opt_set_default_flags(context, "kinit", crealm,
opt);
@@ -976,9 +1003,9 @@ do_pkinit(krb5_context context,
(ret = krb5_get_init_creds_opt_set_addressless(context,
opt, 1)) == 0)
ret = krb5_get_init_creds_opt_set_pkinit(context, opt, p, pkix_store,
NULL, /* XXX pkinit_anchor */
NULL, /* XXX anchor_chain */
NULL, /* XXX pkinit_crl */
NULL, /* pkinit_anchor */
NULL, /* anchor_chain */
NULL, /* pkinit_crl */
0, /* flags */
NULL, /* prompter */
NULL, /* prompter data */
@@ -992,11 +1019,14 @@ do_pkinit(krb5_context context,
/*
* Finally, do the AS exchange w/ PKINIT, extract the new Kerberos creds
* into temp_cc, and rename into place.
* into temp_cc, and rename into place. Note that krb5_cc_move() closes
* the source ccache, so we set temp_cc = NULL if it succeeds.
*/
if (ret == 0 &&
(ret = krb5_init_creds_get(context, ctx)) == 0 &&
(ret = krb5_cc_initialize(context, temp_cc, p)) == 0 &&
(ret = krb5_init_creds_store(context, ctx, temp_cc)) == 0 &&
(ret = krb5_cc_resolve(context, ccname, &cc)) == 0 &&
(ret = krb5_cc_move(context, temp_cc, cc)) == 0)
temp_cc = NULL;
@@ -1005,10 +1035,9 @@ out:
krb5_init_creds_free(context, ctx);
krb5_get_init_creds_opt_free(context, opt);
krb5_free_principal(context, p);
if (temp_cc)
krb5_cc_close(context, temp_cc);
if (cc)
krb5_cc_close(context, cc);
krb5_cc_close(context, temp_cc);
krb5_cc_close(context, cc);
free(temp_ccname);
if (fd != -1)
(void) close(fd); /* Drops the flock */
return ret;
@@ -1067,6 +1096,9 @@ bnegotiate_do_CA(krb5_context context,
ret = krb5_parse_name(context, princ, &p);
if (ret == 0)
hx509_private_key2SPKI(context->hx509ctx, key, &spki);
if (ret == 0)
hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx, req, &spki);
free_SubjectPublicKeyInfo(&spki);
if (ret == 0)
ret = hx509_request_add_pkinit(context->hx509ctx, req, princ);
if (ret == 0)
@@ -1085,23 +1117,29 @@ bnegotiate_do_CA(krb5_context context,
if (ret == 0)
ret = kdc_issue_certificate(context, kdc_config, req, p, token_times,
1 /* send_chain */, &certs);
hx509_private_key_free(&key);
krb5_free_principal(context, p);
hx509_request_free(&req);
p = NULL;
if (ret == KRB5KDC_ERR_POLICY)
if (ret == KRB5KDC_ERR_POLICY) {
hx509_private_key_free(&key);
return bad_500(connection, ret,
"Certificate request denied for policy reasons");
if (ret == ENOMEM)
}
if (ret == ENOMEM) {
hx509_private_key_free(&key);
return bad_503(connection, ret, "Certificate issuance failed");
if (ret)
}
if (ret) {
hx509_private_key_free(&key);
return bad_500(connection, ret, "Certificate issuance failed");
}
/* Setup PKIX store and extract the certificate chain into it */
ret = mk_pkix_store(pkix_store);
if (ret == 0)
ret = store_certs(context->hx509ctx, *pkix_store, certs);
ret = store_certs(context->hx509ctx, *pkix_store, certs, key);
hx509_private_key_free(&key);
hx509_certs_free(&certs);
if (ret) {
(void) unlink(strchr(*pkix_store, ':') + 1);
@@ -1145,7 +1183,7 @@ bnegotiate_get_creds(struct MHD_Connection *connection,
if (ret == 0 &&
(ret = do_pkinit(context, subject_cprinc, pkix_store, *ccname)))
ret = bad_403(connection, ret,
"Could not acquire PKIX credentials using PKINIT");
"Could not acquire Kerberos credentials using PKINIT");
free(pkix_store);
return ret;
@@ -1223,22 +1261,6 @@ bad_req_gss(struct MHD_Connection *connection,
return ret;
}
static gss_OID
get_name_type(struct MHD_Connection *connection)
{
const char *nt;
nt = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND,
"nametype");
if (nt == NULL || strcmp(nt, "hostbased-service") == 0)
return GSS_C_NT_HOSTBASED_SERVICE;
if (strcmp(nt, "exported-name") == 0)
return GSS_C_NT_EXPORT_NAME;
if (strcmp(nt, "krb5") == 0)
return GSS_KRB5_NT_PRINCIPAL_NAME;
return GSS_C_NO_OID;
}
/* Make an HTTP/Negotiate token */
static krb5_error_code
mk_nego_tok(struct MHD_Connection *connection,
@@ -1257,14 +1279,11 @@ mk_nego_tok(struct MHD_Connection *connection,
gss_name_t iname = GSS_C_NO_NAME;
gss_name_t aname = GSS_C_NO_NAME;
OM_uint32 major, minor, junk;
gss_OID nt;
krb5_error_code ret; /* More like a system error code here */
char *token_b64 = NULL;
*nego_tok = NULL;
*nego_toksz = 0;
if ((nt = get_name_type(connection)) == GSS_C_NO_OID)
return bad_400(connection, EINVAL, "unknown GSS name type in request");
/* Import initiator name */
name.length = strlen(cprinc);
@@ -1279,7 +1298,7 @@ mk_nego_tok(struct MHD_Connection *connection,
/* Import target acceptor name */
name.length = strlen(target);
name.value = rk_UNCONST(target);
major = gss_import_name(&minor, &name, nt, &aname);
major = gss_import_name(&minor, &name, GSS_C_NT_HOSTBASED_SERVICE, &aname);
if (major != GSS_S_COMPLETE) {
(void) gss_release_name(&junk, &iname);
return bad_req_gss(connection, major, minor, GSS_C_NO_OID,
@@ -1304,6 +1323,7 @@ mk_nego_tok(struct MHD_Connection *connection,
GSS_KRB5_MECHANISM, 0, GSS_C_INDEFINITE,
NULL, GSS_C_NO_BUFFER, NULL, &token, NULL,
NULL);
(void) gss_delete_sec_context(&junk, &ctx, GSS_C_NO_BUFFER);
(void) gss_release_name(&junk, &aname);
(void) gss_release_cred(&junk, &cred);
if (major != GSS_S_COMPLETE)
@@ -1325,14 +1345,103 @@ mk_nego_tok(struct MHD_Connection *connection,
return 0;
}
static krb5_error_code
bnegotiate_get_target(struct MHD_Connection *connection,
const char **out_target,
const char **out_redir,
char **freeme)
{
const char *target;
const char *redir;
const char *referer; /* misspelled on the wire, misspelled here, FYI */
const char *authority;
const char *local_part;
char *s1 = NULL;
char *s2 = NULL;
*out_target = NULL;
*out_redir = NULL;
*freeme = NULL;
target = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND,
"target");
redir = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND,
"redirect");
referer = MHD_lookup_connection_value(connection, MHD_HEADER_KIND,
"referer");
if (target != NULL && redir == NULL) {
*out_target = target;
return 0;
}
if (target == NULL && redir == NULL)
return bad_400(connection, EINVAL,
"Query missing 'target' or 'redirect' parameter value");
if (target != NULL && redir != NULL)
return bad_403(connection, EACCES,
"Only one of 'target' or 'redirect' parameter allowed");
if (redir != NULL && referer == NULL)
return bad_403(connection, EACCES,
"Redirect request without Referer header nor allowed");
if (strncmp(referer, "https://", sizeof("https://") - 1) ||
strncmp(redir, "https://", sizeof("https://") - 1))
return bad_403(connection, EACCES,
"Redirect requests permitted only for https referrers");
/* Parse out authority from each URI, redirect and referrer */
authority = redir + sizeof("https://") - 1;
if ((local_part = strchr(authority, '/')) == NULL)
local_part = authority + strlen(authority);
if ((s1 = strndup(authority, local_part - authority)) == NULL)
return bad_enomem(connection, ENOMEM);
authority = referer + sizeof("https://") - 1;
if ((local_part = strchr(authority, '/')) == NULL)
local_part = authority + strlen(authority);
if ((s2 = strndup(authority, local_part - authority)) == NULL) {
free(s1);
return bad_enomem(connection, ENOMEM);
}
/* Both must match */
if (strcasecmp(s1, s2)) {
free(s2);
free(s1);
return bad_403(connection, EACCES,
"Redirect request does not match referer");
}
free(s2);
if (strchr(s1, '@')) {
free(s1);
return bad_403(connection, EACCES,
"Redirect request authority has login information");
}
/* Extract hostname portion of authority and format GSS name */
if (strchr(s1, ':'))
*strchr(s1, ':') = '\0';
if (asprintf(freeme, "HTTP@%s", s1) == -1 || *freeme == NULL) {
free(s1);
return bad_enomem(connection, ENOMEM);
}
*out_target = *freeme;
*out_redir = redir;
free(s1);
return 0;
}
/*
* Implements /bnegotiate end-point.
*
* Query parameters:
* Query parameters (mutually exclusive):
*
* - target=<name> (REQUIRED)
* - nametype=hostbased-service|exported-name|krb5 (OPTIONAL)
* - redirect=<URL-encoded-URL> (OPTIONAL)
* - target=<name>
* - redirect=<URL-encoded-URL>
*
* If the redirect query parameter is set then the Referer: header must be as
* well, and the authority of the redirect and Referer URIs must be the same.
*/
static krb5_error_code
bnegotiate(struct MHD_Connection *connection)
@@ -1345,21 +1454,18 @@ bnegotiate(struct MHD_Connection *connection)
char *nego_tok = NULL;
char *cprinc_from_token = NULL;
char *ccname = NULL;
char *freeme = NULL;
target = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND,
"target");
if (target == NULL)
return bad_400(connection, EINVAL,
"Query missing 'target' parameter value");
redir = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND,
"redirect");
/* bnegotiate_get_target() calls bad_req() */
ret = bnegotiate_get_target(connection, &target, &redir, &freeme);
if (ret)
return ret == -1 ? MHD_NO : MHD_YES;
if ((ret = validate_token(connection, &token_times, &cprinc_from_token)))
if ((ret = validate_token(connection, &token_times,
&cprinc_from_token))) {
free(freeme);
return ret; /* validate_token() calls bad_req() */
if (cprinc_from_token == NULL)
return bad_400(connection, EINVAL,
"Could not extract principal name from token");
}
/*
* Make sure we have Kerberos credentials for cprinc. If we have them
@@ -1390,6 +1496,7 @@ bnegotiate(struct MHD_Connection *connection)
free(cprinc_from_token);
free(nego_tok);
free(ccname);
free(freeme);
return ret == -1 ? MHD_NO : MHD_YES;
}

View File

@@ -378,6 +378,7 @@ read_one_token(gss_name_t service, const char *ccname, int negotiate)
return -1;
}
in.length = ret;
ret = 0;
out.length = 0;
out.value = 0;

View File

@@ -44,6 +44,7 @@ TESTS = $(SCRIPT_TESTS)
port = 49188
admport = 49189
pwport = 49190
bx509port = 49191
if HAVE_DLOPEN
do_dlopen = -e 's,[@]DLOPEN[@],true,g'
@@ -57,6 +58,7 @@ do_subst = $(heim_verbose)sed $(do_dlopen) \
-e 's,[@]srcdir[@],$(srcdir),g' \
-e 's,[@]port[@],$(port),g' \
-e 's,[@]admport[@],$(admport),g' \
-e 's,[@]bx509port[@],$(bx509port),g' \
-e 's,[@]pwport[@],$(pwport),g' \
-e 's,[@]objdir[@],$(top_builddir)/tests/kdc,g' \
-e 's,[@]top_builddir[@],$(top_builddir),g' \

View File

@@ -44,19 +44,23 @@ ${have_db} || exit 77
R=TEST.H5L.SE
DCs="DC=test,DC=h5l,DC=se"
H=datan.test.h5l.se
port=@port@
bx509port=@bx509port@
#kadmin="${kadmin} -l -r $R"
bx509="${bx509} --reverse-proxied -p $port"
kadmin="${kadmin} -l -r $R"
bx509="${bx509} --reverse-proxied -p $bx509port"
kdc="${kdc} --addresses=localhost -P $port"
server=datan.test.h5l.se
otherserver=other.test.h5l.se
cache="FILE:${objdir}/cache.krb5"
keyfile="${hx509_data}/key.der"
keyfile2="${hx509_data}/key2.der"
keytab=FILE:${objdir}/kt
kt=${objdir}/kt
keytab=FILE:${kt}
ukt=${objdir}/ukt
ukeytab=FILE:${ukt}
kinit="${kinit} -c $cache ${afs_no_afslog}"
klist="${klist} --hidden -v -c $cache"
@@ -96,9 +100,10 @@ mkdir -p simple_csr_authz
> messages.log
# We'll avoid using a KDC. We only need one for Negotiate tokens, and we'll
# use ktutil and kimpersonate to make it possible to create and accept those
# without a KDC.
# We'll avoid using a KDC for now. For testing /bx509 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. When we test /bnegotiate, however,
# we'll start a KDC.
# csr_grant ext-type value princ
csr_grant() {
@@ -114,21 +119,21 @@ csr_revoke() {
# get_cert "" curl-opts
# get_cert "&qparams" curl-opts
get_cert() {
url="http://${server}:${port}/bx509?csr=$csr${1}"
url="http://${server}:${bx509port}/bx509?csr=$csr${1}"
shift
curl -g --connect-to ${server}:${port}:localhost:${port} \
curl -g --connect-to ${server}:${bx509port}:localhost:${bx509port} \
-H "Authorization: Negotiate $token" \
"$@" "$url"
}
rm -f $kt
rm -f $kt $ukt
$ktutil -k $keytab add -r -V 1 -e aes128-cts-hmac-sha1-96 \
-p HTTP/datan.test.h5l.se@TEST.H5L.SE ||
-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 aes128-cts-hmac-sha1-96 \
-c foo@TEST.H5L.SE -s HTTP/datan.test.h5l.se@TEST.H5L.SE ||
-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; }
@@ -162,11 +167,11 @@ $hxtool ca --issue-ca --self-signed --type=pkinit-kdc \
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
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 \
@@ -184,7 +189,7 @@ $hxtool ca --issue-ca --self-signed \
$hxtool ca --issue-ca --type=https-negotiate-server \
--ca-certificate=PEM-FILE:"${objdir}/server-issuer.pem" \
--ku=digitalSignature --pk-init-principal=HTTP/${H}@${R}\
--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; }
@@ -196,10 +201,10 @@ $hxtool ca --issue-ca --type=https-negotiate-server \
# - the KDC CA tester program works
echo "Check gss-token and Negotiate token validator plugin"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$H | tr A B)
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@$H)
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; }
@@ -211,11 +216,18 @@ $hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
rm -f trivial.pem server.pem email.pem
echo "Testing plain user cert issuance KDC CA"
$test_kdc_ca -a bx509 -A foo@TEST.H5L.SE PKCS10:${objdir}/req \
$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
@@ -225,32 +237,38 @@ $hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
--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@TEST.H5L.SE PKCS10:${objdir}/req \
$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 dnsname foo.test.h5l.se foo@TEST.H5L.SE
csr_grant eku 1.3.6.1.5.5.7.3.1 foo@TEST.H5L.SE
$test_kdc_ca -a bx509 foo@TEST.H5L.SE PKCS10:${objdir}/req \
csr_grant 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@TEST.H5L.SE PKCS10:${objdir}/req \
$test_kdc_ca -a bx509 foo@${R} PKCS10:${objdir}/req \
PEM-FILE:${objdir}/email.pem &&
{ echo "Trivial offline CA test failed: unauthorized issuance (dNSName)"; exit 2; }
csr_grant email foo@test.h5l.se foo@TEST.H5L.SE
csr_grant eku 1.3.6.1.5.5.7.3.2 foo@TEST.H5L.SE
$test_kdc_ca -a bx509 foo@TEST.H5L.SE PKCS10:${objdir}/req \
{ echo "Offline CA test failed: unauthorized issuance (dNSName)"; exit 2; }
csr_grant 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"
@@ -262,12 +280,21 @@ if ! test -x ${objdir}/../../kdc/bx509d; then
exit 0
fi
echo "Creating database"
${kadmin} init \
--realm-max-ticket-life=1day \
--realm-max-renewable-life=1month \
${R} || exit 1
${kadmin} add -r --use-defaults foo@${R} || exit 1
${kadmin} modify --pkinit-acl="CN=foo,DC=test,DC=h5l,DC=se" foo@${R} || exit 1
echo "Starting bx509d"
${bx509d} --reverse-proxied -H $H --cert=${objdir}/bx509.pem -t -p $port --daemon ||
${bx509d} --reverse-proxied -H $server --cert=${objdir}/bx509.pem -t -p $bx509port --daemon ||
{ echo "bx509 failed to start"; exit 2; }
bx509pid=`getpid bx509d`
trap "kill -9 ${bx509pid}; echo signal killing bx509d; cat ca.crt kdc.crt pkinit.crt ;exit 1;" EXIT
trap "kill -9 ${bx509pid}; echo signal killing bx509d; exit 1;" EXIT
ec=0
rm -f trivial.pem server.pem email.pem
@@ -283,17 +310,21 @@ csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
# Create a barebones bx509 HTTP/1.1 client test program?
echo "Fetching a trivial user certificate"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$H)
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@TEST.H5L.SE" "FILE:${objdir}/trivial.pem"; then
-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
@@ -319,11 +350,11 @@ else
fi
echo "Fetching a server certificate with one dNSName SAN"
csr_grant dnsname $server foo@TEST.H5L.SE
csr_grant 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@TEST.H5L.SE \
--end-entity -P foo@${R} \
"FILE:${objdir}/server.pem"); then
echo 'Got a broken server certificate (has PKINIT SAN)'
exit 1
@@ -339,13 +370,13 @@ else
fi
echo "Fetching a server certificate with two dNSName SANs"
csr_grant dnsname "second-$server" foo@TEST.H5L.SE
csr_grant 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@TEST.H5L.SE \
--end-entity -P foo@${R} \
"FILE:${objdir}/server2.pem"; then
echo 'Got a broken server certificate (has PKINIT SAN)'
exit 1
@@ -363,10 +394,10 @@ else
fi
echo "Fetching an email certificate"
csr_grant email foo@bar.example foo@TEST.H5L.SE
csr_grant 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@TEST.H5L.SE" "FILE:${objdir}/email.pem"; then
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} == \"\"" \
@@ -382,30 +413,113 @@ else
exit 1
fi
if false not yet; then
# XXX Need to start a KDC to test this.
echo "Fetching a Negotiate token"
if (set -vx;
curl -o negotiate-token -Lgsf --connect-to ${server}:${port}:localhost:${port} \
-H "Authorization: Negotiate $token" \
"http://${server}:${port}/bnegotiate?target=HTTP%40${server}"); then
# bx509 sends us a token w/o a newline for now; we add one because
# gss-token expects it.
[[ -s negotiate-token ]] && echo >> negotiate-token
if [[ -s negotiate-token ]] && KRB5_KTNAME="${etc}/keytab.user" $gsstoken -Nr < negotiate-token; then
echo 'Successfully obtained a Negotiate token!'
else
echo 'Failed to get a Negotiate token!'
exit 1
fi
# Need to start a KDC to test this.
rm -f $kt $ukt
${kdestroy}
${kadmin} add -r --use-defaults HTTP/${server}@${R} || exit 1
${kadmin} ext_keytab -r -k $keytab HTTP/${server}@${R} || exit 1
${kadmin} add -r --use-defaults HTTP/${otherserver}@${R} || exit 1
${kadmin} ext_keytab -r -k $ukeytab foo@${R} || exit 1
echo "Starting kdc" ; > messages.log
${kdc} --detach --testing || { echo "kdc failed to start"; exit 1; }
kdcpid=`getpid kdc`
trap "kill -9 ${kdcpid} ${bx509pid}; echo signal killing kdc and bx509d; exit 1;" EXIT
${kinit} -kt $ukeytab foo@${R} || exit 1
$klist || { echo "failed to setup kimpersonate credentials"; exit 2; }
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; }
echo "Fetching a Negotiate token"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx;
curl -o negotiate-token -Lgsf \
--connect-to ${server}:${bx509port}:localhost:${bx509port} \
-H "Authorization: Negotiate $token" \
"http://${server}:${bx509port}/bnegotiate?target=HTTP%40${server}"); 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!'
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
echo "killing bx509d (${bx509pid})"
sh ${leaks_kill} bx509 $bx509pid || ec=1
referer=https://${otherserver}/blah
redirect=$(${rkvis} -h https://${otherserver}/blah?q=whatever)
if (set -vx;
curl -o negotiate-token -Lgsf \
--connect-to ${server}:${bx509port}:localhost:${bx509port} \
-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 \
--connect-to ${server}:${bx509port}:localhost:${bx509port} \
-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 \
--connect-to ${server}:${bx509port}:localhost:${bx509port} \
-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 \
--connect-to ${server}:${bx509port}:localhost:${bx509port} \
-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})"
sh ${leaks_kill} kdc $kdcpid || ec=1
sh ${leaks_kill} bx509d $bx509pid || ec=1
trap "" EXIT