0 /* See LICENSE file for copyright and license details. */
1 #include <stdlib.h>
2 #ifdef __linux__
3 #define OPENSSL_API_COMPAT 0x10101000L
4 #endif
5 #include <openssl/bn.h>
6 #include <openssl/evp.h>
7 #include <openssl/rsa.h>
8 #include <stdio.h>
9 #include <tls.h>
10 #include <openssl/x509.h>
11 #include <openssl/pem.h>
12 #include <openssl/err.h>
13 #include <string.h>
14 #include <strings.h>
15 #include <pwd.h>
16 #include <unistd.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 #ifdef __linux__
20 #include <sys/random.h>
21 #endif
22 #include "gemini.h"
23 #include "sandbox.h"
24 #include "str.h"
25 #include "xdg.h"
26 #include "util.h"
27
28 char home_path[1024];
29 char download_path[1024];
30 char config_path[1024];
31 const char* download_str = "Downloads";
32
33 int home_fd = -1;
34 int gethomefd() {
35 if (home_fd > -1)
36 return home_fd;
37 struct passwd *pw = getpwuid(geteuid());
38 if (!pw) return -1;
39 home_fd = open(pw->pw_dir, O_DIRECTORY);
40 STRLCPY(home_path, pw->pw_dir);
41 #ifndef DISABLE_XDG
42 if (!xdg_path(download_path, sizeof(download_path))) {
43 return home_fd;
44 }
45 #endif
46 snprintf(download_path, sizeof(download_path), "%s/%s",
47 home_path, download_str);
48 return home_fd;
49 }
50
51 int download_fd = -1;
52 int getdownloadfd() {
53 if (download_fd > -1)
54 return download_fd;
55 if (home_fd == -1 && gethomefd() == -1)
56 return -1;
57 #ifndef DISABLE_XDG
58 download_fd = open(download_path, O_DIRECTORY);
59 if (download_fd > -1) return download_fd;
60 #endif
61 download_fd = openat(home_fd, download_str, O_DIRECTORY);
62 if (download_fd > -1) return download_fd;
63
64 if (mkdirat(home_fd, download_str, 0700))
65 return -1;
66
67 download_fd = openat(home_fd, download_str, O_DIRECTORY);
68 if (download_fd > -1) return download_fd;
69
70 return -1;
71 }
72
73 int config_fd = -1;
74 int getconfigfd() {
75 if (config_fd > -1)
76 return config_fd;
77 if (home_fd == -1 && gethomefd() == -1)
78 return -1;
79 // check if .config exists first
80 int fd = openat(home_fd, ".config", O_DIRECTORY);
81 if (fd < 0) {
82 mkdirat(home_fd, ".config", 0700);
83 fd = openat(home_fd, ".config", O_DIRECTORY);
84 if (fd < 0)
85 return -1;
86 }
87 config_fd = openat(fd, "vgmi", O_DIRECTORY);
88 if (config_fd < 0) {
89 mkdirat(fd, "vgmi", 0700);
90 config_fd = openat(fd, "vgmi", O_DIRECTORY);
91 if (config_fd < 0)
92 return -1;
93 }
94 close(fd);
95 snprintf(config_path, sizeof(config_path), "%s/%s",
96 home_path, "/.config/vgmi");
97 return config_fd;
98 }
99
100 int cert_getpath(const char* host, char* crt, size_t crt_len,
101 char* key, size_t key_len) {
102 int len = strnlen(host, 1024);
103 if (strlcpy(crt, host, crt_len) >= crt_len - 4)
104 goto getpath_overflow;
105 if (strlcpy(key, host, key_len) >= key_len - 4)
106 goto getpath_overflow;
107 if (strlcpy(&crt[len], ".crt", crt_len - len) + len >= crt_len)
108 goto getpath_overflow;
109 if (strlcpy(&key[len], ".key", key_len - len) + len >= key_len)
110 goto getpath_overflow;
111 return len + 4;
112 getpath_overflow:
113 snprintf(client.tab->error,
114 sizeof(client.tab->error),
115 "The hostname is too long %s", host);
116 return -1;
117 }
118
119 #ifdef SANDBOX_SUN
120 #undef cert_create
121 #endif
122 int cert_create(char* host, char* error, int errlen) {
123 FILE* f = NULL;
124 int fd;
125 int ret = 1;
126 EVP_PKEY* pkey;
127 pkey = EVP_PKEY_new();
128 RSA* rsa = RSA_new();
129 BIGNUM* bne = BN_new();
130 X509* x509 = X509_new();
131 if (BN_set_word(bne, 65537) != 1) goto failed;
132 if (RSA_generate_key_ex(rsa, 2048, bne, NULL) != 1) goto failed;
133
134 EVP_PKEY_assign_RSA(pkey, rsa);
135 int id;
136 #ifdef __linux__
137 getrandom(&id, sizeof(id), GRND_RANDOM);
138 #else
139 arc4random_buf(&id, sizeof(id));
140 #endif
141 if (ASN1_INTEGER_set(X509_get_serialNumber(x509), id) != 1)
142 goto failed;
143
144 X509_gmtime_adj(X509_getm_notBefore(x509), 0);
145 X509_gmtime_adj(X509_getm_notAfter(x509), 157680000L);
146
147 if (X509_set_pubkey(x509, pkey) != 1) goto failed;
148
149 X509_NAME* name = X509_get_subject_name(x509);
150 if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
151 (unsigned char*)host, -1, -1, 0) != 1)
152 goto failed;
153
154 if (X509_set_issuer_name(x509, name) != 1) goto failed;
155 if (X509_sign(x509, pkey, EVP_sha1()) == 0) goto failed;
156
157 char key[1024];
158 char crt[1024];
159 if (cert_getpath(host, crt, sizeof(crt), key, sizeof(key)) == -1)
160 goto skip_error;
161
162 // Key
163 fd = openat(config_fd, key, O_CREAT|O_WRONLY, 0600);
164 if (fd < 0) {
165 snprintf(error, errlen, "Failed to open %s : %s",
166 key, strerror(errno));
167 goto skip_error;
168 }
169 f = fdopen(fd, "wb");
170 #ifdef SANDBOX_FREEBSD
171 if (makefd_writeonly(fd)) {
172 snprintf(error, errlen, "Failed to limit %s", key);
173 goto skip_error;
174 }
175 #endif
176 if (!f) {
177 snprintf(error, errlen, "Failed to write to %s : %s",
178 key, strerror(errno));
179 goto skip_error;
180 }
181 if (PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL) != 1)
182 goto failed;
183 fclose(f);
184
185 // Certificate
186 fd = openat(config_fd, crt, O_CREAT|O_WRONLY, 0600);
187 if (fd < 0) {
188 snprintf(error, errlen, "Failed to open %s", crt);
189 goto skip_error;
190 }
191 f = fdopen(fd, "wb");
192 #ifdef SANDBOX_FREEBSD
193 if (makefd_writeonly(fd)) {
194 snprintf(error, errlen, "Failed to limit %s", crt);
195 goto skip_error;
196 }
197 #endif
198 if (!f) {
199 snprintf(error, errlen, "Failed to write to %s", crt);
200 goto skip_error;
201 }
202 if (PEM_write_X509(f, x509) != 1)
203 goto failed;
204 fclose(f);
205
206 f = NULL;
207 ret = 0;
208 goto skip_error;
209 failed:
210 snprintf(error, errlen, "Failed to generate certificate");
211 skip_error:
212 if (f) fclose(f);
213 BN_free(bne);
214 EVP_PKEY_free(pkey);
215 X509_free(x509);
216 //RSA_free(rsa);
217 //if (ret) client.input.error = 1;
218 return ret;
219 }
220
221 int fatalI();
222 void fatal();
223
224 struct cert {
225 struct cert* next;
226 char hash[256];
227 char host[1024];
228 unsigned long long start;
229 unsigned long long end;
230 };
231 struct cert* first_cert = NULL;
232 struct cert* last_cert = NULL;
233
234 void cert_add(char* host, const char* hash, unsigned long long start,
235 unsigned long long end) {
236 struct cert* cert_ptr = malloc(sizeof(struct cert));
237 if (!cert_ptr) {
238 fatal();
239 return;
240 }
241 cert_ptr->next = NULL;
242 if (!cert_ptr) {
243 fatal();
244 return;
245 }
246 if (!first_cert) {
247 last_cert = first_cert = cert_ptr;
248 } else {
249 last_cert->next = cert_ptr;
250 last_cert = cert_ptr;
251 }
252 STRLCPY(last_cert->hash, hash);
253 STRLCPY(last_cert->host, host);
254 last_cert->start = start;
255 last_cert->end = end;
256 }
257
258 int cert_load() {
259 config_fd = getconfigfd();
260 if (config_fd < 0) return -1;
261 int known_hosts = openat(config_fd, "known_hosts", 0);
262 if (known_hosts < 0) return 0;
263 FILE* f = fdopen(known_hosts, "r");
264 if (!f)
265 return -1;
266 fseek(f, 0, SEEK_END);
267 size_t length = ftell(f);
268 fseek(f, 0, SEEK_SET);
269 char* data = malloc(length);
270 if (!data) return fatalI();
271 if (fread(data, 1, length, f) != length) {
272 fclose(f);
273 return -1;
274 }
275 fclose(f);
276 char* ptr = data;
277 char* host = ptr;
278 char* hash = NULL;
279 char* start = NULL;
280 char* end = NULL;
281 while (ptr < data + length) {
282 if (ptr == data + length - 1) goto add;
283 if (*ptr == ' ' || *ptr == '\t' || (host?(*ptr == '\n'):0)) {
284 *ptr = '\0';
285 ptr++;
286 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
287 ptr++;
288 add:
289 if (!hash) {
290 hash = ptr;
291 ptr++;
292 continue;
293 }
294 if (!start) {
295 start = ptr;
296 ptr++;
297 continue;
298 }
299 if (!end) {
300 end = ptr;
301 ptr++;
302 continue;
303 }
304 cert_add(host, hash,
305 strtoull(start, NULL, 10),
306 strtoull(end, NULL, 10));
307 host = ptr;
308 end = start = hash = NULL;
309 } else if (*ptr == '\n') {
310 host = ptr+1;
311 end = start = hash = NULL;
312 }
313 ptr++;
314 }
315 free(data);
316 return 0;
317 }
318
319 int cert_loadcert(const char* host, struct cert_cache* cert) {
320 char crt[1024];
321 char key[1024];
322 if (cert_getpath(host, crt, sizeof(crt), key, sizeof(key)) == -1) {
323 return -1;
324 }
325 size_t crt_pos = 0;
326 size_t key_pos = 0;
327 int crt_fd = openat(config_fd, crt, O_RDONLY);
328 if (crt_fd < 0) {
329 return -2;
330 }
331 int key_fd = openat(config_fd, key, O_RDONLY);
332 if (key_fd < 0) {
333 close(key_fd);
334 return -2;
335 }
336 FILE* crt_f = fdopen(crt_fd, "rb");
337 FILE* key_f = fdopen(key_fd, "rb");
338 #ifdef SANDBOX_FREEBSD
339 makefd_readonly(crt_fd);
340 makefd_readonly(key_fd);
341 #endif
342 if (!crt_f || !key_f) {
343 if (crt_f)
344 fclose(crt_f);
345 else
346 close(crt_fd);
347 if (key_f)
348 fclose(key_f);
349 else
350 close(key_fd);
351 return -3;
352 }
353 fseek(crt_f, 0, SEEK_END);
354 crt_pos = ftell(crt_f);
355 fseek(key_f, 0, SEEK_END);
356 key_pos = ftell(key_f);
357
358 cert->crt = malloc(crt_pos);
359 if (!cert->crt) return fatalI();
360 cert->key = malloc(key_pos);
361 if (!cert->key) return fatalI();
362
363 fseek(crt_f, 0, SEEK_SET);
364 fseek(key_f, 0, SEEK_SET);
365 if (fread(cert->crt, 1, crt_pos, crt_f) != crt_pos ||
366 fread(cert->key, 1, key_pos, key_f) != key_pos) {
367 fclose(crt_f);
368 fclose(key_f);
369 return -3;
370 }
371
372 fclose(crt_f);
373 fclose(key_f);
374 cert->crt[crt_pos - 1] = '\0';
375 cert->key[key_pos - 1] = '\0';
376 cert->crt_len = crt_pos;
377 cert->key_len = key_pos;
378 STRLCPY(cert->host, host);
379 return 0;
380 }
381
382 int cert_getcert(char* host, int reload) {
383 int index = 0;
384 while (client.certs && index < client.certs_size) {
385 if (!strcmp(client.certs[index].host, host)) {
386 if (reload) {
387 reload = 2;
388 break;
389 }
390 return index;
391 }
392 index++;
393 }
394 if (reload != 2) reload = 0;
395
396 struct cert_cache cert = {0};
397 #ifdef SANDBOX_SUN
398 if (send(rd_pair[1], &RD_CERTIFICATE, sizeof(SBC), 0) != sizeof(SBC))
399 return -1;
400 uint16_t i16 = strlen(host);
401 if (send(rd_pair[1], &i16, sizeof(i16), 0) != sizeof(i16))
402 return -1;
403 if (send(rd_pair[1], host, i16, 0) != i16)
404 return -1;
405 char c;
406 if (recv(rd_pair[1], &c, sizeof(c), 0) != 1 || c)
407 return -1;
408
409 if (recv(rd_pair[1], &cert.crt_len, sizeof(cert.crt_len), 0) !=
410 sizeof(cert.crt_len))
411 return -1;
412 if (recv(rd_pair[1], &cert.key_len, sizeof(cert.key_len), 0) !=
413 sizeof(cert.key_len))
414 return -1;
415 cert.crt = malloc(cert.crt_len);
416 if (!cert.crt) return -1;
417 cert.key = malloc(cert.key_len);
418 if (!cert.key) {
419 free(cert.crt);
420 return -1;
421 }
422 if (recv(rd_pair[1], cert.crt, cert.crt_len, 0) != (signed)cert.crt_len
423 || recv(rd_pair[1], cert.key, cert.key_len, 0) != (signed)cert.key_len)
424 {
425 free(cert.crt);
426 free(cert.key);
427 return -1;
428 }
429 #else
430 if (cert_loadcert(host, &cert))
431 return -1;
432 #endif
433
434 if (!reload) {
435 client.certs = realloc(client.certs,
436 sizeof(*client.certs) * (index + 1));
437 if (!client.certs) return fatalI();
438 bzero(&client.certs[index], sizeof(*client.certs));
439 } else {
440 free(client.certs[index].crt);
441 free(client.certs[index].key);
442 }
443 client.certs[index] = cert;
444 if (!reload)
445 client.certs_size++;
446 return index;
447 }
448
449 #ifdef SANDBOX_SUN
450 int cert_rewrite() {
451 int fd = wr_pair[1];
452 if (send(fd, &WR_KNOWNHOSTS, sizeof(SBC), 0) != sizeof(SBC))
453 return -3;
454 #else
455 int cert_rewrite() {
456 int cfd = getconfigfd();
457 if (cfd < 0) return -1;
458
459 int fd = openat(cfd, "known_hosts",
460 O_CREAT|O_WRONLY|O_CLOEXEC|O_TRUNC, 0600);
461 if (fd == -1)
462 return -2;
463 #ifdef SANDBOX_FREEBSD
464 if (makefd_writeonly(fd))
465 return -3;
466 #endif
467
468 #endif
469 char buf[2048];
470 for (struct cert* cert = first_cert; cert; cert = cert->next) {
471 int len = snprintf(buf, 2048, "%s %s %llu %llu\n",
472 cert->host, cert->hash,
473 cert->start, cert->end);
474 if (write(fd, buf, len) != len) {
475 #ifdef SANDBOX_SUN
476 send(fd, &WR_END, sizeof(SBC), 0);
477 #else
478 close(fd);
479 #endif
480 return -1;
481 }
482 }
483 #ifdef SANDBOX_SUN
484 send(fd, &WR_END, sizeof(SBC), 0);
485 #else
486 close(fd);
487 #endif
488 return 0;
489 }
490
491 int cert_forget(char* host) {
492 struct cert* prev = NULL;
493 for (struct cert* cert = first_cert; cert; cert = cert->next) {
494 if (strcmp(host, cert->host)) {
495 prev = cert;
496 continue;
497 }
498 if (prev)
499 prev->next = cert->next;
500 if (cert->next == NULL)
501 last_cert = prev;
502 free(cert);
503 return cert_rewrite();
504 }
505 return -1;
506 }
507
508 int cert_verify(char* host, const char* hash,
509 unsigned long long start,
510 unsigned long long end) {
511 struct cert* found = NULL;
512 for (struct cert* cert = first_cert; cert; cert = cert->next) {
513 if (strcmp(host, cert->host)) continue;
514 found = cert;
515 break;
516 }
517 unsigned long long now = time(NULL);
518 if (found) {
519 if (found->start < now && found->end > now)
520 return strcmp(found->hash, hash) ? -6 : 0;
521 return -5; // expired
522 }
523 #ifdef SANDBOX_SUN
524 int fd = wr_pair[1];
525 if (send(fd, &WR_KNOWNHOST_ADD, sizeof(SBC), 0) != sizeof(SBC))
526 return -3;
527 #else
528 int cfd = getconfigfd();
529 if (cfd < 0) return -1;
530
531 int fd = openat(cfd, "known_hosts", O_CREAT|O_APPEND|O_WRONLY, 0600);
532 if (fd == -1)
533 return -2;
534 if (!fdopen(fd, "a")) return -3;
535 #ifdef SANDBOX_FREEBSD
536 if (makefd_writeonly(fd))
537 return -3;
538 #endif
539 #endif
540 char buf[2048];
541 int len = snprintf(buf, 2048, "%s %s %lld %lld\n",
542 host, hash, start, end);
543 if (write(fd, buf, len) != len) {
544 close(fd);
545 return -4;
546 }
547
548 #ifdef SANDBOX_SUN
549 send(fd, &WR_END, sizeof(SBC), 0);
550 #else
551 close(fd);
552 #endif
553 cert_add(host, hash, start, end);
554 return 0;
555 }
556
557 void cert_free() {
558 struct cert *cert, *next_cert;
559 cert = first_cert;
560 while (cert) {
561 next_cert = cert->next;
562 free(cert);
563 cert = next_cert;
564 }
565 for (int i = 0; i < client.certs_size; i++) {
566 free(client.certs[i].crt);
567 free(client.certs[i].key);
568 }
569 free(client.certs);
570 if (config_fd > 0)
571 close(config_fd);
572 if (download_fd > 0)
573 close(download_fd);
574 if (home_fd > 0)
575 close(home_fd);
576 }
577
578 struct ignore_entry {
579 char host[1024];
580 struct ignore_entry *next;
581 };
582 struct ignore_entry *ignore_list = NULL;
583 int cert_ignore_expiration(const char *host) {
584 struct ignore_entry *entry = malloc(sizeof(struct ignore_entry));
585 if (!entry) return -1;
586 STRLCPY(entry->host, host);
587 entry->next = ignore_list;
588 ignore_list = entry;
589 return 0;
590 }
591
592 int cert_should_ignore(const char *host) {
593 struct ignore_entry *entry = ignore_list;
594 while (entry) {
595 if (!strncmp(entry->host, host, sizeof(entry->host)))
596 return 1;
597 entry = entry->next;
598 }
599 return 0;
600 }
601