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