xref: /openbsd-src/usr.sbin/rpki-client/tal.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: tal.c,v 1.22 2020/10/11 12:39:25 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 <limits.h>
23 #include <libgen.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "extern.h"
29 
30 static int
31 base64_decode(const unsigned char *in, size_t inlen, unsigned char **out,
32    size_t *outlen)
33 {
34 	static EVP_ENCODE_CTX *ctx;
35 	unsigned char *to;
36 	int tolen;
37 
38 	if (ctx == NULL && (ctx = EVP_ENCODE_CTX_new()) == NULL)
39 		err(1, "EVP_ENCODE_CTX_new");
40 
41 	*out = NULL;
42 	*outlen = 0;
43 
44 	if (inlen >= INT_MAX - 3)
45 		return -1;
46 	tolen = ((inlen + 3) / 4) * 3 + 1;
47 	if ((to = malloc(tolen)) == NULL)
48 		return -1;
49 
50 	EVP_DecodeInit(ctx);
51 	if (EVP_DecodeUpdate(ctx, to, &tolen, in, inlen) == -1)
52 		goto fail;
53 	*outlen = tolen;
54 	if (EVP_DecodeFinal(ctx, to + tolen, &tolen) == -1)
55 		goto fail;
56 	*outlen += tolen;
57 	*out = to;
58 	return 0;
59 
60 fail:
61 	free(to);
62 	return -1;
63 }
64 
65 static int
66 tal_cmp(const void *a, const void *b)
67 {
68 	char * const *sa = a;
69 	char * const *sb = b;
70 
71 	return strcmp(*sa, *sb);
72 }
73 
74 /*
75  * Inner function for parsing RFC 8630 from a buffer.
76  * Returns a valid pointer on success, NULL otherwise.
77  * The pointer must be freed with tal_free().
78  */
79 static struct tal *
80 tal_parse_buffer(const char *fn, char *buf)
81 {
82 	char		*nl, *line, *f, *file = NULL;
83 	unsigned char	*der;
84 	size_t		 sz, dersz;
85 	int		 rc = 0;
86 	struct tal	*tal = NULL;
87 	EVP_PKEY	*pkey = NULL;
88 
89 	if ((tal = calloc(1, sizeof(struct tal))) == NULL)
90 		err(1, NULL);
91 
92 	/* Begin with the URI section, comment section already removed. */
93 	while ((nl = strchr(buf, '\n')) != NULL) {
94 		line = buf;
95 		*nl = '\0';
96 
97 		/* advance buffer to next line */
98 		buf = nl + 1;
99 
100 		/* Zero-length line is end of section. */
101 		if (*line == '\0')
102 			break;
103 
104 		/* Check that the URI is sensible */
105 		if (!(strncasecmp(line, "https://", 8) == 0 ||
106 		    strncasecmp(line, "rsync://", 8) == 0)) {
107 			warnx("%s: unsupported URL schema: %s", fn, line);
108 			goto out;
109 		}
110 		if (strcasecmp(nl - 4, ".cer")) {
111 			warnx("%s: not a certificate URL: %s", fn, line);
112 			goto out;
113 		}
114 
115 		/* Append to list of URIs. */
116 		tal->uri = reallocarray(tal->uri,
117 			tal->urisz + 1, sizeof(char *));
118 		if (tal->uri == NULL)
119 			err(1, NULL);
120 
121 		tal->uri[tal->urisz] = strdup(line);
122 		if (tal->uri[tal->urisz] == NULL)
123 			err(1, NULL);
124 		tal->urisz++;
125 
126 		f = strrchr(line, '/') + 1; /* can not fail */
127 		if (file) {
128 			if (strcmp(file, f)) {
129 				warnx("%s: URL with different file name %s, "
130 				    "instead of %s", fn, f, file);
131 				goto out;
132 			}
133 		} else
134 			file = f;
135 	}
136 
137 	if (tal->urisz == 0) {
138 		warnx("%s: no URIs in manifest part", fn);
139 		goto out;
140 	}
141 
142 	/* sort uri lexicographically so https:// is preferred */
143 	qsort(tal->uri, tal->urisz, sizeof(tal->uri[0]), tal_cmp);
144 
145 	sz = strlen(buf);
146 	if (sz == 0) {
147 		warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
148 		    "zero-length public key", fn);
149 		goto out;
150 	}
151 
152 	/* Now the BASE64-encoded public key. */
153 	if ((base64_decode(buf, sz, &der, &dersz)) == -1)
154 		errx(1, "base64 decode");
155 
156 	tal->pkey = der;
157 	tal->pkeysz = dersz;
158 
159 	/* Make sure it's a valid public key. */
160 	pkey = d2i_PUBKEY(NULL, (const unsigned char **)&der, dersz);
161 	if (pkey == NULL) {
162 		cryptowarnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
163 		    "failed public key parse", fn);
164 		goto out;
165 	}
166 	rc = 1;
167 out:
168 	if (rc == 0) {
169 		tal_free(tal);
170 		tal = NULL;
171 	}
172 	EVP_PKEY_free(pkey);
173 	return tal;
174 }
175 
176 /*
177  * Parse a TAL from "buf" conformant to RFC 7730 originally from a file
178  * named "fn".
179  * Returns the encoded data or NULL on syntax failure.
180  */
181 struct tal *
182 tal_parse(const char *fn, char *buf)
183 {
184 	struct tal	*p;
185 	const char	*d;
186 	size_t		 dlen;
187 
188 	p = tal_parse_buffer(fn, buf);
189 	if (p == NULL)
190 		return NULL;
191 
192 	/* extract the TAL basename (without .tal suffix) */
193 	d = strrchr(fn, '/');
194 	if (d == NULL)
195 		d = fn;
196 	else
197 		d++;
198 	dlen = strlen(d);
199 	if (strcasecmp(d + dlen - 4, ".tal") == 0)
200 		dlen -= 4;
201 	if ((p->descr = malloc(dlen + 1)) == NULL)
202 		err(1, NULL);
203 	memcpy(p->descr, d, dlen);
204 	p->descr[dlen] = '\0';
205 
206 	return p;
207 }
208 
209 /*
210  * Read the file named "file" into a returned, NUL-terminated buffer.
211  * This replaces CRLF terminators with plain LF, if found, and also
212  * elides document-leading comment lines starting with "#".
213  * Files may not exceeds 4096 bytes.
214  * This function exits on failure, so it always returns a buffer with
215  * TAL data.
216  */
217 char *
218 tal_read_file(const char *file)
219 {
220 	char		*nbuf, *line = NULL, *buf = NULL;
221 	FILE		*in;
222 	ssize_t		 n, i;
223 	size_t		 sz = 0, bsz = 0;
224 	int		 optcomment = 1;
225 
226 	if ((in = fopen(file, "r")) == NULL)
227 		err(1, "fopen: %s", file);
228 
229 	while ((n = getline(&line, &sz, in)) != -1) {
230 		/* replace CRLF with just LF */
231 		if (n > 1 && line[n - 1] == '\n' && line[n - 2] == '\r') {
232 			line[n - 2] = '\n';
233 			line[n - 1] = '\0';
234 			n--;
235 		}
236 		if (optcomment) {
237 			/* if this is comment, just eat the line */
238 			if (line[0] == '#')
239 				continue;
240 			optcomment = 0;
241 			/*
242 			 * Empty line is end of section and needs
243 			 * to be eaten as well.
244 			 */
245 			if (line[0] == '\n')
246 				continue;
247 		}
248 
249 		/* make sure every line is valid ascii */
250 		for (i = 0; i < n; i++)
251 			if (!isprint((unsigned char)line[i]) &&
252 			    !isspace((unsigned char)line[i]))
253 				errx(1, "getline: %s: "
254 				    "invalid content", file);
255 
256 		/* concat line to buf */
257 		if ((nbuf = realloc(buf, bsz + n + 1)) == NULL)
258 			err(1, NULL);
259 		if (buf == NULL)
260 			nbuf[0] = '\0';	/* initialize buffer */
261 		buf = nbuf;
262 		bsz += n + 1;
263 		if (strlcat(buf, line, bsz) >= bsz)
264 			errx(1, "strlcat overflow");
265 		/* limit the buffer size */
266 		if (bsz > 4096)
267 			errx(1, "%s: file too big", file);
268 	}
269 
270 	free(line);
271 	if (ferror(in))
272 		err(1, "getline: %s", file);
273 	fclose(in);
274 	if (buf == NULL)
275 		errx(1, "%s: no data", file);
276 	return buf;
277 }
278 
279 /*
280  * Free a TAL pointer.
281  * Safe to call with NULL.
282  */
283 void
284 tal_free(struct tal *p)
285 {
286 	size_t	 i;
287 
288 	if (p == NULL)
289 		return;
290 
291 	if (p->uri != NULL)
292 		for (i = 0; i < p->urisz; i++)
293 			free(p->uri[i]);
294 
295 	free(p->pkey);
296 	free(p->uri);
297 	free(p->descr);
298 	free(p);
299 }
300 
301 /*
302  * Buffer TAL parsed contents for writing.
303  * See tal_read() for the other side of the pipe.
304  */
305 void
306 tal_buffer(char **b, size_t *bsz, size_t *bmax, const struct tal *p)
307 {
308 	size_t	 i;
309 
310 	io_buf_buffer(b, bsz, bmax, p->pkey, p->pkeysz);
311 	io_str_buffer(b, bsz, bmax, p->descr);
312 	io_simple_buffer(b, bsz, bmax, &p->urisz, sizeof(size_t));
313 
314 	for (i = 0; i < p->urisz; i++)
315 		io_str_buffer(b, bsz, bmax, p->uri[i]);
316 }
317 
318 /*
319  * Read parsed TAL contents from descriptor.
320  * See tal_buffer() for the other side of the pipe.
321  * A returned pointer must be freed with tal_free().
322  */
323 struct tal *
324 tal_read(int fd)
325 {
326 	size_t		 i;
327 	struct tal	*p;
328 
329 	if ((p = calloc(1, sizeof(struct tal))) == NULL)
330 		err(1, NULL);
331 
332 	io_buf_read_alloc(fd, (void **)&p->pkey, &p->pkeysz);
333 	assert(p->pkeysz > 0);
334 	io_str_read(fd, &p->descr);
335 	io_simple_read(fd, &p->urisz, sizeof(size_t));
336 	assert(p->urisz > 0);
337 
338 	if ((p->uri = calloc(p->urisz, sizeof(char *))) == NULL)
339 		err(1, NULL);
340 
341 	for (i = 0; i < p->urisz; i++)
342 		io_str_read(fd, &p->uri[i]);
343 
344 	return p;
345 }
346