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