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