mirror of
https://0xacab.org/liberate/trees.git
synced 2024-11-22 10:10:39 +01:00
Use libsodium crypto box and dump OpenSSL RSA/AES
Signed-off-by: David Goulet <dgoulet@ev0ke.net>
This commit is contained in:
parent
6b6ba91254
commit
9b01cb760d
2
Makefile
2
Makefile
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user