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 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 * 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 * 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 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 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 * 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