kdc: Add Heimdal cert ext for ticket max_life
This adds support for using a Heimdal-specific PKIX extension to derive a maximum Kerberos ticket lifetime from a client's PKINIT certificate. KDC configuration parameters: - pkinit_max_life_from_cert_extension - pkinit_max_life_bound If `pkinit_max_life_from_cert_extension` is set to true then the certificate extension or EKU will be checked. If `pkinit_max_life_bound` is set to a positive relative time, then that will be the upper bound of maximum Kerberos ticket lifetime derived from these extensions. The KDC config `pkinit_ticket_max_life_from_cert` that was added earlier has been renamed to `pkinit_max_life_from_cert`. See lib/hx509 and lib/krb5/krb5.conf.5.
This commit is contained in:
@@ -101,6 +101,9 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
|
|||||||
c->enable_pkinit = FALSE;
|
c->enable_pkinit = FALSE;
|
||||||
c->pkinit_princ_in_cert = TRUE;
|
c->pkinit_princ_in_cert = TRUE;
|
||||||
c->pkinit_require_binding = TRUE;
|
c->pkinit_require_binding = TRUE;
|
||||||
|
c->pkinit_max_life_from_cert_extension = FALSE;
|
||||||
|
c->pkinit_max_life_bound = 0;
|
||||||
|
c->pkinit_dh_min_bits = 1024;
|
||||||
c->db = NULL;
|
c->db = NULL;
|
||||||
c->num_db = 0;
|
c->num_db = 0;
|
||||||
c->logf = NULL;
|
c->logf = NULL;
|
||||||
@@ -283,6 +286,23 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
|
|||||||
0,
|
0,
|
||||||
"kdc", "pkinit_dh_min_bits", NULL);
|
"kdc", "pkinit_dh_min_bits", NULL);
|
||||||
|
|
||||||
|
c->pkinit_max_life_from_cert_extension =
|
||||||
|
krb5_config_get_bool_default(context, NULL,
|
||||||
|
c->pkinit_max_life_from_cert_extension,
|
||||||
|
"kdc",
|
||||||
|
"pkinit_max_life_from_cert_extension",
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
c->pkinit_max_life_bound =
|
||||||
|
krb5_config_get_time_default(context, NULL, 0, "kdc",
|
||||||
|
"pkinit_max_life_bound",
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
c->pkinit_max_life_from_cert =
|
||||||
|
krb5_config_get_time_default(context, NULL, 0, "kdc",
|
||||||
|
"pkinit_max_life_from_cert",
|
||||||
|
NULL);
|
||||||
|
|
||||||
*config = c;
|
*config = c;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -85,6 +85,9 @@ typedef struct krb5_kdc_configuration {
|
|||||||
int pkinit_dh_min_bits;
|
int pkinit_dh_min_bits;
|
||||||
int pkinit_require_binding;
|
int pkinit_require_binding;
|
||||||
int pkinit_allow_proxy_certs;
|
int pkinit_allow_proxy_certs;
|
||||||
|
int pkinit_max_life_from_cert_extension;
|
||||||
|
krb5_timestamp pkinit_max_life_from_cert;
|
||||||
|
krb5_timestamp pkinit_max_life_bound;
|
||||||
|
|
||||||
krb5_log_facility *logf;
|
krb5_log_facility *logf;
|
||||||
|
|
||||||
|
@@ -464,7 +464,6 @@ pa_pkinit_validate(astgs_request_t r, const PA_DATA *pa)
|
|||||||
pk_client_params *pkp = NULL;
|
pk_client_params *pkp = NULL;
|
||||||
char *client_cert = NULL;
|
char *client_cert = NULL;
|
||||||
krb5_error_code ret;
|
krb5_error_code ret;
|
||||||
krb5_timestamp max_life;
|
|
||||||
|
|
||||||
ret = _kdc_pk_rd_padata(r, pa, &pkp);
|
ret = _kdc_pk_rd_padata(r, pa, &pkp);
|
||||||
if (ret || pkp == NULL) {
|
if (ret || pkp == NULL) {
|
||||||
@@ -481,13 +480,8 @@ pa_pkinit_validate(astgs_request_t r, const PA_DATA *pa)
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
max_life = krb5_config_get_time_default(r->context, NULL, 0, "kdc",
|
|
||||||
"pkinit_ticket_max_life_from_cert",
|
|
||||||
NULL);
|
|
||||||
if (max_life > 0) {
|
|
||||||
r->pa_max_life = max_life;
|
|
||||||
r->pa_endtime = _kdc_pk_endtime(pkp);
|
r->pa_endtime = _kdc_pk_endtime(pkp);
|
||||||
}
|
r->pa_max_life = _kdc_pk_max_life(pkp);
|
||||||
|
|
||||||
_kdc_r_log(r, 4, "PKINIT pre-authentication succeeded -- %s using %s",
|
_kdc_r_log(r, 4, "PKINIT pre-authentication succeeded -- %s using %s",
|
||||||
r->cname, client_cert);
|
r->cname, client_cert);
|
||||||
@@ -2231,19 +2225,23 @@ _kdc_as_rep(astgs_request_t r)
|
|||||||
|
|
||||||
/* be careful not overflowing */
|
/* be careful not overflowing */
|
||||||
|
|
||||||
if (r->client->entry.max_life) {
|
/*
|
||||||
|
* Pre-auth can override r->client->entry.max_life if configured.
|
||||||
|
*
|
||||||
|
* See pre-auth methods, specifically PKINIT, which can get or derive
|
||||||
|
* this from the client's certificate.
|
||||||
|
*/
|
||||||
|
if (r->pa_max_life > 0)
|
||||||
|
t = start + min(t - start, r->pa_max_life);
|
||||||
|
else if (r->client->entry.max_life)
|
||||||
t = start + min(t - start, *r->client->entry.max_life);
|
t = start + min(t - start, *r->client->entry.max_life);
|
||||||
if (r->pa_max_life > 0 &&
|
|
||||||
r->pa_endtime > 0 &&
|
|
||||||
t < r->pa_endtime &&
|
|
||||||
r->pa_max_life > *r->client->entry.max_life)
|
|
||||||
t = start + min(r->pa_endtime - start, r->pa_max_life);
|
|
||||||
} else if (r->pa_max_life > 0 &&
|
|
||||||
r->pa_endtime > 0 &&
|
|
||||||
t < r->pa_endtime)
|
|
||||||
t = start + min(r->pa_endtime - start, r->pa_max_life);
|
|
||||||
if (r->server->entry.max_life)
|
if (r->server->entry.max_life)
|
||||||
t = start + min(t - start, *r->server->entry.max_life);
|
t = start + min(t - start, *r->server->entry.max_life);
|
||||||
|
|
||||||
|
/* Pre-auth can bound endtime as well */
|
||||||
|
if (r->pa_endtime > 0)
|
||||||
|
t = start + min(t - start, r->pa_endtime);
|
||||||
#if 0
|
#if 0
|
||||||
t = min(t, start + realm->max_life);
|
t = min(t, start + realm->max_life);
|
||||||
#endif
|
#endif
|
||||||
|
22
kdc/pkinit.c
22
kdc/pkinit.c
@@ -59,6 +59,8 @@ struct pk_client_params {
|
|||||||
} ecdh;
|
} ecdh;
|
||||||
} u;
|
} u;
|
||||||
hx509_cert cert;
|
hx509_cert cert;
|
||||||
|
krb5_timestamp endtime;
|
||||||
|
krb5_timestamp max_life;
|
||||||
unsigned nonce;
|
unsigned nonce;
|
||||||
EncryptionKey reply_key;
|
EncryptionKey reply_key;
|
||||||
char *dh_group_name;
|
char *dh_group_name;
|
||||||
@@ -802,7 +804,13 @@ out:
|
|||||||
krb5_timestamp
|
krb5_timestamp
|
||||||
_kdc_pk_endtime(pk_client_params *pkp)
|
_kdc_pk_endtime(pk_client_params *pkp)
|
||||||
{
|
{
|
||||||
return hx509_cert_get_notAfter(pkp->cert);
|
return pkp->endtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
krb5_timestamp
|
||||||
|
_kdc_pk_max_life(pk_client_params *pkp)
|
||||||
|
{
|
||||||
|
return pkp->max_life;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1695,6 +1703,18 @@ _kdc_pk_check_client(astgs_request_t r,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cp->endtime = hx509_cert_get_notAfter(cp->cert);
|
||||||
|
cp->max_life = 0;
|
||||||
|
if (config->pkinit_max_life_from_cert_extension)
|
||||||
|
cp->max_life =
|
||||||
|
hx509_cert_get_pkinit_max_life(context->hx509ctx, cp->cert,
|
||||||
|
config->pkinit_max_life_bound);
|
||||||
|
if (cp->max_life == 0 && config->pkinit_max_life_from_cert > 0) {
|
||||||
|
cp->max_life = cp->endtime - hx509_cert_get_notBefore(cp->cert);
|
||||||
|
if (cp->max_life > config->pkinit_max_life_from_cert)
|
||||||
|
cp->max_life = config->pkinit_max_life_from_cert;
|
||||||
|
}
|
||||||
|
|
||||||
ret = hx509_cert_get_base_subject(context->hx509ctx,
|
ret = hx509_cert_get_base_subject(context->hx509ctx,
|
||||||
cp->cert,
|
cp->cert,
|
||||||
&name);
|
&name);
|
||||||
|
@@ -840,14 +840,42 @@ Defaults to
|
|||||||
.It Li pkinit_dh_min_bits = Va NUMBER
|
.It Li pkinit_dh_min_bits = Va NUMBER
|
||||||
Minimum acceptable modular Diffie-Hellman public key size in
|
Minimum acceptable modular Diffie-Hellman public key size in
|
||||||
bits.
|
bits.
|
||||||
.It Li pkinit_ticket_max_life_from_cert = Va TIME
|
.It Li pkinit_max_life_from_cert_extension = Va BOOL
|
||||||
|
If set to
|
||||||
|
.Va true
|
||||||
|
then the KDC will override the
|
||||||
|
.Va max_life
|
||||||
|
attribute of the client principal's HDB record with a maximum
|
||||||
|
ticket life taken from a certificate extension with OID
|
||||||
|
.Va { iso(1) member-body(2) se(752) su(43) heim-pkix(16) 4 }
|
||||||
|
and the DER encoding of an
|
||||||
|
.Va INTEGER
|
||||||
|
number of seconds.
|
||||||
|
Alternatively, if the extended key usage OID
|
||||||
|
.Va { iso(1) member-body(2) se(752) su(43) heim-pkix(16) 3 }
|
||||||
|
is included in the client's certificate, then the
|
||||||
|
.Va notAfter
|
||||||
|
minus the current time will be used.
|
||||||
|
.It Li pkinit_max_life_bound = Va TIME
|
||||||
|
If set, this will be a hard bound on the maximum ticket lifetime
|
||||||
|
taken from the client's certificate.
|
||||||
|
As usual,
|
||||||
|
.Va TIME
|
||||||
|
can be given as a number followed by a unit, such as
|
||||||
|
.Dq 2d
|
||||||
|
for
|
||||||
|
.Dq two days .
|
||||||
|
.It Li pkinit_max_life_from_cert = Va TIME
|
||||||
If set, this will override the
|
If set, this will override the
|
||||||
.Va max_life
|
.Va max_life
|
||||||
attribute of the client principal's HDB record with the
|
attribute of the client principal's HDB record with the
|
||||||
.Va notAfter
|
.Va notAfter
|
||||||
of the client's certificate minus the current time, bounded to
|
of the client's certificate minus the current time, bounded to
|
||||||
the given relative
|
the given relative
|
||||||
.Va TIME .
|
.Va TIME
|
||||||
|
unless the
|
||||||
|
.Li pkinit_max_life_from_cert_extension
|
||||||
|
parameter is set and the client's certificate has that extension.
|
||||||
As usual,
|
As usual,
|
||||||
.Va TIME
|
.Va TIME
|
||||||
can be given as a number followed by a unit, such as
|
can be given as a number followed by a unit, such as
|
||||||
|
@@ -189,31 +189,65 @@ ${hxtool} issue-certificate \
|
|||||||
echo foo > ${objdir}/foopassword
|
echo foo > ${objdir}/foopassword
|
||||||
|
|
||||||
echo Starting kdc ; > messages.log
|
echo Starting kdc ; > messages.log
|
||||||
|
KRB5_CONFIG="${objdir}/krb5-pkinit2.conf"
|
||||||
${kdc} --detach --testing || { echo "kdc failed to start"; exit 1; }
|
${kdc} --detach --testing || { echo "kdc failed to start"; exit 1; }
|
||||||
kdcpid=`getpid kdc`
|
kdcpid=`getpid kdc`
|
||||||
|
|
||||||
trap "kill -9 ${kdcpid}; echo signal killing kdc; cat ca.crt kdc.crt pkinit.crt ;exit 1;" EXIT
|
trap 'kill -9 ${kdcpid}; echo signal killing kdc; cat ca.crt kdc.crt pkinit.crt; exit 1;' EXIT
|
||||||
|
|
||||||
ec=0
|
ec=0
|
||||||
|
|
||||||
|
echo "Trying pk-init (principal in cert; longer max_life)"; > messages.log
|
||||||
|
base="${objdir}"
|
||||||
|
${kinit} --lifetime=5d -C FILE:${base}/pkinit.crt,${keyfile2} bar@${R} || \
|
||||||
|
{ ec=1 ; eval "${testfailed}"; }
|
||||||
|
${kgetcred} ${server}@${R} || { ec=1 ; eval "${testfailed}"; }
|
||||||
|
${klist}
|
||||||
|
if jq --version >/dev/null 2>&1 && jq -ne true >/dev/null 2>&1; then
|
||||||
|
${klistjson} |
|
||||||
|
jq -e '(((.tickets[0].Expires|
|
||||||
|
strptime("%b %d %H:%M:%S %Y")|mktime) - now) / 86400) |
|
||||||
|
(floor < 4)' >/dev/null &&
|
||||||
|
{ ec=1 ; eval "${testfailed}"; }
|
||||||
|
fi
|
||||||
|
${kdestroy}
|
||||||
|
|
||||||
|
echo "Restarting kdc ($kdcpid)"
|
||||||
|
sh ${leaks_kill} kdc $kdcpid || ec=1
|
||||||
|
KRB5_CONFIG="${objdir}/krb5-pkinit.conf"
|
||||||
|
${kdc} --detach --testing || { echo "kdc failed to start"; exit 1; }
|
||||||
|
kdcpid=`getpid kdc`
|
||||||
|
|
||||||
echo "Trying pk-init (principal in cert)"; > messages.log
|
echo "Trying pk-init (principal in cert)"; > messages.log
|
||||||
base="${objdir}"
|
base="${objdir}"
|
||||||
${kinit} -C FILE:${base}/pkinit.crt,${keyfile2} bar@${R} || \
|
${kinit} -C FILE:${base}/pkinit.crt,${keyfile2} bar@${R} || \
|
||||||
{ ec=1 ; eval "${testfailed}"; }
|
{ ec=1 ; eval "${testfailed}"; }
|
||||||
${kgetcred} ${server}@${R} || { ec=1 ; eval "${testfailed}"; }
|
${kgetcred} ${server}@${R} || { ec=1 ; eval "${testfailed}"; }
|
||||||
${klist}
|
${klist}
|
||||||
|
if jq --version >/dev/null 2>&1 && jq -ne true >/dev/null 2>&1; then
|
||||||
|
${klistjson} |
|
||||||
|
jq -e '(((.tickets[0].Expires|
|
||||||
|
strptime("%b %d %H:%M:%S %Y")|mktime) - now) / 86400) |
|
||||||
|
(floor > 1)' >/dev/null &&
|
||||||
|
{ ec=1 ; eval "${testfailed}"; }
|
||||||
|
fi
|
||||||
${kdestroy}
|
${kdestroy}
|
||||||
|
|
||||||
echo "Restarting kdc (${kdcpid}) for longer max_life test"
|
echo "Trying pk-init (principal in cert; longer max_life from cert ext)"; > messages.log
|
||||||
sh ${leaks_kill} kdc $kdcpid || ec=1
|
# Re-issue cert with --pkinit-max-life=7d
|
||||||
KRB5_CONFIG="${objdir}/krb5-pkinit2.conf"
|
${hxtool} issue-certificate \
|
||||||
${kdc} --detach --testing || { echo "kdc failed to start"; exit 1; }
|
--ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
|
||||||
kdcpid=`getpid kdc`
|
--type="pkinit-client" \
|
||||||
|
--pk-init-principal="bar@TEST.H5L.SE" \
|
||||||
echo "Trying pk-init (principal in cert; longer max_life)"; > messages.log
|
--req="PKCS10:req-pkinit.der" \
|
||||||
|
--lifetime=7d \
|
||||||
|
--pkinit-max-life=7d \
|
||||||
|
--certificate="FILE:pkinit.crt" || exit 1
|
||||||
base="${objdir}"
|
base="${objdir}"
|
||||||
|
set -vx
|
||||||
${kinit} --lifetime=5d -C FILE:${base}/pkinit.crt,${keyfile2} bar@${R} || \
|
${kinit} --lifetime=5d -C FILE:${base}/pkinit.crt,${keyfile2} bar@${R} || \
|
||||||
{ ec=1 ; eval "${testfailed}"; }
|
{ ec=1 ; eval "${testfailed}"; }
|
||||||
|
set +vx
|
||||||
${kgetcred} ${server}@${R} || { ec=1 ; eval "${testfailed}"; }
|
${kgetcred} ${server}@${R} || { ec=1 ; eval "${testfailed}"; }
|
||||||
${klist}
|
${klist}
|
||||||
if jq --version >/dev/null 2>&1 && jq -ne true >/dev/null 2>&1; then
|
if jq --version >/dev/null 2>&1 && jq -ne true >/dev/null 2>&1; then
|
||||||
|
@@ -19,7 +19,8 @@
|
|||||||
pkinit_identity = FILE:@objdir@/kdc.crt,@srcdir@/../../lib/hx509/data/key2.der
|
pkinit_identity = FILE:@objdir@/kdc.crt,@srcdir@/../../lib/hx509/data/key2.der
|
||||||
pkinit_anchors = FILE:@objdir@/ca.crt
|
pkinit_anchors = FILE:@objdir@/ca.crt
|
||||||
pkinit_mappings_file = @srcdir@/pki-mapping
|
pkinit_mappings_file = @srcdir@/pki-mapping
|
||||||
pkinit_ticket_max_life_from_cert = @max_life_from_cert@
|
pkinit_max_life_from_cert_extension = true
|
||||||
|
pkinit_max_life_from_cert = @max_life_from_cert@
|
||||||
|
|
||||||
plugin_dir = @objdir@/../../kdc/.libs
|
plugin_dir = @objdir@/../../kdc/.libs
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user