hx509: 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:

 - a `--pkinit-max-life` to the `hxtool ca` command
 - `hx509_ca_tbs_set_pkinit_max_life()`
 - `hx509_cert_get_pkinit_max_life()`
 - `HX509_CA_TEMPLATE_PKINIT_MAX_LIFE`

There are two extensions.  One is an EKU, which if present means that
the maximum ticket lifetime should be derived from the notAfter minus
notBefore.  The other is a certificate extension whose value is a
maximum ticket lifetime in seconds.  The latter is preferred.
This commit is contained in:
Nicolas Williams
2021-03-24 15:57:15 -05:00
parent f0e628c2cf
commit 15b2094079
7 changed files with 112 additions and 4 deletions

View File

@@ -58,6 +58,7 @@ struct hx509_ca_tbs {
} flags; } flags;
time_t notBefore; time_t notBefore;
time_t notAfter; time_t notAfter;
HeimPkinitPrincMaxLifeSecs pkinitTicketMaxLife;
int pathLenConstraint; /* both for CA and Proxy */ int pathLenConstraint; /* both for CA and Proxy */
CRLDistributionPoints crldp; CRLDistributionPoints crldp;
heim_bit_string subjectUniqueID; heim_bit_string subjectUniqueID;
@@ -186,6 +187,15 @@ hx509_ca_tbs_set_notAfter_lifetime(hx509_context context,
return hx509_ca_tbs_set_notAfter(context, tbs, time(NULL) + delta); return hx509_ca_tbs_set_notAfter(context, tbs, time(NULL) + delta);
} }
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_ca_tbs_set_pkinit_max_life(hx509_context context,
hx509_ca_tbs tbs,
time_t max_life)
{
tbs->pkinitTicketMaxLife = max_life;
return 0;
}
static const struct units templatebits[] = { static const struct units templatebits[] = {
{ "ExtendedKeyUsage", HX509_CA_TEMPLATE_EKU }, { "ExtendedKeyUsage", HX509_CA_TEMPLATE_EKU },
{ "KeyUsage", HX509_CA_TEMPLATE_KU }, { "KeyUsage", HX509_CA_TEMPLATE_KU },
@@ -194,6 +204,7 @@ static const struct units templatebits[] = {
{ "notBefore", HX509_CA_TEMPLATE_NOTBEFORE }, { "notBefore", HX509_CA_TEMPLATE_NOTBEFORE },
{ "serial", HX509_CA_TEMPLATE_SERIAL }, { "serial", HX509_CA_TEMPLATE_SERIAL },
{ "subject", HX509_CA_TEMPLATE_SUBJECT }, { "subject", HX509_CA_TEMPLATE_SUBJECT },
{ "pkinitMaxLife", HX509_CA_TEMPLATE_PKINIT_MAX_LIFE },
{ NULL, 0 } { NULL, 0 }
}; };
@@ -285,6 +296,12 @@ hx509_ca_tbs_set_template(hx509_context context,
} }
free_ExtKeyUsage(&eku); free_ExtKeyUsage(&eku);
} }
if (flags & HX509_CA_TEMPLATE_PKINIT_MAX_LIFE) {
time_t max_life;
if ((max_life = hx509_cert_get_pkinit_max_life(context, cert, 0)) > 0)
hx509_ca_tbs_set_pkinit_max_life(context, tbs, max_life);
}
return 0; return 0;
} }
@@ -1812,7 +1829,7 @@ ca_sign(hx509_context context,
goto out; goto out;
} }
/* add KeyUsage */ /* Add KeyUsage */
if (KeyUsage2int(tbs->ku) > 0) { if (KeyUsage2int(tbs->ku) > 0) {
ASN1_MALLOC_ENCODE(KeyUsage, data.data, data.length, ASN1_MALLOC_ENCODE(KeyUsage, data.data, data.length,
&tbs->ku, &size, ret); &tbs->ku, &size, ret);
@@ -1829,7 +1846,7 @@ ca_sign(hx509_context context,
goto out; goto out;
} }
/* add ExtendedKeyUsage */ /* Add ExtendedKeyUsage */
if (tbs->eku.len > 0) { if (tbs->eku.len > 0) {
ASN1_MALLOC_ENCODE(ExtKeyUsage, data.data, data.length, ASN1_MALLOC_ENCODE(ExtKeyUsage, data.data, data.length,
&tbs->eku, &size, ret); &tbs->eku, &size, ret);
@@ -1846,7 +1863,7 @@ ca_sign(hx509_context context,
goto out; goto out;
} }
/* add Subject Alternative Name */ /* Add Subject Alternative Name */
if (tbs->san.len > 0) { if (tbs->san.len > 0) {
ASN1_MALLOC_ENCODE(GeneralNames, data.data, data.length, ASN1_MALLOC_ENCODE(GeneralNames, data.data, data.length,
&tbs->san, &size, ret); &tbs->san, &size, ret);
@@ -1950,7 +1967,7 @@ ca_sign(hx509_context context,
goto out; goto out;
} }
/* add Proxy */ /* Add Proxy */
if (tbs->flags.proxy) { if (tbs->flags.proxy) {
ProxyCertInfo info; ProxyCertInfo info;
@@ -2044,6 +2061,23 @@ ca_sign(hx509_context context,
goto out; goto out;
} }
/* Add Heimdal PKINIT ticket max life extension */
if (tbs->pkinitTicketMaxLife > 0) {
ASN1_MALLOC_ENCODE(HeimPkinitPrincMaxLifeSecs, data.data, data.length,
&tbs->pkinitTicketMaxLife, &size, ret);
if (ret) {
hx509_set_error_string(context, 0, ret, "Out of memory");
goto out;
}
if (size != data.length)
_hx509_abort("internal ASN.1 encoder error");
ret = add_extension(context, tbsc, FALSE,
&asn1_oid_id_heim_ce_pkinit_princ_max_life, &data);
free(data.data);
if (ret)
goto out;
}
ASN1_MALLOC_ENCODE(TBSCertificate, data.data, data.length,tbsc, &size, ret); ASN1_MALLOC_ENCODE(TBSCertificate, data.data, data.length,tbsc, &size, ret);
if (ret) { if (ret) {
hx509_set_error_string(context, 0, ret, "malloc out of memory"); hx509_set_error_string(context, 0, ret, "malloc out of memory");

View File

@@ -1633,6 +1633,63 @@ hx509_cert_get_notAfter(hx509_cert p)
return _hx509_Time2time_t(&p->data->tbsCertificate.validity.notAfter); return _hx509_Time2time_t(&p->data->tbsCertificate.validity.notAfter);
} }
/**
* Get a maximum Kerberos credential lifetime from a Heimdal certificate
* extension.
*
* @param context hx509 context.
* @param cert Certificate.
* @param bound If larger than zero, return no more than this.
*
* @return maximum ticket lifetime.
*/
HX509_LIB_FUNCTION time_t HX509_LIB_CALL
hx509_cert_get_pkinit_max_life(hx509_context context,
hx509_cert cert,
time_t bound)
{
HeimPkinitPrincMaxLifeSecs r = 0;
size_t sz, i;
time_t b, e;
int ret;
for (i = 0; i < cert->data->tbsCertificate.extensions->len; i++) {
Extension *ext = &cert->data->tbsCertificate.extensions->val[i];
if (ext->_ioschoice_extnValue.element !=
choice_Extension_iosnumunknown &&
ext->_ioschoice_extnValue.element !=
choice_Extension_iosnum_id_heim_ce_pkinit_princ_max_life)
continue;
if (ext->_ioschoice_extnValue.element == choice_Extension_iosnumunknown &&
der_heim_oid_cmp(&asn1_oid_id_heim_ce_pkinit_princ_max_life, &ext->extnID))
continue;
if (ext->_ioschoice_extnValue.u.ext_HeimPkinitPrincMaxLife) {
r = *ext->_ioschoice_extnValue.u.ext_HeimPkinitPrincMaxLife;
} else {
ret = decode_HeimPkinitPrincMaxLifeSecs(ext->extnValue.data,
ext->extnValue.length,
&r, &sz);
/* No need to free_HeimPkinitPrincMaxLifeSecs(); it's an int */
if (ret || r < 1)
return 0;
}
if (bound > 0 && r > bound)
return bound;
return r;
}
if (hx509_cert_check_eku(context, cert,
&asn1_oid_id_heim_eku_pkinit_certlife_is_max_life, 0))
return 0;
b = hx509_cert_get_notBefore(cert);
e = hx509_cert_get_notAfter(cert);
if (e > b)
r = e - b;
if (bound > 0 && r > bound)
return bound;
return r;
}
/** /**
* Get the SubjectPublicKeyInfo structure from the hx509 certificate. * Get the SubjectPublicKeyInfo structure from the hx509 certificate.
* *

View File

@@ -197,6 +197,7 @@ typedef enum {
#define HX509_CA_TEMPLATE_SPKI 16 #define HX509_CA_TEMPLATE_SPKI 16
#define HX509_CA_TEMPLATE_KU 32 #define HX509_CA_TEMPLATE_KU 32
#define HX509_CA_TEMPLATE_EKU 64 #define HX509_CA_TEMPLATE_EKU 64
#define HX509_CA_TEMPLATE_PKINIT_MAX_LIFE 128
/* flags hx509_cms_create_signed* */ /* flags hx509_cms_create_signed* */
#define HX509_CMS_SIGNATURE_DETACHED 0x01 #define HX509_CMS_SIGNATURE_DETACHED 0x01

View File

@@ -791,6 +791,11 @@ command = {
type = "strings" type = "strings"
help = "Certificate Policy mapping (OID:OID)" help = "Certificate Policy mapping (OID:OID)"
} }
option = {
long = "pkinit-max-life"
type = "string"
help = "maximum Kerberos ticket lifetime extension for PKINIT"
}
option = { option = {
long = "req" long = "req"
type = "string" type = "string"

View File

@@ -2297,6 +2297,13 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv)
if (ret) if (ret)
hx509_err(context, 1, ret, "hx509_ca_tbs_set_notAfter_lifetime"); hx509_err(context, 1, ret, "hx509_ca_tbs_set_notAfter_lifetime");
} }
if (opt->pkinit_max_life_string) {
time_t t = parse_time(opt->pkinit_max_life_string, "s");
ret = hx509_ca_tbs_set_pkinit_max_life(context, tbs, t);
if (ret)
hx509_err(context, 1, ret, "hx509_ca_tbs_set_pkinit_max_life");
}
if (opt->self_signed_flag) { if (opt->self_signed_flag) {
ret = hx509_ca_sign_self(context, tbs, private_key, &cert); ret = hx509_ca_sign_self(context, tbs, private_key, &cert);

View File

@@ -90,6 +90,7 @@ EXPORTS
hx509_ca_tbs_set_notAfter hx509_ca_tbs_set_notAfter
hx509_ca_tbs_set_notAfter_lifetime hx509_ca_tbs_set_notAfter_lifetime
hx509_ca_tbs_set_notBefore hx509_ca_tbs_set_notBefore
hx509_ca_tbs_set_pkinit_max_life
hx509_ca_tbs_set_proxy hx509_ca_tbs_set_proxy
hx509_ca_tbs_set_serialnumber hx509_ca_tbs_set_serialnumber
hx509_ca_tbs_set_signature_algorithm hx509_ca_tbs_set_signature_algorithm
@@ -113,6 +114,7 @@ EXPORTS
hx509_cert_get_issuer hx509_cert_get_issuer
hx509_cert_get_notAfter hx509_cert_get_notAfter
hx509_cert_get_notBefore hx509_cert_get_notBefore
hx509_cert_get_pkinit_max_life
hx509_cert_get_serialnumber hx509_cert_get_serialnumber
hx509_cert_get_subject hx509_cert_get_subject
hx509_cert_have_private_key hx509_cert_have_private_key

View File

@@ -74,6 +74,7 @@ HEIMDAL_X509_1.2 {
hx509_ca_tbs_set_notAfter; hx509_ca_tbs_set_notAfter;
hx509_ca_tbs_set_notAfter_lifetime; hx509_ca_tbs_set_notAfter_lifetime;
hx509_ca_tbs_set_notBefore; hx509_ca_tbs_set_notBefore;
hx509_ca_tbs_set_pkinit_max_life;
hx509_ca_tbs_set_proxy; hx509_ca_tbs_set_proxy;
hx509_ca_tbs_set_serialnumber; hx509_ca_tbs_set_serialnumber;
hx509_ca_tbs_set_spki; hx509_ca_tbs_set_spki;
@@ -97,6 +98,7 @@ HEIMDAL_X509_1.2 {
hx509_cert_get_issuer; hx509_cert_get_issuer;
hx509_cert_get_notAfter; hx509_cert_get_notAfter;
hx509_cert_get_notBefore; hx509_cert_get_notBefore;
hx509_cert_get_pkinit_max_life;
hx509_cert_get_serialnumber; hx509_cert_get_serialnumber;
hx509_cert_get_subject; hx509_cert_get_subject;
hx509_cert_get_issuer_unique_id; hx509_cert_get_issuer_unique_id;