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:
@@ -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");
|
||||||
|
@@ -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.
|
||||||
*
|
*
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
||||||
|
@@ -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);
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user