hx509: Add missing CSR extension request support
This is necessary in order to add proper support for CSRs in kx509, where the KDC can examine all requested KUs/EKUs/SANs, check authorization, and issue a certificate with all those extensions if authorized. This is the convention used by OpenSSL, of encoding all the KU, EKUs, and SANs being requested as Extensions as they would appear in the TBSCertificate, then putting those in as a single Attribute in the CSR's Attributes list with attribute OID {id-pkcs-9, 14}. - expose all hx509_request_*() functions - finish support in hx509_request_parse*() for KU, EKU, and SAN CSR attributes - finish support in hx509_request_to_pkcs10() for encoding all requested KU, EKU, and SAN extensions as a CSR extReq (extension request) - add hx509_request_add_*() support for: - id-pkinit-san and ms-upn-pkinit-san - XMPP (Jabber) SAN - registeredID (useless but trivial) - add hxtool request-create options for all supported SANs - add hxtool request-create options for KeyUsage - add hxtool request-create options for ExtKeyUsage - add hxtool request-print support for all these things - fix bugs in existing id-pkinit-san handling Possible future improvements - add HX509_TRACE env var and support (it would be nice to be able to observe why some certificate is rejected, or not matched in a query) - add testing that CSR creating and printing round-trip for all KUs, EKUs, and SANs (probably in tests/kdc/check-pkinit.in) - add testing that OpenSSL can print a CSR made by hxtool and vice-versa - hxtool ca: add KU sanity checking (via hx509_ca_sign() and/or friends) (don't allow encrypt for signing-only algs) (don't allow encrypt for RSA at all, or for RSA with small e exponents) - hxtool request-print: warn about all unknown attributes and extensions - hxtool ca: MAYBE add support for adding requested extensions from the --req=CSR ("Maybe" because CA operators should really verify and authorize all requested attributes, and should acknowledge that they have, and the simplest way to do this is to make them add all the corresponding CLI arguments to the hxtool ca command, but too, that is error-prone, thus it's not clear yet which approach is best. Perhaps interactively prompt for yes/no for each attribute.) - add additional SAN types: - iPAddress (useless?) - dNSSrv (useful!) - directoryName (useless, but trivial) - uniformResourceIdentifier (useful) - it would be nice if the ASN.1 compiler could generate print functions..., and/or even better, to-JSON functions - it would be nice if we had a known-OID db, including the names of the types they refer to in certificate extensions, otherName SANs and CSR attributes, then we could generate a CSR and certificate printer for all known options even when they are not supported by the rest of Heimdal - and we could also get friendly names for OIDs, and we could resolve their arc names - longer term, we could also stand to add some ASN.1 information object system functionality, just enough to make lib/hx509/asn1_print awesome by being able to automatically decode all heim_any and OCTET STRING content (better than its current --inner option)
This commit is contained in:
218
lib/hx509/ca.c
218
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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user