mirror of https://github.com/skeeto/enchive.git
Encrypt the private key.
parent
6c9cb4e14a
commit
ebd34b593a
210
enchive.c
210
enchive.c
|
@ -3,6 +3,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define OPTPARSE_IMPLEMENTATION
|
||||
#include "docs.h"
|
||||
|
@ -12,6 +13,11 @@
|
|||
|
||||
int curve25519_donna(u8 *p, const u8 *s, const u8 *b);
|
||||
|
||||
/* Global options. */
|
||||
static char *global_random_device = "/dev/urandom";
|
||||
static char *global_pubkey = 0;
|
||||
static char *global_seckey = 0;
|
||||
|
||||
static struct {
|
||||
char *name;
|
||||
FILE *file;
|
||||
|
@ -48,8 +54,24 @@ fatal(const char *fmt, ...)
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void
|
||||
get_passphrase_dumb(char *buf, size_t len, char *prompt)
|
||||
{
|
||||
size_t passlen;
|
||||
fprintf(stderr, "warning: reading passphrase from stdin with echo\n");
|
||||
fputs(prompt, stderr);
|
||||
fflush(stderr);
|
||||
if (!fgets(buf, len, stdin))
|
||||
fatal("could not read passphrase");
|
||||
passlen = strlen(buf);
|
||||
if (buf[passlen - 1] < ' ')
|
||||
buf[passlen - 1] = 0;
|
||||
}
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
|
||||
static FILE *
|
||||
secure_creat(char *file)
|
||||
|
@ -59,6 +81,36 @@ secure_creat(char *file)
|
|||
return 0;
|
||||
return fdopen(fd, "wb");
|
||||
}
|
||||
|
||||
static void
|
||||
get_passphrase(char *buf, size_t len, char *prompt)
|
||||
{
|
||||
int tty = open("/dev/tty", O_RDWR);
|
||||
if (tty == -1) {
|
||||
get_passphrase_dumb(buf, len, prompt);
|
||||
} else {
|
||||
char newline = '\n';
|
||||
size_t i = 0;
|
||||
struct termios old, new;
|
||||
write(tty, prompt, strlen(prompt));
|
||||
tcgetattr(tty, &old);
|
||||
new = old;
|
||||
new.c_lflag &= ~ECHO;
|
||||
tcsetattr(tty, TCSANOW, &new);
|
||||
errno = 0;
|
||||
while (i < len - 1 && read(tty, buf + i, 1) == 1) {
|
||||
if (buf[i] == '\n' || buf[i] == '\r')
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
buf[i] = 0;
|
||||
tcsetattr(tty, TCSANOW, &old);
|
||||
write(tty, &newline, 1);
|
||||
close(tty);
|
||||
if (errno)
|
||||
fatal("could not read passphrase from /dev/tty");
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
/* fallback to standard open */
|
||||
|
@ -67,12 +119,30 @@ secure_creat(char *file)
|
|||
{
|
||||
return fopen(file, "wb");
|
||||
}
|
||||
|
||||
static void
|
||||
get_passphrase(char *buf, size_t len, char *prompt)
|
||||
{
|
||||
get_passphrase_dumb(buf, len, prompt);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Global options. */
|
||||
static char *global_random_device = "/dev/urandom";
|
||||
static char *global_pubkey = 0;
|
||||
static char *global_seckey = 0;
|
||||
#define KEY_DERIVE_ITERATIONS 0x400000ul
|
||||
|
||||
static void
|
||||
key_derive(char *key, u8 *buf)
|
||||
{
|
||||
unsigned long i;
|
||||
SHA256_CTX ctx[1];
|
||||
sha256_init(ctx);
|
||||
sha256_final(ctx, buf);
|
||||
for (i = 0; i < KEY_DERIVE_ITERATIONS; i++) {
|
||||
sha256_init(ctx);
|
||||
sha256_update(ctx, (void *)key, strlen(key));
|
||||
sha256_update(ctx, buf, sizeof(buf));
|
||||
sha256_final(ctx, buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
secure_entropy(void *buf, size_t len)
|
||||
|
@ -156,7 +226,7 @@ symmetric_decrypt(FILE *in, FILE *out, u8 *key, u8 *iv)
|
|||
chacha_ivsetup(ctx, iv);
|
||||
sha256_init(hash);
|
||||
|
||||
/* Always keep SHA224_BLOCK_SIZE bytes in the buffer. */
|
||||
/* Always keep SHA256_BLOCK_SIZE bytes in the buffer. */
|
||||
if (!(fread(buffer[0], SHA256_BLOCK_SIZE, 1, in))) {
|
||||
if (ferror(in))
|
||||
fatal("cannot read ciphertext file");
|
||||
|
@ -177,7 +247,7 @@ symmetric_decrypt(FILE *in, FILE *out, u8 *key, u8 *iv)
|
|||
if (!fwrite(buffer[1], z, 1, out))
|
||||
fatal("error writing plaintext file");
|
||||
|
||||
/* Move last SHA224_BLOCK_SIZE bytes to the front. */
|
||||
/* Move last SHA256_BLOCK_SIZE bytes to the front. */
|
||||
memmove(buffer[0], buffer[0] + z, SHA256_BLOCK_SIZE);
|
||||
|
||||
if (z < sizeof(buffer[0]) - SHA256_BLOCK_SIZE)
|
||||
|
@ -232,7 +302,69 @@ default_secfile(void)
|
|||
}
|
||||
|
||||
static void
|
||||
load_key(char *file, u8 *key)
|
||||
write_pubkey(char *file, u8 *key)
|
||||
{
|
||||
FILE *f = fopen(file, "wb");
|
||||
if (!f)
|
||||
fatal("failed to open key file for writing -- %s", file);
|
||||
cleanup_register(f, file);
|
||||
if (!fwrite(key, 32, 1, f))
|
||||
fatal("failed to write key file -- %s", file);
|
||||
if (fclose(f))
|
||||
fatal("failed to flush key file -- %s", file);
|
||||
}
|
||||
|
||||
static void
|
||||
write_seckey(char *file, u8 *seckey, int encrypt)
|
||||
{
|
||||
FILE *secfile;
|
||||
chacha_ctx cha[1];
|
||||
SHA256_CTX sha[1];
|
||||
u8 buf[8 + 24 + 32] = {0};
|
||||
u8 key[32];
|
||||
|
||||
if (encrypt) {
|
||||
char pass[2][256];
|
||||
get_passphrase(pass[0], sizeof(pass[0]),
|
||||
"passphrase (empty for none): ");
|
||||
get_passphrase(pass[1], sizeof(pass[0]),
|
||||
"passphrase (repeat): ");
|
||||
if (strcmp(pass[0], pass[1]) != 0)
|
||||
fatal("passphrase don't match");
|
||||
if (!pass[0][0]) {
|
||||
|
||||
encrypt = 0;
|
||||
} else {
|
||||
key_derive(pass[0], key);
|
||||
|
||||
sha256_init(sha);
|
||||
sha256_update(sha, key, 32);
|
||||
sha256_final(sha, buf + 8);
|
||||
|
||||
secure_entropy(buf, 8);
|
||||
}
|
||||
}
|
||||
|
||||
if (encrypt) {
|
||||
chacha_keysetup(cha, key, 256);
|
||||
chacha_ivsetup(cha, buf);
|
||||
chacha_encrypt_bytes(cha, seckey, buf + 32, 32);
|
||||
} else {
|
||||
memcpy(buf + 32, seckey, 32);
|
||||
}
|
||||
|
||||
secfile = secure_creat(file);
|
||||
if (!secfile)
|
||||
fatal("failed to open key file for writing -- %s", file);
|
||||
cleanup_register(secfile, file);
|
||||
if (!fwrite(buf, sizeof(buf), 1, secfile))
|
||||
fatal("failed to write key file -- %s", file);
|
||||
if (fclose(secfile))
|
||||
fatal("failed to flush key file -- %s", file);
|
||||
}
|
||||
|
||||
static void
|
||||
load_pubkey(char *file, u8 *key)
|
||||
{
|
||||
FILE *f = fopen(file, "rb");
|
||||
if (!f)
|
||||
|
@ -243,22 +375,43 @@ load_key(char *file, u8 *key)
|
|||
}
|
||||
|
||||
static void
|
||||
write_key(char *file, const u8 *key, int clobber)
|
||||
load_seckey(char *file, u8 *seckey)
|
||||
{
|
||||
FILE *f;
|
||||
FILE *secfile;
|
||||
chacha_ctx cha[1];
|
||||
SHA256_CTX sha[1];
|
||||
u8 buf[8 + 24 + 32];
|
||||
u8 empty[8] = {0};
|
||||
u8 keyhash[SHA256_BLOCK_SIZE];
|
||||
u8 key[32];
|
||||
|
||||
if (!clobber && fopen(file, "r"))
|
||||
fatal("operation would clobber %s", file);
|
||||
f = secure_creat(file);
|
||||
if (!f)
|
||||
fatal("failed to open key file for writing -- %s", file);
|
||||
cleanup_register(f, file);
|
||||
if (!fwrite(key, 32, 1, f))
|
||||
fatal("failed to write key file -- %s", file);
|
||||
if (fclose(f))
|
||||
fatal("failed to flush key file -- %s", file);
|
||||
secfile = fopen(file, "rb");
|
||||
if (!secfile)
|
||||
fatal("failed to open key file for reading -- %s", file);
|
||||
if (!fread(buf, sizeof(buf), 1, secfile))
|
||||
fatal("failed to read key file -- %s", file);
|
||||
fclose(secfile);
|
||||
|
||||
if (memcmp(buf, empty, sizeof(empty)) != 0) {
|
||||
char pass[256];
|
||||
get_passphrase(pass, sizeof(pass), "passphrase: ");
|
||||
key_derive(pass, key);
|
||||
|
||||
sha256_init(sha);
|
||||
sha256_update(sha, key, 32);
|
||||
sha256_final(sha, keyhash);
|
||||
if (memcmp(keyhash, buf + 8, 24) != 0)
|
||||
fatal("wrong passphrase");
|
||||
|
||||
chacha_keysetup(cha, key, 256);
|
||||
chacha_ivsetup(cha, buf);
|
||||
chacha_encrypt_bytes(cha, buf + 32, seckey, 32);
|
||||
} else {
|
||||
memcpy(seckey, buf + 32, 32);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum command {
|
||||
COMMAND_UNKNOWN = -2,
|
||||
COMMAND_AMBIGUOUS = -1,
|
||||
|
@ -293,6 +446,7 @@ command_keygen(struct optparse *options)
|
|||
{
|
||||
static const struct optparse_long keygen[] = {
|
||||
{"force", 'f', OPTPARSE_NONE},
|
||||
{"plain", 'u', OPTPARSE_NONE},
|
||||
{0}
|
||||
};
|
||||
|
||||
|
@ -301,6 +455,7 @@ command_keygen(struct optparse *options)
|
|||
u8 public[32];
|
||||
u8 secret[32];
|
||||
int clobber = 0;
|
||||
int encrypt = 1;
|
||||
|
||||
int option;
|
||||
while ((option = optparse_long(options, keygen, 0)) != -1) {
|
||||
|
@ -308,6 +463,9 @@ command_keygen(struct optparse *options)
|
|||
case 'f':
|
||||
clobber = 1;
|
||||
break;
|
||||
case 'u':
|
||||
encrypt = 0;
|
||||
break;
|
||||
default:
|
||||
fatal("%s", options->errmsg);
|
||||
}
|
||||
|
@ -315,13 +473,17 @@ command_keygen(struct optparse *options)
|
|||
|
||||
if (!pubfile)
|
||||
pubfile = default_pubfile();
|
||||
if (!clobber && fopen(pubfile, "r"))
|
||||
fatal("operation would clobber %s", pubfile);
|
||||
if (!secfile)
|
||||
secfile = default_secfile();
|
||||
if (!clobber && fopen(secfile, "r"))
|
||||
fatal("operation would clobber %s", secfile);
|
||||
|
||||
generate_secret(secret);
|
||||
compute_public(public, secret);
|
||||
write_key(pubfile, public, clobber);
|
||||
write_key(secfile, secret, clobber);
|
||||
write_pubkey(pubfile, public);
|
||||
write_seckey(secfile, secret, encrypt);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -352,7 +514,7 @@ command_archive(struct optparse *options)
|
|||
|
||||
if (!pubfile)
|
||||
pubfile = default_pubfile();
|
||||
load_key(pubfile, public);
|
||||
load_pubkey(pubfile, public);
|
||||
|
||||
infile = optparse_arg(options);
|
||||
if (infile) {
|
||||
|
@ -416,7 +578,7 @@ command_extract(struct optparse *options)
|
|||
|
||||
if (!secfile)
|
||||
secfile = default_secfile();
|
||||
load_key(secfile, secret);
|
||||
load_seckey(secfile, secret);
|
||||
|
||||
infile = optparse_arg(options);
|
||||
if (infile) {
|
||||
|
@ -517,7 +679,7 @@ main(int argc, char **argv)
|
|||
options->permute = 0;
|
||||
(void)argc;
|
||||
|
||||
while ((option = optparse_long(options, global, 0)) != -1) {
|
||||
while ((option = optparse_long(options, global, 0)) != -1) {
|
||||
switch (option) {
|
||||
case 'r':
|
||||
global_random_device = options->optarg;
|
||||
|
|
Loading…
Reference in New Issue