/**
 * gnutls_x509_name_constraints_check_crt:
 * @nc: the extracted name constraints
 * @type: the type of the constraint to check (of type gnutls_x509_subject_alt_name_t)
 * @cert: the certificate to be checked
 *
 * This function will check the provided certificate names against the constraints in
 * @nc using the RFC5280 rules. It will traverse all the certificate's names and
 * alternative names.
 *
 * Currently this function is limited to DNS
 * names and emails (of type %GNUTLS_SAN_DNSNAME and %GNUTLS_SAN_RFC822NAME).
 *
 * Returns: zero if the provided name is not acceptable, and non-zero otherwise.
 *
 * Since: 3.3.0
 **/
unsigned gnutls_x509_name_constraints_check_crt(gnutls_x509_name_constraints_t nc,
				       gnutls_x509_subject_alt_name_t type,
				       gnutls_x509_crt_t cert)
{
char name[MAX_CN];
size_t name_size;
int ret;
unsigned idx, t, san_type;
gnutls_datum_t n;
unsigned found_one;

	if (is_nc_empty(nc, type) != 0)
		return 1; /* shortcut; no constraints to check */

	if (type == GNUTLS_SAN_RFC822NAME) {
		idx = found_one = 0;
		do {
			name_size = sizeof(name);
			ret = gnutls_x509_crt_get_subject_alt_name2(cert,
				idx++, name, &name_size, &san_type, NULL);
			if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
				break;
			else if (ret < 0)
				return gnutls_assert_val(0);

			if (san_type != GNUTLS_SAN_RFC822NAME)
				continue;

			found_one = 1;
			n.data = (void*)name;
			n.size = name_size;
			t = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
				&n);
			if (t == 0)
				return gnutls_assert_val(t);
		} while(ret >= 0);

		/* there is at least a single e-mail. That means that the EMAIL field will
		 * not be used for verifying the identity of the holder. */
		if (found_one != 0)
			return 1;

		do {
			/* ensure there is only a single EMAIL, similarly to CN handling (rfc6125) */
			name_size = sizeof(name);
			ret = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_PKCS9_EMAIL,
							    1, 0, name, &name_size);
			if (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
				return gnutls_assert_val(0);

			name_size = sizeof(name);
			ret = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_PKCS9_EMAIL,
							    0, 0, name, &name_size);
			if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
				break;
			else if (ret < 0)
				return gnutls_assert_val(0);

			found_one = 1;
			n.data = (void*)name;
			n.size = name_size;
			t = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME, &n);
			if (t == 0)
				return gnutls_assert_val(t);
		} while(0);

		/* passed */
		if (found_one != 0)
			return 1;
		else {
			/* no name was found. According to RFC5280: 
			 * If no name of the type is in the certificate, the certificate is acceptable.
			 */
			return gnutls_assert_val(1);
		}
	} else if (type == GNUTLS_SAN_DNSNAME) {
		idx = found_one = 0;
		do {
			name_size = sizeof(name);
			ret = gnutls_x509_crt_get_subject_alt_name2(cert,
				idx++, name, &name_size, &san_type, NULL);
			if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
				break;
			else if (ret < 0)
				return gnutls_assert_val(0);

			if (san_type != GNUTLS_SAN_DNSNAME)
				continue;

			found_one = 1;
			n.data = (void*)name;
			n.size = name_size;
			t = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME,
				&n);
			if (t == 0)
				return gnutls_assert_val(t);
		} while(ret >= 0);

		/* there is at least a single DNS name. That means that the CN will
		 * not be used for verifying the identity of the holder. */
		if (found_one != 0)
			return 1;

		/* verify the name constraints against the CN, if the certificate is
		 * not a CA. We do this check only on certificates marked as WWW server,
		 * because that's where the CN check is only performed. */
		if (_gnutls_check_key_purpose(cert, GNUTLS_KP_TLS_WWW_SERVER, 0) != 0)
		do {
			/* ensure there is only a single CN, according to rfc6125 */
			name_size = sizeof(name);
			ret = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME,
				 			    1, 0, name, &name_size);
			if (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
				return gnutls_assert_val(0);

			name_size = sizeof(name);
			ret = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME,
							    0, 0, name, &name_size);
			if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
				break;
			else if (ret < 0)
				return gnutls_assert_val(0);

			found_one = 1;
			n.data = (void*)name;
			n.size = name_size;
			t = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME,
				&n);
			if (t == 0)
				return gnutls_assert_val(t);
		} while(0);

		/* passed */
		if (found_one != 0)
			return 1;
		else {
			/* no name was found. According to RFC5280: 
			 * If no name of the type is in the certificate, the certificate is acceptable.
			 */
			return gnutls_assert_val(1);
		}
	} else if (type == GNUTLS_SAN_IPADDRESS || type == GNUTLS_SAN_URI) {
		return check_unsupported_constraint2(cert, nc, type);
	} else
		return check_unsupported_constraint(nc, type);
}
void doit(void)
{
	int ret, suite;
	gnutls_x509_name_constraints_t nc1, nc2;
	gnutls_datum_t name;

	gnutls_global_set_log_function(tls_log_func);
	if (debug)
		gnutls_global_set_log_level(1000);

	/* 0: test the merge permitted name constraints
	 * NC1: permitted DNS org
	 *      permitted DNS ccc.com
	 *      permitted email ccc.com
	 * NC2: permitted DNS org
	 *      permitted DNS aaa.bbb.ccc.com
	 */
	suite = 0;

	ret = gnutls_x509_name_constraints_init(&nc1);
	check_for_error(ret);

	ret = gnutls_x509_name_constraints_init(&nc2);
	check_for_error(ret);

	set_name("org", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	set_name("ccc.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	set_name("ccc.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc1, GNUTLS_SAN_RFC822NAME, &name);
	check_for_error(ret);

	set_name("org", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc2, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	set_name("aaa.bbb.ccc.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc2, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	ret = _gnutls_x509_name_constraints_merge(nc1, nc2);
	check_for_error(ret);

	/* unrelated */
	set_name("xxx.example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("example.org", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_ACCEPTED, &name);

	set_name("com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("xxx.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("ccc.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	/* check intersection of permitted */
	set_name("xxx.aaa.bbb.ccc.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_ACCEPTED, &name);

	set_name("aaa.bbb.ccc.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_ACCEPTED, &name);

	set_name("xxx.bbb.ccc.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("xxx.ccc.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("ccc.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("ccc.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_RFC822NAME, &name);
	check_test_result(suite, ret, NAME_ACCEPTED, &name);

	set_name("xxx.ccc.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_RFC822NAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	gnutls_x509_name_constraints_deinit(nc1);
	gnutls_x509_name_constraints_deinit(nc2);

	/* 1: test the merge of excluded name constraints
	 * NC1: denied DNS example.com
	 * NC2: denied DNS example.net
	 */
	suite = 1;

	ret = gnutls_x509_name_constraints_init(&nc1);
	check_for_error(ret);

	ret = gnutls_x509_name_constraints_init(&nc2);
	check_for_error(ret);

	set_name("example.com", &name);
	ret = gnutls_x509_name_constraints_add_excluded(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	set_name("example.net", &name);
	ret = gnutls_x509_name_constraints_add_excluded(nc2, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	ret = _gnutls_x509_name_constraints_merge(nc1, nc2);
	check_for_error(ret);

	set_name("xxx.example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("xxx.example.net", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("example.net", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("example.org", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_ACCEPTED, &name);

	gnutls_x509_name_constraints_deinit(nc1);
	gnutls_x509_name_constraints_deinit(nc2);

	/* 2: test permitted constraints with empty intersection
	 *    (no permitted nodes remain)
	 * NC1: permitted DNS one.example.com
	 * NC2: permitted DNS two.example.com
	 */
	suite = 2;

	ret = gnutls_x509_name_constraints_init(&nc1);
	check_for_error(ret);

	ret = gnutls_x509_name_constraints_init(&nc2);
	check_for_error(ret);

	set_name("one.example.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	set_name("two.example.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc2, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	ret = _gnutls_x509_name_constraints_merge(nc1, nc2);
	check_for_error(ret);

	set_name("one.example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("two.example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("three.example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("org", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	gnutls_x509_name_constraints_deinit(nc1);
	gnutls_x509_name_constraints_deinit(nc2);

	/* 3: test more permitted constraints, some with empty intersection
	 * NC1: permitted DNS foo.com
	 *      permitted DNS bar.com
	 *      permitted email redhat.com
	 * NC2: permitted DNS sub.foo.com
	 */
	suite = 3;

	ret = gnutls_x509_name_constraints_init(&nc1);
	check_for_error(ret);

	ret = gnutls_x509_name_constraints_init(&nc2);
	check_for_error(ret);

	set_name("foo.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	set_name("bar.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	set_name("sub.foo.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc2, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	ret = _gnutls_x509_name_constraints_merge(nc1, nc2);
	check_for_error(ret);

	set_name("foo.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("bar.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("sub.foo.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_ACCEPTED, &name);

	set_name("anothersub.foo.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	gnutls_x509_name_constraints_deinit(nc1);
	gnutls_x509_name_constraints_deinit(nc2);

	/* 4: test permitted constraints with empty intersection
	 *    almost identical to 2, but extra name constraint of different type
	 *    that remains after intersection
	 * NC1: permitted DNS three.example.com
	 *      permitted email redhat.com
	 * NC2: permitted DNS four.example.com
	 */
	suite = 4;

	ret = gnutls_x509_name_constraints_init(&nc1);
	check_for_error(ret);

	ret = gnutls_x509_name_constraints_init(&nc2);
	check_for_error(ret);

	set_name("three.example.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	set_name("redhat.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc1, GNUTLS_SAN_RFC822NAME, &name);
	check_for_error(ret);

	set_name("four.example.com", &name);
	ret = gnutls_x509_name_constraints_add_permitted(nc2, GNUTLS_SAN_DNSNAME, &name);
	check_for_error(ret);

	ret = _gnutls_x509_name_constraints_merge(nc1, nc2);
	check_for_error(ret);

	set_name("three.example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("four.example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("five.example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("example.com", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	set_name("org", &name);
	ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME, &name);
	check_test_result(suite, ret, NAME_REJECTED, &name);

	gnutls_x509_name_constraints_deinit(nc1);
	gnutls_x509_name_constraints_deinit(nc2);

	/* Test footer */

	if (debug)
		success("Test success.\n");
}