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