diff --git a/lib/hx509/ca.c b/lib/hx509/ca.c index 6ecd3a84b..8cb4ccc0c 100644 --- a/lib/hx509/ca.c +++ b/lib/hx509/ca.c @@ -32,7 +32,6 @@ */ #include "hx_locl.h" -#include /** * @page page_ca Hx509 CA functions @@ -46,6 +45,8 @@ struct hx509_ca_tbs { KeyUsage ku; ExtKeyUsage eku; GeneralNames san; + CertificatePolicies cps; + PolicyMappings pms; heim_integer serial; struct { unsigned int proxy:1; @@ -102,6 +103,8 @@ hx509_ca_tbs_free(hx509_ca_tbs *tbs) 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); @@ -528,6 +531,127 @@ hx509_ca_tbs_add_eku(hx509_context context, return 0; } +/** + * 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. @@ -1613,8 +1737,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) { @@ -1631,6 +1755,40 @@ 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; + } + 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/hxtool-commands.in b/lib/hx509/hxtool-commands.in index 556b1888e..9ea633dc8 100644 --- a/lib/hx509/hxtool-commands.in +++ b/lib/hx509/hxtool-commands.in @@ -761,6 +761,16 @@ command = { type = "string" help = "XMPP jabber id (for SAN)" } + option = { + long = "policy" + type = "strings" + help = "Certificate Policy OID and optional URI and/or notice (OID:URInotice_text)" + } + option = { + long = "policy-mapping" + type = "strings" + help = "Certificate Policy mapping (OID:OID)" + } option = { long = "req" type = "string" diff --git a/lib/hx509/hxtool.c b/lib/hx509/hxtool.c index ac7ba0960..9e6ed8466 100644 --- a/lib/hx509/hxtool.c +++ b/lib/hx509/hxtool.c @@ -109,7 +109,7 @@ parse_oid(const char *str, const heim_oid *def, heim_oid *oid) if (ret == 0) ret = der_copy_oid(found, oid); else - ret = der_parse_heim_oid (str, " .", oid); + ret = der_parse_heim_oid(str, " .", oid); } else { ret = der_copy_oid(def, oid); } @@ -1411,10 +1411,7 @@ request_create(struct request_create_options *opt, int argc, char **argv) for (i = 0; i < opt->registered_strings.num_strings; i++) { heim_oid oid; - ret = der_parse_heim_oid(opt->registered_strings.strings[i], NULL, - &oid); - if (ret) - hx509_err(context, 1, ret, "OID parse error"); + parse_oid(opt->registered_strings.strings[i], NULL, &oid); ret = hx509_request_add_registered(context, req, &oid); der_free_oid(&oid); if (ret) @@ -1879,9 +1876,11 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv) hx509_private_key cert_key = NULL; hx509_name subject = NULL; SubjectPublicKeyInfo spki; + heim_oid oid; size_t i; int delta = 0; + memset(&oid, 0, sizeof(oid)); memset(&spki, 0, sizeof(spki)); if (opt->ca_certificate_string == NULL && !opt->self_signed_flag) @@ -2021,12 +2020,11 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv) hx509_err(context, 1, ret, "hx509_ca_tbs_init"); for (i = 0; i < opt->eku_strings.num_strings; i++) { - heim_oid oid; - parse_oid(opt->eku_strings.strings[i], NULL, &oid); ret = hx509_ca_tbs_add_eku(context, tbs, &oid); if (ret) hx509_err(context, 1, ret, "hx509_request_add_eku"); + der_free_oid(&oid); } if (opt->ku_strings.num_strings) { const struct units *kus = asn1_KeyUsage_units(); @@ -2117,6 +2115,48 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv) eval_types(context, tbs, opt); + for (i = 0; ret == 0 && i < opt->policy_strings.num_strings; i++) { + char *oidstr, *uri, *dt; + + if ((oidstr = strdup(opt->policy_strings.strings[i])) == NULL) + hx509_err(context, 1, ENOMEM, "out of memory"); + uri = strchr(oidstr, ':'); + if (uri) + *(uri++) = '\0'; + dt = strchr(uri ? uri : "", ' '); + if (dt) + *(dt++) = '\0'; + + parse_oid(oidstr, NULL, &oid); + ret = hx509_ca_tbs_add_pol(context, tbs, &oid, uri, dt); + der_free_oid(&oid); + free(oidstr); + } + + for (i = 0; ret == 0 && i < opt->policy_mapping_strings.num_strings; i++) { + char *issuer_oidstr, *subject_oidstr; + heim_oid issuer_oid, subject_oid; + + if ((issuer_oidstr = + strdup(opt->policy_mapping_strings.strings[i])) == NULL) + hx509_err(context, 1, ENOMEM, "out of memory"); + subject_oidstr = strchr(issuer_oidstr, ':'); + if (subject_oidstr == NULL) + subject_oidstr = issuer_oidstr; + else + *(subject_oidstr++) = '\0'; + + parse_oid(issuer_oidstr, NULL, &issuer_oid); + parse_oid(subject_oidstr, NULL, &subject_oid); + ret = hx509_ca_tbs_add_pol_mapping(context, tbs, &issuer_oid, + &subject_oid); + if (ret) + hx509_err(context, 1, ret, "failed to add policy mapping"); + der_free_oid(&issuer_oid); + der_free_oid(&subject_oid); + free(issuer_oidstr); + } + if (opt->issue_ca_flag) { ret = hx509_ca_tbs_set_ca(context, tbs, opt->path_length_integer); if (ret) @@ -2521,8 +2561,7 @@ acert1_sans(struct acert_options *opt, s = opt->has_registeredID_san_strings.strings[k]; memset(&oid, 0, sizeof(oid)); - if ((ret = der_parse_heim_oid(s, NULL, &oid))) - break; + parse_oid(s, NULL, &oid); if (der_heim_oid_cmp(&gn->u.registeredID, &oid) == 0) { der_free_oid(&oid); if (opt->verbose_flag) @@ -2571,8 +2610,7 @@ acert1_ekus(struct acert_options *opt, heim_oid oid; memset(&oid, 0, sizeof(oid)); - if ((ret = der_parse_heim_oid(s, NULL, &oid))) - break; + parse_oid(s, NULL, &oid); if (der_heim_oid_cmp(&eku.val[i], &oid) == 0) { der_free_oid(&oid); if (opt->verbose_flag) diff --git a/lib/hx509/libhx509-exports.def b/lib/hx509/libhx509-exports.def index abafd1a93..56eaccc48 100644 --- a/lib/hx509/libhx509-exports.def +++ b/lib/hx509/libhx509-exports.def @@ -65,6 +65,8 @@ EXPORTS hx509_ca_tbs_add_crl_dp_uri hx509_ca_tbs_add_eku hx509_ca_tbs_add_ku + hx509_ca_tbs_add_pol + hx509_ca_tbs_add_pol_mapping hx509_ca_tbs_add_san hx509_ca_tbs_add_san_hostname hx509_ca_tbs_add_san_jid diff --git a/lib/hx509/print.c b/lib/hx509/print.c index a5b90d793..c27f8ac98 100644 --- a/lib/hx509/print.c +++ b/lib/hx509/print.c @@ -698,6 +698,169 @@ get_display_text(DisplayText *dt, char **out) return r < 0 ? errno : 0; } +static int +check_certificatePolicies(hx509_validate_ctx ctx, + struct cert_status *status, + enum critical_flag cf, + const Extension *e) +{ + CertificatePolicies cp; + size_t i, size; + int ret = 0; + + check_Null(ctx, status, cf, e); + + if (e->extnValue.length == 0) { + validate_print(ctx, HX509_VALIDATE_F_VALIDATE, + "CertificatePolicies empty, not allowed"); + return 1; + } + ret = decode_CertificatePolicies(e->extnValue.data, e->extnValue.length, + &cp, &size); + if (ret) { + validate_print(ctx, HX509_VALIDATE_F_VALIDATE, + "\tret = %d while decoding CertificatePolicies\n", ret); + return 1; + } + if (cp.len == 0) { + validate_print(ctx, HX509_VALIDATE_F_VALIDATE, + "CertificatePolicies empty, not allowed\n"); + return 1; + } + + for (i = 0; ret == 0 && i < cp.len; i++) { + size_t k; + char *poid = NULL; + char *qoid = NULL; + char *dt = NULL; + + ret = der_print_heim_oid(&cp.val[i].policyIdentifier, '.', &poid); + if (ret == 0) + validate_print(ctx, HX509_VALIDATE_F_VERBOSE, "\tPolicy: %s", poid); + + for (k = 0; + ret == 0 && cp.val[i].policyQualifiers && + k < cp.val[i].policyQualifiers->len; + k++) { + PolicyQualifierInfo *pi = &cp.val[i].policyQualifiers->val[k]; + + if (der_heim_oid_cmp(&pi->policyQualifierId, + &asn1_oid_id_pkix_qt_cps) == 0) { + CPSuri cps; + + ret = decode_CPSuri(pi->qualifier.data, pi->qualifier.length, + &cps, &size); + if (ret == 0) { + if (cps.length > 4096) + cps.length = 4096; + validate_print(ctx, HX509_VALIDATE_F_VERBOSE, + ":CPSuri:%.*s", + (int)cps.length, (char *)cps.data); + free_CPSuri(&cps); + } + } else if (der_heim_oid_cmp(&pi->policyQualifierId, + &asn1_oid_id_pkix_qt_unotice) == 0) { + UserNotice un; + + ret = decode_UserNotice(pi->qualifier.data, + pi->qualifier.length, &un, &size); + if (ret == 0) { + if (un.explicitText) { + /* + * get_display_text() will strvis to make it safer to + * print. + */ + ret = get_display_text(un.explicitText, &dt); + validate_print(ctx, HX509_VALIDATE_F_VERBOSE, + " UserNotice:DistplayText:%s", dt); + } else if (un.noticeRef) { + validate_print(ctx, HX509_VALIDATE_F_VERBOSE, + " UserNotice:NoticeRef:", + qoid); + } else { + ret = der_print_heim_oid(&pi->policyQualifierId, '.', + &qoid); + if (ret) + break; + validate_print(ctx, HX509_VALIDATE_F_VERBOSE, + " Unknown:%s", qoid); + } + } + } else { + validate_print(ctx, HX509_VALIDATE_F_VERBOSE, + ", qualifier %s:", qoid); + } + free(qoid); + free(dt); + qoid = dt = 0; + } + if (ret == 0) { + validate_print(ctx, HX509_VALIDATE_F_VERBOSE, "\n"); + } else { + validate_print(ctx, HX509_VALIDATE_F_VALIDATE, + "\nOut of memory formatting certificate policy"); + ret = ENOMEM; + } + free(poid); + free(qoid); + free(dt); + poid = qoid = dt = 0; + } + + free_CertificatePolicies(&cp); + + return ret ? 1 : 0; +} + +static int +check_policyMappings(hx509_validate_ctx ctx, + struct cert_status *status, + enum critical_flag cf, + const Extension *e) +{ + PolicyMappings pm; + size_t i, size; + int ret = 0; + + check_Null(ctx, status, cf, e); + + if (e->extnValue.length == 0) { + validate_print(ctx, HX509_VALIDATE_F_VALIDATE, + "PolicyMappings empty, not allowed"); + return 1; + } + ret = decode_PolicyMappings(e->extnValue.data, e->extnValue.length, + &pm, &size); + if (ret) { + validate_print(ctx, HX509_VALIDATE_F_VALIDATE, + "\tret = %d while decoding PolicyMappings\n", ret); + return 1; + } + if (pm.len == 0) { + validate_print(ctx, HX509_VALIDATE_F_VALIDATE, + "PolicyMappings empty, not allowed\n"); + return 1; + } + + for (i = 0; ret == 0 && i < pm.len; i++) { + char *idpoid = NULL; + char *sdpoid = NULL; + + ret = der_print_heim_oid(&pm.val[i].issuerDomainPolicy, '.', &idpoid); + if (ret == 0) + ret = der_print_heim_oid(&pm.val[i].subjectDomainPolicy, '.', + &sdpoid); + if (ret == 0) + validate_print(ctx, HX509_VALIDATE_F_VERBOSE, + "\tPolicy mapping %s -> %s\n", idpoid, sdpoid); + else + validate_print(ctx, HX509_VALIDATE_F_VALIDATE, + "ret=%d while decoding PolicyMappings\n", ret); + } + + return 0; +} + /* * */ @@ -727,8 +890,8 @@ struct { { ext(certificateIssuer, Null), M_C }, { ext(nameConstraints, Null), M_C }, { ext(cRLDistributionPoints, CRLDistributionPoints), S_N_C }, - { ext(certificatePolicies, Null), 0 }, - { ext(policyMappings, Null), M_N_C }, + { ext(certificatePolicies, certificatePolicies), 0 }, + { ext(policyMappings, policyMappings), M_N_C }, { ext(authorityKeyIdentifier, authorityKeyIdentifier), M_N_C }, { ext(policyConstraints, Null), D_C }, { ext(extKeyUsage, extKeyUsage), D_C }, diff --git a/lib/hx509/version-script.map b/lib/hx509/version-script.map index fcff8a328..5b8903b39 100644 --- a/lib/hx509/version-script.map +++ b/lib/hx509/version-script.map @@ -49,6 +49,8 @@ HEIMDAL_X509_1.2 { hx509_ca_tbs_add_crl_dp_uri; hx509_ca_tbs_add_eku; hx509_ca_tbs_add_ku; + hx509_ca_tbs_add_pol; + hx509_ca_tbs_add_pol_mapping; hx509_ca_tbs_add_san; hx509_ca_tbs_add_san_hostname; hx509_ca_tbs_add_san_jid;