]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
mail-crypt: Move fs-crypt-common.c code into fs-crypt.c
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 15 Aug 2023 17:05:19 +0000 (13:05 -0400)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 12 Feb 2025 10:34:11 +0000 (12:34 +0200)
There is no more code sharing with fs-mail-crypt.c, so it is unnecessary
to keep it in a separate file.

src/plugins/mail-crypt/Makefile.am
src/plugins/mail-crypt/fs-crypt-common.c [deleted file]
src/plugins/mail-crypt/fs-crypt.c

index 3c7b9d5c36c846280607395f2a9e6d53d3e94e96..17ecde8bb671d042596feb94657055497a1a38bc 100644 (file)
@@ -101,8 +101,6 @@ 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 \
diff --git a/src/plugins/mail-crypt/fs-crypt-common.c b/src/plugins/mail-crypt/fs-crypt-common.c
deleted file mode 100644 (file)
index a9a24e1..0000000
+++ /dev/null
@@ -1,417 +0,0 @@
-/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "randgen.h"
-#include "istream.h"
-#include "istream-try.h"
-#include "ostream.h"
-#include "dcrypt-iostream.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"
-
-#define FS_CRYPT_ISTREAM_MIN_BUFFER_SIZE 1024
-
-struct crypt_fs {
-       struct fs fs;
-       struct mail_crypt_global_keys keys;
-       bool keys_loaded;
-       bool allow_missing_keys;
-
-       char *enc_algo;
-       char *set_prefix;
-       char *public_key_path;
-       char *private_key_path;
-       char *password;
-};
-
-struct crypt_fs_file {
-       struct fs_file file;
-       struct crypt_fs *fs;
-       struct fs_file *super_read;
-       enum fs_open_mode open_mode;
-       struct istream *input;
-
-       struct ostream *super_output;
-       struct ostream *temp_output;
-};
-
-#define CRYPT_FS(ptr)  container_of((ptr), struct crypt_fs, fs)
-#define CRYPT_FILE(ptr)        container_of((ptr), struct crypt_fs_file, file)
-
-/* defined outside this file */
-extern const struct fs FS_CLASS_CRYPT;
-
-static
-int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r);
-
-static struct fs *fs_crypt_alloc(void)
-{
-       struct crypt_fs *fs;
-
-       fs = i_new(struct crypt_fs, 1);
-       fs->fs = FS_CLASS_CRYPT;
-
-       return &fs->fs;
-}
-
-static int
-fs_crypt_init(struct fs *_fs, const char *args,
-             const struct fs_parameters *params, const char **error_r)
-{
-       struct crypt_fs *fs = CRYPT_FS(_fs);
-       const char *enc_algo, *set_prefix;
-       const char *p, *arg, *value, *error, *parent_name, *parent_args;
-       const char *public_key_path = "", *private_key_path = "", *password = "";
-
-       if (!dcrypt_initialize("openssl", NULL, &error))
-               i_fatal("dcrypt_initialize(): %s", error);
-
-       /* [algo=<s>:][set_prefix=<n>:][public_key_path=<s>:]
-          [private_key_path=<s>:[password=<s>:]]<parent fs> */
-       set_prefix = "mail_crypt_global";
-       enc_algo = "aes-256-gcm-sha256";
-       for (;;) {
-               p = strchr(args, ':');
-               if (p == NULL)
-                       break;
-               arg = t_strdup_until(args, p);
-               if (strcmp(arg, "maybe") == 0) {
-                       fs->allow_missing_keys = TRUE;
-                       args = p + 1;
-                       continue;
-               } else if ((value = strchr(arg, '=')) == NULL)
-                       break;
-               arg = t_strdup_until(arg, value++);
-               args = p+1;
-
-               if (strcmp(arg, "algo") == 0)
-                       enc_algo = value;
-               else if (strcmp(arg, "set_prefix") == 0)
-                       set_prefix = value;
-               else if (strcmp(arg, "public_key_path") == 0)
-                       public_key_path = value;
-               else if (strcmp(arg, "private_key_path") == 0)
-                       private_key_path = value;
-               else if (strcmp(arg, "password") == 0)
-                       password = value;
-               else {
-                       *error_r = t_strdup_printf(
-                               "Invalid parameter '%s'", arg);
-                       return -1;
-               }
-       }
-
-       parent_args = strchr(args, ':');
-       if (parent_args == NULL) {
-               parent_name = args;
-               parent_args = "";
-       } else {
-               parent_name = t_strdup_until(args, parent_args);
-               parent_args++;
-       }
-       if (fs_legacy_init(parent_name, parent_args, _fs->event, params,
-                          &_fs->parent, error_r) < 0)
-               return -1;
-       fs->enc_algo = i_strdup(enc_algo);
-       fs->set_prefix = i_strdup(set_prefix);
-       fs->public_key_path = i_strdup_empty(public_key_path);
-       fs->private_key_path = i_strdup_empty(private_key_path);
-       fs->password = i_strdup_empty(password);
-       return 0;
-}
-
-static void fs_crypt_free(struct fs *_fs)
-{
-       struct crypt_fs *fs = CRYPT_FS(_fs);
-
-       mail_crypt_global_keys_free(&fs->keys);
-       i_free(fs->enc_algo);
-       i_free(fs->set_prefix);
-       i_free(fs->public_key_path);
-       i_free(fs->private_key_path);
-       i_free(fs->password);
-       i_free(fs);
-}
-
-static struct fs_file *fs_crypt_file_alloc(void)
-{
-       struct crypt_fs_file *file = i_new(struct crypt_fs_file, 1);
-       return &file->file;
-}
-
-static void
-fs_crypt_file_init(struct fs_file *_file, const char *path,
-                  enum fs_open_mode mode, enum fs_open_flags flags)
-{
-       struct crypt_fs *fs = CRYPT_FS(_file->fs);
-       struct crypt_fs_file *file = CRYPT_FILE(_file);
-
-       file->file.path = i_strdup(path);
-       file->fs = fs;
-       file->open_mode = mode;
-
-       /* avoid unnecessarily creating two seekable streams */
-       flags &= ENUM_NEGATE(FS_OPEN_FLAG_SEEKABLE);
-
-       file->file.parent = fs_file_init_parent(_file, path, mode, flags);
-       if (mode == FS_OPEN_MODE_READONLY &&
-           (flags & FS_OPEN_FLAG_ASYNC) == 0) {
-               /* use async stream for super, so fs_read_stream() won't create
-                  another seekable stream needlessly */
-               file->super_read = fs_file_init_parent(_file, path,
-                       mode, flags | FS_OPEN_FLAG_ASYNC |
-                       FS_OPEN_FLAG_ASYNC_NOQUEUE);
-       } else {
-               file->super_read = file->file.parent;
-       }
-}
-
-static void fs_crypt_file_deinit(struct fs_file *_file)
-{
-       struct crypt_fs_file *file = CRYPT_FILE(_file);
-
-       if (file->super_read != _file->parent)
-               fs_file_deinit(&file->super_read);
-       fs_file_free(_file);
-       i_free(file->file.path);
-       i_free(file);
-}
-
-static void fs_crypt_file_close(struct fs_file *_file)
-{
-       struct crypt_fs_file *file = CRYPT_FILE(_file);
-
-       i_stream_unref(&file->input);
-       fs_file_close(file->super_read);
-       fs_file_close(_file->parent);
-}
-
-static void fs_crypt_set_metadata(struct fs_file *_file,
-                                 const char *key, const char *value)
-{
-       struct crypt_fs_file *file = CRYPT_FILE(_file);
-
-       fs_set_metadata(_file->parent, key, value);
-       if (file->super_read != NULL)
-               fs_set_metadata(file->super_read, key, value);
-}
-
-static int fs_crypt_read_file(const char *set_name, const char *path,
-                             char **key_data_r, const char **error_r)
-{
-       struct istream *input;
-       int ret;
-
-       input = i_stream_create_file(path, SIZE_MAX);
-       while (i_stream_read(input) > 0) ;
-       if (input->stream_errno != 0) {
-               *error_r = t_strdup_printf("%s: read(%s) failed: %s",
-                       set_name, path, i_stream_get_error(input));
-               ret = -1;
-       } else {
-               size_t size;
-               const unsigned char *data = i_stream_get_data(input, &size);
-               *key_data_r = i_strndup(data, size);
-               ret = 0;
-       }
-       i_stream_unref(&input);
-       return ret;
-}
-
-static int
-fs_crypt_load_keys_from_path(struct crypt_fs *fs, const char **error_r)
-{
-       char *key_data;
-
-       mail_crypt_global_keys_init(&fs->keys);
-       if (fs->public_key_path != NULL) {
-               if (fs_crypt_read_file("crypt:public_key_path",
-                                       fs->public_key_path,
-                                       &key_data, error_r) < 0)
-                       return -1;
-               if (mail_crypt_load_global_public_key("crypt:public_key_path",
-                                                     key_data, &fs->keys,
-                                                     error_r) < 0) {
-                       i_free(key_data);
-                       return -1;
-               }
-               i_free(key_data);
-       }
-       if (fs->private_key_path != NULL) {
-               if (fs_crypt_read_file("crypt:private_key_path",
-                                       fs->private_key_path,
-                                       &key_data, error_r) < 0)
-                       return -1;
-               if (mail_crypt_load_global_private_key("crypt:private_key_path",
-                                                       key_data, "crypt:password",
-                                                       fs->password, &fs->keys,
-                                                       error_r) < 0) {
-                       i_free(key_data);
-                       return -1;
-               }
-               i_free(key_data);
-       }
-       return 0;
-}
-
-static int
-fs_crypt_istream_get_key(const char *pubkey_digest,
-                        struct dcrypt_private_key **priv_key_r,
-                        const char **error_r, void *context)
-{
-       struct crypt_fs_file *file = context;
-
-       if (fs_crypt_load_keys(file->fs, error_r) < 0)
-               return -1;
-
-       *priv_key_r = mail_crypt_global_key_find(&file->fs->keys, pubkey_digest);
-       if (*priv_key_r == NULL)
-               return 0;
-       dcrypt_key_ref_private(*priv_key_r);
-       return 1;
-}
-
-static struct istream *
-fs_crypt_read_stream(struct fs_file *_file, size_t max_buffer_size)
-{
-       struct crypt_fs_file *file = CRYPT_FILE(_file);
-       struct istream *input;
-
-       if (file->input != NULL) {
-               i_stream_ref(file->input);
-               i_stream_seek(file->input, 0);
-               return file->input;
-       }
-
-       input = fs_read_stream(file->super_read,
-               I_MAX(FS_CRYPT_ISTREAM_MIN_BUFFER_SIZE, max_buffer_size));
-       if (input->stream_errno != 0) {
-               file->input = input;
-               i_stream_ref(file->input);
-               return file->input;
-       }
-
-       if (file->fs->allow_missing_keys) {
-               struct istream *decrypted_input =
-                       i_stream_create_decrypt_callback(input,
-                                       fs_crypt_istream_get_key, file);
-               struct istream *plaintext_input =
-                       i_stream_create_noop(input);
-               /* If the file is not encrypted, fall back to reading
-                * it as plaintext. */
-               struct istream *inputs[] = {
-                       decrypted_input,
-                       plaintext_input,
-                       NULL
-               };
-               file->input = istream_try_create(inputs, max_buffer_size);
-               i_stream_unref(&decrypted_input);
-               i_stream_unref(&plaintext_input);
-       } else {
-               file->input = i_stream_create_decrypt_callback(input,
-                                       fs_crypt_istream_get_key, file);
-       }
-       i_stream_unref(&input);
-       i_stream_ref(file->input);
-       return file->input;
-}
-
-static void fs_crypt_write_stream(struct fs_file *_file)
-{
-       struct crypt_fs_file *file = CRYPT_FILE(_file);
-       struct event *event = _file->event;
-       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->allow_missing_keys) {
-                       _file->output = o_stream_create_error_str(EINVAL,
-                               "Encryption required, but no public key available");
-                       return;
-               } else {
-                       e_debug(event,
-                               "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 ||
-           str_begins_with(file->fs->enc_algo, "chacha20-poly1305")) {
-               flags = IO_STREAM_ENC_INTEGRITY_AEAD;
-       } else {
-               flags = IO_STREAM_ENC_INTEGRITY_HMAC;
-       }
-
-       file->temp_output =
-               iostream_temp_create_named(_file->fs->temp_path_prefix,
-                                          IOSTREAM_TEMP_FLAG_TRY_FD_DUP,
-                                          fs_file_path(_file));
-       _file->output = o_stream_create_encrypt(file->temp_output,
-               file->fs->enc_algo, file->fs->keys.public_key,
-               flags);
-}
-
-static int fs_crypt_write_stream_finish(struct fs_file *_file, bool success)
-{
-       struct crypt_fs_file *file = CRYPT_FILE(_file);
-       struct istream *input;
-       int ret;
-
-       if (_file->output != NULL) {
-               if (_file->output == file->super_output)
-                       _file->output = NULL;
-               else
-                       o_stream_unref(&_file->output);
-       }
-       if (!success) {
-               if (file->super_output != NULL) {
-                       /* no encryption */
-                       i_assert(file->temp_output == NULL);
-                       fs_write_stream_abort_error(_file->parent, &file->super_output,
-                                                   "write(%s) failed: %s",
-                                                   o_stream_get_name(file->super_output),
-                                                   o_stream_get_error(file->super_output));
-               } else {
-                       o_stream_destroy(&file->temp_output);
-               }
-               return -1;
-       }
-
-       if (file->super_output != NULL) {
-               /* no encrypt */
-               i_assert(file->temp_output == NULL);
-               return fs_write_stream_finish(_file->parent, &file->super_output);
-       }
-       if (file->temp_output == NULL) {
-               /* finishing up */
-               i_assert(file->super_output == NULL);
-               return fs_write_stream_finish_async(_file->parent);
-       }
-
-       /* finish writing the temporary file */
-       input = iostream_temp_finish(&file->temp_output, IO_BLOCK_SIZE);
-       file->super_output = fs_write_stream(_file->parent);
-       o_stream_nsend_istream(file->super_output, input);
-       ret = fs_write_stream_finish(_file->parent, &file->super_output);
-       i_stream_unref(&input);
-       return ret;
-}
index ccc673e04d2a9668bc32d579f18d7d95f4865934..44382195b1597fd55766691b4eb164026be56efa 100644 (file)
@@ -1,6 +1,262 @@
 /* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
-#define FS_CLASS_CRYPT fs_class_crypt
-#include "fs-crypt-common.c"
+
+#include "lib.h"
+#include "randgen.h"
+#include "istream.h"
+#include "istream-try.h"
+#include "ostream.h"
+#include "dcrypt-iostream.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"
+
+#define FS_CRYPT_ISTREAM_MIN_BUFFER_SIZE 1024
+
+struct crypt_fs {
+       struct fs fs;
+       struct mail_crypt_global_keys keys;
+       bool keys_loaded;
+       bool allow_missing_keys;
+
+       char *enc_algo;
+       char *set_prefix;
+       char *public_key_path;
+       char *private_key_path;
+       char *password;
+};
+
+struct crypt_fs_file {
+       struct fs_file file;
+       struct crypt_fs *fs;
+       struct fs_file *super_read;
+       enum fs_open_mode open_mode;
+       struct istream *input;
+
+       struct ostream *super_output;
+       struct ostream *temp_output;
+};
+
+#define CRYPT_FS(ptr)  container_of((ptr), struct crypt_fs, fs)
+#define CRYPT_FILE(ptr)        container_of((ptr), struct crypt_fs_file, file)
+
+/* defined outside this file */
+extern const struct fs fs_class_crypt;
+
+static 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_parameters *params, const char **error_r)
+{
+       struct crypt_fs *fs = CRYPT_FS(_fs);
+       const char *enc_algo, *set_prefix;
+       const char *p, *arg, *value, *error, *parent_name, *parent_args;
+       const char *public_key_path = "", *private_key_path = "", *password = "";
+
+       if (!dcrypt_initialize("openssl", NULL, &error))
+               i_fatal("dcrypt_initialize(): %s", error);
+
+       /* [algo=<s>:][set_prefix=<n>:][public_key_path=<s>:]
+          [private_key_path=<s>:[password=<s>:]]<parent fs> */
+       set_prefix = "mail_crypt_global";
+       enc_algo = "aes-256-gcm-sha256";
+       for (;;) {
+               p = strchr(args, ':');
+               if (p == NULL)
+                       break;
+               arg = t_strdup_until(args, p);
+               if (strcmp(arg, "maybe") == 0) {
+                       fs->allow_missing_keys = TRUE;
+                       args = p + 1;
+                       continue;
+               } else if ((value = strchr(arg, '=')) == NULL)
+                       break;
+               arg = t_strdup_until(arg, value++);
+               args = p+1;
+
+               if (strcmp(arg, "algo") == 0)
+                       enc_algo = value;
+               else if (strcmp(arg, "set_prefix") == 0)
+                       set_prefix = value;
+               else if (strcmp(arg, "public_key_path") == 0)
+                       public_key_path = value;
+               else if (strcmp(arg, "private_key_path") == 0)
+                       private_key_path = value;
+               else if (strcmp(arg, "password") == 0)
+                       password = value;
+               else {
+                       *error_r = t_strdup_printf(
+                               "Invalid parameter '%s'", arg);
+                       return -1;
+               }
+       }
+
+       parent_args = strchr(args, ':');
+       if (parent_args == NULL) {
+               parent_name = args;
+               parent_args = "";
+       } else {
+               parent_name = t_strdup_until(args, parent_args);
+               parent_args++;
+       }
+       if (fs_legacy_init(parent_name, parent_args, _fs->event, params,
+                          &_fs->parent, error_r) < 0)
+               return -1;
+       fs->enc_algo = i_strdup(enc_algo);
+       fs->set_prefix = i_strdup(set_prefix);
+       fs->public_key_path = i_strdup_empty(public_key_path);
+       fs->private_key_path = i_strdup_empty(private_key_path);
+       fs->password = i_strdup_empty(password);
+       return 0;
+}
+
+static void fs_crypt_free(struct fs *_fs)
+{
+       struct crypt_fs *fs = CRYPT_FS(_fs);
+
+       mail_crypt_global_keys_free(&fs->keys);
+       i_free(fs->enc_algo);
+       i_free(fs->set_prefix);
+       i_free(fs->public_key_path);
+       i_free(fs->private_key_path);
+       i_free(fs->password);
+       i_free(fs);
+}
+
+static struct fs_file *fs_crypt_file_alloc(void)
+{
+       struct crypt_fs_file *file = i_new(struct crypt_fs_file, 1);
+       return &file->file;
+}
+
+static void
+fs_crypt_file_init(struct fs_file *_file, const char *path,
+                  enum fs_open_mode mode, enum fs_open_flags flags)
+{
+       struct crypt_fs *fs = CRYPT_FS(_file->fs);
+       struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+       file->file.path = i_strdup(path);
+       file->fs = fs;
+       file->open_mode = mode;
+
+       /* avoid unnecessarily creating two seekable streams */
+       flags &= ENUM_NEGATE(FS_OPEN_FLAG_SEEKABLE);
+
+       file->file.parent = fs_file_init_parent(_file, path, mode, flags);
+       if (mode == FS_OPEN_MODE_READONLY &&
+           (flags & FS_OPEN_FLAG_ASYNC) == 0) {
+               /* use async stream for super, so fs_read_stream() won't create
+                  another seekable stream needlessly */
+               file->super_read = fs_file_init_parent(_file, path,
+                       mode, flags | FS_OPEN_FLAG_ASYNC |
+                       FS_OPEN_FLAG_ASYNC_NOQUEUE);
+       } else {
+               file->super_read = file->file.parent;
+       }
+}
+
+static void fs_crypt_file_deinit(struct fs_file *_file)
+{
+       struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+       if (file->super_read != _file->parent)
+               fs_file_deinit(&file->super_read);
+       fs_file_free(_file);
+       i_free(file->file.path);
+       i_free(file);
+}
+
+static void fs_crypt_file_close(struct fs_file *_file)
+{
+       struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+       i_stream_unref(&file->input);
+       fs_file_close(file->super_read);
+       fs_file_close(_file->parent);
+}
+
+static void fs_crypt_set_metadata(struct fs_file *_file,
+                                 const char *key, const char *value)
+{
+       struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+       fs_set_metadata(_file->parent, key, value);
+       if (file->super_read != NULL)
+               fs_set_metadata(file->super_read, key, value);
+}
+
+static int fs_crypt_read_file(const char *set_name, const char *path,
+                             char **key_data_r, const char **error_r)
+{
+       struct istream *input;
+       int ret;
+
+       input = i_stream_create_file(path, SIZE_MAX);
+       while (i_stream_read(input) > 0) ;
+       if (input->stream_errno != 0) {
+               *error_r = t_strdup_printf("%s: read(%s) failed: %s",
+                       set_name, path, i_stream_get_error(input));
+               ret = -1;
+       } else {
+               size_t size;
+               const unsigned char *data = i_stream_get_data(input, &size);
+               *key_data_r = i_strndup(data, size);
+               ret = 0;
+       }
+       i_stream_unref(&input);
+       return ret;
+}
+
+static int
+fs_crypt_load_keys_from_path(struct crypt_fs *fs, const char **error_r)
+{
+       char *key_data;
+
+       mail_crypt_global_keys_init(&fs->keys);
+       if (fs->public_key_path != NULL) {
+               if (fs_crypt_read_file("crypt:public_key_path",
+                                       fs->public_key_path,
+                                       &key_data, error_r) < 0)
+                       return -1;
+               if (mail_crypt_load_global_public_key("crypt:public_key_path",
+                                                     key_data, &fs->keys,
+                                                     error_r) < 0) {
+                       i_free(key_data);
+                       return -1;
+               }
+               i_free(key_data);
+       }
+       if (fs->private_key_path != NULL) {
+               if (fs_crypt_read_file("crypt:private_key_path",
+                                       fs->private_key_path,
+                                       &key_data, error_r) < 0)
+                       return -1;
+               if (mail_crypt_load_global_private_key("crypt:private_key_path",
+                                                       key_data, "crypt:password",
+                                                       fs->password, &fs->keys,
+                                                       error_r) < 0) {
+                       i_free(key_data);
+                       return -1;
+               }
+               i_free(key_data);
+       }
+       return 0;
+}
 
 static
 int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r)
@@ -25,6 +281,156 @@ int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r)
        return 0;
 }
 
+static int
+fs_crypt_istream_get_key(const char *pubkey_digest,
+                        struct dcrypt_private_key **priv_key_r,
+                        const char **error_r, void *context)
+{
+       struct crypt_fs_file *file = context;
+
+       if (fs_crypt_load_keys(file->fs, error_r) < 0)
+               return -1;
+
+       *priv_key_r = mail_crypt_global_key_find(&file->fs->keys, pubkey_digest);
+       if (*priv_key_r == NULL)
+               return 0;
+       dcrypt_key_ref_private(*priv_key_r);
+       return 1;
+}
+
+static struct istream *
+fs_crypt_read_stream(struct fs_file *_file, size_t max_buffer_size)
+{
+       struct crypt_fs_file *file = CRYPT_FILE(_file);
+       struct istream *input;
+
+       if (file->input != NULL) {
+               i_stream_ref(file->input);
+               i_stream_seek(file->input, 0);
+               return file->input;
+       }
+
+       input = fs_read_stream(file->super_read,
+               I_MAX(FS_CRYPT_ISTREAM_MIN_BUFFER_SIZE, max_buffer_size));
+
+       if (file->fs->allow_missing_keys) {
+               struct istream *decrypted_input =
+                       i_stream_create_decrypt_callback(input,
+                                       fs_crypt_istream_get_key, file);
+               struct istream *plaintext_input =
+                       i_stream_create_noop(input);
+               /* If the file is not encrypted, fall back to reading
+                * it as plaintext. */
+               struct istream *inputs[] = {
+                       decrypted_input,
+                       plaintext_input,
+                       NULL
+               };
+               file->input = istream_try_create(inputs, max_buffer_size);
+               i_stream_unref(&decrypted_input);
+               i_stream_unref(&plaintext_input);
+       } else {
+               file->input = i_stream_create_decrypt_callback(input,
+                                       fs_crypt_istream_get_key, file);
+       }
+       i_stream_unref(&input);
+       i_stream_ref(file->input);
+       return file->input;
+}
+
+static void fs_crypt_write_stream(struct fs_file *_file)
+{
+       struct crypt_fs_file *file = CRYPT_FILE(_file);
+       struct event *event = _file->event;
+       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->allow_missing_keys) {
+                       _file->output = o_stream_create_error_str(EINVAL,
+                               "Encryption required, but no public key available");
+                       return;
+               } else {
+                       e_debug(event,
+                               "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 ||
+           str_begins_with(file->fs->enc_algo, "chacha20-poly1305")) {
+               flags = IO_STREAM_ENC_INTEGRITY_AEAD;
+       } else {
+               flags = IO_STREAM_ENC_INTEGRITY_HMAC;
+       }
+
+       file->temp_output =
+               iostream_temp_create_named(_file->fs->temp_path_prefix,
+                                          IOSTREAM_TEMP_FLAG_TRY_FD_DUP,
+                                          fs_file_path(_file));
+       _file->output = o_stream_create_encrypt(file->temp_output,
+               file->fs->enc_algo, file->fs->keys.public_key,
+               flags);
+}
+
+static int fs_crypt_write_stream_finish(struct fs_file *_file, bool success)
+{
+       struct crypt_fs_file *file = CRYPT_FILE(_file);
+       struct istream *input;
+       int ret;
+
+       if (_file->output != NULL) {
+               if (_file->output == file->super_output)
+                       _file->output = NULL;
+               else
+                       o_stream_unref(&_file->output);
+       }
+       if (!success) {
+               if (file->super_output != NULL) {
+                       /* no encryption */
+                       i_assert(file->temp_output == NULL);
+                       fs_write_stream_abort_error(_file->parent, &file->super_output,
+                                                   "write(%s) failed: %s",
+                                                   o_stream_get_name(file->super_output),
+                                                   o_stream_get_error(file->super_output));
+               } else {
+                       o_stream_destroy(&file->temp_output);
+               }
+               return -1;
+       }
+
+       if (file->super_output != NULL) {
+               /* no encrypt */
+               i_assert(file->temp_output == NULL);
+               return fs_write_stream_finish(_file->parent, &file->super_output);
+       }
+       if (file->temp_output == NULL) {
+               /* finishing up */
+               i_assert(file->super_output == NULL);
+               return fs_write_stream_finish_async(_file->parent);
+       }
+
+       /* finish writing the temporary file */
+       input = iostream_temp_finish(&file->temp_output, IO_BLOCK_SIZE);
+       file->super_output = fs_write_stream(_file->parent);
+       o_stream_nsend_istream(file->super_output, input);
+       ret = fs_write_stream_finish(_file->parent, &file->super_output);
+       i_stream_unref(&input);
+       return ret;
+}
+
 const struct fs fs_class_crypt = {
        .name = "crypt",
        .v = {