Files
heimdal/lib/hx509/name.c
Nicolas Williams db7763ca7b asn1: X.681/682/683 magic handling of open types
Status:

 - And it works!

 - We have an extensive test based on decoding a rich EK certficate.

   This test exercises all of:

    - decoding
    - encoding with and without decoded open types
    - copying of decoded values with decoded open types
    - freeing of decoded values with decoded open types

   Valgrind finds no memory errors.

 - Added a manual page for the compiler.

 - rfc2459.asn1 now has all three primary PKIX types that we care about
   defined as in RFC5912, with IOS constraints and parameterization:

    - `Extension`       (embeds open type in an `OCTET STRING`)
    - `OtherName`       (embeds open type in an        `ANY`-like type)
    - `SingleAttribute` (embeds open type in an        `ANY`-like type)
    - `AttributeSet`    (embeds open type in a  `SET OF ANY`-like type)

   All of these use OIDs as the open type type ID field, but integer
   open type type ID fields are also supported (and needed, for
   Kerberos).

   That will cover every typed hole pattern in all our ASN.1 modules.

   With this we'll be able to automatically and recursively decode
   through all subject DN attributes even when the subject DN is a
   directoryName SAN, and subjectDirectoryAttributes, and all
   extensions, and all SANs, and all authorization-data elements, and
   PA-data, and...

   We're not really using `SingleAttribute` and `AttributeSet` yet
   because various changes are needed in `lib/hx509` for that.

 - `asn1_compile` builds and recognizes the subset of X.681/682/683 that
   we need for, and now use in, rfc2459.asn1.  It builds the necessary
   AST, generates the correct C types, and generates templating for
   object sets and open types!

 - See READMEs for details.

 - Codegen backend not tested; I won't make it implement automatic open
   type handling, but it should at least not crash by substituting
   `heim_any` for open types not embedded in `OCTET STRING`.

 - We're _really_ starting to have problems with the ITU-T ASN.1
   grammar and our version of it...

   Type names have to start with upper-case, value names with
   lower-case, but it's not enough to disambiguate.

   The fact the we've allowed value and type names to violate their
   respective start-with case rules is causing us trouble now that we're
   adding grammar from X.681/682/683, and we're going to have to undo
   that.

   In preparation for that I'm capitalizing the `heim_any` and
   `heim_any_set` types, and doing some additional cleanup, which
   requires changes to other parts of Heimdal (all in this same commit
   for now).

   Problems we have because of this:

    - We cannot IMPORT values into modules because we have no idea if a
      symbol being imported refers to a value or a type because the only
      clue we would have is the symbol's name, so we assume IMPORTed
      symbols are for types.

      This means we can't import OIDs, for example, which is super
      annoying.

      One thing we might be able to do here is mark imported symbols as
      being of an undetermined-but-not-undefined type, then coerce the
      symbol's type the first time it's used in a context where its type
      is inferred as type, value, object, object set, or class.  (Though
      since we don't generate C symbols for objects or classes, we won't
      be able to import them, especially since we need to know them at
      compile time and cannot defer their handling to link- or
      run-time.)

    - The `NULL` type name, and the `NULL` value name now cause two
      reduce/reduce conflicts via the `FieldSetting` production.

    - Various shift/reduce conflicts involving `NULL` values in
      non-top-level contexts (in constraints, for example).

 - Currently I have a bug where to disambiguate the grammar I have a
   CLASS_IDENTIFIER token that is all caps, while TYPE_IDENTIFIER must
   start with a capital but not be all caps, but this breaks Kerberos
   since all its types are all capitalized -- oof!

   To fix this I made it so class names have to be all caps and
   start with an underscore (ick).

TBD:

 - Check all the XXX comments and address them
 - Apply this treatment to Kerberos!  Automatic handling of authz-data
   sounds useful :)
 - Apply this treatment to PKCS#10 (CSRs) and other ASN.1 modules too.
 - Replace various bits of code in `lib/hx509/` with uses of this
   feature.
 - Add JER.
 - Enhance `hxtool` and `asn1_print`.

Getting there!
2021-02-28 18:13:08 -06:00

1401 lines
38 KiB
C

/*
* Copyright (c) 2004 - 2009 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "hx_locl.h"
#include <wind.h>
#include "char_map.h"
/**
* @page page_name PKIX/X.509 Names
*
* There are several names in PKIX/X.509, GeneralName and Name.
*
* A Name consists of an ordered list of Relative Distinguished Names
* (RDN). Each RDN consists of an unordered list of typed strings. The
* types are defined by OID and have long and short description. For
* example id-at-commonName (2.5.4.3) have the long name CommonName
* and short name CN. The string itself can be of several encoding,
* UTF8, UTF16, Teltex string, etc. The type limit what encoding
* should be used.
*
* GeneralName is a broader nametype that can contains al kind of
* stuff like Name, IP addresses, partial Name, etc.
*
* Name is mapped into a hx509_name object.
*
* Parse and string name into a hx509_name object with hx509_parse_name(),
* make it back into string representation with hx509_name_to_string().
*
* Name string are defined rfc2253, rfc1779 and X.501.
*
* See the library functions here: @ref hx509_name
*/
static const struct {
const char *n;
const heim_oid *o;
wind_profile_flags flags;
/*
* RFC52380 imposes maximum lengths for some strings in Names. These are
* ASN.1 size limits. We should implement these in our copy of the PKIX
* ASN.1 module. For now we treat them as maximum byte counts rather than
* maximum character counts, and we encode and enforce them here.
*
* 0 -> no max
*/
size_t max_bytes;
} no[] = {
{ "C", &asn1_oid_id_at_countryName, 0, 2 },
{ "CN", &asn1_oid_id_at_commonName, 0, ub_common_name },
{ "DC", &asn1_oid_id_domainComponent, 0, 63 }, /* DNS label */
{ "L", &asn1_oid_id_at_localityName, 0, ub_locality_name },
{ "O", &asn1_oid_id_at_organizationName, 0, ub_organization_name },
{ "OU", &asn1_oid_id_at_organizationalUnitName, 0, ub_organizational_unit_name },
{ "S", &asn1_oid_id_at_stateOrProvinceName, 0, ub_state_name },
{ "STREET", &asn1_oid_id_at_streetAddress, 0, 0 }, /* ENOTSUP */
{ "UID", &asn1_oid_id_Userid, 0, ub_numeric_user_id_length },
{ "emailAddress", &asn1_oid_id_pkcs9_emailAddress, 0, ub_emailaddress_length },
/* This is for DevID certificates and maybe others */
{ "serialNumber", &asn1_oid_id_at_serialNumber, 0, ub_serial_number },
/* These are for TPM 2.0 Endorsement Key Certificates (EKCerts) */
{ "TPMManufacturer", &asn1_oid_tcg_at_tpmManufacturer, 0, ub_emailaddress_length },
{ "TPMModel", &asn1_oid_tcg_at_tpmModel, 0, ub_emailaddress_length },
{ "TPMVersion", &asn1_oid_tcg_at_tpmVersion, 0, ub_emailaddress_length },
};
static char *
quote_string(const char *f, size_t len, int flags, size_t *rlen)
{
size_t i, j, tolen;
const unsigned char *from = (const unsigned char *)f;
unsigned char *to;
tolen = len * 3 + 1;
to = malloc(tolen);
if (to == NULL)
return NULL;
for (i = 0, j = 0; i < len; i++) {
unsigned char map = char_map[from[i]] & flags;
if (i == 0 && (map & Q_RFC2253_QUOTE_FIRST)) {
to[j++] = '\\';
to[j++] = from[i];
} else if ((i + 1) == len && (map & Q_RFC2253_QUOTE_LAST)) {
to[j++] = '\\';
to[j++] = from[i];
} else if (map & Q_RFC2253_QUOTE) {
to[j++] = '\\';
to[j++] = from[i];
} else if (map & Q_RFC2253_HEX) {
int l = snprintf((char *)&to[j], tolen - j - 1,
"#%02x", (unsigned char)from[i]);
j += l;
} else {
to[j++] = from[i];
}
}
to[j] = '\0';
assert(j < tolen);
*rlen = j;
return (char *)to;
}
static int
append_string(char **str, size_t *total_len, const char *ss,
size_t len, int quote)
{
char *s, *qs;
if (quote)
qs = quote_string(ss, len, Q_RFC2253, &len);
else
qs = rk_UNCONST(ss);
s = realloc(*str, len + *total_len + 1);
if (s == NULL)
_hx509_abort("allocation failure"); /* XXX */
memcpy(s + *total_len, qs, len);
if (qs != ss)
free(qs);
s[*total_len + len] = '\0';
*str = s;
*total_len += len;
return 0;
}
static char *
oidtostring(const heim_oid *type)
{
char *s;
size_t i;
for (i = 0; i < sizeof(no)/sizeof(no[0]); i++) {
if (der_heim_oid_cmp(no[i].o, type) == 0)
return strdup(no[i].n);
}
if (der_print_heim_oid(type, '.', &s) != 0)
return NULL;
return s;
}
static size_t
oidtomaxlen(const heim_oid *type)
{
size_t i;
for (i = 0; i < sizeof(no)/sizeof(no[0]); i++) {
if (der_heim_oid_cmp(no[i].o, type) == 0)
return no[i].max_bytes;
}
return 0;
}
static int
stringtooid(const char *name, size_t len, heim_oid *oid)
{
int ret;
size_t i;
char *s;
memset(oid, 0, sizeof(*oid));
for (i = 0; i < sizeof(no)/sizeof(no[0]); i++) {
if (strncasecmp(no[i].n, name, len) == 0)
return der_copy_oid(no[i].o, oid);
}
s = malloc(len + 1);
if (s == NULL)
return ENOMEM;
memcpy(s, name, len);
s[len] = '\0';
ret = der_parse_heim_oid(s, ".", oid);
free(s);
return ret;
}
/**
* Convert the hx509 name object into a printable string.
* The resulting string should be freed with free().
*
* @param name name to print
* @param str the string to return
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_name_to_string(const hx509_name name, char **str)
{
return _hx509_Name_to_string(&name->der_name, str);
}
HX509_LIB_FUNCTION int HX509_LIB_CALL
_hx509_Name_to_string(const Name *n, char **str)
{
size_t total_len = 0;
size_t i, j, m;
int ret;
*str = strdup("");
if (*str == NULL)
return ENOMEM;
for (m = n->u.rdnSequence.len; m > 0; m--) {
size_t len;
i = m - 1;
for (j = 0; j < n->u.rdnSequence.val[i].len; j++) {
DirectoryString *ds = &n->u.rdnSequence.val[i].val[j].value;
char *oidname;
char *ss;
oidname = oidtostring(&n->u.rdnSequence.val[i].val[j].type);
switch(ds->element) {
case choice_DirectoryString_ia5String:
ss = ds->u.ia5String.data;
len = ds->u.ia5String.length;
break;
case choice_DirectoryString_printableString:
ss = ds->u.printableString.data;
len = ds->u.printableString.length;
break;
case choice_DirectoryString_utf8String:
ss = ds->u.utf8String;
len = strlen(ss);
break;
case choice_DirectoryString_bmpString: {
const uint16_t *bmp = ds->u.bmpString.data;
size_t bmplen = ds->u.bmpString.length;
size_t k;
ret = wind_ucs2utf8_length(bmp, bmplen, &k);
if (ret) {
free(oidname);
free(*str);
*str = NULL;
return ret;
}
ss = malloc(k + 1);
if (ss == NULL)
_hx509_abort("allocation failure"); /* XXX */
ret = wind_ucs2utf8(bmp, bmplen, ss, NULL);
if (ret) {
free(oidname);
free(ss);
free(*str);
*str = NULL;
return ret;
}
ss[k] = '\0';
len = k;
break;
}
case choice_DirectoryString_teletexString:
ss = ds->u.teletexString;
len = strlen(ss);
break;
case choice_DirectoryString_universalString: {
const uint32_t *uni = ds->u.universalString.data;
size_t unilen = ds->u.universalString.length;
size_t k;
ret = wind_ucs4utf8_length(uni, unilen, &k);
if (ret) {
free(oidname);
free(*str);
*str = NULL;
return ret;
}
ss = malloc(k + 1);
if (ss == NULL)
_hx509_abort("allocation failure"); /* XXX */
ret = wind_ucs4utf8(uni, unilen, ss, NULL);
if (ret) {
free(ss);
free(oidname);
free(*str);
*str = NULL;
return ret;
}
ss[k] = '\0';
len = k;
break;
}
default:
_hx509_abort("unknown directory type: %d", ds->element);
exit(1);
}
append_string(str, &total_len, oidname, strlen(oidname), 0);
free(oidname);
append_string(str, &total_len, "=", 1, 0);
append_string(str, &total_len, ss, len, 1);
if (ds->element == choice_DirectoryString_bmpString ||
ds->element == choice_DirectoryString_universalString)
{
free(ss);
}
if (j + 1 < n->u.rdnSequence.val[i].len)
append_string(str, &total_len, "+", 1, 0);
}
if (i > 0)
append_string(str, &total_len, ",", 1, 0);
}
return 0;
}
#define COPYCHARARRAY(_ds,_el,_l,_n) \
(_l) = strlen(_ds->u._el); \
(_n) = malloc((_l) * sizeof((_n)[0])); \
if ((_n) == NULL) \
return ENOMEM; \
for (i = 0; i < (_l); i++) \
(_n)[i] = _ds->u._el[i]
#define COPYVALARRAY(_ds,_el,_l,_n) \
(_l) = _ds->u._el.length; \
(_n) = malloc((_l) * sizeof((_n)[0])); \
if ((_n) == NULL) \
return ENOMEM; \
for (i = 0; i < (_l); i++) \
(_n)[i] = _ds->u._el.data[i]
#define COPYVOIDARRAY(_ds,_el,_l,_n) \
(_l) = _ds->u._el.length; \
(_n) = malloc((_l) * sizeof((_n)[0])); \
if ((_n) == NULL) \
return ENOMEM; \
for (i = 0; i < (_l); i++) \
(_n)[i] = ((unsigned char *)_ds->u._el.data)[i]
static int
dsstringprep(const DirectoryString *ds, uint32_t **rname, size_t *rlen)
{
wind_profile_flags flags;
size_t i, len;
int ret = 0;
uint32_t *name;
*rname = NULL;
*rlen = 0;
switch(ds->element) {
case choice_DirectoryString_ia5String:
flags = WIND_PROFILE_LDAP;
COPYVOIDARRAY(ds, ia5String, len, name);
break;
case choice_DirectoryString_printableString:
flags = WIND_PROFILE_LDAP;
flags |= WIND_PROFILE_LDAP_CASE_EXACT_ATTRIBUTE;
COPYVOIDARRAY(ds, printableString, len, name);
break;
case choice_DirectoryString_teletexString:
flags = WIND_PROFILE_LDAP_CASE;
COPYCHARARRAY(ds, teletexString, len, name);
break;
case choice_DirectoryString_bmpString:
flags = WIND_PROFILE_LDAP;
COPYVALARRAY(ds, bmpString, len, name);
break;
case choice_DirectoryString_universalString:
flags = WIND_PROFILE_LDAP;
COPYVALARRAY(ds, universalString, len, name);
break;
case choice_DirectoryString_utf8String:
flags = WIND_PROFILE_LDAP;
ret = wind_utf8ucs4_length(ds->u.utf8String, &len);
if (ret)
return ret;
name = malloc(len * sizeof(name[0]));
if (name == NULL)
return ENOMEM;
ret = wind_utf8ucs4(ds->u.utf8String, name, &len);
if (ret) {
free(name);
return ret;
}
break;
default:
_hx509_abort("unknown directory type: %d", ds->element);
}
*rlen = len;
/* try a couple of times to get the length right, XXX gross */
for (i = 0; i < 4; i++) {
*rlen = *rlen * 2;
if ((*rname = malloc(*rlen * sizeof((*rname)[0]))) == NULL) {
ret = ENOMEM;
break;
}
ret = wind_stringprep(name, len, *rname, rlen, flags);
if (ret == WIND_ERR_OVERRUN) {
free(*rname);
*rname = NULL;
continue;
} else
break;
}
free(name);
if (ret) {
if (*rname)
free(*rname);
*rname = NULL;
*rlen = 0;
return ret;
}
return 0;
}
HX509_LIB_FUNCTION int HX509_LIB_CALL
_hx509_name_ds_cmp(const DirectoryString *ds1,
const DirectoryString *ds2,
int *diff)
{
uint32_t *ds1lp, *ds2lp;
size_t ds1len, ds2len, i;
int ret;
ret = dsstringprep(ds1, &ds1lp, &ds1len);
if (ret)
return ret;
ret = dsstringprep(ds2, &ds2lp, &ds2len);
if (ret) {
free(ds1lp);
return ret;
}
if (ds1len != ds2len)
*diff = ds1len - ds2len;
else {
for (i = 0; i < ds1len; i++) {
*diff = ds1lp[i] - ds2lp[i];
if (*diff)
break;
}
}
free(ds1lp);
free(ds2lp);
return 0;
}
HX509_LIB_FUNCTION int HX509_LIB_CALL
_hx509_name_cmp(const Name *n1, const Name *n2, int *c)
{
int ret;
size_t i, j;
*c = n1->u.rdnSequence.len - n2->u.rdnSequence.len;
if (*c)
return 0;
for (i = 0 ; i < n1->u.rdnSequence.len; i++) {
*c = n1->u.rdnSequence.val[i].len - n2->u.rdnSequence.val[i].len;
if (*c)
return 0;
for (j = 0; j < n1->u.rdnSequence.val[i].len; j++) {
*c = der_heim_oid_cmp(&n1->u.rdnSequence.val[i].val[j].type,
&n1->u.rdnSequence.val[i].val[j].type);
if (*c)
return 0;
ret = _hx509_name_ds_cmp(&n1->u.rdnSequence.val[i].val[j].value,
&n2->u.rdnSequence.val[i].val[j].value,
c);
if (ret)
return ret;
if (*c)
return 0;
}
}
*c = 0;
return 0;
}
/**
* Compare to hx509 name object, useful for sorting.
*
* @param n1 a hx509 name object.
* @param n2 a hx509 name object.
*
* @return 0 the objects are the same, returns > 0 is n2 is "larger"
* then n2, < 0 if n1 is "smaller" then n2.
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_name_cmp(hx509_name n1, hx509_name n2)
{
int ret, diff;
ret = _hx509_name_cmp(&n1->der_name, &n2->der_name, &diff);
if (ret)
return ret;
return diff;
}
HX509_LIB_FUNCTION int HX509_LIB_CALL
_hx509_name_from_Name(const Name *n, hx509_name *name)
{
int ret;
*name = calloc(1, sizeof(**name));
if (*name == NULL)
return ENOMEM;
ret = copy_Name(n, &(*name)->der_name);
if (ret) {
free(*name);
*name = NULL;
}
return ret;
}
HX509_LIB_FUNCTION int HX509_LIB_CALL
_hx509_name_modify(hx509_context context,
Name *name,
int append,
const heim_oid *oid,
const char *str)
{
RelativeDistinguishedName rdn;
size_t max_len = oidtomaxlen(oid);
int ret;
/*
* Check string length upper bounds.
*
* Because we don't have these bounds in our copy of the PKIX ASN.1 module,
* and because we might like to catch these early anyways, we enforce them
* here.
*/
if (max_len && strlen(str) > max_len) {
const char *a = oidtostring(oid);
ret = HX509_PARSING_NAME_FAILED;
hx509_set_error_string(context, 0, ret, "RDN attribute %s value too "
"long (max %llu): %s", a ? a : "<unknown>",
max_len, str);
return ret;
}
memset(&rdn, 0, sizeof(rdn));
if ((rdn.val = malloc(sizeof(rdn.val[0]))) == NULL) {
hx509_set_error_string(context, 0, ENOMEM, "Out of memory");
return ENOMEM;
}
rdn.len = 1;
rdn.val[0].value.element = choice_DirectoryString_utf8String;
if ((rdn.val[0].value.u.utf8String = strdup(str)) == NULL ||
(ret = der_copy_oid(oid, &rdn.val[0].type))) {
hx509_set_error_string(context, 0, ENOMEM, "Out of memory");
free(rdn.val[0].value.u.utf8String);
free(rdn.val);
return ENOMEM;
}
/* Append RDN. If the caller wanted to prepend instead, we'll rotate. */
ret = add_RDNSequence(&name->u.rdnSequence, &rdn);
free_RelativeDistinguishedName(&rdn);
if (ret || append || name->u.rdnSequence.len < 2)
return ret;
/* Rotate */
rdn = name->u.rdnSequence.val[name->u.rdnSequence.len - 1];
memmove(&name->u.rdnSequence.val[1],
&name->u.rdnSequence.val[0],
(name->u.rdnSequence.len - 1) *
sizeof(name->u.rdnSequence.val[0]));
name->u.rdnSequence.val[0] = rdn;
return 0;
}
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_empty_name(hx509_context context, hx509_name *name)
{
if ((*name = calloc(1, sizeof(**name))) == NULL) {
hx509_set_error_string(context, 0, ENOMEM, "out of memory");
return ENOMEM;
}
(*name)->der_name.element = choice_Name_rdnSequence;
(*name)->der_name.u.rdnSequence.val = 0;
(*name)->der_name.u.rdnSequence.len = 0;
return 0;
}
/**
* Parse a string into a hx509 name object.
*
* @param context A hx509 context.
* @param str a string to parse.
* @param name the resulting object, NULL in case of error.
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_parse_name(hx509_context context, const char *str, hx509_name *name)
{
const char *p, *q;
size_t len;
hx509_name n;
int ret;
*name = NULL;
n = calloc(1, sizeof(*n));
if (n == NULL) {
hx509_set_error_string(context, 0, ENOMEM, "out of memory");
return ENOMEM;
}
n->der_name.element = choice_Name_rdnSequence;
p = str;
while (p != NULL && *p != '\0') {
heim_oid oid;
int last;
q = strchr(p, ',');
if (q) {
len = (q - p);
last = 1;
} else {
len = strlen(p);
last = 0;
}
q = strchr(p, '=');
if (q == NULL) {
ret = HX509_PARSING_NAME_FAILED;
hx509_set_error_string(context, 0, ret, "missing = in %s", p);
goto out;
}
if (q == p) {
ret = HX509_PARSING_NAME_FAILED;
hx509_set_error_string(context, 0, ret,
"missing name before = in %s", p);
goto out;
}
if ((size_t)(q - p) > len) {
ret = HX509_PARSING_NAME_FAILED;
hx509_set_error_string(context, 0, ret, " = after , in %s", p);
goto out;
}
ret = stringtooid(p, q - p, &oid);
if (ret) {
ret = HX509_PARSING_NAME_FAILED;
hx509_set_error_string(context, 0, ret,
"unknown type: %.*s", (int)(q - p), p);
goto out;
}
{
size_t pstr_len = len - (q - p) - 1;
const char *pstr = p + (q - p) + 1;
char *r;
r = malloc(pstr_len + 1);
if (r == NULL) {
der_free_oid(&oid);
ret = ENOMEM;
hx509_set_error_string(context, 0, ret, "out of memory");
goto out;
}
memcpy(r, pstr, pstr_len);
r[pstr_len] = '\0';
ret = _hx509_name_modify(context, &n->der_name, 0, &oid, r);
free(r);
der_free_oid(&oid);
if(ret)
goto out;
}
p += len + last;
}
*name = n;
return 0;
out:
hx509_name_free(&n);
return HX509_NAME_MALFORMED;
}
/**
* Copy a hx509 name object.
*
* @param context A hx509 cotext.
* @param from the name to copy from
* @param to the name to copy to
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_name_copy(hx509_context context, const hx509_name from, hx509_name *to)
{
int ret;
*to = calloc(1, sizeof(**to));
if (*to == NULL)
return ENOMEM;
ret = copy_Name(&from->der_name, &(*to)->der_name);
if (ret) {
free(*to);
*to = NULL;
return ENOMEM;
}
return 0;
}
/**
* Convert a hx509_name into a Name.
*
* @param from the name to copy from
* @param to the name to copy to
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_name_to_Name(const hx509_name from, Name *to)
{
return copy_Name(&from->der_name, to);
}
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_name_normalize(hx509_context context, hx509_name name)
{
return 0;
}
/**
* Expands variables in the name using env. Variables are on the form
* ${name}. Useful when dealing with certificate templates.
*
* @param context A hx509 cotext.
* @param name the name to expand.
* @param env environment variable to expand.
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_name_expand(hx509_context context,
hx509_name name,
hx509_env env)
{
Name *n = &name->der_name;
size_t i, j;
int bounds_check = 1;
if (env == NULL)
return 0;
if (n->element != choice_Name_rdnSequence) {
hx509_set_error_string(context, 0, EINVAL, "RDN not of supported type");
return EINVAL;
}
for (i = 0 ; i < n->u.rdnSequence.len; i++) {
for (j = 0; j < n->u.rdnSequence.val[i].len; j++) {
/** Only UTF8String rdnSequence names are allowed */
/*
THIS SHOULD REALLY BE:
COMP = n->u.rdnSequence.val[i].val[j];
normalize COMP to utf8
check if there are variables
expand variables
convert back to orignal format, store in COMP
free normalized utf8 string
*/
DirectoryString *ds = &n->u.rdnSequence.val[i].val[j].value;
heim_oid *type = &n->u.rdnSequence.val[i].val[j].type;
char *p, *p2;
struct rk_strpool *strpool = NULL;
if (ds->element != choice_DirectoryString_utf8String) {
hx509_set_error_string(context, 0, EINVAL, "unsupported type");
return EINVAL;
}
p = strstr(ds->u.utf8String, "${");
if (p) {
strpool = rk_strpoolprintf(strpool, "%.*s",
(int)(p - ds->u.utf8String),
ds->u.utf8String);
if (strpool == NULL) {
hx509_set_error_string(context, 0, ENOMEM, "out of memory");
return ENOMEM;
}
}
while (p != NULL) {
/* expand variables */
const char *value;
p2 = strchr(p, '}');
if (p2 == NULL) {
hx509_set_error_string(context, 0, EINVAL, "missing }");
rk_strpoolfree(strpool);
return EINVAL;
}
p += 2;
value = hx509_env_lfind(context, env, p, p2 - p);
if (value == NULL) {
hx509_set_error_string(context, 0, EINVAL,
"variable %.*s missing",
(int)(p2 - p), p);
rk_strpoolfree(strpool);
return EINVAL;
}
strpool = rk_strpoolprintf(strpool, "%s", value);
if (strpool == NULL) {
hx509_set_error_string(context, 0, ENOMEM, "out of memory");
return ENOMEM;
}
p2++;
p = strstr(p2, "${");
if (p)
strpool = rk_strpoolprintf(strpool, "%.*s",
(int)(p - p2), p2);
else
strpool = rk_strpoolprintf(strpool, "%s", p2);
if (strpool == NULL) {
hx509_set_error_string(context, 0, ENOMEM, "out of memory");
return ENOMEM;
}
}
if (strpool) {
size_t max_bytes;
free(ds->u.utf8String);
ds->u.utf8String = rk_strpoolcollect(strpool);
if (ds->u.utf8String == NULL) {
hx509_set_error_string(context, 0, ENOMEM, "out of memory");
return ENOMEM;
}
/* Check upper bounds! */
if ((max_bytes = oidtomaxlen(type)) &&
strlen(ds->u.utf8String) > max_bytes)
bounds_check = 0;
}
}
}
if (!bounds_check) {
hx509_set_error_string(context, 0, HX509_PARSING_NAME_FAILED,
"some expanded RDNs are too long");
return HX509_PARSING_NAME_FAILED;
}
return 0;
}
/**
* Free a hx509 name object, upond return *name will be NULL.
*
* @param name a hx509 name object to be freed.
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION void HX509_LIB_CALL
hx509_name_free(hx509_name *name)
{
free_Name(&(*name)->der_name);
memset(*name, 0, sizeof(**name));
free(*name);
*name = NULL;
}
/**
* Convert a DER encoded name info a string.
*
* @param data data to a DER/BER encoded name
* @param length length of data
* @param str the resulting string, is NULL on failure.
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_unparse_der_name(const void *data, size_t length, char **str)
{
Name name;
int ret;
*str = NULL;
ret = decode_Name(data, length, &name, NULL);
if (ret)
return ret;
ret = _hx509_Name_to_string(&name, str);
free_Name(&name);
return ret;
}
/**
* Convert a hx509_name object to DER encoded name.
*
* @param name name to concert
* @param os data to a DER encoded name, free the resulting octet
* string with hx509_xfree(os->data).
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_name_binary(const hx509_name name, heim_octet_string *os)
{
size_t size;
int ret;
ASN1_MALLOC_ENCODE(Name, os->data, os->length, &name->der_name, &size, ret);
if (ret)
return ret;
if (os->length != size)
_hx509_abort("internal ASN.1 encoder error");
return 0;
}
HX509_LIB_FUNCTION int HX509_LIB_CALL
_hx509_unparse_Name(const Name *aname, char **str)
{
hx509_name name;
int ret;
ret = _hx509_name_from_Name(aname, &name);
if (ret)
return ret;
ret = hx509_name_to_string(name, str);
hx509_name_free(&name);
return ret;
}
/**
* Check if a name is empty.
*
* @param name the name to check if its empty/null.
*
* @return non zero if the name is empty/null.
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_name_is_null_p(const hx509_name name)
{
return name->der_name.element == choice_Name_rdnSequence &&
name->der_name.u.rdnSequence.len == 0;
}
int
_hx509_unparse_PermanentIdentifier(hx509_context context,
struct rk_strpool **strpool,
heim_any *value)
{
PermanentIdentifier pi;
size_t len;
const char *pid = "";
char *s = NULL;
int ret;
ret = decode_PermanentIdentifier(value->data, value->length, &pi, &len);
if (ret == 0 && pi.assigner &&
der_print_heim_oid(pi.assigner, '.', &s) != 0)
ret = hx509_enomem(context);
if (pi.identifierValue && *pi.identifierValue)
pid = *pi.identifierValue;
if (ret == 0 &&
(*strpool = rk_strpoolprintf(*strpool, "%s:%s", s ? s : "", pid)) == NULL)
ret = hx509_enomem(context);
free_PermanentIdentifier(&pi);
free(s);
if (ret) {
rk_strpoolfree(*strpool);
*strpool = rk_strpoolprintf(NULL,
"<error-decoding-PermanentIdentifier");
hx509_set_error_string(context, 0, ret,
"Failed to decode PermanentIdentifier");
}
return ret;
}
int
_hx509_unparse_HardwareModuleName(hx509_context context,
struct rk_strpool **strpool,
heim_any *value)
{
HardwareModuleName hm;
size_t len;
char *s = NULL;
int ret;
ret = decode_HardwareModuleName(value->data, value->length, &hm, &len);
if (ret == 0 && hm.hwSerialNum.length > 256)
hm.hwSerialNum.length = 256;
if (ret == 0)
ret = der_print_heim_oid(&hm.hwType, '.', &s);
if (ret == 0) {
*strpool = rk_strpoolprintf(*strpool, "%s:%.*s%s", s,
(int)hm.hwSerialNum.length,
(char *)hm.hwSerialNum.data,
value->length == len ? "" : ", <garbage>");
if (*strpool == NULL)
ret = hx509_enomem(context);
}
free_HardwareModuleName(&hm);
free(s);
if (ret) {
rk_strpoolfree(*strpool);
*strpool = rk_strpoolprintf(NULL,
"<error-decoding-HardwareModuleName");
hx509_set_error_string(context, 0, ret,
"Failed to decode HardwareModuleName");
}
return ret;
}
/*
* 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.
*/
int
_hx509_unparse_KRB5PrincipalName(hx509_context context,
struct rk_strpool **strpool,
heim_any *value)
{
KRB5PrincipalName kn;
size_t len;
int ret;
ret = decode_KRB5PrincipalName(value->data, value->length, &kn, &len);
if (ret == 0 &&
(*strpool = _hx509_unparse_kerberos_name(*strpool, &kn)) == NULL)
ret = hx509_enomem(context);
free_KRB5PrincipalName(&kn);
if (ret == 0 && (value->length != len) &&
(*strpool = rk_strpoolprintf(*strpool, " <garbage>")) == NULL)
ret = hx509_enomem(context);
if (ret) {
rk_strpoolfree(*strpool);
*strpool = rk_strpoolprintf(NULL,
"<error-decoding-PrincipalName");
hx509_set_error_string(context, 0, ret,
"Failed to decode PermanentIdentifier");
}
return ret;
}
struct rk_strpool *
_hx509_unparse_kerberos_name(struct rk_strpool *strpool, KRB5PrincipalName *kn)
{
static const char comp_quotable_chars[] = " \n\t\b\\/@";
static const char realm_quotable_chars[] = " \n\t\b\\@";
const char *s;
size_t i, k, len, plen;
int need_slash = 0;
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;
}
}
}
if (!kn->realm)
return strpool;
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;
}
}
return strpool;
}
int
_hx509_unparse_utf8_string_name(hx509_context context,
struct rk_strpool **strpool,
heim_any *value)
{
PKIXXmppAddr us;
size_t size;
int ret;
ret = decode_PKIXXmppAddr(value->data, value->length, &us, &size);
if (ret == 0 &&
(*strpool = rk_strpoolprintf(*strpool, "%s", us)) == NULL)
ret = hx509_enomem(context);
if (ret) {
rk_strpoolfree(*strpool);
*strpool = rk_strpoolprintf(NULL,
"<error-decoding-UTF8String-SAN>");
hx509_set_error_string(context, 0, ret,
"Failed to decode UTF8String SAN");
}
free_PKIXXmppAddr(&us);
return ret;
}
int
_hx509_unparse_ia5_string_name(hx509_context context,
struct rk_strpool **strpool,
heim_any *value)
{
SRVName us;
size_t size;
int ret;
ret = decode_SRVName(value->data, value->length, &us, &size);
if (ret == 0) {
rk_strpoolfree(*strpool);
*strpool = rk_strpoolprintf(NULL,
"<error-decoding-IA5String-SAN>");
hx509_set_error_string(context, 0, ret,
"Failed to decode UTF8String SAN");
return ret;
}
*strpool = rk_strpoolprintf(*strpool, "%.*s",
(int)us.length, (char *)us.data);
free_SRVName(&us);
return ret;
}
typedef int (*other_unparser_f)(hx509_context,
struct rk_strpool **,
heim_any *);
struct {
const heim_oid *oid;
const char *friendly_name;
other_unparser_f f;
} o_unparsers[] = {
{ &asn1_oid_id_pkinit_san,
"KerberosPrincipalName",
_hx509_unparse_KRB5PrincipalName },
{ &asn1_oid_id_pkix_on_permanentIdentifier,
"PermanentIdentifier",
_hx509_unparse_PermanentIdentifier },
{ &asn1_oid_id_on_hardwareModuleName,
"HardwareModuleName",
_hx509_unparse_HardwareModuleName },
{ &asn1_oid_id_pkix_on_xmppAddr,
"XMPPName",
_hx509_unparse_utf8_string_name },
{ &asn1_oid_id_pkinit_ms_san,
"MSFTKerberosPrincipalName",
_hx509_unparse_utf8_string_name },
{ &asn1_oid_id_pkix_on_dnsSRV,
"SRVName",
_hx509_unparse_ia5_string_name },
};
/**
* Unparse the hx509 name in name into a string.
*
* @param name the name to print
* @param str an allocated string returns the name in string form
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_general_name_unparse(GeneralName *name, char **str)
{
hx509_context context;
int ret;
if ((ret = hx509_context_init(&context)))
return ret;
return hx509_general_name_unparse2(context, name, str);
}
/**
* Unparse the hx509 name in name into a string.
*
* @param context hx509 library context
* @param name the name to print
* @param str an allocated string returns the name in string form
*
* @return An hx509 error code, see hx509_get_error_string().
*
* @ingroup hx509_name
*/
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_general_name_unparse2(hx509_context context,
GeneralName *name,
char **str)
{
struct rk_strpool *strpool = NULL;
int ret = 0;
*str = NULL;
switch (name->element) {
case choice_GeneralName_otherName: {
size_t i;
char *oid;
ret = hx509_oid_sprint(&name->u.otherName.type_id, &oid);
if (ret == 0)
strpool = rk_strpoolprintf(strpool, "otherName: %s ", oid);
if (strpool == NULL)
ret = ENOMEM;
for (i = 0; ret == 0 && i < sizeof(o_unparsers)/sizeof(o_unparsers[0]); i++) {
if (der_heim_oid_cmp(&name->u.otherName.type_id,
o_unparsers[i].oid))
continue;
strpool = rk_strpoolprintf(strpool, "%s ",o_unparsers[i].friendly_name);
if (strpool == NULL)
ret = ENOMEM;
if (ret == 0)
ret = o_unparsers[i].f(context, &strpool, &name->u.otherName.value);
break;
}
if (ret == 0 && i == sizeof(o_unparsers)/sizeof(o_unparsers[0])) {
strpool = rk_strpoolprintf(strpool, "<unknown-other-name-type>");
ret = ENOTSUP;
}
free(oid);
break;
}
case choice_GeneralName_rfc822Name:
strpool = rk_strpoolprintf(strpool, "rfc822Name: %.*s",
(int)name->u.rfc822Name.length,
(char *)name->u.rfc822Name.data);
break;
case choice_GeneralName_dNSName:
strpool = rk_strpoolprintf(strpool, "dNSName: %.*s",
(int)name->u.dNSName.length,
(char *)name->u.dNSName.data);
break;
case choice_GeneralName_directoryName: {
Name dir;
char *s;
memset(&dir, 0, sizeof(dir));
dir.element = (enum Name_enum)name->u.directoryName.element;
dir.u.rdnSequence = name->u.directoryName.u.rdnSequence;
ret = _hx509_unparse_Name(&dir, &s);
if (ret)
return ret;
strpool = rk_strpoolprintf(strpool, "directoryName: %s", s);
free(s);
break;
}
case choice_GeneralName_uniformResourceIdentifier:
strpool = rk_strpoolprintf(strpool, "URI: %.*s",
(int)name->u.uniformResourceIdentifier.length,
(char *)name->u.uniformResourceIdentifier.data);
break;
case choice_GeneralName_iPAddress: {
unsigned char *a = name->u.iPAddress.data;
strpool = rk_strpoolprintf(strpool, "IPAddress: ");
if (strpool == NULL)
break;
if (name->u.iPAddress.length == 4)
strpool = rk_strpoolprintf(strpool, "%d.%d.%d.%d",
a[0], a[1], a[2], a[3]);
else if (name->u.iPAddress.length == 16)
strpool = rk_strpoolprintf(strpool,
"%02X:%02X:%02X:%02X:"
"%02X:%02X:%02X:%02X:"
"%02X:%02X:%02X:%02X:"
"%02X:%02X:%02X:%02X",
a[0], a[1], a[2], a[3],
a[4], a[5], a[6], a[7],
a[8], a[9], a[10], a[11],
a[12], a[13], a[14], a[15]);
else
strpool = rk_strpoolprintf(strpool,
"unknown IP address of length %lu",
(unsigned long)name->u.iPAddress.length);
break;
}
case choice_GeneralName_registeredID: {
char *oid;
hx509_oid_sprint(&name->u.registeredID, &oid);
if (oid == NULL)
return ENOMEM;
strpool = rk_strpoolprintf(strpool, "registeredID: %s", oid);
free(oid);
break;
}
default:
return EINVAL;
}
if (strpool == NULL ||
(*str = rk_strpoolcollect(strpool)) == NULL)
return ENOMEM;
return 0;
}