Compare commits

...

9 Commits

8 changed files with 422 additions and 125 deletions

43
.ci/Jenkinsfile vendored Normal file
View File

@ -0,0 +1,43 @@
properties(
[
disableConcurrentBuilds()
]
)
node('linux && docker') {
try {
stage('Checkout') {
//branch name from Jenkins environment variables
echo "My branch is: ${env.BRANCH_NAME}"
// this doesn't grab tags pointing to this branch
//checkout scm
// this hack does... https://issues.jenkins.io/browse/JENKINS-45164
checkout([
$class: 'GitSCM',
branches: [[name: 'refs/heads/'+env.BRANCH_NAME]],
extensions: [[$class: 'CloneOption', noTags: false, shallow: false, depth: 0, reference: '']],
userRemoteConfigs: scm.userRemoteConfigs,
])
sh '''
set -euxo pipefail
git checkout "$BRANCH_NAME" --
git reset --hard "origin/$BRANCH_NAME"
'''
}
stage('Build + Deploy') {
sh 'curl --compressed -sL https://code.moparisthebest.com/moparisthebest/self-ci/raw/branch/master/build-ci.sh | bash'
}
currentBuild.result = 'SUCCESS'
} catch (Exception err) {
currentBuild.result = 'FAILURE'
} finally {
stage('Email') {
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: 'admin.jenkins@moparisthebest.com', sendToIndividuals: true])
}
sh './bin/build.sh docker-chown'
deleteDir()
}
}

View File

@ -1,12 +1,6 @@
#!/bin/sh
ARCH="$1"
set -exu
# change to the directory this script is in
cd "$(dirname "$0")"
# dependencies to build+test pegh
apk add build-base clang bash libsodium-dev libsodium-static openssl-dev openssl-libs-static
@ -31,58 +25,96 @@ ldd pegh.static.* || true
export TEST_BINS="./pegh.static.openssl ./pegh.openssl ./pegh.static.libsodium-openssl ./pegh.libsodium-openssl ./pegh.static.libsodium ./pegh.libsodium"
# as of 27-Nov-2020 aarch64 openssl has a bug which causes the tests to fail, should try to report upstream...
[ "$ARCH" == "aarch64" ] && export TEST_BINS="./pegh.static.libsodium-openssl ./pegh.libsodium-openssl ./pegh.static.libsodium ./pegh.libsodium"
# compile dynamically linked versions (with gcc) to openssl and libsodium, then test all 4 against each other
./test.sh
echo "successfully built and tested static pegh against libsodium and openssl!"
# tests have all passed, move binaries to release directory for later
mkdir -p release
mv pegh.static.libsodium "./release/pegh-linux-$ARCH-libsodium"
# as of 27-Nov-2020 aarch64 openssl has a bug which causes the tests to fail, should try to report upstream...
if [ "$ARCH" == "aarch64" ]
then
rm -f pegh.static.openssl pegh.static.libsodium-openssl
else
mv pegh.static.openssl "./release/pegh-linux-$ARCH-openssl"
mv pegh.static.libsodium-openssl "./release/pegh-linux-$ARCH-libsodium-openssl"
fi
# for our native arch, just once, go ahead and archive the git repo too for later release
if [ "$ARCH" == "amd64" ]
then
apk add git
git archive HEAD -9 --format zip -o ./release/pegh-source.zip
git archive HEAD -9 --format tar.gz -o ./release/pegh-source.tar.gz
fi
if [ "$ARCH" == "amd64" ] || [ "$ARCH" == "i386" ]
then
echo 'going to try to build windows here...'
apk add mingw-w64-gcc curl
apk add mingw-w64-gcc curl wine
STATIC_LIB_DIR="$(pwd)"
LIBSODIUM_VERSION=1.0.18
LIBSODIUM_VERSION='1.0.18'
OPENSSL_VERSION='1.1.1h_3'
OPENSSL_CURL_VERSION='7.73.0_3'
curl -O https://download.libsodium.org/libsodium/releases/libsodium-${LIBSODIUM_VERSION}-stable-mingw.tar.gz -O https://curl.haxx.se/windows/dl-7.67.0_5/openssl-1.1.1d_5-win64-mingw.zip -O https://curl.haxx.se/windows/dl-7.67.0_5/openssl-1.1.1d_5-win32-mingw.zip
if [ ! -d "${STATIC_LIB_DIR}/libsodium-win32" ]
then
echo "df9c5df0355ddd423350471f37d2a595b9b1be228a787f4ed8109c208d9e2655 libsodium-${LIBSODIUM_VERSION}-stable-mingw.tar.gz" > libs.sha256
echo '4f474918a1597d6d1d35e524cf79827623f8ce511259b0047ee95bc0fddbf29c openssl-1.1.1d_5-win32-mingw.zip' >> libs.sha256
echo '936260c5a865c8e3f6af35a5394dd1acc43063a40a206c717350f1a341d8d822 openssl-1.1.1d_5-win64-mingw.zip' >> libs.sha256
# only need to grab/unpack these once
curl -L -O https://download.libsodium.org/libsodium/releases/libsodium-${LIBSODIUM_VERSION}-mingw.tar.gz -O https://curl.se/windows/dl-${OPENSSL_CURL_VERSION}/openssl-${OPENSSL_VERSION}-win64-mingw.zip -O https://curl.se/windows/dl-${OPENSSL_CURL_VERSION}/openssl-${OPENSSL_VERSION}-win32-mingw.zip
sha256sum -c libs.sha256
echo "e499c65b1c511cbc6700e436deb3771c3baa737981114c9e9f85f2ec90176861 libsodium-${LIBSODIUM_VERSION}-mingw.tar.gz" > libs.sha256
echo "fcaa181d848ac56150f00bc46d204d81fde4448a9afe9ef3ca04cc21d3132cb4 openssl-${OPENSSL_VERSION}-win32-mingw.zip" >> libs.sha256
echo "913ddfa264ed9bae51f9deaa8ebce9d9450fa89fdf4c74ab41a6dfffb5880c67 openssl-${OPENSSL_VERSION}-win64-mingw.zip" >> libs.sha256
tar xzvf libsodium-${LIBSODIUM_VERSION}-stable-mingw.tar.gz
unzip openssl-1.1.1d_5-win32-mingw.zip
unzip openssl-1.1.1d_5-win64-mingw.zip
# fail if any of these hashes have changed
sha256sum -c libs.sha256
tar xzvf libsodium-${LIBSODIUM_VERSION}-mingw.tar.gz
unzip openssl-${OPENSSL_VERSION}-win32-mingw.zip
unzip openssl-${OPENSSL_VERSION}-win64-mingw.zip
fi
if [ "$ARCH" == "i386" ]
then
make CC=i686-w64-mingw32-cc PEGH_LIBSODIUM_WIN="${STATIC_LIB_DIR}/libsodium-win32" clean all
mv pegh.exe pegh-i386-libsodium.exe
mv pegh.exe pegh-windows-i386-libsodium.exe
make CC=i686-w64-mingw32-cc PEGH_OPENSSL_WIN="${STATIC_LIB_DIR}/openssl-1.1.1d-win32-mingw" clean all
mv pegh.exe pegh-i386-openssl.exe
make CC=i686-w64-mingw32-cc PEGH_OPENSSL_WIN="${STATIC_LIB_DIR}/openssl-${OPENSSL_VERSION}-win32-mingw" clean all
mv pegh.exe pegh-windows-i386-openssl.exe
make CC=i686-w64-mingw32-cc PEGH_OPENSSL_WIN="${STATIC_LIB_DIR}/openssl-1.1.1d-win32-mingw" PEGH_LIBSODIUM_WIN="${STATIC_LIB_DIR}/libsodium-win32" clean all
mv pegh.exe pegh-i386-libsodium-openssl.exe
make CC=i686-w64-mingw32-cc PEGH_OPENSSL_WIN="${STATIC_LIB_DIR}/openssl-${OPENSSL_VERSION}-win32-mingw" PEGH_LIBSODIUM_WIN="${STATIC_LIB_DIR}/libsodium-win32" clean all
mv pegh.exe pegh-windows-i386-libsodium-openssl.exe
fi
export wine="wine"
if [ "$ARCH" == "amd64" ]
then
export wine="wine64"
make CC=x86_64-w64-mingw32-cc PEGH_LIBSODIUM_WIN="${STATIC_LIB_DIR}/libsodium-win64" clean all
mv pegh.exe pegh-amd64-libsodium.exe
mv pegh.exe pegh-windows-amd64-libsodium.exe
make CC=x86_64-w64-mingw32-cc PEGH_OPENSSL_WIN="${STATIC_LIB_DIR}/openssl-1.1.1d-win64-mingw" clean all
mv pegh.exe pegh-amd64-openssl.exe
make CC=x86_64-w64-mingw32-cc PEGH_OPENSSL_WIN="${STATIC_LIB_DIR}/openssl-${OPENSSL_VERSION}-win64-mingw" clean all
mv pegh.exe pegh-windows-amd64-openssl.exe
make CC=x86_64-w64-mingw32-cc PEGH_OPENSSL_WIN="${STATIC_LIB_DIR}/openssl-1.1.1d-win64-mingw" PEGH_LIBSODIUM_WIN="${STATIC_LIB_DIR}/libsodium-win64" clean all
mv pegh.exe pegh-amd64-libsodium-openssl.exe
make CC=x86_64-w64-mingw32-cc PEGH_OPENSSL_WIN="${STATIC_LIB_DIR}/openssl-${OPENSSL_VERSION}-win64-mingw" PEGH_LIBSODIUM_WIN="${STATIC_LIB_DIR}/libsodium-win64" clean all
mv pegh.exe pegh-windows-amd64-libsodium-openssl.exe
fi
@ -91,5 +123,37 @@ strip *.exe
ls -lah *.exe
file *.exe
# todo: no testing these here for now...
# running the test script sometimes locks up wine, I think due to races on creating ~/.wine, so do that first...
$wine ./pegh-windows-$ARCH-libsodium.exe -h
# now test windows binaries against the static ones with wine
# no binfmt here where executing .exe *just works*, so do it hacky way :'(
export TEST_BINS="./release/pegh-linux-$ARCH-openssl ./release/pegh-linux-$ARCH-libsodium-openssl ./release/pegh-linux-$ARCH-libsodium"
# we've really already tested all of the above against each other, let's just test windows against one
export TEST_BINS="./release/pegh-linux-$ARCH-openssl"
for exe in *.exe
do
script="$exe.sh"
cat > "$script" <<EOF
#!/bin/sh
exec $wine "./$exe" "\$@"
EOF
chmod +x "$script"
export TEST_BINS="./$script $TEST_BINS"
done
./test.sh
echo "windows binaries pass tests through wine!"
killall pegh-windows-amd64-libsodium-openssl.exe pegh-windows-amd64-libsodium.exe pegh-windows-amd64-openssl.exe pegh-windows-i386-libsodium-openssl.exe pegh-windows-i386-libsodium.exe pegh-windows-i386-openssl.exe || true
sleep 5
killall -9 pegh-windows-amd64-libsodium-openssl.exe pegh-windows-amd64-libsodium.exe pegh-windows-amd64-openssl.exe pegh-windows-i386-libsodium-openssl.exe pegh-windows-i386-libsodium.exe pegh-windows-i386-openssl.exe || true
sleep 5
rm -rf ~/.wine /tmp/.wine*
# for later release
mv *.exe ./release/
fi

View File

@ -1,23 +0,0 @@
#!/bin/sh
DOCKER_IMAGE="$1"
shift
ARCH="$1"
BUILD_DIR=/tmp/static/
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"
cp * .ci/build.sh "$BUILD_DIR"
docker run --rm -v "$BUILD_DIR":/tmp "$DOCKER_IMAGE" /tmp/build.sh "$ARCH" || exit 1
mv "$BUILD_DIR"pegh.static.openssl "./pegh-$ARCH-openssl"
mv "$BUILD_DIR"pegh.static.libsodium "./pegh-$ARCH-libsodium"
mv "$BUILD_DIR"pegh.static.libsodium-openssl "./pegh-$ARCH-libsodium-openssl"
mv "$BUILD_DIR"pegh-*.exe ./
rm -rf "$BUILD_DIR" 2>/dev/null
exit 0

View File

@ -1,22 +0,0 @@
language: minimal
services: docker
matrix:
include:
- env: ARCH='amd64' DOCKER_IMAGE='alpine'
- env: ARCH='i386' DOCKER_IMAGE='i386/alpine'
- env: ARCH='aarch64' DOCKER_IMAGE='alpine'
arch: arm64
script:
- ./.ci/docker_build.sh "$DOCKER_IMAGE" "$ARCH"
deploy:
api_key:
secure: $GITHUB_OAUTH
file_glob: true
file: pegh-*
on:
tags: true
provider: releases
skip_cleanup: true

View File

@ -8,6 +8,11 @@ CFLAGS += -Wall -Wextra -Werror -std=c89 -pedantic \
-Wno-missing-prototypes -Wno-missing-noreturn -Wno-format \
-O3
# for now we want termios used by default unless explicitly disabled
ifndef NO_TERMIOS
CFLAGS += -D_POSIX_SOURCE
endif
# build or grab from https://curl.haxx.se/windows/
ifdef PEGH_OPENSSL_WIN

View File

@ -1,28 +1,30 @@
pegh
----
[![Travis-CI Build Status](https://api.travis-ci.com/moparisthebest/pegh.svg?branch=master)](https://travis-ci.com/moparisthebest/pegh)
[![Build Status](https://ci.moparisthe.best/job/moparisthebest/job/pegh/job/master/badge/icon%3Fstyle=plastic)](https://ci.moparisthe.best/job/moparisthebest/job/pegh/job/master/)
pegh is a file encryption tool using passwords with modern, standardized, and authenticated encryption. It is simple, secure, and returns proper exit codes so you can tell whether encryption or decryption failed or not.
[pegh](http://klingonska.org/dict/?q=tlh%3Apegh) is Klingon for secret
This implementation is built in C and can link with OpenSSL, libsodium, *or* libsodium AND OpenSSL in which case it falls back to OpenSSL's software AES implementation if the CPU does not support libsodium's. Every commit is built and tested in every combination and currently on 3 different architectures on Linux. The code aims to be fully portable C that should compile on anything with a C89 compiler (depending on crypto backend chosen).
This implementation is built in C and can link with OpenSSL, libsodium, *or* libsodium AND OpenSSL in which case it falls back to OpenSSL's software AES implementation if the CPU does not support libsodium's. Every commit is built and tested in every combination and currently on 3 different architectures on Linux and 2 on Windows. The code aims to be fully portable C that should compile on anything with a C89 compiler (depending on crypto backend chosen).
Releases
--------
[Releases](https://github.com/moparisthebest/pegh/releases) contain static binaries for:
* Linux amd64, i386, aarch64
* Linux amd64, i386, aarch64, armv7, ppc64le
* Windows amd64, i386
* more to come?
what do the names mean?
what do the names mean? where `$OS` is your Operating System and `$ARCH` is your CPU architecture:
* `pegh-$ARCH-openssl` - supports AES-256-GCM and Chacha20-Poly1305 on all CPUs
* `pegh-$ARCH-libsodium` - supports Chacha20-Poly1305 on all CPUs, but AES-256-GCM only on CPUs with hardware support for aes-ni
* `pegh-$ARCH-libsodium-openssl` - supports AES-256-GCM and Chacha20-Poly1305 on all CPUs, uses libsodium for everything if possible, but OpenSSL's software AES implementation if the CPU does not support aes-ni
* `pegh-$OS-$ARCH-openssl` - supports AES-256-GCM and Chacha20-Poly1305 on all CPUs
* `pegh-$OS-$ARCH-libsodium` - supports Chacha20-Poly1305 on all CPUs, but AES-256-GCM only on CPUs with hardware support for aes-ni
* `pegh-$OS-$ARCH-libsodium-openssl` - supports AES-256-GCM and Chacha20-Poly1305 on all CPUs, uses libsodium for everything if possible, but OpenSSL's software AES implementation if the CPU does not support aes-ni
Arch Linux [AUR](https://aur.archlinux.org/packages/pegh/) package
Usage
-----
@ -78,6 +80,7 @@ usage: pegh [options...] password
almost linearly scale with -N, if too low operation will fail,
default: 64
-f <filename> read password from file instead of argument, - means stdin
-g prompt for password, confirm on encryption, max characters: 64
-N <num> scrypt parameter N, only applies for encryption, default 32768
this is rounded up to the next highest power of 2
-r <num> scrypt parameter r, only applies for encryption, default 8

289
pegh.c
View File

@ -25,6 +25,25 @@
#include <limits.h>
#include <errno.h>
#if !defined(_WIN32) && (_POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE)
#define USE_TERMIOS 1
#endif
#ifdef USE_TERMIOS
#include <termios.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#define STDIN 0
#define STDOUT 1
#endif
/* default of OpenSSL for now... */
#if !defined(PEGH_OPENSSL) && !defined(PEGH_LIBSODIUM)
#define PEGH_OPENSSL 1
@ -51,19 +70,21 @@
* https://tools.ietf.org/html/rfc7914#section-2
* https://blog.filippo.io/the-scrypt-parameters/
*/
const uint32_t SCRYPT_N = 32768;
const uint8_t SCRYPT_R = 8;
const uint8_t SCRYPT_P = 1;
const size_t SCRYPT_MAX_MEM = 1024 * 1024 * 64; /* 64 megabytes */
static const uint32_t SCRYPT_N = 32768;
static const uint8_t SCRYPT_R = 8;
static const uint8_t SCRYPT_P = 1;
static const uint64_t SCRYPT_MAX_MEM = 1024 * 1024 * 64; /* 64 megabytes */
/* memory use will be twice this */
const uint32_t DEFAULT_CHUNK_SIZE_MB = 32;
static const uint32_t DEFAULT_CHUNK_SIZE_MB = 32;
/*
* this should be increased regularly, and only checked on encryption
* to allow old archives to be decrypted with shorter passwords
*/
const size_t MINIMUM_PASSWORD_LEN = 12;
static const size_t MINIMUM_PASSWORD_LEN = 12;
/* technically they can only enter at most 2 less than this */
static const size_t MANUAL_ENTRY_PASSWORD_MAX_LEN = 66;
/*
* pegh file format, numbers are inclusive 0-based byte array indices
@ -94,18 +115,18 @@ const size_t MINIMUM_PASSWORD_LEN = 12;
/* don't touch below here unless you know what you are doing */
#define PEGH_VERSION "1.0.0"
#define PEGH_VERSION "0.9.3"
/* 256 bit key required for AES-256 */
#define KEY_LEN 32
/* 1 for file format version, 4 for N, 1 for r, 1 for p, 4 for block/buffer size */
const size_t PRE_SALT_LEN = 11;
static const size_t PRE_SALT_LEN = 11;
/* from libsodium's crypto_pwhash_scryptsalsa208sha256_SALTBYTES */
#define SALT_LEN 32
/* AES-GCM/Chacha20-Poly1305 should only ever have an IV_LEN of 12 */
#define IV_LEN 12
const size_t AEAD_TAG_LEN = 16;
static const size_t AEAD_TAG_LEN = 16;
/* libsodium only supports AES on specific platforms, this jazz is to fallback to openssl impls in those cases */
typedef int (*aead_func)(const unsigned char *, const size_t,
@ -358,14 +379,14 @@ int chacha_decrypt(const unsigned char *ciphertext, const size_t ciphertext_len,
/* returns 1 on success, 0 on error */
int scrypt_derive_key(char *password, const size_t password_len,
uint32_t scrypt_max_mem, uint32_t N,
uint64_t scrypt_max_mem, uint32_t N,
uint8_t r, uint8_t p, unsigned char *salt, unsigned char *key, FILE *err) {
/* derive key using salt, password, and scrypt parameters */
if (EVP_PBE_scrypt(
password, password_len,
salt, SALT_LEN,
(uint64_t) N, (uint64_t) r, (uint64_t) p,
(uint64_t) scrypt_max_mem,
scrypt_max_mem,
key, KEY_LEN
) <= 0) {
if(NULL != err) {
@ -524,7 +545,7 @@ int chacha_decrypt(const unsigned char *ciphertext, const size_t ciphertext_len,
/* returns 1 on success, 0 on error */
int scrypt_derive_key(char *password, const size_t password_len,
uint32_t scrypt_max_mem, uint32_t N,
uint64_t scrypt_max_mem, uint32_t N,
uint8_t r, uint8_t p, unsigned char *salt, unsigned char *key, FILE *err) {
size_t needed_memory;
/* derive key using salt, password, and scrypt parameters */
@ -801,7 +822,7 @@ void write_uint32_big_endian(uint32_t val, unsigned char *buf) {
/* returns 1 on success, 0 on failure */
int scrypt_derive_key_stream(const stream_func crypt_stream, const aead_func aead,
char *password, const size_t password_len,
uint32_t scrypt_max_mem, size_t buffer_size,
uint64_t scrypt_max_mem, size_t buffer_size,
FILE *in, FILE *out, FILE *err,
uint32_t N, uint8_t r, uint8_t p, unsigned char *salt) {
unsigned char key[KEY_LEN] = {0};
@ -849,7 +870,7 @@ int check_version(uint8_t version, FILE *err) {
/* returns 1 on success, 0 on failure */
int pegh_encrypt(char *password, const size_t password_len,
uint32_t scrypt_max_mem, size_t buffer_size,
uint64_t scrypt_max_mem, size_t buffer_size,
FILE *in, FILE *out, FILE *err,
uint8_t version,
uint32_t N, uint8_t r, uint8_t p)
@ -888,7 +909,7 @@ int pegh_encrypt(char *password, const size_t password_len,
/* returns 1 on success, 0 on failure */
int pegh_decrypt(char *password, const size_t password_len,
uint32_t scrypt_max_mem, size_t max_buffer_size,
uint64_t scrypt_max_mem, size_t max_buffer_size,
FILE *in, FILE *out, FILE *err)
{
unsigned char salt[SALT_LEN] = {0};
@ -964,6 +985,10 @@ usage: pegh [options...] password\n\
default: %d\n", CHUNK_SIZE_MAX_GCM / 1024 / 1024, CHUNK_SIZE_MAX_CHACHA / 1024 / 1024, DEFAULT_CHUNK_SIZE_MB, SCRYPT_MAX_MEM / 1024 / 1024);
fprintf(stderr, "\
-f <filename> read password from file instead of argument, - means stdin\n\
-g prompt for password, confirm on encryption, max characters: %lu\n",
MANUAL_ENTRY_PASSWORD_MAX_LEN - 2
);
fprintf(stderr, "\
-N <num> scrypt parameter N, only applies for encryption, default %d\n\
this is rounded up to the next highest power of 2\n\
-r <num> scrypt parameter r, only applies for encryption, default %d\n\
@ -988,7 +1013,7 @@ void help_exit(int exit_code) {
exit(exit_code);
}
uint32_t parse_int_arg(int optind, int argc, char **argv) {
uint64_t parse_u64_arg(int optind, int argc, char **argv) {
uint64_t tmp = 0;
if(optind >= argc) {
@ -1003,15 +1028,27 @@ uint32_t parse_int_arg(int optind, int argc, char **argv) {
help_exit(2);
return 0;
}
return tmp;
}
uint32_t parse_u32_arg(int optind, int argc, char **argv) {
uint64_t tmp;
tmp = parse_u64_arg(optind, argc, argv);
if(tmp > 4294967295UL) {
fprintf(stderr, "Error: %s %s failed to parse as a number 0-4294967295\n", argv[optind - 1], argv[optind]);
help_exit(2);
return 0;
}
return (uint32_t) tmp;
}
uint8_t parse_byte_arg(int optind, int argc, char **argv) {
uint32_t tmp;
uint64_t tmp;
tmp = parse_int_arg(optind, argc, argv);
tmp = parse_u64_arg(optind, argc, argv);
if(tmp > 255) {
fprintf(stderr, "Error: %s %s failed to parse as a number 1-255\n", argv[optind - 1], argv[optind]);
fprintf(stderr, "Error: %s %s failed to parse as a number 0-255\n", argv[optind - 1], argv[optind]);
help_exit(2);
return 0;
}
@ -1051,6 +1088,12 @@ char* read_file_fully(const char *in_filename, size_t *file_len) {
return NULL;
}
}
#ifdef _WIN32
else {
/* windows in/out is text and mangles certain bytes by default... */
setmode(STDIN, O_BINARY);
}
#endif
contents = malloc(total_size);
if(!contents) {
@ -1092,14 +1135,148 @@ char* read_file_fully(const char *in_filename, size_t *file_len) {
return contents;
}
/* returns -1 on error, or 0+ for length of string read into buff */
int read_passwd(char *prompt, char *buff, int buff_len, FILE *in, FILE *out)
{
int pass_len;
/*
* Turn echoing off and fail if we cant.
* we support POSIX and Windows here
*/
#ifdef USE_TERMIOS
struct termios orig_input_flags, new_input_flags;
if (tcgetattr (fileno (in), &orig_input_flags) != 0)
return -1;
new_input_flags = orig_input_flags;
new_input_flags.c_lflag &= (tcflag_t) ~ECHO;
if (tcsetattr (fileno (in), TCSAFLUSH, &new_input_flags) != 0)
return -1;
#endif
#ifdef _WIN32
HANDLE hStdin;
DWORD orig_input_flags;
hStdin = GetStdHandle(STD_INPUT_HANDLE);
if (hStdin == INVALID_HANDLE_VALUE)
return -1;
/* Save the current input mode, to be restored on exit. */
if (! GetConsoleMode(hStdin, &orig_input_flags) )
return -1;
if (! SetConsoleMode(hStdin, orig_input_flags & (DWORD) ~ENABLE_ECHO_INPUT) )
return -1;
#endif
/* Display the prompt */
fprintf(out, "%s", prompt);
/* Read the password. cast is safe because buff_len is int */
pass_len = fgets (buff, buff_len, in) == NULL ? 0 : (int) strlen(buff);
/* Remove the line feed */
if (pass_len >= 1 && buff[pass_len - 1] == '\n') {
buff[--pass_len] = 0;
}
/* Remove the carriage return */
if (pass_len >= 1 && buff[pass_len - 1] == '\r') {
buff[--pass_len] = 0;
}
/* Restore terminal. we could check failure here but if it failed, what could we do? */
#ifdef USE_TERMIOS
tcsetattr (fileno (in), TCSAFLUSH, &orig_input_flags);
fprintf(out, "\n"); /* windows does this automatically */
#endif
#ifdef _WIN32
SetConsoleMode(hStdin, orig_input_flags);
#endif
return pass_len;
}
/* returns -1 on error, or 0+ for length of string read into buff */
int read_passwd_console(char *prompt, char *buff, int buff_len) {
int pass_len;
FILE *in;
#ifdef _WIN32
in = fopen("CONIN$", "r+");
/* this *should* work here but does not, just using stderr for now
out = fopen("CONOUT$", "w");
*/
if(in) {
pass_len = read_passwd(prompt, buff, buff_len, in, stderr);
fclose(in);
return pass_len;
}
#else
in = fopen("/dev/tty", "r+");
if(in) {
pass_len = read_passwd(prompt, buff, buff_len, in, in);
fclose(in);
return pass_len;
}
#endif
/* oh well, we tried */
return read_passwd(prompt, buff, buff_len, stdin, stderr);
}
/* returns -1 on error, or 0+ for length of string read into buff */
int read_passwd_console_confirm(char *buff, int buff_len, int min_passwd_length) {
int pass_len, confirm_len;
char *confirm_passwd;
confirm_passwd = malloc((size_t) buff_len);
if(!confirm_passwd)
return -1;
while(1) {
pass_len = read_passwd_console("Enter encryption password: ", buff, buff_len);
if(-1 == pass_len)
break; /* fail */
if(pass_len < min_passwd_length) {
fprintf(stderr, "Error: Minimum password length is %d but was only %d, try again...\n", min_passwd_length, pass_len);
continue;
}
if(pass_len == (buff_len - 1)) {
fprintf(stderr, "Error: Maximum password length for manual entry (%d) exceeded, try again...\n", buff_len - 2);
continue;
}
confirm_len = read_passwd_console("Confirm password: ", confirm_passwd, buff_len);
if(-1 == confirm_len)
break; /* fail */
if(pass_len == confirm_len && strcmp(buff, confirm_passwd) == 0)
break; /* finally success! */
fprintf(stderr, "Error: Entered passwords do not match, try again...\n");
}
/* wipe confirm password */
wipe_memory(confirm_passwd, (size_t) buff_len);
if(pass_len == -1 || pass_len == 0) {
/* wipe entire password buffer */
wipe_memory(buff, (size_t) buff_len);
} else {
/* wipe the unused portion of the read password too, in case some secrets exist there */
wipe_memory(buff + (pass_len + 1), (size_t) (buff_len - pass_len - 1));
}
free(confirm_passwd);
return pass_len;
}
/* returns 0 on success, 1 on cryptography failure, 19 on "libsodium only and CPU does not support AES" error, 2 on other failure */
int main(int argc, char **argv)
{
int optind, decrypt = 0, append = 0, exit_code = 2, version = -1;
int optind, decrypt = 0, append = 0, passwd_prompt = 0, exit_code = 2, version = -1;
char *password = NULL;
uint32_t N = SCRYPT_N, scrypt_max_mem = SCRYPT_MAX_MEM, buffer_size = DEFAULT_CHUNK_SIZE_MB * 1024 * 1024, scale = 1;
uint64_t scrypt_max_mem = SCRYPT_MAX_MEM;
uint32_t N = SCRYPT_N, buffer_size = DEFAULT_CHUNK_SIZE_MB * 1024 * 1024, scale = 1;
uint8_t r = SCRYPT_R, p = SCRYPT_P;
size_t password_len;
size_t password_len = MINIMUM_PASSWORD_LEN;
FILE *in = stdin, *out = stdout, *err = stderr;
char *in_filename = NULL, *out_filename = NULL;
@ -1139,17 +1316,20 @@ int main(int argc, char **argv)
case 'f':
password = read_file_fully(parse_filename_arg(++optind, argc, argv), &password_len);
break;
case 'g':
passwd_prompt = 1;
break;
case 'c':
buffer_size = parse_int_arg(++optind, argc, argv) * 1024 * 1024;
buffer_size = parse_u32_arg(++optind, argc, argv) * 1024 * 1024;
break;
case 'm':
scrypt_max_mem = parse_int_arg(++optind, argc, argv) * 1024 * 1024;
scrypt_max_mem = parse_u64_arg(++optind, argc, argv) * 1024 * 1024;
break;
case 'N':
N = next_highest_power_of_2(parse_int_arg(++optind, argc, argv));
N = next_highest_power_of_2(parse_u32_arg(++optind, argc, argv));
break;
case 's':
scale = next_highest_power_of_2(parse_int_arg(++optind, argc, argv));
scale = next_highest_power_of_2(parse_u32_arg(++optind, argc, argv));
break;
case 'r':
r = parse_byte_arg(++optind, argc, argv);
@ -1199,17 +1379,40 @@ int main(int argc, char **argv)
}
if(password == NULL) {
if(argc == optind) {
fprintf (stderr, "Error: no password provided\n");
return help(exit_code);
}
if(passwd_prompt) {
password = malloc(MANUAL_ENTRY_PASSWORD_MAX_LEN);
if(!password) {
fprintf (stderr, "Error: cannot allocate memory to prompt for password\n");
return exit_code;
}
if(decrypt) {
/* for decryption, we just read the password once, maybe print a warning, try to decrypt */
optind = read_passwd_console("Enter decryption password: ", password, MANUAL_ENTRY_PASSWORD_MAX_LEN);
if(optind == (MANUAL_ENTRY_PASSWORD_MAX_LEN - 1)) {
/* possibly truncated */
fprintf (stderr, "Warning: read maximum characters (%d) for password, possibly truncated, attempting decryption anyway...!\n", optind);
}
} else {
optind = read_passwd_console_confirm(password, MANUAL_ENTRY_PASSWORD_MAX_LEN, MINIMUM_PASSWORD_LEN);
}
if(optind == -1) {
fprintf (stderr, "Error: cannot prompt user for password!\n");
return exit_code;
}
password_len = (size_t) optind;
} else {
if(argc == optind) {
fprintf (stderr, "Error: no password provided\n");
return help(exit_code);
}
if((argc - optind) != 1) {
fprintf (stderr, "Error: more than one password provided\n");
return help(exit_code);
if((argc - optind) != 1) {
fprintf (stderr, "Error: more than one password provided\n");
return help(exit_code);
}
password = argv[optind];
password_len = strlen(password);
}
password = argv[optind];
password_len = strlen(password);
}
if(0 == decrypt && password_len < MINIMUM_PASSWORD_LEN) {
@ -1222,7 +1425,7 @@ int main(int argc, char **argv)
scrypt_max_mem *= scale;
/*
fprintf (stderr, "decrypt = %d, key = %s, scrypt_max_mem = %d, N = %d, r = %d, p = %d, scale = %d\n",
fprintf (stderr, "decrypt = %d, key = %s, scrypt_max_mem = %llu, N = %lu, r = %u, p = %u, scale = %u\n",
decrypt, password, scrypt_max_mem, N, r, p, scale);
return 0;
*/
@ -1234,6 +1437,13 @@ int main(int argc, char **argv)
return exit_code;
}
}
#ifdef _WIN32
else {
/* windows in/out is text and mangles certain bytes by default... */
setmode(STDIN, O_BINARY);
}
#endif
if(NULL != out_filename) {
out = fopen(out_filename, append ? "ab" : "wb");
if(!out) {
@ -1243,6 +1453,13 @@ int main(int argc, char **argv)
return exit_code;
}
}
#ifdef _WIN32
else {
/* windows in/out is text and mangles certain bytes by default... */
setmode(STDOUT, O_BINARY);
}
#endif
if(decrypt)
exit_code = pegh_decrypt(password, password_len, scrypt_max_mem, buffer_size, in, out, err);

30
test.sh
View File

@ -1,19 +1,32 @@
#!/bin/bash
export dummy_file="$1"
export tmp_folder="$1"
shift
export dummy_mb="$1"
[ "$dummy_file" = "" ] && export dummy_file='/tmp/randombytes'
[ "$tmp_folder" = "" ] && export tmp_folder='/tmp'
[ "$dummy_mb" = "" ] && export dummy_mb='100'
[ "$TEST_BINS" = "" ] && TEST_BINS="./pegh.openssl ./pegh.libsodium ./pegh.libsodium-openssl"
dummy_file="${tmp_folder}/randombytes${dummy_mb}"
leading_zero_key="${tmp_folder}/leading_zero_key"
leading_zero_key_a="${tmp_folder}/leading_zero_key_a"
leading_zero_key_b="${tmp_folder}/leading_zero_key_b"
set -euxo pipefail
# try different size files to encrypt/decrypt
[ -e "$dummy_file" ] || dd if=/dev/urandom bs=1M "count=$dummy_mb" of="$dummy_file"
export key="$(< /dev/urandom tr -dc 'a-z0-9' | head -c12)"
echo "key: $key"
[ -e "$leading_zero_key" ] || cat <(dd if=/dev/zero bs=1M count=1) <(echo "$key") > "$leading_zero_key"
[ -e "$leading_zero_key_a" ] || cat "$leading_zero_key" <(echo -n a) > "$leading_zero_key_a"
[ -e "$leading_zero_key_b" ] || cat "$leading_zero_key" <(echo -n b) > "$leading_zero_key_b"
# try make if it's installed, otherwise fall back to cc
rm -f pegh
@ -29,10 +42,6 @@ mv pegh pegh.libsodium
make PEGH_LIBSODIUM=1 PEGH_OPENSSL=1 || cc -O3 -DPEGH_LIBSODIUM -DPEGH_OPENSSL pegh.c -lsodium -lcrypto -o pegh
mv pegh pegh.libsodium-openssl
export key="$(< /dev/urandom tr -dc 'a-z0-9' | head -c12)"
echo "key: $key"
test () {
bin="$1"
shift
@ -58,12 +67,13 @@ test () {
echo 'encrypting then decrypting with the same key should succeed'
"$bin" -e "$@" "$key" < "$dummy_file" | "$bin_decrypt" -d "$key" | cmp - "$dummy_file"
echo 'test with -s 32 requiring 2gb of ram should succeed'
# this test is so (rightly) slow it makes our CI builds take 6+ hours, disable for now
#echo 'test with -s 32 requiring 2gb of ram should succeed'
# can send -s 32 or -m 2048 to decrypt command with identical effect
"$bin" -e "$@" "$key" -s 32 < "$dummy_file" | "$bin_decrypt" -d "$key" -m 2048 | cmp - "$dummy_file"
#"$bin" -e "$@" "$key" -s 32 < "$dummy_file" | "$bin_decrypt" -d "$key" -m 2048 | cmp - "$dummy_file"
echo 'encrypting/decrypting with key in file should work, even when key has leading 0s and a trailing newline'
"$bin" -e "$@" -f <(cat <(dd if=/dev/zero bs=1M count=1) <(echo "$key")) < "$dummy_file" | "$bin_decrypt" -d -f <(cat <(dd if=/dev/zero bs=1M count=1) <(echo "$key")) | cmp - "$dummy_file"
"$bin" -e "$@" -f "$leading_zero_key" < "$dummy_file" | "$bin_decrypt" -d -f "$leading_zero_key" | cmp - "$dummy_file"
set +e
# these should fail
@ -71,7 +81,7 @@ test () {
"$bin" -e "$@" "$key" -i "$dummy_file" | "$bin_decrypt" -d "$key-wrongkey" | cmp - "$dummy_file" && echo "ERROR: appending -wrongkey to key somehow still worked" && exit 1
echo 'encrypting/decrypting with key in file where last byte is different should fail'
"$bin" -e "$@" -f <(cat <(dd if=/dev/zero bs=1M count=1) <(echo "$key") <(echo -n a)) < "$dummy_file" | "$bin_decrypt" -d -f <(cat <(dd if=/dev/zero bs=1M count=1) <(echo "$key") <(echo -n b)) | cmp - "$dummy_file" && echo "ERROR: differing last byte in password file somehow still worked" && exit 1
"$bin" -e "$@" -f "$leading_zero_key_a" < "$dummy_file" | "$bin_decrypt" -d -f "$leading_zero_key_b" | cmp - "$dummy_file" && echo "ERROR: differing last byte in password file somehow still worked" && exit 1
echo 'large values of N without enough memory should fail'
"$bin" -e "$@" "$key" -N 2000000 -i "$dummy_file" >/dev/null && echo "ERROR: N of 2 million without extra memory worked" && exit 1