summaryrefslogtreecommitdiffstats
path: root/src/plugins/mail-crypt/fs-crypt-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/mail-crypt/fs-crypt-common.c')
-rw-r--r--src/plugins/mail-crypt/fs-crypt-common.c368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/plugins/mail-crypt/fs-crypt-common.c b/src/plugins/mail-crypt/fs-crypt-common.c
new file mode 100644
index 0000000..7432fa6
--- /dev/null
+++ b/src/plugins/mail-crypt/fs-crypt-common.c
@@ -0,0 +1,368 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "randgen.h"
+#include "istream.h"
+#include "ostream.h"
+#include "istream-decrypt.h"
+#include "ostream-encrypt.h"
+#include "iostream-temp.h"
+#include "mailbox-list.h"
+#include "mail-namespace.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "dcrypt-iostream.h"
+#include "fs-api-private.h"
+
+struct crypt_fs {
+ struct fs fs;
+ struct mail_crypt_global_keys keys;
+ bool keys_loaded;
+
+ char *enc_algo;
+ char *set_prefix;
+ char *public_key_path;
+ char *private_key_path;
+ char *password;
+};
+
+struct crypt_fs_file {
+ struct fs_file file;
+ struct crypt_fs *fs;
+ struct fs_file *super_read;
+ enum fs_open_mode open_mode;
+ struct istream *input;
+
+ struct ostream *super_output;
+ struct ostream *temp_output;
+};
+
+#define CRYPT_FS(ptr) container_of((ptr), struct crypt_fs, fs)
+#define CRYPT_FILE(ptr) container_of((ptr), struct crypt_fs_file, file)
+
+/* defined outside this file */
+extern const struct fs FS_CLASS_CRYPT;
+
+static
+int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r);
+
+static struct fs *fs_crypt_alloc(void)
+{
+ struct crypt_fs *fs;
+
+ fs = i_new(struct crypt_fs, 1);
+ fs->fs = FS_CLASS_CRYPT;
+
+ return &fs->fs;
+}
+
+static int
+fs_crypt_init(struct fs *_fs, const char *args, const struct fs_settings *set,
+ const char **error_r)
+{
+ struct crypt_fs *fs = CRYPT_FS(_fs);
+ const char *enc_algo, *set_prefix;
+ const char *p, *arg, *value, *error, *parent_name, *parent_args;
+ const char *public_key_path = "", *private_key_path = "", *password = "";
+
+ if (!dcrypt_initialize("openssl", NULL, &error))
+ i_fatal("dcrypt_initialize(): %s", error);
+
+ /* [algo=<s>:][set_prefix=<n>:][public_key_path=<s>:]
+ [private_key_path=<s>:[password=<s>:]]<parent fs> */
+ set_prefix = "mail_crypt_global";
+ enc_algo = "aes-256-gcm-sha256";
+ for (;;) {
+ p = strchr(args, ':');
+ if (p == NULL) {
+ *error_r = "Missing parameters";
+ return -1;
+ }
+ arg = t_strdup_until(args, p);
+ if ((value = strchr(arg, '=')) == NULL)
+ break;
+ arg = t_strdup_until(arg, value++);
+ args = p+1;
+
+ if (strcmp(arg, "algo") == 0)
+ enc_algo = value;
+ else if (strcmp(arg, "set_prefix") == 0)
+ set_prefix = value;
+ else if (strcmp(arg, "public_key_path") == 0)
+ public_key_path = value;
+ else if (strcmp(arg, "private_key_path") == 0)
+ private_key_path = value;
+ else if (strcmp(arg, "password") == 0)
+ password = value;
+ else {
+ *error_r = t_strdup_printf(
+ "Invalid parameter '%s'", arg);
+ return -1;
+ }
+ }
+
+ parent_args = strchr(args, ':');
+ if (parent_args == NULL) {
+ parent_name = args;
+ parent_args = "";
+ } else {
+ parent_name = t_strdup_until(args, parent_args);
+ parent_args++;
+ }
+ if (fs_init(parent_name, parent_args, set, &_fs->parent, error_r) < 0)
+ return -1;
+ fs->enc_algo = i_strdup(enc_algo);
+ fs->set_prefix = i_strdup(set_prefix);
+ fs->public_key_path = i_strdup_empty(public_key_path);
+ fs->private_key_path = i_strdup_empty(private_key_path);
+ fs->password = i_strdup_empty(password);
+ return 0;
+}
+
+static void fs_crypt_free(struct fs *_fs)
+{
+ struct crypt_fs *fs = CRYPT_FS(_fs);
+
+ mail_crypt_global_keys_free(&fs->keys);
+ i_free(fs->enc_algo);
+ i_free(fs->set_prefix);
+ i_free(fs->public_key_path);
+ i_free(fs->private_key_path);
+ i_free(fs->password);
+ i_free(fs);
+}
+
+static struct fs_file *fs_crypt_file_alloc(void)
+{
+ struct crypt_fs_file *file = i_new(struct crypt_fs_file, 1);
+ return &file->file;
+}
+
+static void
+fs_crypt_file_init(struct fs_file *_file, const char *path,
+ enum fs_open_mode mode, enum fs_open_flags flags)
+{
+ struct crypt_fs *fs = CRYPT_FS(_file->fs);
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+ file->file.path = i_strdup(path);
+ file->fs = fs;
+ file->open_mode = mode;
+
+ /* avoid unnecessarily creating two seekable streams */
+ flags &= ENUM_NEGATE(FS_OPEN_FLAG_SEEKABLE);
+
+ file->file.parent = fs_file_init_parent(_file, path, mode, flags);
+ if (mode == FS_OPEN_MODE_READONLY &&
+ (flags & FS_OPEN_FLAG_ASYNC) == 0) {
+ /* use async stream for super, so fs_read_stream() won't create
+ another seekable stream needlessly */
+ file->super_read = fs_file_init_parent(_file, path,
+ mode, flags | FS_OPEN_FLAG_ASYNC |
+ FS_OPEN_FLAG_ASYNC_NOQUEUE);
+ } else {
+ file->super_read = file->file.parent;
+ }
+}
+
+static void fs_crypt_file_deinit(struct fs_file *_file)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+ if (file->super_read != _file->parent)
+ fs_file_deinit(&file->super_read);
+ fs_file_free(_file);
+ i_free(file->file.path);
+ i_free(file);
+}
+
+static void fs_crypt_file_close(struct fs_file *_file)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+ i_stream_unref(&file->input);
+ fs_file_close(file->super_read);
+ fs_file_close(_file->parent);
+}
+
+static int fs_crypt_read_file(const char *set_name, const char *path,
+ char **key_data_r, const char **error_r)
+{
+ struct istream *input;
+ int ret;
+
+ input = i_stream_create_file(path, SIZE_MAX);
+ while (i_stream_read(input) > 0) ;
+ if (input->stream_errno != 0) {
+ *error_r = t_strdup_printf("%s: read(%s) failed: %s",
+ set_name, path, i_stream_get_error(input));
+ ret = -1;
+ } else {
+ size_t size;
+ const unsigned char *data = i_stream_get_data(input, &size);
+ *key_data_r = i_strndup(data, size);
+ ret = 0;
+ }
+ i_stream_unref(&input);
+ return ret;
+}
+
+static int
+fs_crypt_load_keys_from_path(struct crypt_fs *fs, const char **error_r)
+{
+ char *key_data;
+
+ mail_crypt_global_keys_init(&fs->keys);
+ if (fs->public_key_path != NULL) {
+ if (fs_crypt_read_file("crypt:public_key_path",
+ fs->public_key_path,
+ &key_data, error_r) < 0)
+ return -1;
+ if (mail_crypt_load_global_public_key("crypt:public_key_path",
+ key_data, &fs->keys,
+ error_r) < 0) {
+ i_free(key_data);
+ return -1;
+ }
+ i_free(key_data);
+ }
+ if (fs->private_key_path != NULL) {
+ if (fs_crypt_read_file("crypt:private_key_path",
+ fs->private_key_path,
+ &key_data, error_r) < 0)
+ return -1;
+ if (mail_crypt_load_global_private_key("crypt:private_key_path",
+ key_data, "crypt:password",
+ fs->password, &fs->keys,
+ error_r) < 0) {
+ i_free(key_data);
+ return -1;
+ }
+ i_free(key_data);
+ }
+ return 0;
+}
+
+static int
+fs_crypt_istream_get_key(const char *pubkey_digest,
+ struct dcrypt_private_key **priv_key_r,
+ const char **error_r, void *context)
+{
+ struct crypt_fs_file *file = context;
+
+ if (fs_crypt_load_keys(file->fs, error_r) < 0)
+ return -1;
+
+ *priv_key_r = mail_crypt_global_key_find(&file->fs->keys, pubkey_digest);
+ if (*priv_key_r == NULL)
+ return 0;
+ dcrypt_key_ref_private(*priv_key_r);
+ return 1;
+}
+
+static struct istream *
+fs_crypt_read_stream(struct fs_file *_file, size_t max_buffer_size)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+ struct istream *input;
+
+ if (file->input != NULL) {
+ i_stream_ref(file->input);
+ i_stream_seek(file->input, 0);
+ return file->input;
+ }
+
+ input = fs_read_stream(file->super_read, max_buffer_size);
+
+ file->input = i_stream_create_decrypt_callback(input,
+ fs_crypt_istream_get_key, file);
+ i_stream_unref(&input);
+ i_stream_ref(file->input);
+ return file->input;
+}
+
+static void fs_crypt_write_stream(struct fs_file *_file)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+ const char *error;
+
+ i_assert(_file->output == NULL);
+
+ if (fs_crypt_load_keys(file->fs, &error) < 0) {
+ _file->output = o_stream_create_error_str(EIO,
+ "Couldn't read settings: %s", error);
+ return;
+ }
+
+ if (file->fs->keys.public_key == NULL) {
+ if (_file->fs->set.debug)
+ i_debug("No public key provided, "
+ "NOT encrypting stream %s",
+ fs_file_path(_file));
+ file->super_output = fs_write_stream(_file->parent);
+ _file->output = file->super_output;
+ return;
+ }
+
+ enum io_stream_encrypt_flags flags;
+ if (strstr(file->fs->enc_algo, "gcm") != NULL ||
+ strstr(file->fs->enc_algo, "ccm") != NULL) {
+ flags = IO_STREAM_ENC_INTEGRITY_AEAD;
+ } else {
+ flags = IO_STREAM_ENC_INTEGRITY_HMAC;
+ }
+
+ file->temp_output =
+ iostream_temp_create_named(_file->fs->temp_path_prefix,
+ IOSTREAM_TEMP_FLAG_TRY_FD_DUP,
+ fs_file_path(_file));
+ _file->output = o_stream_create_encrypt(file->temp_output,
+ file->fs->enc_algo, file->fs->keys.public_key,
+ flags);
+}
+
+static int fs_crypt_write_stream_finish(struct fs_file *_file, bool success)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+ struct istream *input;
+ int ret;
+
+ if (_file->output != NULL) {
+ if (_file->output == file->super_output)
+ _file->output = NULL;
+ else
+ o_stream_unref(&_file->output);
+ }
+ if (!success) {
+ if (file->super_output != NULL) {
+ /* no encryption */
+ i_assert(file->temp_output == NULL);
+ fs_write_stream_abort_error(_file->parent, &file->super_output,
+ "write(%s) failed: %s",
+ o_stream_get_name(file->super_output),
+ o_stream_get_error(file->super_output));
+ } else {
+ o_stream_destroy(&file->temp_output);
+ }
+ return -1;
+ }
+
+ if (file->super_output != NULL) {
+ /* no encrypt */
+ i_assert(file->temp_output == NULL);
+ return fs_write_stream_finish(_file->parent, &file->super_output);
+ }
+ if (file->temp_output == NULL) {
+ /* finishing up */
+ i_assert(file->super_output == NULL);
+ return fs_write_stream_finish_async(_file->parent);
+ }
+
+ /* finish writing the temporary file */
+ input = iostream_temp_finish(&file->temp_output, IO_BLOCK_SIZE);
+ file->super_output = fs_write_stream(_file->parent);
+ o_stream_nsend_istream(file->super_output, input);
+ ret = fs_write_stream_finish(_file->parent, &file->super_output);
+ i_stream_unref(&input);
+ return ret;
+}