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