summaryrefslogtreecommitdiffstats
path: root/lib/x509/x509_dn.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/x509/x509_dn.c')
-rw-r--r--lib/x509/x509_dn.c702
1 files changed, 702 insertions, 0 deletions
diff --git a/lib/x509/x509_dn.c b/lib/x509/x509_dn.c
new file mode 100644
index 0000000..1dde410
--- /dev/null
+++ b/lib/x509/x509_dn.c
@@ -0,0 +1,702 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>
+ *
+ */
+
+/* This file contains functions to handle X.509 certificate generation.
+ */
+
+#include "gnutls_int.h"
+
+#include <datum.h>
+#include <global.h>
+#include "errors.h"
+#include <common.h>
+#include <x509.h>
+#include <x509_b64.h>
+#include <c-ctype.h>
+
+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;i<tmp.size;i++) {
+ if (1+j!=val->size && 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 && 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_node rdn, elem;
+ asn1_data_node_st 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);
+}