diff --git a/lib/asn1/pkcs10.asn1 b/lib/asn1/pkcs10.asn1 index f3fe37b1b..14685bc7d 100644 --- a/lib/asn1/pkcs10.asn1 +++ b/lib/asn1/pkcs10.asn1 @@ -7,7 +7,6 @@ IMPORTS Name, SubjectPublicKeyInfo, Attribute, AlgorithmIdentifier FROM rfc2459; - CertificationRequestInfo ::= SEQUENCE { version INTEGER { pkcs10-v1(0) }, subject Name, diff --git a/lib/asn1/pkcs9.asn1 b/lib/asn1/pkcs9.asn1 index 50bf9dd1c..43b4a9dfd 100644 --- a/lib/asn1/pkcs9.asn1 +++ b/lib/asn1/pkcs9.asn1 @@ -14,6 +14,7 @@ id-pkcs9-contentType OBJECT IDENTIFIER ::= {id-pkcs-9 3 } id-pkcs9-messageDigest OBJECT IDENTIFIER ::= {id-pkcs-9 4 } id-pkcs9-signingTime OBJECT IDENTIFIER ::= {id-pkcs-9 5 } id-pkcs9-countersignature OBJECT IDENTIFIER ::= {id-pkcs-9 6 } +id-pkcs9-extReq OBJECT IDENTIFIER ::= {id-pkcs-9 14} id-pkcs-9-at-friendlyName OBJECT IDENTIFIER ::= {id-pkcs-9 20} id-pkcs-9-at-localKeyId OBJECT IDENTIFIER ::= {id-pkcs-9 21} diff --git a/lib/asn1/rfc2459.asn1 b/lib/asn1/rfc2459.asn1 index 05214b866..b7deb63ff 100644 --- a/lib/asn1/rfc2459.asn1 +++ b/lib/asn1/rfc2459.asn1 @@ -185,9 +185,11 @@ DirectoryString ::= CHOICE { bmpString BMPString } +AttributeValues ::= SET OF AttributeValue + Attribute ::= SEQUENCE { type AttributeType, - value SET OF -- AttributeValue -- heim_any + value AttributeValues } AttributeTypeAndValue ::= SEQUENCE { diff --git a/lib/hx509/ca.c b/lib/hx509/ca.c index f81641eef..6aa464eab 100644 --- a/lib/hx509/ca.c +++ b/lib/hx509/ca.c @@ -604,6 +604,145 @@ hx509_ca_tbs_add_san_otherName(hx509_context context, return add_GeneralNames(&tbs->san, &gn); } +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_make_pkinit_san(hx509_context context, + const char *principal, + heim_octet_string *os) +{ + KRB5PrincipalName p; + size_t size; + int ret; + + os->data = NULL; + os->length = 0; + memset(&p, 0, sizeof(p)); + + /* Parse principal */ + { + const char *str, *str_start; + size_t n, i; + + /* Count number of components */ + n = 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 == '/') { + 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) { + ret = ENOMEM; + hx509_set_error_string(context, 0, ret, "malloc: out of memory"); + goto out; + } + p.principalName.name_string.len = n; + p.principalName.name_type = KRB5_NT_PRINCIPAL; + + 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); + 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; +} + /** * Add Kerberos Subject Alternative Name to the to-be-signed * certificate object. The principal string is a UTF8 string. @@ -623,84 +762,13 @@ hx509_ca_tbs_add_san_pkinit(hx509_context context, const char *principal) { heim_octet_string os; - KRB5PrincipalName p; - size_t size; int ret; - char *s = NULL; - memset(&p, 0, sizeof(p)); - - /* parse principal */ - { - const char *str; - char *q; - int n; - - /* count number of component */ - n = 1; - for(str = principal; *str != '\0' && *str != '@'; str++){ - if(*str=='\\'){ - if(str[1] == '\0' || str[1] == '@') { - ret = HX509_PARSING_NAME_FAILED; - hx509_set_error_string(context, 0, ret, - "trailing \\ in principal name"); - goto out; - } - str++; - } else if(*str == '/') - n++; - } - p.principalName.name_string.val = - calloc(n, sizeof(*p.principalName.name_string.val)); - if (p.principalName.name_string.val == NULL) { - ret = ENOMEM; - hx509_set_error_string(context, 0, ret, "malloc: out of memory"); - 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'; - } - } - - 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"); - - ret = hx509_ca_tbs_add_san_otherName(context, - tbs, - &asn1_oid_id_pkinit_san, - &os); + 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; } diff --git a/lib/hx509/hxtool-commands.in b/lib/hx509/hxtool-commands.in index 64f59363d..11a4289e6 100644 --- a/lib/hx509/hxtool-commands.in +++ b/lib/hx509/hxtool-commands.in @@ -471,11 +471,36 @@ command = { type = "strings" help = "Email address in SubjectAltName" } + option = { + long = "jid" + type = "strings" + help = "XMPP (Jabber) address in SubjectAltName" + } option = { long = "dnsname" type = "strings" help = "Hostname or domainname in SubjectAltName" } + option = { + long = "kerberos" + type = "strings" + help = "Kerberos principal name as SubjectAltName" + } + option = { + long = "ms-kerberos" + type = "strings" + help = "Kerberos principal name as SubjectAltName (Microsoft variant)" + } + option = { + long = "registered" + type = "strings" + help = "Registered object ID as SubjectAltName" + } + option = { + long = "dn" + type = "strings" + help = "Directory name as SubjectAltName" + } option = { long = "type" type = "string" diff --git a/lib/hx509/hxtool.c b/lib/hx509/hxtool.c index ac748506c..9d5eb383e 100644 --- a/lib/hx509/hxtool.c +++ b/lib/hx509/hxtool.c @@ -1370,29 +1370,65 @@ request_create(struct request_create_options *opt, int argc, char **argv) char *s; hx509_name_to_string(name, &s); printf("%s\n", s); + free(s); } hx509_name_free(&name); } for (i = 0; i < opt->email_strings.num_strings; i++) { - ret = _hx509_request_add_email(context, req, - opt->email_strings.strings[i]); + ret = hx509_request_add_email(context, req, + opt->email_strings.strings[i]); if (ret) hx509_err(context, 1, ret, "hx509_request_add_email"); } + for (i = 0; i < opt->jid_strings.num_strings; i++) { + ret = hx509_request_add_xmpp_name(context, req, + opt->jid_strings.strings[i]); + if (ret) + hx509_err(context, 1, ret, "hx509_request_add_xmpp_name"); + } + for (i = 0; i < opt->dnsname_strings.num_strings; i++) { - ret = _hx509_request_add_dns_name(context, req, - opt->dnsname_strings.strings[i]); + ret = hx509_request_add_dns_name(context, req, + opt->dnsname_strings.strings[i]); if (ret) hx509_err(context, 1, ret, "hx509_request_add_dns_name"); } + for (i = 0; i < opt->kerberos_strings.num_strings; i++) { + ret = hx509_request_add_pkinit(context, req, + opt->kerberos_strings.strings[i]); + if (ret) + hx509_err(context, 1, ret, "hx509_request_add_pkinit"); + } + + for (i = 0; i < opt->ms_kerberos_strings.num_strings; i++) { + ret = hx509_request_add_ms_upn_name(context, req, + opt->ms_kerberos_strings.strings[i]); + if (ret) + hx509_err(context, 1, ret, "hx509_request_add_ms_upn_name"); + } + + 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"); + ret = hx509_request_add_registered(context, req, &oid); + der_free_oid(&oid); + if (ret) + hx509_err(context, 1, ret, "hx509_request_add_registered"); + } + for (i = 0; i < opt->eku_strings.num_strings; i++) { heim_oid oid; parse_oid(opt->eku_strings.strings[i], NULL, &oid); - ret = _hx509_request_add_eku(context, req, &oid); + ret = hx509_request_add_eku(context, req, &oid); + der_free_oid(&oid); if (ret) hx509_err(context, 1, ret, "hx509_request_add_eku"); } @@ -1409,12 +1445,12 @@ request_create(struct request_create_options *opt, int argc, char **argv) if (ret) hx509_err(context, 1, ret, "hx509_request_set_SubjectPublicKeyInfo"); - ret = _hx509_request_to_pkcs10(context, - req, - signer, - &request); + ret = hx509_request_to_pkcs10(context, + req, + signer, + &request); if (ret) - hx509_err(context, 1, ret, "_hx509_request_to_pkcs10"); + hx509_err(context, 1, ret, "hx509_request_to_pkcs10"); hx509_private_key_free(&signer); hx509_request_free(&req); @@ -1440,7 +1476,7 @@ request_print(struct request_print_options *opt, int argc, char **argv) if (ret) hx509_err(context, 1, ret, "parse_request: %s", argv[i]); - ret = _hx509_request_print(context, req, stdout); + ret = hx509_request_print(context, req, stdout); hx509_request_free(&req); if (ret) hx509_err(context, 1, ret, "Failed to print file %s", argv[i]); @@ -1901,6 +1937,11 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv) if (opt->req_string) { hx509_request req; + /* + * XXX Extract the CN and other attributes we want to preserve from the + * requested subjectName and then set them in the hx509_env for the + * template. + */ ret = hx509_request_parse(context, opt->req_string, &req); if (ret) hx509_err(context, 1, ret, "parse_request: %s", opt->req_string); @@ -1910,6 +1951,7 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv) ret = hx509_request_get_SubjectPublicKeyInfo(context, req, &spki); if (ret) hx509_err(context, 1, ret, "get spki"); + /* XXX Add option to extract */ hx509_request_free(&req); } diff --git a/lib/hx509/libhx509-exports.def b/lib/hx509/libhx509-exports.def index 846435f32..621d047de 100644 --- a/lib/hx509/libhx509-exports.def +++ b/lib/hx509/libhx509-exports.def @@ -12,31 +12,46 @@ EXPORTS _hx509_generate_private_key_free _hx509_generate_private_key_init _hx509_generate_private_key_is_ca + _hx509_make_pkinit_san _hx509_map_file_os _hx509_name_from_Name hx509_private_key2SPKI hx509_private_key_free _hx509_private_key_ref - _hx509_request_add_dns_name - _hx509_request_add_eku - _hx509_request_add_email + hx509_request_add_GeneralName + hx509_request_add_dns_name + hx509_request_add_eku + hx509_request_add_email + hx509_request_add_ms_upn_name + hx509_request_add_pkinit + hx509_request_add_registered + hx509_request_add_xmpp_name _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_SubjectPublicKeyInfo + hx509_request_get_xmpp_san hx509_request_get_name hx509_request_init hx509_request_parse hx509_request_parse_der - _hx509_request_print + hx509_request_print hx509_request_set_SubjectPublicKeyInfo -; _hx509_request_set_email + hx509_request_add_email hx509_request_set_name - _hx509_request_to_pkcs10 - _hx509_request_to_pkcs10 + hx509_request_set_ku + hx509_request_to_pkcs10 _hx509_unmap_file_os _hx509_write_file hx509_bitstring_print diff --git a/lib/hx509/name.c b/lib/hx509/name.c index 2dccc6453..69782cd69 100644 --- a/lib/hx509/name.c +++ b/lib/hx509/name.c @@ -1001,6 +1001,102 @@ hx509_name_is_null_p(const hx509_name name) name->der_name.u.rdnSequence.len == 0; } +/* + * This necessarily duplicates code from libkrb5, and has to unless we move + * common code here or to lib/roken for it. We do have slightly different + * needs (e.g., we want space quoted, and we want to indicate whether we saw + * trailing garbage, we have no need for flags, no special realm treatment, + * etc) than the corresponding code in libkrb5, so for now we duplicate this + * code. + * + * The relevant RFCs here are RFC1964 for the string representation of Kerberos + * principal names, and RFC4556 for the KRB5PrincipalName ASN.1 type (Kerberos + * lacks such a type because on the wire the name and realm are sent + * separately as a form of cheap compression). + * + * Note that we cannot handle embedded NULs because of Heimdal's representation + * of ASN.1 strings as C strings. + */ +struct rk_strpool * +_hx509_unparse_kerberos_name(struct rk_strpool *strpool, heim_any *value) +{ + static const char comp_quotable_chars[] = " \n\t\b\\/@"; + static const char realm_quotable_chars[] = " \n\t\b\\@"; + KRB5PrincipalName kn; + const char *s; + size_t i, k, len, plen; + int extra_bits; + int need_slash = 0; + int ret; + + ret = decode_KRB5PrincipalName(value->data, value->length, &kn, &len); + if (ret) + return rk_strpoolprintf(strpool, "length != len); + + for (i = 0; i < kn.principalName.name_string.len; i++) { + s = kn.principalName.name_string.val[i]; + len = strlen(s); + + if (need_slash) + strpool = rk_strpoolprintf(strpool, "/"); + need_slash = 1; + + for (k = 0; k < len; s += plen, k += plen) { + char c; + + plen = strcspn(s, comp_quotable_chars); + if (plen) + strpool = rk_strpoolprintf(strpool, "%.*s", (int)plen, s); + if (k + plen >= len) + continue; + switch ((c = s[plen++])) { + case '\n': strpool = rk_strpoolprintf(strpool, "\\n"); break; + case '\t': strpool = rk_strpoolprintf(strpool, "\\t"); break; + case '\b': strpool = rk_strpoolprintf(strpool, "\\b"); break; + /* default -> '@', ' ', '\\', or '/' */ + default: strpool = rk_strpoolprintf(strpool, "\\%c", c); break; + } + } + } + strpool = rk_strpoolprintf(strpool, "@"); + s = kn.realm; + len = strlen(kn.realm); + for (k = 0; k < len; s += plen, k += plen) { + char c; + + plen = strcspn(s, realm_quotable_chars); + if (plen) + strpool = rk_strpoolprintf(strpool, "%.*s", (int)plen, s); + if (k + plen >= len) + continue; + switch ((c = s[plen++])) { + case '\n': strpool = rk_strpoolprintf(strpool, "\\n"); break; + case '\t': strpool = rk_strpoolprintf(strpool, "\\t"); break; + case '\b': strpool = rk_strpoolprintf(strpool, "\\b"); break; + /* default -> '@', ' ', or '\\' */ + default: strpool = rk_strpoolprintf(strpool, "\\%c", c); break; + } + } + if (extra_bits) + strpool = rk_strpoolprintf(strpool, " "); + free_KRB5PrincipalName(&kn); + return strpool; +} + +struct rk_strpool * +hx509_unparse_utf8_string_name(struct rk_strpool *strpool, heim_any *value) +{ + PKIXXmppAddr us; + size_t size; + + if (decode_PKIXXmppAddr(value->data, value->length, &us, &size)) + return rk_strpoolprintf(strpool, ""); + strpool = rk_strpoolprintf(strpool, "%s", us); + free_PKIXXmppAddr(&us); + return strpool; +} + /** * Unparse the hx509 name in name into a string. * @@ -1025,17 +1121,34 @@ hx509_general_name_unparse(GeneralName *name, char **str) hx509_oid_sprint(&name->u.otherName.type_id, &oid); if (oid == NULL) return ENOMEM; - strpool = rk_strpoolprintf(strpool, "otherName: %s", oid); + strpool = rk_strpoolprintf(strpool, "otherName: %s ", oid); + if (der_heim_oid_cmp(&name->u.otherName.type_id, + &asn1_oid_id_pkinit_san) == 0) { + strpool = _hx509_unparse_kerberos_name(strpool, + &name->u.otherName.value); + } else if (der_heim_oid_cmp(&name->u.otherName.type_id, + &asn1_oid_id_pkix_on_xmppAddr) == 0) { + strpool = rk_strpoolprintf(strpool, "xmppAddr "); + strpool = hx509_unparse_utf8_string_name(strpool, + &name->u.otherName.value); + } else if (der_heim_oid_cmp(&name->u.otherName.type_id, + &asn1_oid_id_pkinit_ms_san) == 0) { + strpool = rk_strpoolprintf(strpool, "pkinitMsSan "); + strpool = hx509_unparse_utf8_string_name(strpool, + &name->u.otherName.value); + } else { + strpool = rk_strpoolprintf(strpool, "u.rfc822Name.length, (char *)name->u.rfc822Name.data); break; case choice_GeneralName_dNSName: - strpool = rk_strpoolprintf(strpool, "dNSName: %.*s\n", + strpool = rk_strpoolprintf(strpool, "dNSName: %.*s", (int)name->u.dNSName.length, (char *)name->u.dNSName.data); break; @@ -1095,10 +1208,8 @@ hx509_general_name_unparse(GeneralName *name, char **str) default: return EINVAL; } - if (strpool == NULL) + if (strpool == NULL || + (*str = rk_strpoolcollect(strpool)) == NULL) return ENOMEM; - - *str = rk_strpoolcollect(strpool); - return 0; } diff --git a/lib/hx509/req.c b/lib/hx509/req.c index dadea7eb2..1a9e9f61c 100644 --- a/lib/hx509/req.c +++ b/lib/hx509/req.c @@ -37,14 +37,22 @@ struct hx509_request_data { hx509_name name; SubjectPublicKeyInfo key; + KeyUsage ku; ExtKeyUsage eku; GeneralNames san; }; -/* +/** + * Allocate and initialize an hx509_request structure representing a PKCS#10 + * certificate signing request. * + * @param context An hx509 context. + * @param req Where to put the new hx509_request object. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request */ - HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_init(hx509_context context, hx509_request *req) { @@ -55,19 +63,41 @@ hx509_request_init(hx509_context context, hx509_request *req) return 0; } +/** + * Free a certificate signing request object. + * + * @param req A pointer to the hx509_request to free. + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION void HX509_LIB_CALL -hx509_request_free(hx509_request *req) +hx509_request_free(hx509_request *reqp) { - if ((*req)->name) - hx509_name_free(&(*req)->name); - free_SubjectPublicKeyInfo(&(*req)->key); - free_ExtKeyUsage(&(*req)->eku); - free_GeneralNames(&(*req)->san); - memset(*req, 0, sizeof(**req)); - free(*req); - *req = NULL; + hx509_request req = *reqp; + + *reqp = NULL; + if (req == NULL) + return; + if (req->name) + hx509_name_free(&req->name); + free_SubjectPublicKeyInfo(&req->key); + free_ExtKeyUsage(&req->eku); + free_GeneralNames(&req->san); + memset(req, 0, sizeof(*req)); + free(req); } +/** + * Set the subjectName of the CSR. + * + * @param context An hx509 context. + * @param req The hx509_request to alter. + * @param name The subjectName. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_set_name(hx509_context context, hx509_request req, @@ -83,6 +113,17 @@ hx509_request_set_name(hx509_context context, return 0; } +/** + * Get the subject name requested by a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param name Where to put the name. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_get_name(hx509_context context, hx509_request req, @@ -95,6 +136,17 @@ hx509_request_get_name(hx509_context context, return hx509_name_copy(context, req->name, name); } +/** + * Set the subject public key requested by a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param key The public key. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_set_SubjectPublicKeyInfo(hx509_context context, hx509_request req, @@ -104,6 +156,17 @@ hx509_request_set_SubjectPublicKeyInfo(hx509_context context, return copy_SubjectPublicKeyInfo(key, &req->key); } +/** + * Get the subject public key requested by a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param key Where to put the key. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_get_SubjectPublicKeyInfo(hx509_context context, hx509_request req, @@ -112,10 +175,57 @@ hx509_request_get_SubjectPublicKeyInfo(hx509_context context, return copy_SubjectPublicKeyInfo(&req->key, key); } +/** + * Set the key usage requested by a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param ku The key usage. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL -_hx509_request_add_eku(hx509_context context, - hx509_request req, - const heim_oid *oid) +hx509_request_set_ku(hx509_context context, hx509_request req, KeyUsage ku) +{ + req->ku = ku; + return 0; +} + +/** + * Get the key usage requested by a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param ku Where to put the key usage. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_get_ku(hx509_context context, hx509_request req, KeyUsage *ku) +{ + *ku = req->ku; + return 0; +} + +/** + * Add an extended key usage OID to a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param oid The EKU OID. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_add_eku(hx509_context context, + hx509_request req, + const heim_oid *oid) { void *val; int ret; @@ -134,10 +244,112 @@ _hx509_request_add_eku(hx509_context context, return 0; } +/** + * Add a GeneralName (Jabber ID) subject alternative name to a CSR. + * + * XXX Make this take a heim_octet_string, not a GeneralName*. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param gn The GeneralName object. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL -_hx509_request_add_dns_name(hx509_context context, - hx509_request req, - const char *hostname) +hx509_request_add_GeneralName(hx509_context context, + hx509_request req, + const GeneralName *gn) +{ + return add_GeneralNames(&req->san, gn); +} + +static int +add_utf8_other_san(hx509_context context, + GeneralNames *gns, + const heim_oid *oid, + const char *s) +{ + const PKIXXmppAddr us = (const PKIXXmppAddr)(uintptr_t)s; + GeneralName gn; + size_t size; + int ret; + + gn.element = choice_GeneralName_otherName; + gn.u.otherName.type_id.length = 0; + gn.u.otherName.type_id.components = 0; + gn.u.otherName.value.data = NULL; + gn.u.otherName.value.length = 0; + ret = der_copy_oid(oid, &gn.u.otherName.type_id); + if (ret == 0) + ASN1_MALLOC_ENCODE(PKIXXmppAddr, gn.u.otherName.value.data, + gn.u.otherName.value.length, &us, &size, ret); + if (ret == 0 && size != gn.u.otherName.value.length) + _hx509_abort("internal ASN.1 encoder error"); + if (ret == 0) + ret = add_GeneralNames(gns, &gn); + free_GeneralName(&gn); + if (ret) + hx509_set_error_string(context, 0, ret, "Out of memory"); + return ret; +} + +/** + * Add an xmppAddr (Jabber ID) subject alternative name to a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param jid The XMPP address. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +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); +} + +/** + * Add a Microsoft UPN subject alternative name to a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param hostname The XMPP address. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +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); +} + +/** + * Add a dNSName (hostname) subject alternative name to a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param hostname The fully-qualified hostname. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_add_dns_name(hx509_context context, + hx509_request req, + const char *hostname) { GeneralName name; @@ -149,33 +361,243 @@ _hx509_request_add_dns_name(hx509_context context, return add_GeneralNames(&req->san, &name); } +/** + * Add an rfc822Name (e-mail address) subject alternative name to a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param email The e-mail address. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL -_hx509_request_add_email(hx509_context context, - hx509_request req, - const char *email) +hx509_request_add_email(hx509_context context, + hx509_request req, + const char *email) { GeneralName name; memset(&name, 0, sizeof(name)); name.element = choice_GeneralName_rfc822Name; - name.u.dNSName.data = rk_UNCONST(email); - name.u.dNSName.length = strlen(email); + name.u.rfc822Name.data = rk_UNCONST(email); + name.u.rfc822Name.length = strlen(email); return add_GeneralNames(&req->san, &name); } - - +/** + * Add a registeredID (OID) subject alternative name to a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param oid The OID. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL -_hx509_request_to_pkcs10(hx509_context context, - const hx509_request req, - const hx509_private_key signer, - heim_octet_string *request) +hx509_request_add_registered(hx509_context context, + hx509_request req, + heim_oid *oid) +{ + GeneralName name; + int ret; + + memset(&name, 0, sizeof(name)); + name.element = choice_GeneralName_registeredID; + ret = der_copy_oid(oid, &name.u.registeredID); + if (ret) + return ret; + ret = add_GeneralNames(&req->san, &name); + free_GeneralName(&name); + return ret; +} + +/** + * Add a Kerberos V5 principal subject alternative name to a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param princ The Kerberos principal name. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_add_pkinit(hx509_context context, + hx509_request req, + const char *princ) +{ + KRB5PrincipalName kn; + GeneralName gn; + int ret; + + memset(&kn, 0, sizeof(kn)); + memset(&gn, 0, sizeof(gn)); + gn.element = choice_GeneralName_otherName; + gn.u.otherName.type_id.length = 0; + gn.u.otherName.type_id.components = 0; + gn.u.otherName.value.data = NULL; + gn.u.otherName.value.length = 0; + ret = der_copy_oid(&asn1_oid_id_pkinit_san, &gn.u.otherName.type_id); + if (ret == 0) + ret = _hx509_make_pkinit_san(context, princ, &gn.u.otherName.value); + if (ret == 0) + ret = add_GeneralNames(&req->san, &gn); + free_GeneralName(&gn); + return ret; +} + +/* XXX Add DNSSRV and other SANs */ + +static int +get_exts(hx509_context context, + const hx509_request req, + Extensions *exts) +{ + uint64_t ku_num; + size_t size; + int ret = 0; + + exts->val = NULL; + exts->len = 0; + + if ((ku_num = KeyUsage2int(req->ku))) { + Extension e; + + memset(&e, 0, sizeof(e)); + /* The critical field needs to be made DEFAULT FALSE... */ + if ((e.critical = malloc(sizeof(*e.critical))) == NULL) + ret = ENOMEM; + if (ret == 0) + *e.critical = 1; + if (ret == 0) + ASN1_MALLOC_ENCODE(KeyUsage, e.extnValue.data, e.extnValue.length, + &req->ku, &size, ret); + if (ret == 0) + ret = der_copy_oid(&asn1_oid_id_x509_ce_keyUsage, &e.extnID); + if (ret == 0) + ret = add_Extensions(exts, &e); + free_Extension(&e); + } + if (ret == 0 && req->eku.len) { + Extension e; + + memset(&e, 0, sizeof(e)); + if ((e.critical = malloc(sizeof(*e.critical))) == NULL) + ret = ENOMEM; + if (ret == 0) + *e.critical = 1; + if (ret == 0) + ASN1_MALLOC_ENCODE(ExtKeyUsage, + e.extnValue.data, e.extnValue.length, + &req->eku, &size, ret); + if (ret == 0) + ret = der_copy_oid(&asn1_oid_id_x509_ce_extKeyUsage, &e.extnID); + if (ret == 0) + ret = add_Extensions(exts, &e); + free_Extension(&e); + } + if (ret == 0 && req->san.len) { + Extension e; + + memset(&e, 0, sizeof(e)); + /* + * SANs are critical when the subject Name is empty. + * + * The empty DN check could probably stand to be a function we export. + */ + e.critical = NULL; + if (req->name && + req->name->der_name.element == choice_Name_rdnSequence && + req->name->der_name.u.rdnSequence.len == 0) { + + if ((e.critical = malloc(sizeof(*e.critical))) == NULL) + ret = ENOMEM; + if (ret == 0) { + *e.critical = 1; + } + } + if (ret == 0) + ASN1_MALLOC_ENCODE(GeneralNames, + e.extnValue.data, e.extnValue.length, + &req->san, + &size, ret); + if (ret == 0) + ret = der_copy_oid(&asn1_oid_id_x509_ce_subjectAltName, &e.extnID); + if (ret == 0) + ret = add_Extensions(exts, &e); + free_Extension(&e); + } + + return ret; +} + +/** + * Get the KU/EKUs/SANs set on a request as a DER-encoding of Extensions. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param exts_der Where to put the DER-encoded Extensions. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_get_exts(hx509_context context, + const hx509_request req, + heim_octet_string *exts_der) +{ + Extensions exts; + size_t size; + int ret; + + exts_der->data = NULL; + exts_der->length = 0; + ret = get_exts(context, req, &exts); + if (ret == 0 && exts_der->data && exts_der->length) + ASN1_MALLOC_ENCODE(Extensions, exts_der->data, exts_der->length, + &exts, &size, ret); + free_Extensions(&exts); + return ret; +} + +/* XXX Add PEM */ + +/** + * Encode a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param signer The private key corresponding to the CSR's subject public key. + * @param request Where to put the DER-encoded CSR. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_to_pkcs10(hx509_context context, + const hx509_request req, + const hx509_private_key signer, + heim_octet_string *request) { CertificationRequest r; - heim_octet_string data, os; - int ret; + Extensions exts; + heim_octet_string data; size_t size; + int ret; + + request->data = NULL; + request->length = 0; + + data.length = 0; + data.data = NULL; if (req->name == NULL) { hx509_set_error_string(context, 0, EINVAL, @@ -184,59 +606,85 @@ _hx509_request_to_pkcs10(hx509_context context, } memset(&r, 0, sizeof(r)); - memset(request, 0, sizeof(*request)); + /* Setup CSR */ r.certificationRequestInfo.version = pkcs10_v1; - ret = copy_Name(&req->name->der_name, &r.certificationRequestInfo.subject); - if (ret) - goto out; - ret = copy_SubjectPublicKeyInfo(&req->key, - &r.certificationRequestInfo.subjectPKInfo); - if (ret) - goto out; - r.certificationRequestInfo.attributes = - calloc(1, sizeof(*r.certificationRequestInfo.attributes)); - if (r.certificationRequestInfo.attributes == NULL) { - ret = ENOMEM; - goto out; + if (ret == 0) + ret = copy_SubjectPublicKeyInfo(&req->key, + &r.certificationRequestInfo.subjectPKInfo); + + /* Encode extReq attribute with requested Certificate Extensions */ + + if (ret == 0) + ret = get_exts(context, req, &exts); + if (ret == 0 && exts.len) { + Attribute *a; + heim_any extns; + + r.certificationRequestInfo.attributes = + calloc(1, sizeof(r.certificationRequestInfo.attributes[0])); + if (r.certificationRequestInfo.attributes == NULL) + ret = ENOMEM; + if (ret == 0) { + r.certificationRequestInfo.attributes[0].len = 1; + r.certificationRequestInfo.attributes[0].val = + calloc(1, sizeof(r.certificationRequestInfo.attributes[0].val[0])); + if (r.certificationRequestInfo.attributes[0].val == NULL) + ret = ENOMEM; + if (ret == 0) + a = r.certificationRequestInfo.attributes[0].val; + } + if (ret == 0) + ASN1_MALLOC_ENCODE(Extensions, extns.data, extns.length, + &exts, &size, ret); + if (ret == 0) + ret = der_copy_oid(&asn1_oid_id_pkcs9_extReq, &a->type); + if (ret == 0) + ret = add_AttributeValues(&a->value, &extns); + free_heim_any(&extns); } - ASN1_MALLOC_ENCODE(CertificationRequestInfo, data.data, data.length, - &r.certificationRequestInfo, &size, ret); - if (ret) - goto out; - if (data.length != size) + /* Encode CSR body for signing */ + if (ret == 0) + ASN1_MALLOC_ENCODE(CertificationRequestInfo, data.data, data.length, + &r.certificationRequestInfo, &size, ret); + if (ret == 0 && data.length != size) abort(); - ret = _hx509_create_signature(context, - signer, - _hx509_crypto_default_sig_alg, - &data, - &r.signatureAlgorithm, - &os); + /* Self-sign CSR body */ + if (ret == 0) { + ret = _hx509_create_signature_bitstring(context, signer, + _hx509_crypto_default_sig_alg, + &data, + &r.signatureAlgorithm, + &r.signature); + } free(data.data); - if (ret) - goto out; - r.signature.data = os.data; - r.signature.length = os.length * 8; - ASN1_MALLOC_ENCODE(CertificationRequest, data.data, data.length, - &r, &size, ret); - if (ret) - goto out; - if (data.length != size) + /* Encode CSR */ + if (ret == 0) + ASN1_MALLOC_ENCODE(CertificationRequest, request->data, request->length, + &r, &size, ret); + if (ret == 0 && request->length != size) abort(); - *request = data; - -out: free_CertificationRequest(&r); - return ret; } +/** + * Parse an encoded CSR and verify its self-signature. + * + * @param context An hx509 context. + * @param der The DER-encoded CSR. + * @param req Where to put request object. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_parse_der(hx509_context context, heim_octet_string *der, @@ -244,75 +692,164 @@ hx509_request_parse_der(hx509_context context, { CertificationRequestInfo *rinfo = NULL; CertificationRequest r; - Certificate c; - hx509_name subject = NULL; hx509_cert signer = NULL; - size_t size; + Extensions exts; + size_t i, size; int ret; + memset(&exts, 0, sizeof(exts)); + + /* Initial setup and decoding of CSR */ + ret = hx509_request_init(context, req); + if (ret) + return ret; ret = decode_CertificationRequest(der->data, der->length, &r, &size); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to decode CSR"); + free(*req); return ret; } rinfo = &r.certificationRequestInfo; + + /* + * Setup a 'signer' for verifying the self-signature for proof of + * possession. + * + * Sadly we need a "certificate" here because _hx509_verify_signature_*() + * functions want one as a signer even though all the verification + * functions that use the signer argument only ever use the spki of the + * signer certificate. + * + * FIXME Change struct signature_alg's verify_signature's prototype to use + * an spki instead of an hx509_cert as the signer! The we won't have + * to do this. + */ if (ret == 0) { - /* - * Sadly we need an hx509_cert here because _hx509_verify_signature_*() - * functions want one as a signer even though all the verification - * functions that use the signer argument always only use the spki of - * the signer certificate. - * - * XXX Change struct signature_alg's verify_signature's prototype to - * use an spki instead of an hx509_cert as the signer! The we - * won't have to do this. - */ + Certificate c; memset(&c, 0, sizeof(c)); c.tbsCertificate.subjectPublicKeyInfo = rinfo->subjectPKInfo; if ((signer = hx509_cert_init(context, &c, NULL)) == NULL) ret = ENOMEM; - } /* Verify the signature */ - if (ret == 0) { - heim_octet_string *d = &r.certificationRequestInfo._save; - - ret = _hx509_verify_signature_bitstring(context, signer, - &r.signatureAlgorithm, d, - &r.signature); - if (ret) - hx509_set_error_string(context, 0, ret, - "CSR signature verification failed"); - } - - /* Build and populate the hx509_request */ if (ret == 0) - ret = hx509_request_init(context, req); + ret = _hx509_verify_signature_bitstring(context, signer, + &r.signatureAlgorithm, + &rinfo->_save, + &r.signature); + if (ret) + hx509_set_error_string(context, 0, ret, + "CSR signature verification failed"); + hx509_cert_free(signer); + + /* Populate the hx509_request */ if (ret == 0) ret = hx509_request_set_SubjectPublicKeyInfo(context, *req, &rinfo->subjectPKInfo); if (ret == 0) - ret = _hx509_name_from_Name(&rinfo->subject, &subject); - if (ret == 0) - ret = hx509_request_set_name(context, *req, subject); + ret = _hx509_name_from_Name(&rinfo->subject, &(*req)->name); - /* - * XXX Extract EKUs and SANs from the CSR's attributes. That means looking - * for an attr of OID asn1_oid_id_x509_ce_subjectAltName - * (id-ce-subjectAltName) and decoding their values. See RFC5912. - */ + /* Extract KUs, EKUs, and SANs from the CSR's attributes */ + if (ret || !rinfo->attributes || !rinfo->attributes[0].len) + goto out; - if (subject) - hx509_name_free(&subject); - if (signer) - hx509_cert_free(signer); + for (i = 0; ret == 0 && i < rinfo->attributes[0].len; i++) { + Attribute *a = &rinfo->attributes[0].val[i]; + heim_any *av = NULL; + + /* We only support Extensions request attributes */ + if (der_heim_oid_cmp(&a->type, &asn1_oid_id_pkcs9_extReq) != 0) { + char *oidstr = NULL; + + /* + * We need an HX509_TRACE facility for this sort of warning. + * + * We'd put the warning in the context and then allow the caller to + * extract and reset the warning. + * + * FIXME + */ + der_print_heim_oid(&a->type, '.', &oidstr); + warnx("Unknown or unsupported CSR attribute %s", + oidstr ? oidstr : ""); + free(oidstr); + continue; + } + if (!a->value.val) + continue; + + av = a->value.val; + ret = decode_Extensions(av->data, av->length, &exts, NULL); + if (ret) { + hx509_set_error_string(context, 0, ret, + "CSR signature verification failed " + "due to invalid extReq attribute"); + goto out; + } + } + for (i = 0; ret == 0 && i < exts.len; i++) { + const char *what = ""; + Extension *e = &exts.val[i]; + + if (der_heim_oid_cmp(&e->extnID, + &asn1_oid_id_x509_ce_keyUsage) == 0) { + ret = decode_KeyUsage(e->extnValue.data, e->extnValue.length, + &(*req)->ku, NULL); + what = "keyUsage"; + } 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"; + } 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"; + } else { + char *oidstr = NULL; + + /* + * We need an HX509_TRACE facility for this sort of warning. + * + * We'd put the warning in the context and then allow the caller to + * extract and reset the warning. + * + * FIXME + */ + der_print_heim_oid(&e->extnID, '.', &oidstr); + warnx("Unknown or unsupported CSR extension request %s", + oidstr ? oidstr : ""); + free(oidstr); + } + if (ret) { + hx509_set_error_string(context, 0, ret, + "CSR signature verification failed " + "due to invalid %s extension", what); + break; + } + } + +out: + free_CertificationRequest(&r); if (ret) hx509_request_free(req); free_CertificationRequest(&r); return ret; } +/** + * Parse an encoded CSR and verify its self-signature. + * + * @param context An hx509 context. + * @param csr The name of a store containing the CSR ("PKCS10:/path/to/file") + * @param req Where to put request object. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL hx509_request_parse(hx509_context context, const char *csr, @@ -342,12 +879,325 @@ hx509_request_parse(hx509_context context, return ret; } - +/** + * Iterate EKUs in a CSR. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @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(). + * + * @ingroup hx509_request + */ HX509_LIB_FUNCTION int HX509_LIB_CALL -_hx509_request_print(hx509_context context, hx509_request req, FILE *f) +hx509_request_get_eku(hx509_context context, + hx509_request req, + char **out, + int *cursor) { + 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; +} + +static int +get_utf8_otherName_san(hx509_context context, + hx509_request req, + const heim_oid *oid, + 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, 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) + 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); + 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. + * + * @param context An hx509 context. + * @param req The hx509_request object. + * @param f A FILE * to print the CSR to. + * + * @return An hx509 error code, see hx509_get_error_string(). + * + * @ingroup hx509_request + */ +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_request_print(hx509_context context, hx509_request req, FILE *f) +{ + uint64_t ku_num; int ret; + /* + * It's really unformatunate that we can't reuse more of the + * lib/hx509/print.c infrastructure here, as it's too focused on + * Certificates. + * + * For that matter, it's really annoying that CSRs don't more resemble + * Certificates. Indeed, an ideal CSR would look like this: + * + * CSRInfo ::= { + * desiredTbsCertificate TBSCertificate, + * attributes [1] SEQUENCE OF Attribute OPTIONAL, + * } + * CSR :: = { + * csrInfo CSRInfo, + * sigAlg AlgorithmIdentifier, + * signature BIT STRING + * } + * + * with everything related to the desired certificate in + * desiredTbsCertificate and anything not related to the CSR's contents in + * the 'attributes' field. + * + * That wouldn't allow one to have optional desired TBSCertificate + * features, but hey. One could express "gimme all or gimme nothing" as an + * attribute, or "gimme what you can", then check what one got. + */ + fprintf(f, "PKCS#10 CertificationRequest:\n"); + if (req->name) { char *subject; ret = hx509_name_to_string(req->name, &subject); @@ -355,10 +1205,55 @@ _hx509_request_print(hx509_context context, hx509_request req, FILE *f) hx509_set_error_string(context, 0, ret, "Failed to print name"); return ret; } - fprintf(f, "name: %s\n", subject); + fprintf(f, " name: %s\n", subject); free(subject); } + /* XXX Use hx509_request_get_ku() accessor */ + if ((ku_num = KeyUsage2int(req->ku))) { + const struct units *u; + const char *first = " "; - return 0; + fprintf(f, " key usage:"); + for (u = asn1_KeyUsage_units(); u->name; ++u) { + if ((ku_num & u->mult)) { + fprintf(f, "%s%s", first, u->name); + first = ", "; + ku_num &= ~u->mult; + } + } + if (ku_num) + 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); + first = ", "; + } + fprintf(f, "\n"); + } + /* XXX Use new hx509_request_get_*_san() accessors! */ + if (req->san.len) { + size_t i; + + 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); + } + } + + return ret; } - diff --git a/lib/hx509/test_name.c b/lib/hx509/test_name.c index 9d21a7f65..7fd6c22b8 100644 --- a/lib/hx509/test_name.c +++ b/lib/hx509/test_name.c @@ -349,6 +349,74 @@ test_compare(hx509_context context) return 0; } +static int +test_pkinit_san(hx509_context context, const char *p, const char *realm, ...) +{ + KRB5PrincipalName kn; + GeneralName gn; + va_list ap; + size_t i, sz; + char *round_trip; + int ret; + + memset(&kn, 0, sizeof(kn)); + memset(&gn, 0, sizeof(gn)); + + ret = _hx509_make_pkinit_san(context, p, &gn.u.otherName.value); + if (ret == 0) + ret = decode_KRB5PrincipalName(gn.u.otherName.value.data, + gn.u.otherName.value.length, &kn, &sz); + if (ret) + return ret; + if (strcmp(realm, kn.realm)) + return ret; + + va_start(ap, realm); + for (i = 0; i < kn.principalName.name_string.len; i++) { + const char *s = va_arg(ap, const char *); + + if (s == NULL || strcmp(kn.principalName.name_string.val[i], s)) + return 1; + } + if (va_arg(ap, const char *) != NULL) + return 1; + va_end(ap); + + gn.element = choice_GeneralName_otherName; + gn.u.otherName.type_id.length = 0; + gn.u.otherName.type_id.components = 0; + ret = der_copy_oid(&asn1_oid_id_pkinit_san, &gn.u.otherName.type_id); + if (ret == 0) + ret = hx509_general_name_unparse(&gn, &round_trip); + if (ret) + return 1; + if (strncmp(round_trip, "otherName: 1.3.6.1.5.2.2 ", + sizeof("otherName: 1.3.6.1.5.2.2 ") - 1)) + return 1; + if (ret || strcmp(round_trip + sizeof("otherName: 1.3.6.1.5.2.2 ") - 1, p)) + return 1; + free_KRB5PrincipalName(&kn); + free_GeneralName(&gn); + free(round_trip); + return 0; +} + +static int +test_pkinit_san_fail(hx509_context context, const char *p) +{ + heim_octet_string os; + KRB5PrincipalName kn; + int ret; + + memset(&kn, 0, sizeof(kn)); + ret = _hx509_make_pkinit_san(context, p, &os); + if (ret == 0) { + free(os.data); + return 1; + } + return 0; +} + int main(int argc, char **argv) @@ -376,7 +444,25 @@ main(int argc, char **argv) ret += test_compare(context); + ret += test_pkinit_san(context, "foo@BAR.H5L.SE", + "BAR.H5L.SE", "foo", NULL); + ret += test_pkinit_san(context, "foo\\ bar@BAR.H5L.SE", + "BAR.H5L.SE", "foo bar", NULL); + ret += test_pkinit_san(context, "foo\\/bar@BAR.H5L.SE", + "BAR.H5L.SE", "foo/bar", NULL); + ret += test_pkinit_san(context, "foo/bar@BAR.H5L.SE", + "BAR.H5L.SE", "foo", "bar", NULL); + ret += test_pkinit_san(context, "foo\\tbar@BAR.H5L.SE", + "BAR.H5L.SE", "foo\tbar", NULL); + ret += test_pkinit_san(context, "foo\\nbar@BAR.H5L.SE", + "BAR.H5L.SE", "foo\nbar", NULL); + ret += test_pkinit_san(context, "foo@\\ BAR.H5L.SE", + " BAR.H5L.SE", "foo", NULL); + ret += test_pkinit_san(context, "foo@\\nBAR.H5L.SE", + "\nBAR.H5L.SE", "foo", NULL); + ret += test_pkinit_san_fail(context, "foo\\0bar@BAR.H5L.SE"); + hx509_context_free(&context); - return ret; + return !!ret; } diff --git a/lib/hx509/test_req.in b/lib/hx509/test_req.in index 49919d918..5d8d56bb5 100644 --- a/lib/hx509/test_req.in +++ b/lib/hx509/test_req.in @@ -50,14 +50,84 @@ fi ${hxtool} request-create \ --subject="CN=Love,DC=it,DC=su,DC=se" \ - --key=FILE:$srcdir/data/key.der \ - request.out || exit 1 + --key="FILE:$srcdir/data/key.der" \ + "${objdir}/request.out" || exit 1 ${hxtool} request-print \ PKCS10:request.out > /dev/null || exit 1 ${hxtool} request-create \ --subject="CN=Love,DC=it,DC=su,DC=se" \ - --dnsname=nutcracker.it.su.se \ - --key=FILE:$srcdir/data/key.der \ - request.out || exit 1 + --eku=1.2.3.4.5.6.7 --eku=1.2.3.4.5.6.8 \ + --registered=1.2.3.4.5.6.9 --eku=1.2.3.4.5.6.10 \ + --dnsname=nutcracker.test.h5l.se \ + --dnsname=foo.nutcracker.test.h5l.se \ + --kerberos=HTTP/foo.nutcracker.it.su.se@TEST.H5L.SE \ + --kerberos=host/foo.nutcracker.it.su.se@TEST.H5L.SE \ + --email=foo@test.h5l.se \ + --key="FILE:$srcdir/data/key.der" \ + "${objdir}/request.out" || exit 1 + +cat > "$objdir/expected" < "${objdir}/actual" || exit 1 + +diff "$objdir/expected" "${objdir}/actual" || exit 1 + +if openssl version > /dev/null && + openssl req -inform DER -in "${objdir}/request.out" -text | + grep 'Version: 0'; then + v=0 + k= +else + v=1 + k="RSA " +fi + +# Check that OpenSSL can parse our request: +cat > "$objdir/expected" <, othername:, Registered ID:1.2.3.4.5.6.9 + Signature Algorithm: sha256WithRSAEncryption +EOF + +if openssl version > /dev/null; then + openssl req -inform DER -in "${objdir}/request.out" -text | head -25 > "${objdir}/actual" + diff -w "${objdir}/expected" "${objdir}/actual" || exit 1 +fi + diff --git a/lib/hx509/version-script.map b/lib/hx509/version-script.map index 9abc06910..67f917fcd 100644 --- a/lib/hx509/version-script.map +++ b/lib/hx509/version-script.map @@ -14,6 +14,7 @@ HEIMDAL_X509_1.2 { _hx509_generate_private_key_free; _hx509_generate_private_key_init; _hx509_generate_private_key_is_ca; + _hx509_make_pkinit_san; _hx509_map_file_os; _hx509_name_from_Name; _hx509_private_key_export; @@ -21,12 +22,16 @@ HEIMDAL_X509_1.2 { _hx509_private_key_get_internal; _hx509_private_key_oid; _hx509_private_key_ref; - _hx509_request_add_dns_name; - _hx509_request_add_eku; - _hx509_request_add_email; - _hx509_request_print; - _hx509_request_set_email; - _hx509_request_to_pkcs10; + hx509_request_add_GeneralName; + hx509_request_add_dns_name; + hx509_request_add_eku; + hx509_request_add_email; + hx509_request_add_ms_upn_name; + hx509_request_add_pkinit; + hx509_request_add_registered; + hx509_request_add_xmpp_name; + hx509_request_print; + hx509_request_to_pkcs10; _hx509_unmap_file_os; _hx509_write_file; hx509_bitstring_print; @@ -206,12 +211,21 @@ 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_SubjectPublicKeyInfo; + hx509_request_get_xmpp_san; hx509_request_free; hx509_request_init; hx509_request_parse; hx509_request_parse_der; + hx509_request_set_ku; hx509_request_set_name; hx509_request_set_SubjectPublicKeyInfo; hx509_revoke_add_crl; diff --git a/lib/roken/parse_units.c b/lib/roken/parse_units.c index 08adf96cf..7f60cc16c 100644 --- a/lib/roken/parse_units.c +++ b/lib/roken/parse_units.c @@ -210,7 +210,7 @@ unparse_something (int num, const struct units *units, char *s, size_t len, tmp = (*print) (s, len, divisor, u->name, num); if (tmp < 0) return tmp; - if (tmp > (int) len) { + if ((size_t)tmp > len) { len = 0; s = NULL; } else {