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:
Nicolas Williams
2021-03-24 17:47:04 -05:00
parent 15b2094079
commit dc74e9d00c
7 changed files with 134 additions and 30 deletions
+20
View File
@@ -101,6 +101,9 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
c->enable_pkinit = FALSE;
c->pkinit_princ_in_cert = 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->num_db = 0;
c->logf = NULL;
@@ -283,6 +286,23 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
0,
"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;
return 0;
+3
View File
@@ -85,6 +85,9 @@ typedef struct krb5_kdc_configuration {
int pkinit_dh_min_bits;
int pkinit_require_binding;
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;
+16 -18
View File
@@ -464,7 +464,6 @@ pa_pkinit_validate(astgs_request_t r, const PA_DATA *pa)
pk_client_params *pkp = NULL;
char *client_cert = NULL;
krb5_error_code ret;
krb5_timestamp max_life;
ret = _kdc_pk_rd_padata(r, pa, &pkp);
if (ret || pkp == NULL) {
@@ -481,13 +480,8 @@ pa_pkinit_validate(astgs_request_t r, const PA_DATA *pa)
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",
r->cname, client_cert);
@@ -2231,19 +2225,23 @@ _kdc_as_rep(astgs_request_t r)
/* 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);
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)
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
t = min(t, start + realm->max_life);
#endif
+21 -1
View File
@@ -59,6 +59,8 @@ struct pk_client_params {
} ecdh;
} u;
hx509_cert cert;
krb5_timestamp endtime;
krb5_timestamp max_life;
unsigned nonce;
EncryptionKey reply_key;
char *dh_group_name;
@@ -802,7 +804,13 @@ out:
krb5_timestamp
_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;
}
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,
cp->cert,
&name);