From 31b05c3c255a48cd2f43dfb5ca070c37530da726 Mon Sep 17 00:00:00 2001 From: Martin Ukrop Date: Wed, 20 Jul 2016 14:52:00 +0200 Subject: x509: Fix DNS name constraints checking - If the intersection of name constraints of the given type was empty, the results allowed all names instead of none. - Fixed by adding an universal excluded name constraint in case the intersection for the particular type is empty. - Moved the logic of creating a name constraint node copy from _gnutls_name_constraints_intersect to name_constraints_intersect_nodes (previously name_constraints_match), as intersecting IP addresses will require further processing (not just taking one of the compared nodes as was the implementation till now). - GNUTLS_SAN_MAX added in order to comfortably iterate over SAN type enum. --- lib/includes/gnutls/gnutls.h.in | 1 + lib/x509/name_constraints.c | 151 ++++++++++++++++++++++++++++++---------- 2 files changed, 115 insertions(+), 37 deletions(-) diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index db00bb278a..a2932e740c 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -2140,6 +2140,7 @@ typedef enum gnutls_x509_subject_alt_name_t { GNUTLS_SAN_IPADDRESS = 4, GNUTLS_SAN_OTHERNAME = 5, GNUTLS_SAN_DN = 6, + GNUTLS_SAN_MAX = GNUTLS_SAN_DN, /* The following are "virtual" subject alternative name types, in that they are represented by an otherName value and an OID. Used by gnutls_x509_crt_get_subject_alt_othername_oid. */ diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c index 6d2e187155..6e6ddf175e 100644 --- a/lib/x509/name_constraints.c +++ b/lib/x509/name_constraints.c @@ -2,6 +2,8 @@ * Copyright (C) 2014-2016 Free Software Foundation, Inc. * Copyright (C) 2016 Red Hat, Inc. * + * Authors: Nikos Mavrogiannopoulos, Daiki Ueno, Martin Ukrop + * * This file is part of GnuTLS. * * The GnuTLS is free software; you can redistribute it and/or @@ -36,8 +38,10 @@ /* Name constraints is limited to DNS names. */ -static int name_constraints_match(name_constraints_node_st * nc1, - name_constraints_node_st * nc2); +// for documentation see the implementation +static int name_constraints_intersect_nodes(name_constraints_node_st * nc1, + name_constraints_node_st * nc2, + name_constraints_node_st ** intersection); static unsigned is_nc_empty(struct gnutls_name_constraints_st* nc, unsigned type) { @@ -148,23 +152,39 @@ _gnutls_name_constraints_node_free(name_constraints_node_st *node) static int _gnutls_name_constraints_intersect(name_constraints_node_st ** _nc, - name_constraints_node_st * _nc2) + name_constraints_node_st * _nc2, + name_constraints_node_st ** _nc_excluded) { - name_constraints_node_st *nc, *nc2, *t, *dest = NULL, *prev = NULL; + name_constraints_node_st *nc, *nc2, *t, *tmp, *dest = NULL, *prev = NULL; int ret; + /* temporary array to see, if we need to add universal excluded constraints + * (see phase 3 for details) + * indexed directly by (gnutls_x509_subject_alt_name_t enum - 1) */ + unsigned char types_with_empty_intersection[GNUTLS_SAN_MAX]; + memset(types_with_empty_intersection, 0, sizeof(types_with_empty_intersection)); + if (*_nc == NULL || _nc2 == NULL) return 0; - /* For each name in _NC, if a _NC2 does not contain a name + /* Phase 1 + * For each name in _NC, if a _NC2 does not contain a name * with the same type, preserve the original name. */ t = nc = *_nc; while (t != NULL) { name_constraints_node_st *next = t->next; nc2 = _nc2; while (nc2 != NULL) { - if (t->type == nc2->type) + if (t->type == nc2->type) { + // check bounds (we will use 't->type' as index) + if (t->type > GNUTLS_SAN_MAX || t->type == 0) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + // note the possibility of empty intersection for this type + // if we add something to the intersection in phase 2, + // we will reset this flag back to 0 then + types_with_empty_intersection[t->type - 1] = 1; break; + } nc2 = nc2->next; } if (nc2 == NULL) { @@ -181,33 +201,29 @@ int _gnutls_name_constraints_intersect(name_constraints_node_st ** _nc, t = next; } + /* Phase 2 + * iterate through all combinations from nc2 and nc1 + * and create intersections of nodes with same type */ nc2 = _nc2; while (nc2 != NULL) { t = nc; while (t != NULL) { - struct name_constraints_node_st *tmp; - - if (!name_constraints_match(t, nc2)) { - t = t->next; - continue; + // save intersection of name constraints into tmp + ret = name_constraints_intersect_nodes(t, nc2, &tmp); + if (ret < 0) return gnutls_assert_val(ret); + // if intersection is not empty + if (tmp != NULL) { // intersection for this type is not empty + // check bounds + if (tmp->type > GNUTLS_SAN_MAX || tmp->type == 0) { + gnutls_free(tmp); + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + // we will not add universal excluded constraint for this type + types_with_empty_intersection[tmp->type - 1] = 0; + // add intersection node to DEST + tmp->next = dest; + dest = tmp; } - - /* copy node at NC2 to DEST */ - tmp = gnutls_malloc(sizeof(struct name_constraints_node_st)); - if (tmp == NULL) { - _gnutls_name_constraints_node_free(dest); - return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); - } - - tmp->type = nc2->type; - ret = _gnutls_set_datum(&tmp->name, nc2->name.data, nc2->name.size); - if (ret < 0) { - _gnutls_name_constraints_node_free(dest); - return gnutls_assert_val(ret); - } - - tmp->next = dest; - dest = tmp; t = t->next; } nc2 = nc2->next; @@ -216,6 +232,32 @@ int _gnutls_name_constraints_intersect(name_constraints_node_st ** _nc, /* replace the original with the new */ _gnutls_name_constraints_node_free(nc); *_nc = dest; + + /* Phase 3 + * For each type: If we have empty permitted name constraints now + * and we didn't have at the beginning, we have to add a new + * excluded constraint with universal wildcard + * (since the intersection of permitted is now empty). */ + for (int type = 1; type <= GNUTLS_SAN_MAX; type++) { + if (types_with_empty_intersection[type-1] == 0) + continue; + _gnutls_hard_log("Adding universal excluded name constraintfor type %d.\n", type); + tmp = gnutls_malloc(sizeof(struct name_constraints_node_st)); + if (tmp == NULL) { + _gnutls_name_constraints_node_free(dest); + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + tmp->type = type; + tmp->name.data = NULL; + tmp->name.size = 0; + if (ret < 0) { + _gnutls_name_constraints_node_free(tmp); + return gnutls_assert_val(ret); + } + tmp->next = *_nc_excluded; + *_nc_excluded = tmp; + } + return 0; } @@ -430,7 +472,7 @@ int _gnutls_x509_name_constraints_merge(gnutls_x509_name_constraints_t nc, ret = _gnutls_name_constraints_intersect(&nc->permitted, - nc2->permitted); + nc2->permitted, &nc->excluded); if (ret < 0) { gnutls_assert(); return ret; @@ -598,20 +640,55 @@ static unsigned email_matches(const gnutls_datum_t *name, const gnutls_datum_t * return email_ends_with(name, suffix); } +/*- + * name_constraints_intersect_nodes: + * @nc1: name constraints node 1 + * @nc2: name constraints node 2 + * @intersection: newly allocated node with intersected constraints, + * NULL if the intersection is empty + * + * Inspect 2 name constraints nodes (of possibly different types) and allocate + * a new node with intersection of given constraints. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value. + -*/ static int -name_constraints_match(name_constraints_node_st * nc1, - name_constraints_node_st * nc2) -{ - if (nc1->type != nc2->type) - return 0; +name_constraints_intersect_nodes(name_constraints_node_st * nc1, + name_constraints_node_st * nc2, + name_constraints_node_st ** intersection) { + // presume empty intersection + *intersection = NULL; + name_constraints_node_st *to_copy = NULL; + + if (nc1->type != nc2->type) { + return GNUTLS_E_SUCCESS; + } switch (nc1->type) { case GNUTLS_SAN_DNSNAME: - return dnsname_matches(&nc2->name, &nc1->name); + if (dnsname_matches(&nc2->name, &nc1->name)) + to_copy = nc2; + break; case GNUTLS_SAN_RFC822NAME: - return email_matches(&nc2->name, &nc1->name); + if (email_matches(&nc2->name, &nc1->name)) + to_copy = nc2; + break; default: - return 0; + return GNUTLS_E_SUCCESS; + } + // copy the node if necessary + if (to_copy != NULL) { + *intersection = gnutls_malloc(sizeof(struct name_constraints_node_st)); + if (*intersection == NULL) { + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + (*intersection)->type = to_copy->type; + int ret = _gnutls_set_datum(&(*intersection)->name, to_copy->name.data, to_copy->name.size); + if (ret < 0) { + _gnutls_name_constraints_node_free(*intersection); + return gnutls_assert_val(ret); + } } + return GNUTLS_E_SUCCESS; } static -- cgit v1.2.1