xref: /openbsd-src/usr.sbin/rpki-client/tal.c (revision 30a085025d55af7cc5799cf5d05a8d03f20d20ee)
1*30a08502Stb /*	$OpenBSD: tal.c,v 1.41 2024/11/13 12:51:04 tb Exp $ */
29a7e9e7fSjob /*
39a7e9e7fSjob  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
49a7e9e7fSjob  *
59a7e9e7fSjob  * Permission to use, copy, modify, and distribute this software for any
69a7e9e7fSjob  * purpose with or without fee is hereby granted, provided that the above
79a7e9e7fSjob  * copyright notice and this permission notice appear in all copies.
89a7e9e7fSjob  *
99a7e9e7fSjob  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
109a7e9e7fSjob  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
119a7e9e7fSjob  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
129a7e9e7fSjob  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
139a7e9e7fSjob  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
149a7e9e7fSjob  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
159a7e9e7fSjob  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
169a7e9e7fSjob  */
179a7e9e7fSjob 
189a7e9e7fSjob #include <assert.h>
199a7e9e7fSjob #include <err.h>
20a382efa2Sclaudio #include <libgen.h>
219a7e9e7fSjob #include <stdio.h>
229a7e9e7fSjob #include <stdlib.h>
239a7e9e7fSjob #include <string.h>
249a7e9e7fSjob 
259a7e9e7fSjob #include "extern.h"
269a7e9e7fSjob 
27e7f33a00Sclaudio static int
2808c1ca85Sclaudio tal_cmp(const void *a, const void *b)
2908c1ca85Sclaudio {
3008c1ca85Sclaudio 	char * const *sa = a;
3108c1ca85Sclaudio 	char * const *sb = b;
3208c1ca85Sclaudio 
3308c1ca85Sclaudio 	return strcmp(*sa, *sb);
3408c1ca85Sclaudio }
3508c1ca85Sclaudio 
369a7e9e7fSjob /*
3708c1ca85Sclaudio  * Inner function for parsing RFC 8630 from a buffer.
389a7e9e7fSjob  * Returns a valid pointer on success, NULL otherwise.
399a7e9e7fSjob  * The pointer must be freed with tal_free().
409a7e9e7fSjob  */
419a7e9e7fSjob static struct tal *
4241edc670Sclaudio tal_parse_buffer(const char *fn, char *buf, size_t len)
439a7e9e7fSjob {
4408c1ca85Sclaudio 	char		*nl, *line, *f, *file = NULL;
45e7f33a00Sclaudio 	unsigned char	*der;
46aebfeadbSclaudio 	size_t		 dersz;
47e7f33a00Sclaudio 	int		 rc = 0;
489a7e9e7fSjob 	struct tal	*tal = NULL;
499a7e9e7fSjob 	EVP_PKEY	*pkey = NULL;
5041edc670Sclaudio 	int		 optcomment = 1;
519a7e9e7fSjob 
529a7e9e7fSjob 	if ((tal = calloc(1, sizeof(struct tal))) == NULL)
5363749b15Sbenno 		err(1, NULL);
549a7e9e7fSjob 
55397f8a58Sclaudio 	/* Begin with the URI section, comment section already removed. */
5641edc670Sclaudio 	while ((nl = memchr(buf, '\n', len)) != NULL) {
57397f8a58Sclaudio 		line = buf;
5841edc670Sclaudio 
59397f8a58Sclaudio 		/* advance buffer to next line */
6041edc670Sclaudio 		len -= nl + 1 - buf;
61397f8a58Sclaudio 		buf = nl + 1;
629a7e9e7fSjob 
637cc8496aStb 		/* replace LF and optional CR with NUL, point nl at first NUL */
647cc8496aStb 		*nl = '\0';
657cc8496aStb 		if (nl > line && nl[-1] == '\r') {
667cc8496aStb 			nl[-1] = '\0';
677cc8496aStb 			nl--;
687cc8496aStb 		}
697cc8496aStb 
7041edc670Sclaudio 		if (optcomment) {
7141edc670Sclaudio 			/* if this is a comment, just eat the line */
7241edc670Sclaudio 			if (line[0] == '#')
7341edc670Sclaudio 				continue;
7441edc670Sclaudio 			optcomment = 0;
7541edc670Sclaudio 		}
7641edc670Sclaudio 
779a7e9e7fSjob 		/* Zero-length line is end of section. */
78397f8a58Sclaudio 		if (*line == '\0')
799a7e9e7fSjob 			break;
809a7e9e7fSjob 
813e23e2bcSclaudio 		/* make sure only US-ASCII chars are in the URL */
828c2eb288Sclaudio 		if (!valid_uri(line, nl - line, NULL)) {
833e23e2bcSclaudio 			warnx("%s: invalid URI", fn);
843e23e2bcSclaudio 			goto out;
853e23e2bcSclaudio 		}
8608c1ca85Sclaudio 		/* Check that the URI is sensible */
870610060dSjob 		if (!(strncasecmp(line, HTTPS_PROTO, HTTPS_PROTO_LEN) == 0 ||
880610060dSjob 		    strncasecmp(line, RSYNC_PROTO, RSYNC_PROTO_LEN) == 0)) {
8908c1ca85Sclaudio 			warnx("%s: unsupported URL schema: %s", fn, line);
9008c1ca85Sclaudio 			goto out;
9108c1ca85Sclaudio 		}
9208c1ca85Sclaudio 		if (strcasecmp(nl - 4, ".cer")) {
9308c1ca85Sclaudio 			warnx("%s: not a certificate URL: %s", fn, line);
9408c1ca85Sclaudio 			goto out;
9571d558daSclaudio 		}
9671d558daSclaudio 
979a7e9e7fSjob 		/* Append to list of URIs. */
989a7e9e7fSjob 		tal->uri = reallocarray(tal->uri,
99*30a08502Stb 		    tal->num_uris + 1, sizeof(char *));
1009a7e9e7fSjob 		if (tal->uri == NULL)
10163749b15Sbenno 			err(1, NULL);
1029a7e9e7fSjob 
103*30a08502Stb 		tal->uri[tal->num_uris] = strdup(line);
104*30a08502Stb 		if (tal->uri[tal->num_uris] == NULL)
10563749b15Sbenno 			err(1, NULL);
106*30a08502Stb 		tal->num_uris++;
1079a7e9e7fSjob 
10808c1ca85Sclaudio 		f = strrchr(line, '/') + 1; /* can not fail */
10908c1ca85Sclaudio 		if (file) {
11008c1ca85Sclaudio 			if (strcmp(file, f)) {
11108c1ca85Sclaudio 				warnx("%s: URL with different file name %s, "
11208c1ca85Sclaudio 				    "instead of %s", fn, f, file);
1139a7e9e7fSjob 				goto out;
114397f8a58Sclaudio 			}
11508c1ca85Sclaudio 		} else
11608c1ca85Sclaudio 			file = f;
117397f8a58Sclaudio 	}
1189a7e9e7fSjob 
119*30a08502Stb 	if (tal->num_uris == 0) {
120ae92bda2Sjob 		warnx("%s: no URIs in TAL file", fn);
1219a7e9e7fSjob 		goto out;
12208c1ca85Sclaudio 	}
12308c1ca85Sclaudio 
12408c1ca85Sclaudio 	/* sort uri lexicographically so https:// is preferred */
125*30a08502Stb 	qsort(tal->uri, tal->num_uris, sizeof(tal->uri[0]), tal_cmp);
1269a7e9e7fSjob 
127aebfeadbSclaudio 	/* Now the Base64-encoded public key. */
12841edc670Sclaudio 	if ((base64_decode(buf, len, &der, &dersz)) == -1) {
12980272c49Sderaadt 		warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
130aebfeadbSclaudio 		    "bad public key", fn);
1319a7e9e7fSjob 		goto out;
1329a7e9e7fSjob 	}
1339a7e9e7fSjob 
134e7f33a00Sclaudio 	tal->pkey = der;
135e7f33a00Sclaudio 	tal->pkeysz = dersz;
1369a7e9e7fSjob 
1379a7e9e7fSjob 	/* Make sure it's a valid public key. */
138e7f33a00Sclaudio 	pkey = d2i_PUBKEY(NULL, (const unsigned char **)&der, dersz);
1399a7e9e7fSjob 	if (pkey == NULL) {
140c0528901Stb 		warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
1419a7e9e7fSjob 		    "failed public key parse", fn);
1429a7e9e7fSjob 		goto out;
1439a7e9e7fSjob 	}
1449a7e9e7fSjob 	rc = 1;
1459a7e9e7fSjob out:
1469a7e9e7fSjob 	if (rc == 0) {
1479a7e9e7fSjob 		tal_free(tal);
1489a7e9e7fSjob 		tal = NULL;
1499a7e9e7fSjob 	}
1509a7e9e7fSjob 	EVP_PKEY_free(pkey);
1519a7e9e7fSjob 	return tal;
1529a7e9e7fSjob }
1539a7e9e7fSjob 
1549a7e9e7fSjob /*
155fec153cdSclaudio  * Parse a TAL from "buf" conformant to RFC 7730 originally from a file
156fec153cdSclaudio  * named "fn".
157fec153cdSclaudio  * Returns the encoded data or NULL on syntax failure.
1589a7e9e7fSjob  */
1599a7e9e7fSjob struct tal *
16041edc670Sclaudio tal_parse(const char *fn, char *buf, size_t len)
1619a7e9e7fSjob {
1629a7e9e7fSjob 	struct tal	*p;
163b55f176aSderaadt 	const char	*d;
164a382efa2Sclaudio 	size_t		 dlen;
1659a7e9e7fSjob 
16641edc670Sclaudio 	p = tal_parse_buffer(fn, buf, len);
1676978176aSclaudio 	if (p == NULL)
1686978176aSclaudio 		return NULL;
169a382efa2Sclaudio 
170a382efa2Sclaudio 	/* extract the TAL basename (without .tal suffix) */
171b55f176aSderaadt 	d = strrchr(fn, '/');
172a382efa2Sclaudio 	if (d == NULL)
173b55f176aSderaadt 		d = fn;
174b55f176aSderaadt 	else
175b55f176aSderaadt 		d++;
176a382efa2Sclaudio 	dlen = strlen(d);
177b6929da3Sclaudio 	if (dlen > 4 && strcasecmp(d + dlen - 4, ".tal") == 0)
178a382efa2Sclaudio 		dlen -= 4;
179cf76813dSclaudio 	if ((p->descr = strndup(d, dlen)) == NULL)
18063749b15Sbenno 		err(1, NULL);
181a382efa2Sclaudio 
1829a7e9e7fSjob 	return p;
1839a7e9e7fSjob }
1849a7e9e7fSjob 
185fec153cdSclaudio /*
1869a7e9e7fSjob  * Free a TAL pointer.
1879a7e9e7fSjob  * Safe to call with NULL.
1889a7e9e7fSjob  */
1899a7e9e7fSjob void
1909a7e9e7fSjob tal_free(struct tal *p)
1919a7e9e7fSjob {
1929a7e9e7fSjob 	size_t	 i;
1939a7e9e7fSjob 
1949a7e9e7fSjob 	if (p == NULL)
1959a7e9e7fSjob 		return;
1969a7e9e7fSjob 
1979a7e9e7fSjob 	if (p->uri != NULL)
198*30a08502Stb 		for (i = 0; i < p->num_uris; i++)
1999a7e9e7fSjob 			free(p->uri[i]);
2009a7e9e7fSjob 
2019a7e9e7fSjob 	free(p->pkey);
2029a7e9e7fSjob 	free(p->uri);
203a382efa2Sclaudio 	free(p->descr);
2049a7e9e7fSjob 	free(p);
2059a7e9e7fSjob }
2069a7e9e7fSjob 
2079a7e9e7fSjob /*
2089a7e9e7fSjob  * Buffer TAL parsed contents for writing.
2099a7e9e7fSjob  * See tal_read() for the other side of the pipe.
2109a7e9e7fSjob  */
2119a7e9e7fSjob void
21208db1177Sclaudio tal_buffer(struct ibuf *b, const struct tal *p)
2139a7e9e7fSjob {
2149a7e9e7fSjob 	size_t	 i;
2159a7e9e7fSjob 
216dc508150Sclaudio 	io_simple_buffer(b, &p->id, sizeof(p->id));
21708db1177Sclaudio 	io_buf_buffer(b, p->pkey, p->pkeysz);
21808db1177Sclaudio 	io_str_buffer(b, p->descr);
219*30a08502Stb 	io_simple_buffer(b, &p->num_uris, sizeof(p->num_uris));
2209a7e9e7fSjob 
221*30a08502Stb 	for (i = 0; i < p->num_uris; i++)
22208db1177Sclaudio 		io_str_buffer(b, p->uri[i]);
2239a7e9e7fSjob }
2249a7e9e7fSjob 
2259a7e9e7fSjob /*
2269a7e9e7fSjob  * Read parsed TAL contents from descriptor.
2279a7e9e7fSjob  * See tal_buffer() for the other side of the pipe.
2289a7e9e7fSjob  * A returned pointer must be freed with tal_free().
2299a7e9e7fSjob  */
2309a7e9e7fSjob struct tal *
2317eb79a4aSclaudio tal_read(struct ibuf *b)
2329a7e9e7fSjob {
2339a7e9e7fSjob 	size_t		 i;
2349a7e9e7fSjob 	struct tal	*p;
2359a7e9e7fSjob 
2369a7e9e7fSjob 	if ((p = calloc(1, sizeof(struct tal))) == NULL)
23763749b15Sbenno 		err(1, NULL);
2389a7e9e7fSjob 
239dc508150Sclaudio 	io_read_buf(b, &p->id, sizeof(p->id));
2407eb79a4aSclaudio 	io_read_buf_alloc(b, (void **)&p->pkey, &p->pkeysz);
2417eb79a4aSclaudio 	io_read_str(b, &p->descr);
242*30a08502Stb 	io_read_buf(b, &p->num_uris, sizeof(p->num_uris));
2439a7e9e7fSjob 	assert(p->pkeysz > 0);
24452c8fec2Sclaudio 	assert(p->descr);
245*30a08502Stb 	assert(p->num_uris > 0);
2469a7e9e7fSjob 
247*30a08502Stb 	if ((p->uri = calloc(p->num_uris, sizeof(char *))) == NULL)
24863749b15Sbenno 		err(1, NULL);
2499a7e9e7fSjob 
250*30a08502Stb 	for (i = 0; i < p->num_uris; i++) {
2517eb79a4aSclaudio 		io_read_str(b, &p->uri[i]);
25252c8fec2Sclaudio 		assert(p->uri[i]);
25352c8fec2Sclaudio 	}
2549a7e9e7fSjob 
2559a7e9e7fSjob 	return p;
2569a7e9e7fSjob }
257