diff options
Diffstat (limited to 'lib/hx509/ca.c')
| -rw-r--r-- | lib/hx509/ca.c | 1889 |
1 files changed, 1671 insertions, 218 deletions
diff --git a/lib/hx509/ca.c b/lib/hx509/ca.c index 418a404b4aa9..1ca8d51da39e 100644 --- a/lib/hx509/ca.c +++ b/lib/hx509/ca.c @@ -32,7 +32,6 @@ */ #include "hx_locl.h" -#include <pkinit_asn1.h> /** * @page page_ca Hx509 CA functions @@ -43,9 +42,11 @@ struct hx509_ca_tbs { hx509_name subject; SubjectPublicKeyInfo spki; + KeyUsage ku; ExtKeyUsage eku; GeneralNames san; - unsigned key_usage; + CertificatePolicies cps; + PolicyMappings pms; heim_integer serial; struct { unsigned int proxy:1; @@ -57,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; @@ -77,7 +79,7 @@ struct hx509_ca_tbs { * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_init(hx509_context context, hx509_ca_tbs *tbs) { *tbs = calloc(1, sizeof(**tbs)); @@ -95,20 +97,23 @@ hx509_ca_tbs_init(hx509_context context, hx509_ca_tbs *tbs) * @ingroup hx509_ca */ -void +HX509_LIB_FUNCTION void HX509_LIB_CALL hx509_ca_tbs_free(hx509_ca_tbs *tbs) { if (tbs == NULL || *tbs == NULL) return; free_SubjectPublicKeyInfo(&(*tbs)->spki); + free_CertificatePolicies(&(*tbs)->cps); + free_PolicyMappings(&(*tbs)->pms); free_GeneralNames(&(*tbs)->san); free_ExtKeyUsage(&(*tbs)->eku); der_free_heim_integer(&(*tbs)->serial); free_CRLDistributionPoints(&(*tbs)->crldp); der_free_bit_string(&(*tbs)->subjectUniqueID); der_free_bit_string(&(*tbs)->issuerUniqueID); - hx509_name_free(&(*tbs)->subject); + if ((*tbs)->subject) + hx509_name_free(&(*tbs)->subject); if ((*tbs)->sigalg) { free_AlgorithmIdentifier((*tbs)->sigalg); free((*tbs)->sigalg); @@ -132,7 +137,7 @@ hx509_ca_tbs_free(hx509_ca_tbs *tbs) * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_notBefore(hx509_context context, hx509_ca_tbs tbs, time_t t) @@ -153,7 +158,7 @@ hx509_ca_tbs_set_notBefore(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_notAfter(hx509_context context, hx509_ca_tbs tbs, time_t t) @@ -174,7 +179,7 @@ hx509_ca_tbs_set_notAfter(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_notAfter_lifetime(hx509_context context, hx509_ca_tbs tbs, time_t delta) @@ -182,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 }, @@ -190,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 } }; @@ -202,19 +217,19 @@ static const struct units templatebits[] = { * @ingroup hx509_ca */ -const struct units * +HX509_LIB_FUNCTION const struct units * HX509_LIB_CALL hx509_ca_tbs_template_units(void) { return templatebits; } /** - * Initialize the to-be-signed certificate object from a template certifiate. + * Initialize the to-be-signed certificate object from a template certificate. * * @param context A hx509 context. * @param tbs object to be signed. * @param flags bit field selecting what to copy from the template - * certifiate. + * certificate. * @param cert template certificate. * * @return An hx509 error code, see hx509_get_error_string(). @@ -222,7 +237,7 @@ hx509_ca_tbs_template_units(void) * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_template(hx509_context context, hx509_ca_tbs tbs, int flags, @@ -262,11 +277,9 @@ hx509_ca_tbs_set_template(hx509_context context, return ret; } if (flags & HX509_CA_TEMPLATE_KU) { - KeyUsage ku; - ret = _hx509_cert_get_keyusage(context, cert, &ku); + ret = _hx509_cert_get_keyusage(context, cert, &tbs->ku); if (ret) return ret; - tbs->key_usage = KeyUsage2int(ku); } if (flags & HX509_CA_TEMPLATE_EKU) { ExtKeyUsage eku; @@ -283,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; } @@ -300,7 +319,7 @@ hx509_ca_tbs_set_template(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_ca(hx509_context context, hx509_ca_tbs tbs, int pathLenConstraint) @@ -324,7 +343,7 @@ hx509_ca_tbs_set_ca(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_proxy(hx509_context context, hx509_ca_tbs tbs, int pathLenConstraint) @@ -346,7 +365,7 @@ hx509_ca_tbs_set_proxy(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_domaincontroller(hx509_context context, hx509_ca_tbs tbs) { @@ -368,7 +387,7 @@ hx509_ca_tbs_set_domaincontroller(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_spki(hx509_context context, hx509_ca_tbs tbs, const SubjectPublicKeyInfo *spki) @@ -393,7 +412,7 @@ hx509_ca_tbs_set_spki(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_serialnumber(hx509_context context, hx509_ca_tbs tbs, const heim_integer *serialNumber) @@ -406,6 +425,65 @@ hx509_ca_tbs_set_serialnumber(hx509_context context, } /** + * Copy elements of a CSR into a TBS, but only if all of them are authorized. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param req CSR + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_set_from_csr(hx509_context context, + hx509_ca_tbs tbs, + hx509_request req) +{ + hx509_san_type san_type; + heim_oid oid = { 0, 0 }; + KeyUsage ku; + size_t i; + char *s = NULL; + int ret; + + if (hx509_request_count_unauthorized(req)) { + hx509_set_error_string(context, 0, ENOMEM, "out of memory"); + return EACCES; + } + + ret = hx509_request_get_ku(context, req, &ku); + if (ret == 0 && KeyUsage2int(ku)) + ret = hx509_ca_tbs_add_ku(context, tbs, ku); + + for (i = 0; ret == 0; i++) { + free(s); s = NULL; + der_free_oid(&oid); + ret = hx509_request_get_eku(req, i, &s); + if (ret == 0) + ret = der_parse_heim_oid(s, ".", &oid); + if (ret == 0) + ret = hx509_ca_tbs_add_eku(context, tbs, &oid); + } + if (ret == HX509_NO_ITEM) + ret = 0; + + for (i = 0; ret == 0; i++) { + free(s); s = NULL; + ret = hx509_request_get_san(req, i, &san_type, &s); + if (ret == 0) + ret = hx509_ca_tbs_add_san(context, tbs, san_type, s); + } + if (ret == HX509_NO_ITEM) + ret = 0; + + der_free_oid(&oid); + free(s); + return ret; +} + +/** * An an extended key usage to the to-be-signed certificate object. * Duplicates will detected and not added. * @@ -418,7 +496,29 @@ hx509_ca_tbs_set_serialnumber(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_ku(hx509_context context, + hx509_ca_tbs tbs, + KeyUsage ku) +{ + tbs->ku = ku; + return 0; +} + +/** + * An an extended key usage to the to-be-signed certificate object. + * Duplicates will detected and not added. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param oid extended key usage to add. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_add_eku(hx509_context context, hx509_ca_tbs tbs, const heim_oid *oid) @@ -449,6 +549,127 @@ hx509_ca_tbs_add_eku(hx509_context context, } /** + * Add a certificate policy to the to-be-signed certificate object. Duplicates + * will detected and not added. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param oid policy OID. + * @param cps_uri CPS URI to qualify policy with. + * @param user_notice user notice display text to qualify policy with. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_pol(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *oid, + const char *cps_uri, + const char *user_notice) +{ + PolicyQualifierInfos pqis; + PolicyQualifierInfo pqi; + PolicyInformation pi; + size_t i, size; + int ret = 0; + + /* search for duplicates */ + for (i = 0; i < tbs->cps.len; i++) { + if (der_heim_oid_cmp(oid, &tbs->cps.val[i].policyIdentifier) == 0) + return 0; + } + + memset(&pi, 0, sizeof(pi)); + memset(&pqi, 0, sizeof(pqi)); + memset(&pqis, 0, sizeof(pqis)); + + pi.policyIdentifier = *oid; + if (cps_uri) { + CPSuri uri; + + uri.length = strlen(cps_uri); + uri.data = (void *)(uintptr_t)cps_uri; + pqi.policyQualifierId = asn1_oid_id_pkix_qt_cps; + + ASN1_MALLOC_ENCODE(CPSuri, + pqi.qualifier.data, + pqi.qualifier.length, + &uri, &size, ret); + if (ret == 0) { + ret = add_PolicyQualifierInfos(&pqis, &pqi); + free_heim_any(&pqi.qualifier); + } + } + if (ret == 0 && user_notice) { + DisplayText dt; + UserNotice un; + + dt.element = choice_DisplayText_utf8String; + dt.u.utf8String = (void *)(uintptr_t)user_notice; + un.explicitText = &dt; + un.noticeRef = 0; + + pqi.policyQualifierId = asn1_oid_id_pkix_qt_unotice; + ASN1_MALLOC_ENCODE(UserNotice, + pqi.qualifier.data, + pqi.qualifier.length, + &un, &size, ret); + if (ret == 0) { + ret = add_PolicyQualifierInfos(&pqis, &pqi); + free_heim_any(&pqi.qualifier); + } + } + + pi.policyQualifiers = pqis.len ? &pqis : 0; + + if (ret == 0) + ret = add_CertificatePolicies(&tbs->cps, &pi); + + free_PolicyQualifierInfos(&pqis); + return ret; +} + +/** + * Add a certificate policy mapping to the to-be-signed certificate object. + * Duplicates will detected and not added. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param issuer issuerDomainPolicy policy OID. + * @param subject subjectDomainPolicy policy OID. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_pol_mapping(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *issuer, + const heim_oid *subject) +{ + PolicyMapping pm; + size_t i; + + /* search for duplicates */ + for (i = 0; i < tbs->pms.len; i++) { + PolicyMapping *pmp = &tbs->pms.val[i]; + if (der_heim_oid_cmp(issuer, &pmp->issuerDomainPolicy) == 0 && + der_heim_oid_cmp(subject, &pmp->subjectDomainPolicy) == 0) + return 0; + } + + memset(&pm, 0, sizeof(pm)); + pm.issuerDomainPolicy = *issuer; + pm.subjectDomainPolicy = *subject; + return add_PolicyMappings(&tbs->pms, &pm); +} + +/** * Add CRL distribution point URI to the to-be-signed certificate * object. * @@ -462,94 +683,49 @@ hx509_ca_tbs_add_eku(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_add_crl_dp_uri(hx509_context context, hx509_ca_tbs tbs, const char *uri, hx509_name issuername) { + DistributionPointName dpn; DistributionPoint dp; + GeneralNames crlissuer; + GeneralName gn, ign; + Name in; int ret; memset(&dp, 0, sizeof(dp)); - - dp.distributionPoint = ecalloc(1, sizeof(*dp.distributionPoint)); - - { - DistributionPointName name; - GeneralName gn; - size_t size; - - name.element = choice_DistributionPointName_fullName; - name.u.fullName.len = 1; - name.u.fullName.val = &gn; - - gn.element = choice_GeneralName_uniformResourceIdentifier; - gn.u.uniformResourceIdentifier.data = rk_UNCONST(uri); - gn.u.uniformResourceIdentifier.length = strlen(uri); - - ASN1_MALLOC_ENCODE(DistributionPointName, - dp.distributionPoint->data, - dp.distributionPoint->length, - &name, &size, ret); - if (ret) { - hx509_set_error_string(context, 0, ret, - "Failed to encoded DistributionPointName"); - goto out; - } - if (dp.distributionPoint->length != size) - _hx509_abort("internal ASN.1 encoder error"); - } + memset(&gn, 0, sizeof(gn)); + memset(&ign, 0, sizeof(ign)); + memset(&in, 0, sizeof(in)); + gn.element = choice_GeneralName_uniformResourceIdentifier; + gn.u.uniformResourceIdentifier.data = rk_UNCONST(uri); + gn.u.uniformResourceIdentifier.length = strlen(uri); + dpn.element = choice_DistributionPointName_fullName; + dpn.u.fullName.len = 1; + dpn.u.fullName.val = &gn; + dp.distributionPoint = &dpn; if (issuername) { -#if 1 - /** - * issuername not supported - */ - hx509_set_error_string(context, 0, EINVAL, - "CRLDistributionPoints.name.issuername not yet supported"); - return EINVAL; -#else - GeneralNames *crlissuer; - GeneralName gn; - Name n; - - crlissuer = calloc(1, sizeof(*crlissuer)); - if (crlissuer == NULL) { - return ENOMEM; - } - memset(&gn, 0, sizeof(gn)); - - gn.element = choice_GeneralName_directoryName; - ret = hx509_name_to_Name(issuername, &n); - if (ret) { - hx509_set_error_string(context, 0, ret, "out of memory"); - goto out; - } - - gn.u.directoryName.element = n.element; - gn.u.directoryName.u.rdnSequence = n.u.rdnSequence; - - ret = add_GeneralNames(&crlissuer, &gn); - free_Name(&n); + ign.element = choice_GeneralName_directoryName; + ret = hx509_name_to_Name(issuername, &ign.u.directoryName); if (ret) { hx509_set_error_string(context, 0, ret, "out of memory"); - goto out; + return ret; } - + crlissuer.len = 1; + crlissuer.val = &ign; dp.cRLIssuer = &crlissuer; -#endif } ret = add_CRLDistributionPoints(&tbs->crldp, &dp); - if (ret) { - hx509_set_error_string(context, 0, ret, "out of memory"); - goto out; - } - -out: - free_DistributionPoint(&dp); + if (issuername) + free_Name(&ign.u.directoryName); + if (ret) + hx509_set_error_string(context, 0, ret, "out of memory"); return ret; } @@ -567,7 +743,7 @@ out: * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_add_san_otherName(hx509_context context, hx509_ca_tbs tbs, const heim_oid *oid, @@ -583,52 +759,100 @@ hx509_ca_tbs_add_san_otherName(hx509_context context, return add_GeneralNames(&tbs->san, &gn); } -/** - * Add Kerberos Subject Alternative Name to the to-be-signed - * certificate object. The principal string is a UTF8 string. - * - * @param context A hx509 context. - * @param tbs object to be signed. - * @param principal Kerberos principal to add to the certificate. - * - * @return An hx509 error code, see hx509_get_error_string(). - * - * @ingroup hx509_ca - */ +static +int +dequote_strndup(hx509_context context, const char *in, size_t len, char **out) +{ + size_t i, k; + char *s; + + *out = NULL; + if ((s = malloc(len + 1)) == NULL) { + hx509_set_error_string(context, 0, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + + for (k = i = 0; i < len; i++) { + if (in[i] == '\\') { + switch (in[++i]) { + case 't': s[k++] = '\t'; break; + case 'b': s[k++] = '\b'; break; + case 'n': s[k++] = '\n'; break; + case '0': + for (i++; i < len; i++) { + if (in[i] == '\0') + break; + if (in[i++] == '\\' && in[i] == '0') + continue; + hx509_set_error_string(context, 0, + HX509_PARSING_NAME_FAILED, + "embedded NULs not supported in " + "PKINIT SANs"); + free(s); + return HX509_PARSING_NAME_FAILED; + } + break; + case '\0': + hx509_set_error_string(context, 0, + HX509_PARSING_NAME_FAILED, + "trailing unquoted backslashes not " + "allowed in PKINIT SANs"); + free(s); + return HX509_PARSING_NAME_FAILED; + default: s[k++] = in[i]; break; + } + } else { + s[k++] = in[i]; + } + } + s[k] = '\0'; + + *out = s; + return 0; +} int -hx509_ca_tbs_add_san_pkinit(hx509_context context, - hx509_ca_tbs tbs, - const char *principal) +_hx509_make_pkinit_san(hx509_context context, + const char *principal, + heim_octet_string *os) { - heim_octet_string os; KRB5PrincipalName p; size_t size; int ret; - char *s = NULL; + os->data = NULL; + os->length = 0; memset(&p, 0, sizeof(p)); - /* parse principal */ + /* Parse principal */ { - const char *str; - char *q; - int n; + const char *str, *str_start; + size_t n, i; - /* count number of component */ + /* Count number of components */ n = 1; - for(str = principal; *str != '\0' && *str != '@'; str++){ - if(*str=='\\'){ - if(str[1] == '\0' || str[1] == '@') { + for (str = principal; *str != '\0' && *str != '@'; str++) { + if (*str == '\\') { + if (str[1] == '\0') { ret = HX509_PARSING_NAME_FAILED; hx509_set_error_string(context, 0, ret, "trailing \\ in principal name"); goto out; } str++; - } else if(*str == '/') + } else if(*str == '/') { n++; + } else if(*str == '@') { + break; + } } + if (*str != '@') { + /* Note that we allow the realm to be empty */ + ret = HX509_PARSING_NAME_FAILED; + hx509_set_error_string(context, 0, ret, "Missing @ in principal"); + goto out; + }; + p.principalName.name_string.val = calloc(n, sizeof(*p.principalName.name_string.val)); if (p.principalName.name_string.val == NULL) { @@ -637,49 +861,136 @@ hx509_ca_tbs_add_san_pkinit(hx509_context context, goto out; } p.principalName.name_string.len = n; - p.principalName.name_type = KRB5_NT_PRINCIPAL; - q = s = strdup(principal); - if (q == NULL) { - ret = ENOMEM; - hx509_set_error_string(context, 0, ret, "malloc: out of memory"); - goto out; - } - p.realm = strrchr(q, '@'); - if (p.realm == NULL) { - ret = HX509_PARSING_NAME_FAILED; - hx509_set_error_string(context, 0, ret, "Missing @ in principal"); - goto out; - }; - *p.realm++ = '\0'; - - n = 0; - while (q) { - p.principalName.name_string.val[n++] = q; - q = strchr(q, '/'); - if (q) - *q++ = '\0'; + + for (i = 0, str_start = str = principal; *str != '\0'; str++) { + if (*str=='\\') { + str++; + } else if(*str == '/') { + /* Note that we allow components to be empty */ + ret = dequote_strndup(context, str_start, str - str_start, + &p.principalName.name_string.val[i++]); + if (ret) + goto out; + str_start = str + 1; + } else if(*str == '@') { + ret = dequote_strndup(context, str_start, str - str_start, + &p.principalName.name_string.val[i++]); + if (ret == 0) + ret = dequote_strndup(context, str + 1, strlen(str + 1), &p.realm); + if (ret) + goto out; + break; + } } } - ASN1_MALLOC_ENCODE(KRB5PrincipalName, os.data, os.length, &p, &size, ret); + ASN1_MALLOC_ENCODE(KRB5PrincipalName, os->data, os->length, &p, &size, ret); if (ret) { hx509_set_error_string(context, 0, ret, "Out of memory"); goto out; } + if (size != os->length) + _hx509_abort("internal ASN.1 encoder error"); + +out: + free_KRB5PrincipalName(&p); + return ret; +} + +static int +add_ia5string_san(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *oid, + const char *string) +{ + SRVName ustring; + heim_octet_string os; + size_t size; + int ret; + + ustring.data = (void *)(uintptr_t)string; + ustring.length = strlen(string); + + os.length = 0; + os.data = NULL; + + ASN1_MALLOC_ENCODE(SRVName, os.data, os.length, &ustring, &size, ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + return ret; + } if (size != os.length) _hx509_abort("internal ASN.1 encoder error"); - ret = hx509_ca_tbs_add_san_otherName(context, - tbs, - &asn1_oid_id_pkinit_san, - &os); + ret = hx509_ca_tbs_add_san_otherName(context, tbs, oid, &os); + free(os.data); + return ret; +} + +/** + * Add DNSSRV Subject Alternative Name to the to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param dnssrv An ASCII string of the for _Service.Name. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_dnssrv(hx509_context context, + hx509_ca_tbs tbs, + const char *dnssrv) +{ + size_t i, len; + + /* Minimal DNSSRV input validation */ + if (dnssrv == 0 || dnssrv[0] != '_') { + hx509_set_error_string(context, 0, EINVAL, "Invalid DNSSRV name"); + return EINVAL; + } + for (i = 1, len = strlen(dnssrv); i < len; i++) { + if (dnssrv[i] == '.' && dnssrv[i + 1] != '\0') + break; + } + if (i == len) { + hx509_set_error_string(context, 0, EINVAL, "Invalid DNSSRV name"); + return EINVAL; + } + + return add_ia5string_san(context, tbs, + &asn1_oid_id_pkix_on_dnsSRV, dnssrv); +} + +/** + * Add Kerberos Subject Alternative Name to the to-be-signed + * certificate object. The principal string is a UTF8 string. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param principal Kerberos principal to add to the certificate. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_pkinit(hx509_context context, + hx509_ca_tbs tbs, + const char *principal) +{ + heim_octet_string os; + int ret; + + ret = _hx509_make_pkinit_san(context, principal, &os); + if (ret == 0) + ret = hx509_ca_tbs_add_san_otherName(context, tbs, + &asn1_oid_id_pkinit_san, &os); free(os.data); -out: - if (p.principalName.name_string.val) - free (p.principalName.name_string.val); - if (s) - free(s); return ret; } @@ -693,7 +1004,7 @@ add_utf8_san(hx509_context context, const heim_oid *oid, const char *string) { - const PKIXXmppAddr ustring = (const PKIXXmppAddr)(intptr_t)string; + const PKIXXmppAddr ustring = (const PKIXXmppAddr)(uintptr_t)string; heim_octet_string os; size_t size; int ret; @@ -704,17 +1015,13 @@ add_utf8_san(hx509_context context, ASN1_MALLOC_ENCODE(PKIXXmppAddr, os.data, os.length, &ustring, &size, ret); if (ret) { hx509_set_error_string(context, 0, ret, "Out of memory"); - goto out; + return ret; } if (size != os.length) _hx509_abort("internal ASN.1 encoder error"); - ret = hx509_ca_tbs_add_san_otherName(context, - tbs, - oid, - &os); + ret = hx509_ca_tbs_add_san_otherName(context, tbs, oid, &os); free(os.data); -out: return ret; } @@ -731,7 +1038,7 @@ out: * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_add_san_ms_upn(hx509_context context, hx509_ca_tbs tbs, const char *principal) @@ -752,7 +1059,7 @@ hx509_ca_tbs_add_san_ms_upn(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_add_san_jid(hx509_context context, hx509_ca_tbs tbs, const char *jid) @@ -777,7 +1084,7 @@ hx509_ca_tbs_add_san_jid(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_add_san_hostname(hx509_context context, hx509_ca_tbs tbs, const char *dnsname) @@ -805,7 +1112,7 @@ hx509_ca_tbs_add_san_hostname(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_add_san_rfc822name(hx509_context context, hx509_ca_tbs tbs, const char *rfc822Name) @@ -820,6 +1127,295 @@ hx509_ca_tbs_add_san_rfc822name(hx509_context context, return add_GeneralNames(&tbs->san, &gn); } +/* + * PermanentIdentifier is one SAN for naming devices with TPMs after their + * endorsement keys or EK certificates. See TPM 2.0 Keys for Device Identity + * and Attestation, Version 1.00, Revision 2, 9/17/2020 (DRAFT). + * + * The text on the form of permanent identifiers for TPM endorsement keys sans + * certificates is clearly problematic, saying: "When the TPM does not have an + * EK certificate, the identifierValue is a digest of a concatenation of the + * UTF8 string “EkPubkey” (terminating NULL not included) with the binary EK + * public key", but since arbitrary binary is not necessarily valid UTF-8... + * and since NULs embedded in UTF-8 might be OK in some contexts but really + * isn't in C (and Heimdal's ASN.1 compiler does not allow NULs in the + * middle of strings)... That just cannot be correct. Since elsewhere the TCG + * specs use the hex encoding of the SHA-256 digest of the DER encoding of + * public keys, that's what we should support in Heimdal, and maybe send in a + * comment. + * + * Also, even where one should use hex encoding of the SHA-256 digest of the + * DER encoding of public keys, how should the public keys be represented? + * Presumably as SPKIs, with all the required parameters and no more. + */ + +/** + * Add a Subject Alternative Name of PermanentIdentifier type to a to-be-signed + * certificate object. The permanent identifier form for TPM endorsement key + * certificates is the hex encoding of the SHA-256 digest of the DER encoding + * of the certificate. The permanent identifier form for TPM endorsement keys + * are of the form "EkPubkey<public-key>", where the form of <public-key> is + * not well specified at this point. It is the caller's responsibility to + * format the identifierValue. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param str permanent identifier name in the form "[<assigner-oid>]:[<id>]". + * @param assigner The OID of an assigner. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_permanentIdentifier_string(hx509_context context, + hx509_ca_tbs tbs, + const char *str) +{ + const heim_oid *found = NULL; + heim_oid oid; + const char *oidstr, *id; + char *freeme, *p; + int ret; + + memset(&oid, 0, sizeof(oid)); + if ((freeme = strdup(str)) == NULL) + return hx509_enomem(context); + + oidstr = freeme; + p = strchr(freeme, ':'); + if (!p) { + hx509_set_error_string(context, 0, EINVAL, + "Invalid PermanentIdentifier string (should be \"[<oid>]:[<id>]\")", + oidstr); + free(freeme); + return EINVAL; + } + if (p) { + *(p++) = '\0'; + id = p; + } + if (oidstr[0] != '\0') { + ret = der_find_heim_oid_by_name(oidstr, &found); + if (ret) { + ret = der_parse_heim_oid(oidstr, " .", &oid); + if (ret == 0) + found = &oid; + } + } + ret = hx509_ca_tbs_add_san_permanentIdentifier(context, tbs, id, found); + if (found == &oid) + der_free_oid(&oid); + free(freeme); + return ret; +} + +/** + * Add a Subject Alternative Name of PermanentIdentifier type to a to-be-signed + * certificate object. The permanent identifier form for TPM endorsement key + * certificates is the hex encoding of the SHA-256 digest of the DER encoding + * of the certificate. The permanent identifier form for TPM endorsement keys + * are of the form "EkPubkey<public-key>", where the form of <public-key> is + * not well specified at this point. It is the caller's responsibility to + * format the identifierValue. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param identifierValue The permanent identifier name. + * @param assigner The OID of an assigner. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_permanentIdentifier(hx509_context context, + hx509_ca_tbs tbs, + const char *identifierValue, + const heim_oid *assigner) +{ + PermanentIdentifier pi; + heim_utf8_string s = (void *)(uintptr_t)identifierValue; + heim_octet_string os; + size_t size; + int ret; + + pi.identifierValue = &s; + pi.assigner = (heim_oid*)(uintptr_t)assigner; + os.length = 0; + os.data = NULL; + + ASN1_MALLOC_ENCODE(PermanentIdentifier, os.data, os.length, &pi, &size, + ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + return ret; + } + if (size != os.length) + _hx509_abort("internal ASN.1 encoder error"); + + ret = hx509_ca_tbs_add_san_otherName(context, tbs, + &asn1_oid_id_pkix_on_permanentIdentifier, + &os); + free(os.data); + return ret; +} + +/** + * Add a Subject Alternative Name of HardwareModuleName type to a to-be-signed + * certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param str a string of the form "<oid>:<serial>". + * @param hwserial The serial number. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_hardwareModuleName_string(hx509_context context, + hx509_ca_tbs tbs, + const char *str) +{ + const heim_oid *found = NULL; + heim_oid oid; + const char *oidstr, *sno; + char *freeme, *p; + int ret; + + memset(&oid, 0, sizeof(oid)); + if ((freeme = strdup(str)) == NULL) + return hx509_enomem(context); + + oidstr = freeme; + p = strchr(freeme, ':'); + if (!p) { + hx509_set_error_string(context, 0, EINVAL, + "Invalid HardwareModuleName string (should be " + "\"<oid>:<serial>\")", + oidstr); + free(freeme); + return EINVAL; + } + if (p) { + *(p++) = '\0'; + sno = p; + } + if (oidstr[0] == '\0') { + found = &asn1_oid_tcg_tpm20; + } else { + ret = der_find_heim_oid_by_name(oidstr, &found); + if (ret) { + ret = der_parse_heim_oid(oidstr, " .", &oid); + if (ret == 0) + found = &oid; + } + } + if (!found) { + hx509_set_error_string(context, 0, EINVAL, + "Could not resolve or parse OID \"%s\"", + oidstr); + free(freeme); + return EINVAL; + } + ret = hx509_ca_tbs_add_san_hardwareModuleName(context, tbs, found, sno); + if (found == &oid) + der_free_oid(&oid); + free(freeme); + return ret; +} + +/** + * Add a Subject Alternative Name of HardwareModuleName type to a to-be-signed + * certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param hwtype The hardwar module type (e.g., `&asn1_oid_tcg_tpm20'). + * @param hwserial The serial number. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san_hardwareModuleName(hx509_context context, + hx509_ca_tbs tbs, + const heim_oid *hwtype, + const char *hwserial) +{ + HardwareModuleName hm; + heim_octet_string os; + size_t size; + int ret; + + hm.hwType = *hwtype; + hm.hwSerialNum.data = (void *)(uintptr_t)hwserial; + hm.hwSerialNum.length = strlen(hwserial); + os.length = 0; + os.data = NULL; + + ASN1_MALLOC_ENCODE(HardwareModuleName, os.data, os.length, &hm, &size, + ret); + if (ret) { + hx509_set_error_string(context, 0, ret, "Out of memory"); + return ret; + } + if (size != os.length) + _hx509_abort("internal ASN.1 encoder error"); + + ret = hx509_ca_tbs_add_san_otherName(context, tbs, + &asn1_oid_id_on_hardwareModuleName, + &os); + free(os.data); + return ret; +} + +/** + * Add a Subject Alternative Name of the given type to the + * to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * @param rfc822Name a string to a email address. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_ca_tbs_add_san(hx509_context context, + hx509_ca_tbs tbs, + hx509_san_type type, + const char *s) +{ + switch (type) { + case HX509_SAN_TYPE_EMAIL: + return hx509_ca_tbs_add_san_rfc822name(context, tbs, s); + case HX509_SAN_TYPE_DNSNAME: + return hx509_ca_tbs_add_san_hostname(context, tbs, s); + case HX509_SAN_TYPE_DN: + return ENOTSUP; + case HX509_SAN_TYPE_REGISTERED_ID: + return ENOTSUP; + case HX509_SAN_TYPE_XMPP: + return hx509_ca_tbs_add_san_jid(context, tbs, s); + case HX509_SAN_TYPE_PKINIT: + return hx509_ca_tbs_add_san_pkinit(context, tbs, s); + case HX509_SAN_TYPE_MS_UPN: + return hx509_ca_tbs_add_san_ms_upn(context, tbs, s); + default: + return ENOTSUP; + } +} + /** * Set the subject name of a to-be-signed certificate object. * @@ -832,7 +1428,7 @@ hx509_ca_tbs_add_san_rfc822name(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_subject(hx509_context context, hx509_ca_tbs tbs, hx509_name subject) @@ -860,7 +1456,7 @@ hx509_ca_tbs_set_subject(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_unique(hx509_context context, hx509_ca_tbs tbs, const heim_bit_string *subjectUniqueID, @@ -900,7 +1496,7 @@ hx509_ca_tbs_set_unique(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_subject_expand(hx509_context context, hx509_ca_tbs tbs, hx509_env env) @@ -909,6 +1505,23 @@ hx509_ca_tbs_subject_expand(hx509_context context, } /** + * Get the name of a to-be-signed certificate object. + * + * @param context A hx509 context. + * @param tbs object to be signed. + * + * @return An hx509 name. + * + * @ingroup hx509_ca + */ + +HX509_LIB_FUNCTION hx509_name HX509_LIB_CALL +hx509_ca_tbs_get_name(hx509_ca_tbs tbs) +{ + return tbs->subject; +} + +/** * Set signature algorithm on the to be signed certificate * * @param context A hx509 context. @@ -920,7 +1533,7 @@ hx509_ca_tbs_subject_expand(hx509_context context, * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_tbs_set_signature_algorithm(hx509_context context, hx509_ca_tbs tbs, const AlgorithmIdentifier *sigalg) @@ -957,16 +1570,7 @@ add_extension(hx509_context context, memset(&ext, 0, sizeof(ext)); - if (critical_flag) { - ext.critical = malloc(sizeof(*ext.critical)); - if (ext.critical == NULL) { - ret = ENOMEM; - hx509_set_error_string(context, 0, ret, "Out of memory"); - goto out; - } - *ext.critical = TRUE; - } - + ext.critical = critical_flag; ret = der_copy_oid(oid, &ext.extnID); if (ret) { hx509_set_error_string(context, 0, ret, "Out of memory"); @@ -1033,7 +1637,6 @@ ca_sign(hx509_context context, const AlgorithmIdentifier *sigalg; time_t notBefore; time_t notAfter; - unsigned key_usage; sigalg = tbs->sigalg; if (sigalg == NULL) @@ -1053,21 +1656,12 @@ ca_sign(hx509_context context, if (notAfter == 0) notAfter = time(NULL) + 3600 * 24 * 365; - key_usage = tbs->key_usage; - if (key_usage == 0) { - KeyUsage ku; - memset(&ku, 0, sizeof(ku)); - ku.digitalSignature = 1; - ku.keyEncipherment = 1; - key_usage = KeyUsage2int(ku); - } - if (tbs->flags.ca) { - KeyUsage ku; - memset(&ku, 0, sizeof(ku)); - ku.keyCertSign = 1; - ku.cRLSign = 1; - key_usage |= KeyUsage2int(ku); + tbs->ku.keyCertSign = 1; + tbs->ku.cRLSign = 1; + } else if (KeyUsage2int(tbs->ku) == 0) { + tbs->ku.digitalSignature = 1; + tbs->ku.keyEncipherment = 1; } /* @@ -1076,6 +1670,12 @@ ca_sign(hx509_context context, tbsc = &c.tbsCertificate; + /* Default subject Name to empty */ + if (tbs->subject == NULL && + (ret = hx509_empty_name(context, &tbs->subject))) + return ret; + + /* Sanity checks */ if (tbs->flags.key == 0) { ret = EINVAL; hx509_set_error_string(context, 0, ret, "No public key set"); @@ -1086,13 +1686,9 @@ ca_sign(hx509_context context, * will be generated below. */ if (!tbs->flags.proxy) { - if (tbs->subject == NULL) { - hx509_set_error_string(context, 0, EINVAL, "No subject name set"); - return EINVAL; - } if (hx509_name_is_null_p(tbs->subject) && tbs->san.len == 0) { hx509_set_error_string(context, 0, EINVAL, - "NULL subject and no SubjectAltNames"); + "Empty subject and no SubjectAltNames"); return EINVAL; } } @@ -1146,7 +1742,7 @@ ca_sign(hx509_context context, /* signature AlgorithmIdentifier, */ ret = copy_AlgorithmIdentifier(sigalg, &tbsc->signature); if (ret) { - hx509_set_error_string(context, 0, ret, "Failed to copy sigature alg"); + hx509_set_error_string(context, 0, ret, "Failed to copy signature alg"); goto out; } /* issuer Name, */ @@ -1159,10 +1755,32 @@ ca_sign(hx509_context context, goto out; } /* validity Validity, */ - tbsc->validity.notBefore.element = choice_Time_generalTime; - tbsc->validity.notBefore.u.generalTime = notBefore; - tbsc->validity.notAfter.element = choice_Time_generalTime; - tbsc->validity.notAfter.u.generalTime = notAfter; + { + /* + * From RFC 5280, section 4.1.2.5: + * + * CAs conforming to this profile MUST always encode certificate + * validity dates through the year 2049 as UTCTime; certificate validity + * dates in 2050 or later MUST be encoded as GeneralizedTime. + * Conforming applications MUST be able to process validity dates that + * are encoded in either UTCTime or GeneralizedTime. + * + * 2524608000 is seconds since the epoch for 2050-01-01T00:00:00Z. + * + * Both, ...u.generalTime and ...u..utcTime are time_t. + */ + if (notBefore < 1 || (int64_t)notBefore < 2524608000) + tbsc->validity.notBefore.element = choice_Time_utcTime; + else + tbsc->validity.notBefore.element = choice_Time_generalTime; + tbsc->validity.notBefore.u.generalTime = notBefore; + + if (notAfter < 1 || (int64_t)notAfter < 2524608000) + tbsc->validity.notAfter.element = choice_Time_utcTime; + else + tbsc->validity.notAfter.element = choice_Time_generalTime; + tbsc->validity.notAfter.u.generalTime = notAfter; + } /* subject Name, */ if (tbs->flags.proxy) { ret = build_proxy_prefix(context, &tbsc->issuer, &tbsc->subject); @@ -1236,12 +1854,10 @@ ca_sign(hx509_context context, goto out; } - /* add KeyUsage */ - { - KeyUsage ku; - - ku = int2KeyUsage(key_usage); - ASN1_MALLOC_ENCODE(KeyUsage, data.data, data.length, &ku, &size, ret); + /* Add KeyUsage */ + if (KeyUsage2int(tbs->ku) > 0) { + ASN1_MALLOC_ENCODE(KeyUsage, data.data, data.length, + &tbs->ku, &size, ret); if (ret) { hx509_set_error_string(context, 0, ret, "Out of memory"); goto out; @@ -1255,7 +1871,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); @@ -1265,14 +1881,14 @@ ca_sign(hx509_context context, } if (size != data.length) _hx509_abort("internal ASN.1 encoder error"); - ret = add_extension(context, tbsc, 0, + ret = add_extension(context, tbsc, 1, &asn1_oid_id_x509_ce_extKeyUsage, &data); free(data.data); if (ret) 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); @@ -1282,9 +1898,10 @@ ca_sign(hx509_context context, } if (size != data.length) _hx509_abort("internal ASN.1 encoder error"); - ret = add_extension(context, tbsc, 0, - &asn1_oid_id_x509_ce_subjectAltName, - &data); + + /* The SAN extension is critical if the subject Name is empty */ + ret = add_extension(context, tbsc, hx509_name_is_null_p(tbs->subject), + &asn1_oid_id_x509_ce_subjectAltName, &data); free(data.data); if (ret) goto out; @@ -1346,13 +1963,12 @@ ca_sign(hx509_context context, /* Add BasicConstraints */ { BasicConstraints bc; - int aCA = 1; unsigned int path; memset(&bc, 0, sizeof(bc)); if (tbs->flags.ca) { - bc.cA = &aCA; + bc.cA = 1; if (tbs->pathLenConstraint >= 0) { path = tbs->pathLenConstraint; bc.pathLenConstraint = &path; @@ -1376,7 +1992,7 @@ ca_sign(hx509_context context, goto out; } - /* add Proxy */ + /* Add Proxy */ if (tbs->flags.proxy) { ProxyCertInfo info; @@ -1418,8 +2034,8 @@ ca_sign(hx509_context context, goto out; } + /* Add CRL distribution point */ if (tbs->crldp.len) { - ASN1_MALLOC_ENCODE(CRLDistributionPoints, data.data, data.length, &tbs->crldp, &size, ret); if (ret) { @@ -1436,6 +2052,57 @@ ca_sign(hx509_context context, goto out; } + /* Add CertificatePolicies */ + if (tbs->cps.len) { + ASN1_MALLOC_ENCODE(CertificatePolicies, data.data, data.length, + &tbs->cps, &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_x509_ce_certificatePolicies, &data); + free(data.data); + if (ret) + goto out; + } + + /* Add PolicyMappings */ + if (tbs->cps.len) { + ASN1_MALLOC_ENCODE(PolicyMappings, data.data, data.length, + &tbs->pms, &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_x509_ce_policyMappings, &data); + free(data.data); + if (ret) + 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"); @@ -1531,8 +2198,7 @@ get_AuthorityKeyIdentifier(hx509_context context, memset(&gn, 0, sizeof(gn)); gn.element = choice_GeneralName_directoryName; - gn.u.directoryName.element = - choice_GeneralName_directoryName_rdnSequence; + gn.u.directoryName.element = choice_Name_rdnSequence; gn.u.directoryName.u.rdnSequence = name.u.rdnSequence; ret = add_GeneralNames(&gns, &gn); @@ -1583,7 +2249,7 @@ out: * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_sign(hx509_context context, hx509_ca_tbs tbs, hx509_cert signer, @@ -1627,7 +2293,7 @@ out: * @ingroup hx509_ca */ -int +HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_ca_sign_self(hx509_context context, hx509_ca_tbs tbs, hx509_private_key signer, @@ -1640,3 +2306,790 @@ hx509_ca_sign_self(hx509_context context, NULL, certificate); } + +/* + * The following used to be `kdc_issue_certificate()', which was added for + * kx509 support in the kdc, then adapted for bx509d. It now has no + * kdc-specific code and very little krb5-specific code, and is named + * `hx509_ca_issue_certificate()'. + */ + +/* From lib/krb5/principal.c */ +#define princ_num_comp(P) ((P)->principalName.name_string.len) +#define princ_type(P) ((P)->principalName.name_type) +#define princ_comp(P) ((P)->principalName.name_string.val) +#define princ_ncomp(P, N) ((P)->principalName.name_string.val[(N)]) +#define princ_realm(P) ((P)->realm) + +static const char * +princ_get_comp_string(KRB5PrincipalName *principal, unsigned int component) +{ + if (component >= princ_num_comp(principal)) + return NULL; + return princ_ncomp(principal, component); +} +/* XXX Add unparse_name() */ + +typedef enum { + CERT_NOTSUP = 0, + CERT_CLIENT = 1, + CERT_SERVER = 2, + CERT_MIXED = 3 +} cert_type; + +static void +frees(char **s) +{ + free(*s); + *s = NULL; +} + +static heim_error_code +count_sans(hx509_request req, size_t *n) +{ + size_t i; + char *s = NULL; + int ret = 0; + + *n = 0; + for (i = 0; ret == 0; i++) { + hx509_san_type san_type; + + ret = hx509_request_get_san(req, i, &san_type, &s); + if (ret) + break; + switch (san_type) { + case HX509_SAN_TYPE_DNSNAME: + case HX509_SAN_TYPE_EMAIL: + case HX509_SAN_TYPE_XMPP: + case HX509_SAN_TYPE_PKINIT: + case HX509_SAN_TYPE_MS_UPN: + (*n)++; + break; + default: + ret = ENOTSUP; + } + frees(&s); + } + free(s); + return ret == HX509_NO_ITEM ? 0 : ret; +} + +static int +has_sans(hx509_request req) +{ + hx509_san_type san_type; + char *s = NULL; + int ret = hx509_request_get_san(req, 0, &san_type, &s); + + frees(&s); + return ret == HX509_NO_ITEM ? 0 : 1; +} + +static cert_type +characterize_cprinc(hx509_context context, + KRB5PrincipalName *cprinc) +{ + unsigned int ncomp = princ_num_comp(cprinc); + const char *comp1 = princ_get_comp_string(cprinc, 1); + + switch (ncomp) { + case 1: + return CERT_CLIENT; + case 2: + if (strchr(comp1, '.') == NULL) + return CERT_CLIENT; + return CERT_SERVER; + case 3: + if (strchr(comp1, '.')) + return CERT_SERVER; + return CERT_NOTSUP; + default: + return CERT_NOTSUP; + } +} + +/* Characterize request as client or server cert req */ +static cert_type +characterize(hx509_context context, + KRB5PrincipalName *cprinc, + hx509_request req) +{ + heim_error_code ret = 0; + cert_type res = CERT_NOTSUP; + size_t i; + char *s = NULL; + int want_ekus = 0; + + if (!has_sans(req)) + return characterize_cprinc(context, cprinc); + + for (i = 0; ret == 0; i++) { + heim_oid oid; + + frees(&s); + ret = hx509_request_get_eku(req, i, &s); + if (ret) + break; + + want_ekus = 1; + ret = der_parse_heim_oid(s, ".", &oid); + if (ret) + break; + /* + * If the client wants only a server certificate, then we'll be + * willing to issue one that may be longer-lived than the client's + * ticket/token. + * + * There may be other server EKUs, but these are the ones we know + * of. + */ + if (der_heim_oid_cmp(&asn1_oid_id_pkix_kp_serverAuth, &oid) && + der_heim_oid_cmp(&asn1_oid_id_pkix_kp_OCSPSigning, &oid) && + der_heim_oid_cmp(&asn1_oid_id_pkix_kp_secureShellServer, &oid)) + res |= CERT_CLIENT; + else + res |= CERT_SERVER; + der_free_oid(&oid); + } + frees(&s); + if (ret == HX509_NO_ITEM) + ret = 0; + + for (i = 0; ret == 0; i++) { + hx509_san_type san_type; + + frees(&s); + ret = hx509_request_get_san(req, i, &san_type, &s); + if (ret) + break; + switch (san_type) { + case HX509_SAN_TYPE_DNSNAME: + if (!want_ekus) + res |= CERT_SERVER; + break; + case HX509_SAN_TYPE_EMAIL: + case HX509_SAN_TYPE_XMPP: + case HX509_SAN_TYPE_PKINIT: + case HX509_SAN_TYPE_MS_UPN: + if (!want_ekus) + res |= CERT_CLIENT; + break; + default: + ret = ENOTSUP; + } + if (ret) + break; + } + frees(&s); + if (ret == HX509_NO_ITEM) + ret = 0; + return ret ? CERT_NOTSUP : res; +} + +/* + * Get a configuration sub-tree for kx509 based on what's being requested and + * by whom. + * + * We have a number of cases: + * + * - default certificate (no CSR used, or no certificate extensions requested) + * - for client principals + * - for service principals + * - client certificate requested (CSR used and client-y SANs/EKUs requested) + * - server certificate requested (CSR used and server-y SANs/EKUs requested) + * - mixed client/server certificate requested (...) + */ +static heim_error_code +get_cf(hx509_context context, + const heim_config_binding *cf, + heim_log_facility *logf, + hx509_request req, + KRB5PrincipalName *cprinc, + const heim_config_binding **out) +{ + heim_error_code ret; + unsigned int ncomp = princ_num_comp(cprinc); + const char *realm = princ_realm(cprinc); + const char *comp0 = princ_get_comp_string(cprinc, 0); + const char *comp1 = princ_get_comp_string(cprinc, 1); + const char *label = NULL; + const char *svc = NULL; + const char *def = NULL; + cert_type certtype = CERT_NOTSUP; + size_t nsans = 0; + + *out = NULL; + if (ncomp == 0) { + heim_log_msg(context->hcontext, logf, 5, NULL, + "Client principal has no components!"); + hx509_set_error_string(context, 0, ret = ENOTSUP, + "Client principal has no components!"); + return ret; + } + + if ((ret = count_sans(req, &nsans)) || + (certtype = characterize(context, cprinc, req)) == CERT_NOTSUP) { + heim_log_msg(context->hcontext, logf, 5, NULL, + "Could not characterize CSR"); + hx509_set_error_string(context, 0, ret, "Could not characterize CSR"); + return ret; + } + + if (nsans) { + def = "custom"; + /* Client requested some certificate extension, a SAN or EKU */ + switch (certtype) { + case CERT_MIXED: label = "mixed"; break; + case CERT_CLIENT: label = "client"; break; + case CERT_SERVER: label = "server"; break; + default: + hx509_set_error_string(context, 0, ret = ENOTSUP, + "Requested SAN/EKU combination not " + "supported"); + return ret; + } + } else { + def = "default"; + /* Default certificate desired */ + if (ncomp == 1) { + label = "user"; + } else if (ncomp == 2 && strcmp(comp1, "root") == 0) { + label = "root_user"; + } else if (ncomp == 2 && strcmp(comp1, "admin") == 0) { + label = "admin_user"; + } else if (strchr(comp1, '.')) { + label = "hostbased_service"; + svc = comp0; + } else { + label = "other"; + } + } + + *out = heim_config_get_list(context->hcontext, cf, label, svc, NULL); + if (*out) { + ret = 0; + } else { + heim_log_msg(context->hcontext, logf, 3, NULL, + "No configuration for %s %s certificate's realm " + "-> %s -> kx509 -> %s%s%s", def, label, realm, label, + svc ? " -> " : "", svc ? svc : ""); + hx509_set_error_string(context, 0, EACCES, + "No configuration for %s %s certificate's realm " + "-> %s -> kx509 -> %s%s%s", def, label, realm, label, + svc ? " -> " : "", svc ? svc : ""); + } + return ret; +} + + +/* + * Find and set a certificate template using a configuration sub-tree + * appropriate to the requesting principal. + * + * This allows for the specification of the following in configuration: + * + * - certificates as templates, with ${var} tokens in subjectName attribute + * values that will be expanded later + * - a plain string with ${var} tokens to use as the subjectName + * - EKUs + * - whether to include a PKINIT SAN + */ +static heim_error_code +set_template(hx509_context context, + heim_log_facility *logf, + const heim_config_binding *cf, + hx509_ca_tbs tbs) +{ + heim_error_code ret = 0; + const char *cert_template = NULL; + const char *subj_name = NULL; + char **ekus = NULL; + + if (cf == NULL) + return EACCES; /* Can't happen */ + + cert_template = heim_config_get_string(context->hcontext, cf, + "template_cert", NULL); + subj_name = heim_config_get_string(context->hcontext, cf, "subject_name", + NULL); + + if (cert_template) { + hx509_certs certs; + hx509_cert template; + + ret = hx509_certs_init(context, cert_template, 0, NULL, &certs); + if (ret == 0) + ret = hx509_get_one_cert(context, certs, &template); + hx509_certs_free(&certs); + if (ret) { + heim_log_msg(context->hcontext, logf, 1, NULL, + "Failed to load certificate template from %s", + cert_template); + hx509_set_error_string(context, 0, EACCES, + "Failed to load certificate template from " + "%s", cert_template); + return ret; + } + + /* + * Only take the subjectName, the keyUsage, and EKUs from the template + * certificate. + */ + ret = hx509_ca_tbs_set_template(context, tbs, + HX509_CA_TEMPLATE_SUBJECT | + HX509_CA_TEMPLATE_KU | + HX509_CA_TEMPLATE_EKU, + template); + hx509_cert_free(template); + if (ret) + return ret; + } + + if (subj_name) { + hx509_name dn = NULL; + + ret = hx509_parse_name(context, subj_name, &dn); + if (ret == 0) + ret = hx509_ca_tbs_set_subject(context, tbs, dn); + hx509_name_free(&dn); + if (ret) + return ret; + } + + if (cert_template == NULL && subj_name == NULL) { + hx509_name dn = NULL; + + ret = hx509_empty_name(context, &dn); + if (ret == 0) + ret = hx509_ca_tbs_set_subject(context, tbs, dn); + hx509_name_free(&dn); + if (ret) + return ret; + } + + ekus = heim_config_get_strings(context->hcontext, cf, "ekus", NULL); + if (ekus) { + size_t i; + + for (i = 0; ret == 0 && ekus[i]; i++) { + heim_oid oid = { 0, 0 }; + + if ((ret = der_find_or_parse_heim_oid(ekus[i], ".", &oid)) == 0) + ret = hx509_ca_tbs_add_eku(context, tbs, &oid); + der_free_oid(&oid); + } + heim_config_free_strings(ekus); + } + + /* + * XXX A KeyUsage template would be nice, but it needs some smarts to + * remove, e.g., encipherOnly, decipherOnly, keyEncipherment, if the SPKI + * algorithm does not support encryption. The same logic should be added + * to hx509_ca_tbs_set_template()'s HX509_CA_TEMPLATE_KU functionality. + */ + return ret; +} + +/* + * Find and set a certificate template, set "variables" in `env', and add add + * default SANs/EKUs as appropriate. + * + * TODO: + * - lookup a template for the client principal in its HDB entry + * - lookup subjectName, SANs for a principal in its HDB entry + * - lookup a host-based client principal's HDB entry and add its canonical + * name / aliases as dNSName SANs + * (this would have to be if requested by the client, perhaps) + */ +static heim_error_code +set_tbs(hx509_context context, + heim_log_facility *logf, + const heim_config_binding *cf, + hx509_request req, + KRB5PrincipalName *cprinc, + hx509_env *env, + hx509_ca_tbs tbs) +{ + KRB5PrincipalName cprinc_no_realm = *cprinc; + heim_error_code ret; + unsigned int ncomp = princ_num_comp(cprinc); + const char *realm = princ_realm(cprinc); + const char *comp0 = princ_get_comp_string(cprinc, 0); + const char *comp1 = princ_get_comp_string(cprinc, 1); + const char *comp2 = princ_get_comp_string(cprinc, 2); + struct rk_strpool *strpool; + char *princ_no_realm = NULL; + char *princ = NULL; + + strpool = _hx509_unparse_kerberos_name(NULL, cprinc); + if (strpool) + princ = rk_strpoolcollect(strpool); + cprinc_no_realm.realm = NULL; + strpool = _hx509_unparse_kerberos_name(NULL, &cprinc_no_realm); + if (strpool) + princ_no_realm = rk_strpoolcollect(strpool); + if (princ == NULL || princ_no_realm == NULL) { + free(princ); + return hx509_enomem(context); + } + strpool = NULL; + ret = hx509_env_add(context, env, "principal-name-without-realm", + princ_no_realm); + if (ret == 0) + ret = hx509_env_add(context, env, "principal-name", princ); + if (ret == 0) + ret = hx509_env_add(context, env, "principal-name-realm", + realm); + + /* Populate requested certificate extensions from CSR/CSRPlus if allowed */ + if (ret == 0) + ret = hx509_ca_tbs_set_from_csr(context, tbs, req); + if (ret == 0) + ret = set_template(context, logf, cf, tbs); + + /* + * Optionally add PKINIT SAN. + * + * Adding an id-pkinit-san means the client can use the certificate to + * initiate PKINIT. That might seem odd, but it enables a sort of PKIX + * credential delegation by allowing forwarded Kerberos tickets to be + * used to acquire PKIX credentials. Thus this can work: + * + * PKIX (w/ HW token) -> Kerberos -> + * PKIX (w/ softtoken) -> Kerberos -> + * PKIX (w/ softtoken) -> Kerberos -> + * ... + * + * Note that we may not have added the PKINIT EKU -- that depends on the + * template, and host-based service templates might well not include it. + */ + if (ret == 0 && !has_sans(req) && + heim_config_get_bool_default(context->hcontext, cf, FALSE, + "include_pkinit_san", NULL)) { + ret = hx509_ca_tbs_add_san_pkinit(context, tbs, princ); + } + + if (ret) + goto out; + + if (ncomp == 1) { + const char *email_domain; + + ret = hx509_env_add(context, env, "principal-component0", + princ_no_realm); + + /* + * If configured, include an rfc822Name that's just the client's + * principal name sans realm @ configured email domain. + */ + if (ret == 0 && !has_sans(req) && + (email_domain = heim_config_get_string(context->hcontext, cf, + "email_domain", NULL))) { + char *email; + + if (asprintf(&email, "%s@%s", princ_no_realm, email_domain) == -1 || + email == NULL) + goto enomem; + ret = hx509_ca_tbs_add_san_rfc822name(context, tbs, email); + free(email); + } + } else if (ncomp == 2 || ncomp == 3) { + /* + * 2- and 3-component principal name. + * + * We do not have a reliable name-type indicator. If the second + * component has a '.' in it then we'll assume that the name is a + * host-based (2-component) or domain-based (3-component) service + * principal name. Else we'll assume it's a two-component admin-style + * username. + */ + + ret = hx509_env_add(context, env, "principal-component0", comp0); + if (ret == 0) + ret = hx509_env_add(context, env, "principal-component1", comp1); + if (ret == 0 && ncomp == 3) + ret = hx509_env_add(context, env, "principal-component2", comp2); + if (ret == 0 && strchr(comp1, '.')) { + /* Looks like host-based or domain-based service */ + ret = hx509_env_add(context, env, "principal-service-name", comp0); + if (ret == 0) + ret = hx509_env_add(context, env, "principal-host-name", + comp1); + if (ret == 0 && ncomp == 3) + ret = hx509_env_add(context, env, "principal-domain-name", + comp2); + if (ret == 0 && !has_sans(req) && + heim_config_get_bool_default(context->hcontext, cf, FALSE, + "include_dnsname_san", NULL)) { + ret = hx509_ca_tbs_add_san_hostname(context, tbs, comp1); + } + } + } else { + heim_log_msg(context->hcontext, logf, 5, NULL, + "kx509/bx509 client %s has too many components!", princ); + hx509_set_error_string(context, 0, ret = EACCES, + "kx509/bx509 client %s has too many " + "components!", princ); + } + +out: + if (ret == ENOMEM) + goto enomem; + free(princ_no_realm); + free(princ); + return ret; + +enomem: + heim_log_msg(context->hcontext, logf, 0, NULL, + "Could not set up TBSCertificate: Out of memory"); + ret = hx509_enomem(context); + goto out; +} + +/* + * Set the notBefore/notAfter for the certificate to be issued. + * + * Here `starttime' is the supplicant's credentials' notBefore equivalent, + * while `endtime' is the supplicant's credentials' notAfter equivalent. + * + * `req_life' is the lifetime requested by the supplicant. + * + * `endtime' must be larger than the current time. + * + * `starttime' can be zero or negative, in which case the notBefore will be the + * current time minus five minutes. + * + * `endtime', `req_life' and configuration parameters will be used to compute + * the actual notAfter. + */ +static heim_error_code +tbs_set_times(hx509_context context, + const heim_config_binding *cf, + heim_log_facility *logf, + time_t starttime, + time_t endtime, + time_t req_life, + hx509_ca_tbs tbs) +{ + time_t now = time(NULL); + time_t force = heim_config_get_time_default(context->hcontext, + cf, 5 * 24 * 3600, + "force_cert_lifetime", NULL); + time_t clamp = heim_config_get_time_default(context->hcontext, cf, 0, + "max_cert_lifetime", NULL); + int allow_more = heim_config_get_bool_default(context->hcontext, cf, FALSE, + "allow_extra_lifetime", + NULL); + starttime = starttime > 0 ? starttime : now - 5 * 60; + + if (endtime < now) { + heim_log_msg(context->hcontext, logf, 3, NULL, + "Endtime is in the past"); + hx509_set_error_string(context, 0, ERANGE, "Endtime is in the past"); + return ERANGE; + } + + /* Apply requested lifetime if shorter or if allowed more */ + if (req_life > 0 && req_life <= endtime - now) + endtime = now + req_life; + else if (req_life > 0 && allow_more) + endtime = now + req_life; + + /* Apply floor */ + if (force > 0 && force > endtime - now) + endtime = now + force; + + /* Apply ceiling */ + if (clamp > 0 && clamp < endtime - now) + endtime = now + clamp; + + hx509_ca_tbs_set_notAfter(context, tbs, endtime); + hx509_ca_tbs_set_notBefore(context, tbs, starttime); + return 0; +} + +/* + * Build a certifate for `principal' and its CSR. + * + * XXX Make `cprinc' a GeneralName! That's why this is private for now. + */ +heim_error_code +_hx509_ca_issue_certificate(hx509_context context, + const heim_config_binding *cf, + heim_log_facility *logf, + hx509_request req, + KRB5PrincipalName *cprinc, + time_t starttime, + time_t endtime, + time_t req_life, + int send_chain, + hx509_certs *out) +{ + heim_error_code ret; + const char *ca; + hx509_ca_tbs tbs = NULL; + hx509_certs chain = NULL; + hx509_cert signer = NULL; + hx509_cert cert = NULL; + hx509_env env = NULL; + KeyUsage ku; + + *out = NULL; + /* Force KU */ + ku = int2KeyUsage(0); + ku.digitalSignature = 1; + hx509_request_authorize_ku(req, ku); + + ret = get_cf(context, cf, logf, req, cprinc, &cf); + if (ret) + return ret; + + if ((ca = heim_config_get_string(context->hcontext, cf, + "ca", NULL)) == NULL) { + heim_log_msg(context->hcontext, logf, 3, NULL, + "No kx509 CA issuer credential specified"); + hx509_set_error_string(context, 0, ret = EACCES, + "No kx509 CA issuer credential specified"); + return ret; + } + + ret = hx509_ca_tbs_init(context, &tbs); + if (ret) { + heim_log_msg(context->hcontext, logf, 0, NULL, + "Failed to create certificate: Out of memory"); + return ret; + } + + /* Lookup a template and set things in `env' and `tbs' as appropriate */ + if (ret == 0) + ret = set_tbs(context, logf, cf, req, cprinc, &env, tbs); + + /* Populate generic template "env" variables */ + + /* + * The `tbs' and `env' are now complete as to naming and EKUs. + * + * We check that the `tbs' is not name-less, after which all remaining + * failures here will not be policy failures. So we also log the intent to + * issue a certificate now. + */ + if (ret == 0 && hx509_name_is_null_p(hx509_ca_tbs_get_name(tbs)) && + !has_sans(req)) { + heim_log_msg(context->hcontext, logf, 3, NULL, + "Not issuing certificate because it would have no names"); + hx509_set_error_string(context, 0, ret = EACCES, + "Not issuing certificate because it " + "would have no names"); + } + if (ret) + goto out; + + /* + * Still to be done below: + * + * - set certificate spki + * - set certificate validity + * - expand variables in certificate subject name template + * - sign certificate + * - encode certificate and chain + */ + + /* Load the issuer certificate and private key */ + { + hx509_certs certs; + hx509_query *q; + + ret = hx509_certs_init(context, ca, 0, NULL, &certs); + if (ret) { + heim_log_msg(context->hcontext, logf, 1, NULL, + "Failed to load CA certificate and private key %s", + ca); + hx509_set_error_string(context, 0, ret, "Failed to load " + "CA certificate and private key %s", ca); + goto out; + } + ret = hx509_query_alloc(context, &q); + if (ret) { + hx509_certs_free(&certs); + goto out; + } + + hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY); + hx509_query_match_option(q, HX509_QUERY_OPTION_KU_KEYCERTSIGN); + + ret = hx509_certs_find(context, certs, q, &signer); + hx509_query_free(context, q); + hx509_certs_free(&certs); + if (ret) { + heim_log_msg(context->hcontext, logf, 1, NULL, + "Failed to find a CA certificate in %s", ca); + hx509_set_error_string(context, 0, ret, + "Failed to find a CA certificate in %s", + ca); + goto out; + } + } + + /* Populate the subject public key in the TBS context */ + { + SubjectPublicKeyInfo spki; + + ret = hx509_request_get_SubjectPublicKeyInfo(context, + req, &spki); + if (ret == 0) + ret = hx509_ca_tbs_set_spki(context, tbs, &spki); + free_SubjectPublicKeyInfo(&spki); + if (ret) + goto out; + } + + /* Work out cert expiration */ + if (ret == 0) + ret = tbs_set_times(context, cf, logf, starttime, endtime, req_life, + tbs); + + /* Expand the subjectName template in the TBS using the env */ + if (ret == 0) + ret = hx509_ca_tbs_subject_expand(context, tbs, env); + hx509_env_free(&env); + + /* All done with the TBS, sign/issue the certificate */ + if (ret == 0) + ret = hx509_ca_sign(context, tbs, signer, &cert); + + /* + * Gather the certificate and chain into a MEMORY store, being careful not + * to include private keys in the chain. + * + * We could have specified a separate configuration parameter for an hx509 + * store meant to have only the chain and no private keys, but expecting + * the full chain in the issuer credential store and copying only the certs + * (but not the private keys) is safer and easier to configure. + */ + if (ret == 0) + ret = hx509_certs_init(context, "MEMORY:certs", + HX509_CERTS_NO_PRIVATE_KEYS, NULL, out); + if (ret == 0) + ret = hx509_certs_add(context, *out, cert); + if (ret == 0 && send_chain) { + ret = hx509_certs_init(context, ca, + HX509_CERTS_NO_PRIVATE_KEYS, NULL, &chain); + if (ret == 0) + ret = hx509_certs_merge(context, *out, chain); + } + +out: + hx509_certs_free(&chain); + if (env) + hx509_env_free(&env); + if (tbs) + hx509_ca_tbs_free(&tbs); + if (cert) + hx509_cert_free(cert); + if (signer) + hx509_cert_free(signer); + if (ret) + hx509_certs_free(out); + return ret; +} |
