diff --git a/lib/hx509/ca.c b/lib/hx509/ca.c index 0759d441b..4e8063216 100644 --- a/lib/hx509/ca.c +++ b/lib/hx509/ca.c @@ -404,6 +404,65 @@ hx509_ca_tbs_set_serialnumber(hx509_context context, return ret; } +/** + * 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. @@ -909,6 +968,45 @@ hx509_ca_tbs_add_san_rfc822name(hx509_context context, return add_GeneralNames(&tbs->san, &gn); } +/** + * 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. * diff --git a/lib/hx509/hx509.h b/lib/hx509/hx509.h index 10f3c083a..e8158e4d7 100644 --- a/lib/hx509/hx509.h +++ b/lib/hx509/hx509.h @@ -64,6 +64,26 @@ typedef struct hx509_crl *hx509_crl; typedef void (*hx509_vprint_func)(void *, const char *, va_list); +typedef enum { + HX509_SAN_TYPE_UNSUPPORTED = 0, + /* The following correspond to the enum GeneralName_enum values: */ + HX509_SAN_TYPE_EMAIL = 2, + HX509_SAN_TYPE_DNSNAME = 3, + HX509_SAN_TYPE_DN = 4, + HX509_SAN_TYPE_REGISTERED_ID = 7, + /* + * Missing support for: + * - URI SANs + * - IP address SANs + * - various otherName SANs we know about (e.g., DNSSRV) + * + * The following are otherName SAN types, and assigned manually here: + */ + HX509_SAN_TYPE_XMPP = 32, + HX509_SAN_TYPE_PKINIT = 33, + HX509_SAN_TYPE_MS_UPN = 34, +} hx509_san_type; + enum { HX509_VHN_F_ALLOW_NO_MATCH = 1 }; diff --git a/lib/hx509/hx509_err.et b/lib/hx509/hx509_err.et index f0a27e836..db81f5d29 100644 --- a/lib/hx509/hx509_err.et +++ b/lib/hx509/hx509_err.et @@ -36,6 +36,7 @@ error_code NAME_MALFORMED, "Name is malformed" error_code CERTIFICATE_MALFORMED, "Certificate is malformed" error_code CERTIFICATE_MISSING_EKU, "Certificate is missing a required EKU" error_code PROXY_CERTIFICATE_NOT_CANONICALIZED, "Proxy certificate not canonicalized" +error_code NO_ITEM, "No such item / iteration end" # cms related errors index 32 diff --git a/lib/hx509/libhx509-exports.def b/lib/hx509/libhx509-exports.def index 0adff2b98..39ac4b275 100644 --- a/lib/hx509/libhx509-exports.def +++ b/lib/hx509/libhx509-exports.def @@ -29,22 +29,23 @@ EXPORTS hx509_request_add_pkinit hx509_request_add_registered hx509_request_add_xmpp_name + hx509_request_authorize_ku + hx509_request_authorize_eku + hx509_request_authorize_san + hx509_request_count_unsupported + hx509_request_count_unauthorized _hx509_private_key_export _hx509_private_key_exportable _hx509_private_key_get_internal _hx509_private_key_oid _hx509_private_key_ref hx509_request_free - hx509_request_get_dns_name_san hx509_request_get_eku - hx509_request_get_email_san hx509_request_get_exts hx509_request_get_ku - hx509_request_get_ms_upn_san hx509_request_get_name - hx509_request_get_pkinit_san + hx509_request_get_san hx509_request_get_SubjectPublicKeyInfo - hx509_request_get_xmpp_san hx509_request_get_name hx509_request_init hx509_request_parse @@ -63,6 +64,7 @@ EXPORTS hx509_ca_tbs_add_crl_dp_uri hx509_ca_tbs_add_eku hx509_ca_tbs_add_ku + hx509_ca_tbs_add_san hx509_ca_tbs_add_san_hostname hx509_ca_tbs_add_san_jid hx509_ca_tbs_add_san_ms_upn @@ -74,6 +76,7 @@ EXPORTS hx509_ca_tbs_init hx509_ca_tbs_set_ca hx509_ca_tbs_set_domaincontroller + hx509_ca_tbs_set_from_csr hx509_ca_tbs_set_notAfter hx509_ca_tbs_set_notAfter_lifetime hx509_ca_tbs_set_notBefore diff --git a/lib/hx509/req.c b/lib/hx509/req.c index 3eabff152..b4fc5d71f 100644 --- a/lib/hx509/req.c +++ b/lib/hx509/req.c @@ -34,12 +34,23 @@ #include "hx_locl.h" #include +typedef struct abitstring_s { + unsigned char *feats; + size_t feat_bytes; +} *abitstring; + struct hx509_request_data { hx509_name name; SubjectPublicKeyInfo key; KeyUsage ku; ExtKeyUsage eku; GeneralNames san; + struct abitstring_s authorized_EKUs; + struct abitstring_s authorized_SANs; + uint32_t nunsupported; /* Count of unsupported features requested */ + uint32_t nrequested; /* Count of supported features requested */ + uint32_t nauthorized; /* Count of supported features authorized */ + uint32_t ku_are_authorized:1; }; /** @@ -80,6 +91,8 @@ hx509_request_free(hx509_request *reqp) return; if (req->name) hx509_name_free(&req->name); + free(req->authorized_EKUs.feats); + free(req->authorized_SANs.feats); free_SubjectPublicKeyInfo(&req->key); free_ExtKeyUsage(&req->eku); free_GeneralNames(&req->san); @@ -189,7 +202,18 @@ hx509_request_get_SubjectPublicKeyInfo(hx509_context context, HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_set_ku(hx509_context context, hx509_request req, KeyUsage ku) { + KeyUsage oldku = req->ku; + uint64_t n = KeyUsage2int(ku); + + if ((KeyUsage2int(req->ku) & n) != n) { + req->ku_are_authorized = 0; + } req->ku = ku; + + if (KeyUsage2int(oldku) == 0 && n != 0) + req->nrequested++; + if (KeyUsage2int(oldku) && KeyUsage2int(req->ku) == 0) + req->nrequested--; return 0; } @@ -262,7 +286,12 @@ hx509_request_add_GeneralName(hx509_context context, hx509_request req, const GeneralName *gn) { - return add_GeneralNames(&req->san, gn); + int ret; + + ret = add_GeneralNames(&req->san, gn); + if (ret == 0) + req->nrequested++; + return ret; } static int @@ -311,8 +340,13 @@ hx509_request_add_xmpp_name(hx509_context context, hx509_request req, const char *jid) { - return add_utf8_other_san(context, &req->san, &asn1_oid_id_pkix_on_xmppAddr, - jid); + int ret; + + ret = add_utf8_other_san(context, &req->san, &asn1_oid_id_pkix_on_xmppAddr, + jid); + if (ret == 0) + req->nrequested++; + return ret; } /** @@ -331,8 +365,13 @@ hx509_request_add_ms_upn_name(hx509_context context, hx509_request req, const char *upn) { - return add_utf8_other_san(context, &req->san, &asn1_oid_id_pkinit_ms_san, - upn); + int ret; + + ret = add_utf8_other_san(context, &req->san, &asn1_oid_id_pkinit_ms_san, + upn); + if (ret == 0) + req->nrequested++; + return ret; } /** @@ -352,13 +391,17 @@ hx509_request_add_dns_name(hx509_context context, const char *hostname) { GeneralName name; + int ret; memset(&name, 0, sizeof(name)); name.element = choice_GeneralName_dNSName; name.u.dNSName.data = rk_UNCONST(hostname); name.u.dNSName.length = strlen(hostname); - return add_GeneralNames(&req->san, &name); + ret = add_GeneralNames(&req->san, &name); + if (ret == 0) + req->nrequested++; + return ret; } /** @@ -378,13 +421,17 @@ hx509_request_add_email(hx509_context context, const char *email) { GeneralName name; + int ret; memset(&name, 0, sizeof(name)); name.element = choice_GeneralName_rfc822Name; name.u.rfc822Name.data = rk_UNCONST(email); name.u.rfc822Name.length = strlen(email); - return add_GeneralNames(&req->san, &name); + ret = add_GeneralNames(&req->san, &name); + if (ret == 0) + req->nrequested++; + return ret; } /** @@ -413,6 +460,8 @@ hx509_request_add_registered(hx509_context context, return ret; ret = add_GeneralNames(&req->san, &name); free_GeneralName(&name); + if (ret == 0) + req->nrequested++; return ret; } @@ -449,6 +498,8 @@ hx509_request_add_pkinit(hx509_context context, if (ret == 0) ret = add_GeneralNames(&req->san, &gn); free_GeneralName(&gn); + if (ret == 0) + req->nrequested++; return ret; } @@ -797,19 +848,40 @@ hx509_request_parse_der(hx509_context context, ret = decode_KeyUsage(e->extnValue.data, e->extnValue.length, &(*req)->ku, NULL); what = "keyUsage"; + /* + * Count all KUs as one requested extension to be authorized, + * though the caller will have to check the KU values individually. + */ + if (KeyUsage2int((*req)->ku) & ~KeyUsage2int(int2KeyUsage(~0))) + (*req)->nunsupported++; + (*req)->nrequested++; } else if (der_heim_oid_cmp(&e->extnID, &asn1_oid_id_x509_ce_extKeyUsage) == 0) { ret = decode_ExtKeyUsage(e->extnValue.data, e->extnValue.length, &(*req)->eku, NULL); what = "extKeyUsage"; + + /* + * Count each EKU as a separate requested extension to be + * authorized. + */ + (*req)->nrequested += (*req)->eku.len; } else if (der_heim_oid_cmp(&e->extnID, &asn1_oid_id_x509_ce_subjectAltName) == 0) { ret = decode_GeneralNames(e->extnValue.data, e->extnValue.length, &(*req)->san, NULL); what = "subjectAlternativeName"; + + /* + * Count each SAN as a separate requested extension to be + * authorized. + */ + (*req)->nrequested += (*req)->san.len; } else { char *oidstr = NULL; + (*req)->nunsupported++; + /* * We need an HX509_TRACE facility for this sort of warning. * @@ -880,279 +952,373 @@ hx509_request_parse(hx509_context context, } /** - * Iterate EKUs in a CSR. + * Get some EKU from a CSR. Usable as an iterator. * * @param context An hx509 context. * @param req The hx509_request object. + * @param idx The index of the EKU (0 for the first) to return * @param out A pointer to a char * variable where the OID will be placed * (caller must free with free()) - * @param cursor An index of EKU (0 for the first); on return it's incremented - * or set to -1 when no EKUs remain. * - * @return An hx509 error code, see hx509_get_error_string(). + * @return Zero on success, HX509_NO_ITEM if no such item exists (denoting + * iteration end), or an error. * * @ingroup hx509_request */ HX509_LIB_FUNCTION int HX509_LIB_CALL -hx509_request_get_eku(hx509_context context, - hx509_request req, - char **out, - int *cursor) +hx509_request_get_eku(hx509_request req, + size_t idx, + char **out) { - size_t i; - *out = NULL; - if (*cursor < 0) - return 0; - i = (size_t)*cursor; - if (i >= req->eku.len) - return 0; /* XXX */ - if (i + 1 < req->eku.len) - (*cursor)++; - else - *cursor = -1; - return der_print_heim_oid(&req->eku.val[i], '.', out); -} - -ssize_t -find_san1(hx509_context context, - hx509_request req, - size_t i, - int kind, - const heim_oid *other_name_oid) -{ - if (i >= req->san.len) - return -1; - do { - GeneralName *san = &req->san.val[i]; - - if (i == INT_MAX) - return -1; - if (san->element == kind && kind != choice_GeneralName_otherName) - return i; - if (san->element == kind && kind == choice_GeneralName_otherName && - der_heim_oid_cmp(&san->u.otherName.type_id, other_name_oid) == 0) - return i; - } while (i++ < req->san.len); - return -1; -} - -ssize_t -find_san(hx509_context context, - hx509_request req, - int *cursor, - int kind, - const heim_oid *other_name_oid) -{ - ssize_t ret; - - if (*cursor < 0) - return -1; - ret = find_san1(context, req, (size_t)*cursor, kind, other_name_oid); - if (ret < 0 || ret >= INT_MAX) - *cursor = -1; - else - *cursor = find_san1(context, req, (size_t)*cursor + 1, kind, - other_name_oid); - return ret; + if (idx >= req->eku.len) + return HX509_NO_ITEM; + return der_print_heim_oid(&req->eku.val[idx], '.', out); } static int -get_utf8_otherName_san(hx509_context context, - hx509_request req, - const heim_oid *oid, - char **out, - int *cursor) +abitstring_check(abitstring a, size_t n, int idx) +{ + size_t bytes; + + if (idx >= n) + return EINVAL; + + bytes = (idx + 1) / CHAR_BIT + (((idx + 1) % CHAR_BIT) ? 1 : 0); + if (a->feat_bytes < bytes) + return 0; + + return !!(a->feats[idx / CHAR_BIT] & (1UL<<(idx % CHAR_BIT))); +} + +/* + * Sets and returns 0 if not already set, -1 if already set. Positive return + * values are system errors. + */ +static int +abitstring_set(abitstring a, size_t n, int idx) +{ + size_t bytes; + + if (idx >= n) + return EINVAL; + + bytes = n / CHAR_BIT + ((n % CHAR_BIT) ? 1 : 0); + if (a->feat_bytes < bytes) { + unsigned char *tmp; + + if ((tmp = realloc(a->feats, bytes)) == NULL) + return ENOMEM; + memset(tmp + a->feat_bytes, 0, bytes - a->feat_bytes); + a->feats = tmp; + a->feat_bytes = bytes; + } + + if (!(a->feats[idx / CHAR_BIT] & (1UL<<(idx % CHAR_BIT)))) { + a->feats[idx / CHAR_BIT] |= 1UL<<(idx % CHAR_BIT); + return 0; + } + return -1; +} + +/* + * Resets and returns 0 if not already reset, -1 if already reset. Positive + * return values are system errors. + */ +static int +abitstring_reset(abitstring a, size_t n, int idx) +{ + size_t bytes; + + if (idx >= n) + return EINVAL; + + bytes = (idx + 1) / CHAR_BIT + (((idx + 1) % CHAR_BIT) ? 1 : 0); + if (a->feat_bytes >= bytes && + (a->feats[idx / CHAR_BIT] & (1UL<<(idx % CHAR_BIT)))) { + a->feats[idx / CHAR_BIT] &= ~(1UL<<(idx % CHAR_BIT)); + return 0; + } + return -1; +} + +static int +authorize_feat(hx509_request req, abitstring a, size_t n, int idx) +{ + int ret; + + ret = abitstring_set(a, n, idx); + switch (ret) { + case 0: + req->nauthorized++; + /*fallthrough*/ + case -1: + return 0; + default: + return ret; + } +} + +static int +reject_feat(hx509_request req, abitstring a, size_t n, int idx) +{ + int ret; + + ret = abitstring_reset(a, n, idx); + switch (ret) { + case 0: + req->nauthorized--; + /*fallthrough*/ + case -1: + return 0; + default: + return ret; + } +} + +/** + * Filter the requested KeyUsage and mark it authorized. + * + * @param req The hx509_request object. + * @param ku Permitted KeyUsage + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION void HX509_LIB_CALL +hx509_request_authorize_ku(hx509_request req, KeyUsage ku) +{ + (void) hx509_request_set_ku(NULL, req, ku); + req->ku = int2KeyUsage(KeyUsage2int(req->ku) & KeyUsage2int(ku)); + if (KeyUsage2int(ku)) + req->ku_are_authorized = 1; +} + +/** + * Mark a requested EKU as authorized. + * + * @param req The hx509_request object. + * @param idx The index of an EKU that can be fetched with + * hx509_request_get_eku() + * + * @return Zero on success, an error otherwise. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_authorize_eku(hx509_request req, size_t idx) +{ + return authorize_feat(req, &req->authorized_EKUs, req->eku.len, idx); +} + +/** + * Mark a requested EKU as not authorized. + * + * @param req The hx509_request object. + * @param idx The index of an EKU that can be fetched with + * hx509_request_get_eku() + * + * @return Zero on success, an error otherwise. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_reject_eku(hx509_request req, size_t idx) +{ + return reject_feat(req, &req->authorized_EKUs, req->eku.len, idx); +} + +/** + * Check if an EKU has been marked authorized. + * + * @param req The hx509_request object. + * @param idx The index of an EKU that can be fetched with + * hx509_request_get_eku() + * + * @return Non-zero if authorized, zero if not. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_eku_authorized_p(hx509_request req, size_t idx) +{ + return abitstring_check(&req->authorized_EKUs, req->eku.len, idx); +} + +/** + * Mark a requested SAN as authorized. + * + * @param req The hx509_request object. + * @param idx The cursor as modified by a SAN iterator. + * + * @return Zero on success, an error otherwise. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_authorize_san(hx509_request req, size_t idx) +{ + return authorize_feat(req, &req->authorized_SANs, req->san.len, idx); +} + +/** + * Mark a requested SAN as not authorized. + * + * @param req The hx509_request object. + * @param idx The cursor as modified by a SAN iterator. + * + * @return Zero on success, an error otherwise. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_reject_san(hx509_request req, size_t idx) +{ + return reject_feat(req, &req->authorized_SANs, req->san.len, idx); +} + +/** + * Check if a SAN has been marked authorized. + * + * @param req The hx509_request object. + * @param idx The index of a SAN that can be fetched with + * hx509_request_get_san() + * + * @return Non-zero if authorized, zero if not. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_san_authorized_p(hx509_request req, size_t idx) +{ + return abitstring_check(&req->authorized_SANs, req->san.len, idx); +} + +/** + * Return the count of unsupported requested certificate extensions. + * + * @param req The hx509_request object. + * @return The number of unsupported certificate extensions requested. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION size_t HX509_LIB_CALL +hx509_request_count_unsupported(hx509_request req) +{ + return req->nunsupported; +} + +/** + * Return the count of as-yet unauthorized certificate extensions requested. + * + * @param req The hx509_request object. + * @return The number of as-yet unauthorized certificate extensions requested. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION size_t HX509_LIB_CALL +hx509_request_count_unauthorized(hx509_request req) +{ + return req->nrequested - (req->nauthorized + req->ku_are_authorized); +} + +static hx509_san_type +san_map_type(GeneralName *san) +{ + static const struct { + const heim_oid *oid; + hx509_san_type type; + } map[] = { + { &asn1_oid_id_pkinit_san, HX509_SAN_TYPE_PKINIT }, + { &asn1_oid_id_pkix_on_xmppAddr, HX509_SAN_TYPE_XMPP }, + { &asn1_oid_id_pkinit_ms_san, HX509_SAN_TYPE_MS_UPN } + }; + size_t i; + + switch (san->element) { + case choice_GeneralName_rfc822Name: return HX509_SAN_TYPE_EMAIL; + case choice_GeneralName_dNSName: return HX509_SAN_TYPE_DNSNAME; + case choice_GeneralName_directoryName: return HX509_SAN_TYPE_DN; + case choice_GeneralName_registeredID: return HX509_SAN_TYPE_REGISTERED_ID; + case choice_GeneralName_otherName: { + for (i = 0; i < sizeof(map)/sizeof(map[0]); i++) + if (der_heim_oid_cmp(&san->u.otherName.type_id, map[i].oid) == 0) + return map[i].type; + } + /*fallthrough*/ + default: return HX509_SAN_TYPE_UNSUPPORTED; + } +} + +/** + * Return the count of as-yet unauthorized certificate extensions requested. + * + * @param req The hx509_request object. + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION size_t HX509_LIB_CALL +hx509_request_get_san(hx509_request req, + size_t idx, + hx509_san_type *type, + char **out) { struct rk_strpool *pool; - ssize_t idx; - size_t i; + GeneralName *san; *out = NULL; - if (*cursor < 0) + if (idx >= req->san.len) + return HX509_NO_ITEM; + + san = &req->san.val[idx]; + switch ((*type = san_map_type(san))) { + case HX509_SAN_TYPE_UNSUPPORTED: return 0; + case HX509_SAN_TYPE_EMAIL: + *out = strndup(san->u.rfc822Name.data, + san->u.rfc822Name.length); + break; + case HX509_SAN_TYPE_DNSNAME: + *out = strndup(san->u.dNSName.data, + san->u.dNSName.length); + break; + case HX509_SAN_TYPE_DN: { + Name name; + + if (san->u.directoryName.element == + choice_GeneralName_directoryName_rdnSequence) { + name.element = choice_Name_rdnSequence; + name.u.rdnSequence = san->u.directoryName.u.rdnSequence; + return _hx509_Name_to_string(&name, out); + } + *type = HX509_SAN_TYPE_UNSUPPORTED; return 0; - idx = find_san(context, req, cursor, choice_GeneralName_otherName, oid); - if (idx < 0) - return -1; - i = (size_t)idx; - - pool = hx509_unparse_utf8_string_name(NULL, - &req->san.val[i].u.otherName.value); - if (pool == NULL || - (*out = rk_strpoolcollect(pool)) == NULL) - return ENOMEM; - return 0; -} - -/* XXX Add hx509_request_get_san() that also outputs the SAN type */ - -/** - * Iterate XMPP SANs in a CSR. - * - * @param context An hx509 context. - * @param req The hx509_request object. - * @param out A pointer to a char * variable where the Jabber address will be - * placed (caller must free with free()) - * @param cursor An index of SAN (0 for the first); on return it's incremented - * or set to -1 when no SANs remain. - * - * @return An hx509 error code, see hx509_get_error_string(). - * - * @ingroup hx509_request - */ -HX509_LIB_FUNCTION int HX509_LIB_CALL -hx509_request_get_xmpp_san(hx509_context context, - hx509_request req, - char **out, - int *cursor) -{ - return get_utf8_otherName_san(context, req, &asn1_oid_id_pkix_on_xmppAddr, - out, cursor); -} - -/** - * Iterate MS UPN SANs in a CSR. - * - * @param context An hx509 context. - * @param req The hx509_request object. - * @param out A pointer to a char * variable where the UPN will be placed - * (caller must free with free()) - * @param cursor An index of SAN (0 for the first); on return it's incremented - * or set to -1 when no SANs remain. - * - * @return An hx509 error code, see hx509_get_error_string(). - * - * @ingroup hx509_request - */ -HX509_LIB_FUNCTION int HX509_LIB_CALL -hx509_request_get_ms_upn_san(hx509_context context, - hx509_request req, - char **out, - int *cursor) -{ - return get_utf8_otherName_san(context, req, &asn1_oid_id_pkinit_ms_san, - out, cursor); -} - -/** - * Iterate e-mail SANs in a CSR. - * - * @param context An hx509 context. - * @param req The hx509_request object. - * @param out A pointer to a char * variable where the e-mail address will be - * placed (caller must free with free()) - * @param cursor An index of SAN (0 for the first); on return it's incremented - * or set to -1 when no SANs remain. - * - * @return An hx509 error code, see hx509_get_error_string(). - * - * @ingroup hx509_request - */ -HX509_LIB_FUNCTION int HX509_LIB_CALL -hx509_request_get_email_san(hx509_context context, - hx509_request req, - char **out, - int *cursor) -{ - ssize_t idx; - size_t i; - - *out = NULL; - if (*cursor < 0) + } + case HX509_SAN_TYPE_REGISTERED_ID: + return der_print_heim_oid(&san->u.registeredID, '.', out); + case HX509_SAN_TYPE_XMPP: + /*fallthrough*/ + case HX509_SAN_TYPE_MS_UPN: + pool = hx509_unparse_utf8_string_name(NULL, + &san->u.otherName.value); + if (pool == NULL || + (*out = rk_strpoolcollect(pool)) == NULL) + return ENOMEM; return 0; - idx = find_san(context, req, cursor, choice_GeneralName_rfc822Name, NULL); - if (idx < 0) - return -1; - i = (size_t)idx; - - *out = strndup(req->san.val[i].u.rfc822Name.data, - req->san.val[i].u.rfc822Name.length); + case HX509_SAN_TYPE_PKINIT: + pool = _hx509_unparse_kerberos_name(NULL, + &san->u.otherName.value); + if (pool == NULL || + (*out = rk_strpoolcollect(pool)) == NULL) + return ENOMEM; + return 0; + default: + *type = HX509_SAN_TYPE_UNSUPPORTED; + return 0; + } if (*out == NULL) return ENOMEM; return 0; } -/** - * Iterate dNSName (DNS domainname/hostname) SANs in a CSR. - * - * @param context An hx509 context. - * @param req The hx509_request object. - * @param out A pointer to a char * variable where the domainname will be - * placed (caller must free with free()) - * @param cursor An index of SAN (0 for the first); on return it's incremented - * or set to -1 when no SANs remain. - * - * @return An hx509 error code, see hx509_get_error_string(). - * - * @ingroup hx509_request - */ -HX509_LIB_FUNCTION int HX509_LIB_CALL -hx509_request_get_dns_name_san(hx509_context context, - hx509_request req, - char **out, - int *cursor) -{ - ssize_t idx; - size_t i; - - *out = NULL; - if (*cursor < 0) - return 0; - idx = find_san(context, req, cursor, choice_GeneralName_dNSName, NULL); - if (idx < 0) - return -1; - i = (size_t)idx; - - *out = strndup(req->san.val[i].u.dNSName.data, - req->san.val[i].u.dNSName.length); - if (*out == NULL) - return ENOMEM; - return 0; -} - -/** - * Iterate Kerberos principal name (PKINIT) SANs in a CSR. - * - * @param context An hx509 context. - * @param req The hx509_request object. - * @param out A pointer to a char * variable where the principal name will be - * placed (caller must free with free()) - * @param cursor An index of SAN (0 for the first); on return it's incremented - * or set to -1 when no SANs remain. - * - * @return An hx509 error code, see hx509_get_error_string(). - * - * @ingroup hx509_request - */ -HX509_LIB_FUNCTION int HX509_LIB_CALL -hx509_request_get_pkinit_san(hx509_context context, - hx509_request req, - char **out, - int *cursor) -{ - struct rk_strpool *pool; - ssize_t idx; - size_t i; - - *out = NULL; - if (*cursor < 0) - return 0; - idx = find_san(context, req, cursor, choice_GeneralName_otherName, - &asn1_oid_id_pkinit_san); - if (idx < 0) - return -1; - i = (size_t)idx; - - pool = _hx509_unparse_kerberos_name(NULL, - &req->san.val[i].u.otherName.value); - if (pool == NULL || - (*out = rk_strpoolcollect(pool)) == NULL) - return ENOMEM; - return 0; -} - -/* XXX More SAN types */ - /** * Display a CSR. * @@ -1168,7 +1334,9 @@ HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_print(hx509_context context, hx509_request req, FILE *f) { uint64_t ku_num; - int ret; + size_t i; + char *s = NULL; + int ret = 0; /* * It's really unformatunate that we can't reuse more of the @@ -1225,35 +1393,59 @@ hx509_request_print(hx509_context context, hx509_request req, FILE *f) fprintf(f, "%s", first); fprintf(f, "\n"); } - /* XXX Use new hx509_request_get_eku() accessor! */ if (req->eku.len) { const char *first = " "; - size_t i; fprintf(f, " eku:"); - for (i = 0; i< req->eku.len; i++) { - char *oidstr = NULL; - - der_print_heim_oid(&req->eku.val[i], '.', &oidstr); - fprintf(f, "%s{%s}", first, oidstr); - free(oidstr); + for (i = 0; ret == 0; i++) { + free(s); s = NULL; + ret = hx509_request_get_eku(req, i, &s); + if (ret) + break; + fprintf(f, "%s{%s}", first, s); first = ", "; } fprintf(f, "\n"); } - /* XXX Use new hx509_request_get_*_san() accessors! */ - if (req->san.len) { - size_t i; + free(s); s = NULL; + if (ret == HX509_NO_ITEM) + ret = 0; + for (i = 0; ret == 0; i++) { + hx509_san_type san_type; - for (i = 0; i < req->san.len; i++) { - GeneralName *san = &req->san.val[i]; - char *s = NULL; - - hx509_general_name_unparse(san, &s); - fprintf(f, " san: %s\n", s ? s : ""); - free(s); + free(s); s = NULL; + ret = hx509_request_get_san(req, i, &san_type, &s); + if (ret) + break; + switch (san_type) { + case HX509_SAN_TYPE_EMAIL: + fprintf(f, " san: rfc822Name: %s\n", s); + break; + case HX509_SAN_TYPE_DNSNAME: + fprintf(f, " san: dNSName: %s\n", s); + break; + case HX509_SAN_TYPE_DN: + fprintf(f, " san: dn: %s\n", s); + break; + case HX509_SAN_TYPE_REGISTERED_ID: + fprintf(f, " san: registeredID: %s\n", s); + break; + case HX509_SAN_TYPE_XMPP: + fprintf(f, " san: xmpp: %s\n", s); + break; + case HX509_SAN_TYPE_PKINIT: + fprintf(f, " san: pkinit: %s\n", s); + break; + case HX509_SAN_TYPE_MS_UPN: + fprintf(f, " san: ms-upn: %s\n", s); + break; + default: + fprintf(f, " san: \n"); + break; } } - + free(s); s = NULL; + if (ret == HX509_NO_ITEM) + ret = 0; return ret; } diff --git a/lib/hx509/test_req.in b/lib/hx509/test_req.in index 5d8d56bb5..a9c81933a 100644 --- a/lib/hx509/test_req.in +++ b/lib/hx509/test_req.in @@ -76,8 +76,8 @@ PKCS#10 CertificationRequest: san: rfc822Name: foo@test.h5l.se san: dNSName: nutcracker.test.h5l.se san: dNSName: foo.nutcracker.test.h5l.se - san: otherName: 1.3.6.1.5.2.2 HTTP/foo.nutcracker.it.su.se@TEST.H5L.SE - san: otherName: 1.3.6.1.5.2.2 host/foo.nutcracker.it.su.se@TEST.H5L.SE + san: pkinit: HTTP/foo.nutcracker.it.su.se@TEST.H5L.SE + san: pkinit: host/foo.nutcracker.it.su.se@TEST.H5L.SE san: registeredID: 1.2.3.4.5.6.9 EOF diff --git a/lib/hx509/version-script.map b/lib/hx509/version-script.map index cdccbf49a..f2fa40409 100644 --- a/lib/hx509/version-script.map +++ b/lib/hx509/version-script.map @@ -33,6 +33,11 @@ HEIMDAL_X509_1.2 { hx509_request_add_pkinit; hx509_request_add_registered; hx509_request_add_xmpp_name; + hx509_request_authorize_ku; + hx509_request_authorize_eku; + hx509_request_authorize_san; + hx509_request_count_unsupported; + hx509_request_count_unauthorized; hx509_request_print; hx509_request_to_pkcs10; _hx509_unmap_file_os; @@ -43,6 +48,7 @@ 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_san; hx509_ca_tbs_add_san_hostname; hx509_ca_tbs_add_san_jid; hx509_ca_tbs_add_san_ms_upn; @@ -54,6 +60,7 @@ HEIMDAL_X509_1.2 { hx509_ca_tbs_init; hx509_ca_tbs_set_ca; hx509_ca_tbs_set_domaincontroller; + hx509_ca_tbs_set_from_csr; hx509_ca_tbs_set_notAfter; hx509_ca_tbs_set_notAfter_lifetime; hx509_ca_tbs_set_notBefore; @@ -216,16 +223,12 @@ HEIMDAL_X509_1.2 { hx509_query_match_option; hx509_query_statistic_file; hx509_query_unparse_stats; - hx509_request_get_dns_name_san; hx509_request_get_eku; - hx509_request_get_email_san; hx509_request_get_exts; hx509_request_get_ku; - hx509_request_get_ms_upn_san; hx509_request_get_name; - hx509_request_get_pkinit_san; + hx509_request_get_san; hx509_request_get_SubjectPublicKeyInfo; - hx509_request_get_xmpp_san; hx509_request_free; hx509_request_init; hx509_request_parse;