diff --git a/configure.ac b/configure.ac index a9cfb6bb7f..64e61c454c 100644 --- a/configure.ac +++ b/configure.ac @@ -3008,6 +3008,7 @@ src/plugins/virtual/Makefile src/plugins/welcome/Makefile src/plugins/zlib/Makefile src/plugins/imap-zlib/Makefile +src/plugins/mail-crypt/Makefile stamp.h dovecot-config.in]) diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 7dc31d954c..86917a11b4 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -37,6 +37,7 @@ SUBDIRS = \ snarf \ stats \ imap-stats \ + mail-crypt \ trash \ virtual \ welcome \ diff --git a/src/plugins/mail-crypt/Makefile.am b/src/plugins/mail-crypt/Makefile.am new file mode 100644 index 0000000000..60f4ee81b3 --- /dev/null +++ b/src/plugins/mail-crypt/Makefile.am @@ -0,0 +1,111 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-dcrypt \ + -I$(top_srcdir)/src/lib-fs \ + -I$(top_srcdir)/src/doveadm \ + -I$(top_srcdir)/src/plugins/acl + +doveadm_moduledir = $(moduledir)/doveadm + +NOPLUGIN_LDFLAGS = + +module_LTLIBRARIES = \ + lib10_mail_crypt_plugin.la \ + lib05_mail_crypt_acl_plugin.la \ + libfs_crypt.la \ + libfs_mail_crypt.la + +doveadm_module_LTLIBRARIES = \ + libdoveadm_mail_crypt_plugin.la + +lib10_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version +lib10_mail_crypt_plugin_la_LIBADD = \ + $(LIBDCRYPT_LIBS) \ + $(LIBDOVECOT) + +lib05_mail_crypt_acl_plugin_la_LDFLAGS = -module -avoid-version +if DOVECOT_PLUGIN_DEPS +lib05_mail_crypt_acl_plugin_la_LIBADD = \ + $(LIBDCRYPT_LIBS) \ + lib10_mail_crypt_plugin.la +endif + +lib10_mail_crypt_plugin_la_SOURCES = \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c \ + mail-crypt-key.c \ + mail-crypt-plugin.c + +lib05_mail_crypt_acl_plugin_la_SOURCES = \ + mail-crypt-acl-plugin.c + +libfs_crypt_la_SOURCES = fs-crypt.c \ + mail-crypt-global-key.c \ + mail-crypt-pluginenv.c \ + fs-crypt-settings.c + +libfs_crypt_la_LIBADD = $(LIBDOVECOT) +libfs_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libfs_crypt_la_LDFLAGS = -module -avoid-version + +libfs_mail_crypt_la_SOURCES = fs-mail-crypt.c \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c +libfs_mail_crypt_la_LIBADD = $(LIBDOVECOT) +libfs_mail_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libfs_mail_crypt_la_LDFLAGS = -module -avoid-version + +libdoveadm_mail_crypt_plugin_la_SOURCES = \ + doveadm-mail-crypt.c +libdoveadm_mail_crypt_plugin_la_LIBADD = $(LIBDOVECOT) +libdoveadm_mail_crypt_plugin_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libdoveadm_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version + +test_programs = \ + test-mail-global-key \ + test-mail-key + +test_mail_global_key_SOURCES = \ + test-mail-global-key.c \ + fs-crypt-settings.c \ + mail-crypt-global-key.c +test_mail_global_key_LDADD = $(LIBDOVECOT) +test_mail_global_key_DEPENDENCIES = $(LIBDOVECOT_DEPS) +test_mail_global_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS) +test_mail_global_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\" + +test_mail_key_SOURCES = \ + test-mail-key.c \ + mail-crypt-key.c \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c + +test_mail_key_LDADD = $(LIBDOVECOT) $(LIBDOVECOT_STORAGE) +test_mail_key_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(LIBDOVECOT_STORAGE_DEPS) +test_mail_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS) +test_mail_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\" + +EXTRA_DIST = fs-crypt-common.c + +noinst_HEADERS = \ + mail-crypt-plugin.h \ + mail-crypt-common.h \ + mail-crypt-global-key.h \ + mail-crypt-key.h \ + fs-crypt-settings.h + +check: check-am check-test +check-test: all-am + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +noinst_PROGRAMS = $(test_programs) diff --git a/src/plugins/mail-crypt/doveadm-mail-crypt.c b/src/plugins/mail-crypt/doveadm-mail-crypt.c new file mode 100644 index 0000000000..9fda47b9ea --- /dev/null +++ b/src/plugins/mail-crypt/doveadm-mail-crypt.c @@ -0,0 +1,1007 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "askpass.h" +#include "doveadm-mail.h" +#include "getopt.h" +#include "array.h" +#include "str.h" +#include "buffer.h" +#include "ioloop.h" +#include "ioloop-private.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "mail-storage-settings.h" +#include "mailbox-attribute.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mailbox-list-iter.h" +#include "doveadm-print.h" +#include "hex-binary.h" + +struct generated_key { + const char *name; + const char *id; + const char *error; + struct mailbox *box; + bool success:1; + bool active:1; +}; + +ARRAY_DEFINE_TYPE(generated_keys, struct generated_key); + +struct mcp_cmd_context { + struct doveadm_mail_cmd_context ctx; + + const char *old_password; + const char *new_password; + + bool userkey_only:1; + bool recrypt_box_keys:1; + bool force:1; + bool ask_old_password:1; + bool ask_new_password:1; + bool clear_password:1; +}; + +struct mcp_key_iter_ctx { + pool_t pool; + ARRAY_TYPE(generated_keys) keys; +}; + +void doveadm_mail_crypt_plugin_init(struct module *mod ATTR_UNUSED); +void doveadm_mail_crypt_plugin_deinit(void); + +static int +mcp_user_create(struct mail_user *user, const char *dest_username, + struct mail_user **dest_user_r, + struct mail_storage_service_user **dest_service_user_r, + const char **error_r) +{ + const struct mail_storage_service_input *old_input; + struct mail_storage_service_input input; + struct mail_storage_service_ctx *service_ctx; + struct ioloop_context *cur_ioloop_ctx; + + int ret; + + i_assert(user->_service_user != NULL); + service_ctx = mail_storage_service_user_get_service_ctx(user->_service_user); + old_input = mail_storage_service_user_get_input(user->_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + + memset(&input, 0, sizeof(input)); + input.module = old_input->module; + input.service = old_input->service; + input.username = dest_username; + input.session_id_prefix = user->session_id; + input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS | + MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT; + + ret = mail_storage_service_lookup_next(service_ctx, &input, + dest_service_user_r, + dest_user_r, error_r); + + if (ret == 0) + *error_r = "User not found"; + + return ret; +} + +static int +mcp_update_shared_key(struct mailbox_transaction_context *t, + struct mail_user *user, const char *target_uid, + struct dcrypt_private_key *key, const char **error_r) +{ + const char *error; + struct mail_user *dest_user; + struct mail_storage_service_user *dest_service_user; + struct ioloop_context *cur_ioloop_ctx; + struct dcrypt_public_key *pkey; + int ret = 0; + + bool disallow_insecure = mail_crypt_acl_secure_sharing_enabled(user); + + ret = mcp_user_create(user, target_uid, &dest_user, + &dest_service_user, &error); + + /* to make sure we get correct logging context */ + mail_storage_service_io_deactivate_user(dest_service_user); + mail_storage_service_io_activate_user(user->_service_user); + + if (ret <= 0) { + i_error("Cannot initialize destination user %s: %s", + target_uid, error); + } else { + i_assert(dest_user != NULL); + /* get public key from target user */ + if ((ret = mail_crypt_user_get_public_key(dest_user, + &pkey, error_r)) <= 0) { + if (ret == 0 && disallow_insecure) { + *error_r = t_strdup_printf("User %s has no active public key", + dest_user->username); + ret = -1; + } else if (ret == 0) { + /* perform insecure sharing */ + dest_user = NULL; + pkey = NULL; + ret = 1; + } + } + + if (ret == 1) { + ARRAY_TYPE(dcrypt_private_key) keys; + t_array_init(&keys, 1); + array_append(&keys, &key, 1); + ret = mail_crypt_box_share_private_keys(t, pkey, + dest_user == NULL ? + NULL : + dest_user->username, + &keys, error_r); + } + + } + + /* logging context swap again */ + mail_storage_service_io_deactivate_user(user->_service_user); + mail_storage_service_io_activate_user(dest_service_user); + + if (dest_user != NULL) + mail_user_unref(&dest_user); + if (dest_service_user != NULL) + mail_storage_service_user_free(&dest_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + + mail_storage_service_io_activate_user(user->_service_user); + + return ret; +} + +static int mcp_update_shared_keys(struct mailbox *box, struct mail_user *user, + const char *pubid, struct dcrypt_private_key *key) +{ + const char *error; + int ret; + + ARRAY_TYPE(const_string) ids; + t_array_init(&ids, 8); + + /* figure out who needs the key */ + if ((ret = mail_crypt_box_get_pvt_digests(box, pool_datastack_create(), + MAIL_ATTRIBUTE_TYPE_SHARED, + &ids, &error)) < 0) { + i_error("mail_crypt_box_get_pvt_digests(%s, /shared) failed: %s", + mailbox_get_vname(box), + error); + return -1; + } + + const char *const *id; + bool found = FALSE; + string_t *uid = t_str_new(64); + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0); + + ret = 0; + + /* then perform sharing */ + array_foreach(&ids, id) { + if (strchr(*id, '/') != NULL) { + str_truncate(uid, 0); + const char *hexuid = t_strcut(*id, '/'); + hex_to_binary(hexuid, uid); + if (mcp_update_shared_key(t, user, str_c(uid), key, + &error) < 0) { + i_error("mcp_update_shared_key(%s, %s) failed: %s", + mailbox_get_vname(box), + str_c(uid), + error); + ret = -1; + break; + } + } else if (!found) { + found = TRUE; + if (mail_crypt_box_set_shared_key(t, pubid, key, + NULL, NULL, + &error) < 0) { + i_error("mail_crypt_box_set_shared_key(%s) failed: %s", + mailbox_get_vname(box), + error); + ret = -1; + break; + } + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + i_error("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + error); + ret = -1; + } + + return ret; +} + +static int mcp_keypair_generate(struct mcp_cmd_context *ctx, + struct dcrypt_public_key *user_key, + struct mailbox *box, struct dcrypt_keypair *pair_r, + const char **pubid_r, const char **error_r) +{ + struct dcrypt_keypair pair = {NULL, NULL}; + + int ret; + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0); + + if ((ret = mail_crypt_box_get_public_key(t, &pair.pub, error_r)) < 0) { + ret = -1; + } else if (ret == 1 && (!ctx->force || ctx->recrypt_box_keys)) { + /* do nothing */ + } else { + if ((ret = mail_crypt_box_generate_keypair(box, &pair, + user_key, pubid_r, error_r)) < 0) { + ret = -1; + } else { + *pubid_r = p_strdup(ctx->ctx.pool, *pubid_r); + *pair_r = pair; + ret = 1; + } + } + + if (ret < 1) { + if (pair.pub != NULL) + dcrypt_key_unref_public(&pair.pub); + if (pair.priv != NULL) + dcrypt_key_unref_private(&pair.priv); + } + + (void)mailbox_transaction_commit(&t); + + return ret; +} + +static int mcp_keypair_generate_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user, + ARRAY_TYPE(generated_keys) *result) +{ + const char *error; + int ret; + struct dcrypt_public_key *user_key; + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + const char *pubid; + bool user_key_generated = FALSE; + struct generated_key *res; + + if ((ret = mail_crypt_user_get_public_key(user, &user_key, + &error)) <= 0) { + struct dcrypt_keypair pair; + if (ret == -1) { + i_error("mail_crypt_user_get_public_key(%s) failed: %s", + user->username, + error); + } else if (ret == 0 && + mail_crypt_user_generate_keypair(user, &pair, + &pubid, &error) < 0) { + ret = -1; + i_error("mail_crypt_user_generate_keypair(%s) failed: %s", + user->username, + error); + res = array_append_space(result); + res->name = ""; + res->error = p_strdup(_ctx->pool, error); + res->success = FALSE; + } else { + res = array_append_space(result); + res->name = ""; + res->id = p_strdup(_ctx->pool, pubid); + res->success = TRUE; + /* don't do it again later on */ + user_key_generated = TRUE; + ret = 1; + user_key = pair.pub; + dcrypt_key_unref_private(&pair.priv); + } + if (ret == -1) return ret; + } + + if (ret == 1 && ctx->force && + ctx->userkey_only && !user_key_generated) { + struct dcrypt_keypair pair; + dcrypt_key_unref_public(&user_key); + /* regen user key */ + res = array_append_space(result); + res->name = ""; + if (mail_crypt_user_generate_keypair(user, &pair, &pubid, + &error) < 0) { + res->success = FALSE; + res->id = p_strdup(_ctx->pool, error); + return -1; + } + user_key = pair.pub; + dcrypt_key_unref_private(&pair.priv); + } + + if (ctx->userkey_only) + return 0; + + const char *const *patterns = (const char *const[]){ "*", NULL }; + + /* only re-encrypt all folder keys if wanted */ + if (!ctx->recrypt_box_keys) { + patterns = ctx->ctx.args; + } + + const struct mailbox_info *info; + struct mailbox_list_iterate_context *iter = + mailbox_list_iter_init_namespaces(user->namespaces, + patterns, + MAIL_NAMESPACE_TYPE_PRIVATE, + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_NO_AUTO_BOXES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while((info = mailbox_list_iter_next(iter)) != NULL) { + if ((info->flags & MAILBOX_NOSELECT) != 0 || + (info->flags & MAILBOX_NONEXISTENT) != 0) continue; + struct dcrypt_keypair pair; + + struct mailbox *box = + mailbox_alloc(info->ns->list, + info->vname, 0); + if (mailbox_open(box) < 0) { + res = array_append_space(result); + res->name = p_strdup(_ctx->pool, info->vname); + res->success = FALSE; + res->error = p_strdup(_ctx->pool, + mailbox_get_last_error(box, NULL)); + } else if ((ret = mcp_keypair_generate(ctx, user_key, box, + &pair, &pubid, + &error)) < 0) { + res = array_append_space(result); + res->name = p_strdup(_ctx->pool, info->vname); + res->success = FALSE; + res->error = p_strdup(_ctx->pool, error); + } else if (ret >= 0) { + res = array_append_space(result); + res->name = p_strdup(_ctx->pool, info->vname); + res->success = TRUE; + res->id = pubid; + T_BEGIN { + mcp_update_shared_keys(box, user, pubid, pair.priv); + } T_END; + dcrypt_keypair_unref(&pair); + } + mailbox_free(&box); + } + + (void)mailbox_list_iter_deinit(&iter); + + dcrypt_key_unref_public(&user_key); + return 0; +} + +static int cmd_mcp_keypair_generate_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + int ret = 0; + + ARRAY_TYPE(generated_keys) result; + p_array_init(&result, _ctx->pool, 8); + + if (mcp_keypair_generate_run(_ctx, user, &result) < 0) + _ctx->exit_code = EX_DATAERR; + + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + doveadm_print_header("success", " ", 0); + doveadm_print_header("box", "Folder", 0); + doveadm_print_header("pubid", "Public ID", 0); + + const struct generated_key *res; + + array_foreach(&result, res) { + if (res->success) + doveadm_print("\xE2\x9C\x93"); + else { + ret = -1; + doveadm_print("x"); + } + doveadm_print(res->name); + if (!res->success) + doveadm_print(t_strdup_printf("ERROR: %s", res->error)); + else + doveadm_print(res->id); + } + + return ret; +} + +static void mcp_key_list(struct mcp_cmd_context *ctx, + struct mail_user *user, + void(*callback)(const struct generated_key *, void *), + void *context) +{ + const char *error; + int ret; + + /* we need to use the mailbox attribute API here, as we + are not necessarely able to decrypt any of these keys + */ + + ARRAY_TYPE(const_string) ids; + t_array_init(&ids, 8); + + if (ctx->userkey_only) { + struct mailbox_attribute_iter *iter; + struct mail_namespace *ns = + mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = + mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY); + struct mail_attribute_value value; + memset(&value, 0, sizeof(value)); + if (mailbox_open(box) < 0) { + i_error("mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + mailbox_free(&box); + return; + } + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0); + + if ((ret = mailbox_attribute_get(t, MAIL_ATTRIBUTE_TYPE_SHARED, + USER_CRYPT_PREFIX + ACTIVE_KEY_NAME, + &value)) < 0) { + i_error("mailbox_get_attribute(%s, %s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_error(box, NULL)); + } + + iter = mailbox_attribute_iter_init(box, + MAIL_ATTRIBUTE_TYPE_PRIVATE, + USER_CRYPT_PREFIX + PRIVKEYS_PREFIX); + const char *key_id; + if (value.value == NULL) + value.value = ""; + while ((key_id = mailbox_attribute_iter_next(iter)) != NULL) { + struct generated_key key; + key.id = key_id; + key.active = strcmp(value.value, key_id) == 0; + key.name = ""; + key.box = box; + callback(&key, context); + } + + if (mailbox_attribute_iter_deinit(&iter) < 0) + i_error("mailbox_attribute_iter_deinit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + + (void)mailbox_transaction_commit(&t); + + mailbox_free(&box); + return; + } + + const struct mailbox_info *info; + struct mailbox_list_iterate_context *iter = + mailbox_list_iter_init_namespaces(user->namespaces, + ctx->ctx.args, + MAIL_NAMESPACE_TYPE_PRIVATE, + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_NO_AUTO_BOXES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + + while((info = mailbox_list_iter_next(iter)) != NULL) { + if ((info->flags & MAILBOX_NOSELECT) != 0 || + (info->flags & MAILBOX_NONEXISTENT) != 0) continue; + + struct mailbox *box = + mailbox_alloc(info->ns->list, + info->vname, MAILBOX_FLAG_READONLY); + + if (mailbox_open(box) < 0) { + i_error("mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + mailbox_free(&box); + continue; + } + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0); + + struct mail_attribute_value value; + memset(&value, 0, sizeof(value)); + array_clear(&ids); + + /* get active ID */ + if ((ret = mailbox_attribute_get(t, MAIL_ATTRIBUTE_TYPE_SHARED, + BOX_CRYPT_PREFIX + ACTIVE_KEY_NAME, + &value)) < 0) { + i_error("mailbox_get_attribute(%s, %s) failed: %s", + mailbox_get_vname(box), + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_error(box, NULL)); + } else if ((ret = mail_crypt_box_get_pvt_digests(box, pool_datastack_create(), + MAIL_ATTRIBUTE_TYPE_PRIVATE, + &ids, &error)) < 0) { + i_error("mail_crypt_box_get_pvt_digests(%s) failed: %s", + mailbox_get_vname(box), + error); + } else { + const char *const *id; + const char *boxname = mailbox_get_vname(box); + if (value.value == NULL) + value.value = ""; + array_foreach(&ids, id) { + struct generated_key key; + key.name = boxname; + key.id = *id; + if (value.value != NULL) + key.active = strcmp(*id, value.value) == 0; + else + key.active = FALSE; + key.box = box; + callback(&key, context); + } + } + + (void)mailbox_transaction_commit(&t); + mailbox_free(&box); + } + + (void)mailbox_list_iter_deinit(&iter); +} + +static void cmd_mcp_key_list_cb(const struct generated_key *_key, void *context) +{ + struct mcp_key_iter_ctx *ctx = context; + struct generated_key *key = array_append_space(&ctx->keys); + key->name = p_strdup(ctx->pool, _key->name); + key->id = p_strdup(ctx->pool, _key->id); + key->active = _key->active; +} + +static int cmd_mcp_key_list_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + struct mcp_key_iter_ctx iter_ctx; + memset(&iter_ctx, 0, sizeof(iter_ctx)); + iter_ctx.pool = _ctx->pool; + p_array_init(&iter_ctx.keys, _ctx->pool, 8); + + mcp_key_list(ctx, user, cmd_mcp_key_list_cb, &iter_ctx); + + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + doveadm_print_header("box", "Folder", 0); + doveadm_print_header("active", "Active", 0); + doveadm_print_header("pubid", "Public ID", 0); + + const struct generated_key *key; + array_foreach(&iter_ctx.keys, key) { + doveadm_print(key->name); + doveadm_print(key->active ? "yes" : "no"); + doveadm_print(key->id); + } + return 0; +} + +static void cmd_mcp_key_export_cb(const struct generated_key *key, + void *context ATTR_UNUSED) +{ + struct dcrypt_private_key *pkey; + bool user_key = FALSE; + const char *error = NULL; + int ret; + + if (*key->name == '\0') + user_key = TRUE; + + doveadm_print(key->name); + doveadm_print(key->id); + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(key->box, 0); + + if ((ret = mail_crypt_get_private_key(t, key->id, user_key, FALSE, + &pkey, &error)) <= 0) { + if (ret == 0) + error = "key not found"; + doveadm_print(t_strdup_printf("ERROR: %s", error)); + doveadm_print(""); + } else { + string_t *out = t_str_new(64); + if (!dcrypt_key_store_private(pkey, DCRYPT_FORMAT_PEM, NULL, out, + NULL, NULL, &error)) { + doveadm_print(t_strdup_printf("ERROR: %s", error)); + doveadm_print(""); + } else { + /* this is to make it more compatible with openssl cli + as it expects BEGIN on it's own line */ + doveadm_print(t_strdup_printf("\n%s", str_c(out))); + } + dcrypt_key_unref_private(&pkey); + } + + (void)mailbox_transaction_commit(&t); +} + +static int cmd_mcp_key_export_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER); + doveadm_print_header("box", "Folder", 0); + doveadm_print_header("name", "Public ID", 0); + doveadm_print_header("error", "Error", 0); + doveadm_print_header("key", "Key", 0); + + mcp_key_list(ctx, user, cmd_mcp_key_export_cb, NULL); + + return 0; +} + +static int cmd_mcp_key_password_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + struct raw_key { + const char *attr; + const char *id; + const char *data; + }; + + ARRAY(struct raw_key) raw_keys; + + doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER); + + doveadm_print_header_simple("result"); + + if (ctx->ask_old_password) { + if (ctx->old_password != NULL) { + doveadm_print("old password specified, cannot ask for it"); + _ctx->exit_code = EX_USAGE; + return -1; + } + if (!_ctx->cli) { + doveadm_print("No cli - cannot ask for password"); + _ctx->exit_code = EX_USAGE; + return -1; + } + ctx->old_password = + p_strdup(_ctx->pool, t_askpass("Old password: ")); + } + + if (ctx->ask_new_password) { + if (ctx->new_password != NULL) { + doveadm_print("new password specified, cannot ask for it"); + _ctx->exit_code = EX_USAGE; + return -1; + } + if (!_ctx->cli) { + doveadm_print("No cli - cannot ask for password"); + _ctx->exit_code = EX_USAGE; + return -1; + } + ctx->new_password = + p_strdup(_ctx->pool, t_askpass("New password: ")); + } + + if (ctx->clear_password && + (ctx->new_password != NULL || + mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD) != NULL)) { + doveadm_print("clear password and new password specified"); + _ctx->exit_code = EX_USAGE; + return -1; + } + + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", 0); + if (mailbox_open(box) < 0) { + doveadm_print(t_strdup_printf("mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL))); + _ctx->exit_code = EX_TEMPFAIL; + return -1; + } + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0); + + t_array_init(&raw_keys, 8); + + /* then get the current user keys, all of them */ + struct mailbox_attribute_iter *iter = + mailbox_attribute_iter_init(box, + MAIL_ATTRIBUTE_TYPE_PRIVATE, + USER_CRYPT_PREFIX + PRIVKEYS_PREFIX); + const char *error; + const char *key_id; + int ret = 1; + unsigned int count = 0; + + while ((key_id = mailbox_attribute_iter_next(iter)) != NULL) { + const char *attr = + t_strdup_printf(USER_CRYPT_PREFIX PRIVKEYS_PREFIX "%s", + key_id); + + struct mail_attribute_value value; + if ((ret = mailbox_attribute_get(t, MAIL_ATTRIBUTE_TYPE_PRIVATE, + attr, &value)) < 0) { + doveadm_print(t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s", + mailbox_get_vname(box), attr, + mailbox_get_last_error(box, NULL))); + _ctx->exit_code = EX_TEMPFAIL; + break; + } else if (ret > 0) { + struct raw_key *raw_key = array_append_space(&raw_keys); + raw_key->attr = p_strdup(_ctx->pool, attr); + raw_key->id = p_strdup(_ctx->pool, key_id); + raw_key->data = p_strdup(_ctx->pool, value.value); + } + } + + if (ret == 1) { + struct dcrypt_private_key *key; + const struct raw_key *raw_key; + const char *algo = ctx->new_password != NULL ? + MAIL_CRYPT_PW_CIPHER : + NULL; + string_t *newkey = t_str_new(256); + + array_foreach(&raw_keys, raw_key) { + struct mail_attribute_value value; + + if (!dcrypt_key_load_private(&key, raw_key->data, + ctx->old_password, NULL, + &error)) { + doveadm_print(t_strdup_printf("dcrypt_key_load_private(%s) failed: %s", + raw_key->id, + error)); + _ctx->exit_code = EX_DATAERR; + ret = -1; + break; + } + + /* save it */ + str_truncate(newkey, 0); + + if (!dcrypt_key_store_private(key, DCRYPT_FORMAT_DOVECOT, + algo, newkey, + ctx->new_password, + NULL, &error)) { + doveadm_print(t_strdup_printf("dcrypt_key_store_private(%s) failed: %s", + raw_key->id, + error)); + _ctx->exit_code = EX_DATAERR; + ret = -1; + } + + dcrypt_key_unref_private(&key); + if (ret == -1) break; + + memset(&value, 0, sizeof(value)); + value.value = str_c(newkey); + + /* and store it */ + if (mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_PRIVATE, + raw_key->attr, &value) < 0) { + doveadm_print(t_strdup_printf("mailbox_attribute_set(%s, %s) failed: %s", + mailbox_get_vname(box), + raw_key->attr, + mailbox_get_last_error(box, NULL))); + _ctx->exit_code = EX_TEMPFAIL; + ret = -1; + break; + } + count++; + } + } + + if (ret < 1) { + mailbox_transaction_rollback(&t); + } else { + if (mailbox_transaction_commit(&t) < 0) { + doveadm_print(t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL))); + } else { + doveadm_print(t_strdup_printf("Changed password for %u key(s)", + count)); + } + } + + (void)mailbox_attribute_iter_deinit(&iter); + mailbox_free(&box); + + return ret; +} + + +static bool cmd_mcp_keypair_generate_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + switch (c) { + case 'U': + ctx->userkey_only = TRUE; + break; + case 'R': + ctx->recrypt_box_keys = TRUE; + break; + case 'f': + ctx->force = TRUE; + default: + return FALSE; + } + return TRUE; + +} + +static bool cmd_mcp_key_password_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + switch (c) { + case 'N': + ctx->ask_new_password = TRUE; + break; + case 'O': + ctx->ask_old_password = TRUE; + break; + case 'C': + ctx->clear_password = TRUE; + break; + case 'o': + ctx->old_password = p_strdup(_ctx->pool, optarg); + break; + case 'n': + ctx->new_password = p_strdup(_ctx->pool, optarg); + break; + default: + return FALSE; + } + return TRUE; +} + +static bool cmd_mcp_key_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + switch (c) { + case 'U': + ctx->userkey_only = TRUE; + break; + default: + return FALSE; + } + return TRUE; + +} + +static struct doveadm_mail_cmd_context *cmd_mcp_keypair_generate_alloc(void) +{ + struct mcp_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context); + ctx->ctx.getopt_args = "URf"; + ctx->ctx.v.parse_arg = cmd_mcp_keypair_generate_parse_arg; + ctx->ctx.v.run = cmd_mcp_keypair_generate_run; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_mcp_key_list_alloc(void) +{ + struct mcp_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context); + ctx->ctx.getopt_args = "U"; + ctx->ctx.v.parse_arg = cmd_mcp_key_parse_arg; + ctx->ctx.v.run = cmd_mcp_key_list_run; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_mcp_key_export_alloc(void) +{ + struct mcp_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context); + ctx->ctx.getopt_args = "U"; + ctx->ctx.v.parse_arg = cmd_mcp_key_parse_arg; + ctx->ctx.v.run = cmd_mcp_key_export_run; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_mcp_key_password_alloc(void) +{ + struct mcp_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context); + ctx->ctx.getopt_args = "NOCo:n:"; + ctx->ctx.v.parse_arg = cmd_mcp_key_password_parse_arg; + ctx->ctx.v.run = cmd_mcp_key_password_run; + return &ctx->ctx; +} + +struct doveadm_cmd_ver2 doveadm_cmd_mcp_keypair_generate = { + .name = "mailbox cryptokey generate", + .mail_cmd = cmd_mcp_keypair_generate_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-URf] mailbox [ mailbox .. ]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('U', "user-key-only", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('R', "re-encrypt-box-keys", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('f', "force", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; + +struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_list = { + .name = "mailbox cryptokey list", + .mail_cmd = cmd_mcp_key_list_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "-U | mailbox [ mailbox .. ]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('U', "user-key", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; + +struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_export = { + .name = "mailbox cryptokey export", + .mail_cmd = cmd_mcp_key_export_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "-U | mailbox [ mailbox .. ]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('U', "user-key", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; + +struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_password = { + .name = "mailbox cryptokey password", + .mail_cmd = cmd_mcp_key_password_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-NOC] [-opassword] [-npassword]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('C', "clear-password", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('N', "ask-new-password", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('n', "new-password", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('O', "ask-old-password", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('o', "old-password", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAMS_END +}; + +void doveadm_mail_crypt_plugin_init(struct module *mod ATTR_UNUSED) +{ + doveadm_cmd_register_ver2(&doveadm_cmd_mcp_keypair_generate); + doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_list); + doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_export); + doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_password); +} + +void doveadm_mail_crypt_plugin_deinit(void) +{ +} 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 0000000000..645ad41a5e --- /dev/null +++ b/src/plugins/mail-crypt/fs-crypt-common.c @@ -0,0 +1,375 @@ +/* Copyright (c) 2015-2016 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; +}; + +/* 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) +{ + struct crypt_fs *fs = (struct 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 = ""; + + random_init(); + if (!dcrypt_initialize("openssl", NULL, &error)) + i_fatal("dcrypt_initialize(): %s", error); + + /* [algo=:][set_prefix=:][public_key_path=:] + [private_key_path=:[password=:]] */ + set_prefix = "mail_crypt_global"; + enc_algo = "aes-256-gcm-sha256"; + for (;;) { + p = strchr(args, ':'); + if (p == NULL) { + fs_set_error(_fs, "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 { + fs_set_error(_fs, "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) < 0) { + fs_set_error(_fs, "%s: %s", parent_name, error); + 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_deinit(struct fs *_fs) +{ + struct crypt_fs *fs = (struct crypt_fs *)_fs; + + mail_crypt_global_keys_free(&fs->keys); + if (_fs->parent != NULL) + fs_deinit(&_fs->parent); + 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); + random_deinit(); +} + +static struct fs_file * +fs_crypt_file_init(struct fs *_fs, const char *path, + enum fs_open_mode mode, enum fs_open_flags flags) +{ + struct crypt_fs *fs = (struct crypt_fs *)_fs; + struct crypt_fs_file *file; + + file = i_new(struct crypt_fs_file, 1); + file->file.fs = _fs; + file->file.path = i_strdup(path); + file->fs = fs; + file->open_mode = mode; + + /* avoid unnecessarily creating two seekable streams */ + flags &= ~FS_OPEN_FLAG_SEEKABLE; + + file->file.parent = fs_file_init(_fs->parent, 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 unneededly */ + file->super_read = fs_file_init(_fs->parent, path, mode | flags | + FS_OPEN_FLAG_ASYNC); + } else { + file->super_read = file->file.parent; + } + return &file->file; +} + +static void fs_crypt_file_deinit(struct fs_file *_file) +{ + struct crypt_fs_file *file = (struct crypt_fs_file *)_file; + + if (file->super_read != _file->parent && file->super_read != NULL) + fs_file_deinit(&file->super_read); + fs_file_deinit(&_file->parent); + i_free(file->file.path); + i_free(file); +} + +static void fs_crypt_file_close(struct fs_file *_file) +{ + struct crypt_fs_file *file = (struct crypt_fs_file *)_file; + + if (file->input != NULL) + i_stream_unref(&file->input); + if (file->super_read != NULL) + fs_file_close(file->super_read); + if (_file->parent != NULL) + 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_t)-1); + 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); + return *priv_key_r == NULL ? 0 : 1; +} + +static struct istream * +fs_crypt_read_stream(struct fs_file *_file, size_t max_buffer_size) +{ + struct crypt_fs_file *file = (struct crypt_fs_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 = (struct crypt_fs_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 = (struct crypt_fs_file *)_file; + struct istream *input; + ssize_t ret; + struct const_iovec iov; + const unsigned char *data; + + 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 if (file->temp_output != NULL) { + 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); + + while (i_stream_read_more(input, &data, &iov.iov_len) > 0) { + iov.iov_base = data; + o_stream_nsendv(file->super_output, &iov, 1); + i_stream_skip(input, iov.iov_len); + } + + ret = fs_write_stream_finish(_file->parent, &file->super_output); + i_stream_unref(&input); + return ret; +} + diff --git a/src/plugins/mail-crypt/fs-crypt-settings.c b/src/plugins/mail-crypt/fs-crypt-settings.c new file mode 100644 index 0000000000..074d15064f --- /dev/null +++ b/src/plugins/mail-crypt/fs-crypt-settings.c @@ -0,0 +1,38 @@ +/* Copyright (c) 2010-2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "settings-parser.h" +#include "service-settings.h" +#include "mail-storage-settings.h" +#include "fs-crypt-settings.h" + +#undef DEF +#define DEF(type, name) \ + { type, #name, offsetof(struct fs_crypt_settings, name), NULL } + +static const struct setting_define fs_crypt_setting_defines[] = { + { SET_STRLIST, "plugin", offsetof(struct fs_crypt_settings, plugin_envs), NULL }, + + SETTING_DEFINE_LIST_END +}; + +const struct fs_crypt_settings fs_crypt_default_settings = { + .plugin_envs = ARRAY_INIT +}; + +static const struct setting_parser_info *fs_crypt_setting_dependencies[] = { + NULL +}; + +const struct setting_parser_info fs_crypt_setting_parser_info = { + .module_name = "fs-crypt", + .defines = fs_crypt_setting_defines, + .defaults = &fs_crypt_default_settings, + + .type_offset = (size_t)-1, + .struct_size = sizeof(struct fs_crypt_settings), + + .parent_offset = (size_t)-1, + .dependencies = fs_crypt_setting_dependencies +}; diff --git a/src/plugins/mail-crypt/fs-crypt-settings.h b/src/plugins/mail-crypt/fs-crypt-settings.h new file mode 100644 index 0000000000..df1a7b19ad --- /dev/null +++ b/src/plugins/mail-crypt/fs-crypt-settings.h @@ -0,0 +1,11 @@ +#ifndef FS_CRYPT_SETTINGS_H +#define FS_CRYPT_SETTINGS_H + +struct fs_crypt_settings { + ARRAY(const char *) plugin_envs; +}; + +extern const struct setting_parser_info fs_crypt_setting_parser_info; + +#endif + diff --git a/src/plugins/mail-crypt/fs-crypt.c b/src/plugins/mail-crypt/fs-crypt.c new file mode 100644 index 0000000000..108b9d4f84 --- /dev/null +++ b/src/plugins/mail-crypt/fs-crypt.c @@ -0,0 +1,62 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ +#define FS_CLASS_CRYPT fs_class_crypt +#include "fs-crypt-common.c" + +static +int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r) +{ + const char *error; + + if (fs->keys_loaded) + return 0; + if (fs->public_key_path != NULL || fs->private_key_path != NULL) { + /* overrides using settings */ + if (fs_crypt_load_keys_from_path(fs, error_r) < 0) + return -1; + fs->keys_loaded = TRUE; + return 0; + } + if (mail_crypt_global_keys_load_pluginenv(fs->set_prefix, &fs->keys, + &error) < 0) { + *error_r = t_strdup_printf("%s: %s", fs->set_prefix, error); + return -1; + } + fs->keys_loaded = TRUE; + return 0; +} + +const struct fs fs_class_crypt = { + .name = "crypt", + .v = { + fs_crypt_alloc, + fs_crypt_init, + fs_crypt_deinit, + fs_wrapper_get_properties, + fs_crypt_file_init, + fs_crypt_file_deinit, + fs_crypt_file_close, + fs_wrapper_file_get_path, + fs_wrapper_set_async_callback, + fs_wrapper_wait_async, + fs_wrapper_set_metadata, + fs_wrapper_get_metadata, + fs_wrapper_prefetch, + fs_read_via_stream, + fs_crypt_read_stream, + fs_write_via_stream, + fs_crypt_write_stream, + fs_crypt_write_stream_finish, + fs_wrapper_lock, + fs_wrapper_unlock, + fs_wrapper_exists, + fs_wrapper_stat, + fs_wrapper_copy, + fs_wrapper_rename, + fs_wrapper_delete, + fs_wrapper_iter_init, + fs_wrapper_iter_next, + fs_wrapper_iter_deinit, + NULL, + fs_wrapper_get_nlinks, + } +}; diff --git a/src/plugins/mail-crypt/fs-mail-crypt.c b/src/plugins/mail-crypt/fs-mail-crypt.c new file mode 100644 index 0000000000..d75b66bf85 --- /dev/null +++ b/src/plugins/mail-crypt/fs-mail-crypt.c @@ -0,0 +1,69 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ +#define FS_CLASS_CRYPT fs_class_mail_crypt +#include "fs-crypt-common.c" + +static +int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r) +{ + struct mailbox_list *list = mailbox_list_fs_get_list(&fs->fs); + const char *error; + + if (fs->keys_loaded) + return 0; + if (fs->public_key_path != NULL || fs->private_key_path != NULL) { + /* overrides using settings */ + if (fs_crypt_load_keys_from_path(fs, error_r) < 0) + return -1; + fs->keys_loaded = TRUE; + return 0; + } + if (list == NULL) { + *error_r = "fs-mail-crypt can be used only via lib-storage"; + return -1; + } + + if (mail_crypt_global_keys_load(mailbox_list_get_namespace(list)->user, + fs->set_prefix, &fs->keys, FALSE, + &error) < 0) { + *error_r = t_strdup_printf("%s: %s", fs->set_prefix, error); + return -1; + } + fs->keys_loaded = TRUE; + return 0; +} + +const struct fs fs_class_mail_crypt = { + .name = "mail-crypt", + .v = { + fs_crypt_alloc, + fs_crypt_init, + fs_crypt_deinit, + fs_wrapper_get_properties, + fs_crypt_file_init, + fs_crypt_file_deinit, + fs_crypt_file_close, + fs_wrapper_file_get_path, + fs_wrapper_set_async_callback, + fs_wrapper_wait_async, + fs_wrapper_set_metadata, + fs_wrapper_get_metadata, + fs_wrapper_prefetch, + fs_read_via_stream, + fs_crypt_read_stream, + fs_write_via_stream, + fs_crypt_write_stream, + fs_crypt_write_stream_finish, + fs_wrapper_lock, + fs_wrapper_unlock, + fs_wrapper_exists, + fs_wrapper_stat, + fs_wrapper_copy, + fs_wrapper_rename, + fs_wrapper_delete, + fs_wrapper_iter_init, + fs_wrapper_iter_next, + fs_wrapper_iter_deinit, + NULL, + fs_wrapper_get_nlinks, + } +}; diff --git a/src/plugins/mail-crypt/mail-crypt-acl-plugin.c b/src/plugins/mail-crypt/mail-crypt-acl-plugin.c new file mode 100644 index 0000000000..cb3c98d99c --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-acl-plugin.c @@ -0,0 +1,412 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop-private.h" +#include "str.h" +#include "sha2.h" +#include "module-dir.h" +#include "var-expand.h" +#include "hex-binary.h" +#include "mail-namespace.h" +#include "mail-storage-hooks.h" +#include "mail-storage-service.h" +#include "acl-plugin.h" +#include "acl-api-private.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" + +#define MAIL_CRYPT_ACL_LIST_CONTEXT(obj) \ + MODULE_CONTEXT(obj, mail_crypt_acl_mailbox_list_module) + +struct mail_crypt_acl_mailbox_list { + union mailbox_list_module_context module_ctx; + struct acl_backend_vfuncs acl_vprev; +}; + +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_acl_mailbox_list_module, + &mailbox_list_module_register); + +void mail_crypt_acl_plugin_init(struct module *module); +void mail_crypt_acl_plugin_deinit(void); + +static int +mail_crypt_acl_has_user_read_right(struct acl_object *aclobj, + const char *username, + const char **error_r ATTR_UNUSED) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret; + + iter = acl_object_list_init(aclobj); + while ((ret = acl_object_list_next(iter, &rights)) > 0) { + if (rights.id_type == ACL_ID_USER && + strcmp(rights.identifier, username) == 0) { + ret = str_array_find(rights.rights, MAIL_ACL_READ) ? 1 : 0; + break; + } + } + acl_object_list_deinit(&iter); + return ret; +} + +static int mail_crypt_acl_has_nonuser_read_right(struct acl_object *aclobj, + const char **error_r ATTR_UNUSED) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret; + + iter = acl_object_list_init(aclobj); + while ((ret = acl_object_list_next(iter, &rights)) > 0) { + if (rights.id_type != ACL_ID_USER && + rights.id_type != ACL_ID_OWNER && + rights.rights != NULL && + str_array_find(rights.rights, MAIL_ACL_READ)) { + ret = 1; + break; + } + } + acl_object_list_deinit(&iter); + return ret; +} + +static int +mail_crypt_acl_unset_private_keys(struct mailbox *src_box, + const char *dest_user, + enum mail_attribute_type type, + const char **error_r) +{ + ARRAY_TYPE(const_string) digests; + const char *error; + int ret = 1; + + t_array_init(&digests, 4); + if (mail_crypt_box_get_pvt_digests(src_box, pool_datastack_create(), + type, &digests, &error) < 0) { + *error_r = t_strdup_printf("mail-crypt-acl-plugin: " + "Failed to lookup public key digests: %s", + error); + mailbox_free(&src_box); + return -1; + } + + struct mailbox_transaction_context *t; + t = mailbox_transaction_begin(src_box, 0); + + const char *const *hash; + array_foreach(&digests, hash) { + const char *ptr; + /* if the id contains username part, skip to key public id */ + if ((ptr = strchr(*hash, '/')) != NULL) + ptr++; + else + ptr = *hash; + if ((ret = mail_crypt_box_unset_shared_key(t, ptr, dest_user, + error_r)) < 0) { + ret = -1; + break; + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + *error_r = t_strdup_printf("mail-crypt-acl-plugin: " + "mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(src_box), + mailbox_get_last_error(src_box, NULL)); + return -1; + } + return 0; +} + +static int +mail_crypt_acl_user_create(struct mail_user *user, const char *dest_username, + struct mail_user **dest_user_r, + struct mail_storage_service_user **dest_service_user_r, + const char **error_r) +{ + const struct mail_storage_service_input *old_input; + struct mail_storage_service_input input; + struct mail_storage_service_ctx *service_ctx; + struct ioloop_context *cur_ioloop_ctx; + + int ret; + + i_assert(user->_service_user != NULL); + service_ctx = mail_storage_service_user_get_service_ctx(user->_service_user); + old_input = mail_storage_service_user_get_input(user->_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + + memset(&input, 0, sizeof(input)); + input.module = old_input->module; + input.service = old_input->service; + input.username = dest_username; + input.session_id_prefix = user->session_id; + input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS | + MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT; + + ret = mail_storage_service_lookup_next(service_ctx, &input, + dest_service_user_r, + dest_user_r, error_r); + + return ret; +} + +static int +mail_crypt_acl_update_private_key(struct mailbox *src_box, + struct mail_user *dest_user, bool set, + bool disallow_insecure, + const char **error_r) +{ + struct dcrypt_public_key *key = NULL; + struct dcrypt_private_key **keyp; + int ret = 0; + + if (!set) { + return mail_crypt_acl_unset_private_keys(src_box, + dest_user->username, + MAIL_ATTRIBUTE_TYPE_SHARED, + error_r); + } + + if (dest_user != NULL) { + /* get public key from target user */ + if ((ret = mail_crypt_user_get_public_key(dest_user, + &key, error_r)) <= 0) { + if (ret == 0 && disallow_insecure) { + *error_r = t_strdup_printf("User %s has no active public key", + dest_user->username); + return -1; + } else if (ret < 0) { + return -1; + } else if (ret == 0) { + /* perform insecure sharing */ + dest_user = NULL; + key = NULL; + } + } + } + + ARRAY_TYPE(dcrypt_private_key) keys; + t_array_init(&keys, 8); + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(src_box, 0); + + /* get private keys from box */ + if (mail_crypt_box_get_private_keys(t, &keys, error_r) < 0 || + mail_crypt_box_share_private_keys(t, key, + dest_user == NULL ? NULL : + dest_user->username, + &keys, error_r) < 0) + ret = -1; + + if (ret >= 0) { + array_foreach_modifiable(&keys, keyp) { + dcrypt_key_unref_private(keyp); + } + } + + if (mailbox_transaction_commit(&t) < 0) { + *error_r = mailbox_get_last_error(src_box, NULL); + ret = -1; + } + + return ret; +} + +static int mail_crypt_acl_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update) +{ + const char *error; + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(aclobj->backend->list); + const char *username; + struct mail_user *dest_user; + struct mail_storage_service_user *dest_service_user; + struct ioloop_context *cur_ioloop_ctx; + bool have_rights; + int ret = 0; + + if (mlist->acl_vprev.object_update(aclobj, update) < 0) + return -1; + + bool disallow_insecure = + mail_crypt_acl_secure_sharing_enabled(aclobj->backend->list->ns->user); + + const char *box_name = mailbox_list_get_vname(aclobj->backend->list, + aclobj->name); + struct mailbox *box = mailbox_alloc(aclobj->backend->list, box_name, 0); + + if (mailbox_open(box) < 0) { + i_error("mail-crypt-acl-plugin: " + "mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + return -1; + } + + switch (update->rights.id_type) { + case ACL_ID_USER: + /* setting rights for specific user: we can encrypt the + mailbox key for the user. */ + username = update->rights.identifier; + ret = mail_crypt_acl_has_user_read_right(aclobj, username, &error); + + if (ret < 0) { + i_error("mail-crypt-acl-plugin: " + "mail_crypt_acl_has_user_read_right(%s) failed: %s", + username, + error); + break; + } + + have_rights = ret > 0; + + ret = mail_crypt_acl_user_create(aclobj->backend->list->ns->user, + username, &dest_user, + &dest_service_user, &error); + + /* to make sure we get correct logging context */ + mail_storage_service_io_deactivate_user(dest_service_user); + mail_storage_service_io_activate_user( + aclobj->backend->list->ns->user->_service_user + ); + + if (ret <= 0) { + i_error("mail-crypt-acl-plugin: " + "Cannot initialize destination user %s: %s", + username, error); + } else { + i_assert(dest_user != NULL); + if ((ret = mail_crypt_acl_update_private_key(box, dest_user, + have_rights, + disallow_insecure, + &error)) < 0) { + i_error("mail-crypt-acl-plugin: " + "acl_update_private_key(%s, %s) failed: %s", + mailbox_get_vname(box), + username, + error); + } + } + + /* logging context swap again */ + mail_storage_service_io_deactivate_user( + aclobj->backend->list->ns->user->_service_user + ); + mail_storage_service_io_activate_user(dest_service_user); + + if (dest_user != NULL) + mail_user_unref(&dest_user); + if (dest_service_user != NULL) + mail_storage_service_user_free(&dest_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + mail_storage_service_io_activate_user( + aclobj->backend->list->ns->user->_service_user + ); + break; + case ACL_ID_OWNER: + /* we should be the one doing this? ignore */ + break; + case ACL_ID_ANYONE: + case ACL_ID_AUTHENTICATED: + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + if (disallow_insecure) { + i_error("mail-crypt-acl-plugin: " + "Secure key sharing is enabled -" + "Remove or set plugin { %s = no }", + MAIL_CRYPT_ACL_SECURE_SHARE_SETTING); + ret = -1; + break; + } + /* the mailbox key needs to be stored unencrypted. for groups + we could in theory use per-group encrypted keys, which the + users belonging to the group would able to decrypt with + their private key, but that becomes quite complicated. */ + if ((ret = mail_crypt_acl_has_nonuser_read_right(aclobj, &error)) < 0) { + i_error("mail-crypt-acl-plugin: %s", error); + } else if ((ret = mail_crypt_acl_update_private_key(box, + NULL, + TRUE, + disallow_insecure, + &error)) < 0) { + i_error("mail-crypt-acl-plugin: " + "acl_update_private_key(%s, %s) failed: %s", + mailbox_get_vname(box), + "", + error); + } + break; + case ACL_ID_TYPE_COUNT: + i_unreached(); + } + + mailbox_free(&box); + return ret; +} + +static void +mail_crypt_acl_mail_namespace_storage_added(struct mail_namespace *ns) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(ns->list); + struct acl_backend *backend; + + if (alist == NULL) + return; + + /* FIXME: this method works only if there's a single plugin doing it. + if there are ever multiple plugins hooking into ACL commands the + ACL core code would need some changing to make it work correctly. */ + backend = alist->rights.backend; + mlist->acl_vprev = backend->v; + backend->v.object_update = mail_crypt_acl_object_update; +} + +static void mail_crypt_acl_mailbox_list_deinit(struct mailbox_list *list) +{ + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(list); + + mlist->module_ctx.super.deinit(list); +} + +static void mail_crypt_acl_mailbox_list_created(struct mailbox_list *list) +{ + struct mailbox_list_vfuncs *v = list->vlast; + struct mail_crypt_acl_mailbox_list *mlist; + + mlist = p_new(list->pool, struct mail_crypt_acl_mailbox_list, 1); + mlist->module_ctx.super = *v; + list->vlast = &mlist->module_ctx.super; + v->deinit = mail_crypt_acl_mailbox_list_deinit; + + MODULE_CONTEXT_SET(list, mail_crypt_acl_mailbox_list_module, mlist); +} + +static struct mail_storage_hooks mail_crypt_acl_mail_storage_hooks = { + .mailbox_list_created = mail_crypt_acl_mailbox_list_created, + .mail_namespace_storage_added = mail_crypt_acl_mail_namespace_storage_added +}; + +void mail_crypt_acl_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &mail_crypt_acl_mail_storage_hooks); +} + +void mail_crypt_acl_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mail_crypt_acl_mail_storage_hooks); +} + +const char *mail_crypt_acl_plugin_dependencies[] = { "acl", NULL }; diff --git a/src/plugins/mail-crypt/mail-crypt-common.h b/src/plugins/mail-crypt/mail-crypt-common.h new file mode 100644 index 0000000000..57e5e2fda3 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-common.h @@ -0,0 +1,30 @@ +#ifndef MAIL_CRYPT_COMMON_H +#define MAIL_CRYPT_COMMON_H + +#include "dcrypt.h" + +#define MAIL_CRYPT_PW_CIPHER "aes-256-ctr" +#define MAIL_CRYPT_KEY_CIPHER "ecdh-aes-256-ctr" +#define MAIL_CRYPT_ENC_ALGORITHM "aes-256-gcm-sha256" +#define MAIL_CRYPT_KEY_ID_ALGORITHM "sha256" +#define MAIL_CRYPT_KEY_ATTRIBUTE_FORMAT DCRYPT_FORMAT_DOVECOT +#define MAIL_CRYPT_ACL_SECURE_SHARE_SETTING "mail_crypt_acl_require_secure_key_sharing" +#define MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY "mail_crypt_require_encrypted_user_key" +#define MAIL_CRYPT_HASH_BUF_SIZE 128 +#define MAIL_CRYPT_KEY_BUF_SIZE 1024 +#define ACTIVE_KEY_NAME "active" +#define PUBKEYS_PREFIX "pubkeys/" +#define PRIVKEYS_PREFIX "privkeys/" + +#define BOX_CRYPT_PREFIX MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"crypt/" +#define USER_CRYPT_PREFIX \ + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER \ + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"crypt/" + +#define MAIL_CRYPT_USERENV_PASSWORD "mail_crypt_private_password" +#define MAIL_CRYPT_USERENV_KEY "mail_crypt_private_key" +#define MAIL_CRYPT_USERENV_CURVE "mail_crypt_curve" + +ARRAY_DEFINE_TYPE(dcrypt_private_key, struct dcrypt_private_key*); + +#endif diff --git a/src/plugins/mail-crypt/mail-crypt-global-key.c b/src/plugins/mail-crypt/mail-crypt-global-key.c new file mode 100644 index 0000000000..f31c901174 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-global-key.c @@ -0,0 +1,172 @@ +/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hex-binary.h" +#include "base64.h" +#include "mail-user.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" + +int mail_crypt_load_global_public_key(const char *set_key, const char *key_data, + struct mail_crypt_global_keys *global_keys, + const char **error_r) +{ + const char *error; + enum dcrypt_key_format format; + enum dcrypt_key_kind kind; + if (!dcrypt_key_string_get_info(key_data, &format, NULL, + &kind, NULL, NULL, NULL, &error)) { + key_data = str_c(t_base64_decode_str(key_data)); + if (!dcrypt_key_string_get_info(key_data, &format, NULL, + &kind, NULL, NULL, NULL, &error)) { + *error_r = t_strdup_printf("%s: Couldn't parse public key: %s", + set_key, error); + return -1; + } + } + if (kind != DCRYPT_KEY_KIND_PUBLIC) { + *error_r = t_strdup_printf("%s: key is not public", set_key); + return -1; + } + if (!dcrypt_key_load_public(&global_keys->public_key, key_data, &error)) { + *error_r = t_strdup_printf("%s: Couldn't load public key: %s", + set_key, error); + return -1; + } + return 0; +} + +static int +mail_crypt_key_get_ids(struct dcrypt_private_key *key, + const char **key_id_r, const char **key_id_old_r, + const char **error_r) +{ + const char *error; + buffer_t *key_id; + + *key_id_r = NULL; + *key_id_old_r = NULL; + + /* new key ID */ + key_id = buffer_create_dynamic(pool_datastack_create(), + MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_private(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error)) { + *error_r = t_strdup_printf("Failed to get private key ID: %s", error); + return -1; + } + *key_id_r = binary_to_hex(key_id->data, key_id->used); + + buffer_set_used_size(key_id, 0); + + /* old key ID */ + if (dcrypt_key_type_private(key) != DCRYPT_KEY_EC) + return 0; + + if (!dcrypt_key_id_private_old(key, key_id, &error)) { + *error_r = t_strdup_printf("Failed to get private key old ID: %s", + error); + return -1; + } + *key_id_old_r = binary_to_hex(key_id->data, key_id->used); + return 0; +} + +int mail_crypt_load_global_private_key(const char *set_key, const char *key_data, + const char *set_pw, const char *key_password, + struct mail_crypt_global_keys *global_keys, + const char **error_r) +{ + enum dcrypt_key_format format; + enum dcrypt_key_kind kind; + enum dcrypt_key_encryption_type enc_type; + const char *error; + + if (!dcrypt_key_string_get_info(key_data, &format, NULL, &kind, + &enc_type, NULL, NULL, &error)) { + key_data = str_c(t_base64_decode_str(key_data)); + if (!dcrypt_key_string_get_info(key_data, &format, NULL, &kind, + &enc_type, NULL, NULL, &error)) { + *error_r = t_strdup_printf("%s: Couldn't parse private" + " key: %s", set_key, error); + return -1; + } + } + if (kind != DCRYPT_KEY_KIND_PRIVATE) { + *error_r = t_strdup_printf("%s: key is not private", set_key); + return -1; + } + + if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD) { + /* Fail here if password is not set since openssl will prompt + * for it otherwise */ + if (key_password == NULL) { + if (error_r != NULL) + *error_r = t_strdup_printf("%s: %s unset, no " + "password to decrypt the key", + set_key, set_pw); + return -1; + } + } + + struct dcrypt_private_key *key = NULL; + if (!dcrypt_key_load_private(&key, key_data, key_password, NULL, &error)) { + *error_r = t_strdup_printf("%s: Couldn't load private key: %s", + set_key, error); + return -1; + } + + const char *key_id, *key_id_old; + if (mail_crypt_key_get_ids(key, &key_id, &key_id_old, error_r) < 0) { + dcrypt_key_unref_private(&key); + return -1; + } + + struct mail_crypt_global_private_key *priv_key = + array_append_space(&global_keys->private_keys); + priv_key->key = key; + priv_key->key_id = i_strdup(key_id); + priv_key->key_id_old = i_strdup(key_id_old); + return 0; +} + +void mail_crypt_global_keys_init(struct mail_crypt_global_keys *global_keys_r) +{ + memset(global_keys_r, 0, sizeof(*global_keys_r)); + i_array_init(&global_keys_r->private_keys, 4); +} + +void mail_crypt_global_keys_free(struct mail_crypt_global_keys *global_keys) +{ + struct mail_crypt_global_private_key *priv_key; + + if (global_keys->public_key != NULL) + dcrypt_key_unref_public(&global_keys->public_key); + + if (!array_is_created(&global_keys->private_keys)) + return; + array_foreach_modifiable(&global_keys->private_keys, priv_key) { + dcrypt_key_unref_private(&priv_key->key); + i_free(priv_key->key_id); + i_free(priv_key->key_id_old); + } + array_free(&global_keys->private_keys); +} + +struct dcrypt_private_key * +mail_crypt_global_key_find(struct mail_crypt_global_keys *global_keys, + const char *pubkey_digest) +{ + const struct mail_crypt_global_private_key *priv_key; + + array_foreach(&global_keys->private_keys, priv_key) { + if (strcmp(priv_key->key_id, pubkey_digest) == 0) + return priv_key->key; + if (priv_key->key_id_old != NULL && + strcmp(priv_key->key_id_old, pubkey_digest) == 0) + return priv_key->key; + } + return NULL; +} diff --git a/src/plugins/mail-crypt/mail-crypt-global-key.h b/src/plugins/mail-crypt/mail-crypt-global-key.h new file mode 100644 index 0000000000..6f4679ac6d --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-global-key.h @@ -0,0 +1,38 @@ +#ifndef MAIL_CRYPT_GLOBAL_KEY_H +#define MAIL_CRYPT_GLOBAL_KEY_H + +struct mail_crypt_global_private_key { + struct dcrypt_private_key *key; + char *key_id, *key_id_old; +}; + +struct mail_crypt_global_keys { + struct dcrypt_public_key *public_key; + ARRAY(struct mail_crypt_global_private_key) private_keys; +}; + +struct mail_user; + +int mail_crypt_global_keys_load(struct mail_user *user, const char *set_prefix, + struct mail_crypt_global_keys *global_keys_r, + bool ignore_privkey_errors, + const char **error_r); +int mail_crypt_global_keys_load_pluginenv(const char *set_prefix, + struct mail_crypt_global_keys *global_keys_r, + const char **error_r); +void mail_crypt_global_keys_init(struct mail_crypt_global_keys *global_keys_r); +void mail_crypt_global_keys_free(struct mail_crypt_global_keys *global_keys); + +int mail_crypt_load_global_public_key(const char *set_key, const char *key_data, + struct mail_crypt_global_keys *global_keys, + const char **error_r); +int mail_crypt_load_global_private_key(const char *set_key, const char *key_data, + const char *set_pw, const char *key_password, + struct mail_crypt_global_keys *global_keys, + const char **error_r); + +struct dcrypt_private_key * +mail_crypt_global_key_find(struct mail_crypt_global_keys *global_keys, + const char *pubkey_digest); + +#endif diff --git a/src/plugins/mail-crypt/mail-crypt-key.c b/src/plugins/mail-crypt/mail-crypt-key.c new file mode 100644 index 0000000000..d673bbc4b7 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-key.c @@ -0,0 +1,1264 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "dict.h" +#include "array.h" +#include "var-expand.h" +#include "mail-storage.h" +#include "mailbox-attribute.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" +#include "mail-user.h" +#include "hex-binary.h" +#include "safe-memset.h" +#include "base64.h" +#include "sha2.h" + +struct mail_crypt_key_cache_entry { + struct mail_crypt_key_cache_entry *next; + + char *pubid; + /* this is lazily initialized */ + struct dcrypt_keypair pair; +}; + +static +int mail_crypt_get_key_cache(struct mail_crypt_key_cache_entry *cache, + const char *pubid, + struct dcrypt_private_key **privkey_r, + struct dcrypt_public_key **pubkey_r) +{ + for(struct mail_crypt_key_cache_entry *ent = cache; + ent != NULL; ent = ent->next) + { + if (strcmp(pubid, ent->pubid) == 0) { + if (privkey_r != NULL && ent->pair.priv != NULL) { + dcrypt_key_ref_private(ent->pair.priv); + *privkey_r = ent->pair.priv; + return 1; + } else if (pubkey_r != NULL && ent->pair.pub != NULL) { + dcrypt_key_ref_public(ent->pair.pub); + *pubkey_r = ent->pair.pub; + return 1; + } else if ((privkey_r == NULL && pubkey_r == NULL) || + (ent->pair.priv == NULL && + ent->pair.pub == NULL)) { + i_unreached(); + } + } + } + return 0; +} + +static +void mail_crypt_put_key_cache(struct mail_crypt_key_cache_entry **cache, + const char *pubid, + struct dcrypt_private_key *privkey, + struct dcrypt_public_key *pubkey) +{ + for(struct mail_crypt_key_cache_entry *ent = *cache; + ent != NULL; ent = ent->next) + { + if (strcmp(pubid, ent->pubid) == 0) { + if (privkey != NULL) { + if (ent->pair.priv == NULL) { + ent->pair.priv = privkey; + dcrypt_key_ref_private(ent->pair.priv); + } + } else if (pubkey != NULL) { + if (ent->pair.pub == NULL) { + ent->pair.pub = pubkey; + dcrypt_key_ref_public(ent->pair.pub); + } + } else + i_unreached(); + return; + } + } + + /* not found */ + struct mail_crypt_key_cache_entry *ent = + i_new(struct mail_crypt_key_cache_entry, 1); + ent->pubid = i_strdup(pubid); + ent->pair.priv = privkey; + ent->pair.pub = pubkey; + if (ent->pair.priv != NULL) + dcrypt_key_ref_private(ent->pair.priv); + if (ent->pair.pub != NULL) + dcrypt_key_ref_public(ent->pair.pub); + + if (*cache == NULL) { + *cache = ent; + } else { + ent->next = *cache; + *cache = ent; + } +} + +void mail_crypt_key_cache_destroy(struct mail_crypt_key_cache_entry **cache) +{ + struct mail_crypt_key_cache_entry *next, *cur = *cache; + + *cache = NULL; + + while(cur != NULL) { + next = cur->next; + i_free(cur->pubid); + if (cur->pair.priv != NULL) + dcrypt_key_unref_private(&cur->pair.priv); + if (cur->pair.pub != NULL) + dcrypt_key_unref_public(&cur->pair.pub); + i_free(cur); + cur = next; + } +} + +int mail_crypt_private_key_id_match(struct dcrypt_private_key *key, + const char *pubid, const char **error_r) +{ + i_assert(key != NULL); + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_private(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, + error_r)) + return -1; + const char *hash = binary_to_hex(key_id->data, key_id->used); + if (strcmp(pubid, hash) == 0) return 1; + + buffer_set_used_size(key_id, 0); + if (!dcrypt_key_id_private_old(key, key_id, error_r)) { + return -1; + } + hash = binary_to_hex(key_id->data, key_id->used); + + if (strcmp(pubid, hash) != 0) { + *error_r = t_strdup_printf("Key %s does not match given ID %s", + hash, pubid); + return 0; + } + return 1; +} + +int mail_crypt_public_key_id_match(struct dcrypt_public_key *key, + const char *pubid, const char **error_r) +{ + i_assert(key != NULL); + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_public(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, + error_r)) + return -1; + const char *hash = binary_to_hex(key_id->data, key_id->used); + if (strcmp(pubid, hash) == 0) return 1; + + buffer_set_used_size(key_id, 0); + if (!dcrypt_key_id_public_old(key, key_id, error_r)) { + return -1; + } + hash = binary_to_hex(key_id->data, key_id->used); + + if (strcmp(pubid, hash) != 0) { + *error_r = t_strdup_printf("Key %s does not match given ID %s", + hash, pubid); + return 0; + } + return 1; +} + +static +int mail_crypt_env_get_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mail_crypt_global_keys global_keys; + int ret = 0; + if (mail_crypt_global_keys_load(user, "mail_crypt", &global_keys, + TRUE, error_r) < 0) { + mail_crypt_global_keys_free(&global_keys); + return -1; + } + + /* see if we got a key */ + struct dcrypt_private_key *key = + mail_crypt_global_key_find(&global_keys, pubid); + + if (key != NULL) { + dcrypt_key_ref_private(key); + *key_r = key; + ret = 1; + } + + mail_crypt_global_keys_free(&global_keys); + + return ret; +} + +static +const char *mail_crypt_get_key_path(bool user_key, bool public, const char *pubid) +{ + const char *ret = t_strdup_printf("%s%s%s", + user_key ? USER_CRYPT_PREFIX : + BOX_CRYPT_PREFIX, + public ? PUBKEYS_PREFIX : + PRIVKEYS_PREFIX, + pubid); + return ret; +} + +static +int mail_crypt_decrypt_private_key(struct mailbox *box, const char *pubid, + const char *data, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + enum dcrypt_key_kind key_kind; + enum dcrypt_key_encryption_type enc_type; + const char *enc_hash = NULL, *key_hash = NULL, *pw = NULL; + struct dcrypt_private_key *key = NULL, *dec_key = NULL; + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + int ret = 0; + + i_assert(pubid != NULL); + i_assert(data != NULL); + + /* see what the key needs for decrypting */ + if (!dcrypt_key_string_get_info(data, NULL, NULL, &key_kind, + &enc_type, &enc_hash, &key_hash, error_r)) { + return -1; + } + + if (key_kind != DCRYPT_KEY_KIND_PRIVATE) { + *error_r = t_strdup_printf("Cannot use key %s: " + "Expected private key, got public key", + pubid); + return -1; + } + + if (key_hash != NULL && strcmp(key_hash, pubid) != 0) { + *error_r = t_strdup_printf("Cannot use key %s: " + "Incorrect key hash %s stored", + pubid, + key_hash); + return -1; + } + + /* see if it needs decrypting */ + if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE) { + /* no key or password */ + } else if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD) { + pw = mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD); + if (pw == NULL) { + *error_r = t_strdup_printf("Cannot decrypt key %s: " + "Password not available", + pubid); + return -1; + } + } else if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_KEY) { + if ((ret = mail_crypt_user_get_private_key(user, enc_hash, + &dec_key, error_r)) <= 0) { + /* last resort, look at environment */ + if (ret == 0 && (ret = mail_crypt_env_get_private_key(user, enc_hash, + &dec_key, error_r)) == 0) { + *error_r = t_strdup_printf("Cannot decrypt key %s: " + "Private key %s not available:", + pubid, enc_hash); + return -1; + } else if (ret < 0) { + *error_r = t_strdup_printf("Cannot decrypt key %s: %s", + pubid, *error_r); + return ret; + } + } + } + + bool res = dcrypt_key_load_private(&key, data, pw, dec_key, error_r); + + if (dec_key != NULL) + dcrypt_key_unref_private(&dec_key); + + if (!res) + return -1; + + if (mail_crypt_private_key_id_match(key, pubid, error_r) <= 0) { + if (key != NULL) + dcrypt_key_unref_private(&key); + return -1; + } + + i_assert(key != NULL); + + *key_r = key; + + return 1; +} + +int mail_crypt_get_private_key(struct mailbox_transaction_context *t, + const char *pubid, + bool user_key, bool shared, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mailbox *box = mailbox_transaction_get_mailbox(t); + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + + /* check cache */ + if (mail_crypt_get_key_cache(muser->key_cache, pubid, key_r, NULL) > 0) { + return 1; + } + + struct mail_attribute_value value; + struct dcrypt_private_key *key; + int ret; + const char *attr_name = mail_crypt_get_key_path(user_key, FALSE, pubid); + + if ((ret = mailbox_attribute_get(t, + shared ? MAIL_ATTRIBUTE_TYPE_SHARED : + MAIL_ATTRIBUTE_TYPE_PRIVATE, + attr_name, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s%s) failed: %s", + mailbox_get_vname(box), + shared ? "/shared/" : + "/priv/", + attr_name, + mailbox_get_last_error(box, NULL)); + } + return ret; + } + + if ((ret = mail_crypt_decrypt_private_key(box, pubid, value.value, + &key, error_r)) <= 0) + return ret; + + i_assert(key != NULL); + + mail_crypt_put_key_cache(&muser->key_cache, pubid, key, NULL); + + *key_r = key; + + return 1; +} + +int mail_crypt_user_get_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + struct mail_attribute_value value; + int ret; + + /* try retrieve currently active user key */ + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("mailbox_open(%s) failed: %s", + "INBOX", + mailbox_get_last_error(box, NULL)); + return -1; + } + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0); + + if (pubid == NULL) { + if ((ret = mailbox_attribute_get(t, MAIL_ATTRIBUTE_TYPE_SHARED, + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_error(box, NULL)); + } + } else { + pubid = value.value; + ret = 1; + } + } else + ret = 1; + + /* try to open key */ + if (ret > 0) + ret = mail_crypt_get_private_key(t, pubid, TRUE, FALSE, + key_r, error_r); + + (void)mailbox_transaction_commit(&t); + mailbox_free(&box); + return ret; +} + +int mail_crypt_box_get_private_key(struct mailbox_transaction_context *t, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mailbox *box = mailbox_transaction_get_mailbox(t); + struct mail_attribute_value value; + int ret; + /* get active key */ + if ((ret = mailbox_attribute_get(t, MAIL_ATTRIBUTE_TYPE_SHARED, + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_error(box, NULL)); + } + return ret; + } + + return mail_crypt_get_private_key(t, value.value, + FALSE, FALSE, + key_r, error_r); +} + +static +int mail_crypt_set_private_key(struct mailbox_transaction_context *t, + bool user_key, bool shared, const char *pubid, + struct dcrypt_public_key *enc_key, + struct dcrypt_private_key *key, + const char **error_r) +{ + /* folder keys must be encrypted with some other key, + unless they are shared keys */ + i_assert(user_key || shared || enc_key != NULL); + + buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE); + const char *pw = NULL; + const char *algo = NULL; + struct mail_user *user = mail_storage_get_user( + mailbox_get_storage( + mailbox_transaction_get_mailbox(t))); + const char *attr_name = mail_crypt_get_key_path(user_key, FALSE, pubid); + struct mail_attribute_value value; + int ret; + + if (enc_key != NULL) { + algo = MAIL_CRYPT_KEY_CIPHER; + } else if (user_key && + (pw = mail_user_plugin_getenv(user,MAIL_CRYPT_USERENV_PASSWORD)) + != NULL) { + algo = MAIL_CRYPT_PW_CIPHER; + } + + /* export key */ + if (!dcrypt_key_store_private(key, DCRYPT_FORMAT_DOVECOT, algo, data, + pw, enc_key, error_r)) { + return -1; + } + + /* store it */ + value.value_stream = NULL; + value.value = str_c(data); + value.last_change = 0; + + if ((ret = mailbox_attribute_set(t, + shared ? MAIL_ATTRIBUTE_TYPE_SHARED : + MAIL_ATTRIBUTE_TYPE_PRIVATE, + attr_name, + &value)) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s/%s) failed: %s", + mailbox_get_vname(mailbox_transaction_get_mailbox(t)), + shared ? "/shared" : "/priv", + attr_name, + mailbox_get_last_error( + mailbox_transaction_get_mailbox(t), NULL)); + } + + safe_memset(buffer_get_modifiable_data(data, NULL), 0, data->used); + + return ret; +} + +int mail_crypt_user_set_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key *key, + const char **error_r) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + struct dcrypt_private_key *env_key = NULL; + struct dcrypt_public_key *enc_key = NULL; + struct mailbox_transaction_context *t; + int ret; + + + if ((ret = mail_crypt_env_get_private_key(user, NULL, &env_key, + error_r)) < 0) { + return -1; + } else if (ret > 0) { + dcrypt_key_convert_private_to_public(env_key, &enc_key); + dcrypt_key_unref_private(&env_key); + } + + if (mail_user_plugin_getenv(user, MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY) != NULL && + mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD) == NULL && + mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_KEY) == NULL) + { + *error_r = MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY " set, cannot " + "generate user keypair without password or key"; + return -1; + } + + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("mailbox_open(%s) failed: %s", + "INBOX", + mailbox_get_last_error(box, NULL)); + return -1; + } + + t = mailbox_transaction_begin(box, 0); + + if ((ret = mail_crypt_set_private_key(t, TRUE, FALSE, pubid, enc_key, key, + error_r)) < 0) { + mailbox_transaction_rollback(&t); + } else if ((ret = mailbox_transaction_commit(&t)) < 0) { + *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + } + + mailbox_free(&box); + + return ret; +} + +int mail_crypt_box_set_private_key(struct mailbox *box, const char *pubid, + struct dcrypt_private_key *key, + struct dcrypt_public_key *user_key, + const char **error_r) +{ + int ret; + struct mailbox_transaction_context *t; + + t = mailbox_transaction_begin(box, 0); + if ((ret = mail_crypt_set_private_key(t, FALSE, FALSE, pubid, user_key, + key, error_r)) < 0) { + mailbox_transaction_rollback(&t); + } else if ((ret = mailbox_transaction_commit(&t)) < 0) { + *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + } + + return ret; +} + +static +int mail_crypt_get_public_key(struct mailbox_transaction_context *t, + const char *pubid, + bool user_key, struct dcrypt_public_key **key_r, + const char **error_r) +{ + struct mailbox *box = mailbox_transaction_get_mailbox(t); + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + + /* check cache */ + if (mail_crypt_get_key_cache(muser->key_cache, pubid, NULL, key_r) > 0) { + return 1; + } + + enum dcrypt_key_kind key_kind; + const char *key_hash = NULL; + struct dcrypt_public_key *key; + struct mail_attribute_value value; + int ret; + const char *attr_name = mail_crypt_get_key_path(user_key, TRUE, pubid); + + if ((ret = mailbox_attribute_get(t, + MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s", + mailbox_get_vname(box), + attr_name, + mailbox_get_last_error(box, NULL)); + } + return ret; + } + + if (!dcrypt_key_string_get_info(value.value, NULL, NULL, &key_kind, + NULL, NULL, &key_hash, error_r)) { + return -1; + } + + if (key_kind != DCRYPT_KEY_KIND_PUBLIC) { + *error_r = t_strdup_printf("Cannot use key %s: " + "Expected public key, got private key", + pubid); + return -1; + } + + if (key_hash != NULL && strcmp(key_hash, pubid) != 0) { + *error_r = t_strdup_printf("Cannot use key %s: " + "Incorrect key hash %s stored", + pubid, key_hash); + return -1; + } + + /* load the key */ + if (!dcrypt_key_load_public(&key, value.value, error_r)) { + return -1; + } + + if (pubid != NULL && + mail_crypt_public_key_id_match(key, pubid, error_r) <= 0) { + dcrypt_key_unref_public(&key); + return -1; + } + + mail_crypt_put_key_cache(&muser->key_cache, pubid, NULL, key); + + *key_r = key; + + return 1; +} + +int mail_crypt_user_get_public_key(struct mail_user *user, + struct dcrypt_public_key **key_r, + const char **error_r) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + struct mail_attribute_value value; + int ret; + + /* try retrieve currently active user key */ + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("mailbox_open(%s) failed: %s", + "INBOX", + mailbox_get_last_error(box, NULL)); + return -1; + } + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0); + + if ((ret = mailbox_attribute_get(t, MAIL_ATTRIBUTE_TYPE_SHARED, + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_error(box, NULL)); + } + } else { + ret = mail_crypt_get_public_key(t, value.value, TRUE, key_r, error_r); + } + + (void)mailbox_transaction_commit(&t); + mailbox_free(&box); + return ret; +} + +int mail_crypt_box_get_public_key(struct mailbox_transaction_context *t, + struct dcrypt_public_key **key_r, + const char **error_r) +{ + struct mailbox *box = mailbox_transaction_get_mailbox(t); + struct mail_attribute_value value; + int ret; + + if ((ret = mailbox_attribute_get(t, MAIL_ATTRIBUTE_TYPE_SHARED, + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_error(box, NULL)); + } + return ret; + } + return mail_crypt_get_public_key(t, value.value, FALSE, key_r, error_r); +} + +static +int mail_crypt_set_public_key(struct mailbox_transaction_context *t, + bool user_key, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r) +{ + buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE); + const char *attr_name = mail_crypt_get_key_path(user_key, TRUE, pubid); + struct mail_attribute_value value; + + /* export key */ + if (!dcrypt_key_store_public(key, DCRYPT_FORMAT_DOVECOT, data, + error_r)) { + return -1; + } + + /* store it */ + value.value_stream = NULL; + value.value = str_c(data); + value.last_change = 0; + + if (mailbox_attribute_set(t, + MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name, + &value) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s/%s) failed: %s", + mailbox_get_vname(mailbox_transaction_get_mailbox(t)), + "/shared", + attr_name, + mailbox_get_last_error( + mailbox_transaction_get_mailbox(t), NULL)); + return -1; + } + + return 0; +} + +int mail_crypt_user_set_public_key(struct mail_user *user, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + struct mailbox_transaction_context *t; + struct mail_attribute_value value; + int ret; + + /* try retrieve currently active user key */ + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("mailbox_open(%s) failed: %s", + "INBOX", + mailbox_get_last_error(box, NULL)); + return -1; + } + + t = mailbox_transaction_begin(box, 0); + + if ((ret = mail_crypt_set_public_key(t, TRUE, pubid, key, + error_r)) == 0) { + value.value_stream = NULL; + value.value = pubid; + value.last_change = 0; + + if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED, + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_error(box, NULL)); + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + ret = -1; + } + + mailbox_free(&box); + + return ret; +} + +int mail_crypt_box_set_public_key(struct mailbox *box, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r) +{ + int ret; + struct mailbox_transaction_context *t; + struct mail_attribute_value value; + + t = mailbox_transaction_begin(box, 0); + if ((ret = mail_crypt_set_public_key(t, FALSE, pubid, key, + error_r)) == 0) { + value.value_stream = NULL; + value.value = pubid; + value.last_change = 0; + + if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED, + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_error(box, NULL)); + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + ret = -1; + } + + return ret; + +} + +static +int mail_crypt_user_set_keys(struct mail_user *user, + const char *pubid, + struct dcrypt_private_key *privkey, + struct dcrypt_public_key *pubkey, + const char **error_r) +{ + if (mail_crypt_user_set_private_key(user, pubid, privkey, error_r) < 0) + return -1; + if (mail_crypt_user_set_public_key(user, pubid, pubkey, error_r) < 0) + return -1; + return 0; +} + +static +int mail_crypt_box_set_keys(struct mailbox *box, + const char *pubid, + struct dcrypt_private_key *privkey, + struct dcrypt_public_key *user_key, + struct dcrypt_public_key *pubkey, + const char **error_r) +{ + if (mail_crypt_box_set_private_key(box, pubid, privkey, user_key, + error_r) < 0) + return -1; + if (mail_crypt_box_set_public_key(box, pubid, pubkey, error_r) < 0) + return -1; + return 0; +} + +int mail_crypt_box_get_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mailbox *box = mailbox_transaction_get_mailbox(t); + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + + struct dcrypt_private_key *key = NULL; + struct mail_attribute_value value; + int ret; + + /* check cache */ + if (mail_crypt_get_key_cache(muser->key_cache, pubid, key_r, NULL) > 0) { + return 1; + } + + const char *hexname = + binary_to_hex((const unsigned char*)user->username, + strlen(user->username)); + + const char *attr_name = t_strdup_printf(BOX_CRYPT_PREFIX + PRIVKEYS_PREFIX"%s/%s", + hexname, + pubid); + + if ((ret = mailbox_attribute_get(t, + MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s", + mailbox_get_vname(box), + attr_name, + mailbox_get_last_error(box, NULL)); + return ret; + } + return mail_crypt_get_private_key(t, pubid, FALSE, TRUE, key_r, + error_r); + } else { + if ((ret = mail_crypt_decrypt_private_key(box, pubid, value.value, + &key, error_r)) <= 0) + return ret; + } + + mail_crypt_put_key_cache(&muser->key_cache, pubid, key, NULL); + + *key_r = key; + + return 1; +} + +int mail_crypt_box_set_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + struct dcrypt_private_key *privkey, + const char *target_uid, + struct dcrypt_public_key *user_key, + const char **error_r) +{ + struct mail_attribute_value value; + buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE); + int ret; + const char *attr_name; + const char *algo = NULL; + + i_assert(target_uid == NULL || user_key != NULL); + + if (target_uid != NULL) { + /* hash target UID */ + algo = MAIL_CRYPT_KEY_CIPHER; + const char *hexname = + binary_to_hex((const unsigned char*)target_uid, + strlen(target_uid)); + attr_name = t_strdup_printf(BOX_CRYPT_PREFIX + PRIVKEYS_PREFIX"%s/%s", + hexname, + pubid); + } else { + attr_name = t_strdup_printf(BOX_CRYPT_PREFIX + PRIVKEYS_PREFIX"%s", + pubid); + } + + if (!dcrypt_key_store_private(privkey, DCRYPT_FORMAT_DOVECOT, + algo, data, + NULL, user_key, error_r)) { + return -1; + } + + value.value_stream = NULL; + value.value = str_c(data); + value.last_change = 0; + + if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name, &value)) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s", + mailbox_get_vname( + mailbox_transaction_get_mailbox(t)), + attr_name, + mailbox_get_last_error( + mailbox_transaction_get_mailbox(t), + NULL)); + } + + safe_memset(buffer_get_modifiable_data(data, NULL), 0, data->used); + + return ret; +} + +int mail_crypt_box_unset_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + const char *target_uid, + const char **error_r) +{ + int ret; + + const char *hexname = + binary_to_hex((const unsigned char*)target_uid, + strlen(target_uid)); + + const char *attr_name = t_strdup_printf(BOX_CRYPT_PREFIX + PRIVKEYS_PREFIX"%s/%s", + hexname, + pubid); + + if ((ret = mailbox_attribute_unset(t, MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_unset(%s, " + " /shared/%s): failed: %s", + mailbox_get_vname( + mailbox_transaction_get_mailbox(t)), + attr_name, + mailbox_get_last_error( + mailbox_transaction_get_mailbox(t), + NULL)); + } + } + + return ret; +} + +static +int mail_crypt_generate_keypair(const char *curve, + struct dcrypt_keypair *pair_r, + const char **pubid_r, + const char **error_r) +{ + if (curve == NULL) { + *error_r = MAIL_CRYPT_USERENV_CURVE " not set, cannot generate EC key"; + return -1; + } + + if (!dcrypt_keypair_generate(pair_r, DCRYPT_KEY_EC, 0, curve, error_r)) { + return -1; + } + + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_public(pair_r->pub, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, + error_r)) { + dcrypt_keypair_unref(pair_r); + return -1; + } + + *pubid_r = binary_to_hex(key_id->data, key_id->used); + + return 0; +}; + +int mail_crypt_user_generate_keypair(struct mail_user *user, + struct dcrypt_keypair *pair, + const char **pubid_r, + const char **error_r) +{ + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + const char *curve = mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_CURVE); + + if (mail_crypt_generate_keypair(curve, pair, pubid_r, error_r) < 0) { + return -1; + } + + if (mail_crypt_user_set_keys(user, *pubid_r, + pair->priv, pair->pub, error_r) < 0) { + dcrypt_keypair_unref(pair); + return -1; + } + + mail_crypt_put_key_cache(&muser->key_cache, *pubid_r, pair->priv, pair->pub); + + return 0; +} + +int mail_crypt_box_generate_keypair(struct mailbox *box, + struct dcrypt_keypair *pair, + struct dcrypt_public_key *user_key, + const char **pubid_r, + const char **error_r) +{ + int ret; + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + const char *curve = mail_user_plugin_getenv(user, + MAIL_CRYPT_USERENV_CURVE); + + if (user_key == NULL) { + if ((ret = mail_crypt_user_get_public_key(user, + &user_key, + error_r)) <= 0) { + if (ret < 0) + return ret; + /* generate keypair */ + struct dcrypt_keypair user_pair; + const char *user_pubid; + if (mail_crypt_user_generate_keypair(user, &user_pair, + &user_pubid, + error_r) < 0) { + return -1; + } + + mail_crypt_put_key_cache(&muser->key_cache, user_pubid, + user_pair.priv, user_pair.pub); + + user_key = user_pair.pub; + dcrypt_key_unref_private(&user_pair.priv); + } + } else { + dcrypt_key_ref_public(user_key); + } + + ret = 0; + + if (mail_crypt_generate_keypair(curve, pair, pubid_r, error_r) < 0) + ret = -1; + if (ret == 0 && + mail_crypt_box_set_keys(box, *pubid_r, + pair->priv, user_key, pair->pub, + error_r) < 0) { + dcrypt_keypair_unref(pair); + ret = -1; + } + + mail_crypt_put_key_cache(&muser->key_cache, *pubid_r, pair->priv, pair->pub); + + dcrypt_key_unref_public(&user_key); + + return ret; +} + +int mail_crypt_box_get_pvt_digests(struct mailbox *box, pool_t pool, + enum mail_attribute_type type, + ARRAY_TYPE(const_string) *digests, + const char **error_r) +{ + struct mailbox_attribute_iter *iter; + const char *key; + int ret; + + iter = mailbox_attribute_iter_init(box, type, + BOX_CRYPT_PREFIX PRIVKEYS_PREFIX); + while ((key = mailbox_attribute_iter_next(iter)) != NULL) { + key = p_strdup(pool, key); + array_append(digests, &key, 1); + } + ret = mailbox_attribute_iter_deinit(&iter); + if (ret < 0) + *error_r = mailbox_get_last_error(box, NULL); + return ret; +} + +int mail_crypt_box_get_private_keys(struct mailbox_transaction_context *t, + ARRAY_TYPE(dcrypt_private_key) *keys_r, + const char **error_r) +{ + struct mailbox *box = mailbox_transaction_get_mailbox(t); + struct mailbox_attribute_iter *iter; + iter = mailbox_attribute_iter_init(box, MAIL_ATTRIBUTE_TYPE_PRIVATE, + BOX_CRYPT_PREFIX PRIVKEYS_PREFIX); + const char *id; + int ret; + + while ((id = mailbox_attribute_iter_next(iter)) != NULL) { + struct dcrypt_private_key *key = NULL; + if ((ret = mail_crypt_get_private_key(t, id, FALSE, FALSE, + &key, error_r)) < 0) { + (void)mailbox_attribute_iter_deinit(&iter); + return -1; + } else if (ret > 0) + array_append(keys_r, &key, 1); + } + + ret = mailbox_attribute_iter_deinit(&iter); + if (ret < 0) + *error_r = mailbox_get_last_error(box, NULL); + return ret; +} + +int mail_crypt_box_share_private_keys(struct mailbox_transaction_context *t, + struct dcrypt_public_key *dest_pub_key, + const char *dest_user, + const ARRAY_TYPE(dcrypt_private_key) *priv_keys, + const char **error_r) +{ + i_assert(dest_user == NULL || dest_pub_key != NULL); + + struct dcrypt_private_key *const *priv_keyp, *priv_key; + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + int ret = 0; + + array_foreach(priv_keys, priv_keyp) { + priv_key = *priv_keyp; + ret = -1; + if (!dcrypt_key_id_private(priv_key, MAIL_CRYPT_KEY_ID_ALGORITHM, + key_id, error_r) || + (ret = mail_crypt_box_set_shared_key(t, + binary_to_hex(key_id->data, + key_id->used), + priv_key, dest_user, + dest_pub_key, error_r)) < 0) + break; + } + + return ret; +} + +int +mail_crypt_user_get_or_gen_public_key(struct mail_user *user, + struct dcrypt_public_key **pub_r, + const char **error_r) +{ + i_assert(user != NULL); + i_assert(pub_r != NULL); + i_assert(error_r != NULL); + + int ret; + if ((ret = mail_crypt_user_get_public_key(user, pub_r, error_r)) == 0) { + struct dcrypt_keypair pair; + const char *pubid = NULL; + if (mail_crypt_user_generate_keypair(user, &pair, + &pubid, error_r) < 0) { + return -1; + } + *pub_r = pair.pub; + dcrypt_key_unref_private(&pair.priv); + } else + return ret; + return 0; +} + +int +mail_crypt_box_get_or_gen_public_key(struct mailbox_transaction_context *t, + struct dcrypt_public_key **pub_r, + const char **error_r) +{ + struct mailbox *box = mailbox_transaction_get_mailbox(t); + i_assert(pub_r != NULL); + i_assert(error_r != NULL); + + struct mail_user *user = + mail_storage_get_user(mailbox_get_storage(box)); + int ret; + if ((ret = mail_crypt_box_get_public_key(t, pub_r, error_r)) == 0) { + struct dcrypt_public_key *user_key; + if (mail_crypt_user_get_or_gen_public_key(user, &user_key, + error_r) < 0) { + return -1; + } + + struct dcrypt_keypair pair; + const char *pubid = NULL; + if (mail_crypt_box_generate_keypair(box, &pair, user_key, + &pubid, error_r) < 0) { + return -1; + } + *pub_r = pair.pub; + dcrypt_key_unref_public(&user_key); + dcrypt_key_unref_private(&pair.priv); + } else + return ret; + return 0; +} + +bool mail_crypt_acl_secure_sharing_enabled(struct mail_user *user) +{ + const char *env = + mail_user_plugin_getenv(user, MAIL_CRYPT_ACL_SECURE_SHARE_SETTING); + + /* disabled by default */ + bool ret = FALSE; + + if (env != NULL) { + /* enable unless specifically + requested not to */ + ret = TRUE; + switch (env[0]) { + case 'n': + case 'N': + case '0': + case 'f': + case 'F': + ret = FALSE; + } + } + + return ret; +} + +static const struct mailbox_attribute_internal mailbox_internal_attributes[] = { + { .type = MAIL_ATTRIBUTE_TYPE_PRIVATE, + .key = BOX_CRYPT_PREFIX, + .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN + }, + { .type = MAIL_ATTRIBUTE_TYPE_SHARED, + .key = BOX_CRYPT_PREFIX, + .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN + }, + { .type = MAIL_ATTRIBUTE_TYPE_PRIVATE, + .key = USER_CRYPT_PREFIX, + .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN + }, + { .type = MAIL_ATTRIBUTE_TYPE_SHARED, + .key = USER_CRYPT_PREFIX, + .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN + } +}; + +void mail_crypt_key_register_mailbox_internal_attributes(void) +{ + mailbox_attribute_register_internals(mailbox_internal_attributes, + N_ELEMENTS(mailbox_internal_attributes)); +} diff --git a/src/plugins/mail-crypt/mail-crypt-key.h b/src/plugins/mail-crypt/mail-crypt-key.h new file mode 100644 index 0000000000..263c439e06 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-key.h @@ -0,0 +1,120 @@ +#ifndef MAIL_CRYPT_KEY +#define MAIL_CRYPT_KEY + +#include "mail-crypt-common.h" +#include "mail-crypt-global-key.h" +#include "mail-storage.h" + +/* + For mailboxes: + + shared//.../crypt/active = digest for the active public key + that is used for encrypting new emails + shared//.../crypt/pubkeys/ = + private//.../crypt/privkeys/ = + + Similarly for users: + + shared//.../crypt/active = digest for the active public key that + is used for encrypting new folder keys + shared//.../crypt/pubkeys/ = + private//.../crypt/privkeys/ = +*/ + +struct mail_crypt_key_cache_entry; + +/** + * key cache management functions + */ +void mail_crypt_key_cache_destroy(struct mail_crypt_key_cache_entry **cache); +void mail_crypt_key_register_mailbox_internal_attributes(void); + +/* returns -1 on error, 0 not found, 1 = found */ +int mail_crypt_get_private_key(struct mailbox_transaction_context *t, + const char *pubid, + bool user_key, bool shared, + struct dcrypt_private_key **key_r, + const char **error_r); +int mail_crypt_user_get_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r); +int mail_crypt_box_get_private_key(struct mailbox_transaction_context *t, + struct dcrypt_private_key **key_r, + const char **error_r); +int mail_crypt_box_get_private_keys(struct mailbox_transaction_context *t, + ARRAY_TYPE(dcrypt_private_key) *keys_r, + const char **error_r); +int mail_crypt_user_get_public_key(struct mail_user *user, + struct dcrypt_public_key **key_r, + const char **error_r); +int mail_crypt_box_get_public_key(struct mailbox_transaction_context *t, + struct dcrypt_public_key **key_r, + const char **error_r); +int mail_crypt_box_get_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r); +/* returns -1 on error, 0 no match , 1 = match */ +int mail_crypt_private_key_id_match(struct dcrypt_private_key *key, + const char *pubid, const char **error_r); +int mail_crypt_public_key_id_match(struct dcrypt_public_key *key, + const char *pubid, const char **error_r); +/* returns -1 on error, 0 = ok */ +int mail_crypt_user_set_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key *key, + const char **error_r); +int mail_crypt_box_set_private_key(struct mailbox *box, const char *pubid, + struct dcrypt_private_key *key, + struct dcrypt_public_key *user_key, + const char **error_r); +int mail_crypt_user_set_public_key(struct mail_user *user, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r); +int mail_crypt_box_set_public_key(struct mailbox *box, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r); +int mail_crypt_user_generate_keypair(struct mail_user *user, + struct dcrypt_keypair *pair, + const char **pubid_r, + const char **error_r); +int mail_crypt_box_generate_keypair(struct mailbox *box, + struct dcrypt_keypair *pair, + struct dcrypt_public_key *user_key, + const char **pubid_r, + const char **error_r); +/* returns -1 on error, 0 = ok */ +int mail_crypt_box_set_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + struct dcrypt_private_key *privkey, + const char *target_uid, + struct dcrypt_public_key *user_key, + const char **error_r); +int mail_crypt_box_unset_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + const char *target_uid, + const char **error_r); +int mail_crypt_box_share_private_keys(struct mailbox_transaction_context *t, + struct dcrypt_public_key *dest_pub_key, + const char *dest_user, + const ARRAY_TYPE(dcrypt_private_key) *priv_keys, + const char **error_r); +/* returns -1 on error, 0 = ok + these will also attempt to generate a keypair +*/ +int mail_crypt_user_get_or_gen_public_key(struct mail_user *user, + struct dcrypt_public_key **pub_key_r, + const char **error_r); +int mail_crypt_box_get_or_gen_public_key(struct mailbox_transaction_context *t, + struct dcrypt_public_key **pub_key_r, + const char **error_r); + +/* Lookup all private keys' digests. Returns 0 if ok, -1 on error. */ +int mail_crypt_box_get_pvt_digests(struct mailbox *box, pool_t pool, + enum mail_attribute_type type, + ARRAY_TYPE(const_string) *digests, + const char **error_r); + +/* is secure sharing enabled */ +bool mail_crypt_acl_secure_sharing_enabled(struct mail_user *user); + +#endif diff --git a/src/plugins/mail-crypt/mail-crypt-plugin.c b/src/plugins/mail-crypt/mail-crypt-plugin.c new file mode 100644 index 0000000000..d8eacc5125 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-plugin.c @@ -0,0 +1,466 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ + +/* FIXME: cache handling could be useful to move to Dovecot core, so that if + we're using this plugin together with zlib plugin there would be just one + cache. */ + +#include "lib.h" +#include "ioloop.h" +#include "randgen.h" +#include "module-dir.h" +#include "str.h" +#include "safe-mkstemp.h" +#include "istream.h" +#include "istream-decrypt.h" +#include "istream-seekable.h" +#include "ostream.h" +#include "ostream-encrypt.h" +#include "mail-user.h" +#include "mail-copy.h" +#include "index-storage.h" +#include "index-mail.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" +#include "sha2.h" +#include "dcrypt-iostream.h" +#include "hex-binary.h" + +struct mail_crypt_mailbox { + union mailbox_module_context module_ctx; + struct dcrypt_public_key *pub_key; +}; + +const char *mail_crypt_plugin_version = DOVECOT_ABI_VERSION; + +#define MAIL_CRYPT_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT(obj, mail_crypt_mail_module) +#define MAIL_CRYPT_CONTEXT(obj) \ + MODULE_CONTEXT(obj, mail_crypt_storage_module) +#define MAIL_CRYPT_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, mail_crypt_user_module) + +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_user_module, + &mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_mail_module, + &mail_module_register); + +struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user) +{ + return MAIL_CRYPT_USER_CONTEXT(user); +} + +static bool mail_crypt_is_stream_encrypted(struct istream *input) +{ + const unsigned char *data = NULL; + size_t size; + + if (i_stream_read_data(input, &data, &size, + sizeof(IOSTREAM_CRYPT_MAGIC)) <= 0) { + return FALSE; + } + + if (memcmp(data, IOSTREAM_CRYPT_MAGIC, + sizeof(IOSTREAM_CRYPT_MAGIC)) != 0) { + return FALSE; + } + return TRUE; +} + +static void mail_crypt_cache_close(struct mail_crypt_user *muser) +{ + struct mail_crypt_cache *cache = &muser->cache; + + if (cache->to != NULL) + timeout_remove(&cache->to); + if (cache->input != NULL) + i_stream_unref(&cache->input); + memset(cache, 0, sizeof(*cache)); +} + +static struct istream * +mail_crypt_cache_open(struct mail_crypt_user *muser, struct mail *mail, + struct istream *input) +{ + struct mail_crypt_cache *cache = &muser->cache; + struct istream *inputs[2]; + string_t *temp_prefix = t_str_new(128); + + mail_crypt_cache_close(muser); + + input->seekable = FALSE; + inputs[0] = input; + inputs[1] = NULL; + mail_user_set_get_temp_prefix(temp_prefix, mail->box->storage->user->set); + input = i_stream_create_seekable_path(inputs, + i_stream_get_max_buffer_size(inputs[0]), + str_c(temp_prefix)); + i_stream_unref(&inputs[0]); + + if (mail->uid > 0) { + cache->to = timeout_add(MAIL_CRYPT_MAIL_CACHE_EXPIRE_MSECS, + mail_crypt_cache_close, muser); + cache->box = mail->box; + cache->uid = mail->uid; + cache->input = input; + /* index-mail wants the stream to be destroyed at close, so create + a new stream instead of just increasing reference. */ + return i_stream_create_limit(cache->input, (uoff_t)-1); + } + + return input; +} + +static int mail_crypt_istream_get_private_key(const char *pubkey_digest, + struct dcrypt_private_key **priv_key_r, + const char **error_r, + void *context) +{ + /* mailbox_crypt_search_all_private_keys requires error_r != NULL */ + i_assert(error_r != NULL); + int ret; + struct mail *_mail = context; + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(_mail->box->storage->user); + i_assert(muser != NULL); + + *priv_key_r = mail_crypt_global_key_find(&muser->global_keys, + pubkey_digest); + if (*priv_key_r != NULL) return 1; + + struct mail_namespace *ns = mailbox_get_namespace(_mail->box); + struct mailbox_transaction_context *t = + mailbox_transaction_begin(_mail->box, 0); + + if (ns->type == MAIL_NAMESPACE_TYPE_SHARED) { + ret = mail_crypt_box_get_shared_key(t, pubkey_digest, + priv_key_r, error_r); + } else if (ns->type != MAIL_NAMESPACE_TYPE_PUBLIC) { + ret = mail_crypt_get_private_key(t, pubkey_digest, + FALSE, FALSE, priv_key_r, + error_r); + } else { + *error_r = "Public emails cannot have keys"; + ret = -1; + } + + (void)mailbox_transaction_commit(&t); + + i_assert(ret <= 0 || *priv_key_r != NULL); + + return ret; +} + +static int +mail_crypt_istream_opened(struct mail *_mail, struct istream **stream) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_user *user = _mail->box->storage->user; + struct mail_crypt_user *muser = MAIL_CRYPT_USER_CONTEXT(user); + struct mail_crypt_cache *cache = &muser->cache; + union mail_module_context *mmail = MAIL_CRYPT_MAIL_CONTEXT(mail); + struct istream *input; + + if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) { + /* use the cached stream. when doing partial reads it should + already be seeked into the wanted offset. */ + i_stream_unref(stream); + i_stream_seek(cache->input, 0); + *stream = i_stream_create_limit(cache->input, (uoff_t)-1); + return mmail->super.istream_opened(_mail, stream); + } + + /* decryption is the outmost stream, so add it before others + (e.g. zlib) */ + if (!mail_crypt_is_stream_encrypted(*stream)) + return mmail->super.istream_opened(_mail, stream); + + input = *stream; + *stream = i_stream_create_decrypt_callback(input, + mail_crypt_istream_get_private_key, _mail); + i_stream_unref(&input); + + *stream = mail_crypt_cache_open(muser, _mail, *stream); + return mmail->super.istream_opened(_mail, stream); +} + +static void mail_crypt_close(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *mmail = MAIL_CRYPT_MAIL_CONTEXT(mail); + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(_mail->box->storage->user); + struct mail_crypt_cache *cache = &muser->cache; + uoff_t size; + + if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) { + /* make sure we have read the entire email into the seekable + stream (which causes the original input stream to be + unrefed). we can't safely keep the original input stream + open after the mail is closed. */ + if (i_stream_get_size(cache->input, TRUE, &size) < 0) + mail_crypt_cache_close(muser); + } + mmail->super.close(_mail); +} + +static void mail_crypt_mail_allocated(struct mail *_mail) +{ + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(_mail->box->storage->user); + if (muser == NULL) return; + + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + union mail_module_context *mmail; + + mmail = p_new(mail->pool, union mail_module_context, 1); + mmail->super = *v; + mail->vlast = &mmail->super; + + v->istream_opened = mail_crypt_istream_opened; + v->close = mail_crypt_close; + MODULE_CONTEXT_SET_SELF(mail, mail_crypt_mail_module, mmail); +} + +static int mail_crypt_mail_save_finish(struct mail_save_context *ctx) +{ + struct mailbox *box = ctx->transaction->box; + union mailbox_module_context *zbox = MAIL_CRYPT_CONTEXT(box); + struct istream *input; + + if (zbox->super.save_finish(ctx) < 0) + return -1; + + /* we're here only if mail-crypt plugin is disabled. we want to make + sure that even though we're saving an unencrypted mail, the mail + can't be faked to look like an encrypted mail. */ + if (mail_get_stream(ctx->dest_mail, NULL, NULL, &input) < 0) + return -1; + + if (mail_crypt_is_stream_encrypted(input)) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Saving mails encrypted by client isn't supported"); + return -1; + } + return 0; +} + +static int +mail_crypt_mail_save_begin(struct mail_save_context *ctx, + struct istream *input) +{ + const char *pubid; + struct mailbox *box = ctx->transaction->box; + struct mail_crypt_mailbox *mbox = MAIL_CRYPT_CONTEXT(box); + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(box->storage->user); + i_assert(muser != NULL); + + enum io_stream_encrypt_flags enc_flags; + if (muser->save_version == 1) { + enc_flags = IO_STREAM_ENC_VERSION_1; + } else if (muser->save_version == 2) { + enc_flags = IO_STREAM_ENC_INTEGRITY_AEAD; + } else { + i_assert(muser->save_version == 0); + i_panic("mail_crypt_mail_save_begin not supposed to be called" + "when mail_crypt_save_version is 0"); + } + + if (mbox->module_ctx.super.save_begin(ctx, input) < 0) + return -1; + + struct dcrypt_public_key *pub_key; + if (muser->global_keys.public_key != NULL) + pub_key = muser->global_keys.public_key; + else if (mbox->pub_key != NULL) + pub_key = mbox->pub_key; + else { + const char *error; + int ret; + + if ((ret = mail_crypt_box_get_public_key(ctx->transaction, &pub_key, + &error)) <= 0) + { + struct dcrypt_keypair pair; + + if (ret < 0) { + mail_storage_set_error(box->storage, + MAIL_ERROR_PARAMS, + t_strdup_printf("get_public_key(%s) failed: %s", + mailbox_get_vname(box), + error)); + return ret; + } + + if (muser->save_version > 1 && + mail_crypt_box_generate_keypair(box, &pair, NULL, + &pubid, &error) < 0) { + mail_storage_set_error(box->storage, + MAIL_ERROR_PARAMS, + t_strdup_printf("generate_keypair(%s) failed: %s", + mailbox_get_vname(box), + error)); + return -1; + } + pub_key = pair.pub; + dcrypt_key_unref_private(&pair.priv); + + } + mbox->pub_key = pub_key; + } + + /* encryption is the outermost layer (zlib etc. are inside) */ + struct ostream *output = o_stream_create_encrypt(ctx->data.output, + MAIL_CRYPT_ENC_ALGORITHM, pub_key, enc_flags); + + o_stream_unref(&ctx->data.output); + ctx->data.output = output; + o_stream_cork(ctx->data.output); + return 0; +} + +static void mail_crypt_mailbox_close(struct mailbox *box) +{ + struct mail_crypt_mailbox *mbox = MAIL_CRYPT_CONTEXT(box); + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(box->storage->user); + + if (mbox->pub_key != NULL) + dcrypt_key_unref_public(&mbox->pub_key); + if (muser != NULL && muser->cache.box == box) + mail_crypt_cache_close(muser); + mbox->module_ctx.super.close(box); +} + +static void mail_crypt_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(box->storage->user); + struct mail_crypt_mailbox *mbox; + enum mail_storage_class_flags class_flags = box->storage->class_flags; + + mbox = p_new(box->pool, struct mail_crypt_mailbox, 1); + mbox->module_ctx.super = *v; + box->vlast = &mbox->module_ctx.super; + v->close = mail_crypt_mailbox_close; + + MODULE_CONTEXT_SET(box, mail_crypt_storage_module, mbox); + + if ((class_flags & MAIL_STORAGE_CLASS_FLAG_BINARY_DATA) != 0) { + if (muser != NULL) { + if (muser->save_version > 0) + v->save_begin = mail_crypt_mail_save_begin; + } else { + v->save_finish = mail_crypt_mail_save_finish; + } + } +} + +static void mail_crypt_mail_user_deinit(struct mail_user *user) +{ + struct mail_crypt_user *muser = MAIL_CRYPT_USER_CONTEXT(user); + + mail_crypt_key_cache_destroy(&muser->key_cache); + mail_crypt_global_keys_free(&muser->global_keys); + mail_crypt_cache_close(muser); + muser->module_ctx.super.deinit(user); +} + +static void mail_crypt_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct mail_crypt_user *muser; + const char *error = NULL; + + muser = p_new(user->pool, struct mail_crypt_user, 1); + muser->module_ctx.super = *v; + user->vlast = &muser->module_ctx.super; + + const char *curve = mail_user_plugin_getenv(user, "mail_crypt_curve"); + buffer_t *tmp = t_str_new(64); + if (curve == NULL) { + if (user->mail_debug) { + i_debug("mail_crypt_plugin: mail_crypt_curve setting " + "missing - generating EC keys disabled"); + } + } else if (!dcrypt_name2oid(curve, tmp, &error)) { + user->error = p_strdup_printf(user->pool, + "mail_crypt_plugin: " + "invalid mail_crypt_curve setting %s: %s - " + "plugin disabled", + curve, error); + } else { + muser->curve = p_strdup(user->pool, curve); + } + + const char *version = mail_user_plugin_getenv(user, + "mail_crypt_save_version"); + + if (version == NULL) { + user->error = p_strdup_printf(user->pool, + "mail_crypt_plugin: " + "mail_crypt_save_version setting missing " + "- plugin disabled"); + } else if (version[0] == '0') { + muser->save_version = 0; + } else if (version[0] == '1') { + muser->save_version = 1; + } else if (version[0] == '2') { + muser->save_version = 2; + } else { + user->error = p_strdup_printf(user->pool, + "mail_crypt_plugin: Invalid " + "mail_crypt_save_version %s: use 0, 1, or 2 ", + version); + } + + if (mail_crypt_global_keys_load(user, "mail_crypt_global", + &muser->global_keys, FALSE, &error) < 0) { + user->error = p_strdup_printf(user->pool, + "mail_crypt_plugin: %s", error); + } + + v->deinit = mail_crypt_mail_user_deinit; + MODULE_CONTEXT_SET(user, mail_crypt_user_module, muser); +} + +static struct mail_storage_hooks mail_crypt_mail_storage_hooks = { + .mail_user_created = mail_crypt_mail_user_created, + .mail_allocated = mail_crypt_mail_allocated +}; + +static struct mail_storage_hooks mail_crypt_mail_storage_hooks_post = { + .mailbox_allocated = mail_crypt_mailbox_allocated +}; + +static struct module crypto_post_module = { + .path = "lib95_mail_crypt_plugin.so" +}; + +void mail_crypt_plugin_init(struct module *module) +{ + const char* error; + random_init(); + if (!dcrypt_initialize("openssl", NULL, &error)) + i_fatal("dcrypt_initialize(): %s", error); + mail_storage_hooks_add(module, &mail_crypt_mail_storage_hooks); + /* rather kludgy. we need to hook into mail reading as early as + possible, but we need to hook into mail writing as late as + possible. we could create just two real plugins, but that's a bit + annoying to configure. */ + mail_storage_hooks_add_forced(&crypto_post_module, + &mail_crypt_mail_storage_hooks_post); + mail_crypt_key_register_mailbox_internal_attributes(); +} + +void mail_crypt_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mail_crypt_mail_storage_hooks); + mail_storage_hooks_remove(&mail_crypt_mail_storage_hooks_post); + random_deinit(); +} diff --git a/src/plugins/mail-crypt/mail-crypt-plugin.h b/src/plugins/mail-crypt/mail-crypt-plugin.h new file mode 100644 index 0000000000..4556a92973 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-plugin.h @@ -0,0 +1,32 @@ +#ifndef MAIL_CRYPT_PLUGIN_H +#define MAIL_CRYPT_PLUGIN_H + +struct mailbox; +struct module; + +struct mail_crypt_cache { + struct timeout *to; + struct mailbox *box; + uint32_t uid; + + struct istream *input; +}; + +struct mail_crypt_user { + union mail_user_module_context module_ctx; + + struct mail_crypt_global_keys global_keys; + struct mail_crypt_cache cache; + struct mail_crypt_key_cache_entry *key_cache; + const char *curve; + int save_version; +}; + +void mail_crypt_plugin_init(struct module *module); +void mail_crypt_plugin_deinit(void); + +#define MAIL_CRYPT_MAIL_CACHE_EXPIRE_MSECS (60*1000) + +struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user); + +#endif diff --git a/src/plugins/mail-crypt/mail-crypt-pluginenv.c b/src/plugins/mail-crypt/mail-crypt-pluginenv.c new file mode 100644 index 0000000000..8a729ee66d --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-pluginenv.c @@ -0,0 +1,105 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ +#include "lib.h" +#include "str.h" +#include "array.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "fs-crypt-settings.h" + +static const struct fs_crypt_settings * +fs_crypt_load_settings(void) +{ + static const struct setting_parser_info *set_roots[] = { + &fs_crypt_setting_parser_info, + NULL + }; + struct master_service_settings_input input; + struct master_service_settings_output output; + const char *error; + + memset(&input, 0, sizeof(input)); + input.roots = set_roots; + input.module = "fs-crypt"; + input.service = "fs-crypt"; + if (master_service_settings_read(master_service, &input, + &output, &error) < 0) + i_fatal("Error reading configuration: %s", error); + + return master_service_settings_get_others(master_service)[0]; +} + +static +const char *mail_crypt_plugin_getenv(const struct fs_crypt_settings *set, + const char *name) +{ + const char *const *envs; + unsigned int i, count; + + if (set == NULL) + return NULL; + + if (!array_is_created(&set->plugin_envs)) + return NULL; + + envs = array_get(&set->plugin_envs, &count); + for (i = 0; i < count; i += 2) { + if (strcmp(envs[i], name) == 0) + return envs[i+1]; + } + return NULL; +} + +static int +mail_crypt_load_global_private_keys(const struct fs_crypt_settings *set, + const char *set_prefix, + struct mail_crypt_global_keys *global_keys, + const char **error_r) +{ + string_t *set_key = t_str_new(64); + str_append(set_key, set_prefix); + str_append(set_key, "_private_key"); + unsigned int prefix_len = str_len(set_key); + + unsigned int i = 1; + const char *key_data; + while ((key_data = mail_crypt_plugin_getenv(set, str_c(set_key))) != NULL) { + const char *set_pw = t_strconcat(str_c(set_key), "_password", NULL); + const char *password = mail_crypt_plugin_getenv(set, set_pw); + if (mail_crypt_load_global_private_key(str_c(set_key), key_data, + set_pw, password, + global_keys, error_r) < 0) + return -1; + str_truncate(set_key, prefix_len); + str_printfa(set_key, "%u", ++i); + } + return 0; +} + +int mail_crypt_global_keys_load_pluginenv(const char *set_prefix, + struct mail_crypt_global_keys *global_keys_r, + const char **error_r) +{ + const struct fs_crypt_settings *set = fs_crypt_load_settings(); + + const char *set_key = t_strconcat(set_prefix, "_public_key", NULL); + const char *key_data = mail_crypt_plugin_getenv(set, set_key); + int ret = 0; + + mail_crypt_global_keys_init(global_keys_r); + if (key_data != NULL) { + if (mail_crypt_load_global_public_key(set_key, key_data, + global_keys_r, error_r) < 0) + ret = -1; + } + + if (ret == 0 && + mail_crypt_load_global_private_keys(set, set_prefix, global_keys_r, + error_r) < 0) + ret = -1; + + mail_crypt_global_keys_free(global_keys_r); + return ret; +} diff --git a/src/plugins/mail-crypt/mail-crypt-userenv.c b/src/plugins/mail-crypt/mail-crypt-userenv.c new file mode 100644 index 0000000000..54a1b96cf8 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-userenv.c @@ -0,0 +1,67 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ +#include "lib.h" +#include "str.h" +#include "mail-user.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" + +static int +mail_crypt_load_global_private_keys(struct mail_user *user, + const char *set_prefix, + struct mail_crypt_global_keys *global_keys, + bool ignore_errors, + const char **error_r) +{ + string_t *set_key = t_str_new(64); + str_append(set_key, set_prefix); + str_append(set_key, "_private_key"); + unsigned int prefix_len = str_len(set_key); + + unsigned int i = 1; + const char *key_data; + while ((key_data = mail_user_plugin_getenv(user, str_c(set_key))) != NULL) { + const char *set_pw = t_strconcat(str_c(set_key), "_password", NULL); + const char *password = mail_user_plugin_getenv(user, set_pw); + if (mail_crypt_load_global_private_key(str_c(set_key), key_data, + set_pw, password, + global_keys, + error_r) < 0) { + /* skip this key */ + if (ignore_errors) { + if (user->namespaces->mail_set->mail_debug) + i_debug("mail-crypt-plugin: " + "mail_crypt_load_global_private_key failed: %s", + *error_r); + *error_r = NULL; + continue; + } + return -1; + } + str_truncate(set_key, prefix_len); + str_printfa(set_key, "%u", ++i); + } + return 0; +} + +int mail_crypt_global_keys_load(struct mail_user *user, const char *set_prefix, + struct mail_crypt_global_keys *global_keys_r, + bool ignore_privkey_errors, + const char **error_r) +{ + const char *set_key = t_strconcat(set_prefix, "_public_key", NULL); + const char *key_data = mail_user_plugin_getenv(user, set_key); + + mail_crypt_global_keys_init(global_keys_r); + if (key_data != NULL) { + if (mail_crypt_load_global_public_key(set_key, + key_data, + global_keys_r, + error_r) < 0) + return -1; + } + if (mail_crypt_load_global_private_keys(user, set_prefix, global_keys_r, + ignore_privkey_errors, + error_r) < 0) + return -1; + return 0; +} diff --git a/src/plugins/mail-crypt/test-mail-global-key.c b/src/plugins/mail-crypt/test-mail-global-key.c new file mode 100644 index 0000000000..2fc8eac992 --- /dev/null +++ b/src/plugins/mail-crypt/test-mail-global-key.c @@ -0,0 +1,115 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-common.h" +#include "randgen.h" +#include "array.h" +#include "dcrypt.h" +#include "hex-binary.h" + +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "fs-crypt-settings.h" + +#include "mail-crypt-pluginenv.c" + +static struct fs_crypt_settings fs_set; + +static const char *settings[] = { + "mail_crypt_global_private_key", + "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ1lJdWZKWlplMlk2aUZ6NXgKa29Jb3lzYjNkWkxaV3N5ZWtqT2MvR2pzTGQyaFJBTkNBQVNuSVdnUXVoRThqcUFMY21maXVuUnlFazd2a3EveQphOXZZSzUwYjNjRmhDc0xVNHRmVlRMa0IxWS82VmxaajYzUUtNelhOdms1RzVPRDFvZkVsY3B5agotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==", + "mail_crypt_global_public_key", + "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFcHlGb0VMb1JQSTZnQzNKbjRycDBjaEpPNzVLdgo4bXZiMkN1ZEc5M0JZUXJDMU9MWDFVeTVBZFdQK2xaV1krdDBDak0xemI1T1J1VGc5YUh4SlhLY293PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==", + "mail_crypt_global_private_key2", + "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLQpNSUhlTUVrR0NTcUdTSWIzRFFFRkRUQThNQnNHQ1NxR1NJYjNEUUVGRERBT0JBaXA2cUpja1FET3F3SUNDQUF3CkhRWUpZSVpJQVdVREJBRXFCQkFXN09oUFRlU0xSOExLcGYwZjZHa3ZCSUdRZk5rYUpodnM2VWVWS2RkN2NzdFMKMURSNXJYTWtON09FbVNjTTljRlk2UDVrMzdnY1VJUFZudTQrOTFYZUE1MTU2cnBpUEpycEdkZnprcjhPNVFqZApsMWRycmR6Z0hqZHE4T2VmbUR1MEEzMjRZd25SS3hGRExUcjlHMkxVMkhoYmV6a0xjV1FwMVJISDZsNXRRcUtwCjZid05iMnc3OXhCb01YSjN6MVZqcElOZk9wRnJ6M3lucVlqUXhseTIrQjg2Ci0tLS0tRU5EIEVOQ1JZUFRFRCBQUklWQVRFIEtFWS0tLS0tCg==", + "mail_crypt_global_private_key2_password", + "password", +}; + +int +mail_crypt_load_global_private_keys(const struct fs_crypt_settings *set, + const char *set_prefix, + struct mail_crypt_global_keys *global_keys, + const char **error_r); + +static void test_setup(void) +{ + struct dcrypt_settings set = { + .module_dir = top_builddir "/src/lib-dcrypt/.libs" + }; + dcrypt_initialize(NULL, &set, NULL); + i_array_init(&fs_set.plugin_envs, 8); + array_append(&fs_set.plugin_envs, settings, N_ELEMENTS(settings)); +} + +static void test_try_load_keys(void) +{ + const char *pubid1 = "c79e262924842de291a8bcd413f4122a570abd033adeff7c1cdfdc9d05998c75"; + const char *pubid2 = "aaf927444bff8b63425e852c6b3f769e8221b952b42cf886fae7d326c5be098e"; + buffer_t *key_id = buffer_create_dynamic(pool_datastack_create(), 128); + + const char *error = NULL; + test_begin("try_load_keys"); + + struct mail_crypt_global_keys keys; + memset(&keys, 0, sizeof(keys)); + mail_crypt_global_keys_init(&keys); + + const char *set_prefix = "mail_crypt_global"; + const char *set_key = t_strconcat(set_prefix, "_public_key", NULL); + const char *key_data = mail_crypt_plugin_getenv(&fs_set, set_key); + + test_assert(key_data != NULL); + + if (key_data != NULL) { + test_assert(mail_crypt_load_global_public_key(set_key, key_data, + &keys, &error) == 0); + test_assert(mail_crypt_load_global_private_keys(&fs_set, set_prefix, + &keys, &error) == 0); + /* did we get two private keys? */ + test_assert(array_count(&keys.private_keys) == 2); + + /* public key id checks */ + + buffer_set_used_size(key_id, 0); + test_assert(dcrypt_key_id_public(keys.public_key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE); + test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid1) == 0); + + const struct mail_crypt_global_private_key *key = + array_idx(&keys.private_keys, 0); + + buffer_set_used_size(key_id, 0); + test_assert(dcrypt_key_id_private(key->key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE); + test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid1) == 0); + + key = array_idx(&keys.private_keys, 1); + buffer_set_used_size(key_id, 0); + test_assert(dcrypt_key_id_private(key->key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE); + test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid2) == 0); + + } + + mail_crypt_global_keys_free(&keys); + + test_end(); +} + +static void test_teardown(void) +{ + array_free(&fs_set.plugin_envs); + dcrypt_deinitialize(); +} + +int main(void) +{ + void (*tests[])(void) = { + test_setup, + test_try_load_keys, + test_teardown, + NULL + }; + + random_init(); + int ret = test_run(tests); + return ret; +} diff --git a/src/plugins/mail-crypt/test-mail-key.c b/src/plugins/mail-crypt/test-mail-key.c new file mode 100644 index 0000000000..66f483d8f3 --- /dev/null +++ b/src/plugins/mail-crypt/test-mail-key.c @@ -0,0 +1,491 @@ +/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-common.h" +#include "ioloop.h" +#include "lib-signals.h" +#include "master-service.h" +#include "mail-storage.h" +#include "mail-storage-service.h" +#include "mailbox-list.h" +#include "settings-parser.h" +#include "mail-user.h" +#include "safe-mkstemp.h" +#include "safe-mkdir.h" +#include "str.h" +#include "unlink-directory.h" +#include "randgen.h" +#include "dcrypt.h" +#include "hex-binary.h" + +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" + +static const char *mcp_old_user_key = "1\t716\t0\t048FD04FD3612B22D32790C592CF21CEF417EFD2EA34AE5F688FA5B51BED29E05A308B68DA78E16E90B47A11E133BD9A208A2894FD01B0BEE865CE339EA3FB17AC\td0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0"; +static const char *mcp_old_user_key_id = "d0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0"; +static const char *mcp_old_box_key = "1\t716\t1\t0567e6bf9579813ae967314423b0fceb14bda24749303923de9a9bb9370e0026f995901a57e63113eeb2baf0c940e978d00686cbb52bd5014bc318563375876255\t0300E46DA2125427BE968EB3B649910CDC4C405E5FFDE18D433A97CABFEE28CEEFAE9EE356C792004FFB80981D67E741B8CC036A34235A8D2E1F98D1658CFC963D07EB\td0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0\t7c9a1039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00fa4f"; +static const char *mcp_old_box_key_id = "7c9a1039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00fa4f"; + +static struct mail_storage_service_ctx *mail_storage_service = NULL; +static struct mail_user *test_mail_user = NULL; +static struct mail_storage_service_user *test_service_user = NULL; +static struct ioloop *test_ioloop = NULL; +static const char *mail_home; + +static const char *test_user_key_id; +static const char *test_box_key_id; + +static pool_t test_pool; + +static struct mail_crypt_user mail_crypt_user; + +struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user ATTR_UNUSED) +{ + return &mail_crypt_user; +} + +static +int test_mail_attribute_get(struct mailbox *box, bool user_key, bool shared, + const char *pubid, const char **value_r, const char **error_r) +{ + const char *attr_name; + enum mail_attribute_type attr_type; + + if (strcmp(pubid, ACTIVE_KEY_NAME) == 0) { + attr_name = user_key ? USER_CRYPT_PREFIX ACTIVE_KEY_NAME : + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME; + attr_type = MAIL_ATTRIBUTE_TYPE_SHARED; + } else { + attr_name = t_strdup_printf("%s%s%s", + user_key ? USER_CRYPT_PREFIX : + BOX_CRYPT_PREFIX, + shared ? PUBKEYS_PREFIX : + PRIVKEYS_PREFIX, + pubid); + attr_type = shared ? MAIL_ATTRIBUTE_TYPE_SHARED : MAIL_ATTRIBUTE_TYPE_PRIVATE; + } + struct mail_attribute_value value; + + int ret; + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0); + + if ((ret = mailbox_attribute_get(t, attr_type, + attr_name, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s", + mailbox_get_vname(box), + attr_name, + mailbox_get_last_error(box, NULL)); + } + } else { + *value_r = t_strdup(value.value); + } + + (void)mailbox_transaction_commit(&t); + + return ret; +} + +static int +test_mail_attribute_set(struct mailbox_transaction_context *t, + bool user_key, bool shared, const char *pubid, + const char *value, const char **error_r) +{ + const char *attr_name; + enum mail_attribute_type attr_type; + + if (strcmp(pubid, ACTIVE_KEY_NAME) == 0) { + attr_name = user_key ? USER_CRYPT_PREFIX ACTIVE_KEY_NAME : + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME; + attr_type = MAIL_ATTRIBUTE_TYPE_SHARED; + } else { + attr_name = t_strdup_printf("%s%s%s", + user_key ? USER_CRYPT_PREFIX : + BOX_CRYPT_PREFIX, + shared ? PUBKEYS_PREFIX : + PRIVKEYS_PREFIX, + pubid); + attr_type = shared ? MAIL_ATTRIBUTE_TYPE_SHARED : MAIL_ATTRIBUTE_TYPE_PRIVATE; + } + + struct mail_attribute_value attr_value; + + int ret; + + memset(&attr_value, 0, sizeof(attr_value)); + attr_value.value = value; + + if ((ret = mailbox_attribute_set(t, attr_type, + attr_name, &attr_value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s) failed: %s", + mailbox_get_vname(mailbox_transaction_get_mailbox(t)), + attr_name, + mailbox_get_last_error(mailbox_transaction_get_mailbox(t), NULL)); + } + } + + return ret; +} + + +static +int init_test_mail_user(void) +{ + struct setting_parser_context *set_parser; + const char *error; + char path_buf[4096]; + + if (getcwd(path_buf, sizeof(path_buf)) == NULL) + i_fatal("getcwd() failed: %m"); + + mail_home = p_strdup_printf(test_pool, "%s/mcp_user/", path_buf); + + struct mail_storage_service_input input = { + .userdb_fields = (const char*const[]){ + t_strdup_printf("mail=maildir:~/"), + t_strdup_printf("home=%s", mail_home), + t_strdup_printf("mail_crypt_curve=prime256v1"), + NULL + }, + .username = "mcp_test", + .no_userdb_lookup = TRUE, + .debug = TRUE, + }; + + mail_storage_service = mail_storage_service_init(master_service, NULL, + MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT | + MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS); + + if (mail_storage_service_lookup(mail_storage_service, &input, + &test_service_user, &error) < 0) + { + i_error("Cannot lookup test user: %s", error); + return -1; + } + + set_parser = + mail_storage_service_user_get_settings_parser(test_service_user); + + if (settings_parse_line(set_parser, + t_strdup_printf("mail_attribute_dict=file:%s/dovecot-attributes", + mail_home)) < 0) { + i_error("Cannot set mail_attribute_dict: %s", + settings_parser_get_error(set_parser)); + return -1; + } + + if (mail_storage_service_next(mail_storage_service, test_service_user, + &test_mail_user) < 0) + { + i_error("Cannot lookup test user"); + return -1; + } + + return 0; +} + +static +void deinit_test_mail_user() +{ + mail_user_unref(&test_mail_user); + mail_storage_service_user_free(&test_service_user); + mail_storage_service_deinit(&mail_storage_service); + if (unlink_directory(mail_home, UNLINK_DIRECTORY_FLAG_RMDIR) < 0) + i_error("unlink_directory(%s) failed", mail_home); +} + +static void test_generate_user_key(void) +{ + struct dcrypt_keypair pair; + const char *pubid; + const char *error = NULL; + + test_begin("generate user key"); + + /* try to generate a keypair for user */ + if (mail_crypt_user_generate_keypair(test_mail_user, &pair, + &pubid, &error) < 0) { + i_error("generate_keypair failed: %s", error); + test_exit(1); + } + + test_assert(pubid != NULL); + + test_user_key_id = p_strdup(test_pool, pubid); + + dcrypt_keypair_unref(&pair); + error = NULL; + + /* keys ought to be in cache or somewhere...*/ + if (mail_crypt_user_get_private_key(test_mail_user, NULL, &pair.priv, &error) <= 0) + { + i_error("Cannot get user private key: %s", error); + } + + test_assert(pair.priv != NULL); + + if (pair.priv != NULL) + dcrypt_key_unref_private(&pair.priv); + + test_end(); +} + +static void test_generate_inbox_key(void) +{ + struct dcrypt_public_key *user_key; + struct dcrypt_keypair pair; + const char *error = NULL, *pubid = NULL; + + test_begin("generate inbox key"); + + if (mail_crypt_user_get_public_key(test_mail_user, &user_key, + &error) <= 0) { + i_error("Cannot get user private key: %s", error); + } + struct mail_namespace *ns = + mail_namespace_find_inbox(test_mail_user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + mailbox_open(box); + if (mail_crypt_box_generate_keypair(box, &pair, user_key, &pubid, + &error) < 0) { + i_error("generate_keypair failed: %s", error); + test_exit(1); + } + + i_assert(pubid != NULL); + + dcrypt_keypair_unref(&pair); + dcrypt_key_unref_public(&user_key); + mailbox_free(&box); + + test_box_key_id = p_strdup(test_pool, pubid); + + test_end(); +} + +static void test_cache_reset(void) +{ + struct dcrypt_keypair pair; + const char *error = NULL; + + test_begin("cache reset"); + + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(test_mail_user); + mail_crypt_key_cache_destroy(&muser->key_cache); + + test_assert(mail_crypt_user_get_private_key(test_mail_user, NULL, + &pair.priv, &error) > 0); + if (error != NULL) + i_error("mail_crypt_user_get_private_key() failed: %s", error); + error = NULL; + test_assert(mail_crypt_user_get_public_key(test_mail_user, + &pair.pub, &error) > 0); + if (error != NULL) + i_error("mail_crypt_user_get_public_key() failed: %s", error); + + dcrypt_keypair_unref(&pair); + + test_end(); +} + +static void test_verify_keys(void) +{ + const char *value, *error = NULL; + + const char *enc_id; + enum dcrypt_key_encryption_type enc_type; + + test_begin("verify keys"); + + struct dcrypt_private_key *privkey = NULL, *user_key = NULL; + struct dcrypt_public_key *pubkey = NULL; + + struct mail_namespace *ns = + mail_namespace_find_inbox(test_mail_user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + mailbox_open(box); + /* verify links */ + + /* user's public key */ + test_assert(test_mail_attribute_get(box, TRUE, TRUE, ACTIVE_KEY_NAME, + &value, &error) > 0); + test_assert(strcmp(value, test_user_key_id) == 0); + + test_assert(test_mail_attribute_get(box, TRUE, TRUE, value, &value, + &error) > 0); + + /* load key */ + test_assert(dcrypt_key_load_public(&pubkey, value, &error) == TRUE); + + /* see if it matches */ + test_assert(mail_crypt_public_key_id_match(pubkey, test_user_key_id, + &error) > 0); + dcrypt_key_unref_public(&pubkey); + + /* user's private key */ + test_assert(test_mail_attribute_get(box, TRUE, FALSE, ACTIVE_KEY_NAME, + &value, &error) > 0); + test_assert(strcmp(value, test_user_key_id) == 0); + + test_assert(test_mail_attribute_get(box, TRUE, FALSE, value, &value, + &error) > 0); + + /* load key */ + test_assert(dcrypt_key_load_private(&user_key, value, NULL, NULL, + &error) == TRUE); + + /* see if it matches */ + test_assert(mail_crypt_private_key_id_match(user_key, test_user_key_id, + &error) > 0); + + + + + /* inbox's public key */ + test_assert(test_mail_attribute_get(box, FALSE, TRUE, ACTIVE_KEY_NAME, + &value, &error) > 0); + test_assert(strcmp(value, test_box_key_id) == 0); + + test_assert(test_mail_attribute_get(box, FALSE, TRUE, value, &value, + &error) > 0); + + /* load key */ + test_assert(dcrypt_key_load_public(&pubkey, value, &error) == TRUE); + + /* see if it matches */ + test_assert(mail_crypt_public_key_id_match(pubkey, test_box_key_id, + &error) > 0); + dcrypt_key_unref_public(&pubkey); + + /* user's private key */ + test_assert(test_mail_attribute_get(box, FALSE, FALSE, ACTIVE_KEY_NAME, + &value, &error) > 0); + test_assert(strcmp(value, test_box_key_id) == 0); + + test_assert(test_mail_attribute_get(box, FALSE, FALSE, value, &value, + &error) > 0); + + test_assert(dcrypt_key_string_get_info(value, NULL, NULL, NULL, + &enc_type, &enc_id, NULL, + &error) == TRUE); + + test_assert(enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_KEY); + test_assert(strcmp(enc_id, test_user_key_id) == 0); + + /* load key */ + test_assert(dcrypt_key_load_private(&privkey, value, NULL, user_key, + &error) == TRUE); + + /* see if it matches */ + test_assert(mail_crypt_private_key_id_match(privkey, test_box_key_id, + &error) > 0); + dcrypt_key_unref_private(&privkey); + dcrypt_key_unref_private(&user_key); + + mailbox_free(&box); + + test_end(); +} + +static void test_old_key(void) +{ + test_begin("old keys"); + + const char *error = NULL; + struct dcrypt_private_key *privkey = NULL; + + struct mail_namespace *ns = + mail_namespace_find_inbox(test_mail_user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + mailbox_open(box); + + struct mailbox_transaction_context *t = mailbox_transaction_begin(box, 0); + + test_mail_attribute_set(t, TRUE, FALSE, mcp_old_user_key_id, + mcp_old_user_key, &error); + test_mail_attribute_set(t, FALSE, FALSE, mcp_old_box_key_id, + mcp_old_box_key, &error); + + error = NULL; + + /* try to load old key */ + test_assert(mail_crypt_get_private_key(t, mcp_old_box_key_id, FALSE, FALSE, + &privkey, &error) > 0); + + (void)mailbox_transaction_commit(&t); + + if (error != NULL) + i_error("mail_crypt_get_private_key(%s) failed: %s", + mcp_old_box_key_id, + error); + + test_assert(privkey != NULL); + + if (privkey != NULL) { + buffer_t *key_id = buffer_create_dynamic(pool_datastack_create(), 32); + dcrypt_key_id_private_old(privkey, key_id, &error); + test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), mcp_old_box_key_id) == 0); + dcrypt_key_unref_private(&privkey); + } + + mailbox_free(&box); + + test_end(); +} + +static void test_setup(void) +{ + struct dcrypt_settings set = { + .module_dir = top_builddir "/src/lib-dcrypt/.libs" + }; + test_pool = pool_alloconly_create(MEMPOOL_GROWING "mcp test pool", 128); + test_ioloop = io_loop_create(); + dcrypt_initialize(NULL, &set, NULL); + /* allocate a user */ + if (init_test_mail_user() < 0) { + test_exit(1); + } + mail_crypt_key_register_mailbox_internal_attributes(); +} + +static void test_teardown(void) +{ + deinit_test_mail_user(); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(test_mail_user); + mail_crypt_key_cache_destroy(&muser->key_cache); + dcrypt_deinitialize(); + io_loop_destroy(&test_ioloop); + pool_unref(&test_pool); +} + +int main(int argc, char **argv) +{ + void (*tests[])(void) = { + test_setup, + test_generate_user_key, + test_generate_inbox_key, + test_cache_reset, + test_verify_keys, + test_old_key, + test_teardown, + NULL + }; + + + master_service = master_service_init("test-mail-key", + MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS | + MASTER_SERVICE_FLAG_NO_SSL_INIT, + &argc, &argv, ""); + random_init(); + int ret = test_run(tests); + master_service_deinit(&master_service); + return ret; +}