aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format95
-rw-r--r--LICENSE18
-rw-r--r--Makefile37
-rw-r--r--README.md29
-rw-r--r--common.h28
-rw-r--r--encpipe.c234
-rw-r--r--encpipe_p.h43
-rw-r--r--safe_rw.c81
-rw-r--r--safe_rw.h15
9 files changed, 580 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..564d08c
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,95 @@
+---
+Language: Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: true
+AlignConsecutiveDeclarations: true
+AlignEscapedNewlinesLeft: true
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: TopLevelDefinitions
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: WebKit
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: true
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: false
+DerivePointerAlignment: true
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IncludeCategories:
+ - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
+ Priority: 2
+ - Regex: '^(<|"(gtest|isl|json)/)'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 1
+IncludeIsMainRegex: '$'
+IndentCaseLabels: false
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: Inner
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Right
+ReflowComments: true
+SortIncludes: true
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 8
+UseTab: Never
+...
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..dcff33a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,18 @@
+/*
+ * ISC License
+ *
+ * Copyright (c) 2018
+ * Frank Denis <j at pureftpd dot org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..12bc6f5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+PREFIX ?= /usr/local
+WFLAGS ?= -Wall -Wextra -Wmissing-prototypes -Wdiv-by-zero -Wbad-function-cast -Wcast-align -Wcast-qual -Wfloat-equal -Wmissing-declarations -Wnested-externs -Wno-unknown-pragmas -Wpointer-arith -Wredundant-decls -Wstrict-prototypes -Wswitch-enum -Wno-type-limits
+CFLAGS ?= -Os -march=native -fno-exceptions $(WFLAGS)
+CFLAGS += -I.
+OBJ = encpipe.o safe_rw.o
+AR ?= ar
+RANLIB ?= ranlib
+STRIP ?= strip
+
+SRC = \
+ common.h \
+ encpipe.c \
+ encpipe_p.h \
+ safe_rw.c \
+ safe_rw.h
+
+all: bin
+
+bin: encpipe
+
+encpipe: $(OBJ)
+ $(CC) $(CFLAGS) -o encpipe $(OBJ) -lhydrogen
+
+install: bin
+ -$(STRIP) encpipe 2> /dev/null
+ mkdir -p $(PREFIX)/bin
+ install -o 0 -g 0 -m 0755 encpipe $(PREFIX)/bin 2> /dev/null || install -m 0755 encpipe $(PREFIX)/bin
+
+uninstall:
+ rm -f $(PREFIX)/bin/encpipe
+
+$(OBJ): $(SRC)
+
+.PHONY: clean
+
+clean:
+ rm -f encpipe $(OBJ)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c5be77f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+Encpipe
+=======
+
+# Usage
+
+Encrypt a file using a password:
+
+```sh
+encpipe -e -p password -i inputfile -o outputfile
+```
+
+Decrypt a file using a password:
+
+```sh
+encpipe -d -p password -i inputfile -o outputfile
+```
+
+`-i` and `-o` can be set to `-` or omitted to read/write from the
+standard input/output.
+
+# Dependencies
+
+[libhydrogen](https://github.com/jedisct1/libhydrogen)
+
+# Yes
+
+It's using passwords. Provided on the command-line. Yes, it actually
+does that. I needed that. Right now. Other options will be
+implemented. Next year.
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..8181c42
--- /dev/null
+++ b/common.h
@@ -0,0 +1,28 @@
+#ifndef common_H
+#define common_H 1
+
+#define LOAD32_LE(SRC) load32_le(SRC)
+static inline uint32_t
+load32_le(const uint8_t src[4])
+{
+ uint32_t w = (uint32_t) src[0];
+ w |= (uint32_t) src[1] << 8;
+ w |= (uint32_t) src[2] << 16;
+ w |= (uint32_t) src[3] << 24;
+ return w;
+}
+
+#define STORE32_LE(DST, W) store32_le((DST), (W))
+static inline void
+store32_le(uint8_t dst[4], uint32_t w)
+{
+ dst[0] = (uint8_t) w;
+ w >>= 8;
+ dst[1] = (uint8_t) w;
+ w >>= 8;
+ dst[2] = (uint8_t) w;
+ w >>= 8;
+ dst[3] = (uint8_t) w;
+}
+
+#endif
diff --git a/encpipe.c b/encpipe.c
new file mode 100644
index 0000000..79500c6
--- /dev/null
+++ b/encpipe.c
@@ -0,0 +1,234 @@
+#include "encpipe_p.h"
+
+static struct option getopt_long_options[] = {
+ { "help", 0, NULL, 'h' }, { "decrypt", 0, NULL, 'd' },
+ { "encrypt", 0, NULL, 'e' }, { "in", 1, NULL, 'i' },
+ { "out", 1, NULL, 'o' }, { "p", 1, NULL, 'p' },
+ { NULL, 0, NULL, 0 }
+};
+static const char *getopt_options = "hdei:o:p:";
+
+static void
+usage(void)
+{
+ puts(
+ "Usage:\n\n"
+ "Encrypt: encpipe -e -p <password> [-i <inputfile>] [-o <outputfile>]\n"
+ "Decrypt: encpipe -d -p <password> [-i <inputfile>] [-o <outputfile>]");
+ exit(0);
+}
+
+static void
+options_parse(Context *ctx, int argc, char *argv[])
+{
+ int opt_flag;
+ int option_index = 0;
+
+ ctx->encrypt = -1;
+ ctx->in = NULL;
+ ctx->out = NULL;
+ ctx->password = NULL;
+ optind = 0;
+#ifdef _OPTRESET
+ optreset = 1;
+#endif
+ while ((opt_flag = getopt_long(argc, argv, getopt_options,
+ getopt_long_options, &option_index)) != -1) {
+ switch (opt_flag) {
+ case 'd':
+ ctx->encrypt = 0;
+ break;
+ case 'e':
+ ctx->encrypt = 1;
+ break;
+ case 'i':
+ ctx->in = optarg;
+ break;
+ case 'o':
+ ctx->out = optarg;
+ break;
+ case 'p':
+ ctx->password = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ if (ctx->password == NULL || ctx->encrypt == -1) {
+ usage();
+ }
+}
+
+static int
+file_open(const char *file, int create)
+{
+ int fd;
+
+ if (file == NULL || (file[0] == '-' && file[1] == 0)) {
+ return create ? STDOUT_FILENO : STDIN_FILENO;
+ }
+ if (create) {
+ fd = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ } else {
+ fd = open(file, O_RDONLY);
+ }
+ if (fd == -1) {
+ fprintf(stderr, "Unable to access [%s]: [%s]\n", file, strerror(errno));
+ exit(1);
+ }
+ return fd;
+}
+
+static void
+derive_key(Context *ctx)
+{
+ static uint8_t master_key[hydro_pwhash_MASTERKEYBYTES] = { 0 };
+ size_t password_len = strlen(ctx->password);
+
+ if (hydro_pwhash_deterministic(ctx->key, sizeof ctx->key, ctx->password,
+ password_len, HYDRO_CONTEXT, master_key,
+ PWHASH_OPSLIMIT, PWHASH_MEMLIMIT,
+ PWHASH_THREADS) != 0) {
+ fprintf(stderr, "Password hashing failed\n");
+ exit(1);
+ }
+ hydro_memzero(ctx->password, password_len);
+}
+
+static int
+stream_encrypt(Context *ctx)
+{
+ unsigned char *const chunk_size_p = ctx->buf;
+ unsigned char *const chunk = chunk_size_p + 4;
+ uint64_t chunk_id;
+ ssize_t max_chunk_size;
+ ssize_t chunk_size;
+
+ assert(ctx->sizeof_buf > 4 + hydro_secretbox_HEADERBYTES);
+ max_chunk_size = ctx->sizeof_buf - 4 - hydro_secretbox_HEADERBYTES;
+ assert(max_chunk_size < 0x7fffffff);
+ chunk_id = 0;
+ while ((chunk_size =
+ safe_read_partial(ctx->fd_in, chunk, max_chunk_size)) >= 0) {
+ STORE32_LE(chunk_size_p, (uint32_t) chunk_size);
+ if (hydro_secretbox_encrypt(chunk, chunk, chunk_size, chunk_id,
+ HYDRO_CONTEXT, ctx->key) != 0) {
+ fprintf(stderr, "Encryption error\n");
+ exit(1);
+ }
+ if (safe_write(ctx->fd_out, chunk_size_p,
+ 4 + hydro_secretbox_HEADERBYTES + (size_t) chunk_size,
+ -1) < 0) {
+ perror("write()");
+ exit(1);
+ }
+ if (chunk_size == 0) {
+ break;
+ }
+ chunk_id++;
+ }
+ if (chunk_size < 0) {
+ perror("read()");
+ exit(1);
+ }
+ return 0;
+}
+
+static int
+stream_decrypt(Context *ctx)
+{
+ unsigned char *const chunk_size_p = ctx->buf;
+ unsigned char *const chunk = chunk_size_p + 4;
+ uint64_t chunk_id;
+ ssize_t readnb;
+ ssize_t max_chunk_size;
+ ssize_t chunk_size;
+
+ assert(ctx->sizeof_buf > 4 + hydro_secretbox_HEADERBYTES);
+ max_chunk_size = ctx->sizeof_buf - 4 - hydro_secretbox_HEADERBYTES;
+ assert(max_chunk_size < 0x7fffffff);
+ chunk_id = 0;
+ while ((readnb = safe_read(ctx->fd_in, chunk_size_p, 4)) == 4) {
+ chunk_size = LOAD32_LE(chunk_size_p);
+ if (chunk_size > max_chunk_size) {
+ fprintf(stderr, "Chunk size too large ([%zd] > [%zd])\n",
+ chunk_size, max_chunk_size);
+ exit(1);
+ }
+ if (safe_read(ctx->fd_in, chunk,
+ (size_t) chunk_size + hydro_secretbox_HEADERBYTES) !=
+ chunk_size + hydro_secretbox_HEADERBYTES) {
+ fprintf(stderr, "Chunk too short ([%zd] bytes expected)\n",
+ chunk_size);
+ exit(1);
+ }
+ if (hydro_secretbox_decrypt(chunk, chunk,
+ chunk_size + hydro_secretbox_HEADERBYTES,
+ chunk_id, HYDRO_CONTEXT, ctx->key) != 0) {
+ fprintf(stderr, "Unable to decrypt chunk #%" PRIu64 " - ",
+ chunk_id);
+ if (chunk_id == 0) {
+ fprintf(stderr, "Wrong password or key?\n");
+ } else {
+ fprintf(stderr, "Corrupted or incomplete file?\n");
+ }
+ exit(1);
+ }
+ if (chunk_size == 0) {
+ break;
+ }
+ if (safe_write(ctx->fd_out, chunk, chunk_size, -1) < 0) {
+ perror("write()");
+ exit(1);
+ }
+ chunk_id++;
+ }
+ if (readnb < 0) {
+ perror("read()");
+ exit(1);
+ }
+ if (chunk_size != 0) {
+ fprintf(stderr, "Premature end of file\n");
+ exit(1);
+ }
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ Context ctx;
+
+ if (hydro_init() < 0) {
+ fprintf(stderr, "Unable to initialize the crypto library");
+ exit(1);
+ }
+ memset(&ctx, 0, sizeof ctx);
+ options_parse(&ctx, argc, argv);
+ derive_key(&ctx);
+ ctx.sizeof_buf = DEFAULT_BUFFER_SIZE;
+ if (ctx.sizeof_buf < MIN_BUFFER_SIZE) {
+ ctx.sizeof_buf = MIN_BUFFER_SIZE;
+ } else if (ctx.sizeof_buf > MAX_BUFFER_SIZE) {
+ ctx.sizeof_buf = MAX_BUFFER_SIZE;
+ }
+ if ((ctx.buf = malloc(ctx.sizeof_buf)) == NULL) {
+ perror("malloc()");
+ exit(1);
+ }
+ assert(sizeof HYDRO_CONTEXT == hydro_secretbox_CONTEXTBYTES);
+
+ ctx.fd_in = file_open(ctx.in, 0);
+ ctx.fd_out = file_open(ctx.out, 1);
+ if (ctx.encrypt) {
+ stream_encrypt(&ctx);
+ } else {
+ stream_decrypt(&ctx);
+ }
+ free(ctx.buf);
+ close(ctx.fd_out);
+ close(ctx.fd_in);
+ hydro_memzero(&ctx, sizeof ctx);
+
+ return 0;
+}
diff --git a/encpipe_p.h b/encpipe_p.h
new file mode 100644
index 0000000..3e8e474
--- /dev/null
+++ b/encpipe_p.h
@@ -0,0 +1,43 @@
+#ifndef encpipe_p_H
+#define encpipe_p_H 1
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <hydrogen.h>
+
+#include "common.h"
+#include "safe_rw.h"
+
+#define MIN_BUFFER_SIZE 512
+#define MAX_BUFFER_SIZE 0x7fffffff
+#define DEFAULT_BUFFER_SIZE (1 * 1024 * 1024)
+#define HYDRO_CONTEXT "EncPipe"
+#define PWHASH_OPSLIMIT 1000000
+#define PWHASH_MEMLIMIT 0
+#define PWHASH_THREADS 1
+
+typedef struct Context_ {
+ char * in;
+ char * out;
+ char * password;
+ unsigned char key[hydro_secretbox_KEYBYTES];
+ unsigned char *buf;
+ size_t sizeof_buf;
+ int fd_in;
+ int fd_out;
+ int encrypt;
+} Context;
+
+#endif
diff --git a/safe_rw.c b/safe_rw.c
new file mode 100644
index 0000000..683d8b5
--- /dev/null
+++ b/safe_rw.c
@@ -0,0 +1,81 @@
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <unistd.h>
+
+#ifndef SSIZE_MAX
+#define SSIZE_MAX (SIZE_MAX / 2 - 1)
+#endif
+
+#include "safe_rw.h"
+
+ssize_t
+safe_write(const int fd, const void *const buf_, size_t count,
+ const int timeout)
+{
+ struct pollfd pfd;
+ const char * buf = (const char *) buf_;
+ ssize_t written;
+
+ pfd.fd = fd;
+ pfd.events = POLLOUT;
+
+ assert(count <= SSIZE_MAX);
+ while (count > (size_t) 0) {
+ while ((written = write(fd, buf, count)) <= (ssize_t) 0) {
+ if (errno == EAGAIN) {
+ if (poll(&pfd, (nfds_t) 1, timeout) == 0) {
+ errno = ETIMEDOUT;
+ goto ret;
+ }
+ } else if (errno != EINTR) {
+ goto ret;
+ }
+ }
+ buf += written;
+ count -= (size_t) written;
+ }
+ret:
+ return (ssize_t)(buf - (const char *) buf_);
+}
+
+ssize_t
+safe_read(const int fd, void *const buf_, size_t count)
+{
+ unsigned char *buf = (unsigned char *) buf_;
+ ssize_t readnb;
+
+ assert(count <= SSIZE_MAX);
+ do {
+ while ((readnb = read(fd, buf, count)) < (ssize_t) 0 && errno == EINTR)
+ ;
+ if (readnb < (ssize_t) 0) {
+ return readnb;
+ }
+ if (readnb == (ssize_t) 0) {
+ break;
+ }
+ count -= (size_t) readnb;
+ buf += readnb;
+ } while (count > (ssize_t) 0);
+
+ return (ssize_t)(buf - (unsigned char *) buf_);
+}
+
+ssize_t
+safe_read_partial(const int fd, void *const buf_, const size_t max_count)
+{
+ unsigned char *const buf = (unsigned char *) buf_;
+ ssize_t readnb;
+
+ assert(max_count <= SSIZE_MAX);
+ while ((readnb = read(fd, buf, max_count)) < (ssize_t) 0 && errno == EINTR)
+ ;
+
+ return readnb;
+}
diff --git a/safe_rw.h b/safe_rw.h
new file mode 100644
index 0000000..31f4aa6
--- /dev/null
+++ b/safe_rw.h
@@ -0,0 +1,15 @@
+#ifndef safe_rw_H
+#define safe_rw_H
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+ssize_t safe_write(const int fd, const void* const buf_, size_t count,
+ const int timeout);
+
+ssize_t safe_read(const int fd, void* const buf_, size_t count);
+
+ssize_t safe_read_partial(const int fd, void* const buf_,
+ const size_t max_count);
+
+#endif