xref: /openbsd-src/usr.sbin/rpki-client/tal.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: tal.c,v 1.30 2021/04/01 06:43:23 claudio Exp $ */
2 /*
3  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <netinet/in.h>
19 #include <assert.h>
20 #include <ctype.h>
21 #include <err.h>
22 #include <libgen.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "extern.h"
28 
29 static int
30 tal_cmp(const void *a, const void *b)
31 {
32 	char * const *sa = a;
33 	char * const *sb = b;
34 
35 	return strcmp(*sa, *sb);
36 }
37 
38 /*
39  * Inner function for parsing RFC 8630 from a buffer.
40  * Returns a valid pointer on success, NULL otherwise.
41  * The pointer must be freed with tal_free().
42  */
43 static struct tal *
44 tal_parse_buffer(const char *fn, char *buf)
45 {
46 	char		*nl, *line, *f, *file = NULL;
47 	unsigned char	*der;
48 	size_t		 dersz;
49 	int		 rc = 0;
50 	struct tal	*tal = NULL;
51 	EVP_PKEY	*pkey = NULL;
52 
53 	if ((tal = calloc(1, sizeof(struct tal))) == NULL)
54 		err(1, NULL);
55 
56 	/* Begin with the URI section, comment section already removed. */
57 	while ((nl = strchr(buf, '\n')) != NULL) {
58 		line = buf;
59 		*nl = '\0';
60 
61 		/* advance buffer to next line */
62 		buf = nl + 1;
63 
64 		/* Zero-length line is end of section. */
65 		if (*line == '\0')
66 			break;
67 
68 		/* make sure only US-ASCII chars are in the URL */
69 		if (!valid_uri(line, nl - line, NULL)) {
70 			warnx("%s: invalid URI", fn);
71 			goto out;
72 		}
73 		/* Check that the URI is sensible */
74 		if (!(strncasecmp(line, "https://", 8) == 0 ||
75 		    strncasecmp(line, "rsync://", 8) == 0)) {
76 			warnx("%s: unsupported URL schema: %s", fn, line);
77 			goto out;
78 		}
79 		if (strcasecmp(nl - 4, ".cer")) {
80 			warnx("%s: not a certificate URL: %s", fn, line);
81 			goto out;
82 		}
83 
84 		/* Append to list of URIs. */
85 		tal->uri = reallocarray(tal->uri,
86 			tal->urisz + 1, sizeof(char *));
87 		if (tal->uri == NULL)
88 			err(1, NULL);
89 
90 		tal->uri[tal->urisz] = strdup(line);
91 		if (tal->uri[tal->urisz] == NULL)
92 			err(1, NULL);
93 		tal->urisz++;
94 
95 		f = strrchr(line, '/') + 1; /* can not fail */
96 		if (file) {
97 			if (strcmp(file, f)) {
98 				warnx("%s: URL with different file name %s, "
99 				    "instead of %s", fn, f, file);
100 				goto out;
101 			}
102 		} else
103 			file = f;
104 	}
105 
106 	if (tal->urisz == 0) {
107 		warnx("%s: no URIs in manifest part", fn);
108 		goto out;
109 	}
110 
111 	/* sort uri lexicographically so https:// is preferred */
112 	qsort(tal->uri, tal->urisz, sizeof(tal->uri[0]), tal_cmp);
113 
114 	/* Now the Base64-encoded public key. */
115 	if ((base64_decode(buf, &der, &dersz)) == -1) {
116 		warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
117 		    "bad public key", fn);
118 		goto out;
119 	}
120 
121 	tal->pkey = der;
122 	tal->pkeysz = dersz;
123 
124 	/* Make sure it's a valid public key. */
125 	pkey = d2i_PUBKEY(NULL, (const unsigned char **)&der, dersz);
126 	if (pkey == NULL) {
127 		cryptowarnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
128 		    "failed public key parse", fn);
129 		goto out;
130 	}
131 	rc = 1;
132 out:
133 	if (rc == 0) {
134 		tal_free(tal);
135 		tal = NULL;
136 	}
137 	EVP_PKEY_free(pkey);
138 	return tal;
139 }
140 
141 /*
142  * Parse a TAL from "buf" conformant to RFC 7730 originally from a file
143  * named "fn".
144  * Returns the encoded data or NULL on syntax failure.
145  */
146 struct tal *
147 tal_parse(const char *fn, char *buf)
148 {
149 	struct tal	*p;
150 	const char	*d;
151 	size_t		 dlen;
152 
153 	p = tal_parse_buffer(fn, buf);
154 	if (p == NULL)
155 		return NULL;
156 
157 	/* extract the TAL basename (without .tal suffix) */
158 	d = strrchr(fn, '/');
159 	if (d == NULL)
160 		d = fn;
161 	else
162 		d++;
163 	dlen = strlen(d);
164 	if (dlen > 4 && strcasecmp(d + dlen - 4, ".tal") == 0)
165 		dlen -= 4;
166 	if ((p->descr = strndup(d, dlen)) == NULL)
167 		err(1, NULL);
168 
169 	return p;
170 }
171 
172 /*
173  * Read the file named "file" into a returned, NUL-terminated buffer.
174  * This replaces CRLF terminators with plain LF, if found, and also
175  * elides document-leading comment lines starting with "#".
176  * Files may not exceeds 4096 bytes.
177  * This function exits on failure, so it always returns a buffer with
178  * TAL data.
179  */
180 char *
181 tal_read_file(const char *file)
182 {
183 	char		*nbuf, *line = NULL, *buf = NULL;
184 	FILE		*in;
185 	ssize_t		 n, i;
186 	size_t		 sz = 0, bsz = 0;
187 	int		 optcomment = 1;
188 
189 	if ((in = fopen(file, "r")) == NULL)
190 		err(1, "fopen: %s", file);
191 
192 	while ((n = getline(&line, &sz, in)) != -1) {
193 		/* replace CRLF with just LF */
194 		if (n > 1 && line[n - 1] == '\n' && line[n - 2] == '\r') {
195 			line[n - 2] = '\n';
196 			line[n - 1] = '\0';
197 			n--;
198 		}
199 		if (optcomment) {
200 			/* if this is comment, just eat the line */
201 			if (line[0] == '#')
202 				continue;
203 			optcomment = 0;
204 			/*
205 			 * Empty line is end of section and needs
206 			 * to be eaten as well.
207 			 */
208 			if (line[0] == '\n')
209 				continue;
210 		}
211 
212 		/* make sure every line is valid ascii */
213 		for (i = 0; i < n; i++)
214 			if (!isprint((unsigned char)line[i]) &&
215 			    !isspace((unsigned char)line[i]))
216 				errx(1, "getline: %s: "
217 				    "invalid content", file);
218 
219 		/* concat line to buf */
220 		if ((nbuf = realloc(buf, bsz + n + 1)) == NULL)
221 			err(1, NULL);
222 		if (buf == NULL)
223 			nbuf[0] = '\0';	/* initialize buffer */
224 		buf = nbuf;
225 		bsz += n + 1;
226 		if (strlcat(buf, line, bsz) >= bsz)
227 			errx(1, "strlcat overflow");
228 		/* limit the buffer size */
229 		if (bsz > 4096)
230 			errx(1, "%s: file too big", file);
231 	}
232 
233 	free(line);
234 	if (ferror(in))
235 		err(1, "getline: %s", file);
236 	fclose(in);
237 	if (buf == NULL)
238 		errx(1, "%s: no data", file);
239 	return buf;
240 }
241 
242 /*
243  * Free a TAL pointer.
244  * Safe to call with NULL.
245  */
246 void
247 tal_free(struct tal *p)
248 {
249 	size_t	 i;
250 
251 	if (p == NULL)
252 		return;
253 
254 	if (p->uri != NULL)
255 		for (i = 0; i < p->urisz; i++)
256 			free(p->uri[i]);
257 
258 	free(p->pkey);
259 	free(p->uri);
260 	free(p->descr);
261 	free(p);
262 }
263 
264 /*
265  * Buffer TAL parsed contents for writing.
266  * See tal_read() for the other side of the pipe.
267  */
268 void
269 tal_buffer(struct ibuf *b, const struct tal *p)
270 {
271 	size_t	 i;
272 
273 	io_buf_buffer(b, p->pkey, p->pkeysz);
274 	io_str_buffer(b, p->descr);
275 	io_simple_buffer(b, &p->urisz, sizeof(size_t));
276 
277 	for (i = 0; i < p->urisz; i++)
278 		io_str_buffer(b, p->uri[i]);
279 }
280 
281 /*
282  * Read parsed TAL contents from descriptor.
283  * See tal_buffer() for the other side of the pipe.
284  * A returned pointer must be freed with tal_free().
285  */
286 struct tal *
287 tal_read(int fd)
288 {
289 	size_t		 i;
290 	struct tal	*p;
291 
292 	if ((p = calloc(1, sizeof(struct tal))) == NULL)
293 		err(1, NULL);
294 
295 	io_buf_read_alloc(fd, (void **)&p->pkey, &p->pkeysz);
296 	assert(p->pkeysz > 0);
297 	io_str_read(fd, &p->descr);
298 	assert(p->descr);
299 	io_simple_read(fd, &p->urisz, sizeof(size_t));
300 	assert(p->urisz > 0);
301 
302 	if ((p->uri = calloc(p->urisz, sizeof(char *))) == NULL)
303 		err(1, NULL);
304 
305 	for (i = 0; i < p->urisz; i++) {
306 		io_str_read(fd, &p->uri[i]);
307 		assert(p->uri[i]);
308 	}
309 
310 	return p;
311 }
312