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