/*	$OpenBSD: constraints.c,v 1.19 2026/04/13 17:04:23 beck Exp $	*/
/*
 * Copyright (c) 2020 Bob Beck <beck@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <err.h>
#include <string.h>

#include <openssl/safestack.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include "x509_internal.h"

#define FAIL(msg, ...)							\
do {									\
	fprintf(stderr, "[%s:%d] FAIL: ", __FILE__, __LINE__);		\
	fprintf(stderr, msg, ##__VA_ARGS__);				\
} while(0)

unsigned char *valid_hostnames[] = {
	"openbsd.org",
	"op3nbsd.org",
	"org",
	"3openbsd.com",
	"3-0penb-d.c-m",
	"a",
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	"open_bsd.org", /* because this is liberal */
	NULL,
};

unsigned char *valid_sandns_names[] = {
	"*.ca",
	"*.op3nbsd.org",
	"c*.openbsd.org",
	"foo.*.d*.c*.openbsd.org",
	NULL,
};

unsigned char *valid_domain_constraints[] = {
	"",
	".ca",
	".op3nbsd.org",
	".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	"www.openbsd.org",
	NULL,
};

unsigned char *valid_mbox_names[] = {
	"\"!#$%&\\\"*+-/=?\002^_`{|}~.\"@openbsd.org",
	"beck@openbsd.org",
	"beck@openbsd.org",
	"beck@op3nbsd.org",
	"beck@org",
	"beck@3openbsd.com",
	"beck@3-0penb-d.c-m",
	"bec@a",
	"beck@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
	"beck@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	"beck@open_bsd.org", /* because this is liberal */
	NULL,
};

unsigned char *invalid_hostnames[] = {
	"openbsd.org.",
	"openbsd..org",
	"openbsd.org-",
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a",
	"-p3nbsd.org",
	"openbs-.org",
	"openbsd\n.org",
	"open\177bsd.org",
	"open\255bsd.org",
	"*.openbsd.org",
	NULL,
};

unsigned char *invalid_sandns_names[] = {
	"",
	".",
	"*.a",
	"*.",
	"*.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
	".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a",
	"*.-p3nbsd.org",
	"*.*..openbsd.org",
	"*..openbsd.org",
	".openbsd.org",
	"c*c.openbsd.org",
	NULL,
};

unsigned char *invalid_mbox_names[] = {
	"beck@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
	"beck@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a",
	"beck@.-openbsd.org",
	"beck@.openbsd.org.",
	"beck@.a",
	"beck@.",
	"beck@",
	"beck@.ca",
	"@openbsd.org",
	NULL,
};

unsigned char *invalid_domain_constraints[] = {
	".",
	".a",
	"..",
	".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
	".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a",
	".-p3nbsd.org",
	"..openbsd.org",
	NULL,
};

unsigned char *invaliduri[] = {
	"https://-www.openbsd.org",
	"https://.www.openbsd.org/",
	"https://www.ope|nbsd.org%",
	"https://www.openbsd.org.#",
	"https://192.168.1.1./",
	"https://192.168.1.1|/",
	"https://.192.168.1.1/",
	"https://192.168..1.1/",
	"https://.2001:0DB8:AC10:FE01::/",
	"https://.2001:0DB8:AC10:FE01::|/",
	"///",
	"//",
	"/",
	"",
	NULL,
};

unsigned char *validuri[] = {
	"https://www.openbsd.org/meep/meep/meep/",
	"https://192.168.1.1/",
	"https://2001:0DB8:AC10:FE01::/",
	"https://192.168.1/",  /* Not an IP, but valid component */
	"https://999.999.999.999/", /* Not an IP, but valid component */
	NULL,
};

static int
test_valid_hostnames(void)
{
	int i, failure = 0;

	for (i = 0; valid_hostnames[i] != NULL; i++) {
		CBS cbs;
		CBS_init(&cbs, valid_hostnames[i], strlen(valid_hostnames[i]));
		if (!x509_constraints_valid_host(&cbs, 0)) {
			FAIL("Valid hostname '%s' rejected\n",
			    valid_hostnames[i]);
			failure = 1;
			goto done;
		}
		CBS_init(&cbs, valid_hostnames[i], strlen(valid_hostnames[i]));
		if (!x509_constraints_valid_sandns(&cbs)) {
			FAIL("Valid sandns '%s' rejected\n",
			    valid_hostnames[i]);
			failure = 1;
			goto done;
		}
	}

 done:
	return failure;
}

static int
test_valid_sandns_names(void)
{
	int i, failure = 0;
	for (i = 0; valid_sandns_names[i] != NULL; i++) {
		CBS cbs;
		CBS_init(&cbs, valid_sandns_names[i],
		    strlen(valid_sandns_names[i]));
		if (!x509_constraints_valid_sandns(&cbs)) {
			FAIL("Valid dnsname '%s' rejected\n",
			    valid_sandns_names[i]);
			failure = 1;
			goto done;
		}
	}

 done:
	return failure;
}

static int
test_valid_domain_constraints(void)
{
	int i, failure = 0;
	for (i = 0; valid_domain_constraints[i] != NULL; i++) {
		CBS cbs;
		CBS_init(&cbs, valid_domain_constraints[i],
		    strlen(valid_domain_constraints[i]));
		if (!x509_constraints_valid_domain_constraint(&cbs)) {
			FAIL("Valid dnsname '%s' rejected\n",
			    valid_domain_constraints[i]);
			failure = 1;
			goto done;
		}
	}

 done:
	return failure;
}

static int
test_valid_mbox_names(void)
{
	struct x509_constraints_name name = {0};
	int i, failure = 0;
	for (i = 0; valid_mbox_names[i] != NULL; i++) {
		CBS cbs;
		CBS_init(&cbs, valid_mbox_names[i],
		    strlen(valid_mbox_names[i]));
		if (!x509_constraints_parse_mailbox(&cbs, &name)) {
			FAIL("Valid mailbox name '%s' rejected\n",
			    valid_mbox_names[i]);
			failure = 1;
			goto done;
		}
		free(name.name);
		name.name = NULL;
		free(name.local);
		name.local = NULL;
	}

 done:
	return failure;
}

static int
test_invalid_hostnames(void)
{
	int i, failure = 0;
	char *nulhost = "www.openbsd.org\0";
	CBS cbs;

	for (i = 0; invalid_hostnames[i] != NULL; i++) {
		CBS_init(&cbs, invalid_hostnames[i],
		    strlen(invalid_hostnames[i]));
		if (x509_constraints_valid_host(&cbs, 0)) {
			FAIL("Invalid hostname '%s' accepted\n",
			    invalid_hostnames[i]);
			failure = 1;
			goto done;
		}
	}
	CBS_init(&cbs, nulhost, strlen(nulhost) + 1);
	if (x509_constraints_valid_host(&cbs, 0)) {
		FAIL("hostname with NUL byte accepted\n");
		failure = 1;
		goto done;
	}
	CBS_init(&cbs, nulhost, strlen(nulhost) + 1);
	if (x509_constraints_valid_sandns(&cbs)) {
		FAIL("sandns with NUL byte accepted\n");
		failure = 1;
		goto done;
	}

 done:
	return failure;
}

static int
test_invalid_sandns_names(void)
{
	int i, failure = 0;
	for (i = 0; invalid_sandns_names[i] != NULL; i++) {
		CBS cbs;
		CBS_init(&cbs, invalid_sandns_names[i],
		    strlen(invalid_sandns_names[i]));
		if (x509_constraints_valid_sandns(&cbs)) {
			FAIL("Valid dnsname '%s' rejected\n",
			    invalid_sandns_names[i]);
			failure = 1;
			goto done;
		}
	}

 done:
	return failure;
}

static int
test_invalid_mbox_names(void)
{
	int i, failure = 0;
	struct x509_constraints_name name = {0};
	for (i = 0; invalid_mbox_names[i] != NULL; i++) {
		CBS cbs;
		CBS_init(&cbs, invalid_mbox_names[i],
		    strlen(invalid_mbox_names[i]));
		if (x509_constraints_parse_mailbox(&cbs, &name)) {
			FAIL("invalid mailbox name '%s' accepted\n",
			    invalid_mbox_names[i]);
			failure = 1;
			goto done;
		}
		free(name.name);
		name.name = NULL;
		free(name.local);
		name.local = NULL;
	}

 done:
	return failure;
}

static int
test_invalid_domain_constraints(void)
{
	int i, failure = 0;
	for (i = 0; invalid_domain_constraints[i] != NULL; i++) {
		CBS cbs;
		CBS_init(&cbs, invalid_domain_constraints[i],
		    strlen(invalid_domain_constraints[i]));
		if (x509_constraints_valid_domain_constraint(&cbs)) {
			FAIL("invalid dnsname '%s' accepted\n",
			    invalid_domain_constraints[i]);
			failure = 1;
			goto done;
		}
	}

 done:
	return failure;
}

static int
test_invalid_uri(void)
{
	int j, failure = 0;
	char *hostpart = NULL;

	for (j = 0; invaliduri[j] != NULL; j++) {
		if (x509_constraints_uri_host(invaliduri[j],
		    strlen(invaliduri[j]), &hostpart) != 0) {
			FAIL("invalid URI '%s' accepted\n",
			    invaliduri[j]);
			failure = 1;
			goto done;
		}
		free(hostpart);
		hostpart = NULL;
	}

 done:
	return failure;
}

static int
test_valid_uri(void)
{
	int j, failure = 0;
	char *hostpart = NULL;

	for (j = 0; validuri[j] != NULL; j++) {
		if (x509_constraints_uri_host(validuri[j],
		    strlen(invaliduri[j]), &hostpart) == 0) {
			FAIL("Valid URI '%s' NOT accepted\n",
			    validuri[j]);
			failure = 1;
			goto done;
		}
		free(hostpart);
		hostpart = NULL;
	}

 done:
	return failure;
}

static int
test_constraints1(void)
{
	char *c;
	size_t cl;
	char *d;
	size_t dl;
	int failure = 0;
	int error = 0;
	int i, j;
	unsigned char *constraints[] = {
		".org",
		".openbsd.org",
		"www.openbsd.org",
		NULL,
	};
	unsigned char *failing[] = {
		".ca",
		"openbsd.ca",
		"org",
		NULL,
	};
	unsigned char *matching[] = {
		"www.openbsd.org",
		NULL,
	};
	unsigned char *matchinguri[] = {
		"https://www.openbsd.org",
		"https://www.openbsd.org/",
		"https://www.openbsd.org?",
		"https://www.openbsd.org#",
		"herp://beck@www.openbsd.org:",
		"spiffe://beck@www.openbsd.org/this/is/so/spiffe/",
		NULL,
	};
	unsigned char *failinguri[] = {
		"https://www.openbsd.ca",
		"https://www.freebsd.com/",
		"https://www.openbsd.net?",
		"https://org#",
		"herp://beck@org:",
		"///",
		"//",
		"/",
		"",
		NULL,
	};
	unsigned char *noauthority[] = {
		"urn:open62541.server.application",
		NULL,
	};
	for (i = 0; constraints[i] != NULL; i++) {
		char *constraint = constraints[i];
		size_t clen = strlen(constraints[i]);
		for (j = 0; matching[j] != NULL; j++) {
			if (!x509_constraints_domain(matching[j],
			    strlen(matching[j]), constraint, clen)) {
				FAIL("constraint '%s' should have matched"
				    " '%s'\n",
				    constraint, matching[j]);
				failure = 1;
				goto done;
			}
		}
		for (j = 0; matchinguri[j] != NULL; j++) {
			error = 0;
			if (!x509_constraints_uri(matchinguri[j],
			    strlen(matchinguri[j]), constraint, clen, &error)) {
				FAIL("constraint '%s' should have matched URI"
				    " '%s' (error %d)\n",
				    constraint, matchinguri[j], error);
				failure = 1;
				goto done;
			}
		}
		for (j = 0; failing[j] != NULL; j++) {
			if (x509_constraints_domain(failing[j],
			    strlen(failing[j]), constraint, clen)) {
				FAIL("constraint '%s' should not have matched"
				    " '%s'\n",
				    constraint, failing[j]);
				failure = 1;
				goto done;
			}
		}
		for (j = 0; failinguri[j] != NULL; j++) {
			error = 0;
			if (x509_constraints_uri(failinguri[j],
			    strlen(failinguri[j]), constraint, clen, &error)) {
				FAIL("constraint '%s' should not have matched URI"
				    " '%s' (error %d)\n",
				    constraint, failinguri[j], error);
				failure = 1;
				goto done;
			}
		}
		for (j = 0; noauthority[j] != NULL; j++) {
			char *hostpart = NULL;
			error = 0;
			if (!x509_constraints_uri_host(noauthority[j],
			    strlen(noauthority[j]), NULL) ||
			    !x509_constraints_uri_host(noauthority[j],
			    strlen(noauthority[j]), &hostpart)) {
				FAIL("name '%s' should parse as a URI",
				    noauthority[j]);
				failure = 1;
				free(hostpart);
				goto done;
			}
			free(hostpart);

			if (x509_constraints_uri(noauthority[j],
			    strlen(noauthority[j]), constraint, clen, &error)) {
				FAIL("constraint '%s' should not have matched URI"
				    " '%s' (error %d)\n",
				    constraint, failinguri[j], error);
				failure = 1;
				goto done;
			}
		}
	}
	c = ".openbsd.org";
	cl = strlen(".openbsd.org");
	d = "*.openbsd.org";
	dl = strlen("*.openbsd.org");
	if (!x509_constraints_domain(d, dl, c, cl)) {
		FAIL("constraint '%s' should have matched '%s'\n",
		    c, d);
		failure = 1;
		goto done;
	}
	c = "www.openbsd.org";
	cl = strlen("www.openbsd.org");
	if (x509_constraints_domain(d, dl, c, cl)) {
		FAIL("constraint '%s' should not have matched '%s'\n",
		    c, d);
		failure = 1;
		goto done;
	}
	c = "";
	cl = 0;
	if (!x509_constraints_domain(d, dl, c, cl)) {
		FAIL("constraint '%s' should have matched '%s'\n",
		    c, d);
		failure = 1;
		goto done;
	}
	c = "openbsd.org";
	cl = strlen("openbsd.org");
	d = "oopenbsd.org";
	dl = strlen("oopenbsd.org");
	if (x509_constraints_sandns(d, dl, c, cl)) {
		FAIL("constraint '%s' should not have matched '%s'\n",
		    c, d);
		failure = 1;
		goto done;
	}
	d = "*.openbsd.org";
	dl = strlen("*.openbsd.org");
	if (!x509_constraints_sandns(d, dl, c, cl)) {
		FAIL("constraint '%s' should have matched '%s'\n",
		    c, d);
		failure = 1;
		goto done;
	}
	c = "www.openbsd.org";
	cl = strlen("www.openbsd.org");
	if (x509_constraints_sandns(d, dl, c, cl)) {
		FAIL("constraint '%s' should not have matched '%s'\n",
		    c, d);
		failure = 1;
		goto done;
	}
	c = "";
	cl = 0;
	if (!x509_constraints_sandns(d, dl, c, cl)) {
		FAIL("constraint '%s' should have matched '%s'\n",
		    c, d);
		failure = 1;
		goto done;
	}
	/*
	 * Note that this *will* match, but we do not allow ".openbsd.org"
	 * as a sandns name - see invalid sandnsname tests above.
	 */
	c = ".openbsd.org";
	cl = strlen(".openbsd.org");
	d = ".openbsd.org";
	dl = strlen(".openbsd.org");
	if (!x509_constraints_sandns(d, dl, c, cl)) {
		FAIL("constraint '%s' should have matched '%s'\n",
		    c, d);
		failure = 1;
		goto done;
	}
 done:
	return failure;
}

int
main(int argc, char **argv)
{
	int failed = 0;

	failed |= test_valid_hostnames();
	failed |= test_invalid_hostnames();
	failed |= test_valid_sandns_names();
	failed |= test_invalid_sandns_names();
	failed |= test_valid_mbox_names();
	failed |= test_invalid_mbox_names();
	failed |= test_valid_domain_constraints();
	failed |= test_invalid_domain_constraints();
	failed |= test_invalid_uri();
	failed |= test_valid_uri();
	failed |= test_constraints1();

	return (failed);
}
