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