xref: /openbsd-src/usr.sbin/rpki-client/tal.c (revision 5a38ef86d0b61900239c7913d24a05e7b88a58f0)
1 /*	$OpenBSD: tal.c,v 1.34 2021/11/04 11:32:55 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, size_t len)
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 	int		 optcomment = 1;
53 
54 	if ((tal = calloc(1, sizeof(struct tal))) == NULL)
55 		err(1, NULL);
56 
57 	/* Begin with the URI section, comment section already removed. */
58 	while ((nl = memchr(buf, '\n', len)) != NULL) {
59 		line = buf;
60 
61 		/* advance buffer to next line */
62 		len -= nl + 1 - buf;
63 		buf = nl + 1;
64 
65 		/* replace LF and optional CR with NUL, point nl at first NUL */
66 		*nl = '\0';
67 		if (nl > line && nl[-1] == '\r') {
68 			nl[-1] = '\0';
69 			nl--;
70 		}
71 
72 		if (optcomment) {
73 			/* if this is a comment, just eat the line */
74 			if (line[0] == '#')
75 				continue;
76 			optcomment = 0;
77 		}
78 
79 		/* Zero-length line is end of section. */
80 		if (*line == '\0')
81 			break;
82 
83 		/* make sure only US-ASCII chars are in the URL */
84 		if (!valid_uri(line, nl - line, NULL)) {
85 			warnx("%s: invalid URI", fn);
86 			goto out;
87 		}
88 		/* Check that the URI is sensible */
89 		if (!(strncasecmp(line, "https://", 8) == 0 ||
90 		    strncasecmp(line, "rsync://", 8) == 0)) {
91 			warnx("%s: unsupported URL schema: %s", fn, line);
92 			goto out;
93 		}
94 		if (strcasecmp(nl - 4, ".cer")) {
95 			warnx("%s: not a certificate URL: %s", fn, line);
96 			goto out;
97 		}
98 
99 		/* Append to list of URIs. */
100 		tal->uri = reallocarray(tal->uri,
101 			tal->urisz + 1, sizeof(char *));
102 		if (tal->uri == NULL)
103 			err(1, NULL);
104 
105 		tal->uri[tal->urisz] = strdup(line);
106 		if (tal->uri[tal->urisz] == NULL)
107 			err(1, NULL);
108 		tal->urisz++;
109 
110 		f = strrchr(line, '/') + 1; /* can not fail */
111 		if (file) {
112 			if (strcmp(file, f)) {
113 				warnx("%s: URL with different file name %s, "
114 				    "instead of %s", fn, f, file);
115 				goto out;
116 			}
117 		} else
118 			file = f;
119 	}
120 
121 	if (tal->urisz == 0) {
122 		warnx("%s: no URIs in manifest part", fn);
123 		goto out;
124 	}
125 
126 	/* sort uri lexicographically so https:// is preferred */
127 	qsort(tal->uri, tal->urisz, sizeof(tal->uri[0]), tal_cmp);
128 
129 	/* Now the Base64-encoded public key. */
130 	if ((base64_decode(buf, len, &der, &dersz)) == -1) {
131 		warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
132 		    "bad public key", fn);
133 		goto out;
134 	}
135 
136 	tal->pkey = der;
137 	tal->pkeysz = dersz;
138 
139 	/* Make sure it's a valid public key. */
140 	pkey = d2i_PUBKEY(NULL, (const unsigned char **)&der, dersz);
141 	if (pkey == NULL) {
142 		cryptowarnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
143 		    "failed public key parse", fn);
144 		goto out;
145 	}
146 	rc = 1;
147 out:
148 	if (rc == 0) {
149 		tal_free(tal);
150 		tal = NULL;
151 	}
152 	EVP_PKEY_free(pkey);
153 	return tal;
154 }
155 
156 /*
157  * Parse a TAL from "buf" conformant to RFC 7730 originally from a file
158  * named "fn".
159  * Returns the encoded data or NULL on syntax failure.
160  */
161 struct tal *
162 tal_parse(const char *fn, char *buf, size_t len)
163 {
164 	struct tal	*p;
165 	const char	*d;
166 	size_t		 dlen;
167 
168 	p = tal_parse_buffer(fn, buf, len);
169 	if (p == NULL)
170 		return NULL;
171 
172 	/* extract the TAL basename (without .tal suffix) */
173 	d = strrchr(fn, '/');
174 	if (d == NULL)
175 		d = fn;
176 	else
177 		d++;
178 	dlen = strlen(d);
179 	if (dlen > 4 && strcasecmp(d + dlen - 4, ".tal") == 0)
180 		dlen -= 4;
181 	if ((p->descr = strndup(d, dlen)) == NULL)
182 		err(1, NULL);
183 
184 	return p;
185 }
186 
187 /*
188  * Free a TAL pointer.
189  * Safe to call with NULL.
190  */
191 void
192 tal_free(struct tal *p)
193 {
194 	size_t	 i;
195 
196 	if (p == NULL)
197 		return;
198 
199 	if (p->uri != NULL)
200 		for (i = 0; i < p->urisz; i++)
201 			free(p->uri[i]);
202 
203 	free(p->pkey);
204 	free(p->uri);
205 	free(p->descr);
206 	free(p);
207 }
208 
209 /*
210  * Buffer TAL parsed contents for writing.
211  * See tal_read() for the other side of the pipe.
212  */
213 void
214 tal_buffer(struct ibuf *b, const struct tal *p)
215 {
216 	size_t	 i;
217 
218 	io_simple_buffer(b, &p->id, sizeof(p->id));
219 	io_buf_buffer(b, p->pkey, p->pkeysz);
220 	io_str_buffer(b, p->descr);
221 	io_simple_buffer(b, &p->urisz, sizeof(p->urisz));
222 
223 	for (i = 0; i < p->urisz; i++)
224 		io_str_buffer(b, p->uri[i]);
225 }
226 
227 /*
228  * Read parsed TAL contents from descriptor.
229  * See tal_buffer() for the other side of the pipe.
230  * A returned pointer must be freed with tal_free().
231  */
232 struct tal *
233 tal_read(struct ibuf *b)
234 {
235 	size_t		 i;
236 	struct tal	*p;
237 
238 	if ((p = calloc(1, sizeof(struct tal))) == NULL)
239 		err(1, NULL);
240 
241 	io_read_buf(b, &p->id, sizeof(p->id));
242 	io_read_buf_alloc(b, (void **)&p->pkey, &p->pkeysz);
243 	io_read_str(b, &p->descr);
244 	io_read_buf(b, &p->urisz, sizeof(p->urisz));
245 	assert(p->pkeysz > 0);
246 	assert(p->descr);
247 	assert(p->urisz > 0);
248 
249 	if ((p->uri = calloc(p->urisz, sizeof(char *))) == NULL)
250 		err(1, NULL);
251 
252 	for (i = 0; i < p->urisz; i++) {
253 		io_read_str(b, &p->uri[i]);
254 		assert(p->uri[i]);
255 	}
256 
257 	return p;
258 }
259