mirror of https://github.com/skeeto/enchive.git
Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
degaart | 54da5bf53f | |
degaart | 2c8642d26a | |
Christopher Wellons | 13aeab9fe8 | |
Daniel Dumitriu | da8de6a647 | |
Christopher Wellons | b6bbbc56c2 | |
Christopher Wellons | 32d4d99472 | |
Christopher Wellons | 50624f2373 | |
Christopher Wellons | a07053778f | |
Christopher Wellons | 6af0168662 | |
Christopher Wellons | 3abfeb2e00 | |
Christopher Wellons | 7cc0e13f0a | |
Christopher Wellons | f7c4b6ba55 | |
Christopher Wellons | b25af46615 |
|
@ -3,3 +3,6 @@ enchive
|
|||
*.enchive
|
||||
enchive-cli.c
|
||||
*.elc
|
||||
*.swp
|
||||
*.com
|
||||
*.com.dbg
|
||||
|
|
16
Makefile
16
Makefile
|
@ -1,14 +1,16 @@
|
|||
.POSIX:
|
||||
.SUFFIXES:
|
||||
CC = cc
|
||||
CFLAGS = -ansi -pedantic -Wall -Wextra -O3 -g3
|
||||
PREFIX = /usr/local
|
||||
CC = cc
|
||||
CFLAGS = -ansi -pedantic -Wall -Wextra -Wno-missing-field-initializers -O3 -g
|
||||
LDFLAGS =
|
||||
LDLIBS =
|
||||
PREFIX = /usr/local
|
||||
|
||||
sources = src/enchive.c src/chacha.c src/curve25519-donna.c src/sha256.c
|
||||
objects = $(sources:.c=.o)
|
||||
headers = config.h src/docs.h src/chacha.h src/sha256.h src/optparse.h
|
||||
|
||||
enchive: $(objects)
|
||||
enchive$(EXE): $(objects)
|
||||
$(CC) $(LDFLAGS) -o $@ $(objects) $(LDLIBS)
|
||||
src/enchive.o: src/enchive.c config.h src/docs.h
|
||||
src/chacha.o: src/chacha.c config.h
|
||||
|
@ -21,16 +23,16 @@ enchive-cli.c: $(sources) $(headers)
|
|||
amalgamation: enchive-cli.c
|
||||
|
||||
clean:
|
||||
rm -f enchive $(objects) enchive-cli.c
|
||||
rm -f enchive$(EXE) $(objects) enchive-cli.c
|
||||
|
||||
install: enchive enchive.1
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1
|
||||
install -m 755 enchive $(DESTDIR)$(PREFIX)/bin
|
||||
install -m 755 enchive$(EXE) $(DESTDIR)$(PREFIX)/bin
|
||||
gzip < enchive.1 > $(DESTDIR)$(PREFIX)/share/man/man1/enchive.1.gz
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/enchive
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/enchive$(EXE)
|
||||
rm -f $(DESTDIR)$(PREFIX)/share/man/man1/enchive.1.gz
|
||||
|
||||
.SUFFIXES: .c .o
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
.PHONY: all clean
|
||||
.SUFFIXES:
|
||||
|
||||
CFLAGS = -w
|
||||
COSMO = ../cosmopolitan
|
||||
COSMO_CFLAGS = -g -Os -static -nostdlib -nostdinc -fno-pie -no-pie -mno-red-zone \
|
||||
-fno-omit-frame-pointer -pg -mnop-mcount -mno-tls-direct-seg-refs -gdwarf-4 \
|
||||
-include $(COSMO)/cosmopolitan.h
|
||||
COSMO_LDFLAGS = -fuse-ld=bfd -Wl,-T,$(COSMO)/ape.lds -Wl,--gc-sections \
|
||||
$(COSMO)/crt.o $(COSMO)/ape-no-modify-self.o $(COSMO)/cosmopolitan.a
|
||||
|
||||
sources = src/enchive.c src/chacha.c src/curve25519-donna.c src/sha256.c
|
||||
headers = config.h src/docs.h src/chacha.h src/sha256.h src/optparse.h
|
||||
|
||||
all: enchive.com
|
||||
|
||||
enchive.com: enchive.com.dbg
|
||||
@objcopy -S -O binary $< $@
|
||||
|
||||
enchive.com.dbg: enchive-cli.c
|
||||
@gcc $(COSMO_CFLAGS) $(CFLAGS) -o $@ $< $(COSMO_LDFLAGS) $(LDFLAGS)
|
||||
|
||||
enchive-cli.c: $(headers) $(sources)
|
||||
cat $(headers) $(sources) | sed 's/^#include.*//g;s/fsum/enchive_fsum/' > $@
|
||||
|
||||
clean:
|
||||
@rm -f *.dbg *.com enchive-cli.c
|
||||
|
|
@ -49,7 +49,7 @@ To archive a file for storage:
|
|||
This will encrypt `sensitive.zip` as `sensitive.zip.enchive` (leaving
|
||||
the original in place). You can safely archive this wherever.
|
||||
|
||||
To extract the file on a machine with `encrypt.sec`, use `extract`. It
|
||||
To extract the file on a machine with `enchive.sec`, use `extract`. It
|
||||
will prompt for the passphrase you entered during key generation.
|
||||
|
||||
$ enchive extract sensitive.zip.enchive
|
||||
|
@ -105,11 +105,6 @@ A purposeful design choice is that encrypted/archived files have no
|
|||
distinguishing marks whatsoever (magic numbers, etc.), making them
|
||||
indistinguishable from random data.
|
||||
|
||||
No effort is made to set stdin and stdout to binary mode. For Windows
|
||||
this means passing data through Enchive using stdin/stdout isn't
|
||||
useful. This is low priority because Microsoft's [UCRT file streams
|
||||
are broken anyway][pipe] when pipes are involved.
|
||||
|
||||
### Frequently asked questions
|
||||
|
||||
> This tool will never achieve critical mass, so what's the point?
|
||||
|
@ -291,6 +286,5 @@ Maximum passphrase size in bytes, including null terminator.
|
|||
[getrandom]: https://manpages.debian.org/testing/manpages-dev/getrandom.2.en.html
|
||||
[getentropy]: http://man.openbsd.org/OpenBSD-current/man2/getentropy.2
|
||||
[csp]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa380246(v=vs.85).aspx
|
||||
[pipe]: https://radiance-online.org/pipermail/radiance-dev/2016-March/001576.html
|
||||
[bw]: https://en.bitcoin.it/wiki/Brainwallet
|
||||
[dw]: http://world.std.com/~reinhold/diceware.html
|
||||
|
|
251
src/enchive.c
251
src/enchive.c
|
@ -10,6 +10,10 @@
|
|||
#include "chacha.h"
|
||||
#include "optparse.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma comment(lib, "advapi32.lib")
|
||||
#endif
|
||||
|
||||
int curve25519_donna(uint8_t *p, const uint8_t *s, const uint8_t *b);
|
||||
|
||||
/* Global options. */
|
||||
|
@ -365,15 +369,8 @@ storage_directory(char *file)
|
|||
s = strchr(path + 1, '/');
|
||||
while (s) {
|
||||
*s = 0;
|
||||
if (dir_exists(path) || !mkdir(path, 0700)) {
|
||||
DIR *dir = opendir(path);
|
||||
if (dir)
|
||||
closedir(dir);
|
||||
else
|
||||
fatal("opendir(%s) -- %s", path, strerror(errno));
|
||||
} else {
|
||||
if (mkdir(path, 0700) && !dir_exists(path))
|
||||
fatal("mkdir(%s) -- %s", path, strerror(errno));
|
||||
}
|
||||
*s = '/';
|
||||
s = strchr(s + 1, '/');
|
||||
}
|
||||
|
@ -434,11 +431,6 @@ get_passphrase_dumb(char *buf, size_t len, char *prompt)
|
|||
buf[passlen - 1] = 0;
|
||||
}
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__)
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
|
||||
static void
|
||||
pinentry_decode(char *buf, size_t blen, const char *str)
|
||||
{
|
||||
|
@ -465,7 +457,49 @@ pinentry_decode(char *buf, size_t blen, const char *str)
|
|||
}
|
||||
|
||||
static void
|
||||
invoke_pinentry(char *buf, size_t len, char *prompt)
|
||||
pinentry(FILE *pfi, FILE *pfo, char *buf, size_t len, char *prompt)
|
||||
{
|
||||
char line[ENCHIVE_PASSPHRASE_MAX * 3 + 32];
|
||||
|
||||
if (!fgets(line, sizeof(line), pfo))
|
||||
/* Likely caused by exec() failure, so exit quietly. */
|
||||
exit(EXIT_FAILURE);
|
||||
if (strncmp(line, "OK", 2) != 0)
|
||||
fatal("pinentry startup failure");
|
||||
|
||||
if (fprintf(pfi, "SETPROMPT %s\n", prompt) < 0 || fflush(pfi) < 0)
|
||||
fatal("pinentry write() -- %s", strerror(errno));
|
||||
|
||||
if (!fgets(line, sizeof(line), pfo))
|
||||
fatal("pinentry read() -- %s", strerror(errno));
|
||||
if (strncmp(line, "OK", 2) != 0)
|
||||
fatal("pinentry protocol failure");
|
||||
|
||||
if (fprintf(pfi, "GETPIN\n") < 0 || fflush(pfi) < 0)
|
||||
fatal("pinentry write() -- %s", strerror(errno));
|
||||
|
||||
if (!fgets(line, sizeof(line), pfo))
|
||||
fatal("pinentry read() -- %s", strerror(errno));
|
||||
if (strncmp(line, "ERR ", 4) == 0)
|
||||
fatal("passphrase entry canceled");
|
||||
else if (strncmp(line, "OK", 2) == 0)
|
||||
buf[0] = 0;
|
||||
else if (strncmp(line, "D ", 2) == 0)
|
||||
pinentry_decode(buf, len, line + 2);
|
||||
else
|
||||
fatal("pinentry protocol failure");
|
||||
|
||||
if (fprintf(pfi, "BYE\n") < 0 || fflush(pfi) < 0)
|
||||
fatal("pinentry write() -- %s", strerror(errno));
|
||||
}
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__)
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
|
||||
static void
|
||||
pinentry_unix(char *buf, size_t len, char *prompt)
|
||||
{
|
||||
int pin[2];
|
||||
int pout[2];
|
||||
|
@ -481,7 +515,6 @@ invoke_pinentry(char *buf, size_t len, char *prompt)
|
|||
fatal("pinentry fork() failed -- %s", strerror(errno));
|
||||
if (pid) {
|
||||
FILE *pfi, *pfo;
|
||||
char line[ENCHIVE_PASSPHRASE_MAX * 3 + 32];
|
||||
|
||||
close(pin[0]);
|
||||
close(pout[1]);
|
||||
|
@ -491,33 +524,7 @@ invoke_pinentry(char *buf, size_t len, char *prompt)
|
|||
if (!(pfo = fdopen(pout[0], "r")))
|
||||
fatal("fdopen() output -- %s", strerror(errno));
|
||||
|
||||
if (!fgets(line, sizeof(line), pfo))
|
||||
/* Likely caused by exec() failure, so exit quietly. */
|
||||
exit(EXIT_FAILURE);
|
||||
if (strncmp(line, "OK", 2) != 0)
|
||||
fatal("pinentry startup failure");
|
||||
|
||||
if (fprintf(pfi, "SETPROMPT %s\n", prompt) < 0 || fflush(pfi) < 0)
|
||||
fatal("pinentry write() -- %s", strerror(errno));
|
||||
|
||||
if (!fgets(line, sizeof(line), pfo))
|
||||
fatal("pinentry read() -- %s", strerror(errno));
|
||||
if (strncmp(line, "OK", 2) != 0)
|
||||
fatal("pinentry protocol failure");
|
||||
|
||||
if (fprintf(pfi, "GETPIN\n") < 0 || fflush(pfi) < 0)
|
||||
fatal("pinentry write() -- %s", strerror(errno));
|
||||
|
||||
if (!fgets(line, sizeof(line), pfo))
|
||||
fatal("pinentry read() -- %s", strerror(errno));
|
||||
if (strncmp(line, "ERR ", 4) == 0)
|
||||
fatal("passphrase entry canceled");
|
||||
else if (strncmp(line, "OK", 2) == 0)
|
||||
buf[0] = 0;
|
||||
else if (strncmp(line, "D ", 2) == 0)
|
||||
pinentry_decode(buf, len, line + 2);
|
||||
else
|
||||
fatal("pinentry protocol failure");
|
||||
pinentry(pfi, pfo, buf, len, prompt);
|
||||
|
||||
fclose(pfo);
|
||||
fclose(pfi);
|
||||
|
@ -538,7 +545,7 @@ get_passphrase(char *buf, size_t len, char *prompt)
|
|||
int tty;
|
||||
|
||||
if (pinentry_path) {
|
||||
invoke_pinentry(buf, len, prompt);
|
||||
pinentry_unix(buf, len, prompt);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -573,25 +580,149 @@ get_passphrase(char *buf, size_t len, char *prompt)
|
|||
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
static void
|
||||
pinentry_win32(char *buf, size_t len, char *prompt)
|
||||
{
|
||||
BOOL r;
|
||||
int fdi, fdo;
|
||||
FILE *pfi, *pfo;
|
||||
HANDLE pi[2], po[2];
|
||||
PROCESS_INFORMATION proc;
|
||||
STARTUPINFOA info = {sizeof(info)};
|
||||
SECURITY_ATTRIBUTES attr = {sizeof(attr), 0, TRUE};
|
||||
|
||||
r = CreatePipe(&pi[0], &pi[1], &attr, 0);
|
||||
if (!r) fatal("could not start pinentry");
|
||||
r = CreatePipe(&po[0], &po[1], &attr, 0);
|
||||
if (!r) fatal("could not start pinentry");
|
||||
|
||||
info.hStdInput = pi[0];
|
||||
info.hStdOutput = po[1];
|
||||
info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
info.dwFlags = STARTF_USESTDHANDLES;
|
||||
|
||||
r = CreateProcessA(0, pinentry_path, 0, 0, TRUE, 0, 0, 0, &info, &proc);
|
||||
if (!r) fatal("could not start pinentry: %s", pinentry_path);
|
||||
CloseHandle(po[1]);
|
||||
CloseHandle(pi[0]);
|
||||
|
||||
fdi = _open_osfhandle((intptr_t)pi[1], _O_APPEND);
|
||||
if (fdi == -1) fatal("could not start pinentry");
|
||||
pfi = fdopen(fdi, "wb");
|
||||
if (!pfi) fatal("could not start pinentry");
|
||||
|
||||
fdo = _open_osfhandle((intptr_t)po[0], _O_RDONLY);
|
||||
if (fdo == -1) fatal("could not start pinentry");
|
||||
pfo = fdopen(fdo, "rb");
|
||||
if (!pfo) fatal("could not start pinentry");
|
||||
setvbuf(pfo, 0, _IONBF, 0);
|
||||
|
||||
pinentry(pfi, pfo, buf, len, prompt);
|
||||
|
||||
fclose(pfo);
|
||||
fclose(pfi);
|
||||
}
|
||||
|
||||
static void
|
||||
get_passphrase(char *buf, size_t len, char *prompt)
|
||||
{
|
||||
DWORD orig;
|
||||
HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
|
||||
if (!GetConsoleMode(in, &orig)) {
|
||||
get_passphrase_dumb(buf, len, prompt);
|
||||
} else {
|
||||
size_t passlen;
|
||||
SetConsoleMode(in, orig & ~ENABLE_ECHO_INPUT);
|
||||
fputs(prompt, stderr);
|
||||
if (!fgets(buf, len, stdin))
|
||||
fatal("could not read passphrase");
|
||||
fputc('\n', stderr);
|
||||
passlen = strlen(buf);
|
||||
if (buf[passlen - 1] < ' ')
|
||||
buf[passlen - 1] = 0;
|
||||
int state = 0;
|
||||
int result = 0;
|
||||
WCHAR prev = 0;
|
||||
DWORD orig, mode;
|
||||
HANDLE hi, ho = INVALID_HANDLE_VALUE;
|
||||
unsigned char *p = (unsigned char *)buf;
|
||||
|
||||
if (pinentry_path) {
|
||||
pinentry_win32(buf, len, prompt);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set up input console handle */
|
||||
hi = CreateFileA(
|
||||
"CONIN$", GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0
|
||||
);
|
||||
orig = 0;
|
||||
if (!GetConsoleMode(hi, &orig)) goto done;
|
||||
mode = orig | ENABLE_PROCESSED_INPUT;
|
||||
mode &= ~ENABLE_LINE_INPUT;
|
||||
mode &= ~ENABLE_ECHO_INPUT;
|
||||
if (!SetConsoleMode(hi, mode)) goto done;
|
||||
|
||||
/* Set up output console handle */
|
||||
ho = CreateFileA(
|
||||
"CONOUT$", GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0
|
||||
);
|
||||
if (!WriteConsoleA(ho, prompt, strlen(prompt), 0, 0)) goto done;
|
||||
|
||||
/* Read a UTF-16 code point at a time. */
|
||||
while (p - (unsigned char *)buf < (ptrdiff_t)len - 1) {
|
||||
DWORD n;
|
||||
WCHAR wc;
|
||||
long c = 0;
|
||||
|
||||
if (!ReadConsoleW(hi, &wc, 1, &n, 0) || !n) goto done;
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (wc == '\r' || wc == '\n') {
|
||||
result = 1;
|
||||
((char *)buf)[p - (unsigned char *)buf] = 0;
|
||||
goto done;
|
||||
} else if (wc >= 0xd800 && wc < 0xdc00) {
|
||||
prev = wc;
|
||||
state = 1;
|
||||
continue;
|
||||
} else if (wc >= 0xdc00 && wc <= 0xdfff) {
|
||||
goto done; /* unpaired low surrogate */
|
||||
} else if (!wc) {
|
||||
goto done; /* binary input? */
|
||||
} else {
|
||||
c = wc;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (wc < 0xdc00 || wc > 0xdfff) {
|
||||
goto done; /* unpaired high surrogate */
|
||||
}
|
||||
c = 0x10000L | (prev - 0xd800)<<10 | (wc - 0xdc00);
|
||||
state = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c >= 1L<<16) {
|
||||
if (len - (p - (unsigned char *)buf) < 4) goto done;
|
||||
p[0] = 0xf0 | (c >> 18);
|
||||
p[1] = 0x80 | ((c >> 12) & 0x3f);
|
||||
p[2] = 0x80 | ((c >> 6) & 0x3f);
|
||||
p[3] = 0x80 | ((c >> 0) & 0x3f);
|
||||
p += 4;
|
||||
} else if (c >= 1L<<11) {
|
||||
if (len - (p - (unsigned char *)buf) < 3) goto done;
|
||||
p[0] = 0xe0 | (c >> 12);
|
||||
p[1] = 0x80 | ((c >> 6) & 0x3f);
|
||||
p[2] = 0x80 | ((c >> 0) & 0x3f);
|
||||
p += 3;
|
||||
} else if (c >= 1L<<7) {
|
||||
if (len - (p - (unsigned char *)buf) < 2) goto done;
|
||||
p[0] = 0xc0 | (c >> 6);
|
||||
p[1] = 0x80 | ((c >> 0) & 0x3f);
|
||||
p += 2;
|
||||
} else if (c) {
|
||||
p[0] = c;
|
||||
p += 1;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
/* Exploit that INVALID_HANDLE_VALUE is a no-op */
|
||||
WriteConsoleA(ho, "\n", 1, 0, 0);
|
||||
SetConsoleMode(hi, orig);
|
||||
CloseHandle(ho);
|
||||
CloseHandle(hi);
|
||||
if (!result) fatal("failed to read passphrase from console");
|
||||
(void)get_passphrase_dumb;
|
||||
}
|
||||
|
||||
#else
|
||||
|
@ -1589,6 +1720,12 @@ main(int argc, char **argv)
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Set stdin/stdout to binary mode. */
|
||||
_setmode(0, 0x8000);
|
||||
_setmode(1, 0x8000);
|
||||
#endif
|
||||
|
||||
switch (parse_command(command)) {
|
||||
case COMMAND_UNKNOWN:
|
||||
case COMMAND_AMBIGUOUS:
|
||||
|
|
Loading…
Reference in New Issue