diff options
| -rw-r--r-- | .clang-format | 95 | ||||
| -rw-r--r-- | LICENSE | 18 | ||||
| -rw-r--r-- | Makefile | 37 | ||||
| -rw-r--r-- | README.md | 29 | ||||
| -rw-r--r-- | common.h | 28 | ||||
| -rw-r--r-- | encpipe.c | 234 | ||||
| -rw-r--r-- | encpipe_p.h | 43 | ||||
| -rw-r--r-- | safe_rw.c | 81 | ||||
| -rw-r--r-- | safe_rw.h | 15 |
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 +... + @@ -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 |