fs-crypt-common.c revision 6d1218e68ce883735ffde9d7907e626ab81b9fb5
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen/* Copyright (c) 2015-2017 Dovecot authors, see the included COPYING file */
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "lib.h"
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen#include "randgen.h"
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen#include "istream.h"
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen#include "ostream.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "istream-decrypt.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "ostream-encrypt.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "iostream-temp.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "mailbox-list.h"
b16ee3cbbcd18cb86f2f73b5cc163ebfb995ffafTimo Sirainen#include "mail-namespace.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "mail-crypt-common.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "mail-crypt-key.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "dcrypt-iostream.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "fs-api-private.h"
539977f9257bd8985be5a8093658da266ae9cd19Timo Sirainen
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainenstruct crypt_fs {
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen struct fs fs;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen struct mail_crypt_global_keys keys;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen bool keys_loaded;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen char *enc_algo;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen char *set_prefix;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen char *public_key_path;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen char *private_key_path;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen char *password;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen};
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstruct crypt_fs_file {
2fb9ae42f9e36388ec6db24188b9108434043fd0Timo Sirainen struct fs_file file;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen struct crypt_fs *fs;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen struct fs_file *super_read;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen enum fs_open_mode open_mode;
563273bdac80393af63b9520cbf4d24cc0efd028Timo Sirainen struct istream *input;
5666a3d6a7ea89362b8d9e8b39b15424cd9d6388Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen struct ostream *super_output;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen struct ostream *temp_output;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen};
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen/* defined outside this file */
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainenextern const struct fs FS_CLASS_CRYPT;
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainenstatic
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainenint fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic struct fs *fs_crypt_alloc(void)
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen{
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen struct crypt_fs *fs;
c7819f72f3f9cbfa976830dc6f3190873469b901Timo Sirainen
c7819f72f3f9cbfa976830dc6f3190873469b901Timo Sirainen fs = i_new(struct crypt_fs, 1);
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen fs->fs = FS_CLASS_CRYPT;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen return &fs->fs;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen}
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainenstatic int
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainenfs_crypt_init(struct fs *_fs, const char *args, const
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen struct fs_settings *set)
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen{
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen struct crypt_fs *fs = (struct crypt_fs *)_fs;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen const char *enc_algo, *set_prefix;
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen const char *p, *arg, *value, *error, *parent_name, *parent_args;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen const char *public_key_path = "", *private_key_path = "", *password = "";
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
2fb9ae42f9e36388ec6db24188b9108434043fd0Timo Sirainen if (!dcrypt_initialize("openssl", NULL, &error))
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen i_fatal("dcrypt_initialize(): %s", error);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen /* [algo=<s>:][set_prefix=<n>:][public_key_path=<s>:]
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen [private_key_path=<s>:[password=<s>:]]<parent fs> */
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen set_prefix = "mail_crypt_global";
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen enc_algo = "aes-256-gcm-sha256";
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen for (;;) {
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen p = strchr(args, ':');
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen if (p == NULL) {
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen fs_set_error(_fs, "Missing parameters");
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen return -1;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen }
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen arg = t_strdup_until(args, p);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if ((value = strchr(arg, '=')) == NULL)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen break;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen arg = t_strdup_until(arg, value++);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen args = p+1;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen if (strcmp(arg, "algo") == 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen enc_algo = value;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen else if (strcmp(arg, "set_prefix") == 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen set_prefix = value;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen else if (strcmp(arg, "public_key_path") == 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen public_key_path = value;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen else if (strcmp(arg, "private_key_path") == 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen private_key_path = value;
70ead6466f9baa8294e71fc2fba0a4f54f488b5eTimo Sirainen else if (strcmp(arg, "password") == 0)
70ead6466f9baa8294e71fc2fba0a4f54f488b5eTimo Sirainen password = value;
70ead6466f9baa8294e71fc2fba0a4f54f488b5eTimo Sirainen else {
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen fs_set_error(_fs, "Invalid parameter '%s'", arg);
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen return -1;
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen }
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen }
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen parent_args = strchr(args, ':');
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if (parent_args == NULL) {
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen parent_name = args;
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen parent_args = "";
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen } else {
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen parent_name = t_strdup_until(args, parent_args);
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen parent_args++;
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen }
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen if (fs_init(parent_name, parent_args, set, &_fs->parent, &error) < 0) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen fs_set_error(_fs, "%s: %s", parent_name, error);
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen return -1;
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen }
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen fs->enc_algo = i_strdup(enc_algo);
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen fs->set_prefix = i_strdup(set_prefix);
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen fs->public_key_path = i_strdup_empty(public_key_path);
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen fs->private_key_path = i_strdup_empty(private_key_path);
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen fs->password = i_strdup_empty(password);
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen 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);
}
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 = (struct crypt_fs *)_file->fs;
struct crypt_fs_file *file = (struct crypt_fs_file *)_file;
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_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);
} else {
file->super_read = file->file.parent;
}
}
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;
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;
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;
}