Use libsodium crypto box and dump OpenSSL RSA/AES

Signed-off-by: David Goulet <dgoulet@ev0ke.net>
This commit is contained in:
David Goulet 2016-11-09 17:12:05 -05:00
parent 6b6ba91254
commit 9b01cb760d
8 changed files with 298 additions and 592 deletions

View File

@ -23,7 +23,7 @@ CFLAGS=-std=gnu99 \
-Wbad-function-cast -fno-builtin-strftime -Wstrict-aliasing=2 -Wl,-z,relro,-z,now \
-fPIC -fstack-check -ftrapv -DPIC -D_FORTIFY_SOURCE=2 -DHAVE_CONFIG_H \
-I$(DOVECOT_INCLUDE_DIR)
LDFLAGS=-gs -shared -lxcrypt -lcrypto -rdynamic -Wl,-soname,lib18_scrambler_plugin.so.1
LDFLAGS=-gs -shared -lsodium -rdynamic -Wl,-soname,lib18_scrambler_plugin.so.1
ifeq ($(DEBUG), 1)
CFLAGS+=-DDEBUG_STREAMS -g

View File

@ -19,29 +19,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <dovecot/lib.h>
#include <dovecot/base64.h>
#include <dovecot/buffer.h>
#include <dovecot/str.h>
#include <unistd.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/pem.h>
#include <xcrypt.h>
#include <errno.h>
#include <string.h>
#include <sodium.h>
#include "scrambler-common.h"
const char scrambler_header[] = { 0xee, 0xff, 0xcc };
void
int
scrambler_initialize(void)
{
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
i_info("scrambler plugin initialized");
if (sodium_init() < 0) {
i_info("scrambler plugin libsodium failed to initialized.");
return -1;
}
i_info("scrambler plugin initialized");
return 0;
}
const char *
@ -73,55 +74,6 @@ scrambler_read_line_fd(pool_t pool, int fd)
return result;
}
const EVP_CIPHER *
scrambler_cipher(enum packages package)
{
switch (package) {
case PACKAGE_RSA_2048_AES_128_CTR_HMAC:
return EVP_aes_128_ctr();
}
return NULL;
}
void
scrambler_generate_mac(unsigned char *tag, unsigned int *tag_size,
const unsigned char *sources[],
size_t source_sizes[], const unsigned char *key,
size_t key_size)
{
HMAC_CTX context;
HMAC_CTX_init(&context);
HMAC_Init_ex(&context, key, key_size, EVP_sha256(), NULL);
unsigned int index = 0;
const unsigned char *source = sources[index];
size_t source_size = source_sizes[index];
while (source != NULL) {
HMAC_Update(&context, source, source_size);
index++;
source = sources[index];
source_size = source_sizes[index];
}
HMAC_Final(&context, tag, tag_size);
HMAC_CTX_cleanup(&context);
}
void
i_error_openssl(const char *function_name)
{
char *output;
BIO *output_bio = BIO_new(BIO_s_mem());
ERR_print_errors(output_bio);
BIO_get_mem_data(output_bio, &output);
i_error("%s: %s", function_name, output);
BIO_free_all(output_bio);
}
void
i_debug_hex(const char *prefix, const unsigned char *data, size_t size)
{

View File

@ -22,30 +22,13 @@
#ifndef SCRAMBLER_COMMON_H
#define SCRAMBLER_COMMON_H
#include <openssl/evp.h>
#include <sodium.h>
// Defines
#define MAGIC_SIZE (sizeof(scrambler_header) + 1)
#define ENCRYPTED_HEADER_SIZE (304)
#define CHUNK_SIZE (8192)
#define CHUNK_TAG_SIZE (32)
#define ENCRYPTED_CHUNK_SIZE ((int)sizeof(unsigned short) + CHUNK_SIZE + CHUNK_TAG_SIZE)
#define MAC_KEY_SIZE (32)
#define MAXIMAL_PASSWORD_LENGTH (256)
#define ASSERT_SUCCESS(command, expected_result, function_name, error_text, error_result) \
if ((expected_result) != (command)) { \
i_error("%s: %s", function_name, error_text); \
return (error_result); \
}
#define ASSERT_OPENSSL_SUCCESS(command, expected_result, function_name, error_text, error_result) \
if ((expected_result) != (command)) { \
i_error("%s: %s", function_name, error_text); \
i_error_openssl(function_name); \
return (error_result); \
}
#define MAGIC_SIZE (sizeof(scrambler_header))
#define CHUNK_SIZE 8192
#define ENCRYPTED_CHUNK_SIZE (crypto_box_SEALBYTES + CHUNK_SIZE)
#define MAXIMAL_PASSWORD_LENGTH 256
#define MAX_ISTREAM_BUFFER_SIZE (ENCRYPTED_CHUNK_SIZE * 2)
#define MIN(a,b) \
({ __typeof__ (a) _a = (a); \
@ -57,30 +40,12 @@
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
// Enums
extern const char scrambler_header[3];
enum packages {
PACKAGE_RSA_2048_AES_128_CTR_HMAC = 0x00
};
// Constants
const char scrambler_header[3];
// Functions
void scrambler_initialize(void);
int scrambler_initialize(void);
const char *scrambler_read_line_fd(pool_t pool, int file_descriptor);
const EVP_CIPHER *scrambler_cipher(enum packages package);
void scrambler_generate_mac(unsigned char *tag, unsigned int *tag_size,
const unsigned char *sources[],
size_t source_sizes[],
const unsigned char *key, size_t key_size);
void i_error_openssl(const char *function_name);
void i_debug_hex(const char *prefix, const unsigned char *data, size_t size);
#endif /* SCRAMBLER_COMMON_H */

View File

@ -22,35 +22,23 @@
#include <dovecot/lib.h>
#include <dovecot/istream.h>
#include <dovecot/istream-private.h>
#include <openssl/aes.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include "scrambler-common.h"
#include "scrambler-istream.h"
// Enums
enum scrambler_istream_mode {
detect,
decrypt,
plain
ISTREAM_MODE_DETECT = 1,
ISTREAM_MODE_DECRYPT = 2,
ISTREAM_MODE_PLAIN = 3,
};
// Structs
struct scrambler_istream {
struct istream_private istream;
enum scrambler_istream_mode mode;
EVP_PKEY *private_key;
EVP_CIPHER_CTX *cipher_context;
const EVP_CIPHER *cipher;
unsigned int encrypted_header_size;
unsigned char mac_key[MAC_KEY_SIZE];
const unsigned char *public_key;
unsigned char *private_key;
unsigned int chunk_index;
bool last_chunk_read;
@ -75,8 +63,9 @@ scrambler_istream_read_parent(struct scrambler_istream *sstream,
result = i_stream_read(stream->parent);
size = i_stream_get_data_size(stream->parent);
if (result > 0 && stream->parent->eof)
if (result > 0 && stream->parent->eof) {
break;
}
if (result <= 0 && (result != -2 || stream->skip == 0)) {
stream->istream.stream_errno = stream->parent->stream_errno;
@ -93,42 +82,26 @@ static ssize_t
scrambler_istream_read_detect_magic(struct scrambler_istream *sstream,
const unsigned char *source)
{
// read header and package information
if (0 == memcmp(scrambler_header, source, sizeof(scrambler_header))) {
/* Check for the scrambler header and if so we have an encrypted email that
* we'll try to decrypt. */
if (memcmp(scrambler_header, source, sizeof(scrambler_header))) {
#ifdef DEBUG_STREAMS
i_debug("istream read encrypted mail");
#endif
sstream->mode = decrypt;
sstream->mode = ISTREAM_MODE_DECRYPT;
if (sstream->private_key == NULL) {
i_error("tried to decrypt a mail without the private key");
sstream->istream.istream.stream_errno = EACCES;
sstream->istream.istream.eof = TRUE;
return -1;
} else {
source += sizeof(scrambler_header);
enum packages package = *source;
sstream->cipher = scrambler_cipher(package);
if (sstream->cipher == NULL) {
i_error("could not detect encryption package signature (%02x)", package);
sstream->istream.istream.stream_errno = EACCES;
sstream->istream.istream.eof = TRUE;
return -1;
}
sstream->encrypted_header_size =
EVP_CIPHER_iv_length(sstream->cipher) +
EVP_PKEY_size(sstream->private_key) +
MAC_KEY_SIZE;
return MAGIC_SIZE;
}
} else {
#ifdef DEBUG_STREAMS
i_debug("istream read plain mail");
#endif
sstream->mode = plain;
return 0;
sstream->mode = ISTREAM_MODE_PLAIN;
}
return 0;
}
static ssize_t
@ -139,147 +112,34 @@ scrambler_istream_read_detect(struct scrambler_istream *sstream)
ssize_t result;
size_t source_size;
i_stream_set_max_buffer_size(sstream->istream.parent, ENCRYPTED_HEADER_SIZE + (2 * ENCRYPTED_CHUNK_SIZE));
i_stream_set_max_buffer_size(sstream->istream.parent,
MAX_ISTREAM_BUFFER_SIZE);
result = scrambler_istream_read_parent(sstream, MAGIC_SIZE, 0);
if (result <= 0)
return result;
if (result <= 0) {
goto end;
}
source = i_stream_get_data(stream->parent, &source_size);
result = scrambler_istream_read_detect_magic(sstream, source);
if (result < 0)
return result;
if (result < 0) {
goto end;
}
#ifdef DEBUG_STREAMS
sstream->in_byte_count += result;
#endif
i_stream_skip(stream->parent, result);
end:
return result;
}
static ssize_t
scrambler_istream_read_decrypt_header(struct scrambler_istream *sstream,
const unsigned char **source)
{
sstream->cipher_context = EVP_CIPHER_CTX_new();
size_t iv_size = EVP_CIPHER_iv_length(sstream->cipher);
unsigned char iv[iv_size];
memcpy(iv, *source, iv_size);
*source += iv_size;
#ifdef DEBUG_STREAMS
sstream->in_byte_count += iv_size;
#endif
size_t encrypted_key_size = EVP_PKEY_size(sstream->private_key);
ASSERT_OPENSSL_SUCCESS(
EVP_OpenInit(sstream->cipher_context, sstream->cipher, *source, encrypted_key_size, iv, sstream->private_key), 1,
"scrambler_istream_read_decrypt_header", "initialization of public key decryption failed", -1);
*source += encrypted_key_size;
#ifdef DEBUG_STREAMS
sstream->in_byte_count += encrypted_key_size;
#endif
// decrypt mac key
int decrypted_mac_key_size;
ASSERT_OPENSSL_SUCCESS(
EVP_OpenUpdate(sstream->cipher_context, sstream->mac_key, &decrypted_mac_key_size, *source, MAC_KEY_SIZE), 1,
"scrambler_istream_read_decrypt_header", "mac key decryption failed", -1)
i_assert(decrypted_mac_key_size == MAC_KEY_SIZE);
*source += MAC_KEY_SIZE;
#ifdef DEBUG_STREAMS
sstream->in_byte_count += MAC_KEY_SIZE;
#endif
return 0;
}
static ssize_t
scrambler_istream_read_decrypt_chunk(struct scrambler_istream *sstream,
unsigned char **destination,
const unsigned char **source,
const unsigned char *source_end)
unsigned char *destination,
const unsigned char *source)
{
int decrypted_size = 0;
int total_decrypted_size = 0;
const unsigned char *header = *source;
unsigned short encrypted_size = *(unsigned short *)header;
bool final = (encrypted_size & 0x8000) != 0;
encrypted_size &= 0x7fff; // clear msb
*source += sizeof(unsigned short);
#ifdef DEBUG_STREAMS
sstream->in_byte_count += sizeof(unsigned short);
#endif
if (*source + encrypted_size + CHUNK_TAG_SIZE > source_end) {
i_error("failed to verify chunk size");
sstream->istream.istream.stream_errno = EIO;
sstream->istream.istream.eof = TRUE;
return -1;
}
const unsigned char *encrypted = *source;
*source += encrypted_size;
#ifdef DEBUG_STREAMS
sstream->in_byte_count += encrypted_size;
#endif
const unsigned char *tag = *source;
*source += CHUNK_TAG_SIZE;
#ifdef DEBUG_STREAMS
sstream->in_byte_count += CHUNK_TAG_SIZE;
#endif
// verify the mac
unsigned int generated_tag_size;
unsigned char generated_tag[CHUNK_TAG_SIZE];
const unsigned char *blocks[] = {
(unsigned char *)&sstream->chunk_index,
(unsigned char *)header,
encrypted,
NULL
};
size_t block_sizes[] = {
sizeof(unsigned int),
sizeof(unsigned short),
encrypted_size,
0
};
scrambler_generate_mac(generated_tag, &generated_tag_size, blocks, block_sizes, sstream->mac_key, MAC_KEY_SIZE);
i_assert(generated_tag_size == CHUNK_TAG_SIZE);
if (CRYPTO_memcmp(tag, generated_tag, generated_tag_size)) {
i_error("failed to verify chunk tag");
sstream->istream.istream.stream_errno = EACCES;
sstream->istream.istream.eof = TRUE;
return -1;
}
// decrypt
ASSERT_OPENSSL_SUCCESS(
EVP_OpenUpdate(sstream->cipher_context, *destination, &decrypted_size, encrypted, encrypted_size), 1,
"scrambler_istream_read_decrypt_chunk", "stream decryption failed", -1)
i_assert(decrypted_size == (int)encrypted_size);
total_decrypted_size += decrypted_size;
*destination += decrypted_size;
if (final) {
ASSERT_OPENSSL_SUCCESS(
EVP_OpenFinal(sstream->cipher_context, *destination, &decrypted_size), 1,
"scrambler_istream_read_decrypt_chunk", "stream finalization failed", -1)
total_decrypted_size += decrypted_size;
*destination += decrypted_size;
}
sstream->chunk_index++;
#ifdef DEBUG_STREAMS
i_debug_hex("chunk", *destination - total_decrypted_size, total_decrypted_size);
#endif
return 0;
return crypto_box_seal_open(destination, source, ENCRYPTED_CHUNK_SIZE,
sstream->public_key, sstream->private_key);
}
static ssize_t
@ -289,14 +149,13 @@ scrambler_istream_read_decrypt(struct scrambler_istream *sstream)
const unsigned char *parent_data, *source, *source_end;
unsigned char *destination, *destination_end;
ssize_t result;
size_t source_size, minimal_size;
size_t source_size;
minimal_size = sstream->cipher_context == NULL ? sstream->encrypted_header_size : 0;
minimal_size += ENCRYPTED_CHUNK_SIZE;
result = scrambler_istream_read_parent(sstream, minimal_size, CHUNK_SIZE + stream->pos);
if (result <= 0 && result != -1)
result = scrambler_istream_read_parent(sstream, ENCRYPTED_CHUNK_SIZE,
CHUNK_SIZE + stream->pos);
if (result <= 0 && result != -1) {
return result;
}
parent_data = i_stream_get_data(stream->parent, &source_size);
source = parent_data;
@ -304,15 +163,6 @@ scrambler_istream_read_decrypt(struct scrambler_istream *sstream)
destination = stream->w_buffer + stream->pos;
destination_end = stream->w_buffer + stream->buffer_size;
// handle header and chiper initialization
if (sstream->cipher_context == NULL) {
result = scrambler_istream_read_decrypt_header(sstream, &source);
if (result < 0) {
stream->istream.stream_errno = EIO;
return result;
}
}
while ( (source_end - source) >= ENCRYPTED_CHUNK_SIZE ) {
if (destination_end - destination < CHUNK_SIZE) {
i_error("output buffer too small");
@ -321,9 +171,11 @@ scrambler_istream_read_decrypt(struct scrambler_istream *sstream)
return -1;
}
result = scrambler_istream_read_decrypt_chunk(sstream, &destination, &source, source_end);
if (result < 0)
result = scrambler_istream_read_decrypt_chunk(sstream, destination,
source);
if (result < 0) {
return result;
}
}
if (stream->parent->eof) {
@ -342,7 +194,7 @@ scrambler_istream_read_decrypt(struct scrambler_istream *sstream)
return -1;
}
result = scrambler_istream_read_decrypt_chunk(sstream, &destination, &source, source_end);
result = scrambler_istream_read_decrypt_chunk(sstream, destination, source);
if (result < 0) {
stream->istream.stream_errno = EIO;
return result;
@ -374,14 +226,15 @@ scrambler_istream_read_decrypt(struct scrambler_istream *sstream)
static ssize_t
scrambler_istream_read_plain(struct scrambler_istream *sstream)
{
struct istream_private *stream = &sstream->istream;
const unsigned char *source;
ssize_t result;
size_t source_size, copy_size;
ssize_t result;
const unsigned char *source;
struct istream_private *stream = &sstream->istream;
result = scrambler_istream_read_parent(sstream, 1, 0);
if (result <= 0)
if (result <= 0) {
return result;
}
source = i_stream_get_data(stream->parent, &source_size);
copy_size = MIN(source_size, stream->buffer_size - stream->pos);
@ -401,44 +254,38 @@ scrambler_istream_read_plain(struct scrambler_istream *sstream)
static ssize_t
scrambler_istream_read(struct istream_private *stream)
{
struct scrambler_istream *sstream = (struct scrambler_istream *)stream;
struct scrambler_istream *sstream = (struct scrambler_istream *) stream;
if (sstream->mode == detect) {
ssize_t result = scrambler_istream_read_detect(sstream);
if (result < 0)
return result;
}
if (sstream->mode == decrypt)
switch (sstream->mode) {
case ISTREAM_MODE_DETECT:
return scrambler_istream_read_detect(sstream);
case ISTREAM_MODE_DECRYPT:
return scrambler_istream_read_decrypt(sstream);
if (sstream->mode == plain)
case ISTREAM_MODE_PLAIN:
return scrambler_istream_read_plain(sstream);
return -1;
default:
/* Should not happened in theory! */
return -1;
}
}
static void
scrambler_istream_seek(struct istream_private *stream, uoff_t v_offset,
bool mark)
{
struct scrambler_istream *sstream = (struct scrambler_istream *)stream;
struct scrambler_istream *sstream = (struct scrambler_istream *) stream;
#ifdef DEBUG_STREAMS
i_debug("scrambler istream seek %d / %d / %d", (int)stream->istream.v_offset, (int)v_offset, (int)mark);
i_debug("scrambler istream seek %d / %d / %d",
(int)stream->istream.v_offset, (int)v_offset, (int)mark);
#endif
if (v_offset < stream->istream.v_offset) {
// seeking backwards - go back to beginning and seek forward from there.
if (sstream->cipher_context != NULL) {
EVP_CIPHER_CTX_free(sstream->cipher_context);
sstream->cipher_context = NULL;
}
sstream->mode = detect;
sstream->mode = ISTREAM_MODE_DETECT;
sstream->chunk_index = 0;
sstream->last_chunk_read = FALSE;
sstream->last_chunk_read = 0;
#ifdef DEBUG_STREAMS
sstream->in_byte_count = 0;
sstream->out_byte_count = 0;
@ -457,10 +304,9 @@ static int
scrambler_istream_stat(struct istream_private *stream, bool exact)
{
const struct stat *stat;
if (i_stream_stat(stream->parent, exact, &stat) < 0)
if (i_stream_stat(stream->parent, exact, &stat) < 0) {
return -1;
}
stream->statbuf = *stat;
return 0;
}
@ -470,22 +316,26 @@ scrambler_istream_close(struct iostream_private *stream, bool close_parent)
{
struct scrambler_istream *sstream = (struct scrambler_istream *)stream;
if (sstream->cipher_context != NULL) {
EVP_CIPHER_CTX_free(sstream->cipher_context);
sstream->cipher_context = NULL;
}
/* Attempt at wiping the private key. XXX: This isn't guaranteed so we
* should try to make the compiler not optimize it out. */
memset(sstream->private_key, 'a', crypto_box_SECRETKEYBYTES);
#ifdef DEBUG_STREAMS
i_debug("scrambler istream close - %u bytes in / %u bytes out / %u bytes overhead",
sstream->in_byte_count, sstream->out_byte_count, sstream->in_byte_count - sstream->out_byte_count);
i_debug("scrambler istream close - %u bytes in / %u bytes out / "
"%u bytes overhead", sstream->in_byte_count,
sstream->out_byte_count,
sstream->in_byte_count - sstream->out_byte_count);
#endif
if (close_parent)
if (close_parent) {
i_stream_close(sstream->istream.parent);
}
}
struct istream *
scrambler_istream_create(struct istream *input, EVP_PKEY *private_key)
scrambler_istream_create(struct istream *input,
const unsigned char *public_key,
unsigned char *private_key)
{
struct scrambler_istream *sstream = i_new(struct scrambler_istream, 1);
@ -493,13 +343,13 @@ scrambler_istream_create(struct istream *input, EVP_PKEY *private_key)
i_debug("scrambler istream create");
#endif
sstream->mode = detect;
sstream->mode = ISTREAM_MODE_DETECT;
sstream->public_key = public_key;
sstream->private_key = private_key;
sstream->cipher_context = NULL;
sstream->chunk_index = 0;
sstream->last_chunk_read = FALSE;
sstream->last_chunk_read = 0;
#ifdef DEBUG_STREAMS
sstream->in_byte_count = 0;
sstream->out_byte_count = 0;

View File

@ -26,6 +26,7 @@
#include <openssl/evp.h>
struct istream *scrambler_istream_create(struct istream *input,
EVP_PKEY *private_key);
const unsigned char *public_key,
unsigned char *private_key);
#endif /* SCRAMBLER_ISTREAM_H */

View File

@ -19,16 +19,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <dovecot/lib.h>
#include <dovecot/ostream.h>
#include <dovecot/ostream-private.h>
#include <openssl/aes.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <sodium.h>
#include "scrambler-common.h"
#include "scrambler-ostream.h"
@ -37,19 +34,13 @@
struct scrambler_ostream {
struct ostream_private ostream;
enum packages package;
EVP_PKEY *public_key;
EVP_CIPHER_CTX *cipher_context;
const EVP_CIPHER *cipher;
unsigned char mac_key[MAC_KEY_SIZE];
const unsigned char *public_key;
unsigned int chunk_index;
unsigned char chunk_buffer[CHUNK_SIZE];
unsigned int chunk_buffer_size;
bool flushed;
unsigned int flushed : 1;
#ifdef DEBUG_STREAMS
unsigned int in_byte_count;
@ -60,123 +51,41 @@ struct scrambler_ostream {
static ssize_t
scrambler_ostream_send_header(struct scrambler_ostream *sstream)
{
// send header and package information
o_stream_send(sstream->ostream.parent, scrambler_header, sizeof(scrambler_header));
o_stream_send(sstream->ostream.parent, (unsigned char *)&sstream->package, 1);
/* The header here consists of a magic number. */
ssize_t ret = o_stream_send(sstream->ostream.parent, scrambler_header,
sizeof(scrambler_header));
#ifdef DEBUG_STREAMS
sstream->out_byte_count += sizeof(scrambler_header) + 1;
sstream->out_byte_count += sizeof(scrambler_header);
#endif
sstream->cipher = scrambler_cipher(sstream->package);
sstream->cipher_context = EVP_CIPHER_CTX_new();
EVP_PKEY *public_keys[] = { sstream->public_key };
int iv_size = EVP_CIPHER_iv_length(sstream->cipher);
unsigned char iv[iv_size];
int encrypted_key_size = EVP_PKEY_size(sstream->public_key);
unsigned char encrypted_key[encrypted_key_size];
unsigned char *encrypted_keys[] = { encrypted_key };
ASSERT_OPENSSL_SUCCESS(
EVP_SealInit(sstream->cipher_context, sstream->cipher,
encrypted_keys, &encrypted_key_size, iv, public_keys, 1), 1,
"scrambler_ostream_send_header", "initialization of public key encryption failed", -1);
i_assert(encrypted_key_size == EVP_PKEY_size(sstream->public_key));
o_stream_send(sstream->ostream.parent, iv, iv_size);
o_stream_send(sstream->ostream.parent, encrypted_key, encrypted_key_size);
#ifdef DEBUG_STREAMS
sstream->out_byte_count += iv_size + encrypted_key_size;
#endif
// generate a mac key
if (1 != RAND_bytes(sstream->mac_key, MAC_KEY_SIZE))
return -1;
// encrypt the mac key
unsigned char encrypted_mac_key[MAC_KEY_SIZE];
int encrypted_mac_key_size;
ASSERT_OPENSSL_SUCCESS(
EVP_SealUpdate(sstream->cipher_context,
encrypted_mac_key, &encrypted_mac_key_size, sstream->mac_key, MAC_KEY_SIZE), 1,
"scrambler_ostream_send_header", "mac key encryption failed", -1)
i_assert(encrypted_mac_key_size == MAC_KEY_SIZE);
o_stream_send(sstream->ostream.parent, encrypted_mac_key, encrypted_mac_key_size);
#ifdef DEBUG_STREAMS
sstream->out_byte_count += encrypted_mac_key_size;
#endif
// i_debug("write header / size = %d / parent_size = %d",
// 0, (int)(iv_size + encrypted_key_size + encrypted_mac_key_size));
// i_debug_hex("mac key", sstream->mac_key, MAC_KEY_SIZE);
return 0;
return ret;
}
static ssize_t
scrambler_ostream_send_chunk(struct scrambler_ostream *sstream,
const unsigned char *chunk, size_t chunk_size,
bool final)
const unsigned char *chunk, size_t chunk_size)
{
int encrypted_size = 0;
unsigned short total_encrypted_size = 0;
unsigned char encrypted[chunk_size + EVP_CIPHER_block_size(sstream->cipher) - 1];
int ret;
/* Extra protection here against overflow. Maybe too agressive! */
assert(chunk_size < (SIZE_MAX - crypto_box_SEALBYTES));
size_t ciphertext_len = crypto_box_SEALBYTES + chunk_size;
unsigned char ciphertext[ciphertext_len];
#ifdef DEBUG_STREAMS
// i_debug("chunk %s", chunk);
i_debug_hex("chunk", chunk, chunk_size);
#endif
ASSERT_OPENSSL_SUCCESS(
EVP_SealUpdate(sstream->cipher_context, encrypted, &encrypted_size, chunk, chunk_size), 1,
"scrambler_ostream_send_chunk", "stream encryption failed", -1)
i_assert((size_t)encrypted_size == chunk_size);
total_encrypted_size += encrypted_size;
if (final) {
encrypted_size = 0;
ASSERT_OPENSSL_SUCCESS(
EVP_SealFinal(sstream->cipher_context, encrypted + total_encrypted_size, &encrypted_size), 1,
"scrambler_ostream_send_chunk", "stream finalization failed", -1)
total_encrypted_size += encrypted_size;
ret = crypto_box_seal(ciphertext, chunk, ciphertext_len,
sstream->public_key);
if (ret < 0) {
return ret;
}
o_stream_send(sstream->ostream.parent, ciphertext, ciphertext_len);
unsigned short header = total_encrypted_size;
if (final)
header |= 0x8000; // set msb
else
header &= 0x7fff; // clear msb
unsigned int tag_size;
unsigned char tag[CHUNK_TAG_SIZE];
const unsigned char *blocks[] = {
(unsigned char *)&sstream->chunk_index,
(unsigned char *)&header,
encrypted,
NULL
};
size_t block_sizes[] = {
sizeof(unsigned int),
sizeof(unsigned short),
total_encrypted_size,
0
};
scrambler_generate_mac(tag, &tag_size, blocks, block_sizes, sstream->mac_key, MAC_KEY_SIZE);
i_assert(tag_size == CHUNK_TAG_SIZE);
o_stream_send(sstream->ostream.parent, &header, sizeof(unsigned short));
o_stream_send(sstream->ostream.parent, encrypted, total_encrypted_size);
o_stream_send(sstream->ostream.parent, tag, tag_size);
#ifdef DEBUG_STREAMS
sstream->out_byte_count += sizeof(unsigned short) + total_encrypted_size + tag_size;
sstream->out_byte_count += ciphertext_len;
#endif
sstream->chunk_index++;
return chunk_size;
}
@ -184,15 +93,12 @@ static ssize_t
scrambler_ostream_sendv(struct ostream_private *stream,
const struct const_iovec *iov, unsigned int iov_count)
{
struct scrambler_ostream *sstream = (struct scrambler_ostream *)stream;
ssize_t result = 0;
ssize_t encrypt_result = 0;
// encrypt and send data
unsigned int index;
const unsigned char *source, *source_end;
size_t chunk_size;
for (index = 0; index < iov_count; index++) {
ssize_t result = 0, encrypt_result = 0;
const unsigned char *source, *source_end;
struct scrambler_ostream *sstream = (struct scrambler_ostream *) stream;
for (unsigned int index = 0; index < iov_count; index++) {
source = iov[index].iov_base;
source_end = (unsigned char *)iov[index].iov_base + iov[index].iov_len;
@ -201,19 +107,25 @@ scrambler_ostream_sendv(struct ostream_private *stream,
if (sstream->chunk_buffer_size > 0 || chunk_size < CHUNK_SIZE) {
chunk_size = MIN(chunk_size, CHUNK_SIZE - sstream->chunk_buffer_size);
memcpy(sstream->chunk_buffer + sstream->chunk_buffer_size, source, chunk_size);
memcpy(sstream->chunk_buffer + sstream->chunk_buffer_size, source,
chunk_size);
sstream->chunk_buffer_size += chunk_size;
if (sstream->chunk_buffer_size == CHUNK_SIZE) {
encrypt_result = scrambler_ostream_send_chunk(sstream, sstream->chunk_buffer, CHUNK_SIZE, FALSE);
if (encrypt_result < 0)
encrypt_result = scrambler_ostream_send_chunk(sstream,
sstream->chunk_buffer,
CHUNK_SIZE);
if (encrypt_result < 0) {
return encrypt_result;
}
sstream->chunk_buffer_size = 0;
}
} else {
encrypt_result = scrambler_ostream_send_chunk(sstream, source, CHUNK_SIZE, FALSE);
if (encrypt_result < 0)
encrypt_result = scrambler_ostream_send_chunk(sstream, source,
CHUNK_SIZE);
if (encrypt_result < 0) {
return encrypt_result;
}
chunk_size = encrypt_result;
}
@ -226,7 +138,7 @@ scrambler_ostream_sendv(struct ostream_private *stream,
#ifdef DEBUG_STREAMS
sstream->in_byte_count += result;
i_debug("scrambler ostream send (%d)", (int)result);
i_debug("scrambler ostream send (%ld)", result);
#endif
return result;
@ -235,69 +147,58 @@ scrambler_ostream_sendv(struct ostream_private *stream,
static int
scrambler_ostream_flush(struct ostream_private *stream)
{
struct scrambler_ostream *sstream = (struct scrambler_ostream *)stream;
ssize_t result;
ssize_t result = 0;
struct scrambler_ostream *sstream = (struct scrambler_ostream *) stream;
if (sstream->flushed)
return 0;
if (sstream->cipher_context != NULL) {
ssize_t result = scrambler_ostream_send_chunk(sstream, sstream->chunk_buffer, sstream->chunk_buffer_size, TRUE);
if (result < 0) {
i_error("error sending last chunk on close");
return result;
}
sstream->chunk_buffer_size = 0;
sstream->ostream.ostream.offset += result;
EVP_CIPHER_CTX_free(sstream->cipher_context);
sstream->cipher_context = NULL;
if (sstream->flushed) {
goto end;
}
result = scrambler_ostream_send_chunk(sstream, sstream->chunk_buffer,
sstream->chunk_buffer_size);
if (result < 0) {
i_error("error sending last chunk on close");
goto end;
}
sstream->chunk_buffer_size = 0;
sstream->ostream.ostream.offset += result;
result = o_stream_flush(stream->parent);
if (result < 0)
if (result < 0) {
o_stream_copy_error_from_parent(stream);
else
sstream->flushed = TRUE;
goto end;
}
sstream->flushed = 1;
#ifdef DEBUG_STREAMS
i_debug("scrambler ostream flush (%d)", (int)result);
#endif
end:
return result;
}
static void
scrambler_ostream_close(struct iostream_private *stream, bool close_parent)
scrambler_ostream_close(struct iostream_private *stream,
bool close_parent)
{
struct scrambler_ostream *sstream = (struct scrambler_ostream *)stream;
/*
if (sstream->cipher_context != NULL) {
ssize_t result = scrambler_ostream_send_chunk(sstream, sstream->chunk_buffer, sstream->chunk_buffer_size, TRUE);
if (result < 0) {
i_error("error sending last chunk on close");
return;
}
sstream->chunk_buffer_size = 0;
sstream->ostream.ostream.offset += result;
EVP_CIPHER_CTX_free(sstream->cipher_context);
sstream->cipher_context = NULL;
}
*/
struct scrambler_ostream *sstream = (struct scrambler_ostream *) stream;
#ifdef DEBUG_STREAMS
i_debug("scrambler ostream close - %u bytes in / %u bytes out / %u bytes overhead",
sstream->in_byte_count, sstream->out_byte_count, sstream->out_byte_count - sstream->in_byte_count);
i_debug("scrambler ostream close - %u bytes in / %u bytes out / "
"%u bytes overhead", sstream->in_byte_count,
sstream->out_byte_count,
sstream->out_byte_count - sstream->in_byte_count);
#endif
if (close_parent)
if (close_parent) {
o_stream_close(sstream->ostream.parent);
}
}
struct ostream *
scrambler_ostream_create(struct ostream *output, EVP_PKEY *public_key)
scrambler_ostream_create(struct ostream *output,
const unsigned char *public_key)
{
struct scrambler_ostream *sstream = i_new(struct scrambler_ostream, 1);
struct ostream *result;
@ -306,10 +207,7 @@ scrambler_ostream_create(struct ostream *output, EVP_PKEY *public_key)
i_debug("scrambler ostream create");
#endif
sstream->package = PACKAGE_RSA_2048_AES_128_CTR_HMAC;
sstream->public_key = public_key;
sstream->cipher_context = EVP_CIPHER_CTX_new();
sstream->chunk_index = 0;
sstream->chunk_buffer_size = 0;
@ -317,14 +215,14 @@ scrambler_ostream_create(struct ostream *output, EVP_PKEY *public_key)
sstream->in_byte_count = 0;
sstream->out_byte_count = 0;
#endif
sstream->flushed = FALSE;
sstream->flushed = 0;
sstream->ostream.iostream.close = scrambler_ostream_close;
sstream->ostream.sendv = scrambler_ostream_sendv;
sstream->ostream.flush = scrambler_ostream_flush;
result = o_stream_create(&sstream->ostream, output, o_stream_get_fd(output));
result = o_stream_create(&sstream->ostream, output,
o_stream_get_fd(output));
if (scrambler_ostream_send_header(sstream) < 0) {
i_error("error creating ostream");
return NULL;

View File

@ -26,6 +26,6 @@
#include <openssl/evp.h>
struct ostream *scrambler_ostream_create(struct ostream *parent_ostream,
EVP_PKEY *public_key);
const unsigned char *public_key);
#endif /* SCRAMBLER_OSTREAM_H */

View File

@ -19,22 +19,28 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dovecot/lib.h"
#include "dovecot/array.h"
#include "dovecot/buffer.h"
#include "dovecot/hash.h"
#include "dovecot/istream.h"
#include "dovecot/ostream.h"
#include "dovecot/ostream-private.h"
#include "dovecot/str.h"
#include "dovecot/safe-mkstemp.h"
#include "dovecot/mail-user.h"
#include "dovecot/mail-storage-private.h"
#include "dovecot/index-storage.h"
#include "dovecot/index-mail.h"
#include "dovecot/strescape.h"
#include <assert.h>
#include <stdio.h>
#include <dovecot/lib.h>
#include <dovecot/array.h>
#include <dovecot/buffer.h>
#include <dovecot/hash.h>
#include <dovecot/istream.h>
#include <dovecot/ostream.h>
#include <dovecot/ostream-private.h>
#include <dovecot/str.h>
#include <dovecot/safe-mkstemp.h>
#include <dovecot/mail-user.h>
#include <dovecot/mail-storage-private.h>
#include <dovecot/index-storage.h>
#include <dovecot/index-mail.h>
#include <dovecot/strescape.h>
#include <sodium/crypto_box.h>
#include <sodium/crypto_pwhash.h>
#include <sodium/utils.h>
#include "scrambler-plugin.h"
#include "scrambler-common.h"
#include "scrambler-ostream.h"
@ -50,98 +56,129 @@
#define SCRAMBLER_USER_CONTEXT(obj) \
MODULE_CONTEXT(obj, scrambler_user_module)
// Structs
struct scrambler_user {
/* Dovecot module context. */
union mail_user_module_context module_ctx;
bool enabled;
EVP_PKEY *public_key;
EVP_PKEY *private_key;
/* Is this user has enabled this plugin? */
unsigned int enabled : 1;
/* User keypair. */
unsigned char public_key[crypto_box_PUBLICKEYBYTES];
/* Indicate if the private key has been set. With inbound mail, the plugin
* doesn't have access to the private key thus being empy. */
unsigned int private_key_set : 1;
unsigned char private_key[crypto_box_SECRETKEYBYTES];
};
const char *scrambler_plugin_version = DOVECOT_ABI_VERSION;
// Statics
static MODULE_CONTEXT_DEFINE_INIT(scrambler_storage_module, &mail_storage_module_register);
static MODULE_CONTEXT_DEFINE_INIT(scrambler_mail_module, &mail_module_register);
static MODULE_CONTEXT_DEFINE_INIT(scrambler_user_module, &mail_user_module_register);
// Functions
static const char *
scrambler_get_string_setting(struct mail_user *user, const char *name)
{
return mail_user_plugin_getenv(user, name);
}
static const char *
scrambler_get_pem_string_setting(struct mail_user *user, const char *name)
{
char *value = (char *)scrambler_get_string_setting(user, name);
if (value == NULL) {
return NULL;
}
scrambler_unescape_pem(value);
return value;
}
static unsigned int
static int
scrambler_get_integer_setting(struct mail_user *user, const char *name)
{
const char *value = scrambler_get_string_setting(user, name);
return value == NULL ? 0 : atoi(value);
if (value == NULL) {
return -1;
}
return atoi(value);
}
static EVP_PKEY *
scrambler_get_pem_key_setting(struct mail_user *user, const char *name)
static int
scrambler_get_user_hexdata(struct mail_user *user, const char *param,
unsigned char *out, size_t out_len)
{
const char *value = scrambler_get_pem_string_setting(user, name);
const char *hex_str;
if (value == NULL) {
return NULL;
hex_str = scrambler_get_string_setting(user, param);
if (hex_str == NULL) {
goto error;
}
if (sodium_hex2bin(out, out_len, hex_str, strlen(hex_str),
NULL, NULL, NULL)) {
user->error = p_strdup_printf(user->pool,
"Unable to convert %s for user %s.", param,
user->username);
goto error;
}
return scrambler_pem_read_public_key(value);
/* Success! */
return 0;
error:
return -1;
}
static void
scrambler_mail_user_created(struct mail_user *user)
{
int have_salt, password_fd;
unsigned char sk_salt[crypto_pwhash_SALTBYTES];
const char *password;
struct mail_user_vfuncs *v = user->vlast;
struct scrambler_user *suser;
suser = p_new(user->pool, struct scrambler_user, 1);
memset(suser, 0, sizeof(*suser));
suser->module_ctx.super = *v;
user->vlast = &suser->module_ctx.super;
suser->enabled = !!scrambler_get_integer_setting(user, "scrambler_enabled");
suser->public_key = scrambler_get_pem_key_setting(user, "scrambler_public_key");
const char *plain_password = scrambler_get_string_setting(user, "scrambler_plain_password");
unsigned int plain_password_fd = scrambler_get_integer_setting(user, "scrambler_plain_password_fd");
const char *private_key = scrambler_get_pem_string_setting(user, "scrambler_private_key");
const char *private_key_salt = scrambler_get_string_setting(user, "scrambler_private_key_salt");
unsigned int private_key_iterations = scrambler_get_integer_setting(user, "scrambler_private_key_iterations");
if (plain_password == NULL && plain_password_fd != 0) {
plain_password = scrambler_read_line_fd(user->pool, plain_password_fd);
/* Does this user should use the scrambler or not? */
suser->enabled = scrambler_get_integer_setting(user, "scrambler_enabled");
if (suser->enabled == -1) {
/* Not present means disabled. */
suser->enabled = 0;
}
if (plain_password != NULL && private_key != NULL && private_key_salt != NULL) {
const char *hashed_password = scrambler_hash_password(plain_password, private_key_salt, private_key_iterations);
suser->private_key = scrambler_pem_read_encrypted_private_key(private_key, hashed_password);
if (suser->private_key == NULL) {
/* Getting user public key. Without it, we can't do much so error if we
* can't find it. */
if (scrambler_get_user_hexdata(user, "scrambler_public_key",
suser->public_key,
sizeof(suser->public_key))) {
user->error = p_strdup_printf(user->pool,
"Unable to find public key for user %s.",
user->username);
goto end;
}
/* Get the user password that we'll use to . */
password = scrambler_get_string_setting(user, "scrambler_plain_password");
password_fd = scrambler_get_integer_setting(user, "scrambler_plain_password_fd");
if (password == NULL && password_fd >= 0) {
password = scrambler_read_line_fd(user->pool, password_fd);
}
/* Get the scrambler user salt. It's possible that it's not available. */
have_salt = !!scrambler_get_user_hexdata(user, "scrambler_private_key_salt",
sk_salt, sizeof(sk_salt));
/* If we have a password and a salt, derive the private key. */
if (password != NULL && have_salt) {
if (crypto_pwhash(suser->private_key, sizeof(suser->private_key),
password, strlen(password), sk_salt,
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT) < 0) {
user->error = p_strdup_printf(user->pool,
"Failed to load and decrypt the private key. May caused by an invalid password.");
"Unable to derive private key for user %s.",
user->username);
goto end;
}
suser->private_key_set = 1;
} else {
suser->private_key = NULL;
/* This means we are receiving an email for a user so we don't have access
* to the private key material. */
suser->private_key_set = 0;
}
end:
MODULE_CONTEXT_SET(user, scrambler_user_module, suser);
}
@ -150,40 +187,38 @@ scrambler_mail_save_begin(struct mail_save_context *context,
struct istream *input)
{
struct mailbox *box = context->transaction->box;
struct scrambler_user *suser = SCRAMBLER_USER_CONTEXT(box->storage->user);
union mailbox_module_context *mbox = SCRAMBLER_CONTEXT(box);
struct scrambler_user *suser = SCRAMBLER_USER_CONTEXT(box->storage->user);
struct ostream *output;
if (suser->enabled && suser->public_key == NULL) {
i_error("scrambler_mail_save_begin: encryption is enabled, but no public key is available");
return -1;
}
if (mbox->super.save_begin(context, input) < 0) {
return -1;
}
if (suser->enabled) {
// TODO: find a better solution for this. this currently works, because
// there is only one other ostream (zlib) in the setup. the scrambler should
// be added to the other end of the ostream chain, not to the
// beginning (the usual way).
if (context->data.output->real_stream->parent == NULL) {
output = scrambler_ostream_create(context->data.output, suser->public_key);
o_stream_unref(&context->data.output);
context->data.output = output;
} else {
output = scrambler_ostream_create(context->data.output->real_stream->parent, suser->public_key);
o_stream_unref(&context->data.output->real_stream->parent);
context->data.output->real_stream->parent = output;
}
#ifdef DEBUG_STREAMS
i_debug("scrambler write encrypted mail");
} else {
if (!suser->enabled) {
i_debug("scrambler write plain mail");
#endif
goto end;
}
// TODO: find a better solution for this. this currently works, because
// there is only one other ostream (zlib) in the setup. the scrambler should
// be added to the other end of the ostream chain, not to the
// beginning (the usual way).
if (context->data.output->real_stream->parent == NULL) {
output = scrambler_ostream_create(context->data.output,
suser->public_key);
o_stream_unref(&context->data.output);
context->data.output = output;
} else {
output = scrambler_ostream_create(context->data.output->real_stream->parent,
suser->public_key);
o_stream_unref(&context->data.output->real_stream->parent);
context->data.output->real_stream->parent = output;
}
i_debug("scrambler write encrypted mail");
end:
return 0;
}
@ -215,7 +250,9 @@ scrambler_istream_opened(struct mail *_mail, struct istream **stream)
struct istream *input;
input = *stream;
*stream = scrambler_istream_create(input, suser->private_key);
assert(suser->private_key_set);
*stream = scrambler_istream_create(input, suser->public_key,
suser->private_key);
i_stream_unref(&input);
int result = mmail->super.istream_opened(_mail, stream);
@ -248,7 +285,10 @@ static struct mail_storage_hooks scrambler_mail_storage_hooks = {
void
scrambler_plugin_init(struct module *module)
{
scrambler_initialize();
if (scrambler_initialize() < 0) {
/* Don't hook anything has we weren't able to initialize ourself. */
return;
}
mail_storage_hooks_add(module, &scrambler_mail_storage_hooks);
}