tofu.c (5988B)
1 #include <assert.h> 2 #include <bearssl.h> 3 #include <errno.h> 4 #include <gmni/gmni.h> 5 #include <gmni/tofu.h> 6 #include <libgen.h> 7 #include <limits.h> 8 #include <stdint.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include "util.h" 13 14 static void 15 xt_start_chain(const br_x509_class **ctx, const char *server_name) 16 { 17 struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx; 18 cc->server_name = server_name; 19 cc->err = 0; 20 cc->pkey = NULL; 21 } 22 23 static void 24 xt_start_cert(const br_x509_class **ctx, uint32_t length) 25 { 26 struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx; 27 if (cc->err != 0 || cc->pkey) { 28 return; 29 } 30 if (length == 0) { 31 cc->err = BR_ERR_X509_TRUNCATED; 32 return; 33 } 34 br_x509_decoder_init(&cc->decoder, NULL, NULL); 35 br_sha512_init(&cc->sha512); 36 } 37 38 static void 39 xt_append(const br_x509_class **ctx, const unsigned char *buf, size_t len) 40 { 41 struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx; 42 if (cc->err != 0 || cc->pkey) { 43 return; 44 } 45 br_x509_decoder_push(&cc->decoder, buf, len); 46 int err = br_x509_decoder_last_error(&cc->decoder); 47 if (err != 0 && err != BR_ERR_X509_TRUNCATED) { 48 cc->err = err; 49 } 50 br_sha512_update(&cc->sha512, buf, len); 51 } 52 53 static void 54 xt_end_cert(const br_x509_class **ctx) 55 { 56 struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx; 57 if (cc->err != 0) { 58 return; 59 } 60 int err = br_x509_decoder_last_error(&cc->decoder); 61 if (err != 0 && err != BR_ERR_X509_TRUNCATED) { 62 cc->err = err; 63 return; 64 } 65 cc->pkey = br_x509_decoder_get_pkey(&cc->decoder); 66 br_sha512_out(&cc->sha512, &cc->hash); 67 } 68 69 static unsigned 70 xt_end_chain(const br_x509_class **ctx) 71 { 72 struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx; 73 if (cc->err != 0) { 74 return (unsigned)cc->err; 75 } 76 if (!cc->pkey) { 77 return BR_ERR_X509_EMPTY_CHAIN; 78 } 79 80 char fingerprint[512 / 8 * 3]; 81 for (size_t i = 0; i < sizeof(cc->hash); ++i) { 82 size_t l = 4; 83 const char *trail = ":"; 84 /* Last entry. */ 85 if (i + 1 == sizeof(cc->hash)) { 86 l = 3; 87 trail = ""; 88 } 89 snprintf(&fingerprint[i * 3], l, "%02X%s", 90 cc->hash[i], trail); 91 } 92 93 enum tofu_error error = TOFU_UNTRUSTED_CERT; 94 struct known_host *host = cc->store->known_hosts; 95 while (host) { 96 if (strcmp(host->host, cc->server_name) != 0) { 97 goto next; 98 } 99 if (strcmp(host->fingerprint, fingerprint) == 0) { 100 // Valid match in known hosts 101 return 0; 102 } 103 error = TOFU_FINGERPRINT_MISMATCH; 104 break; 105 next: 106 host = host->next; 107 } 108 109 switch (cc->store->callback(error, fingerprint, 110 host, cc->store->cb_data)) { 111 case TOFU_ASK: 112 assert(0); // Invariant 113 case TOFU_FAIL: 114 return BR_ERR_X509_NOT_TRUSTED; 115 case TOFU_TRUST_ONCE: 116 // No further action necessary 117 return 0; 118 case TOFU_TRUST_ALWAYS:; 119 FILE *f = fopen(cc->store->known_hosts_path, "a"); 120 if (!f) { 121 fprintf(stderr, "Error opening %s for writing: %s\n", 122 cc->store->known_hosts_path, strerror(errno)); 123 break; 124 }; 125 fprintf(f, "%s %s %s\n", cc->server_name, 126 "SHA-512", fingerprint); 127 fclose(f); 128 129 host = calloc(1, sizeof(struct known_host)); 130 host->host = strdup(cc->server_name); 131 host->fingerprint = strdup(fingerprint); 132 host->lineno = ++cc->store->lineno; 133 host->next = cc->store->known_hosts; 134 cc->store->known_hosts = host; 135 return 0; 136 } 137 138 assert(0); // Unreachable 139 } 140 141 static const br_x509_pkey * 142 xt_get_pkey(const br_x509_class *const *ctx, unsigned *usages) 143 { 144 struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx; 145 if (cc->err != 0) { 146 return NULL; 147 } 148 if (usages) { 149 // XXX: BearSSL doesn't pull the usages out of the X.509 for us 150 *usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN; 151 } 152 return cc->pkey; 153 } 154 155 const br_x509_class xt_vtable = { 156 sizeof(struct x509_tofu_context), 157 xt_start_chain, 158 xt_start_cert, 159 xt_append, 160 xt_end_cert, 161 xt_end_chain, 162 xt_get_pkey, 163 }; 164 165 static void 166 x509_init_tofu(struct x509_tofu_context *ctx, struct gemini_tofu *store) 167 { 168 ctx->vtable = &xt_vtable; 169 ctx->store = store; 170 } 171 172 void 173 gemini_tofu_init(struct gemini_tofu *tofu, tofu_callback_t *cb, void *cb_data) 174 { 175 const struct pathspec paths[] = { 176 {.var = "GMNIDATA", .path = "/%s"}, 177 {.var = "XDG_DATA_HOME", .path = "/gmni/%s"}, 178 {.var = "HOME", .path = "/.local/share/gmni/%s"} 179 }; 180 char *path_fmt = getpath(paths, sizeof(paths) / sizeof(paths[0])); 181 char dname[PATH_MAX+1]; 182 size_t n = 0; 183 184 n = snprintf(tofu->known_hosts_path, 185 sizeof(tofu->known_hosts_path), 186 path_fmt, "known_hosts"); 187 free(path_fmt); 188 assert(n < sizeof(tofu->known_hosts_path)); 189 190 posix_dirname(tofu->known_hosts_path, dname); 191 if (mkdirs(dname, 0755) != 0) { 192 fprintf(stderr, "Error creating directory %s: %s\n", dname, 193 strerror(errno)); 194 return; 195 } 196 197 tofu->callback = cb; 198 tofu->cb_data = cb_data; 199 200 tofu->known_hosts = NULL; 201 202 x509_init_tofu(&tofu->x509_ctx, tofu); 203 204 br_x509_minimal_context _; // Discarded 205 br_ssl_client_init_full(&tofu->sc, &_, NULL, 0); 206 br_ssl_engine_set_x509(&tofu->sc.eng, &tofu->x509_ctx.vtable); 207 br_ssl_engine_set_buffer(&tofu->sc.eng, 208 &tofu->iobuf, sizeof(tofu->iobuf), 1); 209 210 FILE *f = fopen(tofu->known_hosts_path, "r"); 211 if (!f) { 212 return; 213 } 214 n = 0; 215 int lineno = 1; 216 char *line = NULL; 217 while (getline(&line, &n, f) != -1) { 218 int ln = strlen(line); 219 if (line[ln-1] == '\n') { 220 line[ln-1] = 0; 221 } 222 223 struct known_host *host = calloc(1, sizeof(struct known_host)); 224 char *tok = strtok(line, " "); 225 assert(tok); 226 host->host = strdup(tok); 227 228 tok = strtok(NULL, " "); 229 assert(tok); 230 if (strcmp(tok, "SHA-512") != 0) { 231 free(host->host); 232 free(host); 233 continue; 234 } 235 236 tok = strtok(NULL, " "); 237 assert(tok); 238 host->fingerprint = strdup(tok); 239 240 host->lineno = lineno++; 241 242 host->next = tofu->known_hosts; 243 tofu->known_hosts = host; 244 } 245 free(line); 246 fclose(f); 247 } 248 249 void 250 gemini_tofu_finish(struct gemini_tofu *tofu) 251 { 252 struct known_host *host = tofu->known_hosts; 253 while (host) { 254 struct known_host *tmp = host; 255 host = host->next; 256 free(tmp->host); 257 free(tmp->fingerprint); 258 free(tmp); 259 } 260 }