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