bx509d: Add /get-tgts batch end-point

In order to support batch jobs systems that run many users' jobs and
which jobs need credentials, we add a /get-tgts end-point that is a
batched version of the /get-tgt end-point.  This end-point returns JSON.

Also, we make GETs optional, default to not-allowed in preference of
POSTs.

We also correct handling of POST (before POSTs with non-zero-length bodies
would cause the server to close the connection), and add additional CSRF
protection features, including the ability to disable all GET requests
for /get-keys and /get-config.
This commit is contained in:
Nicolas Williams
2022-08-10 18:08:03 -05:00
parent 323f4631a4
commit ae527bf97c
4 changed files with 1414 additions and 160 deletions

View File

@@ -44,6 +44,7 @@ bx509d_LDADD = -ldl \
$(MICROHTTPD_LIBS) \ $(MICROHTTPD_LIBS) \
$(LIB_roken) \ $(LIB_roken) \
$(LIB_heimbase) \ $(LIB_heimbase) \
$(LIB_hcrypto) \
$(top_builddir)/lib/sl/libsl.la \ $(top_builddir)/lib/sl/libsl.la \
$(top_builddir)/lib/asn1/libasn1.la \ $(top_builddir)/lib/asn1/libasn1.la \
$(top_builddir)/lib/krb5/libkrb5.la \ $(top_builddir)/lib/krb5/libkrb5.la \

View File

@@ -40,7 +40,11 @@
.Op Fl Fl version .Op Fl Fl version
.Op Fl H Ar HOSTNAME .Op Fl H Ar HOSTNAME
.Op Fl d | Fl Fl daemon .Op Fl d | Fl Fl daemon
.Op Fl Fl daemon-child .Op Fl Fl allow-GET
.Op Fl Fl no-allow-GET
.Op Fl Fl csrf-protection-type= Ns Ar CSRF-PROTECTION-TYPE
.Op Fl Fl csrf-header= Ns Ar HEADER-NAME
.Op Fl Fl csrf-key-file= Ns Ar FILE
.Op Fl Fl reverse-proxied .Op Fl Fl reverse-proxied
.Op Fl p Ar port number (default: 443) .Op Fl p Ar port number (default: 443)
.Op Fl Fl cache-dir= Ns Ar DIRECTORY .Op Fl Fl cache-dir= Ns Ar DIRECTORY
@@ -53,11 +57,24 @@
.Oc .Oc
.Sh DESCRIPTION .Sh DESCRIPTION
Serves RESTful (HTTPS) GETs of Serves RESTful (HTTPS) GETs of
.Ar /bx509 and .Ar /get-cert ,
.Ar /bnegotiate , .Ar /get-tgt ,
end-points .Ar /get-tgts ,
performing corresponding kx509 and, possibly, PKINIT requests and
to the KDCs of the requested realms (or just the given REALM). .Ar /get-negotiate-token ,
end-points that implement various experimental Heimdal features:
.Bl -bullet -compact -offset indent
.It
.Li an online CA service over HTTPS,
.It
.Li a kinit-over-HTTPS service, and
.It
.Li a Negotiate token over HTTPS service.
.El
.Pp
As well, a
.Ar /health
service can be used for checking service status.
.Pp .Pp
Supported options: Supported options:
.Bl -tag -width Ds .Bl -tag -width Ds
@@ -75,6 +92,64 @@ Print version.
.Xc .Xc
Expected audience(s) of bearer tokens (i.e., acceptor name). Expected audience(s) of bearer tokens (i.e., acceptor name).
.It Xo .It Xo
.Fl Fl allow-GET
.Xc
If given, then HTTP GET will be allowed for the various
end-points other than
.Ar /health .
Otherwise only HEAD and POST will be allowed.
By default GETs are allowed, but this will change soon.
.It Xo
.Fl Fl no-allow-GET
.Xc
If given then HTTP GETs will be rejected for the various
end-points other than
.Ar /health .
.It Xo
.Fl Fl csrf-protection-type= Ns Ar CSRF-PROTECTION-TYPE
.Xc
Possible values of
.Ar CSRF-PROTECTION-TYPE
are
.Bl -bullet -compact -offset indent
.It
.Li GET-with-header
.It
.Li GET-with-token
.It
.Li POST-with-header
.It
.Li POST-with-token
.El
This may be given multiple times.
The default is to require CSRF tokens for POST requests, and to
require neither a non-simple header nor a CSRF token for GET
requests.
.Pp
See
.Sx CROSS-SITE REQUEST FORGERY PROTECTION .
.It Xo
.Fl Fl csrf-header= Ns Ar HEADER-NAME
.Xc
If given, then all requests other than to the
.Ar /health
service must have the given request
.Ar HEADER-NAME
set (the value is irrelevant).
The
.Ar HEADER-NAME
must be a request header name such that a request having it makes
it not a
.Dq simple
request (see the Cross-Origin Resource Sharing specification).
Defaults to
.Ar X-CSRF .
.It Xo
.Fl Fl csrf-key-file= Ns Ar FILE
.Xc
If given, this file must contain a 16 byte binary key for keying
the HMAC used in CSRF token construction.
.It Xo
.Fl d , .Fl d ,
.Fl Fl daemon .Fl Fl daemon
.Xc .Xc
@@ -82,7 +157,8 @@ Detach from TTY and run in the background.
.It Xo .It Xo
.Fl Fl reverse-proxied .Fl Fl reverse-proxied
.Xc .Xc
Serves HTTP instead of HTTPS, accepting only looped-back connections. Serves HTTP instead of HTTPS, accepting only looped-back
connections.
.It Xo .It Xo
.Fl p Ar port number (default: 443) .Fl p Ar port number (default: 443)
.Xc .Xc
@@ -90,29 +166,106 @@ PORT
.It Xo .It Xo
.Fl Fl cache-dir= Ns Ar DIRECTORY .Fl Fl cache-dir= Ns Ar DIRECTORY
.Xc .Xc
Directory for various caches. If not specified then a temporary directory will Directory for various caches.
be made. If not specified then a temporary directory will be made.
.It Xo .It Xo
.Fl Fl cert= Ns Ar HX509-STORE .Fl Fl cert= Ns Ar HX509-STORE
.Xc .Xc
Certificate file path (PEM) for HTTPS service. May contain private key as Certificate file path (PEM) for HTTPS service.
well. May contain private key as well.
.It Xo .It Xo
.Fl Fl private-key= Ns Ar HX509-STORE .Fl Fl private-key= Ns Ar HX509-STORE
.Xc .Xc
Private key file path (PEM), if the private key is not stored along with the Private key file path (PEM), if the private key is not stored
certificiate. along with the certificiate.
.It Xo .It Xo
.Fl t , .Fl t ,
.Fl Fl thread-per-client .Fl Fl thread-per-client
.Xc .Xc
Uses a thread per-client instead of as many threads as there are CPUs. Uses a thread per-client instead of as many threads as there are
CPUs.
.It Xo .It Xo
.Fl v , .Fl v ,
.Fl Fl verbose= Ns Ar run verbosely .Fl Fl verbose= Ns Ar run verbosely
.Xc .Xc
verbose verbose
.El .El
.Sh HTTP APIS
All HTTP APIs served by this program accept POSTs, with all
request parameters given as URI query parameters and/or as
form data in the POST request body, in either
.Ar application/x-www-form-urlencoded
or
.Ar multipart/formdata .
If request parameters are given both as URI query parameters
and as POST forms, then they are merged into a set.
.Pp
If GETs are enabled, then request parameters must be supplied
only as URI query parameters, as GET requests do not have request
bodies.
.Pp
URI query parameters must be of the form
.Ar param0=value&param1=value...
.Pp
Some request parameters can only have one value.
If multiple values are given for such parameters, then either an
error will be produced, or only the first URI query parameter
value will be used, or the first POST form data parameter will be
used.
Other request parameters can have multiple values.
See below.
.Sh CROSS-SITE REQUEST FORGERY PROTECTION
.Em None
of the resources service by this service are intended to be
executed by web pages.
.Pp
All the resources provided by this service are
.Dq safe
in the sense that they do not change server-side state besides
logging, and in that they are idempotent, but they are
only safe to execute
.Em if and only if
the requesting party is trusted to see the response.
Since none of these resources are intended to be used from web
pages, it is important that web pages not be able to execute them
.Em and
observe the responses.
.Pp
In a web browser context, pages from other origins will be able
to attempt requests to this service, but should never be able to
see the responses because browsers normally wouldn't allow that.
Nonetheless, anti cross site request forgery (CSRF) protection
may be desirable.
.Pp
This service provides the following CSRF protection features:
.Bl -tag -width Ds -offset indent
.It requests are rejected if they have a
.Dq Referer
(except the experimental /get-negotiate-token end-point)
.It the service can be configured to require a header that would make the
request not Dq simple
.It GETs can be disabled (see options), thus requiring POSTs
.It GETs can be required to have a CSRF token (see below)
.It POSTs can be required to have a CSRF token
.El
.Pp
The experimental
.Ar /get-negotiate-token
end-point, however, always accepts
.Dq Referer
requests.
.Pp
To obtain a CSRF token, first execute the request without the
CSRF token, and the resulting error
response will include a
.Ar X-CSRF-Token
response header.
.Pp
To execute a request with a CSRF token, first obtain a CSRF token
as described above, then copy the token to the request as the
value of the request's
.Ar X-CSRF-Token
header.
.Sh ONLINE CERTIFICATION AUTHORITY HTTP API .Sh ONLINE CERTIFICATION AUTHORITY HTTP API
This service provides an HTTP-based Certification Authority (CA). This service provides an HTTP-based Certification Authority (CA).
CA credentials and configuration are specified in the CA credentials and configuration are specified in the
@@ -128,8 +281,8 @@ with the base-63 encoding of a DER encoding of a PKCS#10
.Ar CertificationRequest .Ar CertificationRequest
(Certificate Signing Request, or CSR) in a (Certificate Signing Request, or CSR) in a
.Ar csr .Ar csr
required query parameter. required request parameter.
In a successful query, the response body will contain a PEM In a successful request, the response body will contain a PEM
encoded end entity certificate and certification chain. encoded end entity certificate and certification chain.
.Pp .Pp
Or Or
@@ -146,9 +299,9 @@ Unauthorized requests will elicit a 403 response.
.Pp .Pp
Subject Alternative Names (SANs) and Extended Key Usage values Subject Alternative Names (SANs) and Extended Key Usage values
may be requested, both in-band in the CSR as a requested may be requested, both in-band in the CSR as a requested
extensions attribute, and/or via optional query parameters. extensions attribute, and/or via optional request parameters.
.Pp .Pp
Supported query parameters (separated by ampersands) Supported request parameters:
.Bl -tag -width Ds -offset indent .Bl -tag -width Ds -offset indent
.It Li csr = Va base64-encoded-DER-encoded-CSR .It Li csr = Va base64-encoded-DER-encoded-CSR
.It Li dNSName = Va hostname .It Li dNSName = Va hostname
@@ -178,20 +331,20 @@ of
.Ar /get-negotiate-token .Ar /get-negotiate-token
with a with a
.Ar target = Ar service@host .Ar target = Ar service@host
query parameter. request parameter.
.Pp .Pp
In a successful query, the response body will contain a Negotiate In a successful request, the response body will contain a
token for the authenticated client principal to the requested Negotiate token for the authenticated client principal to the
target. requested target.
.Pp .Pp
Authentication is required. Authentication is required.
Unauthenticated requests will elicit a 401 response. Unauthenticated requests will elicit a 401 response.
.Pp .Pp
Subject Alternative Names (SANs) and Extended Key Usage values Subject Alternative Names (SANs) and Extended Key Usage values
may be requested, both in-band in the CSR as a requested may be requested, both in-band in the CSR as a requested
extensions attribute, and/or via optional query parameters. extensions attribute, and/or via optional request parameters.
.Pp .Pp
Supported query parameters (separated by ampersands) Supported request parameters:
.Bl -tag -width Ds -offset indent .Bl -tag -width Ds -offset indent
.It Li target = Va service@hostname .It Li target = Va service@hostname
.It Li redirect = Va URI .It Li redirect = Va URI
@@ -221,13 +374,14 @@ The protocol consists of a
of of
.Ar /get-tgt . .Ar /get-tgt .
.Pp .Pp
Supported query parameters (separated by ampersands) Supported request parameters:
.Bl -tag -width Ds -offset indent .Bl -tag -width Ds -offset indent
.It Li cname = Va principal-name .It Li cname = Va principal-name
.It Li address = Va IP-address .It Li address = Va IP-address
.It Li lifetime = Va relative-time
.El .El
.Pp .Pp
In a successful query, the response body will contain a TGT and In a successful request, the response body will contain a TGT and
its session key encoded as a "ccache" file contents. its session key encoded as a "ccache" file contents.
.Pp .Pp
Authentication is required. Authentication is required.
@@ -239,13 +393,14 @@ same as for
by the authenticated client principal to get a certificate with by the authenticated client principal to get a certificate with
a PKINIT SAN for itself or the requested principal if a a PKINIT SAN for itself or the requested principal if a
.Va cname .Va cname
query parameter was included. request parameter was included.
.Pp .Pp
Unauthorized requests will elicit a 403 response. Unauthorized requests will elicit a 403 response.
.Pp .Pp
Requested IP addresses will be added to the issued TGT if allowed. Requested IP addresses will be added to the issued TGT if
The IP address of the client will be included if address-less TGTs allowed.
are not allowed. The IP address of the client will be included if address-less
TGTs are not allowed.
See the See the
.Va [get-tgt] .Va [get-tgt]
section of section of
@@ -257,6 +412,48 @@ end-point, but as configured in the
.Va [get-tgt] .Va [get-tgt]
section of section of
.Xr krb5.conf 5 . .Xr krb5.conf 5 .
.Sh BATCH TGT HTTP API
Some sites may have special users that operate batch jobs systems
and that can impersonate many others by obtaining TGTs for them,
and which
.Dq prestash
credentials for those users in their credentials caches.
To support these sytems, a
.Ar GET
of
.Ar /get-tgts
with multiple
.Ar cname
request parameters will return those principals' TGTs (if the
caller is authorized).
.Pp
This is similar to the
.Ar /get-tgt
end-point, but a) multiple
.Ar cname
request parameter values may be given, and b) the caller's
principal name is not used as a default for the
.Ar cname
request parameter.
The
.Ar address
and
.Ar lifetime
request parameters are honored.
.Pp
For successful
.Ar GETs
the response body is a sequence of JSON texts each of which is a
JSON object with two keys:
.Bl -tag -width Ds -offset indent
.It Ar ccache
with a base64-encoded FILE-type ccache;
.It Ar name
the name of the principal whose credentials are in that ccache.
.El
.Sh NOTES
A future release may split all these end-points into separate
services.
.Sh ENVIRONMENT .Sh ENVIRONMENT
.Bl -tag -width Ds .Bl -tag -width Ds
.It Ev KRB5_CONFIG .It Ev KRB5_CONFIG

File diff suppressed because it is too large Load Diff

View File

@@ -42,18 +42,21 @@ testfailed="echo test failed; cat messages.log; exit 1"
# If there is no useful db support compiled in, disable test # If there is no useful db support compiled in, disable test
${have_db} || exit 77 ${have_db} || exit 77
umask 077
R=TEST.H5L.SE R=TEST.H5L.SE
DCs="DC=test,DC=h5l,DC=se" DCs="DC=test,DC=h5l,DC=se"
port=@port@ port=@port@
bx509port=@bx509port@ bx509port=@bx509port@
kadmin="${kadmin} -l -r $R"
bx509d="${bx509d} --reverse-proxied -p $bx509port"
kdc="${kdc} --addresses=localhost -P $port"
server=datan.test.h5l.se server=datan.test.h5l.se
otherserver=other.test.h5l.se otherserver=other.test.h5l.se
kadmin="${kadmin} -l -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" cachefile="${objdir}/cache.krb5"
cache="FILE:${cachefile}" cache="FILE:${cachefile}"
cachefile2="${objdir}/cache2.krb5" cachefile2="${objdir}/cache2.krb5"
@@ -131,6 +134,55 @@ get_cert() {
"$@" "$url" "$@" "$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 rm -f $kt $ukt
$ktutil -k $keytab add -r -V 1 -e aes128-cts-hmac-sha1-96 \ $ktutil -k $keytab add -r -V 1 -e aes128-cts-hmac-sha1-96 \
-p HTTP/datan.test.h5l.se@${R} || -p HTTP/datan.test.h5l.se@${R} ||
@@ -292,15 +344,15 @@ ${kadmin} init \
${R} || exit 1 ${R} || exit 1
${kadmin} add -r --use-defaults foo@${R} || exit 1 ${kadmin} add -r --use-defaults foo@${R} || exit 1
${kadmin} add -r --use-defaults bar@${R} || exit 1 ${kadmin} add -r --use-defaults bar@${R} || exit 1
${kadmin} add -r --use-defaults baz@${R} || exit 1
${kadmin} modify --pkinit-acl="CN=foo,DC=test,DC=h5l,DC=se" foo@${R} || exit 1 ${kadmin} modify --pkinit-acl="CN=foo,DC=test,DC=h5l,DC=se" foo@${R} || exit 1
echo "Starting bx509d" echo "Starting bx509d"
${bx509d} -H $server --cert=${objdir}/bx509.pem -t --daemon || ${bx509d} --daemon || { echo "bx509 failed to start"; exit 2; }
{ echo "bx509 failed to start"; exit 2; }
bx509pid=`getpid bx509d` bx509pid=`getpid bx509d`
trap "kill -9 ${bx509pid}; echo signal killing bx509d; exit 1;" EXIT trap 'kill -9 ${bx509pid}; echo signal killing bx509d; exit 1;' EXIT
ec=0 ec=0
rm -f trivial.pem server.pem email.pem rm -f trivial.pem server.pem email.pem
@@ -310,12 +362,25 @@ csr_revoke
$hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \ $hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
--key=FILE:"${objdir}/k.der" "${objdir}/req" || --key=FILE:"${objdir}/k.der" "${objdir}/req" ||
{ echo "Failed to make a CSR"; exit 2; } { echo "Failed to make a CSR"; exit 2; }
csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
# XXX Add autoconf check for curl? # XXX Add autoconf check for curl?
# Create a barebones bx509 HTTP/1.1 client test program? # Create a barebones bx509 HTTP/1.1 client test program?
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" 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) token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
if (set -vx; get_cert '' -sf -o "${objdir}/trivial.pem"); then if (set -vx; get_cert '' -sf -o "${objdir}/trivial.pem"); then
$hxtool print --content "FILE:${objdir}/trivial.pem" $hxtool print --content "FILE:${objdir}/trivial.pem"
@@ -336,6 +401,43 @@ else
exit 1 exit 1
fi 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" echo "Checking that authorization is enforced"
csr_revoke csr_revoke
get_cert '&rfc822Name=foo@bar.example' -vvv -o "${objdir}/bad1.pem" get_cert '&rfc822Name=foo@bar.example' -vvv -o "${objdir}/bad1.pem"
@@ -430,10 +532,10 @@ ${kadmin} ext_keytab -r -k $ukeytab foo@${R} || exit 1
echo "Starting kdc"; echo "Starting kdc";
${kdc} --detach --testing || { echo "kdc failed to start"; cat messages.log; exit 1; } ${kdc} --detach --testing || { echo "kdc failed to start"; cat messages.log; exit 1; }
kdcpid=`getpid kdc` kdcpid=`getpid kdc`
trap "kill -9 ${kdcpid} ${bx509pid}; echo signal killing kdc and bx509d; exit 1;" EXIT trap 'kill -9 ${kdcpid} ${bx509pid}; echo signal killing kdc and bx509d; exit 1;' EXIT
${kinit} -kt $ukeytab foo@${R} || exit 1 ${kinit} -kt $ukeytab foo@${R} || exit 1
$klist || { echo "failed to setup kimpersonate credentials"; exit 2; } $klist || { echo "failed to kinit"; exit 2; }
echo "Fetch TGT (not granted for other)" echo "Fetch TGT (not granted for other)"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server) token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
@@ -474,7 +576,7 @@ if ! (set -vx;
exit 2 exit 2
fi fi
${kgetcred} -H HTTP/${server}@${R} || ${kgetcred} -H HTTP/${server}@${R} ||
{ echo "Trivial offline CA test failed (TGS)"; exit 2; } { echo "Fetched TGT didn't work"; exit 2; }
${klist} | grep Addresses:.IPv4:8.8.8.8 || ${klist} | grep Addresses:.IPv4:8.8.8.8 ||
{ echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; } { echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; }
@@ -491,7 +593,7 @@ if ! (set -vx;
exit 2 exit 2
fi fi
${kgetcred} -H HTTP/${server}@${R} || ${kgetcred} -H HTTP/${server}@${R} ||
{ echo "Trivial offline CA test failed (TGS)"; exit 2; } { echo "Fetched TGT didn't work"; exit 2; }
${klist} | grep Addresses:.IPv4:8.8.8.8 || ${klist} | grep Addresses:.IPv4:8.8.8.8 ||
{ echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; } { echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; }
@@ -509,7 +611,7 @@ if ! (set -vx;
exit 2 exit 2
fi fi
${kgetcred} -H HTTP/${server}@${R} || ${kgetcred} -H HTTP/${server}@${R} ||
{ echo "Trivial offline CA test failed (TGS)"; exit 2; } { echo "Fetched TGT didn't work"; exit 2; }
if which jq >/dev/null; then if which jq >/dev/null; then
if ! ${klistjson} | jq -e ' if ! ${klistjson} | jq -e '
(reduce (.tickets[0]|(.Issued,.Expires)| (reduce (.tickets[0]|(.Issued,.Expires)|
@@ -535,7 +637,7 @@ if ! (set -vx;
exit 2 exit 2
fi fi
${kgetcred} -H HTTP/${server}@${R} || ${kgetcred} -H HTTP/${server}@${R} ||
{ echo "Trivial offline CA test failed (TGS)"; exit 2; } { echo "Fetched TGT didn't work"; exit 2; }
if which jq >/dev/null; then if which jq >/dev/null; then
if ! ${klistjson} | jq -e ' if ! ${klistjson} | jq -e '
(reduce (.tickets[0]|(.Issued,.Expires)| (reduce (.tickets[0]|(.Issued,.Expires)|
@@ -561,7 +663,7 @@ if ! (set -vx;
exit 2 exit 2
fi fi
${kgetcred} -H HTTP/${server}@${R} || ${kgetcred} -H HTTP/${server}@${R} ||
{ echo "Trivial offline CA test failed (TGS)"; exit 2; } { echo "Fetched TGT didn't work"; exit 2; }
if which jq >/dev/null; then if which jq >/dev/null; then
if ! ${klistjson} | jq -e ' if ! ${klistjson} | jq -e '
(reduce (.tickets[0]|(.Issued,.Expires)| (reduce (.tickets[0]|(.Issued,.Expires)|
@@ -573,6 +675,153 @@ if which jq >/dev/null; then
fi fi
fi fi
echo "Fetch TGTs (batch, authz fail)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
(set -vx; csr_grant pkinit bar@${R} foo@${R})
${kdestroy}
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
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
echo "Got TGTs with /get-tgts end-point that should have been denied"
exit 2
fi
echo "Fetch TGTs (batch, authz pass)"
${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
(csr_grant pkinit bar@${R} foo@${R})
(csr_grant 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 pkinit bar@${R} foo@${R})
(csr_grant pkinit baz@${R} foo@${R})
(csr_grant 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)" echo "Fetch negotiate token (pre-test)"
# Do what /bnegotiate does, roughly, prior to testing /bnegotiate # Do what /bnegotiate does, roughly, prior to testing /bnegotiate
$hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \ $hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
@@ -596,11 +845,9 @@ grep 'REQ.*wrongaddr=true' ${objdir}/messages.log |
echo "Fetching a Negotiate token" echo "Fetching a Negotiate token"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server) token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
csr=
if (set -vx; if (set -vx;
curl -o negotiate-token -Lgsf \ get_with_token get-negotiate-token "target=HTTP%40${server}" -o "${objdir}/negotiate-token"); then
--resolve ${server}:${bx509port}:127.0.0.1 \
-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 # bx509 sends us a token w/o a newline for now; we add one because
# gss-token expects it. # gss-token expects it.
test -s negotiate-token && echo >> negotiate-token test -s negotiate-token && echo >> negotiate-token