tsafe.c - safe - password protected secret keeper
(HTM) git clone git://git.z3bra.org/safe.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
tsafe.c (13201B)
---
1 #include <netinet/in.h>
2 #include <sys/resource.h>
3 #include <sys/socket.h>
4 #include <sys/stat.h>
5 #include <sys/types.h>
6 #include <sys/un.h>
7 #include <sys/wait.h>
8
9 #include <err.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <limits.h>
13 #include <paths.h>
14 #include <stdint.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include <sodium.h>
21
22 #include "arg.h"
23 #include "readpassphrase.h"
24 #include "config.h"
25
26 struct crypto {
27 uint8_t magic[6];
28 uint16_t version;
29 uint32_t xchacha20poly1305_bufsiz;
30 uint32_t argon2id_memory;
31 uint32_t argon2id_time;
32 uint32_t argon2id_threads;
33 uint8_t salt[crypto_pwhash_SALTBYTES];
34 };
35
36 struct crypto crypto_defaults = {
37 .magic = {'C','R','E','A','M','\1'},
38 .version = 0x10,
39 .xchacha20poly1305_bufsiz = BUFSIZ, /* must match size in readsecret() and writesecret() */
40 .argon2id_memory = crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE/1024,
41 .argon2id_time = crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE,
42 .argon2id_threads = 1, /* not used, hardcoded in libsodium */
43 };
44
45 struct safe {
46 uint8_t key[crypto_secretstream_xchacha20poly1305_KEYBYTES];
47 struct crypto crypto;
48 };
49
50 enum {
51 SAFE_INIT = 1 << 1,
52 SAFE_FINAL = 1 << 2,
53 };
54
55 uint8_t cleartext[BUFSIZ]; // Fixed-size memory chunk for decrypting messages. Suitable for sodium_mlock()
56 char *argv0;
57
58 void
59 usage(void)
60 {
61 fprintf(stderr, "usage: %s [-bhr] [-s safe] [-p prompt] [[-af] entry]\n", argv0);
62 exit(1);
63 }
64
65 char *
66 dirname(char *path)
67 {
68 static char tmp[PATH_MAX];
69 char *p = NULL;
70 size_t len;
71 snprintf(tmp, sizeof(tmp), "%s", path);
72 len = strlen(tmp);
73 for(p = tmp + len; p > tmp; p--)
74 if(*p == '/')
75 break;
76
77 *p = 0;
78 return tmp;
79 }
80
81 int
82 mkdir_p(char *path, mode_t mode)
83 {
84 char tmp[PATH_MAX] = "";
85 char *p = NULL;
86 size_t len;
87
88 snprintf(tmp, sizeof(tmp), "%s", path);
89 len = strlen(tmp);
90 if(len && tmp[len - 1] == '/')
91 tmp[len - 1] = 0;
92 for(p = tmp + 1; *p; p++)
93 if(*p == '/') {
94 *p = 0;
95 mkdir(tmp, mode);
96 *p = '/';
97 }
98 return mkdir(tmp, mode);
99 }
100
101 ssize_t
102 xread(int fd, void *buf, size_t nbytes, int *eof)
103 {
104 uint8_t *bp = buf;
105 ssize_t total = 0;
106
107 if (eof) *eof = 0;
108 while (nbytes > 0) {
109 ssize_t n;
110
111 n = read(fd, &bp[total], nbytes);
112 if (n < 0) {
113 err(1, "read");
114 } else if (n == 0) {
115 if (eof) *eof = 1;
116 return total;
117 }
118 total += n;
119 nbytes -= n;
120 }
121 return total;
122 }
123
124 ssize_t
125 xwrite(int fd, const void *buf, size_t nbytes)
126 {
127 const uint8_t *bp = buf;
128 ssize_t total = 0;
129
130 while (nbytes > 0) {
131 ssize_t n;
132
133 n = write(fd, &bp[total], nbytes);
134 if (n < 0)
135 err(1, "write");
136 else if (n == 0)
137 return total;
138 total += n;
139 nbytes -= n;
140 }
141 return total;
142 }
143
144 char *
145 spawn_askpass(const char *askpass, const char *msg, char *buf, size_t bufsiz)
146 {
147 pid_t pid, ret;
148 int p[2], eof, status;
149
150 if (!askpass) {
151 sodium_memzero(buf, bufsiz);
152 return buf;
153 }
154
155 if (pipe(p) < 0)
156 return NULL;
157
158 pid = fork();
159 if (pid < 0)
160 return NULL;
161
162 if (!pid) {
163 close(p[0]);
164 if (dup2(p[1], STDOUT_FILENO) < 0)
165 return NULL;
166
167 execlp(askpass, askpass, msg, NULL);
168 err(1, "execlp(%s)", askpass); /* NOTREACHED */
169 }
170 close(p[1]);
171
172 xread(p[0], buf, bufsiz - 1, &eof);
173 close(p[0]);
174
175 ret = waitpid(pid, &status, 0);
176
177 if (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status)) {
178 sodium_memzero(buf, bufsiz);
179 return buf;
180 }
181
182 buf[strcspn(buf, "\r\n")] = '\0';
183 return buf;
184 }
185
186 int
187 readpass(const char *prompt, char *buf, size_t bufsiz, int askflag, int stdinflag)
188 {
189 char *askpass;
190 if (askflag) {
191 askpass = askpass_path;
192 if (getenv("SAFE_ASKPASS"))
193 askpass = getenv("SAFE_ASKPASS");
194 if (!spawn_askpass(askpass, prompt, buf, bufsiz))
195 err(1, "askpass:");
196 } else {
197 int flags = 0;
198 flags |= RPP_ECHO_OFF;
199 flags |= stdinflag ? RPP_STDIN : RPP_REQUIRE_TTY;
200 if (!readpassphrase(prompt, buf, bufsiz, flags))
201 err(1, "readpassphrase:");
202 }
203
204 if (buf[0] == '\0')
205 return -1;
206
207 return strnlen(buf, bufsiz);
208 }
209
210 void
211 deriv(char *pw, struct safe *s)
212 {
213 if (crypto_pwhash(s->key, sizeof(s->key), pw, strlen(pw),
214 s->crypto.salt, s->crypto.argon2id_time,
215 s->crypto.argon2id_memory * 1024,
216 crypto_pwhash_ALG_ARGON2ID13))
217 err(1, "crypto_pwhash:");
218 }
219
220 int
221 pushkey(struct safe *s, char *path)
222 {
223 int sfd;
224 struct sockaddr_un addr;
225
226 addr.sun_family = AF_UNIX;
227 strcpy(addr.sun_path, path);
228
229 if ((sfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
230 return -1;
231
232 if (connect(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
233 return -1;
234
235 if (xwrite(sfd, s->crypto.salt, sizeof(s->crypto.salt)) < 0)
236 return -1;
237
238 if (write(sfd, s->key, sizeof(s->key)) < 0)
239 return -1;
240
241 close(sfd);
242
243 return 0;
244 }
245
246 int
247 readkey(struct safe *s, char *path)
248 {
249 int sfd;
250 ssize_t n;
251 struct sockaddr_un addr;
252
253 addr.sun_family = AF_UNIX;
254 strcpy(addr.sun_path, path);
255
256 if ((sfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
257 return -1;
258
259 if (connect(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
260 goto err;
261
262 if ((n = xread(sfd, &s->crypto.salt, sizeof(s->crypto.salt), NULL)) <= 0)
263 goto err;
264
265 if (xread(sfd, s->key, sizeof(s->key), NULL) < 0)
266 goto err;
267
268 close(sfd);
269 return 0;
270
271 err:
272 close(sfd);
273 return -1;
274 }
275
276 int
277 trydecrypt(struct safe *s, int fd)
278 {
279 int r = 0, eof = 0;
280 ssize_t n;
281 uint8_t tag;
282 uint8_t *m = cleartext;
283 uint8_t c[BUFSIZ + crypto_secretstream_xchacha20poly1305_ABYTES];
284 uint8_t h[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
285 crypto_secretstream_xchacha20poly1305_state st;
286 unsigned long long mlen;
287
288 xread(fd, h, sizeof(h), NULL);
289 if (crypto_secretstream_xchacha20poly1305_init_pull(&st, h, s->key))
290 return -1;
291
292 while ((n = xread(fd, c, sizeof(c), &eof)) > 0) {
293 if (crypto_secretstream_xchacha20poly1305_pull(&st, m, &mlen, &tag, c, n, NULL, 0))
294 r--;
295
296 if (eof && tag != crypto_secretstream_xchacha20poly1305_TAG_FINAL)
297 r--;
298 }
299 return r;
300 }
301
302 int
303 readheader(int fd, struct crypto *hdr)
304 {
305 uint8_t buf[40];
306 uint16_t s;
307 uint32_t l;
308
309 xread(fd, buf, sizeof(buf), NULL);
310
311 memcpy(hdr->magic, buf + 0, 6);
312 memcpy(&s, buf + 6, 2); hdr->version = ntohs(s);
313 memcpy(&l, buf + 8, 4); hdr->xchacha20poly1305_bufsiz = ntohl(l);
314 memcpy(&l, buf + 12, 4); hdr->argon2id_memory = ntohl(l);
315 memcpy(&l, buf + 16, 4); hdr->argon2id_time = ntohl(l);
316 memcpy(&l, buf + 20, 4); hdr->argon2id_threads = ntohl(l);
317 memcpy(hdr->salt, buf + 24, 16);
318
319 return 0;
320 }
321
322 int
323 writeheader(int fd, struct crypto hdr)
324 {
325 uint8_t buf[40];
326 uint16_t s;
327 uint32_t l;
328
329 memcpy(buf + 0, hdr.magic, 6);
330 s = htons(hdr.version); memcpy(buf + 6, (uint8_t *) &s, 2);
331 l = htonl(hdr.xchacha20poly1305_bufsiz); memcpy(buf + 8, (uint8_t *) &l, 4);
332 l = htonl(hdr.argon2id_memory); memcpy(buf + 12, (uint8_t *) &l, 4);
333 l = htonl(hdr.argon2id_time); memcpy(buf + 16, (uint8_t *) &l, 4);
334 l = htonl(hdr.argon2id_threads); memcpy(buf + 20, (uint8_t *) &l, 4);
335 memcpy(buf + 24, hdr.salt, 16);
336
337 return xwrite(fd, buf, sizeof(buf));
338 }
339
340 int
341 writepass(struct safe *s, char *m, size_t mlen, int fd)
342 {
343 uint8_t *c, h[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
344 crypto_secretstream_xchacha20poly1305_state st;
345 unsigned long long clen;
346
347 c = malloc(mlen + crypto_secretstream_xchacha20poly1305_ABYTES);
348 if (!c)
349 err(1, "malloc");
350
351 if (crypto_secretstream_xchacha20poly1305_init_push(&st, h, s->key))
352 return -1;
353
354 if (crypto_secretstream_xchacha20poly1305_push(&st, c, &clen, (uint8_t *)m, mlen, NULL, 0, crypto_secretstream_xchacha20poly1305_TAG_FINAL))
355 return -1;
356
357 xwrite(fd, h, sizeof(h));
358 xwrite(fd, c, clen);
359
360 free(c);
361
362 return 0;
363 }
364
365 int
366 writesecret(struct safe *s, int in, int out)
367 {
368 int eof;
369 ssize_t n;
370 uint8_t tag;
371 uint8_t m[BUFSIZ];
372 uint8_t c[BUFSIZ + crypto_secretstream_xchacha20poly1305_ABYTES];
373 uint8_t h[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
374 crypto_secretstream_xchacha20poly1305_state st;
375 unsigned long long clen;
376
377 if (crypto_secretstream_xchacha20poly1305_init_push(&st, h, s->key))
378 return -1;
379
380 xwrite(out, h, sizeof(h));
381
382 while ((n = xread(in, m, sizeof(m), &eof)) > 0) {
383 tag = eof ? crypto_secretstream_xchacha20poly1305_TAG_FINAL : 0;
384 if (crypto_secretstream_xchacha20poly1305_push(&st, c, &clen, m, n, NULL, 0, tag))
385 return -1;
386
387 xwrite(out, c, clen);
388 }
389 return 0;
390 }
391
392 int
393 readsecret(struct safe *s, int in, int out)
394 {
395 int eof = 0;
396 ssize_t n;
397 uint8_t tag;
398 uint8_t m[BUFSIZ];
399 uint8_t c[BUFSIZ + crypto_secretstream_xchacha20poly1305_ABYTES];
400 uint8_t h[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
401 crypto_secretstream_xchacha20poly1305_state st;
402 unsigned long long mlen;
403
404 xread(in, h, sizeof(h), NULL);
405 if (crypto_secretstream_xchacha20poly1305_init_pull(&st, h, s->key))
406 return -1;
407
408 while ((n = xread(in, c, sizeof(c), &eof)) > 0) {
409 if (crypto_secretstream_xchacha20poly1305_pull(&st, m, &mlen, &tag, c, n, NULL, 0))
410 return -1;
411
412 if (eof && tag != crypto_secretstream_xchacha20poly1305_TAG_FINAL)
413 return -1;
414
415 xwrite(out, m, mlen);
416 }
417 return 0;
418 }
419
420 int
421 main(int argc, char *argv[])
422 {
423 int aflag = 0, bflag = 0, rflag = 0, kflag = 0, fflag = 0;
424 int fd, haskey = 0, hasmaster = 1, ttyfd;
425 char *prompt, *secret, *sockp, *safe = safe_dir;
426 char passphrase[BUFSIZ], verifyphrase[BUFSIZ];
427 ssize_t pplen, vplen = 0;
428 struct safe s;
429
430 safe = getenv("SAFE_DIR");
431 sockp = getenv("SAFE_SOCK");
432 prompt = "password:";
433
434 /* set default cream parameters */
435 memcpy(&s.crypto, &crypto_defaults, sizeof(s.crypto));
436
437 ARGBEGIN {
438 case 'f':
439 fflag = 1;
440 /* FALLTHROUGH */
441 case 'a':
442 aflag = 1;
443 break;
444 case 'b':
445 bflag = 1;
446 break;
447 case 'p':
448 prompt = EARGF(usage());
449 break;
450 case 'r':
451 rflag = 1;
452 break;
453 case 's':
454 safe = EARGF(usage());
455 break;
456 case 'k':
457 kflag = 1;
458 break;
459 default:
460 usage();
461 } ARGEND
462
463 if (!safe)
464 safe = safe_dir;
465
466 if (argc != 1 && !rflag)
467 usage();
468
469 if (sodium_init() < 0)
470 return -1;
471
472 sodium_mlock(&s, sizeof(s));
473 sodium_mlock(&cleartext, sizeof(cleartext));
474 sodium_mlock(&passphrase, sizeof(passphrase));
475 sodium_mlock(&verifyphrase, sizeof(verifyphrase));
476
477 #ifndef _DEBUG
478 /* deny core dump as memory contains passwords and keys */
479 struct rlimit rlim;
480 rlim.rlim_cur = rlim.rlim_max = 0;
481 if (setrlimit(RLIMIT_CORE, &rlim) < 0)
482 err(1, "setrlimit RLIMIT_CORE");
483 #endif
484
485 mkdir(safe, 0700);
486
487 #ifdef __OpenBSD__
488 if (unveil(_PATH_TTY, "rw") == -1)
489 err(1, "unveil %s", _PATH_TTY);
490 if (unveil(safe, "rwc") == -1)
491 err(1, "unveil %s", safe);
492 if (sockp)
493 if (unveil(sockp, "rw") == -1)
494 err(1, "unveil %s", sockp);
495 if (pledge("stdio rpath wpath cpath unix tty", NULL) < 0)
496 err(1, "pledge");
497 #endif
498
499 if (chdir(safe) < 0)
500 err(1, "chdir: %s", safe);
501
502 /* open master password as read only to retrieve salt */
503 fd = open(master_entry, O_RDONLY);
504 if (fd < 0) {
505 if (errno != ENOENT)
506 err(1, "%s", master_entry);
507 hasmaster = 0;
508 }
509
510 if (sockp && !readkey(&s, sockp))
511 haskey = 1;
512
513 /*
514 * read passphrase from an ASKPASS program stdout if there is
515 * no tty available
516 */
517 if ((ttyfd = open(_PATH_TTY, O_RDWR)) < 0)
518 kflag = 1;
519 else
520 close(ttyfd);
521
522 /* write master password entry if not present */
523 if (!hasmaster) {
524 pplen = readpass(prompt, passphrase, sizeof(passphrase), kflag, bflag);
525 if (pplen < 0)
526 return -1;
527
528 /* input for master password again to check */
529 vplen = readpass("verify:", verifyphrase, sizeof(verifyphrase), kflag, bflag);
530 if (vplen < 0)
531 return -1;
532
533 if (pplen != vplen || memcmp(passphrase, verifyphrase, pplen)) {
534 fprintf(stderr, "password mismatch\n");
535 return -1;
536 }
537
538 fd = open(master_entry, O_RDWR | O_CREAT | O_EXCL, 0600);
539 if (fd < 0)
540 err(1, "%s", master_entry);
541
542 randombytes_buf(s.crypto.salt, sizeof(s.crypto.salt));
543 deriv((char *)passphrase, &s);
544
545 writeheader(fd, s.crypto);
546 writepass(&s, passphrase, pplen, fd);
547 haskey = 1;
548 }
549
550 if (!haskey) {
551 pplen = readpass(prompt, passphrase, sizeof(passphrase), kflag, bflag);
552 if (pplen < 0)
553 return -1;
554
555 //xread(fd, &s.crypto, sizeof(s.crypto), NULL);
556 readheader(fd, &s.crypto);
557 deriv(passphrase, &s);
558 haskey = 1;
559 }
560
561 /* try to decrypt master password first, to ensure passphrase match */
562 lseek(fd, sizeof(s.crypto), SEEK_SET);
563 if (trydecrypt(&s, fd) < 0) {
564 fprintf(stderr, "incorrect master password\n");
565 close(fd);
566 return -1;
567 }
568 close(fd);
569
570 /* push the key to a running agent */
571 if (rflag) {
572 if (!sockp) {
573 fprintf(stderr, "SAFE_SOCK variable is not set\n");
574 return -1;
575 }
576 pushkey(&s, sockp);
577 }
578
579 secret = argv[0];
580
581 if (!secret)
582 return 0;
583
584 if (aflag) {
585 mkdir_p(dirname(secret), 0700);
586
587 /* Prevent overwriting unless fflag is set */
588 fd = open(secret, O_WRONLY | O_CREAT | (fflag ? 0 : O_EXCL), 0600);
589 if (fd < 0)
590 err(1, "%s", secret);
591
592 writeheader(fd, s.crypto);
593 writesecret(&s, STDIN_FILENO, fd);
594 close(fd);
595 } else {
596 fd = open(secret, O_RDONLY);
597 if (fd < 0)
598 err(1, "%s", secret);
599
600 /* Read salt from the beginning of the file */
601 lseek(fd, sizeof(s.crypto), SEEK_SET);
602 readsecret(&s, fd, STDOUT_FILENO);
603 close(fd);
604 }
605
606 sodium_memzero(&s, sizeof(s));
607 sodium_memzero(&cleartext, sizeof(cleartext));
608 sodium_memzero(&passphrase, sizeof(passphrase));
609 sodium_memzero(&verifyphrase, sizeof(verifyphrase));
610
611 return 0;
612 }