From 4760bc63531e3f5039e70ede91a20e1194410892 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@gnu.org>
Date: Mon, 18 Nov 2024 17:23:46 +0900
Subject: [PATCH] x509: optimize name constraints processing

This switches the representation name constraints from linked lists to
array lists to optimize the lookup performance from O(n) to O(1), also
enforces a limit of name constraint checks against subject alternative
names.

Signed-off-by: Daiki Ueno <ueno@gnu.org>

Index: gnutls-3.4.17/lib/x509/name_constraints.c
===================================================================
--- gnutls-3.4.17.orig/lib/x509/name_constraints.c
+++ gnutls-3.4.17/lib/x509/name_constraints.c
@@ -28,53 +28,234 @@
 #include <common.h>
 #include <gnutls_x509.h>
 #include <gnutls/x509-ext.h>
+#include <x509_ext_int.h>
 #include <x509_b64.h>
 #include <x509_int.h>
 #include <libtasn1.h>
 
-/* Name constraints is limited to DNS names.
- */
+#include "intprops.h"
 
-static unsigned is_nc_empty(struct gnutls_name_constraints_st* nc, unsigned type)
-{
-	name_constraints_node_st *t;
+#define MAX_NC_CHECKS (1 << 20)
 
-	if (nc->permitted == NULL && nc->excluded == NULL)
-		return 1;
+struct name_constraints_node_st {
+	unsigned type;
+	gnutls_datum_t name;
+};
+
+struct name_constraints_node_list_st {
+	struct name_constraints_node_st **data;
+	size_t size;
+	size_t capacity;
+};
+
+struct gnutls_name_constraints_st {
+	struct name_constraints_node_list_st nodes; /* owns elements */
+	struct name_constraints_node_list_st permitted; /* borrows elements */
+	struct name_constraints_node_list_st excluded; /* borrows elements */
+};
+
+static struct name_constraints_node_st *
+name_constraints_node_new(gnutls_x509_name_constraints_t nc, unsigned type,
+						unsigned char *data, unsigned int size);
+
+static int
+name_constraints_node_list_add(struct name_constraints_node_list_st *list,
+													struct name_constraints_node_st *node)
+{
+	if (!list->capacity || list->size == list->capacity) {
+		size_t new_capacity = list->capacity;
+		struct name_constraints_node_st **new_data;
+
+		if (INT_MULTIPLY_OVERFLOW(new_capacity, 2))
+			return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+		new_capacity *= 2;
+		if (INT_ADD_OVERFLOW(new_capacity, 1))
+			return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+		new_capacity += 1;
+		new_data = gnutls_realloc(
+				list->data,
+				new_capacity * sizeof(struct name_constraints_node_st *)
+			);
+		if (!new_data)
+			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+		list->capacity = new_capacity;
+		list->data = new_data;
+	}
+	list->data[list->size++] = node;
+	return 0;
+}
+
+static int name_constraints_intersect_nodes(
+	gnutls_x509_name_constraints_t nc,
+	const struct name_constraints_node_st *node1,
+	const struct name_constraints_node_st *node2,
+	struct name_constraints_node_st **intersection);
+
+/*
+ * _gnutls_x509_name_constraints_is_empty:
+ * @nc: name constraints structure
+ * @type: type (gnutls_x509_subject_alt_name_t or 0)
+ *
+ * Test whether given name constraints structure has any constraints (permitted
+ * or excluded) of a given type. @nc must be allocated (not NULL) before the call.
+ * If @type is 0, type checking will be skipped.
+ *
+ * Returns: false if @nc contains constraints of type @type, true otherwise
+ */
+bool _gnutls_x509_name_constraints_is_empty(gnutls_x509_name_constraints_t nc,
+									unsigned type)
+{
+	if (nc->permitted.size == 0 && nc->excluded.size == 0)
+		return true;
 
-	t = nc->permitted;
-	while (t != NULL) {
-		if (t->type == type)
-			return 0;
-		t = t->next;
+	if (type == 0)
+		return false;
+	size_t i = 0;
+	for (i = 0; i < nc->permitted.size; i++) {
+		if (nc->permitted.data[i]->type == type)
+			return false;
 	}
 
-	t = nc->excluded;
-	while (t != NULL) {
-		if (t->type == type)
-			return 0;
-		t = t->next;
+	for (i = 0; i < nc->excluded.size; i++) {
+		if (nc->excluded.data[i]->type == type)
+			return false;
 	}
 
 	/* no constraint for that type exists */
 	return 1;
 }
 
-int _gnutls_extract_name_constraints(ASN1_TYPE c2, const char *vstr,
-				    name_constraints_node_st ** _nc)
+/*-
+ * _gnutls_name_constraints_node_free:
+ * @node: name constriants node
+ *
+ * Deallocate a list of name constraints nodes starting at the given node.
+ -*/
+void name_constraints_node_free(struct name_constraints_node_st *node)
+{
+	if (node) {
+		gnutls_free(node->name.data);
+		gnutls_free(node);
+ 	}
+}
+
+ /*
+  * name_constraints_node_new:
+  * @type: name constraints type to set (gnutls_x509_subject_alt_name_t)
+  * @nc: a %gnutls_x509_name_constraints_t
+  * @data: name.data to set or NULL
+  * @size: name.size to set
+  *
+  * Allocate a new name constraints node and set its type, name size and name data.
+  *
+  * Returns: Pointer to newly allocated node or NULL in case of memory error.
+  */
+
+static struct name_constraints_node_st *
+name_constraints_node_new(gnutls_x509_name_constraints_t nc, unsigned type,
+						   			  unsigned char *data, unsigned int size)
+{
+	struct name_constraints_node_st *tmp;
+	int ret;
+	tmp = gnutls_calloc(1, sizeof(struct name_constraints_node_st));
+ 	if (tmp == NULL)
+ 		return NULL;
+ 	tmp->type = type;
+
+
+	if (data) {
+		ret = _gnutls_set_strdatum(&tmp->name, data, size);
+		if (ret < 0) {
+			gnutls_assert();
+ 			gnutls_free(tmp);
+ 			return NULL;
+ 		}
+	}
+
+	ret = name_constraints_node_list_add(&nc->nodes, tmp);
+	if (ret < 0) {
+		gnutls_assert();
+		name_constraints_node_free(tmp);
+		return NULL;
+	}
+
+ 	return tmp;
+}
+
+int _gnutls_mask_to_prefix(const unsigned char *mask, unsigned mask_size)
+{
+        unsigned i, prefix_length = 0;
+        for (i=0; i<mask_size; i++) {
+                if (mask[i] == 0xFF) {
+                        prefix_length += 8;
+                } else {
+                        switch(mask[i]) {
+                                case 0xFE: prefix_length += 7; break;
+                                case 0xFC: prefix_length += 6; break;
+                                case 0xF8: prefix_length += 5; break;
+                                case 0xF0: prefix_length += 4; break;
+                                case 0xE0: prefix_length += 3; break;
+                                case 0xC0: prefix_length += 2; break;
+                                case 0x80: prefix_length += 1; break;
+                                case 0x00: break;
+                                default:
+                                        return -1;
+                        }
+                        break;
+                }
+        }
+        i++;
+        // mask is invalid, if there follows something else than 0x00
+        for ( ; i<mask_size; i++) {
+                if (mask[i] != 0)
+                        return -1;
+        }
+        return prefix_length;
+}
+
+
+/*-
+ * validate_name_constraints_node:
+ * @type: type of name constraints
+ * @name: datum of name constraint
+ *
+ * Check the validity of given name constraints node (@type and @name).
+ * The supported types are GNUTLS_SAN_DNSNAME, GNUTLS_SAN_RFC822NAME,
+ * GNUTLS_SAN_DN, GNUTLS_SAN_URI and GNUTLS_SAN_IPADDRESS.
+ *
+ * CIDR ranges are checked for correct length (IPv4/IPv6) and correct mask format.
+ *
+ * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value.
+ -*/
+static int validate_name_constraints_node(gnutls_x509_subject_alt_name_t type,
+                                          const gnutls_datum_t* name)
+{
+        if (type != GNUTLS_SAN_DNSNAME && type != GNUTLS_SAN_RFC822NAME &&
+                type != GNUTLS_SAN_DN && type != GNUTLS_SAN_URI &&
+                type != GNUTLS_SAN_IPADDRESS) {
+                return gnutls_assert_val(GNUTLS_E_X509_UNKNOWN_SAN);
+        }
+
+        if (type == GNUTLS_SAN_IPADDRESS) {
+                if (name->size != 8 && name->size != 32)
+                        return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER);
+                int prefix = _gnutls_mask_to_prefix(name->data + name->size/2, name->size/2);
+                if (prefix < 0)
+                        return gnutls_assert_val(GNUTLS_E_MALFORMED_CIDR);
+        }
+
+        return GNUTLS_E_SUCCESS;
+}
+
+static int extract_name_constraints(gnutls_x509_name_constraints_t nc, asn1_node c2, const char *vstr,struct name_constraints_node_list_st *nodes)
 {
 	int ret;
 	char tmpstr[128];
 	unsigned indx = 0;
 	gnutls_datum_t tmp = { NULL, 0 };
 	unsigned int type;
-	struct name_constraints_node_st *nc, *prev;
+	struct name_constraints_node_st *node;
 
-	prev = *_nc;
-	if (prev != NULL) {
-		while(prev->next != NULL)
-			prev = prev->next;
-	}
 
 	do {
 		indx++;
@@ -96,25 +277,20 @@ int _gnutls_extract_name_constraints(ASN
 			goto cleanup;
 		}
 
-		nc = gnutls_malloc(sizeof(struct name_constraints_node_st));
-		if (nc == NULL) {
+		node = name_constraints_node_new(nc, type, tmp.data, tmp.size);
+		_gnutls_free_datum(&tmp);
+		if (node == NULL) {
 			gnutls_assert();
 			ret = GNUTLS_E_MEMORY_ERROR;
 			goto cleanup;
 		}
 
-		memcpy(&nc->name, &tmp, sizeof(gnutls_datum_t));
-		nc->type = type;
-		nc->next = NULL;
-
-		if (prev == NULL) {
-			*_nc = prev = nc;
-		} else {
-			prev->next = nc;
-			prev = nc;
+		ret = name_constraints_node_list_add(nodes, node);
+		if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
 		}
 
-		tmp.data = NULL;
 	} while (ret >= 0);
 
 	if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
@@ -128,6 +304,22 @@ int _gnutls_extract_name_constraints(ASN
 	return ret;
 }
 
+int _gnutls_x509_name_constraints_extract(asn1_node c2,
+									const char *permitted_name,
+									const char *excluded_name,
+									gnutls_x509_name_constraints_t nc)
+{
+	int ret;
+	ret = extract_name_constraints(nc, c2, permitted_name, &nc->permitted);
+	if (ret < 0)
+		return gnutls_assert_val(ret);
+	ret = extract_name_constraints(nc, c2, excluded_name, &nc->excluded);
+	if (ret < 0)
+		return gnutls_assert_val(ret);
+
+	return ret;
+}
+
 /**
  * gnutls_x509_crt_get_name_constraints:
  * @crt: should contain a #gnutls_x509_crt_t type
@@ -190,6 +382,261 @@ int gnutls_x509_crt_get_name_constraints
 
 }
 
+
+/*-
+ * @brief name_constraints_node_list_intersect:
+ * @nc: %gnutls_x509_name_constraints_t
+ * @permitted: first name constraints list (permitted)
+ * @permitted2: name constraints list to merge with (permitted)
+ * @excluded: Corresponding excluded name constraints list
+ *
+ * This function finds the intersection of @permitted and @permitted2. The result is placed in @permitted,
+ * the original @permitted is modified. @permitted2 is not changed. If necessary, a universal
+ * excluded name constraint node of the right type is added to the list provided
+ * in @excluded.
+ *
+ * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value.
+ -*/
+static int name_constraints_node_list_intersect(
+        gnutls_x509_name_constraints_t nc,
+        struct name_constraints_node_list_st *permitted,
+        const struct name_constraints_node_list_st *permitted2,
+        struct name_constraints_node_list_st *excluded)
+{
+	struct name_constraints_node_st *tmp;
+	int ret, type, used;
+	struct name_constraints_node_list_st removed = { .data = NULL,
+							 .size = 0,
+							 .capacity = 0 };
+
+	/* 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 (permitted->size == 0 || permitted2->size == 0)
+		return 0;
+	
+	size_t i = 0, j = 0;
+	/* Phase 1
+	 * For each name in PERMITTED, if a PERMITTED2 does not contain a name
+	 * with the same type, move the original name to REMOVED.
+	 * Do this also for node of unknown type (not DNS, email, IP) */
+	for (i = 0; i < permitted->size;) {
+		struct name_constraints_node_st *t = permitted->data[i];
+		const struct name_constraints_node_st *found = NULL;
+
+		for (j = 0; j < permitted2->size; j++) {
+			const struct name_constraints_node_st *t2 =
+				permitted2->data[j];
+			if (t->type == t2->type) {
+				// check bounds (we will use 't->type' as index)
+				if (t->type > GNUTLS_SAN_MAX || t->type == 0) {
+					gnutls_assert();
+					ret = GNUTLS_E_INTERNAL_ERROR;
+					goto cleanup;
+				}
+				// 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;
+				found = t2;
+				break;
+			}
+		}
+
+		if (found != NULL && (t->type == GNUTLS_SAN_DNSNAME ||
+				      t->type == GNUTLS_SAN_RFC822NAME ||
+				      t->type == GNUTLS_SAN_IPADDRESS)) {
+			/* move node from PERMITTED to REMOVED */
+			ret = name_constraints_node_list_add(&removed, t);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
+			}
+			/* remove node by swapping */
+			if (i < permitted->size - 1)
+				permitted->data[i] =
+					permitted->data[permitted->size - 1];
+			permitted->size--;
+			continue;
+		}
+		i++;
+	}
+
+	/* Phase 2
+	 * iterate through all combinations from PERMITTED2 and PERMITTED
+	 * and create intersections of nodes with same type */
+	for (i = 0; i < permitted2->size; i++) {
+		const struct name_constraints_node_st *t2 = permitted2->data[i];
+
+		// current PERMITTED2 node has not yet been used for any intersection
+		// (and is not in REMOVED either)
+		used = 0;
+		for (j = 0; j < removed.size; j++) {
+			const struct name_constraints_node_st *t =
+				removed.data[j];
+			// save intersection of name constraints into tmp
+			ret = name_constraints_intersect_nodes(nc, t, t2, &tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
+			}
+			used = 1;
+			// 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 PERMITTED
+				ret = name_constraints_node_list_add(permitted,
+								     tmp);
+				if (ret < 0) {
+					gnutls_assert();
+					goto cleanup;
+				}
+			}
+		}
+		// if the node from PERMITTED2 was not used for intersection, copy it to DEST
+		// Beware: also copies nodes other than DNS, email, IP,
+		//       since their counterpart may have been moved in phase 1.
+		if (!used) {
+			tmp = name_constraints_node_new(
+				nc, t2->type, t2->name.data, t2->name.size);
+			if (tmp == NULL) {
+				gnutls_assert();
+				ret = GNUTLS_E_MEMORY_ERROR;
+				goto cleanup;
+			}
+			ret = name_constraints_node_list_add(permitted, tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
+			}
+		}
+	}
+
+	/* 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 (type = 1; type <= GNUTLS_SAN_MAX; type++) {
+		if (types_with_empty_intersection[type - 1] == 0)
+			continue;
+		_gnutls_hard_log(
+			"Adding universal excluded name constraint for type %d.\n",
+			type);
+		switch (type) {
+		case GNUTLS_SAN_IPADDRESS:
+			// add universal restricted range for IPv4
+			tmp = name_constraints_node_new(
+				nc, GNUTLS_SAN_IPADDRESS, NULL, 8);
+			if (tmp == NULL) {
+				gnutls_assert();
+				ret = GNUTLS_E_MEMORY_ERROR;
+				goto cleanup;
+			}
+			ret = name_constraints_node_list_add(excluded, tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
+			}
+			// add universal restricted range for IPv6
+			tmp = name_constraints_node_new(
+				nc, GNUTLS_SAN_IPADDRESS, NULL, 32);
+			if (tmp == NULL) {
+				gnutls_assert();
+				ret = GNUTLS_E_MEMORY_ERROR;
+				goto cleanup;
+			}
+			ret = name_constraints_node_list_add(excluded, tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
+			}
+			break;
+		case GNUTLS_SAN_DNSNAME:
+		case GNUTLS_SAN_RFC822NAME:
+			tmp = name_constraints_node_new(nc, type, NULL, 0);
+			if (tmp == NULL) {
+				gnutls_assert();
+				ret = GNUTLS_E_MEMORY_ERROR;
+				goto cleanup;
+			}
+			ret = name_constraints_node_list_add(excluded, tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
+			}
+			break;
+		default: // do nothing, at least one node was already moved in phase 1
+			break;
+		}
+	}
+	ret = GNUTLS_E_SUCCESS;
+
+cleanup:
+	gnutls_free(removed.data);
+	return ret;
+}
+
+static int name_constraints_node_list_concat(
+	gnutls_x509_name_constraints_t nc,
+	struct name_constraints_node_list_st *nodes,
+	const struct name_constraints_node_list_st *nodes2)
+{
+	size_t i = 0;
+	for (i = 0; i < nodes2->size; i++) {
+		const struct name_constraints_node_st *node = nodes2->data[i];
+		struct name_constraints_node_st *tmp;
+		int ret;
+
+		tmp = name_constraints_node_new(nc, node->type, node->name.data,
+						node->name.size);
+		if (tmp == NULL) {
+			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+		}
+		ret = name_constraints_node_list_add(nodes, tmp);
+		if (ret < 0) {
+			name_constraints_node_free(tmp);
+			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+		}
+	}
+
+	return 0;
+}
+
+void _gnutls_x509_name_constraints_clear(gnutls_x509_name_constraints_t nc)
+{
+	size_t i = 0;
+	for (i = 0; i < nc->nodes.size; i++) {
+		struct name_constraints_node_st *node = nc->nodes.data[i];
+		name_constraints_node_free(node);
+	}
+	gnutls_free(nc->nodes.data);
+	nc->nodes.capacity = 0;
+	nc->nodes.size = 0;
+
+	gnutls_free(nc->permitted.data);
+	nc->permitted.capacity = 0;
+	nc->permitted.size = 0;
+
+	gnutls_free(nc->excluded.data);
+	nc->excluded.capacity = 0;
+	nc->excluded.size = 0;
+}
+
 /**
  * gnutls_x509_name_constraints_deinit:
  * @nc: The nameconstraints
@@ -200,23 +647,7 @@ int gnutls_x509_crt_get_name_constraints
  **/
 void gnutls_x509_name_constraints_deinit(gnutls_x509_name_constraints_t nc)
 {
-	name_constraints_node_st * next, *t;
-
-	t = nc->permitted;
-	while (t != NULL) {
-		next = t->next;
-		gnutls_free(t->name.data);
-		gnutls_free(t);
-		t = next;
-	}
-
-	t = nc->excluded;
-	while (t != NULL) {
-		next = t->next;
-		gnutls_free(t->name.data);
-		gnutls_free(t);
-		t = next;
-	}
+	_gnutls_x509_name_constraints_clear(nc);
 	gnutls_free(nc);
 }
 
@@ -232,12 +663,15 @@ void gnutls_x509_name_constraints_deinit
  **/
 int gnutls_x509_name_constraints_init(gnutls_x509_name_constraints_t *nc)
 {
-	*nc = gnutls_calloc(1, sizeof(struct gnutls_name_constraints_st));
-	if (*nc == NULL) {
+	struct gnutls_name_constraints_st *tmp;
+
+	tmp = gnutls_calloc(1, sizeof(struct gnutls_name_constraints_st));
+	if (tmp == NULL) {
 		gnutls_assert();
 		return GNUTLS_E_MEMORY_ERROR;
 	}
 
+	*nc = tmp;
 	return 0;
 }
 
@@ -247,48 +681,163 @@ int name_constraints_add(gnutls_x509_nam
 		       const gnutls_datum_t * name,
 		       unsigned permitted)
 {
-	struct name_constraints_node_st * tmp, *prev = NULL;
+	struct name_constraints_node_st *tmp;
+	struct name_constraints_node_list_st *nodes;
 	int ret;
 
-	if (type != GNUTLS_SAN_DNSNAME && type != GNUTLS_SAN_RFC822NAME &&
-		type != GNUTLS_SAN_DN && type != GNUTLS_SAN_URI && type != GNUTLS_SAN_IPADDRESS)
-		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+	ret = validate_name_constraints_node(type, name);
+	if (ret < 0)
+		return gnutls_assert_val(ret);
+
+	nodes = permitted ? &nc->permitted : &nc->excluded;
 
-	if (type == GNUTLS_SAN_IPADDRESS && (name->size != 8 && name->size != 32)) {
-		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+	tmp = name_constraints_node_new(nc, type, name->data, name->size);
+	if (tmp == NULL)
+		return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+	ret = name_constraints_node_list_add(nodes, tmp);
+	if (ret < 0) {
+		name_constraints_node_free(tmp);
+		return gnutls_assert_val(ret);
 	}
 
-	if (permitted != 0)
-		prev = tmp = nc->permitted;
-	else
-		prev = tmp = nc->excluded;
+	return 0;
+}
+
+static unsigned dnsname_matches(const gnutls_datum_t *name, const gnutls_datum_t *suffix);
+static unsigned email_matches(const gnutls_datum_t *name, const gnutls_datum_t *suffix);
+
+/*-
+ * _gnutls_mask_ip:
+ * @ip: IP of @ipsize bytes
+ * @mask: netmask of @ipsize bytes
+ * @ipsize: IP length (4 or 16)
+ *
+ * Mask given IP in place according to the given mask.
+ *
+ * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value.
+ -*/
+int _gnutls_mask_ip(unsigned char *ip, const unsigned char *mask, unsigned ipsize) {
+        unsigned i = 0;
+		if (ipsize != 4 && ipsize != 16)
+                return GNUTLS_E_MALFORMED_CIDR;
+        for (i = 0;i  < ipsize; i++)
+                ip[i] &= mask[i];
+        return GNUTLS_E_SUCCESS;
+}
+
+static int name_constraints_intersect_nodes(
+	gnutls_x509_name_constraints_t nc,
+	const struct name_constraints_node_st *node1,
+	const struct name_constraints_node_st *node2,
+	struct name_constraints_node_st **_intersection)
+{
+	// presume empty intersection
+	struct name_constraints_node_st *intersection = NULL;
+	const struct name_constraints_node_st *to_copy = NULL;
+	unsigned iplength = 0;
+	unsigned byte;
 
-	while(tmp != NULL) {
-		tmp = tmp->next;
-		if (tmp != NULL)
-			prev = tmp;
+	*_intersection = NULL;
+
+	if (node1->type != node2->type) {
+		return GNUTLS_E_SUCCESS;
+	}
+	switch (node1->type) {
+	case GNUTLS_SAN_DNSNAME:
+		if (!dnsname_matches(&node2->name, &node1->name))
+			return GNUTLS_E_SUCCESS;
+		to_copy = node2;
+		break;
+	case GNUTLS_SAN_RFC822NAME:
+		if (!email_matches(&node2->name, &node1->name))
+			return GNUTLS_E_SUCCESS;
+		to_copy = node2;
+		break;
+	case GNUTLS_SAN_IPADDRESS:
+		if (node1->name.size != node2->name.size)
+			return GNUTLS_E_SUCCESS;
+		iplength = node1->name.size / 2;
+		for (byte = 0; byte < iplength; byte++) {
+			if (((node1->name.data[byte] ^
+			      node2->name.data[byte]) // XOR of addresses
+			     & node1->name.data[byte +
+						iplength] // AND mask from nc1
+			     & node2->name.data[byte +
+						iplength]) // AND mask from nc2
+			    != 0) {
+				// CIDRS do not intersect
+				return GNUTLS_E_SUCCESS;
+			}
+		}
+		to_copy = node2;
+		break;
+	default:
+		// for other types, we don't know how to do the intersection, assume empty
+		return GNUTLS_E_SUCCESS;
 	}
 
-	tmp = gnutls_malloc(sizeof(struct name_constraints_node_st));
-	if (tmp == NULL)
-		return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+	// copy existing node if applicable
+	if (to_copy != NULL) {
+		*_intersection = name_constraints_node_new(nc, to_copy->type,
+							   to_copy->name.data,
+							   to_copy->name.size);
+		if (*_intersection == NULL)
+			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+		intersection = *_intersection;
+
+		if (intersection->type == GNUTLS_SAN_IPADDRESS) {
+			// make sure both IP addresses are correctly masked
+			_gnutls_mask_ip(intersection->name.data,
+					intersection->name.data + iplength,
+					iplength);
+			_gnutls_mask_ip(node1->name.data,
+					node1->name.data + iplength, iplength);
+			// update intersection, if necessary (we already know one is subset of other)
+			for (byte = 0; byte < 2 * iplength; byte++) {
+				intersection->name.data[byte] |=
+					node1->name.data[byte];
+			}
+		}
+	}
+
+	return GNUTLS_E_SUCCESS;
+}
+
+/*-
+ * _gnutls_x509_name_constraints_merge:
+ * @nc: The nameconstraints
+ * @nc2: The name constraints to be merged with
+ *
+ * This function will merge the provided name constraints structures
+ * as per RFC5280 p6.1.4. That is, the excluded constraints will be appended,
+ * and permitted will be intersected. The intersection assumes that @nc
+ * is the root CA constraints.
+ *
+ * The merged constraints will be placed in @nc.
+ *
+ * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value.
+ *
+ * Since: 3.5.0
+ -*/
+int _gnutls_x509_name_constraints_merge(gnutls_x509_name_constraints_t nc,
+					gnutls_x509_name_constraints_t nc2)
+{
+	int ret;
 
-	tmp->next = NULL;
-	tmp->type = type;
-	ret = _gnutls_set_datum(&tmp->name, name->data, name->size);
+	ret = name_constraints_node_list_intersect(
+		nc, &nc->permitted, &nc2->permitted, &nc->excluded);
 	if (ret < 0) {
 		gnutls_assert();
-		gnutls_free(tmp);
 		return ret;
 	}
 
-	if (prev == NULL) {
-		if (permitted != 0)
-			nc->permitted = tmp;
-		else
-			nc->excluded = tmp;
-	} else
-		prev->next = tmp;
+	ret = name_constraints_node_list_concat(nc, &nc->excluded,
+						&nc2->excluded);
+	if (ret < 0) {
+		gnutls_assert();
+		return ret;
+	}
 
 	return 0;
 }
@@ -669,10 +1218,18 @@ int ret;
 unsigned idx, t, san_type;
 gnutls_datum_t n;
 unsigned found_one;
+size_t checks;
+
 
-	if (is_nc_empty(nc, type) != 0)
+	if (_gnutls_x509_name_constraints_is_empty(nc, type) != 0)
 		return 1; /* shortcut; no constraints to check */
 
+	if (!INT_ADD_OK(nc->permitted.size, nc->excluded.size, &checks) ||
+	    !INT_MULTIPLY_OK(checks, cert->san->size, &checks) ||
+	    checks > MAX_NC_CHECKS) {
+		return gnutls_assert_val(0);
+	}
+
 	if (type == GNUTLS_SAN_RFC822NAME) {
 		idx = found_one = 0;
 		do {
@@ -829,21 +1386,13 @@ int gnutls_x509_name_constraints_get_per
 				     unsigned idx,
 				     unsigned *type, gnutls_datum_t * name)
 {
-	unsigned int i;
-	struct name_constraints_node_st * tmp = nc->permitted;
-
-	for (i = 0; i < idx; i++) {
-		if (tmp == NULL)
-			return
-			    gnutls_assert_val
-			    (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
+	const struct name_constraints_node_st *tmp;
 
-		tmp = tmp->next;
-	}
-
-	if (tmp == NULL)
+	if (idx >= nc->permitted.size)
 		return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
 
+	tmp = nc->permitted.data[idx];
+
 	*type = tmp->type;
 	*name = tmp->name;
 
@@ -873,21 +1422,13 @@ int gnutls_x509_name_constraints_get_exc
 				     unsigned idx,
 				     unsigned *type, gnutls_datum_t * name)
 {
-	unsigned int i;
-	struct name_constraints_node_st * tmp = nc->excluded;
-
-	for (i = 0; i < idx; i++) {
-		if (tmp == NULL)
-			return
-			    gnutls_assert_val
-			    (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
+	const struct name_constraints_node_st *tmp;
 
-		tmp = tmp->next;
-	}
-
-	if (tmp == NULL)
+	if (idx >= nc->excluded.size)
 		return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
 
+	tmp = nc->excluded.data[idx];
+
 	*type = tmp->type;
 	*name = tmp->name;
 
Index: gnutls-3.4.17/lib/x509/x509_ext.c
===================================================================
--- gnutls-3.4.17.orig/lib/x509/x509_ext.c
+++ gnutls-3.4.17/lib/x509/x509_ext.c
@@ -28,20 +28,11 @@
 #include <common.h>
 #include <gnutls_x509.h>
 #include <x509_b64.h>
+#include <x509_ext_int.h>
 #include <c-ctype.h>
 #include <gnutls/x509-ext.h>
 
-struct name_st {
-	unsigned int type;
-	gnutls_datum_t san;
-	gnutls_datum_t othername_oid;
-};
-
 #define MAX_ENTRIES 64
-struct gnutls_subject_alt_names_st {
-	struct name_st *names;
-	unsigned int size;
-};
 
 /**
  * gnutls_subject_alt_names_init:
@@ -373,6 +364,7 @@ int gnutls_x509_ext_import_name_constrai
 {
 	int result, ret;
 	ASN1_TYPE c2 = ASN1_TYPE_EMPTY;
+	gnutls_x509_name_constraints_t nc2 = NULL;
 
 	result = asn1_create_element
 	    (_gnutls_get_pkix(), "PKIX1.NameConstraints", &c2);
@@ -388,23 +380,36 @@ int gnutls_x509_ext_import_name_constrai
 		goto cleanup;
 	}
 
-	if (!(flags & GNUTLS_NAME_CONSTRAINTS_FLAG_APPEND)
-	    || (nc->permitted == NULL && nc->excluded == NULL)) {
-		ret =
-		    _gnutls_extract_name_constraints(c2, "permittedSubtrees",
-						     &nc->permitted);
+	if (flags & GNUTLS_NAME_CONSTRAINTS_FLAG_APPEND &&
+	    !_gnutls_x509_name_constraints_is_empty(nc, 0)) {
+		ret = gnutls_x509_name_constraints_init(&nc2);
+		if (ret < 0) {
+			gnutls_assert();
+			goto cleanup;
+		}
+
+		ret = _gnutls_x509_name_constraints_extract(
+			c2, "permittedSubtrees", "excludedSubtrees", nc2);
+		if (ret < 0) {
+			gnutls_assert();
+			goto cleanup;
+		}
+
+		ret = _gnutls_x509_name_constraints_merge(nc, nc2);
 		if (ret < 0) {
 			gnutls_assert();
 			goto cleanup;
 		}
 	}
+	else {
+		_gnutls_x509_name_constraints_clear(nc);
 
-	ret =
-	    _gnutls_extract_name_constraints(c2, "excludedSubtrees",
-					     &nc->excluded);
-	if (ret < 0) {
-		gnutls_assert();
-		goto cleanup;
+		ret = _gnutls_x509_name_constraints_extract(
+			c2, "permittedSubtrees", "excludedSubtrees", nc);
+		if (ret < 0) {
+			gnutls_assert();
+			goto cleanup;
+		}
 	}
 
 	ret = 0;
@@ -433,112 +438,115 @@ int gnutls_x509_ext_export_name_constrai
 {
 	int ret, result;
 	uint8_t null = 0;
-	ASN1_TYPE c2 = ASN1_TYPE_EMPTY;
-	struct name_constraints_node_st *tmp;
-
-	if (nc->permitted == NULL && nc->excluded == NULL)
+	asn1_node c2 = NULL;
+	unsigned rtype;
+	gnutls_datum_t rname;
+	unsigned i = 0;
+	if (_gnutls_x509_name_constraints_is_empty(nc, 0))
 		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
 
-	result = asn1_create_element
-	    (_gnutls_get_pkix(), "PKIX1.NameConstraints", &c2);
+	result = asn1_create_element(_gnutls_get_pkix(),
+				     "PKIX1.NameConstraints", &c2);
 	if (result != ASN1_SUCCESS) {
 		gnutls_assert();
 		return _gnutls_asn2err(result);
 	}
 
-	if (nc->permitted == NULL) {
-		asn1_write_value(c2, "permittedSubtrees", NULL, 0);
+	ret = gnutls_x509_name_constraints_get_permitted(nc, 0, &rtype, &rname);
+	if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+		(void)asn1_write_value(c2, "permittedSubtrees", NULL, 0);
 	} else {
-		tmp = nc->permitted;
-		do {
-			result =
-			    asn1_write_value(c2, "permittedSubtrees", "NEW", 1);
+		for (i = 0;; i++) {
+			ret = gnutls_x509_name_constraints_get_permitted(
+				nc, i, &rtype, &rname);
+			if (ret < 0) {
+				if (ret ==
+				    GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+					break;
+				gnutls_assert();
+				goto cleanup;
+			}
+			result = asn1_write_value(c2, "permittedSubtrees",
+						  "NEW", 1);
 			if (result != ASN1_SUCCESS) {
 				gnutls_assert();
 				ret = _gnutls_asn2err(result);
 				goto cleanup;
 			}
 
-			result =
-			    asn1_write_value(c2,
-					     "permittedSubtrees.?LAST.maximum",
-					     NULL, 0);
+			result = asn1_write_value(
+				c2, "permittedSubtrees.?LAST.maximum", NULL, 0);
 			if (result != ASN1_SUCCESS) {
 				gnutls_assert();
 				ret = _gnutls_asn2err(result);
 				goto cleanup;
 			}
 
-			result =
-			    asn1_write_value(c2,
-					     "permittedSubtrees.?LAST.minimum",
-					     &null, 1);
+			result = asn1_write_value(
+				c2, "permittedSubtrees.?LAST.minimum", &null,
+				1);
 			if (result != ASN1_SUCCESS) {
 				gnutls_assert();
 				ret = _gnutls_asn2err(result);
 				goto cleanup;
 			}
 
-			ret =
-			    _gnutls_write_general_name(c2,
-						       "permittedSubtrees.?LAST.base",
-						       tmp->type,
-						       tmp->name.data,
-						       tmp->name.size);
+			ret = _gnutls_write_general_name(
+				c2, "permittedSubtrees.?LAST.base", rtype,
+				rname.data, rname.size);
 			if (ret < 0) {
 				gnutls_assert();
 				goto cleanup;
 			}
-			tmp = tmp->next;
-		} while (tmp != NULL);
+		}
 	}
 
-	if (nc->excluded == NULL) {
-		asn1_write_value(c2, "excludedSubtrees", NULL, 0);
+	ret = gnutls_x509_name_constraints_get_excluded(nc, 0, &rtype, &rname);
+	if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+		(void)asn1_write_value(c2, "excludedSubtrees", NULL, 0);
 	} else {
-		tmp = nc->excluded;
-		do {
-			result =
-			    asn1_write_value(c2, "excludedSubtrees", "NEW", 1);
+		for (i = 0;; i++) {
+			ret = gnutls_x509_name_constraints_get_excluded(
+				nc, i, &rtype, &rname);
+			if (ret < 0) {
+				if (ret ==
+				    GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+					break;
+				gnutls_assert();
+				goto cleanup;
+			}
+			result = asn1_write_value(c2, "excludedSubtrees", "NEW",
+						  1);
 			if (result != ASN1_SUCCESS) {
 				gnutls_assert();
 				ret = _gnutls_asn2err(result);
 				goto cleanup;
 			}
 
-			result =
-			    asn1_write_value(c2,
-					     "excludedSubtrees.?LAST.maximum",
-					     NULL, 0);
+			result = asn1_write_value(
+				c2, "excludedSubtrees.?LAST.maximum", NULL, 0);
 			if (result != ASN1_SUCCESS) {
 				gnutls_assert();
 				ret = _gnutls_asn2err(result);
 				goto cleanup;
 			}
 
-			result =
-			    asn1_write_value(c2,
-					     "excludedSubtrees.?LAST.minimum",
-					     &null, 1);
+			result = asn1_write_value(
+				c2, "excludedSubtrees.?LAST.minimum", &null, 1);
 			if (result != ASN1_SUCCESS) {
 				gnutls_assert();
 				ret = _gnutls_asn2err(result);
 				goto cleanup;
 			}
 
-			ret =
-			    _gnutls_write_general_name(c2,
-						       "excludedSubtrees.?LAST.base",
-						       tmp->type,
-						       tmp->name.data,
-						       tmp->name.size);
+			ret = _gnutls_write_general_name(
+				c2, "excludedSubtrees.?LAST.base", rtype,
+				rname.data, rname.size);
 			if (ret < 0) {
 				gnutls_assert();
 				goto cleanup;
 			}
-			tmp = tmp->next;
-		} while (tmp != NULL);
-
+		}
 	}
 
 	ret = _gnutls_x509_der_encode(c2, "", ext, 0);
@@ -549,7 +557,7 @@ int gnutls_x509_ext_export_name_constrai
 
 	ret = 0;
 
- cleanup:
+cleanup:
 	asn1_delete_structure(&c2);
 	return ret;
 }
Index: gnutls-3.4.17/lib/x509/x509_int.h
===================================================================
--- gnutls-3.4.17.orig/lib/x509/x509_int.h
+++ gnutls-3.4.17/lib/x509/x509_int.h
@@ -465,20 +465,17 @@ _gnutls_x509_crt_check_revocation(gnutls
 				  int crl_list_length,
 				  gnutls_verify_output_function func);
 
-typedef struct gnutls_name_constraints_st {
-	struct name_constraints_node_st * permitted;
-	struct name_constraints_node_st * excluded;
-} gnutls_name_constraints_st;
-
-typedef struct name_constraints_node_st {
-	unsigned type;
-	gnutls_datum_t name;
-	struct name_constraints_node_st *next;
-} name_constraints_node_st;
-
-int _gnutls_extract_name_constraints(ASN1_TYPE c2, const char *vstr,
-				    name_constraints_node_st ** _nc);
+bool _gnutls_x509_name_constraints_is_empty(gnutls_x509_name_constraints_t nc,
+											unsigned type);
+int _gnutls_x509_name_constraints_extract(asn1_node c2,
+										  const char *permitted_name,
+										  const char *excluded_name,
+										  gnutls_x509_name_constraints_t nc);
+void _gnutls_x509_name_constraints_clear(gnutls_x509_name_constraints_t nc);
 
 void _gnutls_x509_policies_erase(gnutls_x509_policies_t policies, unsigned int seq);
 
+int _gnutls_x509_name_constraints_merge(gnutls_x509_name_constraints_t nc,
+                                       gnutls_x509_name_constraints_t nc2);
+
 #endif
Index: gnutls-3.4.17/lib/includes/gnutls/gnutls.h.in
===================================================================
--- gnutls-3.4.17.orig/lib/includes/gnutls/gnutls.h.in
+++ gnutls-3.4.17/lib/includes/gnutls/gnutls.h.in
@@ -1968,6 +1968,7 @@ typedef enum gnutls_x509_subject_alt_nam
 	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.  */
@@ -2490,6 +2491,8 @@ int gnutls_fips140_mode_enabled(void);
 #define GNUTLS_E_UNKNOWN_SRP_USERNAME -109
 #define GNUTLS_E_PREMATURE_TERMINATION -110
 
+#define GNUTLS_E_MALFORMED_CIDR -111
+
 #define GNUTLS_E_BASE64_ENCODING_ERROR -201
 #define GNUTLS_E_INCOMPATIBLE_GCRYPT_LIBRARY -202	/* obsolete */
 #define GNUTLS_E_INCOMPATIBLE_CRYPTO_LIBRARY -202
Index: gnutls-3.4.17/gl/intprops.h
===================================================================
--- gnutls-3.4.17.orig/gl/intprops.h
+++ gnutls-3.4.17/gl/intprops.h
@@ -425,4 +425,35 @@ verify (TYPE_MAXIMUM (long long int) ==
    ? (t) ((ut) (a) op (ut) (b)) \
    : ((t) (((ut) (a) op (ut) (b)) - (tmin)) + (tmin)))
 
+/* The following macros compute A + B, A - B, and A * B, respectively.
+   If no overflow occurs, they set *R to the result and return 1;
+   otherwise, they return 0 and may modify *R.
+
+   Example usage:
+
+     long int result;
+     if (INT_ADD_OK (a, b, &result))
+       printf ("result is %ld\n", result);
+     else
+       printf ("overflow\n");
+
+   A, B, and *R should be integers; they need not be the same type,
+   and they need not be all signed or all unsigned.
+   However, none of the integer types should be bit-precise,
+   and *R's type should not be char, bool, or an enumeration type.
+
+   These macros work correctly on all known practical hosts, and do not rely
+   on undefined behavior due to signed arithmetic overflow.
+
+   These macros are not constant expressions.
+
+   These macros may evaluate their arguments zero or multiple times, so the
+   arguments should not have side effects.
+
+   These macros are tuned for B being a constant.  */
+
+#define INT_ADD_OK(a, b, r) (! INT_ADD_WRAPV (a, b, r))
+#define INT_SUBTRACT_OK(a, b, r) (! INT_SUBTRACT_WRAPV (a, b, r))
+#define INT_MULTIPLY_OK(a, b, r) (! INT_MULTIPLY_WRAPV (a, b, r))
+
 #endif /* _GL_INTPROPS_H */
Index: gnutls-3.4.17/lib/x509/x509_ext_int.h
===================================================================
--- /dev/null
+++ gnutls-3.4.17/lib/x509/x509_ext_int.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014-2016 Free Software Foundation
+ * Copyright (C) 2014-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/>
+ *
+ */
+
+#ifndef GNUTLS_LIB_X509_X509_EXT_INT_H
+#define GNUTLS_LIB_X509_X509_EXT_INT_H
+
+#include "gnutls_int.h"
+struct name_st {
+        unsigned int type;
+        gnutls_datum_t san;
+        gnutls_datum_t othername_oid;
+};
+
+struct gnutls_subject_alt_names_st {
+        struct name_st *names;
+        unsigned int size;
+};
+
+int _gnutls_alt_name_process(gnutls_datum_t *out, unsigned type,
+                             const gnutls_datum_t *san, unsigned raw);
+
+#endif /* GNUTLS_LIB_X509_X509_EXT_INT_H */
+
Index: gnutls-3.4.17/lib/x509/Makefile.am
===================================================================
--- gnutls-3.4.17.orig/lib/x509/Makefile.am
+++ gnutls-3.4.17/lib/x509/Makefile.am
@@ -57,6 +57,7 @@ libgnutls_x509_la_SOURCES =	\
 	x509_int.h		\
 	x509_write.c		\
 	name_constraints.c	\
+        x509_ext_int.h          \
 	verify-high.c		\
 	verify-high2.c		\
 	verify-high.h		\
