# HG changeset patch # User Guido Berhoerster # Date 1390933396 -3600 # Node ID 73af139d1a94b197a6a24bd2bb7f6eeadc268bd5 Initial revision diff -r 000000000000 -r 73af139d1a94 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Tue Jan 28 19:23:16 2014 +0100 @@ -0,0 +1,92 @@ +# +# Copyright (C) 2014 Guido Berhoerster +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +PACKAGE = sencrypt +VERSION = 1 +DISTNAME := $(PACKAGE)-$(VERSION) +DECRYPT_ALIAS = sdecrypt + +COMPILE.c = $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(TARGET_ARCH) -c +# gcc, clang, icc +MAKEDEPEND.c = $(CC) -MM $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) +# Sun/Solaris Studio +#MAKEDEPEND.c = $(CC) -xM1 $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) +# X makedepend +#MAKEDEPEND.c = makedepend -f- -Y -- $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) -- +LINK.c = $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH) +LINK.o = $(CC) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH) +INSTALL := install +INSTALL.exec := $(INSTALL) -D -m 0755 +INSTALL.data := $(INSTALL) -D -m 0644 +PAX := pax +GZIP := gzip +SED := sed + +DESTDIR ?= +prefix ?= /usr/local +bindir ?= $(prefix)/bin +datadir ?= $(prefix)/share + +HAVE_ERR_H ?= 1 + +OBJS = sencrypt.o + +ifeq ($(HAVE_ERR_H),0) + OBJS += err.o +endif + +.DEFAULT_TARGET = all + +.PHONY: all clean clobber dist install + +all: $(PACKAGE) + +$(PACKAGE): XCPPFLAGS := -DOPENSSL_LOAD_CONF +ifeq ($(HAVE_ERR_H),1) + $(PACKAGE): XCPPFLAGS += -DHAVE_ERR_H +endif +$(PACKAGE): XCFLAGS := $(shell getconf LFS_CFLAGS) +$(PACKAGE): LDLIBS := -lcrypto +$(PACKAGE): XLDFLAGS := $(shell getconf LFS_LDFLAGS) +$(PACKAGE): $(OBJS) + $(LINK.o) $^ $(LDLIBS) -o $@ + +%.o: %.c + $(MAKEDEPEND.c) $< | $(SED) -f deps.sed >$*.d + $(COMPILE.c) -o $@ $< + +install: + $(INSTALL.exec) $(PACKAGE) "$(DESTDIR)$(bindir)/$(PACKAGE)" + ln -f $(PACKAGE) "$(DESTDIR)$(bindir)/$(DECRYPT_ALIAS)" + +clean: + rm -f $(PACKAGE) $(OBJS) + +clobber: clean + rm -f $(patsubst %.o,%.d,$(OBJS)) + +dist: clobber + $(PAX) -w -x ustar -s ',.*/\..*,,' -s ',./[^/]*\.tar\.gz,,' \ + -s ',\./,$(DISTNAME)/,' . | $(GZIP) > $(DISTNAME).tar.gz + +-include $(patsubst %.o,%.d,$(OBJS)) diff -r 000000000000 -r 73af139d1a94 README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Tue Jan 28 19:23:16 2014 +0100 @@ -0,0 +1,48 @@ +sencrypt +======== + +Description +----------- + +sencrypt and sdecrypt are utilities for encrypting and decrypting data with the +AES, DES, 3DES, and RC4 algorithms. It can read keys from files or ask for a +passphrase and use that together with a salt to derive a key using the PBKDF2 +key derivation function. + +sencrypt and sdecrypt are portable and compatible reimplementations of the +encrypt and decrypt utilities in Solaris/Illumos-based operating systems. + +Build Instructions +------------------ + +sencrypt requires a POSIX:2004 compatible operating system, and needs GNU make, +GNU or BSD install, and the OpenSSL library to be installed. It has been tested +on Linux distributions, FreeBSD, Solaris and Illumos-derived distributions, +UnixWare, and OpenServer. + +License +------- + +Except otherwise noted, all files are Copyright (C) 2014 Guido Berhoerster and +distributed under the following license terms: + +Copyright (C) 2014 Guido Berhoerster + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -r 000000000000 -r 73af139d1a94 deps.sed --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/deps.sed Tue Jan 28 19:23:16 2014 +0100 @@ -0,0 +1,26 @@ +/^[^:]\{1,\}:.*\\$/{ + h + s/\([^:]\{1,\}:\).*/\1/ + x + s/[^:]\{1,\}:// +} +/\\$/,/^$/bgen +/\\$/,/[^\\]$/{ +:gen + s/[[:blank:]]*\\$// + s/^[[:blank:]]*// + G + s/\(.*\)\n\(.*\)/\2 \1/ +} +/^[^:]\{1,\}:[[:blank:]]*$/d +/^[^:]\{1,\}\.o:/{ + s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC][[:blank:]]*/ /g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC]$//g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc[[:blank:]]*/ /g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc$//g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp[[:blank:]]*/ /g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp$//g + /^[^:]\{1,\}:[[:blank:]]*$/d + s/^\([^:]\{1,\}\)\.o[[:blank:]]*:[[:blank:]]*\(.*\)/\1.d: $(wildcard \2)\ +&/ +} diff -r 000000000000 -r 73af139d1a94 err.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/err.c Tue Jan 28 19:23:16 2014 +0100 @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 Guido Berhoerster + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "err.h" + +static char __progname[] = "unknown"; + +void +err(int eval, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vwarn(fmt, args); + va_end(args); + + exit(eval); +} + +void +errx(int eval, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vwarnx(fmt, args); + va_end(args); + + exit(eval); +} + +void +warn(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vwarn(fmt, args); + va_end(args); +} + +void +warnx(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vwarnx(fmt, args); + va_end(args); +} + +void +verr(int eval, const char *fmt, va_list args) +{ + vwarn(fmt, args); + + exit(eval); +} + +void +verrx(int eval, const char *fmt, va_list args) +{ + vwarnx(fmt, args); + + exit(eval); +} + +void +vwarn(const char *fmt, va_list args) +{ + int old_errno = errno; + + fprintf(stderr, "%s: ", __progname); + if (fmt != NULL) { + vfprintf(stderr, fmt, args); + fprintf(stderr, ": "); + } + fprintf(stderr, "%s\n", strerror(old_errno)); + errno = old_errno; +} + +void +vwarnx(const char *fmt, va_list args) +{ + fprintf(stderr, "%s: ", __progname); + if (fmt != NULL) { + vfprintf(stderr, fmt, args); + } + fputc('\n', stderr); +} diff -r 000000000000 -r 73af139d1a94 err.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/err.h Tue Jan 28 19:23:16 2014 +0100 @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 Guido Berhoerster + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef ERR_H +#define ERR_H + +#include + +void err(int, const char *, ...); +void errx(int, const char *, ...); +void warn(const char *, ...); +void warnx(const char *, ...); +void verr(int, const char *, va_list); +void verrx(int, const char *, va_list); +void vwarn(const char *, va_list); +void vwarnx(const char *, va_list); + +#endif /* ERR_H */ diff -r 000000000000 -r 73af139d1a94 sencrypt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sencrypt.c Tue Jan 28 19:23:16 2014 +0100 @@ -0,0 +1,815 @@ +/* + * Copyright (C) 2011 Guido Berhoerster + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#define _XOPEN_SOURCE 600 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ERR_H +#include +#else +#include "err.h" +#endif /* HAVE_ERR_H */ + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#define EXIT_USAGE 2 + +#define SENCRYPT_FORMAT_VERSION 1 +#define PBKDF2_ITERATIONS 1000 +#define SALT_LEN 16 +#define BUFFER_SIZE (16 * 1024) +#define MAX_PASSWORD_LEN 256 + +enum { + CMD_SENCRYPT, + CMD_SDECRYPT +}; + +static void +openssl_warn(void) { + unsigned long errcode; + + while ((errcode = ERR_get_error()) != 0) { + warnx("%s", ERR_error_string(errcode, NULL)); + } +} + +static size_t +read_keyfile(const char *filename, unsigned char *key, size_t key_size_max) +{ + size_t keyfile_size = 0; + FILE *fp = NULL; + struct stat statbuf; + + fp = fopen(filename, "r"); + if (fp == NULL) { + warn("could not open key file \"%s\"", filename); + goto out; + } + + if (fstat(fileno(fp), &statbuf) == -1) { + warn("could not stat key file \"%s\"", filename); + goto out; + } + + if (!S_ISREG(statbuf.st_mode)) { + warnx("key file \"%s\" is not a regular file", filename); + goto out; + } + + if ((uintmax_t)statbuf.st_size > SIZE_MAX) { + warnx("key file \"%s\" is too large", filename); + goto out; + } + keyfile_size = (size_t)statbuf.st_size; + if ((keyfile_size > key_size_max) || + (keyfile_size == 0)) { + warnx("invalid key size"); + goto out; + } + + if (fread(key, 1, keyfile_size, fp) != keyfile_size) { + warnx("could not read key file \"%s\"", filename); + goto out; + } + +out: + if (fp != NULL) { + fclose(fp); + } + + return (keyfile_size); +} + +static int +find_algorithm(const char *algo_name, const EVP_CIPHER **cipher_ptr, + size_t *key_len_ptr) +{ + int retval = 0; + const EVP_CIPHER *cipher = NULL; + size_t key_len = *key_len_ptr; + + if (strcmp(algo_name, "aes") == 0) { + switch (key_len) { + case 0: + key_len = 16; + case 16: + cipher = EVP_aes_128_cbc(); + break; + case 24: + cipher = EVP_aes_192_cbc(); + break; + case 32: + cipher = EVP_aes_256_cbc(); + break; + default: + warnx("invalid key length %zu", key_len); + retval = -1; + } + } else if (strcmp(algo_name, "arcfour") == 0) { + if (key_len == 0) { + key_len = 16; + cipher = EVP_rc4(); + } else if (key_len <= EVP_MAX_KEY_LENGTH) { + /* + * for RC4 keys are not used verbatim but dervied using + * PBKDF2 with a hardcoded key length of 128 bit + */ + key_len = 16; + cipher = EVP_rc4(); + } else { + warnx("invalid key length %zu", key_len); + retval = -1; + } + } else if (strcmp(algo_name, "des") == 0) { + if (key_len == 0) { + key_len = 8; + cipher = EVP_des_cbc(); + } else if (key_len == 8) { + cipher = EVP_des_cbc(); + } else { + warnx("invalid key length %zu", key_len); + retval = -1; + } + } else if (strcmp(algo_name, "3des") == 0) { + if (key_len == 0) { + key_len = 24; + cipher = EVP_des_ede3_cbc(); + } else if (key_len == 24) { + cipher = EVP_des_ede3_cbc(); + } else { + warnx("invalid key length %zu", key_len); + retval = -1; + } + } else { + warnx("unknown algorithm \"%s\"", algo_name); + retval = -1; + } + + *cipher_ptr = cipher; + *key_len_ptr = key_len; + + return (retval); +} + +static int +read_header(BIO *bio_in, uint32_t *iterations, unsigned char *iv, int iv_len, + unsigned char *salt, int salt_len) +{ + int read_len; + uint32_t version; + int retval = 0; + + read_len = BIO_read(bio_in, &version, sizeof (version)); + if (read_len != sizeof (version)) { + warnx("failed to read version from input file"); + if (read_len < 0) { + openssl_warn(); + } + retval = -1; + goto out; + } + version = htonl(version); + if (version != SENCRYPT_FORMAT_VERSION) { + warnx("unknown format version %d", version); + retval = -1; + goto out; + } + + read_len = BIO_read(bio_in, iterations, sizeof (*iterations)); + if (read_len != sizeof (*iterations)) { + warnx("failed to read iterations from input file"); + if (read_len < 0) { + openssl_warn(); + } + retval = -1; + goto out; + } + *iterations = htonl(*iterations); + if ((*iterations == 0) || ((sizeof (int) <= sizeof (uint32_t)) && + (*iterations > INT_MAX))) { + warnx("invalid number of iterations"); + retval = -1; + goto out; + } + + if (iv_len > 0) { + read_len = BIO_read(bio_in, iv, iv_len); + if (read_len != iv_len) { + warnx("failed to read IV from input file"); + if (read_len < 0) { + openssl_warn(); + } + retval = -1; + goto out; + } + } + + read_len = BIO_read(bio_in, salt, salt_len); + if (read_len != salt_len) { + warnx("failed to read salt from input file"); + if (read_len < 0) { + openssl_warn(); + } + retval = -1; + goto out; + } + +out: + return (retval); +} + +static int +sencrypt(const EVP_CIPHER *cipher, BIO *bio_in, BIO *bio_out, + const unsigned char *key, size_t key_len, const unsigned char *iv, + const unsigned char *salt) +{ + int retval = 0; + uint32_t version; + uint32_t iterations; + int iv_len; + int write_len; + int read_len; + BIO *bio_cipher = NULL; + char *buf = NULL; + EVP_CIPHER_CTX *cipher_ctx; + + /* set up cipher filter */ + bio_cipher = BIO_new(BIO_f_cipher()); + BIO_set_cipher(bio_cipher, cipher, NULL, NULL, 1); + BIO_get_cipher_ctx(bio_cipher, &cipher_ctx); + if (EVP_CIPHER_CTX_set_key_length(cipher_ctx, (int)key_len) != 1) { + warnx("failed to set key length"); + openssl_warn(); + retval = 1; + goto out; + } + if (EVP_CipherInit_ex(cipher_ctx, NULL, NULL, key, iv, 1) != 1) { + warnx("failed to initialize cipher"); + openssl_warn(); + retval = 1; + goto out; + } + BIO_push(bio_cipher, bio_out); + + /* write header */ + version = htonl(SENCRYPT_FORMAT_VERSION); + write_len = BIO_write(bio_out, &version, sizeof (version)); + if (write_len != sizeof (version)) { + warnx("failed to write version to output file"); + if (write_len < 0) { + openssl_warn(); + } + retval = 1; + goto out; + } + + iterations = htonl(PBKDF2_ITERATIONS); + write_len = BIO_write(bio_out, &iterations, sizeof (iterations)); + if (write_len != sizeof (iterations)) { + warnx("failed to write iterations to output file"); + if (write_len < 0) { + openssl_warn(); + } + retval = 1; + goto out; + } + + iv_len = EVP_CIPHER_iv_length(cipher); + if (iv_len > 0) { + write_len = BIO_write(bio_out, iv, iv_len); + if (write_len != iv_len) { + warnx("failed to write IV to output file"); + if (write_len < 0) { + openssl_warn(); + } + retval = 1; + goto out; + } + } + + write_len = BIO_write(bio_out, salt, SALT_LEN); + if (write_len != SALT_LEN) { + warnx("failed to write salt to output file"); + if (write_len < 0) { + openssl_warn(); + } + retval = 1; + goto out; + } + + if (BIO_flush(bio_out) < 1) { + warnx("failed to flush output file"); + openssl_warn(); + retval = 1; + goto out; + } + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + warn(NULL); + retval = 1; + goto out; + } + + /* encrypt data */ + while ((read_len = BIO_read(bio_in, buf, BUFFER_SIZE)) > 0) { + if ((write_len = BIO_write(bio_cipher, buf, read_len)) != + read_len) { + warnx("failed to write to output file"); + if (write_len < 0) { + openssl_warn(); + } + retval = 1; + goto out; + } + } + if (read_len < 0) { + warnx("failed to read from input file"); + openssl_warn(); + retval = 1; + goto out; + } + + if (BIO_flush(bio_cipher) < 1) { + warnx("failed to flush output file"); + openssl_warn(); + retval = 1; + goto out; + } + +out: + free(buf); + + if (bio_cipher != NULL) { + BIO_pop(bio_cipher); + BIO_free(bio_cipher); + } + + return (retval); +} + +static int +sdecrypt(const EVP_CIPHER *cipher, BIO *bio_in, BIO *bio_out, + const unsigned char *key, size_t key_len, const unsigned char *iv) +{ + int read_len; + BIO *bio_cipher = NULL; + int write_len; + char *buf = NULL; + EVP_CIPHER_CTX *cipher_ctx; + int retval = 0; + + buf = malloc(BUFFER_SIZE); + if (buf == NULL) { + warn(NULL); + retval = 1; + goto out; + } + + /* set up cipher filter */ + bio_cipher = BIO_new(BIO_f_cipher()); + BIO_set_cipher(bio_cipher, cipher, NULL, NULL, 0); + BIO_get_cipher_ctx(bio_cipher, &cipher_ctx); + if (EVP_CIPHER_CTX_set_key_length(cipher_ctx, (int)key_len) != 1) { + warnx("failed to set key length"); + openssl_warn(); + retval = 1; + goto out; + } + if (EVP_CipherInit_ex(cipher_ctx, NULL, NULL, key, iv, 0) != 1) { + warnx("failed to initialize cipher"); + openssl_warn(); + retval = 1; + goto out; + } + BIO_push(bio_cipher, bio_in); + + /* decrypt data */ + while ((read_len = BIO_read(bio_cipher, buf, BUFFER_SIZE)) > 0) { + if ((write_len = BIO_write(bio_out, buf, read_len)) != + read_len) { + warnx("failed to write to to output file"); + if (write_len < 0) { + openssl_warn(); + } + retval = 1; + goto out; + } + } + if (read_len < 0) { + warnx("failed to read from input file"); + openssl_warn(); + retval = 1; + goto out; + } + + if (BIO_flush(bio_out) < 1) { + warnx("failed to flush output file"); + openssl_warn(); + retval = 1; + goto out; + } + + if (BIO_get_cipher_status(bio_cipher) == 0) { + warnx("decryption failed"); + openssl_warn(); + retval = 1; + goto out; + } + +out: + free(buf); + + if (bio_cipher != NULL) { + BIO_pop(bio_cipher); + BIO_free(bio_cipher); + } + + return (retval); +} + +static void +list_algorithms(void) +{ + printf("Algorithm Keysize: Min Max (bits)\n" + "------------------------------------------\n"); + printf("%-15s %5u %5u\n", "aes", 128, 256); + printf("%-15s %5u %5u\n", "arcfour", 8, + EVP_MAX_KEY_LENGTH * 8); + printf("%-15s %5u %5u\n", "des", 64, 64); + printf("%-15s %5u %5u\n", "3des", 192, 192); +} + +static void +usage(int cmd) +{ + if (cmd == CMD_SENCRYPT) { + fprintf(stderr, "usage: sencrypt -l | [-v] -a algorithm " + "[-k key_file] [-i input_file] [-o output_file]\n"); + } else if (cmd == CMD_SDECRYPT) { + fprintf(stderr, "usage: sdecrypt -l | [-v] -a algorithm " + "[-k key_file] [-i input_file] [-o output_file]\n"); + } +} + +int +main(int argc, char *argv[]) +{ + char *progname; + int cmd; + int c; + bool aflag = false; + char *algo_name = NULL; + bool is_algo_rc4 = false; + bool iflag = false; + char *in_filename = NULL; + bool kflag = false; + char *key_filename = NULL; + bool lflag = false; + bool oflag = false; + char *out_filename = NULL; + bool vflag = false; + bool errflag = false; + unsigned char key[EVP_MAX_KEY_LENGTH]; + size_t key_len = 0; + size_t key_file_len; + const EVP_CIPHER *cipher; + BIO *bio_in = NULL; + uint32_t iterations = PBKDF2_ITERATIONS; + unsigned char iv[EVP_MAX_IV_LENGTH]; + unsigned char salt[SALT_LEN]; + BIO *bio_out = NULL; + int need_tmpfile = 0; + FILE *fp_in; + struct stat statbuf_in; + struct stat statbuf_out; + int fd_tmp = -1; + FILE *fp_tmp = NULL; + char *out_filename_tmp = NULL; + char *out_dir = NULL; + char *tmp_filename = NULL; + int len; + mode_t old_mode; + char pwdata[MAX(MAX_PASSWORD_LEN, EVP_MAX_KEY_LENGTH)]; + size_t pwdata_len = 0; + int status = EXIT_SUCCESS; + + /* initialize OpenSSL */ + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + OPENSSL_config(NULL); + + progname = strrchr(argv[0], '/'); + progname = (progname != NULL) ? progname + 1 : argv[0]; + if ((strcmp(progname, "sencrypt") == 0) || + (strcmp(progname, "encrypt") == 0)) { + cmd = CMD_SENCRYPT; + } else if ((strcmp(progname, "sdecrypt") == 0) || + (strcmp(progname, "decrypt") == 0)) { + cmd = CMD_SDECRYPT; + } else { + fprintf(stderr, "invalid command name"); + status = EXIT_FAILURE; + goto out; + } + + while (!errflag && (c = getopt(argc, argv, "a:i:k:lo:v")) != -1) { + switch (c) { + case 'a': + aflag = true; + algo_name = optarg; + is_algo_rc4 = (strcmp(algo_name, "arcfour") == 0); + break; + case 'i': + iflag = true; + in_filename = optarg; + break; + case 'k': + kflag = true; + key_filename = optarg; + break; + case 'l': + lflag = true; + break; + case 'o': + oflag = true; + out_filename = optarg; + break; + case 'v': + vflag = true; + break; + default: + errflag = true; + } + } + if (errflag || (!lflag && !aflag) || (lflag && aflag) || + (argc > optind)) { + usage(cmd); + status = EXIT_USAGE; + goto out; + } + + if (lflag) { + list_algorithms(); + goto out; + } + + if (kflag) { + key_file_len = read_keyfile(key_filename, key, + (off_t)sizeof (key)); + if (key_file_len < 1) { + status = EXIT_FAILURE; + goto out; + } + key_len = key_file_len; + } else { + if (EVP_read_pw_string(pwdata, sizeof (pwdata), "Enter key:", + (cmd == CMD_SENCRYPT) ? 1 : 0) != 0) { + warnx("could not read passphrase"); + openssl_warn(); + status = EXIT_FAILURE; + goto out; + } + pwdata_len = strlen(pwdata); + if (pwdata_len < 1) { + warnx("invalid passphrase"); + status = EXIT_FAILURE; + goto out; + } + } + + /* the cipher is determined based on name and length of the key file */ + if (find_algorithm(algo_name, &cipher, &key_len) == -1) { + status = EXIT_FAILURE; + goto out; + } + + if (iflag) { + bio_in = BIO_new_file(in_filename, "r"); + } else { + bio_in = BIO_new_fp(stdin, BIO_NOCLOSE); + } + if (bio_in == NULL) { + warnx("could not open input file"); + openssl_warn(); + status = EXIT_FAILURE; + goto out; + } + + if (cmd == CMD_SENCRYPT) { + /* generate random salt and IV */ + if ((RAND_bytes(salt, sizeof (salt)) == 0) || + (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) == 0)) { + /* not enough entropy or unknown error */ + warnx("failed to generate random data"); + status = EXIT_FAILURE; + goto out; + } + } else { + read_header(bio_in, &iterations, iv, + EVP_CIPHER_iv_length(cipher), salt, (int)sizeof (salt)); + } + + /* + * if no keyfile was given or the RC4 cipher is used, derive the key + * from the password and salt + */ + if (kflag && is_algo_rc4) { + memcpy(pwdata, key, key_file_len); + pwdata_len = key_file_len; + } + if (!kflag || is_algo_rc4) { + if (PKCS5_PBKDF2_HMAC_SHA1(pwdata, (int)pwdata_len, salt, + sizeof (salt), (int)iterations, (int)key_len, key) == 0) { + warnx("failed to generate key"); + status = EXIT_FAILURE; + goto out; + } + } + + if (oflag) { + /* + * if input and output files are identical, create and write the + * output to a temporary file for the output which is then + * renamed to out_filename + */ + if (iflag) { + BIO_get_fp(bio_in, &fp_in); + if (fstat(fileno(fp_in), &statbuf_in) == -1) { + warn("could not stat input file"); + status = EXIT_FAILURE; + goto out; + } + if (stat(out_filename, &statbuf_out) == -1) { + if (errno != ENOENT) { + warn("could not stat output file"); + status = EXIT_FAILURE; + goto out; + } + } else if ((statbuf_in.st_ino == statbuf_out.st_ino) && + (statbuf_in.st_dev == statbuf_out.st_dev)) { + need_tmpfile = 1; + } + } + + if (need_tmpfile) { + out_filename_tmp = strdup(out_filename); + if (out_filename_tmp == NULL) { + warn(NULL); + status = EXIT_FAILURE; + goto out; + } + out_dir = dirname(out_filename_tmp); + len = snprintf(NULL, 0, "%s/sencryptXXXXXX", out_dir); + if (len < 0) { + warn(NULL); + status = EXIT_FAILURE; + goto out; + } + tmp_filename = malloc((size_t)len + 1); + if (tmp_filename == NULL) { + warn(NULL); + status = EXIT_FAILURE; + goto out; + } + if (snprintf(tmp_filename, (size_t)len + 1, + "%s/sencryptXXXXXX", out_dir) != len) { + warn(NULL); + status = EXIT_FAILURE; + goto out; + } + old_mode = umask(077); + fd_tmp = mkstemp(tmp_filename); + umask(old_mode); + if (fd_tmp == -1) { + warn("could not create temporary file"); + status = EXIT_FAILURE; + goto out; + } + fp_tmp = fdopen(fd_tmp, "w"); + if (fp_tmp == NULL) { + warn("could not open temporary file"); + status = EXIT_FAILURE; + goto out; + } + fd_tmp = -1; + bio_out = BIO_new_fp(fp_tmp, BIO_CLOSE); + if (bio_out == NULL) { + warnx("could not open temporary file"); + openssl_warn(); + status = EXIT_FAILURE; + goto out; + } + fp_tmp = NULL; + } else { + old_mode = umask(077); + bio_out = BIO_new_file(out_filename, "w"); + umask(old_mode); + if (bio_out == NULL) { + warnx("could not open output file"); + openssl_warn(); + status = EXIT_FAILURE; + goto out; + } + } + } else { + bio_out = BIO_new_fp(stdout, BIO_NOCLOSE); + if (bio_out == NULL) { + warnx("could not open output file"); + openssl_warn(); + status = EXIT_FAILURE; + goto out; + } + } + + if (cmd == CMD_SENCRYPT) { + if (sencrypt(cipher, bio_in, bio_out, key, key_len, + iv, salt) == -1) { + status = EXIT_FAILURE; + } + } else { + if (sdecrypt(cipher, bio_in, bio_out, key, key_len, + iv) == -1) { + status = EXIT_FAILURE; + } + } + +out: + OPENSSL_cleanse(pwdata, pwdata_len); + OPENSSL_cleanse(key, key_len); + + if (fd_tmp != -1) { + close(fd_tmp); + } + + if (fp_tmp != NULL) { + fclose(fp_tmp); + } + + if (bio_in != NULL) { + BIO_free_all(bio_in); + } + + if (bio_out != NULL) { + BIO_free_all(bio_out); + + if (status == 0) { + if (need_tmpfile) { + if (rename(tmp_filename, out_filename) == -1) { + warn("could not create output file"); + status = EXIT_FAILURE; + unlink(tmp_filename); + } + } + } else { + if (need_tmpfile) { + unlink(tmp_filename); + } else if (oflag) { + unlink(out_filename); + } + } + } + + free(out_filename_tmp); + free(tmp_filename); + + EVP_cleanup(); + ERR_free_strings(); + CONF_modules_free(); + + exit(status); +}