changeset 20215:389c5094c2a8

lib-dcrypt: Initial implementation
author Aki Tuomi <aki.tuomi@dovecot.fi>
date Wed, 27 Apr 2016 14:08:00 +0300
parents 6a1c780c8136
children 4dd0eae220d3
files configure.ac src/Makefile.am src/lib-dcrypt/Makefile.am src/lib-dcrypt/dcrypt-gnutls.c src/lib-dcrypt/dcrypt-iostream-private.h src/lib-dcrypt/dcrypt-openssl.c src/lib-dcrypt/dcrypt-private.h src/lib-dcrypt/dcrypt.c src/lib-dcrypt/dcrypt.h src/lib-dcrypt/istream-decrypt.c src/lib-dcrypt/istream-decrypt.h src/lib-dcrypt/ostream-encrypt.c src/lib-dcrypt/ostream-encrypt.h src/lib-dcrypt/sample-v1.bin src/lib-dcrypt/sample-v2.bin src/lib-dcrypt/test-crypto.c src/lib-dcrypt/test-stream.c src/lib-storage/index/dbox-single/sdbox-file.c
diffstat 18 files changed, 5332 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Mon May 30 14:45:09 2016 +0300
+++ b/configure.ac	Wed Apr 27 14:08:00 2016 +0300
@@ -2860,6 +2860,7 @@
 src/lib-auth/Makefile
 src/lib-charset/Makefile
 src/lib-compression/Makefile
+src/lib-dcrypt/Makefile
 src/lib-dict/Makefile
 src/lib-dict-extra/Makefile
 src/lib-dns/Makefile
--- a/src/Makefile.am	Mon May 30 14:45:09 2016 +0300
+++ b/src/Makefile.am	Wed Apr 27 14:08:00 2016 +0300
@@ -9,6 +9,7 @@
 	lib-auth \
 	lib-master \
 	lib-charset \
+	lib-dcrypt \
 	lib-dns \
 	lib-dict \
 	lib-sasl \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/Makefile.am	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,64 @@
+noinst_LTLIBRARIES = libdcrypt.la
+pkglib_LTLIBRARIES =
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-test
+
+libdcrypt_la_SOURCES = \
+	dcrypt.c \
+	istream-decrypt.c \
+	ostream-encrypt.c
+
+libdcrypt_la_CFLAGS = $(AM_CPPFLAGS) \
+	-DDCRYPT_MODULE_DIR=\"$(pkglibdir)\"
+
+if BUILD_OPENSSL
+pkglib_LTLIBRARIES += libdcrypt_openssl.la
+libdcrypt_openssl_la_SOURCES = dcrypt-openssl.c
+libdcrypt_openssl_la_DEPENDENCIES = $(SSL_LIBS)
+libdcrypt_openssl_la_LDFLAGS = -module -avoid-version -shared $(SSL_LIBS)
+libdcrypt_openssl_la_CFLAGS = $(AM_CPPFLAGS) \
+	$(SSL_CFLAGS)
+endif
+
+headers = \
+	dcrypt.h \
+	dcrypt-iostream-private.h \
+	dcrypt-private.h \
+	ostream-encrypt.h \
+	istream-decrypt.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+EXTRA_DIST = \
+	sample-v1.bin \
+	sample-v2.bin
+
+check_PROGRAMS = test-crypto test-stream
+
+check: check-am check-test
+
+check-test: all-am
+	for bin in $(check_PROGRAMS); do \
+	  if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+	done
+
+
+noinst_DATA = \
+	sample-v1.bin \
+	sample-v2.bin
+
+LIBDOVECOT_TEST = \
+	../lib-test/libtest.la \
+	../lib/liblib.la \
+	$(MODULE_LIBS)
+
+test_crypto_LDADD = $(LIBDOVECOT_TEST)
+test_crypto_CFLAGS = $(AM_CPPFLAGS) -DDCRYPT_MODULE_DIR=\"$(top_srcdir)/src/lib-dcrypt/.libs\"
+test_crypto_SOURCES = $(libdcrypt_la_SOURCES) test-crypto.c
+
+test_stream_LDADD = $(LIBDOVECOT_TEST)
+test_stream_CFLAGS = $(AM_CPPFLAGS) -DDCRYPT_MODULE_DIR=\"$(top_srcdir)/src/lib-dcrypt/.libs\"
+test_stream_SOURCES = $(libdcrypt_la_SOURCES) test-stream.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/dcrypt-gnutls.c	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,493 @@
+#include "lib.h"
+#include "buffer.h"
+#include "randgen.h"
+#include "array.h"
+#include "hash-method.h"
+#include "pkcs5.h"
+#include "module-dir.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/compat.h>
+#include <gnutls/abstract.h>
+#include <gnutls/crypto.h>
+#include <nettle/version.h>
+#include <nettle/hmac.h>
+#include <nettle/pbkdf2.h>
+#include <nettle/ecc.h>
+
+#include "dcrypt.h"
+#include "dcrypt-private.h"
+
+struct dcrypt_context_symmetric {
+	pool_t pool;
+	gnutls_cipher_hd_t ctx;
+	gnutls_cipher_algorithm_t cipher;
+	gnutls_datum_t key;
+	gnutls_datum_t iv;
+	enum dcrypt_sym_mode mode;
+};
+
+struct dcrypt_context_hmac {
+	pool_t pool;
+	gnutls_hmac_hd_t ctx;
+	gnutls_mac_algorithm_t md;
+	gnutls_datum_t key;
+	size_t klen;
+};
+
+struct dcrypt_public_key {
+	void *ctx;
+};
+
+struct dcrypt_private_key {
+	void *ctx;
+};
+
+static
+int dcrypt_gnutls_private_to_public_key(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r, const char **error_r);
+
+static
+int dcrypt_gnutls_error(int ec, const char **error_r)
+{
+	i_assert(ec < 0);
+	if(error_r != NULL) {
+		*error_r = gnutls_strerror(ec);
+	}
+	return -1;
+}
+
+static
+int dcrypt_gnutls_ctx_sym_create(const char *algorithm, enum dcrypt_sym_mode mode, struct dcrypt_context_symmetric **ctx_r, const char **error_r)
+{
+	gnutls_cipher_algorithm_t cipher = gnutls_cipher_get_id(algorithm);
+	if(cipher == GNUTLS_CIPHER_UNKNOWN) return dcrypt_gnutls_error(cipher, error_r);
+	pool_t pool = pool_alloconly_create("dcrypt gnutls", 128);
+	struct dcrypt_context_symmetric *ctx = p_new(pool, struct dcrypt_context_symmetric, 1);
+	ctx->pool = pool;
+	ctx->cipher = cipher;
+	ctx->mode = mode;
+	*ctx_r = ctx;
+	return 0;
+}
+
+static
+int dcrypt_gnutls_ctx_sym_destroy(struct dcrypt_context_symmetric **ctx)
+{
+	pool_t pool =(*ctx)->pool;
+	gnutls_cipher_deinit((*ctx)->ctx);
+	pool_unref(&pool);
+	*ctx = NULL;
+	return 0;
+}
+
+static
+void dcrypt_gnutls_ctx_sym_set_key(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len)
+{
+	if(ctx->key.data != NULL) p_free(ctx->pool, ctx->key.data);
+	ctx->key.size = I_MIN(key_len,(size_t)gnutls_cipher_get_key_size(ctx->cipher));
+	ctx->key.data = p_malloc(ctx->pool, ctx->key.size);
+	memcpy(ctx->key.data, key, ctx->key.size);
+}
+
+static
+void dcrypt_gnutls_ctx_sym_set_iv(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len)
+{
+	if(ctx->iv.data != NULL) p_free(ctx->pool, ctx->iv.data);
+	ctx->iv.size = I_MIN(iv_len,(size_t)gnutls_cipher_get_iv_size(ctx->cipher));
+	ctx->iv.data = p_malloc(ctx->pool, ctx->iv.size);
+	memcpy(ctx->iv.data, iv, ctx->iv.size);
+}
+
+static
+void dcrypt_gnutls_ctx_sym_set_key_iv_random(struct dcrypt_context_symmetric *ctx)
+{
+	if(ctx->key.data != NULL) p_free(ctx->pool, ctx->key.data);
+	if(ctx->iv.data != NULL) p_free(ctx->pool, ctx->iv.data);
+	ctx->key.data = p_malloc(ctx->pool, gnutls_cipher_get_key_size(ctx->cipher));
+	random_fill(ctx->key.data, gnutls_cipher_get_key_size(ctx->cipher));
+	ctx->key.size = gnutls_cipher_get_key_size(ctx->cipher);
+	ctx->iv.data = p_malloc(ctx->pool, gnutls_cipher_get_iv_size(ctx->cipher));
+	random_fill(ctx->iv.data, gnutls_cipher_get_iv_size(ctx->cipher));
+	ctx->iv.size = gnutls_cipher_get_iv_size(ctx->cipher);
+}
+
+static
+int dcrypt_gnutls_ctx_sym_get_key(struct dcrypt_context_symmetric *ctx, buffer_t *key)
+{
+	if(ctx->key.data == NULL) return -1;
+	buffer_append(key, ctx->key.data, ctx->key.size);
+	return 0;
+}
+static
+int dcrypt_gnutls_ctx_sym_get_iv(struct dcrypt_context_symmetric *ctx, buffer_t *iv)
+{
+	if(ctx->iv.data == NULL) return -1;
+	buffer_append(iv, ctx->iv.data, ctx->iv.size);
+	return 0;
+}
+
+static
+int dcrypt_gnutls_ctx_sym_get_key_length(struct dcrypt_context_symmetric *ctx)
+{
+	return gnutls_cipher_get_iv_size(ctx->cipher);
+}
+static
+int dcrypt_gnutls_ctx_sym_get_iv_length(struct dcrypt_context_symmetric *ctx)
+{
+	return gnutls_cipher_get_iv_size(ctx->cipher);
+}
+static
+int dcrypt_gnutls_ctx_sym_get_block_size(struct dcrypt_context_symmetric *ctx)
+{
+	return gnutls_cipher_get_block_size(ctx->cipher);
+}
+
+static
+int dcrypt_gnutls_ctx_sym_init(struct dcrypt_context_symmetric *ctx, const char **error_r)
+{
+	int ec;
+	ec = gnutls_cipher_init(&(ctx->ctx), ctx->cipher, &ctx->key, &ctx->iv);
+	if(ec < 0) return dcrypt_gnutls_error(ec, error_r);
+	return 0;
+}
+
+static
+int dcrypt_gnutls_ctx_sym_update(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+	int ec;
+	size_t outl = gnutls_cipher_get_block_size(ctx->cipher);
+	unsigned char buf[outl];
+	ec = gnutls_cipher_encrypt2(ctx->ctx, data, data_len, buf, outl);
+	if(ec < 0) return dcrypt_gnutls_error(ec, error_r);
+	buffer_append(result, buf, outl);
+	return ec;
+}
+
+static
+int dcrypt_gnutls_ctx_sym_final(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r)
+{
+	return dcrypt_gnutls_ctx_sym_update(ctx, (const unsigned char*)"", 0, result, error_r);
+}
+
+
+static
+int dcrypt_gnutls_ctx_hmac_create(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r)
+{
+	gnutls_mac_algorithm_t md = gnutls_mac_get_id(algorithm);
+	if (md == GNUTLS_MAC_UNKNOWN) return dcrypt_gnutls_error(md, error_r);
+	pool_t pool = pool_alloconly_create("dcrypt gnutls", 128);
+	struct dcrypt_context_hmac *ctx = p_new(pool, struct dcrypt_context_hmac, 1);
+	ctx->pool = pool;
+	ctx->md = md;
+	*ctx_r = ctx;
+	return 0;
+}
+
+static
+int dcrypt_gnutls_ctx_hmac_destroy(struct dcrypt_context_hmac **ctx)
+{
+	pool_t pool = (*ctx)->pool;
+	gnutls_hmac_deinit((*ctx)->ctx, NULL);
+	pool_unref(&pool);
+	*ctx = NULL;
+	return 0;
+}
+
+
+static
+void dcrypt_gnutls_ctx_hmac_set_key(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len)
+{
+        if(ctx->key.data != NULL) p_free(ctx->pool, ctx->key.data);
+        ctx->key.size = I_MIN(key_len,(size_t)gnutls_hmac_get_len(ctx->md));
+        ctx->key.data = p_malloc(ctx->pool, ctx->key.size);
+        memcpy(ctx->key.data, key, ctx->key.size);
+}
+
+static
+int dcrypt_gnutls_ctx_hmac_get_key(struct dcrypt_context_hmac *ctx, buffer_t *key)
+{
+	if (ctx->key.data == NULL) return -1;
+	buffer_append(key, ctx->key.data, ctx->key.size);
+	return 0;
+}
+
+static
+int dcrypt_gnutls_ctx_hmac_init(struct dcrypt_context_hmac *ctx, const char **error_r)
+{
+	int ec;
+	ec = gnutls_hmac_init(&(ctx->ctx), ctx->md, ctx->key.data, ctx->key.size);
+	if (ec < 0) return dcrypt_gnutls_error(ec, error_r);
+	return 0;
+}
+static
+int dcrypt_gnutls_ctx_hmac_update(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r)
+{
+	int ec;
+	if ((ec = gnutls_hmac(ctx->ctx, data, data_len)) != 0)
+		return dcrypt_gnutls_error(ec, error_r);
+	return 0;
+}
+static
+int dcrypt_gnutls_ctx_hmac_final(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r)
+{
+	size_t hlen = gnutls_hmac_get_len(ctx->md);
+	unsigned char buf[hlen];
+	gnutls_hmac_output(ctx->ctx, buf);
+	buffer_append(result, buf, hlen);
+	return 0;
+}
+
+static
+int dcrypt_gnutls_ecdh_derive_secret(struct dcrypt_public_key *peer_key, buffer_t *R, buffer_t *S, const char **error_r)
+{
+
+}
+
+static
+int dcrypt_gnutls_pbkdf2(const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, const char *algorithm,
+	unsigned int rounds, buffer_t *result, unsigned int result_len, const char **error_r)
+{
+	unsigned char buf[result_len];
+	/* only sha1 or sha256 is supported */
+	if (strncasecmp(algorithm, "sha1", 4) == 0) {
+		pbkdf2_hmac_sha1(password_len, password, rounds, salt_len, salt, result_len, buf);
+	} else if (strncasecmp(algorithm, "sha256", 6) == 0) {
+		pbkdf2_hmac_sha256(password_len, password, rounds, salt_len, salt, result_len, buf);
+	} else if (strncasecmp(algorithm, "sha512", 6) == 0) {
+		struct hmac_sha512_ctx ctx;
+		hmac_sha512_set_key(&ctx, password_len, password);
+		PBKDF2(&ctx, hmac_sha512_update, hmac_sha512_digest, 64, rounds, salt_len, salt, result_len, buf);
+		memset(&ctx, 0, sizeof(ctx));
+	} else {
+		*error_r = "Unsupported algorithm";
+		return -1;
+	}
+	buffer_append(result, buf, result_len);
+	memset(buf, 0, sizeof(buf));
+	return 0;
+}
+
+static
+int dcrypt_gnutls_generate_keypair(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind, unsigned int bits, const char *curve, const char **error_r)
+{
+	gnutls_pk_algorithm_t pk_algo;
+	gnutls_ecc_curve_t pk_curve;
+
+        if (kind == DCRYPT_KEY_EC) {
+		pk_curve = gnutls_ecc_curve_get_id(curve);
+		if (pk_curve == GNUTLS_ECC_CURVE_INVALID) {
+			*error_r = "Invalid curve";
+			return -1;
+		}
+		bits = GNUTLS_CURVE_TO_BITS(pk_curve);
+#if GNUTLS_VERSION_NUMBER >= 0x030500
+		pk_algo = gnutls_curve_get_pk(pk_curve);
+#else
+		pk_algo = GNUTLS_PK_EC;
+#endif 
+        } else if (kind == DCRYPT_KEY_RSA) {
+                pk_algo = gnutls_pk_get_id("RSA");
+        } else {
+		*error_r = "Unsupported key type";
+		return -1;
+	}
+
+	int ec;
+	gnutls_privkey_t priv;
+	if ((ec = gnutls_privkey_init(&priv)) != GNUTLS_E_SUCCESS) return dcrypt_gnutls_error(ec, error_r);
+#if GNUTLS_VERSION_NUMBER >= 0x030500
+	gnutls_privkey_set_flags(priv, GNUTLS_PRIVKEY_FLAG_EXPORT_COMPAT);
+#endif
+	ec = gnutls_privkey_generate(priv, pk_algo, bits, 0);
+	if (ec != GNUTLS_E_SUCCESS) {
+		gnutls_privkey_deinit(priv);
+		return dcrypt_gnutls_error(ec, error_r);
+	}
+
+	pair_r->priv = (struct dcrypt_private_key*)priv;
+
+	return dcrypt_gnutls_private_to_public_key(pair_r->priv, &(pair_r->pub), error_r);
+} 
+
+static
+int dcrypt_gnutls_load_private_key(struct dcrypt_private_key **key_r, const unsigned char *data, size_t data_len, dcrypt_password_cb *cb, void *ctx, const char **error_r)
+{
+
+}
+static
+int dcrypt_gnutls_load_public_key(struct dcrypt_public_key **key_r, const unsigned char *data, size_t data_len, const char **error_r)
+{
+
+}
+
+static
+int dcrypt_gnutls_store_private_key(struct dcrypt_private_key *key, const char *cipher, buffer_t *destination, dcrypt_password_cb *cb, void *ctx, const char **error_r)
+{
+	gnutls_privkey_t priv = (gnutls_privkey_t)key;
+	gnutls_x509_privkey_t xkey;
+	gnutls_privkey_export_x509(priv, &xkey);
+	/* then export PEM */
+	size_t outl = 0;
+	gnutls_x509_privkey_export_pkcs8(xkey, GNUTLS_X509_FMT_PEM, NULL, 0, NULL, &outl);
+	char buffer[outl];
+	gnutls_x509_privkey_export_pkcs8(xkey, GNUTLS_X509_FMT_PEM, NULL, 0, buffer, &outl);
+	buffer_append(destination, buffer, outl);
+	memset(buffer, 0, sizeof(buffer));
+	return 0;
+}
+
+static
+int dcrypt_gnutls_store_public_key(struct dcrypt_public_key *key, buffer_t *destination, const char **error_r)
+{
+	gnutls_pubkey_t pub = (gnutls_pubkey_t)key;
+	size_t outl = 0;
+	gnutls_pubkey_export(pub, GNUTLS_X509_FMT_PEM, NULL, &outl);
+	char buffer[outl];
+	gnutls_pubkey_export(pub, GNUTLS_X509_FMT_PEM, buffer, &outl);
+	buffer_append(destination, buffer, outl);
+	return 0;
+}
+
+static
+int dcrypt_gnutls_private_to_public_key(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r, const char **error_r)
+{
+	int ec;
+
+	gnutls_privkey_t priv = (gnutls_privkey_t)priv_key;
+	if (gnutls_privkey_get_pk_algorithm(priv, NULL) == GNUTLS_PK_RSA) {
+		gnutls_datum_t m,e;
+		/* do not extract anything we don't need */
+		ec = gnutls_privkey_export_rsa_raw(priv, &m, &e, NULL, NULL, NULL, NULL, NULL, NULL);
+		if (ec != GNUTLS_E_SUCCESS) return dcrypt_gnutls_error(ec, error_r);
+		gnutls_pubkey_t pub;
+		gnutls_pubkey_init(&pub);
+		ec = gnutls_pubkey_import_rsa_raw(pub, &m, &e);
+		gnutls_free(m.data);
+		gnutls_free(e.data);
+		if (ec < 0) {
+			gnutls_pubkey_deinit(pub);
+			return dcrypt_gnutls_error(ec, error_r);
+		}
+		*pub_key_r = (struct dcrypt_public_key*)pub;
+		return 0;
+	} else if (gnutls_privkey_get_pk_algorithm(priv, NULL) == GNUTLS_PK_EC) {
+		gnutls_ecc_curve_t curve;
+		gnutls_datum_t x,y,k;
+		ec = gnutls_privkey_export_ecc_raw(priv, &curve, &x, &y, NULL);
+		if (ec != GNUTLS_E_SUCCESS) return dcrypt_gnutls_error(ec, error_r);
+		gnutls_pubkey_t pub;
+		gnutls_pubkey_init(&pub);
+		ec = gnutls_pubkey_import_ecc_raw(pub, curve, &x, &y);
+		gnutls_free(x.data);
+		gnutls_free(y.data);
+		if (ec < 0) {
+			gnutls_pubkey_deinit(pub);
+			return dcrypt_gnutls_error(ec, error_r);
+		}
+		*pub_key_r = (struct dcrypt_public_key*)pub;
+		return 0;
+	}
+
+	return -1;
+}
+
+static
+void dcrypt_gnutls_free_public_key(struct dcrypt_public_key **key)
+{
+	gnutls_pubkey_deinit((gnutls_pubkey_t)*key);
+	*key = NULL;
+}
+static
+void dcrypt_gnutls_free_private_key(struct dcrypt_private_key **key)
+{
+	gnutls_privkey_deinit((gnutls_privkey_t)*key);
+	*key = NULL;
+}
+static
+void dcrypt_gnutls_free_keypair(struct dcrypt_keypair *keypair)
+{
+	dcrypt_gnutls_free_public_key(&(keypair->pub));
+	dcrypt_gnutls_free_private_key(&(keypair->priv));
+}
+
+static
+int dcrypt_gnutls_rsa_encrypt(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+
+}
+static
+int dcrypt_gnutls_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+
+}
+
+static
+int dcrypt_gnutls_oid_keytype(const unsigned char *oid, size_t oid_len, enum dcrypt_key_type *key_type, const char **error_r)
+{
+
+}
+static
+int dcrypt_gnutls_keytype_oid(enum dcrypt_key_type key_type, buffer_t *oid, const char **error_r)
+{
+
+}
+
+static
+const char *dcrypt_gnutls_oid2name(const unsigned char *oid, size_t oid_len, const char **error_r)
+{
+}
+
+static
+int dcrypt_gnutls_name2oid(const char *name, buffer_t *oid, const char **error_r)
+{
+
+}
+
+static struct dcrypt_vfs dcrypt_gnutls_vfs = {
+	.ctx_sym_create = dcrypt_gnutls_ctx_sym_create,
+	.ctx_sym_destroy = dcrypt_gnutls_ctx_sym_destroy,
+	.ctx_sym_set_key = dcrypt_gnutls_ctx_sym_set_key,
+	.ctx_sym_set_iv = dcrypt_gnutls_ctx_sym_set_iv,
+	.ctx_sym_set_key_iv_random = dcrypt_gnutls_ctx_sym_set_key_iv_random,
+	.ctx_sym_get_key = dcrypt_gnutls_ctx_sym_get_key,
+	.ctx_sym_get_iv = dcrypt_gnutls_ctx_sym_get_iv,
+	.ctx_sym_get_key_length = dcrypt_gnutls_ctx_sym_get_key_length,
+	.ctx_sym_get_iv_length = dcrypt_gnutls_ctx_sym_get_iv_length,
+	.ctx_sym_init = dcrypt_gnutls_ctx_sym_init,
+	.ctx_sym_update = dcrypt_gnutls_ctx_sym_update,
+	.ctx_sym_final = dcrypt_gnutls_ctx_sym_final,
+	.ctx_hmac_create = dcrypt_gnutls_ctx_hmac_create,
+	.ctx_hmac_destroy = dcrypt_gnutls_ctx_hmac_destroy,
+	.ctx_hmac_set_key = dcrypt_gnutls_ctx_hmac_set_key,
+	.ctx_hmac_get_key = dcrypt_gnutls_ctx_hmac_get_key,
+	.ctx_hmac_init = dcrypt_gnutls_ctx_hmac_init,
+	.ctx_hmac_update = dcrypt_gnutls_ctx_hmac_update,
+	.ctx_hmac_final = dcrypt_gnutls_ctx_hmac_final,
+//	.ecdh_derive_secret = dcrypt_gnutls_ecdh_derive_secret,
+	.pbkdf2 = dcrypt_gnutls_pbkdf2,
+	.generate_keypair = dcrypt_gnutls_generate_keypair,
+	.load_private_key = dcrypt_gnutls_load_private_key,
+	.load_public_key = dcrypt_gnutls_load_public_key,
+	.store_private_key = dcrypt_gnutls_store_private_key,
+	.store_public_key = dcrypt_gnutls_store_public_key,
+	.private_to_public_key = dcrypt_gnutls_private_to_public_key,
+	.free_keypair = dcrypt_gnutls_free_keypair,
+	.free_public_key = dcrypt_gnutls_free_public_key,
+	.free_private_key = dcrypt_gnutls_free_private_key,
+	.rsa_encrypt = dcrypt_gnutls_rsa_encrypt,
+	.rsa_decrypt = dcrypt_gnutls_rsa_decrypt,
+	.oid_keytype = dcrypt_gnutls_oid_keytype,
+	.keytype_oid = dcrypt_gnutls_keytype_oid,
+	.oid2name = dcrypt_gnutls_oid2name,
+	.name2oid = dcrypt_gnutls_name2oid
+};
+
+void dcrypt_gnutls_init(struct module *module ATTR_UNUSED)
+{
+	gnutls_global_init();
+	dcrypt_set_vfs(&dcrypt_gnutls_vfs);
+}
+
+void dcrypt_gnutls_deinit(void)
+{
+	gnutls_global_deinit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/dcrypt-iostream-private.h	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,8 @@
+#ifndef DCRYPT_IOSTREAM_PRIVATE_H
+#define DCRYPT_IOSTREAM_PRIVATE 1
+
+static const unsigned char IOSTREAM_CRYPT_MAGIC[] = {'C','R','Y','P','T','E','D','\x03','\x07'};
+#define IOSTREAM_CRYPT_VERSION 2
+#define IOSTREAM_TAG_SIZE 16
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/dcrypt-openssl.c	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,2015 @@
+#include "lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "safe-memset.h"
+#include "randgen.h"
+#include "array.h"
+#include "module-dir.h"
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <openssl/err.h>
+#include <openssl/rsa.h>
+#include <openssl/ec.h>
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/engine.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include "dcrypt.h"
+#include "dcrypt-private.h"
+
+/**
+
+ key format documentation:
+ =========================
+
+ v1 key
+ ------
+ algo id = openssl NID
+ enctype = 0 = none, 1 = ecdhe, 2 = password
+ key id = sha256(hex encoded public point)
+
+ public key
+ ----------
+ 1<tab>algo id<tab>public point
+
+ private key
+ -----------
+ - enctype none
+ 1<tab>algo id<tab>0<tab>private point<tab>key id
+
+ - enctype ecdh (algorithm AES-256-CTR, key = SHA256(shared secret), IV = \0\0\0...)
+ 1<tab>algo id<tab>1<tab>private point<tab>ephemeral public key<tab>encryption key id<tab>key id
+
+ - enctype password (algorithm AES-256-CTR, key = PBKDF2(SHA1, 16, password, salt), IV = \0\0\0...)
+ 1<tab>algo id<tab>2<tab>private point<tab>salt<tab>key id
+
+ v2 key
+ ------
+ algo oid = ASN1 OID of key algorithm (RSA or EC curve)
+ enctype = 0 = none, 1 = ecdhe, 2 = password
+ key id = SHA256(i2d_PUBKEY)
+
+ public key
+ ----------
+ 2<tab>HEX(i2d_PUBKEY)
+
+ - enctype none
+ 2<tab>key algo oid<tab>0<tab>(RSA = i2d_PrivateKey, EC=Private Point)<tab>key id
+
+ - enctype ecdh, key,iv = PBKDF2(hash algo, rounds, shared secret, salt)
+ 2<tab>key algo oid<tab>1<tab>symmetric algo name<tab>salt<tab>hash algo<tab>rounds<tab>E(RSA = i2d_PrivateKey, EC=Private Point)<tab>ephemeral public key<tab>encryption key id<tab>key id
+
+ - enctype password, key,iv = PBKDF2(hash algo, rounds, password, salt)
+  2<tab>key algo oid<tab>1<tab>symmetric algo name<tab>salt<tab>hash algo<tab>rounds<tab>E(RSA = i2d_PrivateKey, EC=Private Point)<tab>key id
+**/
+
+struct dcrypt_context_symmetric {
+	pool_t pool;
+	const EVP_CIPHER *cipher;
+	EVP_CIPHER_CTX *ctx;
+	unsigned char *key;
+	unsigned char *iv;
+	unsigned char *aad;
+	size_t aad_len;
+	unsigned char *tag;
+	size_t tag_len;
+	int padding;
+	int mode;
+};
+
+struct dcrypt_context_hmac {
+	pool_t pool;
+	const EVP_MD *md;
+#if SSLEAY_VERSION_NUMBER >= 0x1010000fL
+	HMAC_CTX *ctx;
+#else
+	HMAC_CTX ctx;
+#endif
+	unsigned char *key;
+	size_t klen;
+};
+
+struct dcrypt_public_key {
+	void *ctx;
+};
+
+struct dcrypt_private_key {
+	void *ctx;
+};
+
+static
+bool dcrypt_openssl_private_to_public_key(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key, const char **error_r);
+static
+bool dcrypt_openssl_public_key_id(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r);
+static
+bool dcrypt_openssl_public_key_id_old(struct dcrypt_public_key *key, buffer_t *result, const char **error_r);
+static
+bool dcrypt_openssl_private_to_public_key(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r, const char **error_r ATTR_UNUSED);
+static
+void dcrypt_openssl_free_private_key(struct dcrypt_private_key **key);
+static
+void dcrypt_openssl_free_public_key(struct dcrypt_public_key **key);
+static
+bool dcrypt_openssl_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r);
+
+static
+bool dcrypt_openssl_error(const char **error_r)
+{
+	if(error_r == NULL) return FALSE; /* caller is not really interested */
+	unsigned long ec = ERR_get_error();
+	*error_r = t_strdup_printf("%s", ERR_error_string(ec, NULL));
+	return FALSE;
+}
+
+/* legacy function for old formats that generates
+   hex encoded point from EC public key
+ */
+static
+char *ec_key_get_pub_point_hex(const EC_KEY *key)
+{
+	const EC_POINT *p;
+	const EC_GROUP *g;
+
+	p = EC_KEY_get0_public_key(key);
+	g = EC_KEY_get0_group(key);
+	return EC_POINT_point2hex(g, p, POINT_CONVERSION_COMPRESSED, NULL);
+}
+
+static
+bool dcrypt_openssl_ctx_sym_create(const char *algorithm, enum dcrypt_sym_mode mode, struct dcrypt_context_symmetric **ctx_r, const char **error_r)
+{
+	struct dcrypt_context_symmetric *ctx;
+	pool_t pool;
+	const EVP_CIPHER *cipher;
+	cipher = EVP_get_cipherbyname(algorithm);
+	if (cipher == NULL) {
+		if (error_r != NULL)
+			*error_r = t_strdup_printf("Invalid cipher %s", algorithm);
+		return FALSE;
+	}
+	/* allocate context */
+	pool = pool_alloconly_create("dcrypt openssl", 1024);
+	ctx = p_new(pool, struct dcrypt_context_symmetric, 1);
+	ctx->pool = pool;
+	ctx->cipher = cipher;
+	ctx->padding = 1;
+	ctx->mode =( mode == DCRYPT_MODE_ENCRYPT ? 1 : 0 );
+	*ctx_r = ctx;
+	return TRUE;
+}
+
+static
+void dcrypt_openssl_ctx_sym_destroy(struct dcrypt_context_symmetric **ctx)
+{
+	pool_t pool = (*ctx)->pool;
+	if ((*ctx)->ctx) EVP_CIPHER_CTX_free((*ctx)->ctx);
+	pool_unref(&pool);
+	*ctx = NULL;
+}
+
+static
+void dcrypt_openssl_ctx_sym_set_key(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len)
+{
+	if(ctx->key != NULL) p_free(ctx->pool, ctx->key);
+	ctx->key = p_malloc(ctx->pool, EVP_CIPHER_key_length(ctx->cipher));
+	memcpy(ctx->key, key, I_MIN(key_len,(size_t)EVP_CIPHER_key_length(ctx->cipher)));
+}
+
+static
+void dcrypt_openssl_ctx_sym_set_iv(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len)
+{
+	if(ctx->iv != NULL) p_free(ctx->pool, ctx->iv);
+	ctx->iv = p_malloc(ctx->pool, EVP_CIPHER_iv_length(ctx->cipher));
+	memcpy(ctx->iv, iv, I_MIN(iv_len,(size_t)EVP_CIPHER_iv_length(ctx->cipher)));
+}
+
+static
+void dcrypt_openssl_ctx_sym_set_key_iv_random(struct dcrypt_context_symmetric *ctx)
+{
+	if(ctx->key != NULL) p_free(ctx->pool, ctx->key);
+	if(ctx->iv != NULL) p_free(ctx->pool, ctx->iv);
+	ctx->key = p_malloc(ctx->pool, EVP_CIPHER_key_length(ctx->cipher));
+	random_fill(ctx->key, EVP_CIPHER_key_length(ctx->cipher));
+	ctx->iv = p_malloc(ctx->pool, EVP_CIPHER_iv_length(ctx->cipher));
+	random_fill(ctx->iv, EVP_CIPHER_iv_length(ctx->cipher));
+}
+
+static
+void dcrypt_openssl_ctx_sym_set_padding(struct dcrypt_context_symmetric *ctx, bool padding)
+{
+	ctx->padding = (padding?1:0);
+}
+
+static
+bool dcrypt_openssl_ctx_sym_get_key(struct dcrypt_context_symmetric *ctx, buffer_t *key)
+{
+	if(ctx->key == NULL) return FALSE;
+	buffer_append(key, ctx->key, EVP_CIPHER_key_length(ctx->cipher));
+	return TRUE;
+}
+static
+bool dcrypt_openssl_ctx_sym_get_iv(struct dcrypt_context_symmetric *ctx, buffer_t *iv)
+{
+	if(ctx->iv == NULL) return FALSE;
+	buffer_append(iv, ctx->iv, EVP_CIPHER_iv_length(ctx->cipher));
+	return TRUE;
+}
+
+static
+void dcrypt_openssl_ctx_sym_set_aad(struct dcrypt_context_symmetric *ctx, const unsigned char *aad, size_t aad_len)
+{
+	if (ctx->aad != NULL) p_free(ctx->pool, ctx->aad);
+	/* allow empty aad */
+	ctx->aad = p_malloc(ctx->pool, I_MAX(1,aad_len));
+	memcpy(ctx->aad, aad, aad_len);
+	ctx->aad_len = aad_len;
+}
+
+static
+bool dcrypt_openssl_ctx_sym_get_aad(struct dcrypt_context_symmetric *ctx, buffer_t *aad)
+{
+	if (ctx->aad == NULL) return FALSE;
+	buffer_append(aad, ctx->aad, ctx->aad_len);
+	return TRUE;
+}
+
+static
+void dcrypt_openssl_ctx_sym_set_tag(struct dcrypt_context_symmetric *ctx, const unsigned char *tag, size_t tag_len)
+{
+	if (ctx->tag != NULL) p_free(ctx->pool, ctx->tag);
+	/* unlike aad, tag cannot be empty */
+	ctx->tag = p_malloc(ctx->pool, tag_len);
+	memcpy(ctx->tag, tag, tag_len);
+	ctx->tag_len = tag_len;
+}
+
+static
+bool dcrypt_openssl_ctx_sym_get_tag(struct dcrypt_context_symmetric *ctx, buffer_t *tag)
+{
+	if (ctx->tag == NULL) return FALSE;
+	buffer_append(tag, ctx->tag, ctx->tag_len);
+	return TRUE;
+}
+
+static
+unsigned int dcrypt_openssl_ctx_sym_get_key_length(struct dcrypt_context_symmetric *ctx)
+{
+	return EVP_CIPHER_iv_length(ctx->cipher);
+}
+static
+unsigned int dcrypt_openssl_ctx_sym_get_iv_length(struct dcrypt_context_symmetric *ctx)
+{
+	return EVP_CIPHER_iv_length(ctx->cipher);
+}
+static
+unsigned int dcrypt_openssl_ctx_sym_get_block_size(struct dcrypt_context_symmetric *ctx)
+{
+	return EVP_CIPHER_block_size(ctx->cipher);
+}
+
+static
+bool dcrypt_openssl_ctx_sym_init(struct dcrypt_context_symmetric *ctx, const char **error_r)
+{
+	int ec;
+	int len;
+	i_assert(ctx->key != NULL);
+	i_assert(ctx->iv != NULL);
+	i_assert(ctx->ctx == NULL);
+
+	if((ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
+		return dcrypt_openssl_error(error_r);
+
+	ec = EVP_CipherInit_ex(ctx->ctx, ctx->cipher, NULL, ctx->key, ctx->iv, ctx->mode);
+	if (ec != 1) return dcrypt_openssl_error(error_r);
+	EVP_CIPHER_CTX_set_padding(ctx->ctx, ctx->padding);
+	len = 0;
+	if (ctx->aad != NULL) ec = EVP_CipherUpdate(ctx->ctx, NULL, &len, ctx->aad, ctx->aad_len);
+	if (ec != 1) return dcrypt_openssl_error(error_r);
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_ctx_sym_update(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+	const size_t block_size = (size_t)EVP_CIPHER_block_size(ctx->cipher);
+	size_t buf_used = result->used;
+	unsigned char *buf;
+	int outl;
+
+	i_assert(ctx->ctx != NULL);
+
+	/* From `man 3 evp_cipherupdate`:
+
+	   EVP_EncryptUpdate() encrypts inl bytes from the buffer in and writes
+	   the encrypted version to out. This function can be called multiple
+	   times to encrypt successive blocks of data. The amount of data written
+	   depends on the block alignment of the encrypted data: as a result the
+	   amount of data written may be anything from zero bytes to
+	   (inl + cipher_block_size - 1) so out should contain sufficient room.
+	   The actual number of bytes written is placed in outl.
+	 */
+
+	buf = buffer_append_space_unsafe(result, data_len + block_size);
+	outl = 0;
+	if (EVP_CipherUpdate
+		(ctx->ctx, buf, &outl, data, data_len) != 1)
+		return dcrypt_openssl_error(error_r);
+	buffer_set_used_size(result, buf_used + outl);
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_ctx_sym_final(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r)
+{
+	const size_t block_size = (size_t)EVP_CIPHER_block_size(ctx->cipher);
+	size_t buf_used = result->used;
+	unsigned char *buf;
+	int outl;
+	int ec;
+
+	i_assert(ctx->ctx != NULL);
+
+	/* From `man 3 evp_cipherupdate`:
+
+	   If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts
+	   the "final" data, that is any data that remains in a partial block. It
+	   uses standard block padding (aka PKCS padding). The encrypted final data
+	   is written to out which should have sufficient space for one cipher
+	   block. The number of bytes written is placed in outl. After this
+	   function is called the encryption operation is finished and no further
+	   calls to EVP_EncryptUpdate() should be made.
+	 */
+
+	buf = buffer_append_space_unsafe(result, block_size);
+	outl = 0;
+
+	/* when **DECRYPTING** set expected tag */
+	if (ctx->mode == 0 && ctx->tag != NULL) {
+		ec = EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_SET_TAG, ctx->tag_len, ctx->tag);
+	} else ec = 1;
+
+	if (ec == 1)
+		ec = EVP_CipherFinal_ex(ctx->ctx, buf, &outl);
+
+	if (ec == 1) {
+		buffer_set_used_size(result, buf_used + outl);
+		/* when **ENCRYPTING** recover tag */
+		if (ctx->mode == 1 && ctx->aad != NULL) {
+			/* tag should be NULL here */
+			i_assert(ctx->tag == NULL);
+			/* openssl claims taglen is always 16, go figure .. */
+			ctx->tag = p_malloc(ctx->pool, EVP_GCM_TLS_TAG_LEN);
+			ec = EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, ctx->tag);
+			ctx->tag_len = EVP_GCM_TLS_TAG_LEN;
+		}
+	}
+
+	if (ec == 0 && error_r != NULL)
+		*error_r = "data authentication failed";
+	else if (ec < 0) dcrypt_openssl_error(error_r);
+
+	EVP_CIPHER_CTX_free(ctx->ctx);
+	ctx->ctx = NULL;
+
+	return ec == 1;
+}
+
+static
+bool dcrypt_openssl_ctx_hmac_create(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r)
+{
+	struct dcrypt_context_hmac *ctx;
+	pool_t pool;
+	const EVP_MD *md;
+	md = EVP_get_digestbyname(algorithm);
+	if(md == NULL) {
+		*error_r = t_strdup_printf("Invalid digest %s", algorithm);
+		return FALSE;
+	}
+	/* allocate context */
+	pool = pool_alloconly_create("dcrypt openssl", 1024);
+	ctx = p_new(pool, struct dcrypt_context_hmac, 1);
+	ctx->pool = pool;
+	ctx->md = md;
+	*ctx_r = ctx;
+	return TRUE;
+}
+
+static
+void dcrypt_openssl_ctx_hmac_destroy(struct dcrypt_context_hmac **ctx)
+{
+	pool_t pool = (*ctx)->pool;
+#if SSLEAY_VERSION_NUMBER >= 0x1010000fL
+	if ((*ctx)->ctx) HMAC_CTX_free((*ctx)->ctx);
+#else
+	HMAC_cleanup(&((*ctx)->ctx));
+#endif
+	pool_unref(&pool);
+	*ctx = NULL;
+}
+
+static
+void dcrypt_openssl_ctx_hmac_set_key(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len)
+{
+	if(ctx->key != NULL) p_free(ctx->pool, ctx->key);
+	ctx->klen = I_MIN(key_len, HMAC_MAX_MD_CBLOCK);
+	ctx->key = p_malloc(ctx->pool, ctx->klen);
+	memcpy(ctx->key, key, ctx->klen);
+}
+static
+bool dcrypt_openssl_ctx_hmac_get_key(struct dcrypt_context_hmac *ctx, buffer_t *key)
+{
+	if(ctx->key == NULL) return FALSE;
+	buffer_append(key, ctx->key, ctx->klen);
+	return TRUE;
+}
+static
+void dcrypt_openssl_ctx_hmac_set_key_random(struct dcrypt_context_hmac *ctx)
+{
+	ctx->klen = HMAC_MAX_MD_CBLOCK;
+	ctx->key = p_malloc(ctx->pool, ctx->klen);
+	random_fill(ctx->key, ctx->klen);
+}
+
+static
+unsigned int dcrypt_openssl_ctx_hmac_get_digest_length(struct dcrypt_context_hmac *ctx)
+{
+	return EVP_MD_size(ctx->md);
+}
+
+static
+bool dcrypt_openssl_ctx_hmac_init(struct dcrypt_context_hmac *ctx, const char **error_r)
+{
+	int ec;
+	i_assert(ctx->md != NULL);
+#if SSLEAY_VERSION_NUMBER >= 0x1010000fL
+	ctx->ctx = HMAC_CTX_new();
+	if (ctx->ctx == NULL) return FALSE;
+	ec = HMAC_Init_ex(ctx->ctx, ctx->key, ctx->klen, ctx->md, NULL);
+#else
+	HMAC_CTX_init(&ctx->ctx);
+	ec = HMAC_Init_ex(&(ctx->ctx), ctx->key, ctx->klen, ctx->md, NULL);
+#endif
+	if (ec != 1) return dcrypt_openssl_error(error_r);
+	return TRUE;
+}
+static
+bool dcrypt_openssl_ctx_hmac_update(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r)
+{
+	int ec;
+#if SSLEAY_VERSION_NUMBER >= 0x1010000fL
+	ec = HMAC_Update(ctx->ctx, data, data_len);
+#else
+	ec = HMAC_Update(&(ctx->ctx), data, data_len);
+#endif
+	if (ec != 1) return dcrypt_openssl_error(error_r);
+	return TRUE;
+}
+static
+bool dcrypt_openssl_ctx_hmac_final(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r)
+{
+	int ec;
+	unsigned char buf[HMAC_MAX_MD_CBLOCK];
+	unsigned int outl;
+#if SSLEAY_VERSION_NUMBER >= 0x1010000fL
+	ec = HMAC_Final(ctx->ctx, buf, &outl);
+	HMAC_CTX_free(ctx->ctx);
+	ctx->ctx = NULL;
+#else
+	ec = HMAC_Final(&(ctx->ctx), buf, &outl);
+	HMAC_cleanup(&(ctx->ctx));
+#endif
+	if (ec == 1) {
+		buffer_append(result, buf, outl);
+	} else return dcrypt_openssl_error(error_r);
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_generate_ec_key(int nid, EVP_PKEY **key, const char **error_r)
+{
+	EVP_PKEY_CTX *pctx;
+	EVP_PKEY_CTX *ctx;
+	EVP_PKEY *params = NULL;
+
+	/* generate parameters for EC */
+	pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+	if (pctx == NULL ||
+	    EVP_PKEY_paramgen_init(pctx) < 1 ||
+	    EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid) < 1 ||
+	    EVP_PKEY_paramgen(pctx, &params) < 1)
+	{
+		dcrypt_openssl_error(error_r);
+		EVP_PKEY_CTX_free(pctx);
+		return FALSE;
+	}
+
+	/* generate key from parameters */
+	ctx = EVP_PKEY_CTX_new(params, NULL);
+	if (EVP_PKEY_keygen_init(ctx) < 1 ||
+	    EVP_PKEY_keygen(ctx, key) < 1)
+	{
+		dcrypt_openssl_error(error_r);
+		EVP_PKEY_free(params);
+		EVP_PKEY_CTX_free(pctx);
+		EVP_PKEY_CTX_free(ctx);
+		return FALSE;
+	}
+
+	EVP_PKEY_free(params);
+	EVP_PKEY_CTX_free(pctx);
+	EVP_PKEY_CTX_free(ctx);
+	EC_KEY_set_asn1_flag((*key)->pkey.ec, OPENSSL_EC_NAMED_CURVE);
+	EC_KEY_set_conv_form((*key)->pkey.ec, POINT_CONVERSION_COMPRESSED);
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_generate_rsa_key(int bits, EVP_PKEY **key, const char **error_r)
+{
+	int ec = 0;
+
+	EVP_PKEY_CTX *ctx;
+	ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+	if (ctx == NULL ||
+	    EVP_PKEY_keygen_init(ctx) < 1 ||
+	    EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) < 1 ||
+	    EVP_PKEY_keygen(ctx, key) < 1) {
+		dcrypt_openssl_error(error_r);
+		ec = -1;
+	}
+
+	EVP_PKEY_CTX_free(ctx);
+	return ec == 0;
+}
+
+static
+bool dcrypt_openssl_ecdh_derive_secret_local(struct dcrypt_private_key *local_key, buffer_t *R, buffer_t *S, const char **error_r)
+{
+	EVP_PKEY *local = (EVP_PKEY*)local_key;
+	BN_CTX *bn_ctx = BN_CTX_new();
+	const EC_GROUP *grp = EC_KEY_get0_group(local->pkey.ec);
+	EC_POINT *pub = EC_POINT_new(grp);
+	/* convert ephemeral key data EC point */
+	if (EC_POINT_oct2point(grp, pub, R->data, R->used, bn_ctx) != 1)
+	{
+		EC_POINT_free(pub);
+		BN_CTX_free(bn_ctx);
+		return dcrypt_openssl_error(error_r);
+	}
+	EC_KEY *ec_key = EC_KEY_new();
+	/* convert point to public key */
+	EC_KEY_set_conv_form(ec_key, POINT_CONVERSION_COMPRESSED);
+	EC_KEY_set_group(ec_key, grp);
+	EC_KEY_set_public_key(ec_key, pub);
+	EC_POINT_free(pub);
+	BN_CTX_free(bn_ctx);
+
+	/* make sure it looks like a valid key */
+	if (EC_KEY_check_key(ec_key) != 1) {
+		EC_KEY_free(ec_key);
+		return dcrypt_openssl_error(error_r);
+	}
+
+	EVP_PKEY *peer = EVP_PKEY_new();
+	EVP_PKEY_set1_EC_KEY(peer, ec_key);
+	EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(local, NULL);
+
+	/* initialize derivation */
+	if (pctx == NULL ||
+	    EVP_PKEY_derive_init(pctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(pctx, peer) != 1) {
+		EVP_PKEY_CTX_free(pctx);
+		EC_KEY_free(ec_key);
+		return dcrypt_openssl_error(error_r);
+	}
+
+	/* have to do it twice to get the data length */
+	size_t len;
+	if (EVP_PKEY_derive(pctx, NULL, &len) != 1) {
+		EVP_PKEY_CTX_free(pctx);
+		EC_KEY_free(ec_key);
+		return dcrypt_openssl_error(error_r);
+	}
+	unsigned char buf[len];
+	memset(buf,0,len);
+	if (EVP_PKEY_derive(pctx, buf, &len) != 1) {
+		EVP_PKEY_CTX_free(pctx);
+		EC_KEY_free(ec_key);
+		return dcrypt_openssl_error(error_r);
+	}
+	EVP_PKEY_CTX_free(pctx);
+	buffer_append(S, buf, len);
+	EC_KEY_free(ec_key);
+	EVP_PKEY_free(peer);
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_ecdh_derive_secret_peer(struct dcrypt_public_key *peer_key, buffer_t *R, buffer_t *S, const char **error_r)
+{
+	/* ensure peer_key is EC key */
+	EVP_PKEY *local = NULL;
+	EVP_PKEY *peer = (EVP_PKEY*)peer_key;
+	if (EVP_PKEY_base_id(peer) != EVP_PKEY_EC) {
+		if (error_r != NULL)
+			*error_r = "Only ECC key can be used";
+		return FALSE;
+	}
+
+	/* generate another key from same group */
+	int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(peer->pkey.ec));
+	if (!dcrypt_openssl_generate_ec_key(nid, &local, error_r)) return FALSE;
+
+	/* initialize */
+	EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(local, NULL);
+	if (pctx == NULL ||
+	    EVP_PKEY_derive_init(pctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(pctx, peer) != 1) {
+		EVP_PKEY_CTX_free(pctx);
+		return dcrypt_openssl_error(error_r);
+	}
+
+	/* derive */
+	size_t len;
+	if (EVP_PKEY_derive(pctx, NULL, &len) != 1) {
+		EVP_PKEY_CTX_free(pctx);
+		return dcrypt_openssl_error(error_r);
+	}
+	unsigned char buf[len];
+	if (EVP_PKEY_derive(pctx, buf, &len) != 1) {
+		EVP_PKEY_CTX_free(pctx);
+		return dcrypt_openssl_error(error_r);
+	}
+
+	EVP_PKEY_CTX_free(pctx);
+	buffer_append(S, buf, len);
+
+	/* get ephemeral key (=R) */
+	BN_CTX *bn_ctx = BN_CTX_new();
+	const EC_POINT *pub = EC_KEY_get0_public_key(local->pkey.ec);
+	const EC_GROUP *grp = EC_KEY_get0_group(local->pkey.ec);
+	len = EC_POINT_point2oct(grp, pub, POINT_CONVERSION_COMPRESSED, NULL, 0, bn_ctx);
+	unsigned char R_buf[len];
+	EC_POINT_point2oct(grp, pub, POINT_CONVERSION_COMPRESSED, R_buf, len, bn_ctx);
+	BN_CTX_free(bn_ctx);
+	buffer_append(R, R_buf, len);
+	EVP_PKEY_free(local);
+
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_pbkdf2(const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len,
+	const char *hash, unsigned int rounds, buffer_t *result, unsigned int result_len, const char **error_r)
+{
+	int ret;
+	i_assert(rounds > 0);
+	i_assert(result_len > 0);
+	i_assert(result != NULL);
+	T_BEGIN {
+		/* determine MD */
+		const EVP_MD* md = EVP_get_digestbyname(hash);
+		if (md == NULL) {
+			*error_r = t_strdup_printf("Invalid digest %s", hash);
+			return FALSE;
+		}
+
+		unsigned char buffer[result_len];
+		if ((ret = PKCS5_PBKDF2_HMAC((const char*)password, password_len, salt, salt_len, rounds,
+						md, result_len, buffer)) == 1) {
+			buffer_append(result, buffer, result_len);
+		}
+	} T_END;
+	if (ret != 1) return dcrypt_openssl_error(error_r);
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_generate_keypair(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind, unsigned int bits, const char *curve, const char **error_r)
+{
+	if (kind == DCRYPT_KEY_RSA) {
+		if (dcrypt_openssl_generate_rsa_key(bits, (EVP_PKEY**)&(pair_r->priv), error_r) == 0) {
+			return dcrypt_openssl_private_to_public_key(pair_r->priv, &(pair_r->pub), error_r);
+		} else return dcrypt_openssl_error(error_r);
+	} else if (kind == DCRYPT_KEY_EC) {
+		int nid = OBJ_sn2nid(curve);
+		if (nid == NID_undef) {
+			if (error_r != NULL)
+				*error_r = t_strdup_printf("Unknown EC curve %s", curve);
+			return FALSE;
+		}
+		if (dcrypt_openssl_generate_ec_key(nid, (EVP_PKEY**)&(pair_r->priv), error_r) == 0) {
+			return dcrypt_openssl_private_to_public_key(pair_r->priv, &(pair_r->pub), error_r);
+		} else return dcrypt_openssl_error(error_r);
+	}
+	if (error_r != NULL)
+		*error_r = "Key type not supported in this build";
+	return FALSE;
+}
+
+static
+bool dcrypt_openssl_decrypt_point_v1(buffer_t *data, buffer_t *key, BIGNUM **point_r, const char **error_r)
+{
+	struct dcrypt_context_symmetric *dctx;
+	buffer_t *tmp = buffer_create_dynamic(pool_datastack_create(), 64);
+
+	if (!dcrypt_openssl_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_DECRYPT, &dctx, error_r)) {
+		return FALSE;
+	}
+
+	/* v1 KEYS have all-zero IV - have to use it ourselves too */
+	dcrypt_openssl_ctx_sym_set_iv(dctx, (const unsigned char*)"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", 16);
+	dcrypt_openssl_ctx_sym_set_key(dctx, key->data, key->used);
+
+	if (!dcrypt_openssl_ctx_sym_init(dctx, error_r) ||
+	    !dcrypt_openssl_ctx_sym_update(dctx, data->data, data->used, tmp, error_r) ||
+	    !dcrypt_openssl_ctx_sym_final(dctx, tmp, error_r)) {
+		dcrypt_openssl_ctx_sym_destroy(&dctx);
+		return FALSE;
+	}
+
+	dcrypt_openssl_ctx_sym_destroy(&dctx);
+
+	*point_r = BN_bin2bn(tmp->data, tmp->used, NULL);
+	safe_memset(buffer_get_modifiable_data(tmp, NULL), 0,tmp->used);
+	buffer_set_used_size(key, 0);
+
+	if (*point_r == FALSE)
+		return dcrypt_openssl_error(error_r);
+
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_decrypt_point_ec_v1(struct dcrypt_private_key *dec_key,
+	const char *data_hex, const char *peer_key_hex, BIGNUM **point_r, const char **error_r)
+{
+	buffer_t *peer_key, *data, key, *secret;
+	bool res;
+
+	data = buffer_create_dynamic(pool_datastack_create(), 128);
+	peer_key = buffer_create_dynamic(pool_datastack_create(), 64);
+
+	hex_to_binary(data_hex, data);
+	hex_to_binary(peer_key_hex, peer_key);
+
+	secret = buffer_create_dynamic(pool_datastack_create(), 64);
+
+	if (!dcrypt_openssl_ecdh_derive_secret_local(dec_key, peer_key, secret, error_r))
+		return FALSE;
+
+	/* run it thru SHA256 once */
+	unsigned char digest[SHA256_DIGEST_LENGTH];
+	SHA256(secret->data, secret->used, digest);
+	safe_memset(buffer_get_modifiable_data(secret, NULL), 0, secret->used);
+	buffer_set_used_size(secret, 0);
+	buffer_create_from_const_data(&key, digest, SHA256_DIGEST_LENGTH);
+
+	/* then use this as key */
+	res = dcrypt_openssl_decrypt_point_v1(data, &key, point_r, error_r);
+	memset(digest, 0, sizeof(digest));
+	safe_memset(digest, 0, SHA256_DIGEST_LENGTH);
+
+	return res;
+}
+
+static
+bool dcrypt_openssl_decrypt_point_password_v1(const char *data_hex, const char *password_hex,
+	const char *salt_hex, BIGNUM **point_r, const char **error_r)
+{
+	buffer_t *salt, *data, *password, *key;
+	struct dcrypt_context_symmetric *dctx;
+
+	data = buffer_create_dynamic(pool_datastack_create(), 128);
+	salt = buffer_create_dynamic(pool_datastack_create(), 16);
+	password = buffer_create_dynamic(pool_datastack_create(), 32);
+	key = buffer_create_dynamic(pool_datastack_create(), 32);
+
+	hex_to_binary(data_hex, data);
+	hex_to_binary(salt_hex, salt);
+	hex_to_binary(password_hex, password);
+
+	/* aes-256-ctr uses 32 byte key, and v1 uses all-zero IV */
+	if (!dcrypt_openssl_pbkdf2(password->data, password->used, salt->data, salt->used,
+				   "sha256", 16, key, 32, error_r)) {
+		dcrypt_ctx_sym_destroy(&dctx);
+		return FALSE;
+	}
+
+	return dcrypt_openssl_decrypt_point_v1(data, key, point_r, error_r);
+}
+
+static
+bool dcrypt_openssl_load_private_key_dovecot_v1(struct dcrypt_private_key **key_r,
+	int len, const char **input, const char *password, struct dcrypt_private_key *dec_key,
+	const char **error_r)
+{
+	int nid, ec, enctype;
+	EC_KEY *eckey = NULL;
+	BIGNUM *point = NULL;
+
+	if (str_to_int(input[1], &nid) != 0) {
+		if (error_r != NULL)
+			*error_r = "Corrupted data";
+		return FALSE;
+	}
+
+	if (str_to_int(input[2], &enctype) != 0) {
+		if (error_r != NULL)
+			*error_r = "Corrupted data";
+		return FALSE;
+	}
+
+	eckey = EC_KEY_new_by_curve_name(nid);
+	if (eckey == NULL) return dcrypt_openssl_error(error_r);
+
+	/* decode and optionally decipher private key value */
+	if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_NONE) {
+		point = BN_new();
+		if (BN_hex2bn(&point, input[3]) < 1) {
+			BN_free(point);
+			EC_KEY_free(eckey);
+			return dcrypt_openssl_error(error_r);
+		}
+	} else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD) {
+		/* by password */
+		const char *enc_priv_pt = input[3];
+		const char *salt = input[4];
+		if (!dcrypt_openssl_decrypt_point_password_v1(enc_priv_pt, password, salt, &point, error_r)) {
+			EC_KEY_free(eckey);
+			return FALSE;
+		}
+	} else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) {
+		/* by key */
+		const char *enc_priv_pt = input[3];
+		const char *peer_key = input[4];
+		if (!dcrypt_openssl_decrypt_point_ec_v1(dec_key, enc_priv_pt, peer_key, &point, error_r)) {
+			EC_KEY_free(eckey);
+			return FALSE;
+		}
+	} else {
+		if (error_r != NULL)
+			*error_r = "Invalid key data";
+		EC_KEY_free(eckey);
+		return FALSE;
+	}
+
+	/* assign private key */
+	BN_CTX *bnctx = BN_CTX_new();
+	EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED);
+	EC_KEY_set_private_key(eckey, point);
+	EC_KEY_precompute_mult(eckey, bnctx);
+	EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+	EC_POINT *pub = EC_POINT_new(EC_KEY_get0_group(eckey));
+	/* calculate public key */
+	ec = EC_POINT_mul(EC_KEY_get0_group(eckey), pub, point, NULL, NULL, bnctx);
+	EC_KEY_set_public_key(eckey, pub);
+	BN_free(point);
+	EC_POINT_free(pub);
+	BN_CTX_free(bnctx);
+
+	/* make sure it looks OK and is correct */
+	if (ec == 1 && EC_KEY_check_key(eckey) == 1) {
+		unsigned char digest[SHA256_DIGEST_LENGTH];
+		/* validate that the key was loaded correctly */
+		char *id = ec_key_get_pub_point_hex(eckey);
+		SHA256((unsigned char*)id, strlen(id), digest);
+		OPENSSL_free(id);
+		const char *digest_hex = binary_to_hex(digest, SHA256_DIGEST_LENGTH);
+		if (strcmp(digest_hex, input[len-1]) != 0) {
+			if (error_r != NULL)
+				*error_r = "Key id mismatch after load";
+			EC_KEY_free(eckey);
+			return FALSE;
+		}
+		EVP_PKEY *key = EVP_PKEY_new();
+		EVP_PKEY_set1_EC_KEY(key, eckey);
+		EC_KEY_free(eckey);
+		*key_r = (struct dcrypt_private_key *)key;
+		return TRUE;
+	}
+
+	EC_KEY_free(eckey);
+
+	return dcrypt_openssl_error(error_r);
+}
+
+/* encrypt/decrypt private keys */
+static
+bool dcrypt_openssl_cipher_key_dovecot_v2(const char *cipher, enum dcrypt_sym_mode mode,
+	buffer_t *input, buffer_t *secret, buffer_t *salt, const char *digalgo, unsigned int rounds,
+	buffer_t *result_r, const char **error_r)
+{
+	struct dcrypt_context_symmetric *dctx;
+	bool res;
+
+	if (!dcrypt_openssl_ctx_sym_create(cipher, mode, &dctx, error_r)) {
+		return FALSE;
+	}
+
+	/* generate encryption key/iv based on secret/salt */
+	buffer_t *key_data = buffer_create_dynamic(pool_datastack_create(), 128);
+	res = dcrypt_openssl_pbkdf2(secret->data, secret->used, salt->data, salt->used,
+		digalgo, rounds, key_data,
+		dcrypt_openssl_ctx_sym_get_key_length(dctx)+dcrypt_openssl_ctx_sym_get_iv_length(dctx), error_r);
+
+	if (!res) {
+		dcrypt_openssl_ctx_sym_destroy(&dctx);
+		return FALSE;
+	}
+
+	buffer_t *tmp = buffer_create_dynamic(pool_datastack_create(), 128);
+	const unsigned char *kd = buffer_free_without_data(&key_data);
+
+	/* perform ciphering */
+	dcrypt_openssl_ctx_sym_set_key(dctx, kd, dcrypt_openssl_ctx_sym_get_key_length(dctx));
+	dcrypt_openssl_ctx_sym_set_iv(dctx, kd+dcrypt_openssl_ctx_sym_get_key_length(dctx), dcrypt_openssl_ctx_sym_get_iv_length(dctx));
+
+	if (!dcrypt_openssl_ctx_sym_init(dctx, error_r) ||
+	    !dcrypt_openssl_ctx_sym_update(dctx, input->data, input->used, tmp, error_r) ||
+	    !dcrypt_openssl_ctx_sym_final(dctx, tmp, error_r)) {
+		res = FALSE;
+	} else {
+		/* provide result if succeeded */
+		buffer_append_buf(result_r, tmp, 0, (size_t)-1);
+		res = TRUE;
+	}
+	/* and ensure no data leaks */
+	safe_memset(buffer_get_modifiable_data(tmp, NULL), 0, tmp->used);
+
+	dcrypt_openssl_ctx_sym_destroy(&dctx);
+	return res;
+}
+
+static
+bool dcrypt_openssl_load_private_key_dovecot_v2(struct dcrypt_private_key **key_r,
+	int len, const char **input, const char *password, struct dcrypt_private_key *dec_key,
+	const char **error_r)
+{
+	int enctype;
+	buffer_t *key_data = buffer_create_dynamic(pool_datastack_create(), 256);
+
+	/* check for encryption type */
+	if (str_to_int(input[2], &enctype) != 0) {
+		if (error_r != NULL)
+			*error_r = "Corrupted data";
+		return FALSE;
+	}
+
+	if (enctype < 0 || enctype > 2) {
+		if (error_r != NULL)
+			*error_r = "Corrupted data";
+		return FALSE;
+	}
+
+	/* match encryption type to field counts */
+	if ((enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_NONE && len != 5) ||
+	    (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD && len != 9) ||
+ 	    (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK && len != 11)) {
+		if (error_r != NULL)
+			*error_r = "Corrupted data";
+		return FALSE;
+	}
+
+	/* get key type */
+	int nid = OBJ_txt2nid(input[1]);
+
+	if (nid == NID_undef)
+		return dcrypt_openssl_error(error_r);
+
+	/* decode and possibly decipher private key value */
+	if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_NONE) {
+		if (hex_to_binary(input[3], key_data) != 0) {
+			if (error_r != NULL)
+				*error_r = "Corrupted data";
+		}
+	} else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) {
+		unsigned int rounds;
+		struct dcrypt_public_key *pubkey = NULL;
+		if (str_to_uint(input[6], &rounds) != 0) {
+			if (error_r != NULL)
+				*error_r = "Corrupted data";
+			return FALSE;
+		}
+
+		buffer_t *data = buffer_create_dynamic(pool_datastack_create(), 128);
+
+		/* check that we have correct decryption key */
+		if (!dcrypt_openssl_private_to_public_key(dec_key, &pubkey, error_r) ||
+		    !dcrypt_openssl_public_key_id(pubkey, "sha256", data, error_r)) {
+			if (pubkey != NULL) dcrypt_openssl_free_public_key(&pubkey);
+			return FALSE;
+		}
+
+		dcrypt_openssl_free_public_key(&pubkey);
+
+		if (strcmp(binary_to_hex(data->data, data->used), input[9]) != 0) {
+			dcrypt_openssl_free_public_key(&pubkey);
+			if (error_r != NULL)
+				*error_r = "No private key available";
+			return FALSE;
+		}
+
+
+		buffer_t *salt, *peer_key, *secret;
+		salt = buffer_create_dynamic(pool_datastack_create(), strlen(input[4])/2);
+		peer_key = buffer_create_dynamic(pool_datastack_create(), strlen(input[8])/2);
+		secret = buffer_create_dynamic(pool_datastack_create(), 128);
+
+		buffer_set_used_size(data, 0);
+		hex_to_binary(input[4], salt);
+		hex_to_binary(input[8], peer_key);
+		hex_to_binary(input[7], data);
+
+		/* get us secret value to use for key/iv generation */
+		if (EVP_PKEY_base_id((EVP_PKEY*)dec_key) == EVP_PKEY_RSA) {
+			if (!dcrypt_openssl_rsa_decrypt(dec_key, peer_key->data, peer_key->used, secret, error_r))
+				return FALSE;
+		} else {
+			/* perform ECDH */
+			if (!dcrypt_openssl_ecdh_derive_secret_local(dec_key, peer_key, secret, error_r))
+				return FALSE;
+		}
+		/* decrypt key */
+		if (!dcrypt_openssl_cipher_key_dovecot_v2(input[3], DCRYPT_MODE_DECRYPT, data, secret, salt,
+		    input[5], rounds, key_data, error_r)) {
+			return FALSE;
+		}
+	} else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD) {
+		unsigned int rounds;
+		if (str_to_uint(input[6], &rounds) != 0) {
+			if (error_r != NULL)
+				*error_r = "Corrupted data";
+			return FALSE;
+		}
+
+		buffer_t *salt, secret, *data;
+		salt = buffer_create_dynamic(pool_datastack_create(), strlen(input[4])/2);
+		buffer_create_from_const_data(&secret, password, strlen(password));
+		data = buffer_create_dynamic(pool_datastack_create(), strlen(input[7])/2);
+		if (hex_to_binary(input[4], salt) != 0 ||
+		    hex_to_binary(input[7], data) != 0) {
+			if (error_r != NULL)
+				*error_r = "Corrupted data";
+			return FALSE;
+		}
+
+		if (!dcrypt_openssl_cipher_key_dovecot_v2(input[3], DCRYPT_MODE_DECRYPT, data, &secret, salt,
+		    input[5], rounds, key_data, error_r)) {
+			return FALSE;
+		}
+	}
+
+	/* decode actual key */
+	if (EVP_PKEY_type(nid) == EVP_PKEY_RSA) {
+		RSA *rsa = RSA_new();
+		const unsigned char *ptr = buffer_get_data(key_data, NULL);
+		if (d2i_RSAPrivateKey(&rsa, &ptr, key_data->used) == NULL ||
+		    RSA_check_key(rsa) != 1) {
+			safe_memset(buffer_get_modifiable_data(key_data, NULL), 0, key_data->used);
+			RSA_free(rsa);
+			return dcrypt_openssl_error(error_r);
+		}
+		safe_memset(buffer_get_modifiable_data(key_data, NULL), 0, key_data->used);
+		buffer_set_used_size(key_data, 0);
+		EVP_PKEY *pkey = EVP_PKEY_new();
+		EVP_PKEY_set1_RSA(pkey, rsa);
+		*key_r = (struct dcrypt_private_key *)pkey;
+	} else {
+		int ec;
+		BIGNUM *point = BN_new();
+		if (BN_mpi2bn(key_data->data, key_data->used, point) == NULL) {
+			safe_memset(buffer_get_modifiable_data(key_data, NULL), 0, key_data->used);
+			BN_free(point);
+			return dcrypt_openssl_error(error_r);
+		}
+		EC_KEY *eckey = EC_KEY_new_by_curve_name(nid);
+		safe_memset(buffer_get_modifiable_data(key_data, NULL), 0, key_data->used);
+		buffer_set_used_size(key_data, 0);
+		if (eckey == NULL) {
+			return dcrypt_openssl_error(error_r);
+		}
+		BN_CTX *bnctx = BN_CTX_new();
+		EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED);
+		EC_KEY_set_private_key(eckey, point);
+		EC_KEY_precompute_mult(eckey, bnctx);
+		EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+		EC_POINT *pub = EC_POINT_new(EC_KEY_get0_group(eckey));
+		/* calculate public key */
+		ec = EC_POINT_mul(EC_KEY_get0_group(eckey), pub, point, NULL, NULL, bnctx);
+		EC_KEY_set_public_key(eckey, pub);
+		BN_free(point);
+		EC_POINT_free(pub);
+		BN_CTX_free(bnctx);
+		/* make sure the EC key is valid */
+		if (ec == 1 && EC_KEY_check_key(eckey) == 1) {
+			EVP_PKEY *key = EVP_PKEY_new();
+			EVP_PKEY_set1_EC_KEY(key, eckey);
+			EC_KEY_free(eckey);
+			*key_r = (struct dcrypt_private_key *)key;
+		} else {
+			EC_KEY_free(eckey);
+			return dcrypt_openssl_error(error_r);
+		}
+	}
+
+	/* finally compare key to key id */
+	struct dcrypt_public_key *pubkey = NULL;
+	dcrypt_openssl_private_to_public_key(*key_r, &pubkey, NULL);
+	dcrypt_openssl_public_key_id(pubkey, "sha256", key_data, NULL);
+	dcrypt_openssl_free_public_key(&pubkey);
+
+	if (strcmp(binary_to_hex(key_data->data, key_data->used), input[len-1]) != 0) {
+		dcrypt_openssl_free_private_key(key_r);
+		if (error_r != NULL)
+			*error_r = "Key id mismatch after load";
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+static
+bool dcrypt_openssl_load_private_key_dovecot(struct dcrypt_private_key **key_r,
+	const char *data, const char *password, struct dcrypt_private_key *key,
+	const char **error_r)
+{
+	bool ret;
+	T_BEGIN {
+		const char **input = t_strsplit_tab(data);
+		size_t len;
+		for(len=0;input[len]!=NULL;len++);
+		if (len < 4) {
+			if (error_r != NULL)
+				*error_r = "Corrupted data";
+			ret = FALSE;
+		} else if (*(input[0])== '1')
+			ret = dcrypt_openssl_load_private_key_dovecot_v1(key_r, len, input, password, key, error_r);
+		else if (*(input[0])== '2')
+			ret = dcrypt_openssl_load_private_key_dovecot_v2(key_r, len, input, password, key, error_r);
+		else {
+			if (error_r != NULL)
+				*error_r = "Unsupported key version";
+			ret = FALSE;
+		}
+	} T_END;
+	return ret;
+}
+
+static
+int dcrypt_openssl_load_public_key_dovecot_v1(struct dcrypt_public_key **key_r,
+	int len, const char **input, const char **error_r)
+{
+	int nid;
+	if (len != 3) {
+		if (error_r != NULL)
+			*error_r = "Corrupted data";
+		return -1;
+	}
+	if (str_to_int(input[1], &nid) != 0) {
+		if (error_r != NULL)
+			*error_r = "Corrupted data";
+		return -1;
+	}
+
+	EC_KEY *eckey = EC_KEY_new_by_curve_name(nid);
+	if (eckey == NULL) {
+		dcrypt_openssl_error(error_r);
+		return -1;
+	}
+
+	EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+	BN_CTX *bnctx = BN_CTX_new();
+
+	EC_POINT *point = EC_POINT_new(EC_KEY_get0_group(eckey));
+	if (EC_POINT_hex2point(EC_KEY_get0_group(eckey),
+ 	    input[2], point, bnctx) == NULL) {
+		BN_CTX_free(bnctx);
+		EC_KEY_free(eckey);
+		EC_POINT_free(point);
+		dcrypt_openssl_error(error_r);
+		return -1;
+	}
+	BN_CTX_free(bnctx);
+
+	EC_KEY_set_public_key(eckey, point);
+	EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+
+	EC_POINT_free(point);
+
+	if (EC_KEY_check_key(eckey) == 1) {
+		EVP_PKEY *key = EVP_PKEY_new();
+		EVP_PKEY_set1_EC_KEY(key, eckey);
+		*key_r = (struct dcrypt_public_key *)key;
+		return 0;
+	}
+
+	dcrypt_openssl_error(error_r);
+	return -1;
+}
+
+static
+bool dcrypt_openssl_load_public_key_dovecot_v2(struct dcrypt_public_key **key_r,
+	int len, const char **input, const char **error_r)
+{
+	if (len != 2 || strlen(input[1]) < 2 || (strlen(input[1])%2) != 0) {
+		if (error_r != NULL)
+			*error_r = "Corrupted data";
+		return -1;
+	}
+	buffer_t tmp;
+	size_t keylen = strlen(input[1])/2;
+	unsigned char keybuf[keylen];
+	buffer_create_from_data(&tmp, keybuf, keylen);
+	hex_to_binary(input[1], &tmp);
+
+	EVP_PKEY *pkey = EVP_PKEY_new();
+	if (d2i_PUBKEY(&pkey, (const unsigned char**)(tmp.data), tmp.used)==NULL) {
+		EVP_PKEY_free(pkey);
+		dcrypt_openssl_error(error_r);
+		return -1;
+	}
+
+	*key_r = (struct dcrypt_public_key *)pkey;
+	return 0;
+}
+
+static
+bool dcrypt_openssl_load_public_key_dovecot(struct dcrypt_public_key **key_r,
+	const char *data, const char **error_r)
+{
+	int ec = 0;
+
+	T_BEGIN {
+		const char **input = t_strsplit_tab(data);
+		size_t len;
+		for(len=0;input[len]!=NULL;len++);
+		if (len < 2) ec = -1;
+		if (ec == 0 && *(input[0]) == '1') {
+			ec = dcrypt_openssl_load_public_key_dovecot_v1(key_r, len,
+				input, error_r);
+		} else if (ec == 0 && *(input[0]) == '2') {
+			ec = dcrypt_openssl_load_public_key_dovecot_v2(key_r, len,
+				input, error_r);
+		} else {
+			if (error_r != NULL)
+				*error_r = "Unsupported key version";
+			ec = -1;
+		}
+	} T_END;
+
+	return (ec == 0 ? TRUE : FALSE);
+}
+
+static
+bool dcrypt_openssl_encrypt_private_key_dovecot(buffer_t *key, int enctype, const char *cipher, const char *password,
+	struct dcrypt_public_key *enc_key, buffer_t *destination, const char **error_r)
+{
+	bool res;
+	unsigned char *ptr;
+
+	unsigned char salt[8];
+	buffer_t *peer_key = buffer_create_dynamic(pool_datastack_create(), 128);
+	buffer_t *secret = buffer_create_dynamic(pool_datastack_create(), 128);
+	cipher = t_str_lcase(cipher);
+
+	str_append(destination, cipher);
+	str_append_c(destination, '\t');
+	random_fill(salt, sizeof(salt));
+	binary_to_hex_append(destination, salt, sizeof(salt));
+	buffer_t saltbuf;
+	buffer_create_from_const_data(&saltbuf, salt, sizeof(salt));
+
+	/* so we don't have to make new version if we ever upgrade these */
+	str_append(destination, t_strdup_printf("\t%s\t%d\t",
+		DCRYPT_DOVECOT_KEY_ENCRYPT_HASH,
+		DCRYPT_DOVECOT_KEY_ENCRYPT_ROUNDS));
+
+	if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) {
+		if (EVP_PKEY_base_id((EVP_PKEY*)enc_key) == EVP_PKEY_RSA) {
+			size_t used = buffer_get_used_size(secret);
+			/* peer key, in this case, is encrypted secret, which is 16 bytes of data */
+			ptr = buffer_append_space_unsafe(secret, 16);
+			random_fill(ptr, 16);
+			buffer_set_used_size(secret, used+16);
+			if (!dcrypt_rsa_encrypt(enc_key, secret->data, secret->used, peer_key, error_r)) {
+				return FALSE;
+			}
+		} else if (EVP_PKEY_base_id((EVP_PKEY*)enc_key) == EVP_PKEY_EC) {
+			/* generate secret by ECDHE */
+			if (!dcrypt_openssl_ecdh_derive_secret_peer(enc_key, peer_key, secret, error_r)) {
+				return FALSE;
+			}
+		} else {
+			if (error_r != NULL)
+				*error_r = "Unsupported encryption key";
+			return FALSE;
+		}
+		/* add encryption key id, reuse peer_key buffer */
+	} else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD) {
+		str_append(secret, password);
+	}
+
+	/* encrypt key using secret and salt */
+	buffer_t *tmp = buffer_create_dynamic(pool_datastack_create(), 128);
+	res = dcrypt_openssl_cipher_key_dovecot_v2(cipher, DCRYPT_MODE_ENCRYPT, key, secret, &saltbuf,
+		DCRYPT_DOVECOT_KEY_ENCRYPT_HASH, DCRYPT_DOVECOT_KEY_ENCRYPT_ROUNDS, tmp, error_r);
+	safe_memset(buffer_get_modifiable_data(secret, NULL), 0, secret->used);
+	binary_to_hex_append(destination, tmp->data, tmp->used);
+
+	/* some additional fields or private key version */
+	if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) {
+		str_append_c(destination, '\t');
+
+		/* for RSA, this is the actual encrypted secret */
+		binary_to_hex_append(destination, peer_key->data, peer_key->used);
+		str_append_c(destination, '\t');
+
+		buffer_set_used_size(peer_key, 0);
+		if (!dcrypt_openssl_public_key_id(enc_key, "sha256", peer_key, error_r))
+			return FALSE;
+		binary_to_hex_append(destination, peer_key->data, peer_key->used);
+	}
+	return res;
+}
+
+static
+bool dcrypt_openssl_store_private_key_dovecot(struct dcrypt_private_key *key, const char *cipher, buffer_t *destination,
+	const char *password, struct dcrypt_public_key *enc_key, const char **error_r)
+{
+	size_t dest_used = buffer_get_used_size(destination);
+	const char *cipher2 = NULL;
+	EVP_PKEY *pkey = (EVP_PKEY*)key;
+	char objtxt[80]; /* openssl manual says this is OK */
+	ASN1_OBJECT *obj;
+	if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) {
+		/* because otherwise we get wrong nid */
+		obj = OBJ_nid2obj(EC_GROUP_get_curve_name(EC_KEY_get0_group(pkey->pkey.ec)));
+
+	} else {
+		obj = OBJ_nid2obj(EVP_PKEY_id(pkey));
+	}
+
+	int enctype = 0;
+	int ln = OBJ_obj2txt(objtxt, sizeof(objtxt), obj, 1);
+	if (ln < 1)
+		return dcrypt_openssl_error(error_r);
+	if (ln > (int)sizeof(objtxt)) {
+		if (error_r != NULL)
+			*error_r = "Object identifier too long";
+		return FALSE;
+	}
+
+	buffer_t *buf = buffer_create_dynamic(pool_datastack_create(), 256);
+
+	/* convert key to private key value */
+	if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) {
+		unsigned char *ptr;
+		RSA *rsa = pkey->pkey.rsa;
+		int ln = i2d_RSAPrivateKey(rsa, &ptr);
+		if (ln < 1)
+			return dcrypt_openssl_error(error_r);
+		buffer_append(buf, ptr, ln);
+	} else if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) {
+		unsigned char *ptr;
+		EC_KEY *eckey = pkey->pkey.ec;
+		const BIGNUM *pk = EC_KEY_get0_private_key(eckey);
+		/* serialize to MPI which is portable */
+		int len = BN_bn2mpi(pk, NULL);
+		ptr = buffer_append_space_unsafe(buf, len);
+		BN_bn2mpi(pk, ptr);
+	} else {
+		if (*error_r != NULL)
+			*error_r = "Unsupported key type";
+		return FALSE;
+	}
+
+	/* see if we want ECDH based or password based encryption */
+	if (cipher != NULL && strncasecmp(cipher, "ecdh-", 5) == 0) {
+		i_assert(enc_key != NULL);
+		i_assert(password == NULL);
+		enctype = DCRYPT_DOVECOT_KEY_ENCRYPT_PK;
+		cipher2 = cipher+5;
+	} else if (cipher != NULL) {
+		i_assert(enc_key == NULL);
+		i_assert(password != NULL);
+		enctype = DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD;
+		cipher2 = cipher;
+	}
+
+	/* put in OID and encryption type */
+	str_append(destination, t_strdup_printf("2\t%s\t%d\t",
+		objtxt, enctype));
+
+	/* perform encryption if desired */
+	if (enctype > 0) {
+		if (!dcrypt_openssl_encrypt_private_key_dovecot(buf, enctype, cipher2, password, enc_key, destination, error_r)) {
+			buffer_set_used_size(destination, dest_used);
+			return FALSE;
+		}
+	} else {
+		binary_to_hex_append(destination, buf->data, buf->used);
+	}
+
+	/* append public key id */
+	struct dcrypt_public_key *pubkey = NULL;
+	if (!dcrypt_openssl_private_to_public_key(key, &pubkey, error_r)) {
+		buffer_set_used_size(destination, dest_used);
+		return FALSE;
+	}
+
+	str_append_c(destination, '\t');
+	buffer_set_used_size(buf, 0);
+	bool res = dcrypt_openssl_public_key_id(pubkey, "sha256", buf, error_r);
+	dcrypt_openssl_free_public_key(&pubkey);
+	binary_to_hex_append(destination, buf->data, buf->used);
+
+	if (!res) {
+		/* well, that didn't end well */
+		buffer_set_used_size(destination, dest_used);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_store_public_key_dovecot(struct dcrypt_public_key *key, buffer_t *destination, const char **error_r)
+{
+	EVP_PKEY *pubkey = (EVP_PKEY*)key;
+	unsigned char *tmp = NULL;
+
+	int rv = i2d_PUBKEY(pubkey, &tmp);
+
+	if (tmp == NULL)
+		return dcrypt_openssl_error(error_r);
+	/* then store it */
+	str_append_c(destination, '2');
+	str_append_c(destination, '\t');
+	binary_to_hex_append(destination, tmp, rv);
+	OPENSSL_free(tmp);
+
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_load_private_key(struct dcrypt_private_key **key_r, enum dcrypt_key_format format,
+	const char *data, const char *password, struct dcrypt_private_key *dec_key,
+	const char **error_r)
+{
+	EVP_PKEY *key = NULL, *key2;
+	if (format == DCRYPT_FORMAT_DOVECOT)
+		return dcrypt_openssl_load_private_key_dovecot(key_r, data, password, dec_key, error_r);
+
+	BIO *key_in = BIO_new_mem_buf((void*)data, strlen(data));
+
+	key = EVP_PKEY_new();
+
+	key2 = PEM_read_bio_PrivateKey(key_in, &key, NULL, (void*)password);
+
+	BIO_vfree(key_in);
+
+	if (key2 == NULL) {
+		EVP_PKEY_free(key);
+		return dcrypt_openssl_error(error_r);
+	}
+
+	if (EVP_PKEY_base_id(key) == EVP_PKEY_EC) {
+		EC_KEY_set_conv_form(key->pkey.ec, POINT_CONVERSION_COMPRESSED);
+	}
+
+	*key_r = (struct dcrypt_private_key *)key;
+
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_load_public_key(struct dcrypt_public_key **key_r, enum dcrypt_key_format format,
+	const char *data, const char **error_r)
+{
+	EVP_PKEY *key = NULL;
+	if (format == DCRYPT_FORMAT_DOVECOT)
+		return dcrypt_openssl_load_public_key_dovecot(key_r, data, error_r);
+
+	BIO *key_in = BIO_new_mem_buf((void*)data, strlen(data));
+
+	key = PEM_read_bio_PUBKEY(key_in, &key, NULL, NULL);
+	BIO_reset(key_in);
+	if (key == NULL) { /* ec keys are bother */
+		/* read the header */
+		char buf[27]; /* begin public key */
+		if (BIO_gets(key_in, buf, sizeof(buf)) != 1) {
+			BIO_vfree(key_in);
+			return dcrypt_openssl_error(error_r);
+		}
+		if (strcmp(buf, "-----BEGIN PUBLIC KEY-----") != 0) {
+			if (error_r != NULL)
+				*error_r = "Missing public key header";
+			return FALSE;
+		}
+		BIO *b64 = BIO_new(BIO_f_base64());
+		EC_KEY *eckey = d2i_EC_PUBKEY_bio(b64, NULL);
+		if (eckey != NULL) {
+			EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED);
+			EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+			key = EVP_PKEY_new();
+			EVP_PKEY_set1_EC_KEY(key, eckey);
+		}
+	}
+
+	BIO_vfree(key_in);
+
+	if (key == NULL)
+		return dcrypt_openssl_error(error_r);
+
+	*key_r = (struct dcrypt_public_key *)key;
+
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_store_private_key(struct dcrypt_private_key *key, enum dcrypt_key_format format,
+	const char *cipher, buffer_t *destination, const char *password, struct dcrypt_public_key *enc_key,
+	const char **error_r)
+{
+	int ec;
+	if (format == DCRYPT_FORMAT_DOVECOT) {
+		bool ret;
+		T_BEGIN {
+			ret = dcrypt_openssl_store_private_key_dovecot(key, cipher, destination, password, enc_key, error_r);
+		} T_END;
+		return ret;
+	}
+
+	EVP_PKEY *pkey = (EVP_PKEY*)key;
+	BIO *key_out = BIO_new(BIO_s_mem());
+	const EVP_CIPHER *algo = NULL;
+	if (cipher != NULL) {
+		algo = EVP_get_cipherbyname(cipher);
+		if (algo == NULL) {
+			*error_r = t_strdup_printf("Invalid cipher %s", cipher);
+			return FALSE;
+		}
+	}
+
+	ec = PEM_write_bio_PrivateKey(key_out, pkey, algo, NULL, 0, NULL, (void*)password);
+
+	BIO_flush(key_out);
+
+	if (ec != 1) {
+		BIO_vfree(key_out);
+		return dcrypt_openssl_error(error_r);
+	}
+
+	long bs;
+	char *buf;
+	bs = BIO_get_mem_data(key_out, &buf);
+	buffer_append(destination, buf, bs);
+	BIO_vfree(key_out);
+
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_store_public_key(struct dcrypt_public_key *key, enum dcrypt_key_format format, buffer_t *destination, const char **error_r)
+{
+	int ec;
+	if (format == DCRYPT_FORMAT_DOVECOT)
+		return dcrypt_openssl_store_public_key_dovecot(key, destination, error_r);
+
+	EVP_PKEY *pkey = (EVP_PKEY*)key;
+	BIO *key_out = BIO_new(BIO_s_mem());
+
+	if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA)
+		ec = PEM_write_bio_PUBKEY(key_out, pkey);
+	else {
+		BIO *b64 = BIO_new(BIO_f_base64());
+		BIO_puts(key_out, "-----BEGIN PUBLIC KEY-----\n");
+		BIO_push(b64, key_out);
+		ec = i2d_EC_PUBKEY_bio(b64, pkey->pkey.ec);
+		BIO_flush(b64);
+		BIO_pop(b64);
+		BIO_vfree(b64);
+		BIO_puts(key_out, "-----END PUBLIC KEY-----");
+	}
+
+	if (ec != 1) {
+		BIO_vfree(key_out);
+		return dcrypt_openssl_error(error_r);
+	}
+
+	long bs;
+	char *buf;
+	bs = BIO_get_mem_data(key_out, &buf);
+	buffer_append(destination, buf, bs);
+	BIO_vfree(key_out);
+
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_private_to_public_key(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r, const char **error_r)
+{
+	EVP_PKEY *pkey = (EVP_PKEY*)priv_key;
+	EVP_PKEY *pk;
+
+	if (*pub_key_r == NULL)
+		pk = EVP_PKEY_new();
+	else
+		pk = (EVP_PKEY*)*pub_key_r;
+
+	if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA)
+	{
+		EVP_PKEY_set1_RSA(pk, RSAPublicKey_dup(pkey->pkey.rsa));
+	} else if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) {
+		EC_KEY* eck = EVP_PKEY_get1_EC_KEY(pkey);
+		EC_KEY_set_asn1_flag(eck, OPENSSL_EC_NAMED_CURVE);
+		EVP_PKEY_set1_EC_KEY(pk, eck);
+		EC_KEY_free(eck);
+	} else {
+		*error_r = "Invalid private key";
+		return FALSE;
+	}
+
+	*pub_key_r = (struct dcrypt_public_key*)pk;
+	return TRUE;
+}
+
+static
+bool dcrypt_openssl_key_string_get_info(const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r,
+	enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r,
+	const char **key_hash_r, const char **error_r)
+{
+	enum dcrypt_key_format format = DCRYPT_FORMAT_PEM;
+	enum dcrypt_key_version version = DCRYPT_KEY_VERSION_NA;
+	enum dcrypt_key_encryption_type encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_NONE;
+	enum dcrypt_key_kind kind = DCRYPT_KEY_KIND_PUBLIC;
+	const char *encryption_key_hash = NULL;
+	const char *key_hash = NULL;
+
+	if (key_data == NULL) {
+		if (error_r != NULL)
+			*error_r = "NULL key passed";
+		return FALSE;
+	}
+
+	/* is it PEM key */
+	if (strstr(key_data, "----- BEGIN ") != NULL) {
+		format = DCRYPT_FORMAT_PEM;
+		version = DCRYPT_KEY_VERSION_NA;
+		if (strstr(key_data, "ENCRYPTED") != NULL) {
+			encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD;
+		}
+		if (strstr(key_data, "----- BEGIN PRIVATE KEY") != NULL)
+			kind = DCRYPT_KEY_KIND_PRIVATE;
+		else if (strstr(key_data, "----- BEGIN PUBLIC KEY") != NULL)
+			kind = DCRYPT_KEY_KIND_PUBLIC;
+		else {
+			if (error_r != NULL)
+				*error_r = "Unknown/invalid PEM key type";
+			return FALSE;
+		}
+	} else T_BEGIN {
+		const char **fields = t_strsplit_tab(key_data);
+		int nfields;
+		for(nfields=0;fields[nfields]!=NULL;nfields++);
+		if (nfields < 2) {
+			if (error_r != NULL)
+				*error_r = "Unknown key format";
+			return FALSE;
+		}
+
+		format = DCRYPT_FORMAT_DOVECOT;
+
+		/* field 1 - version */
+		if (strcmp(fields[0], "1") == 0) {
+			version = DCRYPT_KEY_VERSION_1;
+			if (nfields == 3) {
+				kind = DCRYPT_KEY_KIND_PUBLIC;
+			} else if (nfields == 5 && strcmp(fields[2],"0") == 0) {
+				kind = DCRYPT_KEY_KIND_PRIVATE;
+				encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_NONE;
+			} else if (nfields == 6 && strcmp(fields[2],"2") == 0) {
+				kind = DCRYPT_KEY_KIND_PRIVATE;
+				encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD;
+			} else if (nfields == 11 && strcmp(fields[2],"1") == 0) {
+				kind = DCRYPT_KEY_KIND_PRIVATE;
+				encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_KEY;
+				if (encryption_key_hash_r != NULL)
+					encryption_key_hash = i_strdup(fields[nfields-2]);
+			} else {
+				if (error_r != NULL)
+					*error_r = "Invalid dovecot v1 encoding";
+				return FALSE;
+			}
+		} else if (strcmp(fields[0], "2") == 0) {
+			version = DCRYPT_KEY_VERSION_1;
+			if (nfields == 2) {
+				kind = DCRYPT_KEY_KIND_PUBLIC;
+			} else if (nfields == 5 && strcmp(fields[2],"0") == 0) {
+				kind = DCRYPT_KEY_KIND_PRIVATE;
+				encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_NONE;
+			} else if (nfields == 9 && strcmp(fields[2],"2") == 0) {
+				kind = DCRYPT_KEY_KIND_PRIVATE;
+				encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD;
+			} else if (nfields == 11 && strcmp(fields[2],"1") == 0) {
+				kind = DCRYPT_KEY_KIND_PRIVATE;
+				encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_KEY;
+				if (encryption_key_hash_r != NULL)
+					encryption_key_hash = i_strdup(fields[nfields-2]);
+			} else {
+				if (error_r != NULL)
+					*error_r = "Invalid dovecot v2 encoding";
+				return FALSE;
+			}
+		}
+
+		/* last field is always key hash */
+		if (key_hash_r != NULL)
+			key_hash = i_strdup(fields[nfields-1]);
+	} T_END;
+
+	if (format_r != NULL) *format_r = format;
+	if (version_r != NULL) *version_r = version;
+	if (encryption_type_r != NULL) *encryption_type_r = encryption_type;
+	if (encryption_key_hash_r != NULL) {
+		*encryption_key_hash_r = t_strdup(encryption_key_hash);
+		i_free(encryption_key_hash);
+	}
+	if (kind_r != NULL) *kind_r = kind;
+	if (key_hash_r != NULL) {
+		*key_hash_r = t_strdup(key_hash);
+		i_free(key_hash);
+	}
+	return TRUE;
+}
+
+static
+void dcrypt_openssl_free_public_key(struct dcrypt_public_key **key)
+{
+	EVP_PKEY_free((EVP_PKEY*)*key);
+	*key = NULL;
+}
+static
+void dcrypt_openssl_free_private_key(struct dcrypt_private_key **key)
+{
+	EVP_PKEY_free((EVP_PKEY*)*key);
+	*key = NULL;
+}
+static
+void dcrypt_openssl_free_keypair(struct dcrypt_keypair *keypair)
+{
+	dcrypt_openssl_free_public_key(&(keypair->pub));
+	dcrypt_openssl_free_private_key(&(keypair->priv));
+}
+
+static
+bool dcrypt_openssl_rsa_encrypt(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+	int ec;
+
+	EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*)key, NULL);
+	size_t outl = EVP_PKEY_size((EVP_PKEY*)key);
+	unsigned char buf[outl];
+
+	if (ctx == NULL ||
+	    EVP_PKEY_encrypt_init(ctx) < 1 ||
+	    EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) < 1 ||
+	    EVP_PKEY_encrypt(ctx, buf, &outl, data, data_len) < 1) {
+		dcrypt_openssl_error(error_r);
+		ec = -1;
+	} else {
+		buffer_append(result, buf, outl);
+		ec = 0;
+	}
+
+	EVP_PKEY_CTX_free(ctx);
+
+	return ec == 0;
+}
+static
+bool dcrypt_openssl_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+	int ec;
+
+	EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*)key, NULL);
+	size_t outl = EVP_PKEY_size((EVP_PKEY*)key);
+	unsigned char buf[outl];
+
+	if (ctx == NULL ||
+	    EVP_PKEY_decrypt_init(ctx) < 1 ||
+	    EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) < 1 ||
+	    EVP_PKEY_decrypt(ctx, buf, &outl, data, data_len) < 1) {
+		dcrypt_openssl_error(error_r);
+		ec = -1;
+	} else {
+		buffer_append(result, buf, outl);
+		ec = 0;
+	}
+
+	EVP_PKEY_CTX_free(ctx);
+
+	return ec == 0;
+}
+
+static
+const char *dcrypt_openssl_oid2name(const unsigned char *oid, size_t oid_len, const char **error_r)
+{
+	const char *name;
+	ASN1_OBJECT *obj = d2i_ASN1_OBJECT(NULL, &oid, oid_len);
+	if (obj == NULL) {
+		dcrypt_openssl_error(error_r);
+		return NULL;
+	}
+	name = OBJ_nid2sn(OBJ_obj2nid(obj));
+	ASN1_OBJECT_free(obj);
+	return name;
+}
+
+static
+bool dcrypt_openssl_name2oid(const char *name, buffer_t *oid, const char **error_r)
+{
+	ASN1_OBJECT *obj = OBJ_txt2obj(name, 0);
+	if (obj == NULL)
+		return dcrypt_openssl_error(error_r);
+	if (obj->length == 0) {
+		if (error_r != NULL)
+			*error_r = "Object has no OID assigned";
+		return FALSE;
+	}
+	unsigned char *bufptr = buffer_append_space_unsafe(oid, obj->length + 2);
+	i2d_ASN1_OBJECT(obj, &bufptr);
+	ASN1_OBJECT_free(obj);
+	if (bufptr != NULL) {
+		return TRUE;
+	}
+	return dcrypt_openssl_error(error_r);
+}
+
+static
+bool dcrypt_openssl_private_key_type(struct dcrypt_private_key *key, enum dcrypt_key_type *key_type)
+{
+	EVP_PKEY *priv = (EVP_PKEY*)key;
+	if (priv == NULL) return FALSE;
+	if (EVP_PKEY_base_id(priv) == EVP_PKEY_RSA) *key_type = DCRYPT_KEY_RSA;
+	else if (EVP_PKEY_base_id(priv) == EVP_PKEY_EC) *key_type = DCRYPT_KEY_EC;
+	return FALSE;
+}
+
+static
+bool dcrypt_openssl_public_key_type(struct dcrypt_public_key *key, enum dcrypt_key_type *key_type)
+{
+	EVP_PKEY *pub = (EVP_PKEY*)key;
+	if (pub == NULL) return FALSE;
+	if (EVP_PKEY_base_id(pub) == EVP_PKEY_RSA) *key_type = DCRYPT_KEY_RSA;
+	else if (EVP_PKEY_base_id(pub) == EVP_PKEY_EC) *key_type = DCRYPT_KEY_EC;
+	return FALSE;
+}
+
+/** this is the v1 old legacy way of doing key id's **/
+static
+bool dcrypt_openssl_public_key_id_old(struct dcrypt_public_key *key, buffer_t *result, const char **error_r)
+{
+	unsigned char buf[SHA256_DIGEST_LENGTH];
+	EVP_PKEY *pub = (EVP_PKEY*)key;
+
+	if (pub == NULL) {
+		if (error_r != NULL)
+			*error_r = "key is NULL";
+		return FALSE;
+	}
+	if (EVP_PKEY_base_id(pub) != EVP_PKEY_EC) {
+		if (error_r != NULL)
+			*error_r = "Only EC key supported";
+		return FALSE;
+	}
+
+	char *pub_pt_hex = ec_key_get_pub_point_hex(pub->pkey.ec);
+	/* digest this */
+	SHA256((const unsigned char*)pub_pt_hex, strlen(pub_pt_hex), buf);
+	buffer_append(result, buf, SHA256_DIGEST_LENGTH);
+	OPENSSL_free(pub_pt_hex);
+	return TRUE;
+}
+
+/** this is the new which uses H(der formatted public key) **/
+static
+bool dcrypt_openssl_public_key_id(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r)
+{
+	const EVP_MD *md = EVP_get_digestbyname(algorithm);
+	if (md == NULL) {
+		if (error_r != NULL)
+			*error_r = t_strdup_printf("Unknown cipher %s", algorithm);
+		return FALSE;
+	}
+	unsigned char buf[EVP_MD_size(md)];
+	EVP_PKEY *pub = (EVP_PKEY*)key;
+	const char *ptr;
+	int ec;
+	if (pub == NULL) {
+		if (error_r != NULL)
+			*error_r = "key is NULL";
+		return FALSE;
+	}
+	if (EVP_PKEY_base_id(pub) == EVP_PKEY_EC) {
+		EC_KEY_set_conv_form(pub->pkey.ec, POINT_CONVERSION_COMPRESSED);
+	}
+	BIO *b = BIO_new(BIO_s_mem());
+	if (i2d_PUBKEY_bio(b, pub) < 1) {
+		BIO_vfree(b);
+		return dcrypt_openssl_error(error_r);
+	}
+	long len = BIO_get_mem_data(b, &ptr);
+	unsigned int hlen = sizeof(buf);
+	/* then hash it */
+#if SSLEAY_VERSION_NUMBER >= 0x1010000fL
+	EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+#else
+	EVP_MD_CTX *ctx = EVP_MD_CTX_create();
+#endif
+	if (EVP_DigestInit_ex(ctx, md, NULL) < 1 ||
+	    EVP_DigestUpdate(ctx, (const unsigned char*)ptr, len) < 1 ||
+	    EVP_DigestFinal_ex(ctx, buf, &hlen) < 1) {
+		ec = dcrypt_openssl_error(error_r);
+	} else {
+		buffer_append(result, buf, hlen);
+		ec = 0;
+	}
+
+#if SSLEAY_VERSION_NUMBER >= 0x1010000fL
+	EVP_MD_CTX_free(ctx);
+#else
+	EVP_MD_CTX_destroy(ctx);
+#endif
+	BIO_vfree(b);
+
+	return ec == 0;
+}
+
+static struct dcrypt_vfs dcrypt_openssl_vfs = {
+	.ctx_sym_create = dcrypt_openssl_ctx_sym_create,
+	.ctx_sym_destroy = dcrypt_openssl_ctx_sym_destroy,
+	.ctx_sym_set_key = dcrypt_openssl_ctx_sym_set_key,
+	.ctx_sym_set_iv = dcrypt_openssl_ctx_sym_set_iv,
+	.ctx_sym_set_key_iv_random = dcrypt_openssl_ctx_sym_set_key_iv_random,
+	.ctx_sym_set_padding = dcrypt_openssl_ctx_sym_set_padding,
+	.ctx_sym_get_key = dcrypt_openssl_ctx_sym_get_key,
+	.ctx_sym_get_iv = dcrypt_openssl_ctx_sym_get_iv,
+	.ctx_sym_set_aad = dcrypt_openssl_ctx_sym_set_aad,
+	.ctx_sym_get_aad = dcrypt_openssl_ctx_sym_get_aad,
+	.ctx_sym_set_tag = dcrypt_openssl_ctx_sym_set_tag,
+	.ctx_sym_get_tag = dcrypt_openssl_ctx_sym_get_tag,
+	.ctx_sym_get_key_length = dcrypt_openssl_ctx_sym_get_key_length,
+	.ctx_sym_get_iv_length = dcrypt_openssl_ctx_sym_get_iv_length,
+	.ctx_sym_get_block_size = dcrypt_openssl_ctx_sym_get_block_size,
+	.ctx_sym_init = dcrypt_openssl_ctx_sym_init,
+	.ctx_sym_update = dcrypt_openssl_ctx_sym_update,
+	.ctx_sym_final = dcrypt_openssl_ctx_sym_final,
+	.ctx_hmac_create = dcrypt_openssl_ctx_hmac_create,
+	.ctx_hmac_destroy = dcrypt_openssl_ctx_hmac_destroy,
+	.ctx_hmac_set_key = dcrypt_openssl_ctx_hmac_set_key,
+	.ctx_hmac_set_key_random = dcrypt_openssl_ctx_hmac_set_key_random,
+	.ctx_hmac_get_digest_length = dcrypt_openssl_ctx_hmac_get_digest_length,
+	.ctx_hmac_get_key = dcrypt_openssl_ctx_hmac_get_key,
+	.ctx_hmac_init = dcrypt_openssl_ctx_hmac_init,
+	.ctx_hmac_update = dcrypt_openssl_ctx_hmac_update,
+	.ctx_hmac_final = dcrypt_openssl_ctx_hmac_final,
+	.ecdh_derive_secret_local = dcrypt_openssl_ecdh_derive_secret_local,
+	.ecdh_derive_secret_peer = dcrypt_openssl_ecdh_derive_secret_peer,
+	.pbkdf2 = dcrypt_openssl_pbkdf2,
+	.generate_keypair = dcrypt_openssl_generate_keypair,
+	.load_private_key = dcrypt_openssl_load_private_key,
+	.load_public_key = dcrypt_openssl_load_public_key,
+	.store_private_key = dcrypt_openssl_store_private_key,
+	.store_public_key = dcrypt_openssl_store_public_key,
+	.private_to_public_key = dcrypt_openssl_private_to_public_key,
+	.key_string_get_info = dcrypt_openssl_key_string_get_info,
+	.free_keypair = dcrypt_openssl_free_keypair,
+	.free_public_key = dcrypt_openssl_free_public_key,
+	.free_private_key = dcrypt_openssl_free_private_key,
+	.rsa_encrypt = dcrypt_openssl_rsa_encrypt,
+	.rsa_decrypt = dcrypt_openssl_rsa_decrypt,
+	.oid2name = dcrypt_openssl_oid2name,
+	.name2oid = dcrypt_openssl_name2oid,
+	.private_key_type = dcrypt_openssl_private_key_type,
+	.public_key_type = dcrypt_openssl_public_key_type,
+	.public_key_id = dcrypt_openssl_public_key_id,
+	.public_key_id_old = dcrypt_openssl_public_key_id_old,
+};
+
+void dcrypt_openssl_init(struct module *module ATTR_UNUSED)
+{
+	OpenSSL_add_all_algorithms();
+	ERR_load_crypto_strings();
+	dcrypt_set_vfs(&dcrypt_openssl_vfs);
+}
+
+void dcrypt_openssl_deinit(void)
+{
+#if OPENSSL_API_COMPAT < 0x10100000L
+	OBJ_cleanup();
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/dcrypt-private.h	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,104 @@
+#ifndef DCRYPT_PRIVATE_H
+#define DCRYPT_PRIVATE_H
+
+#define DCRYPT_DOVECOT_KEY_ENCRYPT_HASH "sha256"
+#define DCRYPT_DOVECOT_KEY_ENCRYPT_ROUNDS 2048
+
+#define DCRYPT_DOVECOT_KEY_ENCRYPT_NONE 0
+#define DCRYPT_DOVECOT_KEY_ENCRYPT_PK 1
+#define DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD 2
+
+struct dcrypt_vfs {
+	bool (*ctx_sym_create)(const char *algorithm,
+		enum dcrypt_sym_mode mode,
+		struct dcrypt_context_symmetric **ctx_r, const char **error_r);
+	void (*ctx_sym_destroy)(struct dcrypt_context_symmetric **ctx);
+
+	void (*ctx_sym_set_key)(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len);
+	void (*ctx_sym_set_iv)(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len);
+	void (*ctx_sym_set_key_iv_random)(struct dcrypt_context_symmetric *ctx);
+
+	void (*ctx_sym_set_padding)(struct dcrypt_context_symmetric *ctx, bool padding);
+
+	bool (*ctx_sym_get_key)(struct dcrypt_context_symmetric *ctx, buffer_t *key);
+	bool (*ctx_sym_get_iv)(struct dcrypt_context_symmetric *ctx, buffer_t *iv);
+
+	void (*ctx_sym_set_aad)(struct dcrypt_context_symmetric *ctx, const unsigned char *aad, size_t aad_len);
+	bool (*ctx_sym_get_aad)(struct dcrypt_context_symmetric *ctx, buffer_t *aad);
+	void (*ctx_sym_set_tag)(struct dcrypt_context_symmetric *ctx, const unsigned char *tag, size_t tag_len);
+	bool (*ctx_sym_get_tag)(struct dcrypt_context_symmetric *ctx, buffer_t *tag);
+
+	unsigned int (*ctx_sym_get_key_length)(struct dcrypt_context_symmetric *ctx);
+	unsigned int (*ctx_sym_get_iv_length)(struct dcrypt_context_symmetric *ctx);
+	unsigned int (*ctx_sym_get_block_size)(struct dcrypt_context_symmetric *ctx);
+
+	bool (*ctx_sym_init)(struct dcrypt_context_symmetric *ctx, const char **error_r);
+	bool (*ctx_sym_update)(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len,
+		buffer_t *result, const char **error_r);
+	bool (*ctx_sym_final)(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r);
+
+	bool (*ctx_hmac_create)(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r);
+	void (*ctx_hmac_destroy)(struct dcrypt_context_hmac **ctx);
+
+	void (*ctx_hmac_set_key)(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len);
+	bool (*ctx_hmac_get_key)(struct dcrypt_context_hmac *ctx, buffer_t *key);
+	unsigned int (*ctx_hmac_get_digest_length)(struct dcrypt_context_hmac *ctx);
+	void (*ctx_hmac_set_key_random)(struct dcrypt_context_hmac *ctx);
+
+	bool (*ctx_hmac_init)(struct dcrypt_context_hmac *ctx, const char **error_r);
+	bool (*ctx_hmac_update)(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r);
+	bool (*ctx_hmac_final)(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r);
+
+	bool (*ecdh_derive_secret_local)(struct dcrypt_private_key *local_key,
+		buffer_t *R, buffer_t *S, const char **error_r);
+	bool (*ecdh_derive_secret_peer)(struct dcrypt_public_key *peer_key,
+		buffer_t *R, buffer_t *S, const char **error_r);
+	bool (*pbkdf2)(const unsigned char *password, size_t password_len,
+		const unsigned char *salt, size_t salt_len, const char *hash,
+		unsigned int rounds, buffer_t *result, unsigned int result_len,
+		const char **error_r);
+
+	bool (*generate_keypair)(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind,
+		unsigned int bits, const char *curve, const char **error_r);
+
+	bool (*load_private_key)(struct dcrypt_private_key **key_r, enum dcrypt_key_format format, const char *data,
+		const char *password, struct dcrypt_private_key *dec_key, const char **error_r);
+	bool (*load_public_key)(struct dcrypt_public_key **key_r, enum dcrypt_key_format format, const char *data,
+		const char **error_r);
+
+	bool (*store_private_key)(struct dcrypt_private_key *key, enum dcrypt_key_format format, const char *cipher, buffer_t *destination,
+		const char *password, struct dcrypt_public_key *enc_key, const char **error_r);
+	bool (*store_public_key)(struct dcrypt_public_key *key, enum dcrypt_key_format format, buffer_t *destination, const char **error_r);
+
+	bool (*private_to_public_key)(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r, const char **error_r);
+
+	bool (*key_string_get_info)(const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r,
+		enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r,
+		const char **key_hash_r, const char **error_r);
+
+	void (*free_keypair)(struct dcrypt_keypair *keypair);
+	void (*free_public_key)(struct dcrypt_public_key **key);
+	void (*free_private_key)(struct dcrypt_private_key **key);
+
+	bool (*rsa_encrypt)(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len,
+		buffer_t *result, const char **error_r);
+	bool (*rsa_decrypt)(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len,
+		buffer_t *result, const char **error_r);
+
+	const char *(*oid2name)(const unsigned char *oid, size_t oid_len, const char **error_r);
+	bool (*name2oid)(const char *name, buffer_t *oid, const char **error_r);
+
+	bool (*private_key_type)(struct dcrypt_private_key *key, enum dcrypt_key_type *key_type);
+	bool (*public_key_type)(struct dcrypt_public_key *key, enum dcrypt_key_type *key_type);
+	bool (*public_key_id)(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r);
+	bool (*public_key_id_old)(struct dcrypt_public_key *key, buffer_t *result, const char **error_r);
+};
+
+void dcrypt_set_vfs(struct dcrypt_vfs *vfs);
+
+void dcrypt_openssl_init(struct module *module ATTR_UNUSED);
+void dcrypt_gnutls_init(struct module *module ATTR_UNUSED);
+void dcrypt_openssl_deinit(void);
+void dcrypt_gnutls_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/dcrypt.c	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,275 @@
+#include "lib.h"
+#include "module-dir.h"
+#include "dcrypt.h"
+#include "dcrypt-private.h"
+
+static struct module *dcrypt_module = NULL;
+static struct dcrypt_vfs *dcrypt_vfs = NULL;
+
+bool dcrypt_initialize(const char *backend, const char **error_r)
+{
+	struct module_dir_load_settings mod_set;
+
+	if (dcrypt_vfs != NULL) return TRUE;
+	if (backend == NULL) backend = "openssl"; /* default for now */
+
+	const char *implementation = t_strconcat("dcrypt_",backend,NULL);
+
+	memset(&mod_set, 0, sizeof(mod_set));
+	mod_set.abi_version = DOVECOT_ABI_VERSION;
+	mod_set.require_init_funcs = 1;
+	dcrypt_module = module_dir_load(DCRYPT_MODULE_DIR, implementation, &mod_set);
+	if (dcrypt_module == NULL) {
+		if (*error_r != NULL)
+			*error_r = "No such module";
+		return FALSE;
+	}
+	if (dcrypt_module->init == NULL) {
+		if (*error_r != NULL)
+			*error_r = "Module missing init/deinit";
+		return FALSE;
+	}
+	dcrypt_module->init(dcrypt_module);
+	i_assert(dcrypt_vfs != NULL);
+	/* Destroy SSL module after(most of) the others. Especially lib-fs
+	   backends may still want to access SSL module in their own
+	   atexit-callbacks. */
+//	lib_atexit_priority(dcrypt_deinitialize, LIB_ATEXIT_PRIORITY_LOW);
+	return TRUE;
+}
+
+void dcrypt_deinitialize(void)
+{
+	i_error("I got called");
+	if (dcrypt_module != NULL) {
+		dcrypt_module->deinit();
+		module_dir_unload(&dcrypt_module);
+	}
+}
+
+void dcrypt_set_vfs(struct dcrypt_vfs *vfs)
+{
+	dcrypt_vfs = vfs;
+}
+
+bool dcrypt_ctx_sym_create(const char *algorithm, enum dcrypt_sym_mode mode, struct dcrypt_context_symmetric **ctx_r, const char **error_r)
+{
+	return dcrypt_vfs->ctx_sym_create(algorithm, mode, ctx_r, error_r);
+}
+void dcrypt_ctx_sym_destroy(struct dcrypt_context_symmetric **ctx)
+{
+	dcrypt_vfs->ctx_sym_destroy(ctx);
+}
+
+void dcrypt_ctx_sym_set_key(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len)
+{
+	dcrypt_vfs->ctx_sym_set_key(ctx, key, key_len);
+}
+void dcrypt_ctx_sym_set_iv(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len)
+{
+	dcrypt_vfs->ctx_sym_set_iv(ctx, iv, iv_len);
+}
+void dcrypt_ctx_sym_set_key_iv_random(struct dcrypt_context_symmetric *ctx)
+{
+	dcrypt_vfs->ctx_sym_set_key_iv_random(ctx);
+}
+
+bool dcrypt_ctx_sym_get_key(struct dcrypt_context_symmetric *ctx, buffer_t *key)
+{
+	return dcrypt_vfs->ctx_sym_get_key(ctx, key);
+}
+bool dcrypt_ctx_sym_get_iv(struct dcrypt_context_symmetric *ctx, buffer_t *iv)
+{
+	return dcrypt_vfs->ctx_sym_get_iv(ctx, iv);
+}
+
+unsigned int dcrypt_ctx_sym_get_key_length(struct dcrypt_context_symmetric *ctx)
+{
+	return dcrypt_vfs->ctx_sym_get_key_length(ctx);
+}
+
+unsigned int dcrypt_ctx_sym_get_iv_length(struct dcrypt_context_symmetric *ctx)
+{
+	return dcrypt_vfs->ctx_sym_get_iv_length(ctx);
+}
+
+void dcrypt_ctx_sym_set_aad(struct dcrypt_context_symmetric *ctx, const unsigned char *aad, size_t aad_len)
+{
+	dcrypt_vfs->ctx_sym_set_aad(ctx, aad, aad_len);
+}
+
+bool dcrypt_ctx_sym_get_aad(struct dcrypt_context_symmetric *ctx, buffer_t *aad)
+{
+	return dcrypt_vfs->ctx_sym_get_aad(ctx, aad);
+}
+
+void dcrypt_ctx_sym_set_tag(struct dcrypt_context_symmetric *ctx, const unsigned char *tag, size_t tag_len)
+{
+	dcrypt_vfs->ctx_sym_set_tag(ctx, tag, tag_len);
+}
+
+bool dcrypt_ctx_sym_get_tag(struct dcrypt_context_symmetric *ctx, buffer_t *tag)
+{
+	return dcrypt_vfs->ctx_sym_get_tag(ctx, tag);
+}
+
+unsigned int dcrypt_ctx_sym_get_block_size(struct dcrypt_context_symmetric *ctx) {
+	return dcrypt_vfs->ctx_sym_get_block_size(ctx);
+}
+
+bool dcrypt_ctx_sym_init(struct dcrypt_context_symmetric *ctx, const char **error_r)
+{
+	return dcrypt_vfs->ctx_sym_init(ctx, error_r);
+}
+bool dcrypt_ctx_sym_update(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+	return dcrypt_vfs->ctx_sym_update(ctx, data, data_len, result, error_r);
+}
+bool dcrypt_ctx_sym_final(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r)
+{
+	return dcrypt_vfs->ctx_sym_final(ctx, result, error_r);
+}
+
+void dcrypt_ctx_sym_set_padding(struct dcrypt_context_symmetric *ctx, bool padding)
+{
+	return dcrypt_vfs->ctx_sym_set_padding(ctx, padding);
+}
+
+bool dcrypt_ctx_hmac_create(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r)
+{
+	return dcrypt_vfs->ctx_hmac_create(algorithm, ctx_r, error_r);
+}
+void dcrypt_ctx_hmac_destroy(struct dcrypt_context_hmac **ctx)
+{
+	dcrypt_vfs->ctx_hmac_destroy(ctx);
+}
+
+void dcrypt_ctx_hmac_set_key(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len)
+{
+	dcrypt_vfs->ctx_hmac_set_key(ctx, key, key_len);
+}
+bool dcrypt_ctx_hmac_get_key(struct dcrypt_context_hmac *ctx, buffer_t *key)
+{
+	return dcrypt_vfs->ctx_hmac_get_key(ctx, key);
+}
+void dcrypt_ctx_hmac_set_key_random(struct dcrypt_context_hmac *ctx)
+{
+	dcrypt_vfs->ctx_hmac_set_key_random(ctx);
+}
+
+unsigned int dcrypt_ctx_hmac_get_digest_length(struct dcrypt_context_hmac *ctx)
+{
+	return dcrypt_vfs->ctx_hmac_get_digest_length(ctx);
+}
+
+bool dcrypt_ctx_hmac_init(struct dcrypt_context_hmac *ctx, const char **error_r)
+{
+	return dcrypt_vfs->ctx_hmac_init(ctx, error_r);
+}
+bool dcrypt_ctx_hmac_update(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r)
+{
+	return dcrypt_vfs->ctx_hmac_update(ctx, data, data_len, error_r);
+}
+bool dcrypt_ctx_hmac_final(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r)
+{
+	return dcrypt_vfs->ctx_hmac_final(ctx, result, error_r);
+}
+
+bool dcrypt_ecdh_derive_secret_local(struct dcrypt_private_key *local_key, buffer_t *R, buffer_t *S, const char **error_r)
+{
+	return dcrypt_vfs->ecdh_derive_secret_local(local_key, R, S, error_r);
+}
+bool dcrypt_ecdh_derive_secret_peer(struct dcrypt_public_key *peer_key, buffer_t *R, buffer_t *S, const char **error_r)
+{
+	return dcrypt_vfs->ecdh_derive_secret_peer(peer_key, R, S, error_r);
+}
+
+bool dcrypt_pbkdf2(const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len,
+	const char *hash, unsigned int rounds, buffer_t *result, unsigned int result_len, const char **error_r)
+{
+	return dcrypt_vfs->pbkdf2(password, password_len, salt, salt_len, hash, rounds, result, result_len, error_r);
+}
+
+bool dcrypt_keypair_generate(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind, unsigned int bits, const char *curve, const char **error_r)
+{
+	return dcrypt_vfs->generate_keypair(pair_r, kind, bits, curve, error_r);
+}
+
+bool dcrypt_key_load_private(struct dcrypt_private_key **key_r, enum dcrypt_key_format format, const char *data,
+	const char *password, struct dcrypt_private_key *dec_key, const char **error_r)
+{
+	return dcrypt_vfs->load_private_key(key_r, format, data, password, dec_key, error_r);
+}
+bool dcrypt_key_load_public(struct dcrypt_public_key **key_r, enum dcrypt_key_format format, const char *data, const char **error_r)
+{
+	return dcrypt_vfs->load_public_key(key_r, format, data, error_r);
+}
+bool dcrypt_key_store_private(struct dcrypt_private_key *key, enum dcrypt_key_format format, const char *cipher, buffer_t *destination,
+	const char *password, struct dcrypt_public_key *enc_key, const char **error_r)
+{
+	return dcrypt_vfs->store_private_key(key, format, cipher, destination, password, enc_key, error_r);
+}
+bool dcrypt_key_store_public(struct dcrypt_public_key *key, enum dcrypt_key_format format, buffer_t *destination, const char **error_r)
+{
+	return dcrypt_vfs->store_public_key(key, format, destination, error_r);
+}
+
+bool dcrypt_key_convert_private_to_public(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r, const char **error_r)
+{
+	return dcrypt_vfs->private_to_public_key(priv_key, pub_key_r, error_r);
+}
+bool dcrypt_key_string_get_info(const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r,
+	enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r,
+	const char **key_hash_r, const char **error_r)
+{
+	return dcrypt_vfs->key_string_get_info(key_data, format_r, version_r, kind_r, encryption_type_r,
+		encryption_key_hash_r, key_hash_r, error_r);
+}
+
+bool dcrypt_key_type_private(struct dcrypt_private_key *key, enum dcrypt_key_type *type)
+{
+	return dcrypt_vfs->private_key_type(key, type);
+}
+bool dcrypt_key_type_public(struct dcrypt_public_key *key, enum dcrypt_key_type *type)
+{
+	return dcrypt_vfs->public_key_type(key, type);
+}
+bool dcrypt_key_id_public(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r)
+{
+	return dcrypt_vfs->public_key_id(key, algorithm, result, error_r);
+}
+bool dcrypt_key_id_public_old(struct dcrypt_public_key *key, buffer_t *result, const char **error_r)
+{
+	return dcrypt_vfs->public_key_id_old(key, result, error_r);
+}
+void dcrypt_keypair_free(struct dcrypt_keypair *keypair)
+{
+	dcrypt_vfs->free_keypair(keypair);
+}
+void dcrypt_key_free_public(struct dcrypt_public_key **key)
+{
+	dcrypt_vfs->free_public_key(key);
+}
+void dcrypt_key_free_private(struct dcrypt_private_key **key)
+{
+	dcrypt_vfs->free_private_key(key);
+}
+
+bool dcrypt_rsa_encrypt(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+	return dcrypt_vfs->rsa_encrypt(key, data, data_len, result, error_r);
+}
+bool dcrypt_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r)
+{
+	return dcrypt_vfs->rsa_decrypt(key, data, data_len, result, error_r);
+}
+
+const char *dcrypt_oid2name(const unsigned char *oid, size_t oid_len, const char **error_r)
+{
+	return dcrypt_vfs->oid2name(oid, oid_len, error_r);
+}
+bool dcrypt_name2oid(const char *name, buffer_t *oid, const char **error_r)
+{
+	return dcrypt_vfs->name2oid(name, oid, error_r);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/dcrypt.h	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,204 @@
+#ifndef DCRYPT_H
+#define DCRYPT_H 1
+
+struct dcrypt_context_symmetric;
+struct dcrypt_context_hmac;
+struct dcrypt_public_key;
+struct dcrypt_private_key;
+
+struct dcrypt_keypair {
+	struct dcrypt_public_key *pub;
+	struct dcrypt_private_key *priv;
+};
+
+enum dcrypt_sym_mode {
+	DCRYPT_MODE_ENCRYPT,
+	DCRYPT_MODE_DECRYPT
+};
+
+enum dcrypt_key_type {
+	DCRYPT_KEY_RSA = 0x1,
+	DCRYPT_KEY_EC  = 0x2
+};
+
+/**
+ * dovecot key format:
+ * version tab version-specific data
+ * v1: version tab nid tab raw ec private key (in hex)
+ * v2: version tab algorithm oid tab private-or-public-key-only (in hex)
+ */
+enum dcrypt_key_format {
+	DCRYPT_FORMAT_PEM,
+	DCRYPT_FORMAT_DOVECOT,
+};
+
+enum dcrypt_key_encryption_type {
+	DCRYPT_KEY_ENCRYPTION_TYPE_NONE,
+	DCRYPT_KEY_ENCRYPTION_TYPE_KEY,
+	DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD
+};
+
+enum dcrypt_key_version {
+	DCRYPT_KEY_VERSION_1,
+	DCRYPT_KEY_VERSION_2,
+	DCRYPT_KEY_VERSION_NA /* not applicable, PEM key */
+};
+
+enum dcrypt_key_kind {
+	DCRYPT_KEY_KIND_PUBLIC,
+	DCRYPT_KEY_KIND_PRIVATE
+};
+
+/**
+ * load and initialize dcrypt backend, use either openssl or gnutls
+ */
+bool dcrypt_initialize(const char *backend, const char **error_r);
+
+/**
+ * deinitialize dcrypt
+ */
+void dcrypt_deinitialize(void);
+
+/**
+ * create symmetric context
+ */
+bool dcrypt_ctx_sym_create(const char *algorithm, enum dcrypt_sym_mode mode, struct dcrypt_context_symmetric **ctx_r, const char **error_r);
+
+/**
+ * destroy symmetric context and free memory
+ */
+void dcrypt_ctx_sym_destroy(struct dcrypt_context_symmetric **ctx);
+
+/**
+ * key and IV manipulation functions
+ */
+void dcrypt_ctx_sym_set_key(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len);
+void dcrypt_ctx_sym_set_iv(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len);
+void dcrypt_ctx_sym_set_key_iv_random(struct dcrypt_context_symmetric *ctx);
+bool dcrypt_ctx_sym_get_key(struct dcrypt_context_symmetric *ctx, buffer_t *key);
+bool dcrypt_ctx_sym_get_iv(struct dcrypt_context_symmetric *ctx, buffer_t *iv);
+
+/**
+ * turn padding on/off (default: on)
+ */
+void dcrypt_ctx_sym_set_padding(struct dcrypt_context_symmetric *ctx, bool padding);
+
+
+/**
+ * authentication data manipulation (use with GCM only)
+ */
+void dcrypt_ctx_sym_set_aad(struct dcrypt_context_symmetric *ctx, const unsigned char *aad, size_t aad_len);
+bool dcrypt_ctx_sym_get_aad(struct dcrypt_context_symmetric *ctx, buffer_t *aad);
+/**
+ * result tag from aead (use with GCM only)
+ */
+void dcrypt_ctx_sym_set_tag(struct dcrypt_context_symmetric *ctx, const unsigned char *tag, size_t tag_len);
+bool dcrypt_ctx_sym_get_tag(struct dcrypt_context_symmetric *ctx, buffer_t *tag);
+
+/* get various lengths */
+unsigned int dcrypt_ctx_sym_get_key_length(struct dcrypt_context_symmetric *ctx);
+unsigned int dcrypt_ctx_sym_get_iv_length(struct dcrypt_context_symmetric *ctx);
+unsigned int dcrypt_ctx_sym_get_block_size(struct dcrypt_context_symmetric *ctx);
+
+/**
+ * initialize crypto
+ */
+bool dcrypt_ctx_sym_init(struct dcrypt_context_symmetric *ctx, const char **error_r);
+/**
+ * update with data
+ */
+bool dcrypt_ctx_sym_update(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r);
+/**
+ * perform final step (may or may not emit data)
+ */
+bool dcrypt_ctx_sym_final(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r);
+
+/**
+ * create HMAC context, algorithm is digest algorithm
+ */
+bool dcrypt_ctx_hmac_create(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r);
+/**
+ * destroy HMAC context and free memory
+ */
+void dcrypt_ctx_hmac_destroy(struct dcrypt_context_hmac **ctx);
+
+/**
+ * hmac key manipulation
+ */
+void dcrypt_ctx_hmac_set_key(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len);
+bool dcrypt_ctx_hmac_get_key(struct dcrypt_context_hmac *ctx, buffer_t *key);
+void dcrypt_ctx_hmac_set_key_random(struct dcrypt_context_hmac *ctx);
+
+/**
+ * get digest length for HMAC
+ */
+unsigned int dcrypt_ctx_hmac_get_digest_length(struct dcrypt_context_hmac *ctx);
+
+/**
+ * initialize hmac
+ */
+bool dcrypt_ctx_hmac_init(struct dcrypt_context_hmac *ctx, const char **error_r);
+/**
+ * update hmac context with data
+ */
+bool dcrypt_ctx_hmac_update(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r);
+/**
+ * perform final rounds and retrieve result
+ */
+bool dcrypt_ctx_hmac_final(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r);
+
+
+/**
+ * Elliptic Curve based Diffie-Heffman shared secret derivation */
+bool dcrypt_ecdh_derive_secret_local(struct dcrypt_private_key *local_key, buffer_t *R, buffer_t *S, const char **error_r);
+bool dcrypt_ecdh_derive_secret_peer(struct dcrypt_public_key *peer_key, buffer_t *R, buffer_t *S, const char **error_r);
+
+/**
+ * generate cryptographic data from password and salt. Use 1000-10000 for rounds.
+ */
+bool dcrypt_pbkdf2(const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len,
+	const char *hash, unsigned int rounds, buffer_t *result, unsigned int result_len, const char **error_r);
+
+bool dcrypt_keypair_generate(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind, unsigned int bits, const char *curve, const char **error_r);
+
+/**
+ * load loads key structure from external format. 
+ * store stores key structure into external format.
+ *
+ * you can provide either PASSWORD or ENC_KEY, not both.
+ */
+bool dcrypt_key_load_private(struct dcrypt_private_key **key_r, enum dcrypt_key_format format, const char *data, 
+	const char *password, struct dcrypt_private_key *dec_key, const char **error_r);
+
+bool dcrypt_key_load_public(struct dcrypt_public_key **key_r, enum dcrypt_key_format format, const char *data, const char **error_r);
+
+bool dcrypt_key_store_private(struct dcrypt_private_key *key, enum dcrypt_key_format format, const char *cipher, 
+	buffer_t *destination, const char *password, struct dcrypt_public_key *enc_key, const char **error_r);
+
+bool dcrypt_key_store_public(struct dcrypt_public_key *key, enum dcrypt_key_format format, buffer_t *destination, const char **error_r);
+
+bool dcrypt_key_convert_private_to_public(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r, const char **error_r);
+
+void dcrypt_keypair_free(struct dcrypt_keypair *keypair);
+
+void dcrypt_key_free_public(struct dcrypt_public_key **key);
+void dcrypt_key_free_private(struct dcrypt_private_key **key);
+
+bool dcrypt_key_type_private(struct dcrypt_private_key *key, enum dcrypt_key_type *type);
+bool dcrypt_key_type_public(struct dcrypt_public_key *key, enum dcrypt_key_type *type);
+bool dcrypt_key_id_public(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r); /* return digest of key */
+bool dcrypt_key_id_public_old(struct dcrypt_public_key *key, buffer_t *result, const char **error_r); /* return SHA1 sum of key */
+
+bool dcrypt_key_string_get_info(const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r,
+	enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r,
+	const char **key_hash_r, const char **error_r);
+
+/* RSA stuff */
+bool dcrypt_rsa_encrypt(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r);
+bool dcrypt_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r);
+
+/* OID stuff */
+const char *dcrypt_oid2name(const unsigned char *oid, size_t oid_len, const char **error_r);
+bool dcrypt_name2oid(const char *name, buffer_t *oid, const char **error_r);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/istream-decrypt.c	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,873 @@
+#include "lib.h"
+#include "buffer.h"
+#include "randgen.h"
+#include "safe-memset.h"
+#include "hash-method.h"
+#include "sha2.h"
+#include "dcrypt.h"
+#include "istream.h"
+#include "istream-decrypt.h"
+#include "istream-private.h"
+#include "dcrypt-iostream-private.h"
+
+#include "hex-binary.h"
+
+#include <arpa/inet.h>
+
+#define ISTREAM_DECRYPT_READ_FIRST 15
+
+enum io_stream_encrypt_flags {
+	IO_STREAM_ENC_INTEGRITY_HMAC = 0x1,
+	IO_STREAM_ENC_INTEGRITY_AEAD = 0x2,
+	IO_STREAM_ENC_INTEGRITY_NONE = 0x4,
+	IO_STREAM_ENC_VERSION_1      = 0x8,
+};
+
+struct decrypt_istream {
+	struct istream_private istream;
+	buffer_t *buf;
+
+	i_stream_decrypt_get_key_callback_t *key_callback;
+	void *key_context;
+
+	struct dcrypt_private_key *priv_key;
+	bool initialized;
+	bool finalized;
+	bool use_mac;
+
+	uoff_t ftr, pos;
+	enum io_stream_encrypt_flags flags;
+
+	unsigned char *iv;  /* original iv, in case seeking is done, future feature */
+
+	struct dcrypt_context_symmetric *ctx_sym;
+	struct dcrypt_context_hmac *ctx_mac;
+
+	enum {
+		DECRYPT_FORMAT_V1,
+		DECRYPT_FORMAT_V2
+	} format;
+};
+
+static
+ssize_t i_stream_decrypt_read_header_v1(struct decrypt_istream *stream,
+	const unsigned char *data, size_t mlen)
+{
+	const char *error = NULL;
+	size_t hdr_len = 0;
+	uint16_t len;
+	int ec, i = 0;
+
+	const unsigned char *digest_pos = NULL, *key_digest_pos = NULL, *key_ct_pos = NULL;
+
+	size_t pos = 7;
+	size_t digest_len = 0;
+	size_t key_ct_len = 0;
+	size_t key_digest_size = 0;
+
+	buffer_t ephemeral_key;
+	buffer_t *secret = buffer_create_dynamic(pool_datastack_create(), 256);
+	buffer_t *key = buffer_create_dynamic(pool_datastack_create(), 256);
+
+	hdr_len = ((data[0] << 8) | data[1]) + 12;
+	if (mlen < hdr_len) {
+		/* try to read more */
+		return 0;
+	}
+
+	data+=2;
+	mlen-=2;
+
+	memcpy(&len, data, 2);
+
+	while(i < 4 && mlen > 2 && (len = ntohs(len)) <= (mlen - 2) && len > 0) {
+		data += 2;
+		mlen -= 2;
+		pos += 2;
+
+		switch(i++) {
+		case 0:
+			buffer_create_from_const_data(&ephemeral_key, data, len);
+			break;
+		case 1:
+			/* public key id */
+			digest_pos = data;
+			digest_len = len;
+			break;
+		case 2:
+			/* encryption key digest */
+			key_digest_pos = data;
+			key_digest_size = len;
+			break;
+		case 3:
+			/* encrypted key data */
+			key_ct_pos = data;
+			key_ct_len = len;
+			break;
+		}
+		pos += len;
+		data += len;
+		mlen -= len;
+		memcpy(&len, data, 2);
+	}
+
+	if (i < 4) {
+		io_stream_set_error(&stream->istream.iostream, "Invalid or corrupted header");
+		return -1;
+	}
+
+	/* we don't have a private key */
+	if (stream->priv_key == NULL) {
+		/* see if we can get one */
+		if (stream->key_callback != NULL) {
+			unsigned char *key_id = t_malloc(digest_len);
+			memcpy(key_id, digest_pos, digest_len);
+			int ret = stream->key_callback(key_id, &(stream->priv_key), &error, stream->key_context);
+			if (ret < 0) {
+				io_stream_set_error(&stream->istream.iostream, "Private key not available: %s", error);
+				return -1;
+			}
+			if (ret == 0) {
+				io_stream_set_error(&stream->istream.iostream, "Private key not available");
+				return -1;
+			}
+		} else {
+			io_stream_set_error(&stream->istream.iostream, "Private key not available");
+			return -1;
+		}
+	}
+
+	buffer_t *check = buffer_create_dynamic(pool_datastack_create(), 32);
+	struct dcrypt_public_key *pubkey = NULL;
+
+	/* do we have correct private key? */
+	if (!dcrypt_key_convert_private_to_public(stream->priv_key, &pubkey, &error)) {
+		io_stream_set_error(&stream->istream.iostream, "Cannot convert private key to public: %s", error);
+		return -1;
+	}
+	ec = 0;
+	if (!dcrypt_key_id_public_old(pubkey, check, &error)) {
+		io_stream_set_error(&stream->istream.iostream, "Cannot get public key hash: %s", error);
+		ec = -1;
+	} else {
+		if (memcmp(digest_pos, check->data, I_MIN(digest_len,check->used)) != 0) {
+			io_stream_set_error(&stream->istream.iostream, "Private key not available");
+			ec = -1;
+		}
+	}
+	dcrypt_key_free_public(&pubkey);
+	if (ec != 0) return ec;
+
+	/* derive shared secret */
+	if (!dcrypt_ecdh_derive_secret_local(stream->priv_key, &ephemeral_key, secret, &error)) {
+		io_stream_set_error(&stream->istream.iostream, "Cannot perform ECDH: %s", error);
+		return -1;
+	}
+
+	/* run it thru SHA256 once */
+	const struct hash_method *hash = &hash_method_sha256;
+	unsigned char hctx[hash->context_size];
+	unsigned char hres[hash->digest_size];
+	hash->init(hctx);
+	hash->loop(hctx, secret->data, secret->used);
+	hash->result(hctx, hres);
+	safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
+
+	/* NB! The old code was broken and used this kind of IV - it is not correct, but
+	   we need to stay compatible with old data */
+
+	/* use it to decrypt the actual encryption key */
+	struct dcrypt_context_symmetric *dctx;
+	if (!dcrypt_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_DECRYPT, &dctx, &error)) {
+		io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error);
+		return -1;
+	}
+
+	ec = 0;
+	dcrypt_ctx_sym_set_iv(dctx, (const unsigned char*)"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", 16);
+	dcrypt_ctx_sym_set_key(dctx, hres, hash->digest_size);
+	if (!dcrypt_ctx_sym_init(dctx, &error) ||
+	    !dcrypt_ctx_sym_update(dctx, key_ct_pos, key_ct_len, key, &error) ||
+	    !dcrypt_ctx_sym_final(dctx, key, &error)) {
+		io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error);
+		ec = -1;
+	}
+	dcrypt_ctx_sym_destroy(&dctx);
+
+	if (ec != 0) {
+		io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error);
+		return -1;
+	}
+
+	/* see if we got the correct key */
+	hash->init(hctx);
+	hash->loop(hctx, key->data, key->used);
+	hash->result(hctx, hres);
+
+	if (key_digest_size != sizeof(hres)) {
+		io_stream_set_error(&stream->istream.iostream, "Key decryption error: invalid digest length");
+		return -1;
+	}
+	if (memcmp(hres, key_digest_pos, sizeof(hres)) != 0) {
+		io_stream_set_error(&stream->istream.iostream, "Key decryption error: decrypted key is invalid");
+		return -1;
+	}
+
+	/* prime context with key */
+	if (!dcrypt_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_DECRYPT, &(stream->ctx_sym), &error)) {
+		io_stream_set_error(&stream->istream.iostream, "Decryption context create error: %s", error);
+		return -1;
+	}
+
+	/* Again, old code used this IV, so we have to use it too */
+	dcrypt_ctx_sym_set_iv(stream->ctx_sym, (const unsigned char*)"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", 16);
+	dcrypt_ctx_sym_set_key(stream->ctx_sym, key->data, key->used);
+
+	safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used);
+
+	if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) {
+		io_stream_set_error(&stream->istream.iostream, "Decryption init error: %s", error);
+		return -1;
+	}
+
+	stream->use_mac = FALSE;
+	stream->initialized = TRUE;
+	/* now we are ready to decrypt stream */
+
+	return hdr_len;
+}
+
+static bool get_msb32(const unsigned char **_data, const unsigned char *end, uint32_t *num_r)
+{
+	const unsigned char *data = *_data;
+	if (end-data < 4)
+		return FALSE;
+	*num_r = ((uint32_t)data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+	*_data += 4;
+	return TRUE;
+}
+
+static
+bool i_stream_decrypt_der(const unsigned char **_data,
+			  const unsigned char *end, const char **str_r)
+{
+	const unsigned char *data = *_data;
+	unsigned int len;
+
+	if (end-data < 2)
+		return FALSE;
+	/* get us DER encoded length */
+	if ((data[1] & 0x80) != 0) {
+		/* two byte length */
+		if (end-data < 3)
+			return FALSE;
+		len = ((data[1] & 0x7f) << 8) + data[2] + 3;
+	} else {
+		len = data[1] + 2;
+	}
+	if ((size_t)(end-data) < len)
+		return FALSE;
+	*str_r = dcrypt_oid2name(data, len, NULL);
+	*_data += len;
+	return TRUE;
+}
+
+static
+ssize_t i_stream_decrypt_key(struct decrypt_istream *stream, const char *malg, unsigned int rounds,
+	const unsigned char *data, const unsigned char *end, buffer_t *key, size_t key_len)
+{
+	const char *error;
+	enum dcrypt_key_type ktype;
+	int keys;
+	bool have_key = FALSE;
+	unsigned char dgst[32];
+	uint32_t val;
+	buffer_t buf;
+
+	if (data == end)
+		return 0;
+
+	keys = *data++;
+
+	/* if we have a key, prefab the digest */
+	if (stream->key_callback == NULL) {
+		if (stream->priv_key == NULL) {	
+			io_stream_set_error(&stream->istream.iostream, "Decryption error: no private key available");
+			return -1;
+		}
+		buffer_create_from_data(&buf, dgst, sizeof(dgst));
+		struct dcrypt_public_key *pub = NULL;
+		dcrypt_key_convert_private_to_public(stream->priv_key, &pub, NULL);
+		dcrypt_key_id_public(pub, "sha256", &buf, NULL);
+		dcrypt_key_free_public(&pub);
+	}
+
+	/* for each key */
+	for(;keys>0;keys--) {
+		if ((size_t)(end-data) < 1 + (ssize_t)sizeof(dgst))
+			return 0;
+		ktype = *data++;
+
+		if (stream->key_callback != NULL) {
+			memcpy(dgst, data, sizeof(dgst));
+			/* hope you going to give us right key.. */
+			int ret = stream->key_callback(dgst, &(stream->priv_key), &error, stream->key_context);
+			if (ret < 0) {
+				io_stream_set_error(&stream->istream.iostream, "Private key not available: %s", error);
+				return -1;
+			}
+			if (ret > 0) {
+				have_key = TRUE;
+				break;
+			}
+		} else {
+			/* see if key matches to the one we have */
+			if (memcmp(dgst, data, sizeof(dgst)) == 0) {
+			      	have_key = TRUE;
+				break;
+			}
+		}
+		data += sizeof(dgst);
+
+		/* wasn't correct key, skip over some data */
+		if (!get_msb32(&data, end, &val) ||
+		    !get_msb32(&data, end, &val))
+			return 0;
+	}
+
+	/* didn't find matching key */
+	if (!have_key) {
+		io_stream_set_error(&stream->istream.iostream, "Decryption error: no private key available");
+		return -1;
+	}
+
+	data += sizeof(dgst);
+
+	const unsigned char *ephemeral_key;
+	uint32_t ep_key_len;
+	const unsigned char *encrypted_key;
+	uint32_t eklen;
+	const unsigned char *ekhash;
+	uint32_t ekhash_len;
+
+	/* read ephemeral key (can be missing for RSA) */
+	if (!get_msb32(&data, end, &ep_key_len) || (size_t)(end-data) < ep_key_len)
+		return 0;
+	ephemeral_key = data;
+	data += ep_key_len;
+
+	/* read encrypted key */
+	if (!get_msb32(&data, end, &eklen) || (size_t)(end-data) < eklen)
+		return 0;
+	encrypted_key = data;
+	data += eklen;
+
+	/* read key data hash */
+	if (!get_msb32(&data, end, &ekhash_len) || (size_t)(end-data) < ekhash_len)
+		return 0;
+	ekhash = data;
+	data += ekhash_len;
+
+	/* decrypt the seed */
+	if (ktype == DCRYPT_KEY_RSA) {
+		if (!dcrypt_rsa_decrypt(stream->priv_key, encrypted_key, eklen, key, &error)) {
+			io_stream_set_error(&stream->istream.iostream, "key decryption error: %s", error);
+			return -1;
+		}
+	} else if (ktype == DCRYPT_KEY_EC) {
+		/* perform ECDHE */
+		buffer_t *temp_key = buffer_create_dynamic(pool_datastack_create(), 256);
+		buffer_t *secret = buffer_create_dynamic(pool_datastack_create(), 256);
+		buffer_t peer_key;
+		buffer_create_from_const_data(&peer_key, ephemeral_key, ep_key_len);
+		if (!dcrypt_ecdh_derive_secret_local(stream->priv_key, &peer_key, secret, &error)) {
+			io_stream_set_error(&stream->istream.iostream, "Key decryption error: corrupted header");
+			return -1;
+		}
+
+		/* use shared secret and peer key to generate decryption key, AES-256-CBC has 32 byte key and 16 byte IV */
+		if (!dcrypt_pbkdf2(secret->data, secret->used, peer_key.data, peer_key.used,
+		    malg, rounds, temp_key, 32+16, &error)) {
+			safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
+			io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error);
+			return -1;
+		}
+
+		safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
+		if (temp_key->used != 32+16) {
+			safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used);
+			io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: invalid temporary key");
+			return -1;
+		}
+		struct dcrypt_context_symmetric *dctx;
+		if (!dcrypt_ctx_sym_create("AES-256-CBC", DCRYPT_MODE_DECRYPT, &dctx, &error)) {
+			safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used);
+			io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error);
+			return -1;
+		}
+		const unsigned char *ptr = temp_key->data;
+
+		/* we use ephemeral_key for IV */
+		dcrypt_ctx_sym_set_key(dctx, ptr, 32);
+		dcrypt_ctx_sym_set_iv(dctx, ptr+32, 16);
+		safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used);
+
+		int ec = 0;
+		if (!dcrypt_ctx_sym_init(dctx, &error) ||
+		    !dcrypt_ctx_sym_update(dctx, encrypted_key, eklen, key, &error) ||
+		    !dcrypt_ctx_sym_final(dctx, key, &error)) {
+			io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: %s", error);
+			ec = -1;
+		}
+
+		if (key->used != key_len) {
+			io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: invalid key length");
+			ec = -1;
+		}
+
+		dcrypt_ctx_sym_destroy(&dctx);
+		if (ec != 0) return ec;
+	} else {
+		io_stream_set_error(&stream->istream.iostream, "Decryption error: unsupported key type 0x%02x", ktype);
+		return -1;
+	}
+
+	/* make sure we were able to decrypt the encrypted key correctly */
+	const struct hash_method *hash = hash_method_lookup(t_str_lcase(malg));
+	if (hash == NULL) {
+		safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used);
+		io_stream_set_error(&stream->istream.iostream, "Decryption error: unsupported hash algorithm: %s", malg);
+		return -1;
+	}
+	unsigned char hctx[hash->context_size];
+	unsigned char hres[hash->digest_size];
+	hash->init(hctx);
+	hash->loop(hctx, key->data, key->used);
+	hash->result(hctx, hres);
+
+	for(int i = 1; i < 2049; i++) {
+		uint32_t i_msb = htonl(i);
+
+		hash->init(hctx);
+		hash->loop(hctx, hres, sizeof(hres));
+		hash->loop(hctx, &i_msb, sizeof(i_msb));
+		hash->result(hctx, hres);
+	}
+
+	/* do the comparison */
+	if (memcmp(ekhash, hres, I_MIN(ekhash_len, sizeof(hres))) != 0) {
+		safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used);
+		io_stream_set_error(&stream->istream.iostream, "Decryption error: corrupted header ekhash");
+		return -1;
+	}
+	return 1;
+}
+
+static
+int i_stream_decrypt_header_contents(struct decrypt_istream *stream,
+				     const unsigned char *data, size_t size)
+{
+	const unsigned char *end = data + size;
+	bool failed = FALSE;
+
+	/* read cipher OID */
+	const char *calg;
+	if (!i_stream_decrypt_der(&data, end, &calg))
+		return 0;
+	if (calg == NULL || !dcrypt_ctx_sym_create(calg, DCRYPT_MODE_DECRYPT, &(stream->ctx_sym), NULL)) {
+		io_stream_set_error(&stream->istream.iostream, "Decryption error: unsupported/invalid cipher: %s", calg);
+		return -1;
+	}
+
+	/* read MAC oid (MAC is used for PBKDF2 and key data digest, too) */
+	const char *malg;
+	if (!i_stream_decrypt_der(&data, end, &malg))
+		return 0;
+	if (malg == NULL || !dcrypt_ctx_hmac_create(malg, &(stream->ctx_mac), NULL)) {
+		io_stream_set_error(&stream->istream.iostream, "Decryption error: unsupported/invalid MAC algorithm: %s", malg);
+		return -1;
+	}
+
+	/* read rounds (for PBKDF2) */
+	uint32_t rounds;
+	if (!get_msb32(&data, end, &rounds))
+		return 0;
+	/* read key data length */
+	uint32_t kdlen;
+	if (!get_msb32(&data, end, &kdlen))
+		return 0;
+
+	size_t tagsize;
+
+	if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+		tagsize = IOSTREAM_TAG_SIZE;
+	} else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) {
+		tagsize = IOSTREAM_TAG_SIZE;
+	} else {
+		tagsize = 0;
+	}
+
+	/* how much key data we should be getting */
+	size_t kl = dcrypt_ctx_sym_get_key_length(stream->ctx_sym) + dcrypt_ctx_sym_get_iv_length(stream->ctx_sym) + tagsize;
+	buffer_t *keydata = buffer_create_dynamic(pool_datastack_create(), kl);
+
+	/* try to decrypt the keydata with a private key */
+	int ret;
+	if ((ret = i_stream_decrypt_key(stream, malg, rounds, data, end, keydata, kl)) <= 0)
+		return ret;
+
+	/* oh, it worked! */
+	const unsigned char *ptr = keydata->data;
+	if (keydata->used != kl) {
+		/* but returned wrong amount of data */
+		io_stream_set_error(&stream->istream.iostream, "Key decryption error: Key data length mismatch");
+		return -1;
+	}
+
+	/* prime contexts */
+	dcrypt_ctx_sym_set_key(stream->ctx_sym, ptr, dcrypt_ctx_sym_get_key_length(stream->ctx_sym));
+	ptr += dcrypt_ctx_sym_get_key_length(stream->ctx_sym);
+	dcrypt_ctx_sym_set_iv(stream->ctx_sym, ptr, dcrypt_ctx_sym_get_iv_length(stream->ctx_sym));
+	stream->iv = i_malloc(dcrypt_ctx_sym_get_iv_length(stream->ctx_sym));
+	memcpy(stream->iv, ptr, dcrypt_ctx_sym_get_iv_length(stream->ctx_sym));
+	ptr += dcrypt_ctx_sym_get_iv_length(stream->ctx_sym);
+
+	/* based on the chosen MAC, initialize HMAC or AEAD */
+	if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+		const char *error;
+		dcrypt_ctx_hmac_set_key(stream->ctx_mac, ptr, tagsize);
+		if (!dcrypt_ctx_hmac_init(stream->ctx_mac, &error)) {
+			io_stream_set_error(&stream->istream.iostream, "MAC error: %s", error);
+			failed = TRUE;
+		}
+		stream->ftr = dcrypt_ctx_hmac_get_digest_length(stream->ctx_mac);
+		stream->use_mac = TRUE;
+	} else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) {
+		dcrypt_ctx_sym_set_aad(stream->ctx_sym, ptr, tagsize);
+		stream->ftr = tagsize;
+		stream->use_mac = TRUE;
+	} else {
+		stream->use_mac = FALSE;
+	}
+	/* destroy private key data */
+	safe_memset(buffer_get_modifiable_data(keydata, 0), 0, keydata->used);
+	buffer_set_used_size(keydata, 0);
+	return failed ? -1 : 1;
+}
+
+static
+ssize_t i_stream_decrypt_read_header(struct decrypt_istream *stream,
+	const unsigned char *data, size_t mlen)
+{
+	const char *error;
+	const unsigned char *end = data + mlen;
+
+	/* check magic */
+	if (mlen < sizeof(IOSTREAM_CRYPT_MAGIC))
+		return 0;
+	if (memcmp(data, IOSTREAM_CRYPT_MAGIC, sizeof(IOSTREAM_CRYPT_MAGIC)) != 0) {
+		io_stream_set_error(&stream->istream.iostream, "Invalid magic");
+		return -1;
+	}
+	data += sizeof(IOSTREAM_CRYPT_MAGIC);
+
+	if (data >= end)
+		return 0; /* read more? */
+
+	/* check version */
+	if (*data == '\x01') {
+		stream->format = DECRYPT_FORMAT_V1;
+		return i_stream_decrypt_read_header_v1(stream, data+1, end - (data+1));
+	} else if (*data != '\x02') {
+		io_stream_set_error(&stream->istream.iostream, "Unsupported encrypted data 0x%02x", *data);
+		return -1;
+	}
+
+	stream->format = DECRYPT_FORMAT_V2;
+
+	data++;
+
+	/* read flags */
+	uint32_t flags;
+	if (!get_msb32(&data, end, &flags))
+		return 0;
+	stream->flags = flags;
+
+	/* get the total length of header */
+	uint32_t hdr_len;
+	if (!get_msb32(&data, end, &hdr_len))
+		return 0;
+	if ((size_t)(end-data) < hdr_len)
+		return 0;
+
+	int ret;
+	if ((ret = i_stream_decrypt_header_contents(stream, data, hdr_len)) < 0)
+		return -1;
+	else if (ret == 0) {
+		io_stream_set_error(&stream->istream.iostream, "Decryption error: truncate header length");
+		return -1;
+	}
+	stream->initialized = TRUE;
+
+	/* if it all went well, try to initialize decryption context */
+	if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) {
+		io_stream_set_error(&stream->istream.iostream, "Decryption init error: %s", error);
+		return -1;
+	}
+	return hdr_len;
+}
+
+static ssize_t
+i_stream_decrypt_read(struct istream_private *stream)
+{
+	struct decrypt_istream *dstream =
+		(struct decrypt_istream *)stream;
+	const unsigned char *data;
+	size_t size, decrypt_size;
+	const char *error = NULL;
+	int ret;
+	bool check_mac = FALSE;
+
+	/* not if it's broken */
+	if (stream->istream.stream_errno != 0)
+		return -1;
+
+	for (;;) {
+		/* remove skipped data from buffer */
+		if (stream->skip > 0) {
+			i_assert(stream->skip <= dstream->buf->used);
+			buffer_delete(dstream->buf, 0, stream->skip);
+			stream->pos -= stream->skip;
+			stream->skip = 0;
+		}
+
+		stream->buffer = dstream->buf->data;
+
+		i_assert(stream->pos <= dstream->buf->used);
+		if (stream->pos >= dstream->istream.max_buffer_size) {
+			/* stream buffer still at maximum */
+			return -2;
+		}
+
+		/* if something is already decrypted, return as much of it as
+		   we can */
+		if (dstream->buf->used > 0) {
+			size_t new_pos, bytes;
+
+			/* only return up to max_buffer_size bytes, even when buffer
+			   actually has more, as not to confuse the caller */
+			if (dstream->buf->used <= dstream->istream.max_buffer_size) {
+				new_pos = dstream->buf->used;
+				if (dstream->finalized)
+					stream->istream.eof = TRUE;
+			} else {
+				new_pos = dstream->istream.max_buffer_size;
+			}
+
+			bytes = new_pos - stream->pos;
+			stream->pos = new_pos;
+			return (ssize_t)bytes;
+		}
+		if (dstream->finalized) {
+			/* all data decrypted */
+			stream->istream.eof = TRUE;
+			return -1;
+		}
+		/* need to read more input */
+		ret = i_stream_read(stream->parent);
+		if (ret == 0)
+			return 0;
+		data = i_stream_get_data(stream->parent, &size);
+
+		if (ret == -1 && (size == 0 || stream->parent->stream_errno != 0)) {
+			stream->istream.stream_errno = stream->parent->stream_errno;
+			if (stream->istream.stream_errno != 0)
+				return -1;
+
+			if (!dstream->initialized) {
+				io_stream_set_error(&stream->iostream,
+					"Decryption error: %s",
+					"Input truncated in decryption header");
+				stream->istream.stream_errno = EINVAL;
+				return -1;
+			}
+
+			/* final block */
+			if (dcrypt_ctx_sym_final(dstream->ctx_sym,
+				dstream->buf, &error)) {
+				dstream->finalized = TRUE;
+				continue;
+			}
+			io_stream_set_error(&stream->iostream,
+				"MAC error: %s", error);
+			stream->istream.stream_errno = EINVAL;
+			return -1;
+		}
+
+		if (!dstream->initialized) {
+			ssize_t hret;
+			if ((hret=i_stream_decrypt_read_header
+				(dstream, data, size)) <= 0) {
+				if (hret < 0) {
+					stream->istream.stream_errno = EINVAL;
+				}
+				return hret;
+			}
+			i_stream_skip(stream->parent, hret);
+			data = i_stream_get_data(stream->parent, &size);
+		}
+		decrypt_size = size;
+
+		if (dstream->use_mac) {
+			if (stream->parent->eof) {
+				if (decrypt_size < dstream->ftr) {
+					io_stream_set_error(&stream->iostream,
+						"Decryption error: footer is longer than data");
+					stream->istream.stream_errno = EINVAL;
+					return -1;
+				}
+				check_mac = TRUE;
+			} else {
+				/* ignore footer's length of data until we
+				   reach EOF */
+				size -= dstream->ftr;
+			}
+			decrypt_size -= dstream->ftr;
+			if ((dstream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+				if (!dcrypt_ctx_hmac_update(dstream->ctx_mac,
+				    data, decrypt_size, &error)) {
+					io_stream_set_error(&stream->iostream,
+						"MAC error: %s", error);
+					return -1;
+				}
+			}
+		}
+
+		if (check_mac) {
+			if ((dstream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+				unsigned char dgst[dcrypt_ctx_hmac_get_digest_length(dstream->ctx_mac)];
+				buffer_t db;
+				buffer_create_from_data(&db, dgst, sizeof(dgst));
+				if (!dcrypt_ctx_hmac_final(dstream->ctx_mac, &db, &error)) {
+					io_stream_set_error(&stream->iostream,
+						"Cannot verify MAC: %s", error);
+				}
+				if (memcmp(dgst, data + decrypt_size, dcrypt_ctx_hmac_get_digest_length(dstream->ctx_mac)) != 0) {
+					io_stream_set_error(&stream->iostream,
+						"Cannot verify MAC: mismatch");
+					return -1;
+				}
+			} else if ((dstream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) {
+				dcrypt_ctx_sym_set_tag(dstream->ctx_sym, data + decrypt_size, dstream->ftr);
+			}
+		}
+
+		if (!dcrypt_ctx_sym_update(dstream->ctx_sym,
+		    data, decrypt_size, dstream->buf, &error)) {
+			io_stream_set_error(&stream->iostream,
+				"Decryption error: %s", error);
+			stream->istream.stream_errno = EINVAL;
+			return -1;
+		}
+		i_stream_skip(stream->parent, size);
+	}
+}
+
+static
+void i_stream_decrypt_close(struct iostream_private *stream,
+				   bool close_parent)
+{
+	struct decrypt_istream *dstream =
+		(struct decrypt_istream *)stream;
+
+	if (close_parent)
+		i_stream_close(dstream->istream.parent);
+}
+
+static
+void i_stream_decrypt_destroy(struct iostream_private *stream)
+{
+	struct decrypt_istream *dstream =
+		(struct decrypt_istream *)stream;
+
+	if (dstream->buf != NULL)
+		buffer_free(&dstream->buf);
+	if (dstream->iv != NULL)
+		i_free_and_null(dstream->iv);
+	if (dstream->ctx_sym != NULL)
+		dcrypt_ctx_sym_destroy(&(dstream->ctx_sym));
+	if (dstream->ctx_mac != NULL)
+		dcrypt_ctx_hmac_destroy(&(dstream->ctx_mac));
+
+	i_stream_unref(&(dstream->istream.parent));
+}
+
+static
+struct decrypt_istream *i_stream_create_decrypt_common(struct istream *input)
+{
+	struct decrypt_istream *dstream;
+
+	dstream = i_new(struct decrypt_istream, 1);
+	dstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+	dstream->istream.read = i_stream_decrypt_read;
+	dstream->istream.iostream.close = i_stream_decrypt_close;
+	dstream->istream.iostream.destroy = i_stream_decrypt_destroy;
+
+	dstream->istream.istream.readable_fd = FALSE;
+	dstream->istream.istream.blocking = TRUE;
+	dstream->istream.istream.seekable = FALSE;
+
+	dstream->buf = buffer_create_dynamic(default_pool, 128);
+
+	(void)i_stream_create(&dstream->istream, input,
+			      i_stream_get_fd(input));
+	return dstream;
+}
+
+struct istream *
+i_stream_create_decrypt(struct istream *input, struct dcrypt_private_key *priv_key)
+{
+	struct decrypt_istream *dstream;
+
+	dstream = i_stream_create_decrypt_common(input);
+	dstream->priv_key = priv_key;
+	return &dstream->istream.istream;
+}
+
+struct istream *
+i_stream_create_sym_decrypt(struct istream *input, struct dcrypt_context_symmetric *ctx)
+{
+	const char *error;
+	int ec;
+	struct decrypt_istream *dstream;
+	dstream = i_stream_create_decrypt_common(input);
+	dstream->use_mac = FALSE;
+	dstream->initialized = TRUE;
+
+	if (!dcrypt_ctx_sym_init(ctx, &error)) ec = -1;
+	else ec = 0;
+
+	dstream->ctx_sym = ctx;
+
+	if (ec != 0) {
+		io_stream_set_error(&dstream->istream.iostream, "Cannot initialize decryption: %s", error);
+		dstream->istream.istream.stream_errno = EINVAL;
+	};
+
+	return &dstream->istream.istream;
+}
+
+struct istream *
+i_stream_create_decrypt_callback(struct istream *input,
+				 i_stream_decrypt_get_key_callback_t *callback,
+				 void *context)
+{
+	struct decrypt_istream *dstream;
+
+	i_assert(callback != NULL);
+
+	dstream = i_stream_create_decrypt_common(input);
+	dstream->key_callback = callback;
+	dstream->key_context = context;
+	return &dstream->istream.istream;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/istream-decrypt.h	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,30 @@
+#ifndef ISTREAM_DECRYPT_H
+#define ISTREAM_DECRYPT_H
+
+struct dcrypt_private_key;
+struct dcrypt_context_symmetric;
+
+/* Look for a private key for a specified public key digest and set it to
+   priv_key_r. Returns 1 if ok, 0 if key doesn't exist, -1 on internal error. */
+typedef int
+i_stream_decrypt_get_key_callback_t(const unsigned char *pubkey_digest,
+				    struct dcrypt_private_key **priv_key_r,
+				    const char **error_r, void *context);
+
+struct istream *
+i_stream_create_decrypt(struct istream *input, struct dcrypt_private_key *priv_key);
+
+/* create stream for reading plain encrypted data with no header or MAC.
+   do not call dcrypt_ctx_sym_init
+ */
+struct istream *
+i_stream_create_sym_decrypt(struct istream *input, struct dcrypt_context_symmetric *ctx);
+
+
+/* Decrypt the istream. When a private key is needed, the callback will be
+   called. This allows using multiple private keys for different mails. */
+struct istream *
+i_stream_create_decrypt_callback(struct istream *input,
+				 i_stream_decrypt_get_key_callback_t *callback,
+				 void *context);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/ostream-encrypt.c	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,686 @@
+/* file truct dcrypt_public_keyyntax
+ * magic (14 bytes)
+ * version (1 bytes)
+ * flags (4 bytes)
+ * size of header (4 bytes)
+ * sha1 of key id (20 bytes)
+ * cipher oid
+ * mac oid
+ * rounds (4 bytes)
+ * key data size (4 bytes)
+ * key data
+ * cipher data
+ * mac data (mac specific bytes)
+ */
+
+#include "lib.h"
+#include "buffer.h"
+#include "randgen.h"
+#include "ostream-encrypt.h"
+#include "ostream-private.h"
+#include "hash-method.h"
+#include "sha2.h"
+#include "safe-memset.h"
+#include "dcrypt.h"
+#include "dcrypt-iostream-private.h"
+
+#include <arpa/inet.h>
+
+#define IO_STREAM_ENCRYPT_SEED_SIZE 32
+#define IO_STREAM_ENCRYPT_ROUNDS 2048
+
+struct encrypt_ostream {
+	struct ostream_private ostream;
+
+	struct dcrypt_context_symmetric *ctx_sym;
+	struct dcrypt_context_hmac *ctx_mac;
+
+	enum io_stream_encrypt_flags flags;
+	struct dcrypt_public_key *pub;
+
+	unsigned char *key_data;
+	size_t key_data_len;
+
+	buffer_t *cipher_oid;
+	buffer_t *mac_oid;
+	size_t block_size;
+
+	bool finalized;
+	bool failed;
+	bool prefix_written;
+};
+
+static
+int o_stream_encrypt_send(struct encrypt_ostream *stream,
+			  const unsigned char *data, size_t size)
+{
+	ssize_t ec;
+
+	ec = o_stream_send(stream->ostream.parent, data, size);
+	if (ec == (ssize_t)size)
+		return 0;
+	else if (ec < 0) {
+		o_stream_copy_error_from_parent(&stream->ostream);
+		return -1;
+	} else {
+		io_stream_set_error(&stream->ostream.iostream,
+			"ostream-encrypt: Unexpectedly short write to parent stream");
+		stream->ostream.ostream.stream_errno = EINVAL;
+		return -1;
+	}
+}
+
+static
+int o_stream_encrypt_send_header_v1(struct encrypt_ostream *stream)
+{
+	unsigned char c;
+	unsigned short s;
+
+	i_assert(!stream->prefix_written);
+	stream->prefix_written = TRUE;
+
+	buffer_t *values = buffer_create_dynamic(pool_datastack_create(), 256);
+	buffer_append(values, IOSTREAM_CRYPT_MAGIC, sizeof(IOSTREAM_CRYPT_MAGIC));
+	/* version */
+	c = 1;
+	buffer_append(values, &c, 1);
+	/* header length including this and data written so far */
+	s = htons(stream->key_data_len);
+	buffer_append(values, &s, 2);
+	/* then write key data */
+	buffer_append(values, stream->key_data, stream->key_data_len);
+	i_free_and_null(stream->key_data);
+
+	/* then send it to stream */
+	return o_stream_encrypt_send(stream, values->data, values->used);
+}
+
+static
+int o_stream_encrypt_send_header_v2(struct encrypt_ostream *stream)
+{
+	unsigned char c;
+	unsigned int i;
+
+	i_assert(!stream->prefix_written);
+	stream->prefix_written = TRUE;
+
+	buffer_t *values = buffer_create_dynamic(pool_datastack_create(), 256);
+	buffer_append(values, IOSTREAM_CRYPT_MAGIC, sizeof(IOSTREAM_CRYPT_MAGIC));
+	c = 2;
+	buffer_append(values, &c, 1);
+	i = htonl(stream->flags);
+	buffer_append(values, &i, 4);
+	/* store total length of header
+	   9 = version + flags + length
+	   8 = rounds + key data length
+	   */
+	i = htonl(sizeof(IOSTREAM_CRYPT_MAGIC) + 9 + stream->cipher_oid->used + stream->mac_oid->used + 8 + stream->key_data_len);
+	buffer_append(values, &i, 4);
+
+	buffer_append_buf(values, stream->cipher_oid, 0, (size_t)-1);
+	buffer_append_buf(values, stream->mac_oid, 0, (size_t)-1);
+	i = htonl(IO_STREAM_ENCRYPT_ROUNDS);
+	buffer_append(values, &i, 4);
+	i = htonl(stream->key_data_len);
+	buffer_append(values, &i, 4);
+	buffer_append(values, stream->key_data, stream->key_data_len);
+	i_free_and_null(stream->key_data);
+
+	return o_stream_encrypt_send(stream, values->data, values->used);
+}
+
+static
+int o_stream_encrypt_keydata_create_v1(struct encrypt_ostream *stream)
+{
+	buffer_t *encrypted_key, *ephemeral_key, *secret, *res, buf;
+	const char *error = NULL;
+	const struct hash_method *hash = &hash_method_sha256;
+
+	/* various temporary buffers */
+	unsigned char seed[IO_STREAM_ENCRYPT_SEED_SIZE];
+	unsigned char pkhash[hash->digest_size];
+	unsigned char ekhash[hash->digest_size];
+	unsigned char hres[hash->digest_size];
+
+	unsigned char hctx[hash->context_size];
+
+	/* hash the public key first */
+	buffer_create_from_data(&buf, pkhash, sizeof(pkhash));
+	if (!dcrypt_key_id_public_old(stream->pub, &buf, &error)) {
+		io_stream_set_error(&stream->ostream.iostream, "Key hash failed: %s", error);
+		return -1;
+	}
+
+	/* hash the key base */
+	hash->init(hctx);
+	hash->loop(hctx, seed, sizeof(seed));
+	hash->result(hctx, ekhash);
+
+	ephemeral_key = buffer_create_dynamic(pool_datastack_create(), 256);
+	encrypted_key = buffer_create_dynamic(pool_datastack_create(), 256);
+	secret = buffer_create_dynamic(pool_datastack_create(), 256);
+
+	if (!dcrypt_ecdh_derive_secret_peer(stream->pub, ephemeral_key, secret, &error)) {
+		io_stream_set_error(&stream->ostream.iostream, "Cannot perform ECDH: %s", error);
+		return -1;
+	}
+
+	/* hash the secret data */
+	hash->init(hctx);
+	hash->loop(hctx, secret->data, secret->used);
+	hash->result(hctx, hres);
+	safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
+
+	/* use it to encrypt the actual encryption key */
+	struct dcrypt_context_symmetric *dctx;
+	if (!dcrypt_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_ENCRYPT, &dctx, &error)) {
+		io_stream_set_error(&stream->ostream.iostream, "Key encryption error: %s", error);
+		return -1;
+	}
+
+	random_fill(seed, sizeof(seed));
+	hash->init(hctx);
+	hash->loop(hctx, seed, sizeof(seed));
+	hash->result(hctx, ekhash);
+
+	int ec = 0;
+
+	/* NB! The old code was broken and used this kind of IV - it is not correct, but
+	   we need to stay compatible with old data */
+	dcrypt_ctx_sym_set_iv(dctx, (const unsigned char*)"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16);
+	dcrypt_ctx_sym_set_key(dctx, hres, sizeof(hres));
+
+	if (!dcrypt_ctx_sym_init(dctx, &error) ||
+	    !dcrypt_ctx_sym_update(dctx, seed, sizeof(seed), encrypted_key, &error) ||
+	    !dcrypt_ctx_sym_final(dctx, encrypted_key, &error)) {
+		ec = -1;
+	}
+	dcrypt_ctx_sym_destroy(&dctx);
+
+	if (ec != 0) {
+		safe_memset(seed, 0, sizeof(seed));
+		io_stream_set_error(&stream->ostream.iostream, "Key encryption error: %s", error);
+		return -1;
+	}
+
+	if (ec == 0) {
+		/* same as above */
+		dcrypt_ctx_sym_set_iv(stream->ctx_sym, (const unsigned char*)"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16);
+		dcrypt_ctx_sym_set_key(stream->ctx_sym, seed, sizeof(seed));
+	}
+	safe_memset(seed, 0, sizeof(seed));
+
+	if (ec != 0) {
+		io_stream_set_error(&stream->ostream.iostream, "Encryption init error: %s", error);
+		return -1;
+	}
+
+	if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) {
+		io_stream_set_error(&stream->ostream.iostream, "Encryption init error: %s", error);
+		return -1;
+	}
+
+	res = buffer_create_dynamic(default_pool, 256);
+
+	/* ephemeral key */
+	unsigned short s;
+	s = htons(ephemeral_key->used);
+	buffer_append(res, &s, 2);
+	buffer_append(res, ephemeral_key->data, ephemeral_key->used);
+	/* public key hash */
+	s = htons(sizeof(pkhash));
+	buffer_append(res, &s, 2);
+	buffer_append(res, pkhash, sizeof(pkhash));
+	/* encrypted key hash */
+	s = htons(sizeof(ekhash));
+	buffer_append(res, &s, 2);
+	buffer_append(res, ekhash, sizeof(ekhash));
+	/* encrypted key */
+	s = htons(encrypted_key->used);
+	buffer_append(res, &s, 2);
+	buffer_append(res, encrypted_key->data, encrypted_key->used);
+
+	stream->key_data_len = res->used;
+	stream->key_data = buffer_free_without_data(&res);
+
+	return 0;
+}
+
+static
+int o_stream_encrypt_key_for_pubkey_v2(struct encrypt_ostream *stream, const char *malg,
+	const unsigned char *key, size_t key_len, struct dcrypt_public_key *pubkey, buffer_t *res)
+{
+	enum dcrypt_key_type ktype;
+	const char *error;
+	buffer_t *encrypted_key, *ephemeral_key, *temp_key;
+
+	ephemeral_key = buffer_create_dynamic(pool_datastack_create(), 256);
+	encrypted_key = buffer_create_dynamic(pool_datastack_create(), 256);
+	temp_key = buffer_create_dynamic(pool_datastack_create(), 48);
+
+	dcrypt_key_type_public(pubkey, &ktype);
+
+	if (ktype == DCRYPT_KEY_RSA) {
+		/* encrypt key as R (as we don't need DH with RSA)*/
+		if (!dcrypt_rsa_encrypt(pubkey, key, key_len, encrypted_key, &error)) {
+			io_stream_set_error(&stream->ostream.iostream, "Cannot encrypt key data: %s", error);
+			return -1;
+		}
+	} else if (ktype == DCRYPT_KEY_EC) {
+		/* R = our ephemeral public key */
+		buffer_t *secret = buffer_create_dynamic(pool_datastack_create(), 256);
+
+		/* derive ephemeral key and shared secret */
+		if (!dcrypt_ecdh_derive_secret_peer(pubkey, ephemeral_key, secret, &error)) {
+			io_stream_set_error(&stream->ostream.iostream, "Cannot perform ECDH: %s", error);
+			return -1;
+		}
+
+		/* use shared secret and ephemeral key to generate encryption key/iv */
+		if (!dcrypt_pbkdf2(secret->data, secret->used, ephemeral_key->data, ephemeral_key->used,
+		    malg, IO_STREAM_ENCRYPT_ROUNDS, temp_key, 48, &error)) {
+			safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
+			io_stream_set_error(&stream->ostream.iostream, "Cannot perform key encryption: %s", error);
+		}
+		safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
+
+		/* encrypt key with shared secret */
+		struct dcrypt_context_symmetric *dctx;
+		if (!dcrypt_ctx_sym_create("AES-256-CBC", DCRYPT_MODE_ENCRYPT, &dctx, &error)) {
+			safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used);
+			io_stream_set_error(&stream->ostream.iostream, "Cannot perform key encryption: %s", error);
+			return -1;
+		}
+
+		const unsigned char *ptr = temp_key->data;
+		i_assert(temp_key->used == 48);
+
+		dcrypt_ctx_sym_set_key(dctx, ptr, 32);
+		dcrypt_ctx_sym_set_iv(dctx, ptr+32, 16);
+		safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used);
+
+		int ec = 0;
+		if (!dcrypt_ctx_sym_init(dctx, &error) ||
+		    !dcrypt_ctx_sym_update(dctx, key, key_len, encrypted_key, &error) ||
+		    !dcrypt_ctx_sym_final(dctx, encrypted_key, &error)) {
+			io_stream_set_error(&stream->ostream.iostream, "Cannot perform key encryption: %s", error);
+			ec = -1;
+		}
+
+		dcrypt_ctx_sym_destroy(&dctx);
+		if (ec != 0) return ec;
+	} else {
+		io_stream_set_error(&stream->ostream.iostream, "Unsupported key type");
+		return -1;
+	}
+
+	/* store key type */
+	char kt = ktype;
+	buffer_append(res, &kt, 1);
+	/* store hash of public key as ID */
+	dcrypt_key_id_public(stream->pub, "sha256", res, NULL);
+	/* store ephemeral key (if present) */
+	unsigned int val = htonl(ephemeral_key->used);
+	buffer_append(res, &val, 4);
+	buffer_append_buf(res, ephemeral_key, 0, (size_t)-1);
+	/* store encrypted key */
+	val = htonl(encrypted_key->used);
+	buffer_append(res, &val, 4);
+	buffer_append_buf(res, encrypted_key, 0, (size_t)-1);
+
+	return 0;
+}
+
+static
+int o_stream_encrypt_keydata_create_v2(struct encrypt_ostream *stream, const char *malg)
+{
+	const struct hash_method *hash = hash_method_lookup(malg);
+	const char *error;
+	size_t tagsize;
+	const unsigned char *ptr;
+	size_t kl;
+	unsigned int val;
+
+	buffer_t *keydata, *res;
+
+	if (hash == NULL) {
+		io_stream_set_error(&stream->ostream.iostream,
+			"Encryption init error: Hash algorithm '%s' not supported", malg);
+		return -1;
+	}
+
+	/* key data length for internal use */
+	if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+		tagsize = IOSTREAM_TAG_SIZE; 
+	} else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) {
+		tagsize = IOSTREAM_TAG_SIZE;
+	} else {
+		/* do not include MAC */
+		tagsize = 0;
+	}
+
+	/* generate keydata length of random data for key/iv/mac */
+	kl = dcrypt_ctx_sym_get_key_length(stream->ctx_sym) + dcrypt_ctx_sym_get_iv_length(stream->ctx_sym) + tagsize;
+	keydata = buffer_create_dynamic(pool_datastack_create(), kl);
+	random_fill(buffer_append_space_unsafe(keydata, kl), kl);
+	buffer_set_used_size(keydata, kl);
+	ptr = keydata->data;
+
+	res = buffer_create_dynamic(default_pool, 256);
+
+	/* store number of public key(s) */
+	buffer_append(res, "\1", 1); /* one key for now */
+
+	/* we can do multiple keys at this point, but do it only once now */
+	if (o_stream_encrypt_key_for_pubkey_v2(stream, malg, ptr, kl, stream->pub, res) != 0) {
+		buffer_free(&res);
+		return -1;
+	}
+
+	/* create hash of the key data */
+	unsigned char hctx[hash->context_size];
+	unsigned char hres[hash->digest_size];
+	hash->init(hctx);
+	hash->loop(hctx, ptr, kl);
+	hash->result(hctx, hres);
+
+	for(int i = 1; i < 2049; i++) {
+		uint32_t i_msb = htonl(i);
+
+		hash->init(hctx);
+		hash->loop(hctx, hres, sizeof(hres));
+		hash->loop(hctx, &i_msb, sizeof(i_msb));
+		hash->result(hctx, hres);
+	}
+
+	/* store key data hash */
+	val = htonl(sizeof(hres));
+	buffer_append(res, &val, 4);
+	buffer_append(res, hres, sizeof(hres));
+
+	/* pick up key data that goes into stream */
+	stream->key_data_len = res->used;
+	stream->key_data = buffer_free_without_data(&res);
+
+	/* prime contexts */
+	dcrypt_ctx_sym_set_key(stream->ctx_sym, ptr, dcrypt_ctx_sym_get_key_length(stream->ctx_sym));
+	ptr += dcrypt_ctx_sym_get_key_length(stream->ctx_sym);
+	dcrypt_ctx_sym_set_iv(stream->ctx_sym, ptr, dcrypt_ctx_sym_get_iv_length(stream->ctx_sym));
+	ptr += dcrypt_ctx_sym_get_iv_length(stream->ctx_sym);
+
+	if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+		dcrypt_ctx_hmac_set_key(stream->ctx_mac, ptr, tagsize);
+		dcrypt_ctx_hmac_init(stream->ctx_mac, &error);
+	} else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) {
+		dcrypt_ctx_sym_set_aad(stream->ctx_sym, ptr, tagsize);
+	}
+
+	/* clear out private key data */
+	safe_memset(buffer_get_modifiable_data(keydata, 0), 0, keydata->used);
+
+	if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) {
+		io_stream_set_error(&stream->ostream.iostream, "Encryption init error: %s", error);
+		return -1;
+	}
+	return 0;
+}
+
+static
+ssize_t o_stream_encrypt_sendv(struct ostream_private *stream,
+		       const struct const_iovec *iov, unsigned int iov_count)
+{
+	struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+	const char *error;
+	ssize_t ec,total = 0;
+
+	/* not if finalized */
+	i_assert(!estream->finalized);
+
+	/* write prefix */
+	if (!estream->prefix_written) {
+		T_BEGIN {
+			if ((estream->flags & IO_STREAM_ENC_VERSION_1) == IO_STREAM_ENC_VERSION_1)
+				ec = o_stream_encrypt_send_header_v1(estream);
+			else
+				ec = o_stream_encrypt_send_header_v2(estream);
+		} T_END;
+		if (ec < 0) {
+			return -1;
+		}
+	}
+
+	/* buffer for encrypted data */
+	unsigned char ciphertext[IO_BLOCK_SIZE];
+	buffer_t buf;
+	buffer_create_from_data(&buf, ciphertext, sizeof(ciphertext));
+
+	/* encrypt & send all blocks of data at max ciphertext buffer's length */
+	for(unsigned int i = 0; i < iov_count; i++) {
+		size_t bl, off = 0, len = iov[i].iov_len;
+		const unsigned char *ptr = iov[i].iov_base;
+		while(len > 0) {
+			buffer_set_used_size(&buf, 0);
+			/* update can emite twice the size of input */
+			bl = I_MIN(sizeof(ciphertext)/2, len);
+
+			if (!dcrypt_ctx_sym_update(estream->ctx_sym, ptr + off, bl, &buf, &error)) {
+				io_stream_set_error(&stream->iostream, "Encryption failure: %s", error);
+				return -1;
+			}
+			if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+				/* update mac */
+				if (!dcrypt_ctx_hmac_update(estream->ctx_mac, buf.data, buf.used, &error)) {
+					io_stream_set_error(&stream->iostream, "MAC failure: %s", error);
+					return -1;
+				}
+			}
+
+			/* hopefully upstream can accomondate */
+			if (o_stream_encrypt_send(estream, buf.data, buf.used) < 0) {
+				return -1;
+			}
+
+			len -= bl;
+			off += bl;
+			total += bl;
+		}
+	}
+
+	stream->ostream.offset += total;
+	return total;
+}
+
+static
+int o_stream_encrypt_flush(struct ostream_private *stream)
+{
+	const char *error;
+	struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+
+	i_assert(!estream->finalized);
+	estream->finalized = TRUE;
+
+	/* acquire last block */
+	buffer_t *buf = buffer_create_dynamic(pool_datastack_create(), dcrypt_ctx_sym_get_block_size(estream->ctx_sym));
+	if (!dcrypt_ctx_sym_final(estream->ctx_sym, buf, &error)) {
+		io_stream_set_error(&estream->ostream.iostream, "Encryption failure: %s", error);
+		return -1;
+	}
+	/* sometimes final does not emit anything */
+	if (buf->used > 0) {
+		/* update mac */
+		if (((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC)) {
+			if (!dcrypt_ctx_hmac_update(estream->ctx_mac, buf->data, buf->used, &error)) {
+				io_stream_set_error(&estream->ostream.iostream, "MAC failure: %s", error);
+				return -1;
+			}
+		}
+		if (o_stream_encrypt_send(estream, buf->data, buf->used) < 0) {
+			return -1;
+		}
+	}
+
+	/* write last mac bytes */
+	buffer_set_used_size(buf, 0);
+	if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+		if (!dcrypt_ctx_hmac_final(estream->ctx_mac, buf, &error)) {
+			io_stream_set_error(&estream->ostream.iostream, "MAC failure: %s", error);
+			return -1;
+		}
+	} else if ((estream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) {
+		dcrypt_ctx_sym_get_tag(estream->ctx_sym, buf);
+		i_assert(buf->used > 0);
+	}
+	if (buf->used > 0 && o_stream_encrypt_send(estream, buf->data, buf->used) < 0) {
+		return -1;
+	}
+
+	/* flush parent */
+	return o_stream_flush(stream->parent);
+}
+
+static
+void o_stream_encrypt_close(struct iostream_private *stream,
+			    bool close_parent)
+{
+	struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+	if (estream->ctx_sym != NULL && !estream->finalized &&
+	    estream->ostream.ostream.stream_errno == 0)
+		o_stream_encrypt_flush(&estream->ostream);
+	if (close_parent) {
+		o_stream_close(estream->ostream.parent);
+	}
+}
+
+static
+void o_stream_encrypt_destroy(struct iostream_private *stream)
+{
+	struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+	/* release resources */
+	if (estream->ctx_sym != NULL) dcrypt_ctx_sym_destroy(&(estream->ctx_sym));
+	if (estream->ctx_mac != NULL) dcrypt_ctx_hmac_destroy(&(estream->ctx_mac));
+	if (estream->key_data != NULL) i_free(estream->key_data);
+	if (estream->cipher_oid != NULL) buffer_free(&(estream->cipher_oid));
+	if (estream->mac_oid != NULL) buffer_free(&(estream->mac_oid));
+
+	o_stream_unref(&estream->ostream.parent);
+}
+
+static
+int o_stream_encrypt_init(struct encrypt_ostream *estream, const char *algorithm)
+{
+	const char *error;
+	char *calg, *malg;
+
+	if ((estream->flags & IO_STREAM_ENC_VERSION_1) == IO_STREAM_ENC_VERSION_1) {
+		if (!dcrypt_ctx_sym_create("AES-256-CTR", DCRYPT_MODE_ENCRYPT, &(estream->ctx_sym), &error)) {
+			io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error);
+			return -1;
+		}
+		estream->flags |= IO_STREAM_ENC_INTEGRITY_NONE; /* disable MAC */
+		/* then do keying */
+		return o_stream_encrypt_keydata_create_v1(estream);
+	} else {
+		calg = t_strdup_noconst(algorithm);
+		malg = strrchr(calg, '-');
+
+		if (malg == NULL) {
+			io_stream_set_error(&estream->ostream.iostream, "Invalid algorithm (must be cipher-mac)");
+			return -1;
+		}
+		(*malg++) = '\0';
+
+		if (!dcrypt_ctx_sym_create(calg, DCRYPT_MODE_ENCRYPT, &(estream->ctx_sym), &error)) {
+			io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error);
+			return -1;
+		}
+
+		/* create cipher and mac context, take note of OIDs */
+		estream->cipher_oid = buffer_create_dynamic(default_pool, 12);
+		estream->block_size = dcrypt_ctx_sym_get_block_size(estream->ctx_sym);
+		if (!dcrypt_name2oid(calg, estream->cipher_oid, &error)) {
+			io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error);
+			return -1;
+		}
+
+		/* mac context is optional */
+		if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) {
+			if (!dcrypt_ctx_hmac_create(malg, &(estream->ctx_mac), &error)) {
+				io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error);
+				return -1;
+			}
+		}
+
+		estream->mac_oid = buffer_create_dynamic(default_pool, 12);
+		if (!dcrypt_name2oid(malg, estream->mac_oid, &error)) {
+			io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error);
+			return -1;
+		}
+
+		/* MAC algoritm is used for PBKDF2 and keydata hashing */
+		return o_stream_encrypt_keydata_create_v2(estream, malg);
+	}
+}
+
+static
+struct encrypt_ostream *
+o_stream_create_encrypt_common(enum io_stream_encrypt_flags flags)
+{
+	struct encrypt_ostream *estream;
+
+	estream = i_new(struct encrypt_ostream, 1);
+	estream->ostream.sendv = o_stream_encrypt_sendv;
+	estream->ostream.flush = o_stream_encrypt_flush;
+	estream->ostream.iostream.close = o_stream_encrypt_close;
+	estream->ostream.iostream.destroy = o_stream_encrypt_destroy;
+
+	estream->flags = flags;
+
+	return estream;
+}
+
+struct ostream *
+o_stream_create_encrypt(struct ostream *output, const char *algorithm,
+	struct dcrypt_public_key *box_pub, enum io_stream_encrypt_flags flags)
+{
+	struct encrypt_ostream *estream = o_stream_create_encrypt_common(flags);
+	int ec;
+
+	estream->pub = box_pub;
+
+	T_BEGIN {
+		ec = o_stream_encrypt_init(estream, algorithm);
+	} T_END;
+
+	struct ostream *os = o_stream_create(&estream->ostream, output,
+			       o_stream_get_fd(output));
+
+	if (ec != 0) {
+		os->stream_errno = EINVAL;
+	}
+
+	return os;
+}
+
+struct ostream *
+o_stream_create_sym_encrypt(struct ostream *output, struct dcrypt_context_symmetric *ctx)
+{
+	struct encrypt_ostream *estream = o_stream_create_encrypt_common(IO_STREAM_ENC_INTEGRITY_NONE);
+	const char *error;
+	int ec;
+
+	estream->prefix_written = TRUE;
+
+	if (!dcrypt_ctx_sym_init(estream->ctx_sym, &error)) ec = -1;
+	else ec = 0;
+
+	estream->ctx_sym = ctx;
+
+	struct ostream *os = o_stream_create(&estream->ostream, output,
+		 o_stream_get_fd(output));
+	if (ec != 0) {
+		io_stream_set_error(&estream->ostream.iostream, "Could not initialize stream: %s", error);
+		os->stream_errno = EINVAL;
+	}
+
+	return os;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/ostream-encrypt.h	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,38 @@
+#ifndef OSTREAM_ENCRYPT_H
+#define OSTREAM_ENCRYPT_H
+
+struct dcrypt_public_key;
+struct dcrypt_context_symmetric;
+
+/**
+ * algorithm is in form AES-256-CBC-SHA1, recommended
+ * AES-256-GCM-SHA256
+ *
+ * Algorithms (both crypto and digest) *MUST* have OID to use it.
+ *
+ */
+
+enum io_stream_encrypt_flags {
+	IO_STREAM_ENC_INTEGRITY_HMAC = 0x1,
+	IO_STREAM_ENC_INTEGRITY_AEAD = 0x2,
+	IO_STREAM_ENC_INTEGRITY_NONE = 0x4,
+	IO_STREAM_ENC_VERSION_1      = 0x8,
+};
+
+struct ostream *
+o_stream_create_encrypt(struct ostream *output,
+	const char *algorithm,
+	struct dcrypt_public_key *box_pub,
+	enum io_stream_encrypt_flags flags);
+
+/* create context for performing encryption with
+   preset crypto context. do not call ctx_sym_init.
+
+   no header or mac is written, just plain crypto
+   data.
+ */
+struct ostream *
+o_stream_create_sym_encrypt(struct ostream *output,
+	struct dcrypt_context_symmetric *ctx);
+
+#endif
Binary file src/lib-dcrypt/sample-v1.bin has changed
Binary file src/lib-dcrypt/sample-v2.bin has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/test-crypto.c	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,305 @@
+#include "lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "dcrypt.h"
+#include "ostream.h"
+#include "ostream-encrypt.h"
+#include "istream.h"
+#include "istream-decrypt.h"
+#include "iostream-temp.h"
+#include "randgen.h"
+#include "test-common.h"
+#include "hex-binary.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdio.h>
+
+
+static
+void test_cipher_test_vectors(void)
+{
+	static struct {
+		const char *key;
+		const char *iv;
+		const char *pt;
+		const char *ct;
+	} vectors[] =
+	{
+		{ "2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090a0b0c0d0e0f", "6bc1bee22e409f96e93d7e117393172a", "7649abac8119b246cee98e9b12e9197d" },
+		{ "2b7e151628aed2a6abf7158809cf4f3c", "7649ABAC8119B246CEE98E9B12E9197D", "ae2d8a571e03ac9c9eb76fac45af8e51", "5086cb9b507219ee95db113a917678b2" }
+	};
+
+
+	test_begin("test_cipher_test_vectors");
+
+	buffer_t *key,*iv,*pt,*ct,*res_enc,*res_dec;
+
+	key = buffer_create_dynamic(pool_datastack_create(), 16);
+	iv = buffer_create_dynamic(pool_datastack_create(), 16);
+	pt = buffer_create_dynamic(pool_datastack_create(), 16);
+	ct = buffer_create_dynamic(pool_datastack_create(), 16);
+
+	res_enc = buffer_create_dynamic(pool_datastack_create(), 32);
+	res_dec = buffer_create_dynamic(pool_datastack_create(), 32);
+
+	for(size_t i = 0; i < N_ELEMENTS(vectors); i++) {
+		struct dcrypt_context_symmetric *ctx;
+
+		buffer_set_used_size(key, 0);
+		buffer_set_used_size(iv, 0);
+		buffer_set_used_size(pt, 0);
+		buffer_set_used_size(ct, 0);
+		buffer_set_used_size(res_enc, 0);
+		buffer_set_used_size(res_dec, 0);
+
+		hex_to_binary(vectors[i].key, key);
+		hex_to_binary(vectors[i].iv, iv);
+		hex_to_binary(vectors[i].pt, pt);
+		hex_to_binary(vectors[i].ct, ct);
+
+		if (!dcrypt_ctx_sym_create("AES-128-CBC", DCRYPT_MODE_ENCRYPT, &ctx, NULL)) {
+			test_assert_failed("dcrypt_ctx_sym_create", __FILE__, __LINE__-1);
+			continue;
+		}
+
+		dcrypt_ctx_sym_set_padding(ctx, FALSE);
+
+		dcrypt_ctx_sym_set_key(ctx, key->data, key->used);
+		dcrypt_ctx_sym_set_iv(ctx, iv->data, iv->used);
+
+		test_assert_idx(dcrypt_ctx_sym_init(ctx, NULL), i);
+
+		test_assert_idx(dcrypt_ctx_sym_update(ctx, pt->data, pt->used, res_enc, NULL), i);
+		test_assert_idx(dcrypt_ctx_sym_final(ctx, res_enc, NULL), i);
+
+		test_assert_idx(buffer_cmp(ct, res_enc), i);
+
+		dcrypt_ctx_sym_destroy(&ctx);
+
+		if (!dcrypt_ctx_sym_create("AES-128-CBC", DCRYPT_MODE_DECRYPT, &ctx, NULL)) {
+			test_assert_failed("dcrypt_ctx_sym_create", __FILE__, __LINE__-1);
+			continue;
+		}
+
+		dcrypt_ctx_sym_set_padding(ctx, FALSE);
+
+		dcrypt_ctx_sym_set_key(ctx, key->data, key->used);
+		dcrypt_ctx_sym_set_iv(ctx, iv->data, iv->used);
+
+		test_assert_idx(dcrypt_ctx_sym_init(ctx, NULL), i);
+		test_assert_idx(dcrypt_ctx_sym_update(ctx, res_enc->data, res_enc->used, res_dec, NULL), i);
+		test_assert_idx(dcrypt_ctx_sym_final(ctx, res_dec, NULL), i);
+
+		test_assert_idx(buffer_cmp(pt, res_dec), i);
+
+		dcrypt_ctx_sym_destroy(&ctx);
+	}
+
+	test_end();
+}
+
+static
+void test_cipher_aead_test_vectors(void)
+{
+	struct dcrypt_context_symmetric *ctx;
+	const char *error = NULL;
+
+	test_begin("test_cipher_aead_test_vectors");
+
+	if (!dcrypt_ctx_sym_create("aes-128-gcm", DCRYPT_MODE_ENCRYPT, &ctx, &error)) {
+		test_assert_failed("dcrypt_ctx_sym_create", __FILE__, __LINE__-1);
+		return;
+	}
+
+	buffer_t *key, *iv, *aad, *pt, *ct, *tag, *tag_res, *res;
+
+	key = buffer_create_dynamic(pool_datastack_create(), 16);
+	iv = buffer_create_dynamic(pool_datastack_create(), 16);
+	aad = buffer_create_dynamic(pool_datastack_create(), 16);
+	pt = buffer_create_dynamic(pool_datastack_create(), 16);
+	ct = buffer_create_dynamic(pool_datastack_create(), 16);
+	tag = buffer_create_dynamic(pool_datastack_create(), 16);
+	res = buffer_create_dynamic(pool_datastack_create(), 16);
+	tag_res = buffer_create_dynamic(pool_datastack_create(), 16);
+
+	hex_to_binary("feffe9928665731c6d6a8f9467308308", key);
+	hex_to_binary("cafebabefacedbaddecaf888", iv);
+	hex_to_binary("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", pt);
+	hex_to_binary("42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985", ct);
+	hex_to_binary("4d5c2af327cd64a62cf35abd2ba6fab4", tag);
+
+	dcrypt_ctx_sym_set_key(ctx, key->data, key->used);
+	dcrypt_ctx_sym_set_iv(ctx, iv->data, iv->used);
+	dcrypt_ctx_sym_set_aad(ctx, aad->data, aad->used);
+	test_assert(dcrypt_ctx_sym_init(ctx, &error));
+	test_assert(dcrypt_ctx_sym_update(ctx, pt->data, pt->used, res, &error));
+	test_assert(dcrypt_ctx_sym_final(ctx, res, &error));
+	dcrypt_ctx_sym_get_tag(ctx, tag_res);
+
+	test_assert(buffer_cmp(ct, res) == TRUE);
+	test_assert(buffer_cmp(tag, tag_res) == TRUE);
+
+	dcrypt_ctx_sym_destroy(&ctx);
+
+	if (!dcrypt_ctx_sym_create("aes-128-gcm", DCRYPT_MODE_DECRYPT, &ctx, &error)) {
+		test_assert_failed("dcrypt_ctx_sym_create", __FILE__, __LINE__-1);
+	} else {
+
+		buffer_set_used_size(res, 0);
+
+		dcrypt_ctx_sym_set_key(ctx, key->data, key->used);
+		dcrypt_ctx_sym_set_iv(ctx, iv->data, iv->used);
+		dcrypt_ctx_sym_set_aad(ctx, aad->data, aad->used);
+		dcrypt_ctx_sym_set_tag(ctx, tag->data, tag->used);
+		test_assert(dcrypt_ctx_sym_init(ctx, &error));
+		test_assert(dcrypt_ctx_sym_update(ctx, ct->data, ct->used, res, &error));
+		test_assert(dcrypt_ctx_sym_final(ctx, res, &error));
+
+		test_assert(buffer_cmp(pt, res) == TRUE);
+
+		dcrypt_ctx_sym_destroy(&ctx);
+	}
+
+	test_end();
+}
+
+static
+void test_hmac_test_vectors(void)
+{
+	buffer_t *pt, *ct, *key, *res;
+	pt = buffer_create_dynamic(pool_datastack_create(), 50);
+	key = buffer_create_dynamic(pool_datastack_create(), 20);
+	ct = buffer_create_dynamic(pool_datastack_create(), 32);
+	res = buffer_create_dynamic(pool_datastack_create(), 32);
+
+	hex_to_binary("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", key);
+	hex_to_binary("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", pt);
+	hex_to_binary("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", res);
+
+	struct dcrypt_context_hmac *hctx;
+	if (!dcrypt_ctx_hmac_create("sha256", &hctx, NULL)) {
+		test_assert_failed("dcrypt_ctx_hmac_create", __FILE__, __LINE__-1);
+	} else {
+		dcrypt_ctx_hmac_set_key(hctx, key->data, key->used);
+		test_assert(dcrypt_ctx_hmac_init(hctx, NULL));
+		test_assert(dcrypt_ctx_hmac_update(hctx, pt->data, pt->used, NULL));
+		test_assert(dcrypt_ctx_hmac_final(hctx, ct, NULL));
+		test_assert(buffer_cmp(ct, res));
+		dcrypt_ctx_hmac_destroy(&hctx);
+	}
+}
+
+static
+void test_load_v1_key(void)
+{
+	test_begin("test_load_v1_key");
+
+	buffer_t *key_1 = buffer_create_dynamic(pool_datastack_create(), 128);
+
+	struct dcrypt_private_key *pkey, *pkey2;
+	const char *error = NULL;
+
+	test_assert(dcrypt_key_load_private(&pkey, DCRYPT_FORMAT_DOVECOT, "1\t716\t0\t048FD04FD3612B22D32790C592CF21CEF417EFD2EA34AE5F688FA5B51BED29E05A308B68DA78E16E90B47A11E133BD9A208A2894FD01B0BEE865CE339EA3FB17AC\td0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0", NULL, NULL, &error));
+	if (pkey != NULL) {
+		buffer_set_used_size(key_1, 0);
+		/* check that key_id matches */
+		struct dcrypt_public_key *pubkey = NULL;
+		dcrypt_key_convert_private_to_public(pkey, &pubkey, &error);
+		dcrypt_key_store_public(pubkey, DCRYPT_FORMAT_DOVECOT, key_1, NULL);
+		buffer_set_used_size(key_1, 0);
+		dcrypt_key_id_public(pubkey, "sha256", key_1, &error);
+		test_assert(strcmp("792caad4d38c9eb2134a0cbc844eae386116de096a0ccafc98479825fc99b6a1", binary_to_hex(key_1->data, key_1->used)) == 0);
+
+		dcrypt_key_free_public(&pubkey);
+		pkey2 = NULL;
+
+		test_assert(dcrypt_key_load_private(&pkey2, DCRYPT_FORMAT_DOVECOT, "1\t716\t1\t0567e6bf9579813ae967314423b0fceb14bda24749303923de9a9bb9370e0026f995901a57e63113eeb2baf0c940e978d00686cbb52bd5014bc318563375876255\t0300E46DA2125427BE968EB3B649910CDC4C405E5FFDE18D433A97CABFEE28CEEFAE9EE356C792004FFB80981D67E741B8CC036A34235A8D2E1F98D1658CFC963D07EB\td0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0\t7c9a1039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00fa4f", NULL, pkey, &error));
+		if (pkey2 != NULL) {
+			buffer_set_used_size(key_1, 0);
+			/* check that key_id matches */
+			struct dcrypt_public_key *pubkey = NULL;
+			dcrypt_key_convert_private_to_public(pkey2, &pubkey, &error);
+			dcrypt_key_store_public(pubkey, DCRYPT_FORMAT_DOVECOT, key_1, NULL);
+			buffer_set_used_size(key_1, 0);
+			dcrypt_key_id_public_old(pubkey, key_1, &error);
+			test_assert(strcmp("7c9a1039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00fa4f", binary_to_hex(key_1->data, key_1->used)) == 0);
+
+			dcrypt_key_free_public(&pubkey);
+			dcrypt_key_free_private(&pkey2);
+		}
+		dcrypt_key_free_private(&pkey);
+	}
+
+	test_end();
+}
+
+static
+void test_load_v2_key(void)
+{
+	const char *keys[] = {
+		"-----BEGIN PRIVATE KEY-----\n" \
+"MGcCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcETTBLAgEBBCC25AkD65uhlZXCAdwN\n" \
+"yLJV2ui8A/CUyqyEMrezvwgMO6EkAyIAAybRUR3MsH0+0PQcDwkrXOJ9aePwzTQV\n" \
+"DN51+n1JCxbI\n" \
+"-----END PRIVATE KEY-----\n",
+		"2\t1.2.840.10045.3.1.7\t0\t0000002100b6e40903eb9ba19595c201dc0dc8b255dae8bc03f094caac8432b7b3bf080c3b\tab13d251976dedab546b67354e7678821740dd534b749c2857f66bf62bbaddfd",
+		"2\t1.2.840.10045.3.1.7\t2\taes-256-ctr\t2b19763d4bbf7754\tsha256\t2048\tc36fa194669a1aec400eae32fbadaa7c58b14f53c464cfbb0a4b61fbe24ab7750637c4025d\tab13d251976dedab546b67354e7678821740dd534b749c2857f66bf62bbaddfd",
+		"2\t1.2.840.10045.3.1.7\t1\taes-256-ctr\t7c7f1d12a7c011de\tsha256\t2048\tf5d1de11d58a81b141cf038012a618623e9d7b18062deeb3a4e35872c62ca0837db8688370\t021abfbc5bc4f6cf49c40b9fc388c4616ea079941675f477ee4557df1919626d35\tab13d251976dedab546b67354e7678821740dd534b749c2857f66bf62bbaddfd\tab13d251976dedab546b67354e7678821740dd534b749c2857f66bf62bbaddfd"
+	};
+
+	test_begin("test_load_v2_key");
+	const char *error = NULL;
+	buffer_t *tmp = buffer_create_dynamic(default_pool, 256);
+
+	struct dcrypt_private_key *priv,*priv2;
+
+	test_assert_idx(dcrypt_key_load_private(&priv2, DCRYPT_FORMAT_PEM, keys[0], NULL, NULL, &error), 0);
+	test_assert_idx(dcrypt_key_store_private(priv2, DCRYPT_FORMAT_PEM, NULL, tmp, NULL, NULL, &error), 0);
+	test_assert_idx(strcmp(str_c(tmp), keys[0])==0, 0);
+	buffer_set_used_size(tmp, 0);
+
+	test_assert_idx(dcrypt_key_load_private(&priv, DCRYPT_FORMAT_DOVECOT, keys[1], NULL, NULL, &error), 1);
+	test_assert_idx(dcrypt_key_store_private(priv, DCRYPT_FORMAT_DOVECOT, NULL, tmp, NULL, NULL, &error), 1);
+	test_assert_idx(strcmp(str_c(tmp), keys[1])==0, 1);
+	buffer_set_used_size(tmp, 0);
+	dcrypt_key_free_private(&priv);
+
+	test_assert_idx(dcrypt_key_load_private(&priv, DCRYPT_FORMAT_DOVECOT, keys[2], "This Is Sparta", NULL, &error), 2);
+	test_assert_idx(dcrypt_key_store_private(priv, DCRYPT_FORMAT_DOVECOT, "aes-256-ctr", tmp, "This Is Sparta", NULL, &error), 2);
+	buffer_set_used_size(tmp, 0);
+	dcrypt_key_free_private(&priv);
+
+	struct dcrypt_public_key *pub = NULL;
+	test_assert_idx(dcrypt_key_convert_private_to_public(priv2, &pub, &error), 3);
+	test_assert_idx(dcrypt_key_load_private(&priv, DCRYPT_FORMAT_DOVECOT, keys[3], NULL, priv2, &error), 3);
+	test_assert_idx(dcrypt_key_store_private(priv, DCRYPT_FORMAT_DOVECOT, "ecdh-aes-256-ctr", tmp, NULL, pub, &error), 3);
+	buffer_set_used_size(tmp, 0);
+	dcrypt_key_free_private(&priv2);
+	dcrypt_key_free_private(&priv);
+	dcrypt_key_free_public(&pub);
+
+	buffer_free(&tmp);
+
+	if (error != NULL) error = NULL;
+
+	test_end();
+}
+
+int main(void) {
+	dcrypt_initialize("openssl", NULL);
+	random_init();
+	static void (*test_functions[])(void) = {
+		test_cipher_test_vectors,
+		test_cipher_aead_test_vectors,
+		test_hmac_test_vectors,
+		test_load_v1_key,
+		test_load_v2_key,
+		NULL
+	};
+
+	int ret;
+
+	ret = test_run(test_functions);
+
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dcrypt/test-stream.c	Wed Apr 27 14:08:00 2016 +0300
@@ -0,0 +1,232 @@
+#include "lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "dcrypt.h"
+#include "ostream.h"
+#include "ostream-encrypt.h"
+#include "istream.h"
+#include "istream-decrypt.h"
+#include "istream-hash.h"
+#include "iostream-temp.h"
+#include "randgen.h"
+#include "hash-method.h"
+#include "test-common.h"
+#include "hex-binary.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdio.h>
+
+static const char key_v1_priv[] = "-----BEGIN PRIVATE KEY-----\n" \
+"MIGpAgEAMBAGByqGSM49AgEGBSuBBAAjBIGRMIGOAgEBBEGz2V2VMi/5s+Z+GJh7\n" \
+"4WfqZjZUpqqm+NJWojm6BbrZMY+9ZComlTGVcUZ007acFxV93oMmrfmtRUb5ynrb\n" \
+"MRFskKFGA0QAAwHrAJc8TvyPzspOoz6UH1C1YRmaUVm8tsLu2d0dYtZeOKJUl52J\n" \
+"4o8MKIg+ce4q0mTNFrhj+glKj29ppWti6JGAQA==\n" \
+"-----END PRIVATE KEY-----";
+
+static const char key_v1_pub[] = "-----BEGIN PUBLIC KEY-----\n" \
+"MFgwEAYHKoZIzj0CAQYFK4EEACMDRAADAesAlzxO/I/Oyk6jPpQfULVhGZpRWby2\n" \
+"wu7Z3R1i1l44olSXnYnijwwoiD5x7irSZM0WuGP6CUqPb2mla2LokYBA\n" \
+"-----END PUBLIC KEY-----";
+
+static const char key_v2_priv[] = "-----BEGIN PRIVATE KEY-----\n" \
+"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgtuQJA+uboZWVwgHc\n" \
+"DciyVdrovAPwlMqshDK3s78IDDuhRANCAAQm0VEdzLB9PtD0HA8JK1zifWnj8M00\n" \
+"FQzedfp9SQsWyA8dzs5/NFR5MTe6Xbh/ndKEs1zZH3vZ4FlNrilZc0st\n" \
+"-----END PRIVATE KEY-----";
+
+static const char key_v2_pub[] = "-----BEGIN PUBLIC KEY-----\n" \
+"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJtFRHcywfT7Q9BwPCStc4n1p4/DN\n" \
+"NBUM3nX6fUkLFsgPHc7OfzRUeTE3ul24f53ShLNc2R972eBZTa4pWXNLLQ==\n" \
+"-----END PUBLIC KEY-----";
+
+static const char test_sample_v1_hash[] = "1d7cc2cc1f1983f76241cc42389911e88590ad58cf9d54cafeb5b198d3723dd1";
+static const char test_sample_v2_hash[] = "2e31218656dd34db65b321688bf418dee4ee785e99eb9c21e0d29b4af27a863e";
+
+static struct dcrypt_keypair test_v1_kp;
+static struct dcrypt_keypair test_v2_kp;
+
+static
+void test_static_v1_input(void)
+{
+	ssize_t siz;
+	const struct hash_method *hash = hash_method_lookup("sha256");
+	unsigned char hash_ctx[hash->context_size];
+	unsigned char hash_dgst[hash->digest_size];
+	hash->init(hash_ctx);
+
+	test_begin("test_static_v1_input");
+
+	struct istream *is_1 = i_stream_create_file("sample-v1.bin", IO_BLOCK_SIZE);
+	struct istream *is_2 = i_stream_create_decrypt(is_1, test_v1_kp.priv);
+	i_stream_unref(&is_1);
+	struct istream *is_3 = i_stream_create_hash(is_2, hash, hash_ctx);
+	i_stream_unref(&is_2);
+
+	while((siz = i_stream_read(is_3))>0) { i_stream_skip(is_3, siz); }
+
+	if (is_3->stream_errno != 0)
+		i_debug("error: %s", i_stream_get_error(is_3));
+
+	test_assert(is_3->stream_errno == 0);
+
+	i_stream_unref(&is_3);
+
+	hash->result(hash_ctx, hash_dgst);
+
+	test_assert(strcmp(test_sample_v1_hash, binary_to_hex(hash_dgst, sizeof(hash_dgst))) == 0);
+
+	test_end();
+}
+
+static
+void test_static_v2_input(void)
+{
+	test_begin("test_static_v2_input");
+	ssize_t amt;
+	const struct hash_method *hash = hash_method_lookup("sha256");
+	unsigned char hash_ctx[hash->context_size];
+	unsigned char hash_dgst[hash->digest_size];
+	hash->init(hash_ctx);
+
+	struct istream *is_1 = i_stream_create_file("sample-v2.bin", IO_BLOCK_SIZE);
+	struct istream *is_2 = i_stream_create_decrypt(is_1, test_v2_kp.priv);
+	i_stream_unref(&is_1);
+	struct istream *is_3 = i_stream_create_hash(is_2, hash, hash_ctx);
+	i_stream_unref(&is_2);
+
+	while((amt = i_stream_read(is_3))>0) { i_stream_skip(is_3, amt); }
+
+        if (is_3->stream_errno != 0)
+                i_debug("error: %s", i_stream_get_error(is_3));
+
+	test_assert(is_3->stream_errno == 0);
+
+	i_stream_unref(&is_3);
+
+	hash->result(hash_ctx, hash_dgst);
+
+	test_assert(strcmp(test_sample_v2_hash, binary_to_hex(hash_dgst, sizeof(hash_dgst))) == 0);
+
+	test_end();
+
+/** this code is left here to show how the sample file is created
+	struct istream *is = i_stream_create_file("../lib-fts/udhr_fra.txt", 8192);
+	struct istream *is_2 = i_stream_create_hash(is, hash, hash_ctx);
+	int fd = open("sample-v2.bin", O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU);
+	struct ostream *os = o_stream_create_fd_file(fd, 0, TRUE);
+	struct ostream *os_2 = o_stream_create_encrypt(os, "aes-256-gcm-sha256", test_v2_kp.pub, IO_STREAM_ENC_INTEGRITY_AEAD);
+	const unsigned char *ptr;
+	size_t siz;
+
+	while(i_stream_read_data(is_2, &ptr, &siz, 0)>0) {
+		o_stream_nsend(os_2, ptr, siz);
+		i_stream_skip(is_2, siz);
+	}
+
+	i_assert(o_stream_nfinish(os_2)==0);
+
+	o_stream_close(os_2);
+	i_stream_close(is_2);
+
+	hash->result(hash_ctx, hash_dgst);
+	printf("%s\n", binary_to_hex(hash_dgst, sizeof(hash_dgst)));
+*/
+}
+
+static
+void test_write_read_v1(void)
+{
+	test_begin("test_write_read_v1");
+	unsigned char payload[IO_BLOCK_SIZE];
+	const unsigned char *ptr;
+	size_t pos = 0, siz;
+	random_fill_weak(payload, IO_BLOCK_SIZE);
+
+	struct ostream *os = iostream_temp_create("/tmp", 0);
+	struct ostream *os_2 = o_stream_create_encrypt(os, "<unused>", test_v2_kp.pub, IO_STREAM_ENC_VERSION_1);
+	o_stream_nsend(os_2, payload, sizeof(payload));
+
+        if (os_2->stream_errno != 0)
+                i_debug("error: %s", o_stream_get_error(os_2));
+
+	test_assert(os_2->stream_errno == 0);
+	test_assert(o_stream_nfinish(os_2) == 0);
+	test_assert(os_2->stream_errno == 0);
+
+	o_stream_unref(&os_2);
+
+	struct istream *is = iostream_temp_finish(&os, IO_BLOCK_SIZE);
+	struct istream *is_2 = i_stream_create_decrypt(is, test_v2_kp.priv);
+	i_stream_unref(&is);
+
+	while(i_stream_read_data(is_2, &ptr, &siz, 0)>0) {
+		test_assert_idx(pos + siz <= sizeof(payload), pos);
+		if (pos + siz > sizeof(payload)) break;
+		test_assert_idx(memcmp(ptr, payload + pos, siz) == 0, pos);
+		i_stream_skip(is_2, siz);
+	}
+
+	test_assert(is_2->stream_errno == 0);
+
+	i_stream_unref(&is_2);
+
+	test_end();
+}
+
+static
+void test_write_read_v2(void)
+{
+	test_begin("test_write_read_v2");
+	unsigned char payload[IO_BLOCK_SIZE];
+	const unsigned char *ptr;
+	size_t pos = 0, siz;
+	random_fill_weak(payload, IO_BLOCK_SIZE);
+
+	struct ostream *os = iostream_temp_create("/tmp", 0);
+	struct ostream *os_2 = o_stream_create_encrypt(os, "aes-256-gcm-sha256", test_v1_kp.pub, IO_STREAM_ENC_INTEGRITY_AEAD);
+	o_stream_nsend(os_2, payload, IO_BLOCK_SIZE);
+	test_assert(o_stream_nfinish(os_2) == 0);
+        if (os_2->stream_errno != 0)
+                i_debug("error: %s", o_stream_get_error(os_2));
+
+	o_stream_unref(&os_2);
+
+	struct istream *is = iostream_temp_finish(&os, IO_BLOCK_SIZE);
+	struct istream *is_2 = i_stream_create_decrypt(is, test_v1_kp.priv);
+	i_stream_unref(&is);
+
+	while(i_stream_read_data(is_2, &ptr, &siz, 0)>0) {
+		test_assert_idx(pos + siz <= sizeof(payload), pos);
+		if (pos + siz > sizeof(payload)) break;
+		test_assert_idx(memcmp(ptr, payload + pos, siz) == 0, pos);
+		i_stream_skip(is_2, siz);
+	}
+
+	test_assert(is_2->stream_errno == 0);
+        if (is_2->stream_errno != 0)
+                i_debug("error: %s", i_stream_get_error(is_2));
+
+	i_stream_unref(&is_2);
+
+	test_end();
+}
+
+int main(void) {
+	dcrypt_initialize("openssl", NULL);
+	random_init();
+
+	dcrypt_key_load_private(&test_v1_kp.priv, DCRYPT_FORMAT_PEM, key_v1_priv, NULL, NULL, NULL);
+	dcrypt_key_load_public(&test_v1_kp.pub, DCRYPT_FORMAT_PEM, key_v1_pub, NULL);
+	dcrypt_key_load_private(&test_v2_kp.priv, DCRYPT_FORMAT_PEM, key_v2_priv, NULL, NULL, NULL);
+	dcrypt_key_load_public(&test_v2_kp.pub, DCRYPT_FORMAT_PEM, key_v2_pub, NULL);
+
+	static void (*test_functions[])(void) = {
+		test_static_v1_input,
+		test_static_v2_input,
+		test_write_read_v1,
+		test_write_read_v2,
+		NULL
+	};
+
+	return test_run(test_functions);
+}
--- a/src/lib-storage/index/dbox-single/sdbox-file.c	Mon May 30 14:45:09 2016 +0300
+++ b/src/lib-storage/index/dbox-single/sdbox-file.c	Wed Apr 27 14:08:00 2016 +0300
@@ -311,6 +311,9 @@
 	}
 
 	dest_path = !alt_path ? file->primary_path : file->alt_path;
+
+	i_assert(dest_path != NULL);
+
 	p = strrchr(dest_path, '/');
 	i_assert(p != NULL);
 	dest_dir = t_strdup_until(dest_path, p);