Mercurial > dovecot > core-2.2
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, ¶ms) < 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
--- /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);