tofu.c (5892B)
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 snprintf(&fingerprint[i * 3], 4, "%02X%s", 83 cc->hash[i], i + 1 == sizeof(cc->hash) ? "" : ":"); 84 } 85 86 enum tofu_error error = TOFU_UNTRUSTED_CERT; 87 struct known_host *host = cc->store->known_hosts; 88 while (host) { 89 if (strcmp(host->host, cc->server_name) != 0) { 90 goto next; 91 } 92 if (strcmp(host->fingerprint, fingerprint) == 0) { 93 // Valid match in known hosts 94 return 0; 95 } 96 error = TOFU_FINGERPRINT_MISMATCH; 97 break; 98 next: 99 host = host->next; 100 } 101 102 switch (cc->store->callback(error, fingerprint, 103 host, cc->store->cb_data)) { 104 case TOFU_ASK: 105 assert(0); // Invariant 106 case TOFU_FAIL: 107 return BR_ERR_X509_NOT_TRUSTED; 108 case TOFU_TRUST_ONCE: 109 // No further action necessary 110 return 0; 111 case TOFU_TRUST_ALWAYS:; 112 FILE *f = fopen(cc->store->known_hosts_path, "a"); 113 if (!f) { 114 fprintf(stderr, "Error opening %s for writing: %s\n", 115 cc->store->known_hosts_path, strerror(errno)); 116 break; 117 }; 118 fprintf(f, "%s %s %s\n", cc->server_name, 119 "SHA-512", fingerprint); 120 fclose(f); 121 122 host = calloc(1, sizeof(struct known_host)); 123 host->host = strdup(cc->server_name); 124 host->fingerprint = strdup(fingerprint); 125 host->lineno = ++cc->store->lineno; 126 host->next = cc->store->known_hosts; 127 cc->store->known_hosts = host; 128 return 0; 129 } 130 131 assert(0); // Unreachable 132 } 133 134 static const br_x509_pkey * 135 xt_get_pkey(const br_x509_class *const *ctx, unsigned *usages) 136 { 137 struct x509_tofu_context *cc = (struct x509_tofu_context *)(void *)ctx; 138 if (cc->err != 0) { 139 return NULL; 140 } 141 if (usages) { 142 // XXX: BearSSL doesn't pull the usages out of the X.509 for us 143 *usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN; 144 } 145 return cc->pkey; 146 } 147 148 const br_x509_class xt_vtable = { 149 sizeof(struct x509_tofu_context), 150 xt_start_chain, 151 xt_start_cert, 152 xt_append, 153 xt_end_cert, 154 xt_end_chain, 155 xt_get_pkey, 156 }; 157 158 static void 159 x509_init_tofu(struct x509_tofu_context *ctx, struct gemini_tofu *store) 160 { 161 ctx->vtable = &xt_vtable; 162 ctx->store = store; 163 } 164 165 void 166 gemini_tofu_init(struct gemini_tofu *tofu, tofu_callback_t *cb, void *cb_data) 167 { 168 const struct pathspec paths[] = { 169 {.var = "GMNIDATA", .path = "/%s"}, 170 {.var = "XDG_DATA_HOME", .path = "/gmni/%s"}, 171 {.var = "HOME", .path = "/.local/share/gmni/%s"} 172 }; 173 char *path_fmt = getpath(paths, sizeof(paths) / sizeof(paths[0])); 174 char dname[PATH_MAX+1]; 175 size_t n = 0; 176 177 n = snprintf(tofu->known_hosts_path, 178 sizeof(tofu->known_hosts_path), 179 path_fmt, "known_hosts"); 180 free(path_fmt); 181 assert(n < sizeof(tofu->known_hosts_path)); 182 183 posix_dirname(tofu->known_hosts_path, dname); 184 if (mkdirs(dname, 0755) != 0) { 185 fprintf(stderr, "Error creating directory %s: %s\n", dname, 186 strerror(errno)); 187 return; 188 } 189 190 tofu->callback = cb; 191 tofu->cb_data = cb_data; 192 193 tofu->known_hosts = NULL; 194 195 x509_init_tofu(&tofu->x509_ctx, tofu); 196 197 br_x509_minimal_context _; // Discarded 198 br_ssl_client_init_full(&tofu->sc, &_, NULL, 0); 199 br_ssl_engine_set_x509(&tofu->sc.eng, &tofu->x509_ctx.vtable); 200 br_ssl_engine_set_buffer(&tofu->sc.eng, 201 &tofu->iobuf, sizeof(tofu->iobuf), 1); 202 203 FILE *f = fopen(tofu->known_hosts_path, "r"); 204 if (!f) { 205 return; 206 } 207 n = 0; 208 int lineno = 1; 209 char *line = NULL; 210 while (getline(&line, &n, f) != -1) { 211 int ln = strlen(line); 212 if (line[ln-1] == '\n') { 213 line[ln-1] = 0; 214 } 215 216 struct known_host *host = calloc(1, sizeof(struct known_host)); 217 char *tok = strtok(line, " "); 218 assert(tok); 219 host->host = strdup(tok); 220 221 tok = strtok(NULL, " "); 222 assert(tok); 223 if (strcmp(tok, "SHA-512") != 0) { 224 free(host->host); 225 free(host); 226 continue; 227 } 228 229 tok = strtok(NULL, " "); 230 assert(tok); 231 host->fingerprint = strdup(tok); 232 233 host->lineno = lineno++; 234 235 host->next = tofu->known_hosts; 236 tofu->known_hosts = host; 237 } 238 free(line); 239 fclose(f); 240 } 241 242 void 243 gemini_tofu_finish(struct gemini_tofu *tofu) 244 { 245 struct known_host *host = tofu->known_hosts; 246 while (host) { 247 struct known_host *tmp = host; 248 host = host->next; 249 free(tmp->host); 250 free(tmp->fingerprint); 251 free(tmp); 252 } 253 }