safe

Password protected secret keeper
git clone git://git.z3bra.org/safe.git
Log | Files | Refs | README | LICENSE

safe.c (10529B)


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