From 15b2094079631e229de82aa82d7335b2af8340c2 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Wed, 24 Mar 2021 15:57:15 -0500 Subject: [PATCH] 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. --- lib/hx509/ca.c | 42 ++++++++++++++++++++++--- lib/hx509/cert.c | 57 ++++++++++++++++++++++++++++++++++ lib/hx509/hx509.h | 1 + lib/hx509/hxtool-commands.in | 5 +++ lib/hx509/hxtool.c | 7 +++++ lib/hx509/libhx509-exports.def | 2 ++ lib/hx509/version-script.map | 2 ++ 7 files changed, 112 insertions(+), 4 deletions(-) diff --git a/lib/hx509/ca.c b/lib/hx509/ca.c index 4e8ab18ba..fb3456c3e 100644 --- a/lib/hx509/ca.c +++ b/lib/hx509/ca.c @@ -58,6 +58,7 @@ struct hx509_ca_tbs { } flags; time_t notBefore; time_t notAfter; + HeimPkinitPrincMaxLifeSecs pkinitTicketMaxLife; int pathLenConstraint; /* both for CA and Proxy */ CRLDistributionPoints crldp; 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); } +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[] = { { "ExtendedKeyUsage", HX509_CA_TEMPLATE_EKU }, { "KeyUsage", HX509_CA_TEMPLATE_KU }, @@ -194,6 +204,7 @@ static const struct units templatebits[] = { { "notBefore", HX509_CA_TEMPLATE_NOTBEFORE }, { "serial", HX509_CA_TEMPLATE_SERIAL }, { "subject", HX509_CA_TEMPLATE_SUBJECT }, + { "pkinitMaxLife", HX509_CA_TEMPLATE_PKINIT_MAX_LIFE }, { NULL, 0 } }; @@ -285,6 +296,12 @@ hx509_ca_tbs_set_template(hx509_context context, } 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; } @@ -1812,7 +1829,7 @@ ca_sign(hx509_context context, goto out; } - /* add KeyUsage */ + /* Add KeyUsage */ if (KeyUsage2int(tbs->ku) > 0) { ASN1_MALLOC_ENCODE(KeyUsage, data.data, data.length, &tbs->ku, &size, ret); @@ -1829,7 +1846,7 @@ ca_sign(hx509_context context, goto out; } - /* add ExtendedKeyUsage */ + /* Add ExtendedKeyUsage */ if (tbs->eku.len > 0) { ASN1_MALLOC_ENCODE(ExtKeyUsage, data.data, data.length, &tbs->eku, &size, ret); @@ -1846,7 +1863,7 @@ ca_sign(hx509_context context, goto out; } - /* add Subject Alternative Name */ + /* Add Subject Alternative Name */ if (tbs->san.len > 0) { ASN1_MALLOC_ENCODE(GeneralNames, data.data, data.length, &tbs->san, &size, ret); @@ -1950,7 +1967,7 @@ ca_sign(hx509_context context, goto out; } - /* add Proxy */ + /* Add Proxy */ if (tbs->flags.proxy) { ProxyCertInfo info; @@ -2044,6 +2061,23 @@ ca_sign(hx509_context context, 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); if (ret) { hx509_set_error_string(context, 0, ret, "malloc out of memory"); diff --git a/lib/hx509/cert.c b/lib/hx509/cert.c index f9907ea7d..cfaed79f7 100644 --- a/lib/hx509/cert.c +++ b/lib/hx509/cert.c @@ -1633,6 +1633,63 @@ hx509_cert_get_notAfter(hx509_cert p) 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. * diff --git a/lib/hx509/hx509.h b/lib/hx509/hx509.h index ee9c27811..ce065c134 100644 --- a/lib/hx509/hx509.h +++ b/lib/hx509/hx509.h @@ -197,6 +197,7 @@ typedef enum { #define HX509_CA_TEMPLATE_SPKI 16 #define HX509_CA_TEMPLATE_KU 32 #define HX509_CA_TEMPLATE_EKU 64 +#define HX509_CA_TEMPLATE_PKINIT_MAX_LIFE 128 /* flags hx509_cms_create_signed* */ #define HX509_CMS_SIGNATURE_DETACHED 0x01 diff --git a/lib/hx509/hxtool-commands.in b/lib/hx509/hxtool-commands.in index de0b9e4e3..279095d27 100644 --- a/lib/hx509/hxtool-commands.in +++ b/lib/hx509/hxtool-commands.in @@ -791,6 +791,11 @@ command = { type = "strings" help = "Certificate Policy mapping (OID:OID)" } + option = { + long = "pkinit-max-life" + type = "string" + help = "maximum Kerberos ticket lifetime extension for PKINIT" + } option = { long = "req" type = "string" diff --git a/lib/hx509/hxtool.c b/lib/hx509/hxtool.c index fef3ca2b1..7b8adfdae 100644 --- a/lib/hx509/hxtool.c +++ b/lib/hx509/hxtool.c @@ -2297,6 +2297,13 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv) if (ret) 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) { ret = hx509_ca_sign_self(context, tbs, private_key, &cert); diff --git a/lib/hx509/libhx509-exports.def b/lib/hx509/libhx509-exports.def index feb4262bc..951b57b9c 100644 --- a/lib/hx509/libhx509-exports.def +++ b/lib/hx509/libhx509-exports.def @@ -90,6 +90,7 @@ EXPORTS hx509_ca_tbs_set_notAfter hx509_ca_tbs_set_notAfter_lifetime hx509_ca_tbs_set_notBefore + hx509_ca_tbs_set_pkinit_max_life hx509_ca_tbs_set_proxy hx509_ca_tbs_set_serialnumber hx509_ca_tbs_set_signature_algorithm @@ -113,6 +114,7 @@ EXPORTS hx509_cert_get_issuer hx509_cert_get_notAfter hx509_cert_get_notBefore + hx509_cert_get_pkinit_max_life hx509_cert_get_serialnumber hx509_cert_get_subject hx509_cert_have_private_key diff --git a/lib/hx509/version-script.map b/lib/hx509/version-script.map index a3326638d..a4e702da9 100644 --- a/lib/hx509/version-script.map +++ b/lib/hx509/version-script.map @@ -74,6 +74,7 @@ HEIMDAL_X509_1.2 { hx509_ca_tbs_set_notAfter; hx509_ca_tbs_set_notAfter_lifetime; hx509_ca_tbs_set_notBefore; + hx509_ca_tbs_set_pkinit_max_life; hx509_ca_tbs_set_proxy; hx509_ca_tbs_set_serialnumber; hx509_ca_tbs_set_spki; @@ -97,6 +98,7 @@ HEIMDAL_X509_1.2 { hx509_cert_get_issuer; hx509_cert_get_notAfter; hx509_cert_get_notBefore; + hx509_cert_get_pkinit_max_life; hx509_cert_get_serialnumber; hx509_cert_get_subject; hx509_cert_get_issuer_unique_id;