Compare commits

...

13 Commits
3.5 ... master

Author SHA1 Message Date
degaart 54da5bf53f Added Makefile.cosmopolitan
Closes #30.
2023-02-14 10:55:40 -05:00
degaart 2c8642d26a Add cosmopolitan output files to .gitignore 2023-02-13 05:14:25 +00:00
Christopher Wellons 13aeab9fe8 Fix file name typo in the README
Thanks to an anonymous tip!
2021-06-08 08:35:01 -04:00
Daniel Dumitriu da8de6a647 Remove read constraint for directories on key path
On Unix, do not check whether all directories on the path to storage_dir
are readable; in corporate environments this is often not the case.

Closes #28.
2021-05-31 09:43:52 -04:00
Christopher Wellons b6bbbc56c2 Do not set EXE, accept value from environment
When building this program for Windows, users need only have EXE=.exe in
their environment for the Makefile to behave well.
2021-02-14 12:41:38 -05:00
Christopher Wellons 32d4d99472 Properly terminate pinentry protocol (BYE)
This isn't strictly necessary on unix-like systems since it will exit
when the pipes are closed. Unfortunately Windows isn't so nice and neat
and the program will remain open indefinitely even though its inputs and
outputs have been closed. So ask pinentry to terminate gracefully.
2020-05-18 19:09:22 -04:00
Christopher Wellons 50624f2373 Add pinentry support on Windows 2020-05-18 22:43:35 +00:00
Christopher Wellons a07053778f Add LDFLAGS and LDLIBS to Makefile 2020-05-18 22:37:30 +00:00
Christopher Wellons 6af0168662 Add EXE definition to Makefile
This helps with building for Windows:
make CC=x86_64-w64-mingw32-gcc EXE=.exe
2020-05-18 22:36:15 +00:00
Christopher Wellons 3abfeb2e00 Add -Wno-missing-field-initializers to CFLAGS
This is warning is annoying and is making too much noise.
2020-05-18 22:35:53 +00:00
Christopher Wellons 7cc0e13f0a Allow Unicode passphrases on Windows
This change uses ReadConsoleW() to read passphrase input. It converts
the passphrase from UTF-16 to UTF-8 before further processing. With
this, passphrase input is now consistent between platforms.

Currently Windows provides no options for reading keyboard input as
UTF-8, and it's certainly not supported by any of the various CRT
implementations. This is the only way to do it.
2020-05-03 14:39:07 -04:00
Christopher Wellons f7c4b6ba55 Support binary stdin/stdout on Windows
It's now possible to pipe files over standard input and standard output
in Windows.
2020-05-03 14:32:38 -04:00
Christopher Wellons b25af46615 Add MSVC linker #pragma for advzpi32.lib
This makes compilation using Visual Studio (cl.exe) slightly simpler.
2020-05-03 14:19:30 -04:00
5 changed files with 235 additions and 71 deletions

3
.gitignore vendored
View File

@ -3,3 +3,6 @@ enchive
*.enchive
enchive-cli.c
*.elc
*.swp
*.com
*.com.dbg

View File

@ -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

28
Makefile.cosmopolitan Normal file
View File

@ -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

View File

@ -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

View File

@ -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: