/* * Copyright (C) 2013-2016 Nikos Mavrogiannopoulos * Copyright (C) 2016 Red Hat, Inc. * * This file is part of GnuTLS. * * The GnuTLS is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see * */ /* This file contains functions to handle X.509 certificate generation. */ #include "gnutls_int.h" #include #include #include "errors.h" #include #include #include #include typedef int (*set_dn_func) (void *, const char *oid, unsigned int raw_flag, const void *name, unsigned int name_size); static int dn_attr_crt_set(set_dn_func f, void *crt, const gnutls_datum_t * name, const gnutls_datum_t * val, unsigned is_raw) { char _oid[MAX_OID_SIZE]; gnutls_datum_t tmp; const char *oid; int ret; unsigned i,j; if (name->size == 0 || val->size == 0) return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); if (c_isdigit(name->data[0]) != 0) { if (name->size >= sizeof(_oid)) return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); memcpy(_oid, name->data, name->size); _oid[name->size] = 0; oid = _oid; if (gnutls_x509_dn_oid_known(oid) == 0 && !is_raw) { _gnutls_debug_log("Unknown OID: '%s'\n", oid); return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); } } else { oid = _gnutls_ldap_string_to_oid((char *) name->data, name->size); } if (oid == NULL) { _gnutls_debug_log("Unknown DN attribute: '%.*s'\n", (int) name->size, name->data); return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); } if (is_raw) { gnutls_datum_t hex = {val->data+1, val->size-1}; ret = gnutls_hex_decode2(&hex, &tmp); if (ret < 0) return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); } else { tmp.size = val->size; tmp.data = gnutls_malloc(tmp.size+1); if (tmp.data == NULL) { return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); } /* unescape */ for (j=i=0;isize && val->data[j] == '\\') { if (val->data[j+1] == ',' || val->data[j+1] == '#' || val->data[j+1] == ' ' || val->data[j+1] == '+' || val->data[j+1] == '"' || val->data[j+1] == '<' || val->data[j+1] == '>' || val->data[j+1] == ';' || val->data[j+1] == '\\' || val->data[j+1] == '=') { tmp.data[i] = val->data[j+1]; j+=2; tmp.size--; } else { ret = gnutls_assert_val(GNUTLS_E_PARSING_ERROR); goto fail; } } else { tmp.data[i] = val->data[j++]; } } tmp.data[tmp.size] = 0; } ret = f(crt, oid, is_raw, tmp.data, tmp.size); if (ret < 0) { gnutls_assert(); goto fail; } ret = 0; fail: gnutls_free(tmp.data); return ret; } static int read_attr_and_val(const char **ptr, gnutls_datum_t *name, gnutls_datum_t *val, unsigned *is_raw) { const unsigned char *p = (void *) *ptr; *is_raw = 0; /* skip any space */ while (c_isspace(*p)) p++; /* Read the name */ name->data = (void *) p; while (*p != '=' && *p != 0 && !c_isspace(*p)) p++; name->size = p - name->data; /* skip any space */ while (c_isspace(*p)) p++; if (*p != '=') return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); p++; while (c_isspace(*p)) p++; if (*p == '#') { *is_raw = 1; } /* Read value */ val->data = (void *) p; while (*p != 0 && (*p != ',' || (*p == ',' && *(p - 1) == '\\')) && *p != '\n') { p++; } val->size = p - (val->data); *ptr = (void*)p; p = val->data; /* check for unescaped '+' - we do not support them */ while (*p != 0) { if (*p == '+' && (*(p - 1) != '\\')) return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); p++; } /* remove spaces from the end */ while(val->size > 0 && c_isspace(val->data[val->size-1])) { if (val->size-2 > 0 && val->data[val->size-2] == '\\') break; val->size--; } if (val->size == 0 || name->size == 0) return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); return 0; } typedef struct elem_list_st { gnutls_datum_t name; gnutls_datum_t val; const char *pos; unsigned is_raw; struct elem_list_st *next; } elem_list_st; static int add_new_elem(elem_list_st **head, const gnutls_datum_t *name, const gnutls_datum_t *val, const char *pos, unsigned is_raw) { elem_list_st *elem = gnutls_malloc(sizeof(*elem)); if (elem == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); memcpy(&elem->name, name, sizeof(*name)); memcpy(&elem->val, val, sizeof(*val)); elem->pos = pos; elem->is_raw = is_raw; elem->next = *head; *head = elem; return 0; } static int crt_set_dn(set_dn_func f, void *crt, const char *dn, const char **err) { const char *p = dn; int ret; gnutls_datum_t name, val; unsigned is_raw; elem_list_st *list = NULL, *plist, *next; if (crt == NULL || dn == NULL) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); /* We parse the string and set all elements to a linked list in * reverse order. That way we can encode in reverse order, * the way RFC4514 requires. */ /* For each element */ while (*p != 0 && *p != '\n') { if (err) *err = p; is_raw = 0; ret = read_attr_and_val(&p, &name, &val, &is_raw); if (ret < 0) { gnutls_assert(); goto fail; } /* skip spaces and look for comma */ while (c_isspace(*p)) p++; ret = add_new_elem(&list, &name, &val, p, is_raw); if (ret < 0) { gnutls_assert(); goto fail; } if (*p != ',' && *p != 0 && *p != '\n') { ret = gnutls_assert_val(GNUTLS_E_PARSING_ERROR); goto fail; } if (*p == ',') p++; } plist = list; while(plist) { if (err) *err = plist->pos; ret = dn_attr_crt_set(f, crt, &plist->name, &plist->val, plist->is_raw); if (ret < 0) goto fail; plist = plist->next; } ret = 0; fail: plist = list; while(plist) { next = plist->next; gnutls_free(plist); plist = next; } return ret; } /** * gnutls_x509_crt_set_dn: * @crt: a certificate of type #gnutls_x509_crt_t * @dn: a comma separated DN string (RFC4514) * @err: indicates the error position (if any) * * This function will set the DN on the provided certificate. * The input string should be plain ASCII or UTF-8 encoded. On * DN parsing error %GNUTLS_E_PARSING_ERROR is returned. * * Note that DNs are not expected to hold DNS information, and thus * no automatic IDNA conversions are attempted when using this function. * If that is required (e.g., store a domain in CN), process the corresponding * input with gnutls_idna_map(). * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. **/ int gnutls_x509_crt_set_dn(gnutls_x509_crt_t crt, const char *dn, const char **err) { return crt_set_dn((set_dn_func) gnutls_x509_crt_set_dn_by_oid, crt, dn, err); } /** * gnutls_x509_crt_set_issuer_dn: * @crt: a certificate of type #gnutls_x509_crt_t * @dn: a comma separated DN string (RFC4514) * @err: indicates the error position (if any) * * This function will set the DN on the provided certificate. * The input string should be plain ASCII or UTF-8 encoded. On * DN parsing error %GNUTLS_E_PARSING_ERROR is returned. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. **/ int gnutls_x509_crt_set_issuer_dn(gnutls_x509_crt_t crt, const char *dn, const char **err) { return crt_set_dn((set_dn_func) gnutls_x509_crt_set_issuer_dn_by_oid, crt, dn, err); } /** * gnutls_x509_crq_set_dn: * @crq: a certificate of type #gnutls_x509_crq_t * @dn: a comma separated DN string (RFC4514) * @err: indicates the error position (if any) * * This function will set the DN on the provided certificate. * The input string should be plain ASCII or UTF-8 encoded. On * DN parsing error %GNUTLS_E_PARSING_ERROR is returned. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. **/ int gnutls_x509_crq_set_dn(gnutls_x509_crq_t crq, const char *dn, const char **err) { return crt_set_dn((set_dn_func) gnutls_x509_crq_set_dn_by_oid, crq, dn, err); } static int set_dn_by_oid(gnutls_x509_dn_t dn, const char *oid, unsigned int raw_flag, const void *name, unsigned name_size) { return _gnutls_x509_set_dn_oid(dn->asn, "", oid, raw_flag, name, name_size); } /** * gnutls_x509_dn_set_str: * @dn: a pointer to DN * @str: a comma separated DN string (RFC4514) * @err: indicates the error position (if any) * * This function will set the DN on the provided DN structure. * The input string should be plain ASCII or UTF-8 encoded. On * DN parsing error %GNUTLS_E_PARSING_ERROR is returned. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.5.3 **/ int gnutls_x509_dn_set_str(gnutls_x509_dn_t dn, const char *str, const char **err) { if (dn == NULL) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } return crt_set_dn((set_dn_func) set_dn_by_oid, dn, str, err); } /** * gnutls_x509_dn_init: * @dn: the object to be initialized * * This function initializes a #gnutls_x509_dn_t type. * * The object returned must be deallocated using * gnutls_x509_dn_deinit(). * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 2.4.0 **/ int gnutls_x509_dn_init(gnutls_x509_dn_t * dn) { int result; *dn = gnutls_calloc(1, sizeof(gnutls_x509_dn_st)); if ((result = asn1_create_element(_gnutls_get_pkix(), "PKIX1.Name", &(*dn)->asn)) != ASN1_SUCCESS) { gnutls_assert(); gnutls_free(*dn); return _gnutls_asn2err(result); } return 0; } /** * gnutls_x509_dn_import: * @dn: the structure that will hold the imported DN * @data: should contain a DER encoded RDN sequence * * This function parses an RDN sequence and stores the result to a * #gnutls_x509_dn_t type. The data must have been initialized * with gnutls_x509_dn_init(). You may use gnutls_x509_dn_get_rdn_ava() to * decode the DN. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 2.4.0 **/ int gnutls_x509_dn_import(gnutls_x509_dn_t dn, const gnutls_datum_t * data) { int result; char err[ASN1_MAX_ERROR_DESCRIPTION_SIZE]; if (data->data == NULL || data->size == 0) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); result = _asn1_strict_der_decode(&dn->asn, data->data, data->size, err); if (result != ASN1_SUCCESS) { /* couldn't decode DER */ _gnutls_debug_log("ASN.1 Decoding error: %s\n", err); gnutls_assert(); return _gnutls_asn2err(result); } return 0; } /** * gnutls_x509_dn_deinit: * @dn: a DN uint8_t object pointer. * * This function deallocates the DN object as returned by * gnutls_x509_dn_import(). * * Since: 2.4.0 **/ void gnutls_x509_dn_deinit(gnutls_x509_dn_t dn) { asn1_delete_structure(&dn->asn); gnutls_free(dn); } /** * gnutls_x509_dn_export: * @dn: Holds the uint8_t DN object * @format: the format of output params. One of PEM or DER. * @output_data: will contain a DN PEM or DER encoded * @output_data_size: holds the size of output_data (and will be * replaced by the actual size of parameters) * * This function will export the DN to DER or PEM format. * * If the buffer provided is not long enough to hold the output, then * *@output_data_size is updated and %GNUTLS_E_SHORT_MEMORY_BUFFER * will be returned. * * If the structure is PEM encoded, it will have a header * of "BEGIN NAME". * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. **/ int gnutls_x509_dn_export(gnutls_x509_dn_t dn, gnutls_x509_crt_fmt_t format, void *output_data, size_t * output_data_size) { if (dn == NULL) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } return _gnutls_x509_export_int_named(dn->asn, "rdnSequence", format, "NAME", output_data, output_data_size); } /** * gnutls_x509_dn_export2: * @dn: Holds the uint8_t DN object * @format: the format of output params. One of PEM or DER. * @out: will contain a DN PEM or DER encoded * * This function will export the DN to DER or PEM format. * * The output buffer is allocated using gnutls_malloc(). * * If the structure is PEM encoded, it will have a header * of "BEGIN NAME". * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.1.3 **/ int gnutls_x509_dn_export2(gnutls_x509_dn_t dn, gnutls_x509_crt_fmt_t format, gnutls_datum_t * out) { if (dn == NULL) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } return _gnutls_x509_export_int_named2(dn->asn, "rdnSequence", format, "NAME", out); } /** * gnutls_x509_dn_get_rdn_ava: * @dn: a pointer to DN * @irdn: index of RDN * @iava: index of AVA. * @ava: Pointer to structure which will hold output information. * * Get pointers to data within the DN. The format of the @ava structure * is shown below. * * struct gnutls_x509_ava_st { * gnutls_datum_t oid; * gnutls_datum_t value; * unsigned long value_tag; * }; * * The X.509 distinguished name is a sequence of sequences of strings * and this is what the @irdn and @iava indexes model. * * Note that @ava will contain pointers into the @dn structure which * in turns points to the original certificate. Thus you should not * modify any data or deallocate any of those. * * This is a low-level function that requires the caller to do the * value conversions when necessary (e.g. from UCS-2). * * Returns: Returns 0 on success, or an error code. **/ int gnutls_x509_dn_get_rdn_ava(gnutls_x509_dn_t dn, int irdn, int iava, gnutls_x509_ava_st * ava) { ASN1_TYPE rdn, elem; ASN1_DATA_NODE vnode; long len; int lenlen, remlen, ret; char rbuf[MAX_NAME_SIZE]; unsigned char cls; const unsigned char *ptr; iava++; irdn++; /* 0->1, 1->2 etc */ snprintf(rbuf, sizeof(rbuf), "rdnSequence.?%d.?%d", irdn, iava); rdn = asn1_find_node(dn->asn, rbuf); if (!rdn) { gnutls_assert(); return GNUTLS_E_ASN1_ELEMENT_NOT_FOUND; } snprintf(rbuf, sizeof(rbuf), "?%d.type", iava); elem = asn1_find_node(rdn, rbuf); if (!elem) { gnutls_assert(); return GNUTLS_E_ASN1_ELEMENT_NOT_FOUND; } ret = asn1_read_node_value(elem, &vnode); if (ret != ASN1_SUCCESS) { gnutls_assert(); return GNUTLS_E_ASN1_ELEMENT_NOT_FOUND; } ava->oid.data = (void *) vnode.value; ava->oid.size = vnode.value_len; snprintf(rbuf, sizeof(rbuf), "?%d.value", iava); elem = asn1_find_node(rdn, rbuf); if (!elem) { gnutls_assert(); return GNUTLS_E_ASN1_ELEMENT_NOT_FOUND; } ret = asn1_read_node_value(elem, &vnode); if (ret != ASN1_SUCCESS) { gnutls_assert(); return GNUTLS_E_ASN1_ELEMENT_NOT_FOUND; } /* The value still has the previous tag's length bytes, plus the * current value's tag and length bytes. Decode them. */ ptr = vnode.value; remlen = vnode.value_len; len = asn1_get_length_der(ptr, remlen, &lenlen); if (len < 0) { gnutls_assert(); return GNUTLS_E_ASN1_DER_ERROR; } ptr += lenlen; remlen -= lenlen; ret = asn1_get_tag_der(ptr, remlen, &cls, &lenlen, &ava->value_tag); if (ret) { gnutls_assert(); return _gnutls_asn2err(ret); } ptr += lenlen; remlen -= lenlen; { signed long tmp; tmp = asn1_get_length_der(ptr, remlen, &lenlen); if (tmp < 0) { gnutls_assert(); return GNUTLS_E_ASN1_DER_ERROR; } ava->value.size = tmp; } ava->value.data = (void *) (ptr + lenlen); return 0; } /** * gnutls_x509_dn_get_str: * @dn: a pointer to DN * @str: a datum that will hold the name * * This function will allocate buffer and copy the name in the provided DN. * The name will be in the form "C=xxxx,O=yyyy,CN=zzzz" as * described in RFC4514. The output string will be ASCII or UTF-8 * encoded, depending on the certificate data. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.4.2 **/ int gnutls_x509_dn_get_str(gnutls_x509_dn_t dn, gnutls_datum_t *str) { if (dn == NULL) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } return _gnutls_x509_get_dn(dn->asn, "rdnSequence", str, GNUTLS_X509_DN_FLAG_COMPAT); } /** * gnutls_x509_dn_get_str: * @dn: a pointer to DN * @str: a datum that will hold the name * @flags: zero or %GNUTLS_X509_DN_FLAG_COMPAT * * This function will allocate buffer and copy the name in the provided DN. * The name will be in the form "C=xxxx,O=yyyy,CN=zzzz" as * described in RFC4514. The output string will be ASCII or UTF-8 * encoded, depending on the certificate data. * * When the flag %GNUTLS_X509_DN_FLAG_COMPAT is specified, the output * format will match the format output by previous to 3.5.6 versions of GnuTLS * which was not not fully RFC4514-compliant. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.5.7 **/ int gnutls_x509_dn_get_str2(gnutls_x509_dn_t dn, gnutls_datum_t *str, unsigned flags) { if (dn == NULL) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } return _gnutls_x509_get_dn(dn->asn, "rdnSequence", str, flags); }