diff options
Diffstat (limited to 'src/lib-dcrypt/dcrypt-openssl.c')
-rw-r--r-- | src/lib-dcrypt/dcrypt-openssl.c | 3807 |
1 files changed, 3807 insertions, 0 deletions
diff --git a/src/lib-dcrypt/dcrypt-openssl.c b/src/lib-dcrypt/dcrypt-openssl.c new file mode 100644 index 0000000..1cbe352 --- /dev/null +++ b/src/lib-dcrypt/dcrypt-openssl.c @@ -0,0 +1,3807 @@ +/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "base64.h" +#include "str.h" +#include "hex-binary.h" +#include "safe-memset.h" +#include "randgen.h" +#include "array.h" +#include "module-dir.h" +#include "istream.h" +#include "json-tree.h" +#include "dovecot-openssl-common.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 <openssl/bn.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)<tab>key id + + - 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 +**/ + +#ifndef HAVE_EVP_PKEY_get0 +#define EVP_PKEY_get0_EC_KEY(x) x->pkey.ec +#define EVP_PKEY_get0_RSA(x) x->pkey.rsa +#endif + +#ifndef HAVE_OBJ_LENGTH +#define OBJ_length(o) ((o)->length) +#endif + +#ifndef HAVE_EVP_MD_CTX_NEW +# define EVP_MD_CTX_new() EVP_MD_CTX_create() +# define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx) +#endif + +#ifndef HAVE_HMAC_CTX_NEW +# define HMAC_Init_ex(ctx, key, key_len, md, impl) \ + HMAC_Init_ex(&(ctx), key, key_len, md, impl) +# define HMAC_Update(ctx, data, len) HMAC_Update(&(ctx), data, len) +# define HMAC_Final(ctx, md, len) HMAC_Final(&(ctx), md, len) +# define HMAC_CTX_free(ctx) HMAC_cleanup(&(ctx)) +#else +# define HMAC_CTX_free(ctx) \ + STMT_START { HMAC_CTX_free(ctx); (ctx) = NULL; } STMT_END +#endif + +/* Not always present */ +#ifndef HAVE_BN_SECURE_NEW +# define BN_secure_new BN_new +#endif + +/* openssl manual says this is OK */ +#define OID_TEXT_MAX_LEN 80 + +#define t_base64url_decode_str(x) t_base64url_decode_str(BASE64_DECODE_FLAG_IGNORE_PADDING, (x)) + +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; +#ifdef HAVE_HMAC_CTX_NEW + HMAC_CTX *ctx; +#else + HMAC_CTX ctx; +#endif + unsigned char *key; + size_t klen; +}; + +struct dcrypt_public_key { + EVP_PKEY *key; + unsigned int ref; + enum dcrypt_key_usage usage; + char *key_id; +}; + +struct dcrypt_private_key { + EVP_PKEY *key; + unsigned int ref; + enum dcrypt_key_usage usage; + char *key_id; +}; + +#define DCRYPT_SET_ERROR(error) STMT_START { if (error_r != NULL) *error_r = (error); } STMT_END + +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_key_id(struct dcrypt_private_key *key, + const char *algorithm, buffer_t *result, + const char **error_r); +static bool +dcrypt_openssl_private_key_id_old(struct dcrypt_private_key *key, + buffer_t *result, const char **error_r); +static void +dcrypt_openssl_private_to_public_key(struct dcrypt_private_key *priv_key, + struct dcrypt_public_key **pub_key_r); +static void +dcrypt_openssl_unref_private_key(struct dcrypt_private_key **key); +static void +dcrypt_openssl_unref_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, enum dcrypt_padding padding, + const char **error_r); +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); + +#ifndef HAVE_EC_GROUP_order_bits +static int EC_GROUP_order_bits(const EC_GROUP *grp) +{ + int bits; + BIGNUM *bn = BN_new(); + (void)EC_GROUP_get_order(grp, bn, NULL); + bits = BN_num_bits(bn); + BN_free(bn); + return bits; +} +#endif + +static bool dcrypt_openssl_error(const char **error_r) +{ + unsigned long ec; + + if (error_r == NULL) { + /* caller is not really interested */ + return FALSE; + } + + ec = ERR_get_error(); + DCRYPT_SET_ERROR(t_strdup_printf("%s", ERR_error_string(ec, NULL))); + return FALSE; +} + +static int +dcrypt_openssl_padding_mode(enum dcrypt_padding padding, + bool sig, const char **error_r) +{ + switch (padding) { + case DCRYPT_PADDING_DEFAULT: + if (sig) return RSA_PKCS1_PSS_PADDING; + else return RSA_PKCS1_OAEP_PADDING; + case DCRYPT_PADDING_RSA_PKCS1_OAEP: + return RSA_PKCS1_OAEP_PADDING; + case DCRYPT_PADDING_RSA_PKCS1_PSS: + return RSA_PKCS1_PSS_PADDING; + case DCRYPT_PADDING_RSA_PKCS1: + return RSA_PKCS1_PADDING; + case DCRYPT_PADDING_RSA_NO: + return RSA_NO_PADDING; + default: + DCRYPT_SET_ERROR("Unsupported padding mode"); + return -1; + } + i_unreached(); +} + +static bool dcrypt_openssl_initialize(const struct dcrypt_settings *set, + const char **error_r) +{ + if (set->crypto_device != NULL && set->crypto_device[0] != '\0') { + if (dovecot_openssl_common_global_set_engine( + set->crypto_device, error_r) <= 0) + return FALSE; + } + return TRUE; +} + +/* 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) { + DCRYPT_SET_ERROR(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 != NULL) + 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_key_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) + DCRYPT_SET_ERROR("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) { + DCRYPT_SET_ERROR(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; + HMAC_CTX_free((*ctx)->ctx); + 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); +#ifdef HAVE_HMAC_CTX_NEW + ctx->ctx = HMAC_CTX_new(); + if (ctx->ctx == NULL) + return dcrypt_openssl_error(error_r); +#endif + ec = HMAC_Init_ex(ctx->ctx, ctx->key, ctx->klen, ctx->md, NULL); + 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; + + ec = HMAC_Update(ctx->ctx, data, data_len); + 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; + + ec = HMAC_Final(ctx->ctx, buf, &outl); + HMAC_CTX_free(ctx->ctx); + 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 (ctx == NULL || + 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(EVP_PKEY_get0_EC_KEY((*key)), + OPENSSL_EC_NAMED_CURVE); + return TRUE; +} + +static bool +dcrypt_openssl_generate_rsa_key(int bits, EVP_PKEY **key, const char **error_r) +{ + i_assert(bits >= 256); + 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(struct dcrypt_private_key *priv_key, + struct dcrypt_public_key *pub_key, + buffer_t *shared_secret, + const char **error_r) +{ + /* initialize */ + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(priv_key->key, NULL); + if (pctx == NULL || + EVP_PKEY_derive_init(pctx) != 1 || + EVP_PKEY_derive_set_peer(pctx, pub_key->key) != 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(shared_secret, buf, len); + + return TRUE; +} + +static bool +dcrypt_openssl_ecdh_derive_secret_local(struct dcrypt_private_key *local_key, + buffer_t *R, buffer_t *S, + const char **error_r) +{ + bool ret; + i_assert(local_key != NULL && local_key->key != NULL); + + EVP_PKEY *local = local_key->key; + BN_CTX *bn_ctx = BN_CTX_new(); + if (bn_ctx == NULL) + return dcrypt_openssl_error(error_r); + + const EC_GROUP *grp = EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(local)); + EC_POINT *pub = EC_POINT_new(grp); + + /* convert ephemeral key data EC point */ + if (pub == NULL || + 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 */ + int ec = 0; + if (ec_key == NULL || + EC_KEY_set_group(ec_key, grp) != 1 || + EC_KEY_set_public_key(ec_key, pub) != 1) + ec = -1; + else + EC_POINT_free(pub); + BN_CTX_free(bn_ctx); + + /* make sure it looks like a valid key */ + if (ec == -1 || EC_KEY_check_key(ec_key) != 1) { + EC_KEY_free(ec_key); + return dcrypt_openssl_error(error_r); + } + + EVP_PKEY *peer = EVP_PKEY_new(); + if (peer == NULL) { + EC_KEY_free(ec_key); + return dcrypt_openssl_error(error_r); + } + EVP_PKEY_set1_EC_KEY(peer, ec_key); + EC_KEY_free(ec_key); + + struct dcrypt_public_key pub_key; + i_zero(&pub_key); + pub_key.key = peer; + + ret = dcrypt_openssl_ecdh_derive_secret(local_key, &pub_key, S, error_r); + + EVP_PKEY_free(peer); + return ret; +} + +static bool +dcrypt_openssl_ecdh_derive_secret_peer(struct dcrypt_public_key *peer_key, + buffer_t *R, buffer_t *S, + const char **error_r) +{ + i_assert(peer_key != NULL && peer_key->key != NULL); + bool ret; + + /* ensure peer_key is EC key */ + EVP_PKEY *local = NULL; + EVP_PKEY *peer = peer_key->key; + if (EVP_PKEY_base_id(peer) != EVP_PKEY_EC) { + DCRYPT_SET_ERROR("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(EVP_PKEY_get0_EC_KEY(peer))); + if (!dcrypt_openssl_generate_ec_key(nid, &local, error_r)) + return FALSE; + + struct dcrypt_private_key priv_key; + i_zero(&priv_key); + priv_key.key = local; + + if (!(ret = dcrypt_openssl_ecdh_derive_secret(&priv_key, peer_key, S, + error_r))) { + EVP_PKEY_free(local); + return FALSE; + } + + /* get ephemeral key (=R) */ + BN_CTX *bn_ctx = BN_CTX_new(); + const EC_POINT *pub = EC_KEY_get0_public_key(EVP_PKEY_get0_EC_KEY(local)); + const EC_GROUP *grp = EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(local)); + size_t len = EC_POINT_point2oct(grp, pub, POINT_CONVERSION_UNCOMPRESSED, + NULL, 0, bn_ctx); + unsigned char R_buf[len]; + EC_POINT_point2oct(grp, pub, POINT_CONVERSION_UNCOMPRESSED, + R_buf, len, bn_ctx); + BN_CTX_free(bn_ctx); + buffer_append(R, R_buf, len); + EVP_PKEY_free(local); + + return ret; +} + +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); + /* determine MD */ + const EVP_MD* md = EVP_get_digestbyname(hash); + if (md == NULL) { + DCRYPT_SET_ERROR(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); + } + 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) +{ + EVP_PKEY *pkey = NULL; + + i_assert(pair_r != NULL); + i_zero(pair_r); + if (kind == DCRYPT_KEY_RSA) { + if (dcrypt_openssl_generate_rsa_key(bits, &pkey, error_r)) { + pair_r->priv = i_new(struct dcrypt_private_key, 1); + pair_r->priv->key = pkey; + pair_r->priv->ref++; + pair_r->pub = NULL; + dcrypt_openssl_private_to_public_key(pair_r->priv, + &pair_r->pub); + return TRUE; + } else { + return dcrypt_openssl_error(error_r); + } + } else if (kind == DCRYPT_KEY_EC) { + int nid = OBJ_sn2nid(curve); + if (nid == NID_undef) { + DCRYPT_SET_ERROR(t_strdup_printf("Unknown EC curve %s", + curve)); + return FALSE; + } + if (dcrypt_openssl_generate_ec_key(nid, &pkey, error_r)) { + pair_r->priv = i_new(struct dcrypt_private_key, 1); + pair_r->priv->key = pkey; + pair_r->priv->ref++; + pair_r->pub = NULL; + dcrypt_openssl_private_to_public_key(pair_r->priv, + &pair_r->pub); + return TRUE; + } else { + return dcrypt_openssl_error(error_r); + } + } + DCRYPT_SET_ERROR("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 = t_buffer_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 == NULL) + 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 = t_buffer_create(128); + peer_key = t_buffer_create(64); + + hex_to_binary(data_hex, data); + hex_to_binary(peer_key_hex, peer_key); + + secret = t_buffer_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; + + data = t_buffer_create(128); + salt = t_buffer_create(16); + password = t_buffer_create(32); + key = t_buffer_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)) + 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; + BIGNUM *point = NULL; + + if (str_to_int(input[1], &nid) != 0) { + DCRYPT_SET_ERROR("Corrupted data"); + return FALSE; + } + + if (str_to_int(input[2], &enctype) != 0) { + DCRYPT_SET_ERROR("Corrupted data"); + return FALSE; + } + + /* decode and optionally decipher private key value */ + if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_NONE) { + point = BN_secure_new(); + if (point == NULL || BN_hex2bn(&point, input[3]) < 1) { + BN_free(point); + return dcrypt_openssl_error(error_r); + } + } else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD) { + /* by password */ + if (password == NULL) { + DCRYPT_SET_ERROR("password missing"); + return FALSE; + } + 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)) { + return FALSE; + } + } else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) { + /* by key */ + if (dec_key == NULL) { + DCRYPT_SET_ERROR("decrypt key missing"); + return FALSE; + } + 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)) { + return FALSE; + } + } else { + DCRYPT_SET_ERROR("Invalid key data"); + return FALSE; + } + + EC_KEY *eckey = EC_KEY_new_by_curve_name(nid); + if (eckey == NULL) return dcrypt_openssl_error(error_r); + + /* assign private key */ + BN_CTX *bnctx = BN_CTX_new(); + if (bnctx == NULL) { + EC_KEY_free(eckey); + return dcrypt_openssl_error(error_r); + } + 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)); + if (pub == NULL) { + EC_KEY_free(eckey); + BN_CTX_free(bnctx); + return dcrypt_openssl_error(error_r); + } + /* 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); + if (id == NULL) { + EC_KEY_free(eckey); + return dcrypt_openssl_error(error_r); + } + 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) { + DCRYPT_SET_ERROR("Key id mismatch after load"); + EC_KEY_free(eckey); + return FALSE; + } + EVP_PKEY *key = EVP_PKEY_new(); + if (key == NULL) { + EC_KEY_free(eckey); + return dcrypt_openssl_error(error_r); + } + EVP_PKEY_set1_EC_KEY(key, eckey); + EC_KEY_free(eckey); + *key_r = i_new(struct dcrypt_private_key, 1); + (*key_r)->key = key; + (*key_r)->ref++; + 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 = t_buffer_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 = t_buffer_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_MAX); + 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 = t_buffer_create(256); + + /* check for encryption type */ + if (str_to_int(input[2], &enctype) != 0) { + DCRYPT_SET_ERROR("Corrupted data"); + return FALSE; + } + + if (enctype < 0 || enctype > 2) { + DCRYPT_SET_ERROR("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)) { + DCRYPT_SET_ERROR("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) { + DCRYPT_SET_ERROR("Corrupted data"); + } + } else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) { + if (dec_key == NULL) { + DCRYPT_SET_ERROR("decrypt key missing"); + return FALSE; + } + unsigned int rounds; + struct dcrypt_public_key *pubkey = NULL; + if (str_to_uint(input[6], &rounds) != 0) { + DCRYPT_SET_ERROR("Corrupted data"); + return FALSE; + } + + buffer_t *data = t_buffer_create(128); + + /* check that we have correct decryption key */ + dcrypt_openssl_private_to_public_key(dec_key, &pubkey); + if (!dcrypt_openssl_public_key_id(pubkey, "sha256", + data, error_r)) { + dcrypt_openssl_unref_public_key(&pubkey); + return FALSE; + } + + dcrypt_openssl_unref_public_key(&pubkey); + + if (strcmp(binary_to_hex(data->data, data->used), + input[9]) != 0) { + DCRYPT_SET_ERROR("No private key available"); + return FALSE; + } + + + buffer_t *salt, *peer_key, *secret; + salt = t_buffer_create(strlen(input[4])/2); + peer_key = t_buffer_create(strlen(input[8])/2); + secret = t_buffer_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, + DCRYPT_PADDING_RSA_PKCS1_OAEP, 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) { + if (password == NULL) { + DCRYPT_SET_ERROR("password missing"); + return FALSE; + } + unsigned int rounds; + if (str_to_uint(input[6], &rounds) != 0) { + DCRYPT_SET_ERROR("Corrupted data"); + return FALSE; + } + + buffer_t *salt, secret, *data; + salt = t_buffer_create(strlen(input[4])/2); + buffer_create_from_const_data(&secret, password, strlen(password)); + data = t_buffer_create(strlen(input[7])/2); + if (hex_to_binary(input[4], salt) != 0 || + hex_to_binary(input[7], data) != 0) { + DCRYPT_SET_ERROR("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 (rsa == NULL || + 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(); + if (pkey == NULL) { + RSA_free(rsa); + return dcrypt_openssl_error(error_r); + } + EVP_PKEY_set1_RSA(pkey, rsa); + RSA_free(rsa); + *key_r = i_new(struct dcrypt_private_key, 1); + (*key_r)->key = pkey; + (*key_r)->ref++; + } else { + int ec; + BIGNUM *point = BN_secure_new(); + if (point == NULL || + 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); + BN_CTX *bnctx = BN_CTX_new(); + if (eckey == NULL || bnctx == NULL) { + BN_free(point); + EC_KEY_free(eckey); + BN_CTX_free(bnctx); + return dcrypt_openssl_error(error_r); + } + 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)); + if (pub == NULL) + ec = -1; + else { + /* calculate public key */ + ec = EC_POINT_mul(EC_KEY_get0_group(eckey), pub, point, + NULL, NULL, bnctx); + EC_KEY_set_public_key(eckey, pub); + EC_POINT_free(pub); + } + BN_free(point); + BN_CTX_free(bnctx); + /* make sure the EC key is valid */ + EVP_PKEY *key = EVP_PKEY_new(); + if (ec == 1 && key != NULL && EC_KEY_check_key(eckey) == 1) { + EVP_PKEY_set1_EC_KEY(key, eckey); + EC_KEY_free(eckey); + *key_r = i_new(struct dcrypt_private_key, 1); + (*key_r)->key = key; + (*key_r)->ref++; + } else { + EVP_PKEY_free(key); + EC_KEY_free(eckey); + return dcrypt_openssl_error(error_r); + } + } + + /* finally compare key to key id */ + dcrypt_openssl_private_key_id(*key_r, "sha256", key_data, NULL); + + if (strcmp(binary_to_hex(key_data->data, key_data->used), + input[len-1]) != 0) { + dcrypt_openssl_unref_private_key(key_r); + DCRYPT_SET_ERROR("Key id mismatch after load"); + return FALSE; + } + + return TRUE; +} + +/* JWK Parameter names defined at https://www.iana.org/assignments/jose/jose.xhtml */ + +static const struct jwk_to_ssl_map_entry { + const char *jwk_curve; + int nid; +} jwk_to_ssl_curves[] = +{ + /* See https://tools.ietf.org/search/rfc8422#appendix-A */ + { .jwk_curve = "P-256", .nid = NID_X9_62_prime256v1 }, + { .jwk_curve = "P-384", .nid = NID_secp384r1 }, + { .jwk_curve = "P-521", .nid = NID_secp521r1 }, + { .jwk_curve = NULL, .nid = 0 } +}; + +static const char *key_usage_to_jwk_use(enum dcrypt_key_usage usage) +{ + switch(usage) { + case DCRYPT_KEY_USAGE_NONE: + return NULL; + case DCRYPT_KEY_USAGE_ENCRYPT: + return "enc"; + case DCRYPT_KEY_USAGE_SIGN: + return "sig"; + }; + i_unreached(); +} + +static enum dcrypt_key_usage jwk_use_to_key_usage(const char *use) +{ + if (strcmp(use, "enc") == 0) + return DCRYPT_KEY_USAGE_ENCRYPT; + if (strcmp(use, "sig") == 0) + return DCRYPT_KEY_USAGE_SIGN; + return DCRYPT_KEY_USAGE_NONE; +} + +static int jwk_curve_to_nid(const char *curve) +{ + /* use static mapping table to get correct input for OpenSSL */ + const struct jwk_to_ssl_map_entry *entry = jwk_to_ssl_curves; + for (;entry->jwk_curve != NULL;entry++) + if (strcmp(curve, entry->jwk_curve) == 0) + return entry->nid; + return 0; +} + +static const char *nid_to_jwk_curve(int nid) +{ + const struct jwk_to_ssl_map_entry *entry = jwk_to_ssl_curves; + for (;entry->jwk_curve != NULL;entry++) + if (nid == entry->nid) + return entry->jwk_curve; + return NULL; +} + +/* Loads both public and private key */ +static bool load_jwk_ec_key(EVP_PKEY **key_r, bool want_private_key, + const struct json_tree_node *root, + const char *password ATTR_UNUSED, + struct dcrypt_private_key *dec_key ATTR_UNUSED, + const char **error_r) +{ + i_assert(password == NULL && dec_key == NULL); + const char *crv, *x, *y, *d; + const struct json_tree_node *node; + + if ((node = json_tree_find_key(root, "crv")) == NULL || + (crv = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing crv parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "x")) == NULL || + (x = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing x parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "y")) == NULL || + (y = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing y parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "d")) == NULL || + (d = json_tree_get_value_str(node)) == NULL) { + if (want_private_key) { + DCRYPT_SET_ERROR("Missing d parameter"); + return FALSE; + } + } + + /* base64 decode x and y */ + buffer_t *bx = t_base64url_decode_str(x); + buffer_t *by = t_base64url_decode_str(y); + + /* determine NID */ + int nid = jwk_curve_to_nid(crv); + if (nid == 0) { + DCRYPT_SET_ERROR(t_strdup_printf("Unsupported curve: %s", crv)); + return FALSE; + } + /* create key */ + EC_KEY *ec_key = EC_KEY_new_by_curve_name(nid); + if (ec_key == NULL) { + DCRYPT_SET_ERROR("Cannot allocate memory"); + return FALSE; + } + + BIGNUM *px = BN_new(); + BIGNUM *py = BN_new(); + + if (BN_bin2bn(bx->data, bx->used, px) == NULL || + BN_bin2bn(by->data, by->used, py) == NULL) { + EC_KEY_free(ec_key); + BN_free(px); + BN_free(py); + return dcrypt_openssl_error(error_r); + } + + int ret = EC_KEY_set_public_key_affine_coordinates(ec_key, px, py); + BN_free(px); + BN_free(py); + + if (ret != 1) { + EC_KEY_free(ec_key); + return dcrypt_openssl_error(error_r); + } + + /* FIXME: Support decryption */ + if (want_private_key) { + buffer_t *bd = t_base64url_decode_str(d); + BIGNUM *pd = BN_secure_new(); + if (BN_bin2bn(bd->data, bd->used, pd) == NULL) { + EC_KEY_free(ec_key); + return dcrypt_openssl_error(error_r); + } + ret = EC_KEY_set_private_key(ec_key, pd); + BN_free(pd); + if (ret != 1) { + EC_KEY_free(ec_key); + return dcrypt_openssl_error(error_r); + } + } + + if (EC_KEY_check_key(ec_key) != 1) { + EC_KEY_free(ec_key); + return dcrypt_openssl_error(error_r); + } + + EC_KEY_precompute_mult(ec_key, NULL); + EC_KEY_set_asn1_flag(ec_key, OPENSSL_EC_NAMED_CURVE); + + /* return as EVP_PKEY */ + EVP_PKEY *pkey = EVP_PKEY_new(); + EVP_PKEY_set1_EC_KEY(pkey, ec_key); + EC_KEY_free(ec_key); + *key_r = pkey; + + return TRUE; +} + +/* RSA helpers */ +#if !defined(HAVE_RSA_SET0_KEY) +static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) +{ + if (n == NULL || e == NULL) { + RSAerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + BN_free(r->n); + r->n = n; + BN_free(r->e); + r->e = e; + BN_free(r->d); + r->d = d; + return 1; +} +#endif +#if !defined(HAVE_RSA_SET0_FACTORS) +static int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) +{ + if (p == NULL || q == NULL) { + RSAerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + BN_free(r->p); + r->p = p; + BN_free(r->q); + r->q = q; + return 1; +} +#endif +#if !defined(HAVE_RSA_SET0_CRT_PARAMS) +static int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) +{ + if (dmp1 == NULL || dmq1 == NULL || iqmp == NULL) { + RSAerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + BN_free(r->dmp1); + r->dmp1 = dmp1; + BN_free(r->dmq1); + r->dmq1 = dmq1; + BN_free(r->iqmp); + r->iqmp = iqmp; + return 1; +} +#endif + +/* Loads both public and private key */ +static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key, + const struct json_tree_node *root, + const char *password ATTR_UNUSED, + struct dcrypt_private_key *dec_key ATTR_UNUSED, + const char **error_r) +{ + const char *n, *e, *d = NULL, *p = NULL, *q = NULL, *dp = NULL; + const char *dq = NULL, *qi = NULL; + const struct json_tree_node *node; + + /* n and e must be present */ + if ((node = json_tree_find_key(root, "n")) == NULL || + (n = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing n parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "e")) == NULL || + (e = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing e parameter"); + return FALSE; + } + + if (want_private_key) { + if ((node = json_tree_find_key(root, "d")) == NULL || + (d = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing d parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "p")) == NULL || + (p = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing p parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "q")) == NULL || + (q = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing q parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "dp")) == NULL || + (dp = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing dp parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "dq")) == NULL || + (dq = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing dq parameter"); + return FALSE; + } + + if ((node = json_tree_find_key(root, "qi")) == NULL || + (qi = json_tree_get_value_str(node)) == NULL) { + DCRYPT_SET_ERROR("Missing qi parameter"); + return FALSE; + } + } + + /* convert into BIGNUMs */ + BIGNUM *pn, *pe, *pd, *pp, *pq, *pdp, *pdq, *pqi; + buffer_t *bn = t_base64url_decode_str(n); + buffer_t *be = t_base64url_decode_str(e); + if (want_private_key) { + pd = BN_secure_new(); + buffer_t *bd = t_base64url_decode_str(d); + if (BN_bin2bn(bd->data, bd->used, pd) == NULL) { + BN_free(pd); + return dcrypt_openssl_error(error_r); + } + } else { + pd = NULL; + } + + pn = BN_new(); + pe = BN_new(); + + if (BN_bin2bn(bn->data, bn->used, pn) == NULL || + BN_bin2bn(be->data, be->used, pe) == NULL) { + if (pd != NULL) + BN_free(pd); + BN_free(pn); + BN_free(pe); + return dcrypt_openssl_error(error_r); + } + + RSA *rsa_key = RSA_new(); + if (rsa_key == NULL) { + if (pd != NULL) + BN_free(pd); + BN_free(pn); + BN_free(pe); + return dcrypt_openssl_error(error_r); + } + + if (RSA_set0_key(rsa_key, pn, pe, pd) != 1) { + if (pd != NULL) + BN_free(pd); + BN_free(pn); + BN_free(pe); + RSA_free(rsa_key); + return dcrypt_openssl_error(error_r); + } + + if (want_private_key) { + pp = BN_secure_new(); + pq = BN_secure_new(); + pdp = BN_secure_new(); + pdq = BN_secure_new(); + pqi = BN_secure_new(); + + buffer_t *bp = t_base64url_decode_str(p); + buffer_t *bq = t_base64url_decode_str(q); + buffer_t *bdp = t_base64url_decode_str(dp); + buffer_t *bdq = t_base64url_decode_str(dq); + buffer_t *bqi = t_base64url_decode_str(qi); + + if (BN_bin2bn(bp->data, bp->used, pp) == NULL || + BN_bin2bn(bq->data, bq->used, pq) == NULL || + BN_bin2bn(bdp->data, bdp->used, pdp) == NULL || + BN_bin2bn(bdq->data, bdq->used, pdq) == NULL || + BN_bin2bn(bqi->data, bqi->used, pqi) == NULL || + RSA_set0_factors(rsa_key, pp, pq) != 1) { + RSA_free(rsa_key); + BN_free(pp); + BN_free(pq); + BN_free(pdp); + BN_free(pdq); + BN_free(pqi); + return dcrypt_openssl_error(error_r); + } else if (RSA_set0_crt_params(rsa_key, pdp, pdq, pqi) != 1) { + RSA_free(rsa_key); + BN_free(pdp); + BN_free(pdq); + BN_free(pqi); + return dcrypt_openssl_error(error_r); + } + } + + /* return as EVP_PKEY */ + EVP_PKEY *pkey = EVP_PKEY_new(); + EVP_PKEY_set1_RSA(pkey, rsa_key); + RSA_free(rsa_key); + *key_r = pkey; + + return TRUE; +} + + +static bool +dcrypt_openssl_load_private_key_jwk(struct dcrypt_private_key **key_r, + const char *data, const char *password, + struct dcrypt_private_key *dec_key, + const char **error_r) +{ + const char *kty; + const char *error; + const struct json_tree_node *root, *node; + struct json_tree *key_tree; + EVP_PKEY *pkey; + bool ret; + + if (parse_jwk_key(data, &key_tree, &error) != 0) { + DCRYPT_SET_ERROR(t_strdup_printf("Cannot load JWK private key: %s", + error)); + return FALSE; + } + + root = json_tree_root(key_tree); + + /* check key type */ + if ((node = json_tree_find_key(root, "kty")) == NULL) { + DCRYPT_SET_ERROR("Cannot load JWK private key: no kty parameter"); + json_tree_deinit(&key_tree); + return FALSE; + } + + kty = json_tree_get_value_str(node); + + if (null_strcmp(kty, "EC") == 0) { + ret = load_jwk_ec_key(&pkey, TRUE, root, password, dec_key, &error); + } else if (strcmp(kty, "RSA") == 0) { + ret = load_jwk_rsa_key(&pkey, TRUE, root, password, dec_key, &error); + } else { + error = "Unsupported key type"; + ret = FALSE; + } + + i_assert(ret || error != NULL); + + if (!ret) + DCRYPT_SET_ERROR(t_strdup_printf("Cannot load JWK private key: %s", error)); + else if (ret) { + *key_r = i_new(struct dcrypt_private_key, 1); + (*key_r)->key = pkey; + (*key_r)->ref++; + /* check if kid is present */ + if ((node = json_tree_find_key(root, "kid")) != NULL) + (*key_r)->key_id = i_strdup_empty(json_tree_get_value_str(node)); + /* check if use is present */ + if ((node = json_tree_find_key(root, "use")) != NULL) + (*key_r)->usage = jwk_use_to_key_usage(json_tree_get_value_str(node)); + } + + json_tree_deinit(&key_tree); + + return ret; +} + +static bool +dcrypt_openssl_load_public_key_jwk(struct dcrypt_public_key **key_r, + const char *data, const char **error_r) +{ + const char *kty; + const char *error; + const struct json_tree_node *root, *node; + struct json_tree *key_tree; + EVP_PKEY *pkey; + bool ret; + + if (parse_jwk_key(data, &key_tree, &error) != 0) { + DCRYPT_SET_ERROR(t_strdup_printf("Cannot load JWK public key: %s", + error)); + return FALSE; + } + + root = json_tree_root(key_tree); + + /* check key type */ + if ((node = json_tree_find_key(root, "kty")) == NULL) { + DCRYPT_SET_ERROR("Cannot load JWK public key: no kty parameter"); + json_tree_deinit(&key_tree); + return FALSE; + } + + kty = json_tree_get_value_str(node); + + if (null_strcmp(kty, "EC") == 0) { + ret = load_jwk_ec_key(&pkey, FALSE, root, NULL, NULL, &error); + } else if (strcmp(kty, "RSA") == 0) { + ret = load_jwk_rsa_key(&pkey, FALSE, root, NULL, NULL, &error); + } else { + error = "Unsupported key type"; + ret = FALSE; + } + + i_assert(ret || error != NULL); + + if (!ret) + DCRYPT_SET_ERROR(t_strdup_printf("Cannot load JWK public key: %s", error)); + else if (ret) { + *key_r = i_new(struct dcrypt_public_key, 1); + (*key_r)->key = pkey; + (*key_r)->ref++; + /* check if kid is present */ + if ((node = json_tree_find_key(root, "kid")) != NULL) + (*key_r)->key_id = i_strdup_empty(json_tree_get_value_str(node)); + /* check if use is present */ + if ((node = json_tree_find_key(root, "use")) != NULL) + (*key_r)->usage = jwk_use_to_key_usage(json_tree_get_value_str(node)); + } + + json_tree_deinit(&key_tree); + + return ret; +} + + +static int bn2base64url(const BIGNUM *bn, string_t *dest) +{ + int len = BN_num_bytes(bn); + unsigned char *data = t_malloc_no0(len); + if (BN_bn2bin(bn, data) != len) + return -1; + base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, data, len, dest); + return 0; +} + +/* FIXME: Add encryption support */ +/* FIXME: Add support for 'algo' field */ +static bool store_jwk_ec_key(EVP_PKEY *pkey, bool is_private_key, + enum dcrypt_key_usage usage, + const char *key_id, + const char *cipher ATTR_UNUSED, + const char *password ATTR_UNUSED, + struct dcrypt_public_key *enc_key ATTR_UNUSED, + string_t *dest, const char **error_r) +{ + i_assert(cipher == NULL && password == NULL && enc_key == NULL); + string_t *temp = t_str_new(256); + const EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); + i_assert(ec_key != NULL); + + int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key)); + const EC_POINT *public_point = EC_KEY_get0_public_key(ec_key); + BIGNUM *x, *y; + + x = BN_new(); + y = BN_new(); + if (EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec_key), public_point, + x, y, NULL) != 1) { + BN_free(x); + BN_free(y); + return dcrypt_openssl_error(error_r); + } + + const char *curve = nid_to_jwk_curve(nid); + const char *use = key_usage_to_jwk_use(usage); + + str_printfa(temp, "{\"kty\":\"EC\",\"crv\":\"%s\"", curve); + str_append(temp, ",\"x\":\""); + bn2base64url(x, temp); + str_append(temp, "\",\"y\":\""); + bn2base64url(y, temp); + + if (use != NULL) { + str_append(temp, "\",\"use\":\""); + json_append_escaped(temp, use); + } + if (key_id != NULL) { + str_append(temp, "\",\"kid\":\""); + json_append_escaped(temp, key_id); + } + BN_free(x); + BN_free(y); + + if (is_private_key) { + const BIGNUM *d = EC_KEY_get0_private_key(ec_key); + if (d == NULL) { + DCRYPT_SET_ERROR("No private key available"); + return FALSE; + } + str_append(temp, "\",\"d\":\""); + bn2base64url(d, temp); + } + str_append(temp, "\"}"); + str_append_str(dest, temp); + return TRUE; +} + +/* FIXME: Add RSA support */ + +static bool store_jwk_key(EVP_PKEY *pkey, bool is_private_key, + enum dcrypt_key_usage usage, + const char *key_id, + const char *cipher, + const char *password, + struct dcrypt_public_key *enc_key, + string_t *dest, const char **error_r) +{ + i_assert(cipher == NULL && password == NULL && enc_key == NULL); + if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) { + return store_jwk_ec_key(pkey, is_private_key, usage, key_id, + cipher, password, enc_key, dest, error_r); + } + DCRYPT_SET_ERROR("Unsupported key type"); + return FALSE; +} + +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, + enum dcrypt_key_version version, + const char **error_r) +{ + const char **input = t_strsplit(data, ":\t"); + size_t len = str_array_length(input); + + switch (version) { + case DCRYPT_KEY_VERSION_1: + return dcrypt_openssl_load_private_key_dovecot_v1( + key_r, len, input, password, key, error_r); + case DCRYPT_KEY_VERSION_2: + return dcrypt_openssl_load_private_key_dovecot_v2( + key_r, len, input, password, key, error_r); + case DCRYPT_KEY_VERSION_NA: + i_unreached(); + } + return FALSE; +} + +static bool +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 (str_to_int(input[1], &nid) != 0) { + DCRYPT_SET_ERROR("Corrupted data"); + return FALSE; + } + + EC_KEY *eckey = EC_KEY_new_by_curve_name(nid); + if (eckey == NULL) { + dcrypt_openssl_error(error_r); + return FALSE; + } + + 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 (bnctx == NULL || point == NULL || + 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 FALSE; + } + 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); + EC_KEY_free(eckey); + /* make sure digest matches */ + buffer_t *dgst = t_buffer_create(32); + struct dcrypt_public_key tmp; + i_zero(&tmp); + tmp.key = key; + dcrypt_openssl_public_key_id_old(&tmp, dgst, NULL); + if (strcmp(binary_to_hex(dgst->data, dgst->used), + input[len-1]) != 0) { + DCRYPT_SET_ERROR("Key id mismatch after load"); + EVP_PKEY_free(key); + return FALSE; + } + *key_r = i_new(struct dcrypt_public_key, 1); + (*key_r)->key = key; + (*key_r)->ref++; + return TRUE; + } + + dcrypt_openssl_error(error_r); + return FALSE; +} + +static bool +dcrypt_openssl_load_public_key_dovecot_v2(struct dcrypt_public_key **key_r, + int len, const char **input, + const char **error_r) +{ + buffer_t tmp; + size_t keylen = strlen(input[1])/2; + unsigned char keybuf[keylen]; + const unsigned char *ptr; + buffer_create_from_data(&tmp, keybuf, keylen); + hex_to_binary(input[1], &tmp); + ptr = keybuf; + + EVP_PKEY *pkey = EVP_PKEY_new(); + if (pkey == NULL || d2i_PUBKEY(&pkey, &ptr, keylen)==NULL) { + EVP_PKEY_free(pkey); + dcrypt_openssl_error(error_r); + return FALSE; + } + + /* make sure digest matches */ + buffer_t *dgst = t_buffer_create(32); + struct dcrypt_public_key tmpkey; + i_zero(&tmpkey); + tmpkey.key = pkey; + dcrypt_openssl_public_key_id(&tmpkey, "sha256", dgst, NULL); + if (strcmp(binary_to_hex(dgst->data, dgst->used), input[len-1]) != 0) { + DCRYPT_SET_ERROR("Key id mismatch after load"); + EVP_PKEY_free(pkey); + return FALSE; + } + + *key_r = i_new(struct dcrypt_public_key, 1); + (*key_r)->key = pkey; + (*key_r)->ref++; + return TRUE; +} + +static bool +dcrypt_openssl_load_public_key_dovecot(struct dcrypt_public_key **key_r, + const char *data, + enum dcrypt_key_version version, + const char **error_r) +{ + const char **input = t_strsplit(data, ":\t"); + size_t len = str_array_length(input); + + switch (version) { + case DCRYPT_KEY_VERSION_1: + return dcrypt_openssl_load_public_key_dovecot_v1( + key_r, len, input, error_r); + break; + case DCRYPT_KEY_VERSION_2: + return dcrypt_openssl_load_public_key_dovecot_v2( + key_r, len, input, error_r); + break; + case DCRYPT_KEY_VERSION_NA: + i_unreached(); + } + return 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 = t_buffer_create(128); + buffer_t *secret = t_buffer_create(128); + cipher = t_str_lcase(cipher); + + str_append(destination, cipher); + str_append_c(destination, ':'); + 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(":%s:%d:", + DCRYPT_DOVECOT_KEY_ENCRYPT_HASH, + DCRYPT_DOVECOT_KEY_ENCRYPT_ROUNDS)); + + if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) { + if (EVP_PKEY_base_id(enc_key->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, + DCRYPT_PADDING_RSA_PKCS1_OAEP, + error_r)) { + return FALSE; + } + } else if (EVP_PKEY_base_id(enc_key->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 { + /* Loading the key should have failed */ + i_unreached(); + } + /* 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 = t_buffer_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, ':'); + + /* for RSA, this is the actual encrypted secret */ + binary_to_hex_append(destination, + peer_key->data, peer_key->used); + str_append_c(destination, ':'); + + 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 = key->key; + char objtxt[OID_TEXT_MAX_LEN]; + 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(EVP_PKEY_get0_EC_KEY(pkey)))); + EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey), + POINT_CONVERSION_COMPRESSED); + + } else { + obj = OBJ_nid2obj(EVP_PKEY_id(pkey)); + } + + int enctype = DCRYPT_KEY_ENCRYPTION_TYPE_NONE; + int len = OBJ_obj2txt(objtxt, sizeof(objtxt), obj, 1); + if (len < 1) + return dcrypt_openssl_error(error_r); + if (len > (int)sizeof(objtxt)) { + DCRYPT_SET_ERROR("Object identifier too long"); + return FALSE; + } + + buffer_t *buf = t_buffer_create(256); + + /* convert key to private key value */ + if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) { + unsigned char *ptr; + RSA *rsa = EVP_PKEY_get0_RSA(pkey); + int len = i2d_RSAPrivateKey(rsa, &ptr); + if (len < 1) + return dcrypt_openssl_error(error_r); + buffer_append(buf, ptr, len); + } else if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) { + unsigned char *ptr; + EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pkey); + 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 { + /* Loading the key should have failed */ + i_unreached(); + } + + /* 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; + } else if (enctype == DCRYPT_KEY_ENCRYPTION_TYPE_NONE) { + i_assert(enc_key == NULL && password == NULL); + } + + /* put in OID and encryption type */ + str_append(destination, t_strdup_printf("2:%s:%d:", + objtxt, enctype)); + + /* perform encryption if desired */ + if (enctype != DCRYPT_KEY_ENCRYPTION_TYPE_NONE) { + 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 */ + str_append_c(destination, ':'); + buffer_set_used_size(buf, 0); + bool res = dcrypt_openssl_private_key_id(key, "sha256", buf, error_r); + 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 = key->key; + unsigned char *tmp = NULL; + size_t dest_used = buffer_get_used_size(destination); + + if (EVP_PKEY_base_id(pubkey) == EVP_PKEY_EC) + EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pubkey), + POINT_CONVERSION_COMPRESSED); + 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, ':'); + binary_to_hex_append(destination, tmp, rv); + OPENSSL_free(tmp); + + /* append public key ID */ + str_append_c(destination, ':'); + + buffer_t *buf = t_buffer_create(32); + bool res = dcrypt_openssl_public_key_id(key, "sha256", buf, error_r); + + if (!res) { + buffer_set_used_size(destination, dest_used); + return FALSE; + } + + str_append(destination, binary_to_hex(buf->data, buf->used)); + return TRUE; +} + +static bool +dcrypt_openssl_load_private_key(struct dcrypt_private_key **key_r, + const char *data, const char *password, + struct dcrypt_private_key *dec_key, + const char **error_r) +{ + i_assert(key_r != NULL); + + enum dcrypt_key_format format; + enum dcrypt_key_version version; + enum dcrypt_key_kind kind; + if (!dcrypt_openssl_key_string_get_info(data, &format, &version, + &kind, NULL, NULL, NULL, error_r)) { + return FALSE; + } + if (kind != DCRYPT_KEY_KIND_PRIVATE) { + DCRYPT_SET_ERROR("key is not private"); + return FALSE; + } + + if (format == DCRYPT_FORMAT_JWK) + return dcrypt_openssl_load_private_key_jwk(key_r, data, password, + dec_key, error_r); + + if (format == DCRYPT_FORMAT_DOVECOT) + return dcrypt_openssl_load_private_key_dovecot(key_r, data, + password, dec_key, version, error_r); + + EVP_PKEY *key = NULL, *key2; + + 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_asn1_flag(EVP_PKEY_get0_EC_KEY(key), + OPENSSL_EC_NAMED_CURVE); + } + + *key_r = i_new(struct dcrypt_private_key, 1); + (*key_r)->key = key; + (*key_r)->ref++; + + return TRUE; +} + +static bool +dcrypt_openssl_load_public_key(struct dcrypt_public_key **key_r, + const char *data, const char **error_r) +{ + enum dcrypt_key_format format; + enum dcrypt_key_version version; + enum dcrypt_key_kind kind; + i_assert(key_r != NULL); + + if (!dcrypt_openssl_key_string_get_info(data, &format, &version, + &kind, NULL, NULL, NULL, + error_r)) { + return FALSE; + } + /* JWK private keys can be loaded as public */ + if (kind != DCRYPT_KEY_KIND_PUBLIC && format != DCRYPT_FORMAT_JWK) { + DCRYPT_SET_ERROR("key is not public"); + return FALSE; + } + + if (format == DCRYPT_FORMAT_JWK) + return dcrypt_openssl_load_public_key_jwk(key_r, data, error_r); + + if (format == DCRYPT_FORMAT_DOVECOT) + return dcrypt_openssl_load_public_key_dovecot(key_r, data, + version, error_r); + + EVP_PKEY *key = NULL; + BIO *key_in = BIO_new_mem_buf((void*)data, strlen(data)); + if (key_in == NULL) + return dcrypt_openssl_error(error_r); + + key = PEM_read_bio_PUBKEY(key_in, &key, NULL, NULL); + if (BIO_reset(key_in) <= 0) + i_unreached(); + 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) { + DCRYPT_SET_ERROR("Missing public key header"); + return FALSE; + } + BIO *b64 = BIO_new(BIO_f_base64()); + if (b64 == NULL) { + BIO_vfree(key_in); + return dcrypt_openssl_error(error_r); + } + EC_KEY *eckey = d2i_EC_PUBKEY_bio(b64, NULL); + if (eckey != NULL) { + EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); + key = EVP_PKEY_new(); + if (key != NULL) + EVP_PKEY_set1_EC_KEY(key, eckey); + EC_KEY_free(eckey); + } + } + + BIO_vfree(key_in); + + if (key == NULL) + return dcrypt_openssl_error(error_r); + + *key_r = i_new(struct dcrypt_public_key, 1); + (*key_r)->key = key; + (*key_r)->ref++; + + 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) +{ + i_assert(key != NULL && key->key != NULL); + + int ec; + if (format == DCRYPT_FORMAT_DOVECOT) { + bool ret; + ret = dcrypt_openssl_store_private_key_dovecot( + key, cipher, destination, password, enc_key, error_r); + return ret; + } + + EVP_PKEY *pkey = key->key; + + if (format == DCRYPT_FORMAT_JWK) { + bool ret; + ret = store_jwk_key(pkey, TRUE, key->usage, key->key_id, + cipher, password, enc_key, + destination, error_r); + return ret; + } + + if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) + EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey), + POINT_CONVERSION_UNCOMPRESSED); + + BIO *key_out = BIO_new(BIO_s_mem()); + if (key_out == NULL) + return dcrypt_openssl_error(error_r); + + const EVP_CIPHER *algo = NULL; + if (cipher != NULL) { + algo = EVP_get_cipherbyname(cipher); + if (algo == NULL) { + DCRYPT_SET_ERROR(t_strdup_printf("Invalid cipher %s", + cipher)); + return FALSE; + } + } + + ec = PEM_write_bio_PrivateKey(key_out, pkey, algo, + NULL, 0, NULL, (void*)password); + + if (BIO_flush(key_out) <= 0) + ec = -1; + + 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; + + i_assert(key != NULL && key->key != NULL); + + if (format == DCRYPT_FORMAT_DOVECOT) { + return dcrypt_openssl_store_public_key_dovecot(key, destination, + error_r); + } + + EVP_PKEY *pkey = key->key; + + if (format == DCRYPT_FORMAT_JWK) { + bool ret; + ret = store_jwk_key(pkey, FALSE, key->usage, key->key_id, + NULL, NULL, NULL, + destination, error_r); + return ret; + } + + if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) + EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey), + POINT_CONVERSION_UNCOMPRESSED); + + BIO *key_out = BIO_new(BIO_s_mem()); + if (key_out == NULL) + return dcrypt_openssl_error(error_r); + + BIO *b64; + if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) + ec = PEM_write_bio_PUBKEY(key_out, pkey); + else if ((b64 = BIO_new(BIO_f_base64())) == NULL) + ec = -1; + else { + (void)BIO_puts(key_out, "-----BEGIN PUBLIC KEY-----\n"); + (void)BIO_push(b64, key_out); + ec = i2d_EC_PUBKEY_bio(b64, EVP_PKEY_get0_EC_KEY(pkey)); + if (BIO_flush(b64) <= 0) + ec = -1; + (void)BIO_pop(b64); + BIO_vfree(b64); + if (BIO_puts(key_out, "-----END PUBLIC KEY-----") <= 0) + ec = -1; + } + + 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 void +dcrypt_openssl_private_to_public_key(struct dcrypt_private_key *priv_key, + struct dcrypt_public_key **pub_key_r) +{ + i_assert(priv_key != NULL && pub_key_r != NULL); + + EVP_PKEY *pkey = priv_key->key; + EVP_PKEY *pk; + + pk = EVP_PKEY_new(); + i_assert(pk != NULL); /* we shouldn't get malloc() failures */ + + if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) + { + RSA *rsa = RSAPublicKey_dup(EVP_PKEY_get0_RSA(pkey)); + EVP_PKEY_set1_RSA(pk, rsa); + RSA_free(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 { + /* Loading the key should have failed */ + i_unreached(); + } + + *pub_key_r = i_new(struct dcrypt_public_key, 1); + (*pub_key_r)->key = pk; + (*pub_key_r)->ref++; +} + +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; + char *encryption_key_hash = NULL; + char *key_hash = NULL; + + i_assert(key_data != NULL); + + /* is it PEM key */ + if (str_begins(key_data, "-----BEGIN ")) { + format = DCRYPT_FORMAT_PEM; + version = DCRYPT_KEY_VERSION_NA; + key_data += 11; + if (str_begins(key_data, "RSA ")) { + DCRYPT_SET_ERROR("RSA private key format not supported, convert it to PKEY format with openssl pkey"); + return FALSE; + } + if (str_begins(key_data, "ENCRYPTED ")) { + encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD; + key_data += 10; + } + if (str_begins(key_data, "PRIVATE KEY-----")) + kind = DCRYPT_KEY_KIND_PRIVATE; + else if (str_begins(key_data, "PUBLIC KEY-----")) + kind = DCRYPT_KEY_KIND_PUBLIC; + else { + DCRYPT_SET_ERROR("Unknown/invalid PEM key type"); + return FALSE; + } + } else if (*key_data == '{') { + /* possibly a JWK key */ + format = DCRYPT_FORMAT_JWK; + version = DCRYPT_KEY_VERSION_NA; + struct json_tree *tree; + const struct json_tree_node *root, *node; + const char *value, *error; + if (parse_jwk_key(key_data, &tree, &error) != 0) { + DCRYPT_SET_ERROR("Unknown/invalid key data"); + return FALSE; + } + + /* determine key type */ + root = json_tree_root(tree); + if ((node = json_tree_find_key(root, "kty")) == NULL || + (value = json_tree_get_value_str(node)) == NULL) { + json_tree_deinit(&tree); + DCRYPT_SET_ERROR("Invalid JWK key: Missing kty parameter"); + return FALSE; + } else if (strcmp(value, "RSA") == 0) { + if (json_tree_find_key(root, "d") != NULL) + kind = DCRYPT_KEY_KIND_PRIVATE; + else + kind = DCRYPT_KEY_KIND_PUBLIC; + } else if (strcmp(value, "EC") == 0) { + if (json_tree_find_key(root, "d") != NULL) + kind = DCRYPT_KEY_KIND_PRIVATE; + else + kind = DCRYPT_KEY_KIND_PUBLIC; + } else { + json_tree_deinit(&tree); + DCRYPT_SET_ERROR("Unsupported JWK key type"); + return FALSE; + } + json_tree_deinit(&tree); + } else { + if (str_begins(key_data, "1:")) { + DCRYPT_SET_ERROR("Dovecot v1 key format uses tab to separate fields"); + return FALSE; + } else if (str_begins(key_data, "2\t")) { + DCRYPT_SET_ERROR("Dovecot v2 key format uses colon to separate fields"); + return FALSE; + } + const char **fields = t_strsplit(key_data, ":\t"); + int nfields = str_array_length(fields); + + if (nfields < 2) { + DCRYPT_SET_ERROR("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 == 4) { + 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 == 7 && 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 { + DCRYPT_SET_ERROR("Invalid dovecot v1 encoding"); + return FALSE; + } + } else if (strcmp(fields[0], "2") == 0) { + version = DCRYPT_KEY_VERSION_2; + 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 == 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 { + DCRYPT_SET_ERROR("Invalid dovecot v2 encoding"); + return FALSE; + } + } else { + DCRYPT_SET_ERROR("Invalid dovecot key version"); + return FALSE; + } + + /* last field is always key hash */ + if (key_hash_r != NULL) + key_hash = i_strdup(fields[nfields-1]); + } + + 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_ref_public_key(struct dcrypt_public_key *key) +{ + i_assert(key != NULL && key->ref > 0); + key->ref++; +} + +static void dcrypt_openssl_ref_private_key(struct dcrypt_private_key *key) +{ + i_assert(key != NULL && key->ref > 0); + key->ref++; +} + +static void dcrypt_openssl_unref_public_key(struct dcrypt_public_key **key) +{ + i_assert(key != NULL); + struct dcrypt_public_key *_key = *key; + if (_key == NULL) + return; + i_assert(_key->ref > 0); + *key = NULL; + if (--_key->ref > 0) return; + EVP_PKEY_free(_key->key); + i_free(_key->key_id); + i_free(_key); +} + +static void dcrypt_openssl_unref_private_key(struct dcrypt_private_key **key) +{ + i_assert(key != NULL); + struct dcrypt_private_key *_key = *key; + if (_key == NULL) + return; + i_assert(_key->ref > 0); + *key = NULL; + if (--_key->ref > 0) return; + EVP_PKEY_free(_key->key); + i_free(_key->key_id); + i_free(_key); +} + +static void dcrypt_openssl_unref_keypair(struct dcrypt_keypair *keypair) +{ + i_assert(keypair != NULL); + dcrypt_openssl_unref_public_key(&keypair->pub); + dcrypt_openssl_unref_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, enum dcrypt_padding padding, + const char **error_r) +{ + i_assert(key != NULL && key->key != NULL); + int ec, pad = dcrypt_openssl_padding_mode(padding, FALSE, error_r); + if (pad == -1) + return FALSE; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key->key, NULL); + size_t outl = EVP_PKEY_size(key->key); + unsigned char buf[outl]; + + if (ctx == NULL || + EVP_PKEY_encrypt_init(ctx) < 1 || + EVP_PKEY_CTX_set_rsa_padding(ctx, pad) < 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, enum dcrypt_padding padding, + const char **error_r) +{ + i_assert(key != NULL && key->key != NULL); + int ec, pad = dcrypt_openssl_padding_mode(padding, FALSE, error_r); + if (pad == -1) + return FALSE; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key->key, NULL); + size_t outl = EVP_PKEY_size(key->key); + unsigned char buf[outl]; + + if (ctx == NULL || + EVP_PKEY_decrypt_init(ctx) < 1 || + EVP_PKEY_CTX_set_rsa_padding(ctx, pad) < 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; + i_assert(oid != NULL); + 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) +{ + i_assert(name != NULL); + ASN1_OBJECT *obj = OBJ_txt2obj(name, 0); + if (obj == NULL) + return dcrypt_openssl_error(error_r); + + size_t len = OBJ_length(obj); + if (len == 0) + { + DCRYPT_SET_ERROR("Object has no OID assigned"); + return FALSE; + } + len = i2d_ASN1_OBJECT(obj, NULL); + unsigned char *bufptr = buffer_append_space_unsafe(oid, len); + i2d_ASN1_OBJECT(obj, &bufptr); + ASN1_OBJECT_free(obj); + if (bufptr != NULL) { + return TRUE; + } + return dcrypt_openssl_error(error_r); +} + +static enum dcrypt_key_type +dcrypt_openssl_private_key_type(struct dcrypt_private_key *key) +{ + i_assert(key != NULL && key->key != NULL); + EVP_PKEY *priv = key->key; + if (EVP_PKEY_base_id(priv) == EVP_PKEY_RSA) return DCRYPT_KEY_RSA; + else if (EVP_PKEY_base_id(priv) == EVP_PKEY_EC) return DCRYPT_KEY_EC; + else i_unreached(); +} + +static enum dcrypt_key_type +dcrypt_openssl_public_key_type(struct dcrypt_public_key *key) +{ + i_assert(key != NULL && key->key != NULL); + EVP_PKEY *pub = key->key; + if (EVP_PKEY_base_id(pub) == EVP_PKEY_RSA) return DCRYPT_KEY_RSA; + else if (EVP_PKEY_base_id(pub) == EVP_PKEY_EC) return DCRYPT_KEY_EC; + else i_unreached(); +} + +/** 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]; + i_assert(key != NULL && key->key != NULL); + EVP_PKEY *pub = key->key; + + if (EVP_PKEY_base_id(pub) != EVP_PKEY_EC) { + DCRYPT_SET_ERROR("Only EC key supported"); + return FALSE; + } + + char *pub_pt_hex = ec_key_get_pub_point_hex(EVP_PKEY_get0_EC_KEY(pub)); + if (pub_pt_hex == NULL) + return dcrypt_openssl_error(error_r); + /* 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; +} + +static bool +dcrypt_openssl_private_key_id_old(struct dcrypt_private_key *key, + buffer_t *result, const char **error_r) +{ + unsigned char buf[SHA256_DIGEST_LENGTH]; + i_assert(key != NULL && key->key != NULL); + EVP_PKEY *priv = key->key; + + if (EVP_PKEY_base_id(priv) != EVP_PKEY_EC) { + DCRYPT_SET_ERROR("Only EC key supported"); + return FALSE; + } + + char *pub_pt_hex = ec_key_get_pub_point_hex(EVP_PKEY_get0_EC_KEY(priv)); + if (pub_pt_hex == NULL) + return dcrypt_openssl_error(error_r); + /* 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_evp(EVP_PKEY *key, + const EVP_MD *md, buffer_t *result, + const char **error_r) +{ + bool res = FALSE; + unsigned char buf[EVP_MD_size(md)], *ptr; + + if (EVP_PKEY_base_id(key) == EVP_PKEY_EC) { + EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(key), + POINT_CONVERSION_COMPRESSED); + } + BIO *b = BIO_new(BIO_s_mem()); + if (b == NULL || i2d_PUBKEY_bio(b, key) < 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 */ + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (ctx == NULL || + EVP_DigestInit_ex(ctx, md, NULL) < 1 || + EVP_DigestUpdate(ctx, (const unsigned char*)ptr, len) < 1 || + EVP_DigestFinal_ex(ctx, buf, &hlen) < 1) { + res = dcrypt_openssl_error(error_r); + } else { + buffer_append(result, buf, hlen); + res = TRUE; + } + EVP_MD_CTX_free(ctx); + BIO_vfree(b); + + return res; +} + +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); + i_assert(key != NULL && key->key != NULL); + EVP_PKEY *pub = key->key; + + if (md == NULL) { + DCRYPT_SET_ERROR(t_strdup_printf("Unknown cipher %s", algorithm)); + return FALSE; + } + + return dcrypt_openssl_public_key_id_evp(pub, md, result, error_r); +} + +static bool +dcrypt_openssl_private_key_id(struct dcrypt_private_key *key, + const char *algorithm, buffer_t *result, + const char **error_r) +{ + const EVP_MD *md = EVP_get_digestbyname(algorithm); + i_assert(key != NULL && key->key != NULL); + EVP_PKEY *priv = key->key; + + if (md == NULL) { + DCRYPT_SET_ERROR(t_strdup_printf("Unknown cipher %s", algorithm)); + return FALSE; + } + + return dcrypt_openssl_public_key_id_evp(priv, md, result, error_r); +} + +static bool +dcrypt_openssl_digest(const char *algorithm, const void *data, size_t data_len, + buffer_t *digest_r, const char **error_r) +{ + bool ret; + EVP_MD_CTX *mdctx; + const EVP_MD *md = EVP_get_digestbyname(algorithm); + if (md == NULL) + return dcrypt_openssl_error(error_r); + unsigned int md_size = EVP_MD_size(md); + if ((mdctx = EVP_MD_CTX_create()) == NULL) + return dcrypt_openssl_error(error_r); + unsigned char *buf = buffer_append_space_unsafe(digest_r, md_size); + if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1 || + EVP_DigestUpdate(mdctx, data, data_len) != 1 || + EVP_DigestFinal_ex(mdctx, buf, &md_size) != 1) { + ret = dcrypt_openssl_error(error_r); + } else { + ret = TRUE; + } + EVP_MD_CTX_free(mdctx); + return ret; +} + +#ifndef HAVE_ECDSA_SIG_GET0 +static void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) +{ + i_assert(sig != NULL); + *pr = sig->r; + *ps = sig->s; +} +#endif +#ifndef HAVE_ECDSA_SIG_SET0 +static int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) +{ + if (sig == NULL || r == NULL || s == NULL) { + ECDSAerr(0, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + BN_free(sig->r); + sig->r = r; + BN_free(sig->s); + sig->s = s; + + return 1; +} +#endif + +static bool +dcrypt_openssl_sign_ecdsa(struct dcrypt_private_key *key, const char *algorithm, + const void *data, size_t data_len, buffer_t *signature_r, + const char **error_r) +{ + EVP_PKEY *pkey = key->key; + EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); + bool ret; + int rs_len = EC_GROUP_order_bits(EC_KEY_get0_group(ec_key)) / 8; + + /* digest data */ + buffer_t *digest = t_buffer_create(64); + if (!dcrypt_openssl_digest(algorithm, data, data_len, digest, error_r)) + return FALSE; + + /* sign data */ + ECDSA_SIG *ec_sig; + if ((ec_sig = ECDSA_do_sign(digest->data, digest->used, ec_key)) == NULL) + return dcrypt_openssl_error(error_r); + + /* export signature */ + const BIGNUM *r; + const BIGNUM *s; + + ECDSA_SIG_get0(ec_sig, &r, &s); + + int r_len = BN_num_bytes(r); + i_assert(rs_len >= r_len); + + /* write r */ + unsigned char *buf = buffer_append_space_unsafe(signature_r, rs_len); + if (BN_bn2bin(r, buf + (rs_len - r_len)) != r_len) { + ret = dcrypt_openssl_error(error_r); + } else { + buf = buffer_append_space_unsafe(signature_r, rs_len); + int s_len = BN_num_bytes(s); + i_assert(rs_len >= s_len); + if (BN_bn2bin(s, buf + (rs_len - s_len)) != s_len) { + ret = dcrypt_openssl_error(error_r); + } else { + ret = TRUE; + } + } + + ECDSA_SIG_free(ec_sig); + + return ret; +} + +static bool +dcrypt_openssl_sign(struct dcrypt_private_key *key, const char *algorithm, + enum dcrypt_signature_format format, + const void *data, size_t data_len, buffer_t *signature_r, + enum dcrypt_padding padding, const char **error_r) +{ + switch (format) { + case DCRYPT_SIGNATURE_FORMAT_DSS: + break; + case DCRYPT_SIGNATURE_FORMAT_X962: + if (EVP_PKEY_base_id(key->key) == EVP_PKEY_RSA) { + DCRYPT_SET_ERROR("Format does not support RSA"); + return FALSE; + } + return dcrypt_openssl_sign_ecdsa(key, algorithm, + data, data_len, signature_r, error_r); + default: + i_unreached(); + } + + EVP_PKEY_CTX *pctx = NULL; + EVP_MD_CTX *dctx; + bool ret; + const EVP_MD *md = EVP_get_digestbyname(algorithm); + size_t siglen; + int pad = dcrypt_openssl_padding_mode(padding, TRUE, error_r); + + if (pad == -1) + return FALSE; + + if (md == NULL) { + DCRYPT_SET_ERROR(t_strdup_printf("Unknown digest %s", algorithm)); + return FALSE; + } + + dctx = EVP_MD_CTX_create(); + + /* NB! Padding is set only on RSA signatures + ECDSA signatures use whatever is default */ + if (EVP_DigestSignInit(dctx, &pctx, md, NULL, key->key) != 1 || + (EVP_PKEY_base_id(key->key) == EVP_PKEY_RSA && + EVP_PKEY_CTX_set_rsa_padding(pctx, pad) != 1) || + EVP_DigestSignUpdate(dctx, data, data_len) != 1 || + EVP_DigestSignFinal(dctx, NULL, &siglen) != 1) { + ret = dcrypt_openssl_error(error_r); + } else { + i_assert(siglen > 0); + /* @UNSAFE */ + unsigned char *buf = + buffer_append_space_unsafe(signature_r, siglen); + if (EVP_DigestSignFinal(dctx, buf, &siglen) != 1) { + ret = dcrypt_openssl_error(error_r); + } else { + buffer_set_used_size(signature_r, siglen); + ret = TRUE; + } + } + + EVP_MD_CTX_destroy(dctx); + + return ret; +} + +static bool +dcrypt_openssl_verify_ecdsa(struct dcrypt_public_key *key, const char *algorithm, + const void *data, size_t data_len, + const unsigned char *signature, size_t signature_len, + bool *valid_r, const char **error_r) +{ + if ((signature_len % 2) != 0) { + DCRYPT_SET_ERROR("Truncated signature"); + return FALSE; + } + + EVP_PKEY *pkey = key->key; + EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); + int ec; + + /* digest data */ + buffer_t *digest = t_buffer_create(64); + if (!dcrypt_openssl_digest(algorithm, data, data_len, digest, error_r)) + return FALSE; + + BIGNUM *r = BN_new(); + BIGNUM *s = BN_new(); + /* attempt to decode BIGNUMs */ + if (BN_bin2bn(signature, signature_len / 2, r) == NULL) { + BN_free(r); + BN_free(s); + return dcrypt_openssl_error(error_r); + } + /* then next */ + if (BN_bin2bn(CONST_PTR_OFFSET(signature, signature_len / 2), + signature_len / 2, s) == NULL) { + BN_free(r); + BN_free(s); + return dcrypt_openssl_error(error_r); + } + + /* reconstruct signature */ + ECDSA_SIG *ec_sig = ECDSA_SIG_new(); + ECDSA_SIG_set0(ec_sig, r, s); + + /* verify it */ + ec = ECDSA_do_verify(digest->data, digest->used, ec_sig, ec_key); + ECDSA_SIG_free(ec_sig); + + if (ec == 1) { + *valid_r = TRUE; + } else if (ec == 0) { + *valid_r = FALSE; + } else { + return dcrypt_openssl_error(error_r); + } + return TRUE; +} + +static bool +dcrypt_openssl_verify(struct dcrypt_public_key *key, const char *algorithm, + enum dcrypt_signature_format format, + const void *data, size_t data_len, + const unsigned char *signature, size_t signature_len, + bool *valid_r, enum dcrypt_padding padding, + const char **error_r) +{ + switch (format) { + case DCRYPT_SIGNATURE_FORMAT_DSS: + break; + case DCRYPT_SIGNATURE_FORMAT_X962: + if (EVP_PKEY_base_id(key->key) == EVP_PKEY_RSA) { + DCRYPT_SET_ERROR("Format does not support RSA"); + return FALSE; + } + return dcrypt_openssl_verify_ecdsa(key, algorithm, + data, data_len, signature, signature_len, + valid_r, error_r); + default: + i_unreached(); + } + + EVP_PKEY_CTX *pctx = NULL; + EVP_MD_CTX *dctx; + bool ret; + const EVP_MD *md = EVP_get_digestbyname(algorithm); + int rc, pad = dcrypt_openssl_padding_mode(padding, TRUE, error_r); + + if (pad == -1) + return FALSE; + + if (md == NULL) { + DCRYPT_SET_ERROR(t_strdup_printf("Unknown digest %s", algorithm)); + return FALSE; + } + + dctx = EVP_MD_CTX_create(); + + /* NB! Padding is set only on RSA signatures + ECDSA signatures use whatever is default */ + if (EVP_DigestVerifyInit(dctx, &pctx, md, NULL, key->key) != 1 || + (EVP_PKEY_base_id(key->key) == EVP_PKEY_RSA && + EVP_PKEY_CTX_set_rsa_padding(pctx, pad) != 1) || + EVP_DigestVerifyUpdate(dctx, data, data_len) != 1 || + (rc = EVP_DigestVerifyFinal(dctx, signature, signature_len)) < 0) { + ret = dcrypt_openssl_error(error_r); + } else { + /* return code 1 means valid signature, otherwise invalid */ + *valid_r = (rc == 1); + ret = TRUE; + } + + EVP_MD_CTX_destroy(dctx); + + return ret; +} + +static bool +dcrypt_openssl_key_store_private_raw(struct dcrypt_private_key *key, + pool_t pool, + enum dcrypt_key_type *type_r, + ARRAY_TYPE(dcrypt_raw_key) *keys_r, + const char **error_r) +{ + i_assert(key != NULL && key->key != NULL); + i_assert(array_is_created(keys_r)); + EVP_PKEY *priv = key->key; + ARRAY_TYPE(dcrypt_raw_key) keys; + t_array_init(&keys, 2); + + if (EVP_PKEY_base_id(priv) == EVP_PKEY_RSA) { + DCRYPT_SET_ERROR("Not implemented"); + return FALSE; + } else if (EVP_PKEY_base_id(priv) == EVP_PKEY_EC) { + /* store OID */ + EC_KEY *key = EVP_PKEY_get0_EC_KEY(priv); + EC_KEY_set_conv_form(key, POINT_CONVERSION_UNCOMPRESSED); + int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(key)); + ASN1_OBJECT *obj = OBJ_nid2obj(nid); + int len = OBJ_length(obj); + if (len == 0) { + DCRYPT_SET_ERROR("Object has no OID assigned"); + return FALSE; + } + len = i2d_ASN1_OBJECT(obj, NULL); + unsigned char *bufptr = p_malloc(pool, len); + struct dcrypt_raw_key *item = array_append_space(&keys); + item->parameter = bufptr; + item->len = i2d_ASN1_OBJECT(obj, &bufptr); + ASN1_OBJECT_free(obj); + /* store private key */ + const BIGNUM *b = EC_KEY_get0_private_key(key); + len = BN_num_bytes(b); + item = array_append_space(&keys); + bufptr = p_malloc(pool, len); + if (BN_bn2bin(b, bufptr) < len) + return dcrypt_openssl_error(error_r); + item->parameter = bufptr; + item->len = len; + *type_r = DCRYPT_KEY_EC; + } else { + DCRYPT_SET_ERROR("Key type unsupported"); + return FALSE; + } + + array_append_array(keys_r, &keys); + return TRUE; +} + +static bool +dcrypt_openssl_key_store_public_raw(struct dcrypt_public_key *key, + pool_t pool, + enum dcrypt_key_type *type_r, + ARRAY_TYPE(dcrypt_raw_key) *keys_r, + const char **error_r) +{ + i_assert(key != NULL && key->key != NULL); + EVP_PKEY *pub = key->key; + ARRAY_TYPE(dcrypt_raw_key) keys; + t_array_init(&keys, 2); + + if (EVP_PKEY_base_id(pub) == EVP_PKEY_RSA) { + DCRYPT_SET_ERROR("Not implemented"); + return FALSE; + } else if (EVP_PKEY_base_id(pub) == EVP_PKEY_EC) { + /* store OID */ + EC_KEY *key = EVP_PKEY_get0_EC_KEY(pub); + EC_KEY_set_conv_form(key, POINT_CONVERSION_UNCOMPRESSED); + int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(key)); + ASN1_OBJECT *obj = OBJ_nid2obj(nid); + int len = OBJ_length(obj); + if (len == 0) { + DCRYPT_SET_ERROR("Object has no OID assigned"); + return FALSE; + } + len = i2d_ASN1_OBJECT(obj, NULL); + unsigned char *bufptr = p_malloc(pool, len); + struct dcrypt_raw_key *item = array_append_space(&keys); + item->parameter = bufptr; + item->len = i2d_ASN1_OBJECT(obj, &bufptr); + ASN1_OBJECT_free(obj); + + /* store public key */ + const EC_POINT *point = EC_KEY_get0_public_key(key); + len = EC_POINT_point2oct(EC_KEY_get0_group(key), point, + POINT_CONVERSION_UNCOMPRESSED, + NULL, 0, NULL); + bufptr = p_malloc(pool, len); + item = array_append_space(&keys); + item->parameter = bufptr; + item->len = len; + if (EC_POINT_point2oct(EC_KEY_get0_group(key), point, + POINT_CONVERSION_UNCOMPRESSED, + bufptr, len, NULL) < (unsigned int)len) + return dcrypt_openssl_error(error_r); + *type_r = DCRYPT_KEY_EC; + } else { + DCRYPT_SET_ERROR("Key type unsupported"); + return FALSE; + } + + array_append_array(keys_r, &keys); + + return TRUE; +} + +static bool +dcrypt_openssl_key_load_private_raw(struct dcrypt_private_key **key_r, + enum dcrypt_key_type type, + const ARRAY_TYPE(dcrypt_raw_key) *keys, + const char **error_r) +{ + int ec; + i_assert(keys != NULL && array_is_created(keys) && array_count(keys) > 1); + const struct dcrypt_raw_key *item; + + if (type == DCRYPT_KEY_RSA) { + DCRYPT_SET_ERROR("Not implemented"); + return FALSE; + } else if (type == DCRYPT_KEY_EC) { + /* get curve */ + if (array_count(keys) < 2) { + DCRYPT_SET_ERROR("Invalid parameters"); + return FALSE; + } + item = array_idx(keys, 0); + const unsigned char *oid = item->parameter; + ASN1_OBJECT *obj = d2i_ASN1_OBJECT(NULL, &oid, item->len); + if (obj == NULL) + return dcrypt_openssl_error(error_r); + int nid = OBJ_obj2nid(obj); + ASN1_OBJECT_free(obj); + + /* load private point */ + item = array_idx(keys, 1); + BIGNUM *bn = BN_secure_new(); + if (BN_bin2bn(item->parameter, item->len, bn) == NULL) { + BN_free(bn); + return dcrypt_openssl_error(error_r); + } + + /* setup a key */ + EC_KEY *key = EC_KEY_new_by_curve_name(nid); + ec = EC_KEY_set_private_key(key, bn); + BN_free(bn); + + if (ec != 1) { + EC_KEY_free(key); + return dcrypt_openssl_error(error_r); + } + + /* calculate & assign public key */ + EC_POINT *pub = EC_POINT_new(EC_KEY_get0_group(key)); + if (pub == NULL) { + EC_KEY_free(key); + return dcrypt_openssl_error(error_r); + } + /* calculate public key */ + ec = EC_POINT_mul(EC_KEY_get0_group(key), pub, + EC_KEY_get0_private_key(key), + NULL, NULL, NULL); + if (ec == 1) + ec = EC_KEY_set_public_key(key, pub); + EC_POINT_free(pub); + + /* check the key */ + if (ec != 1 || EC_KEY_check_key(key) != 1) { + EC_KEY_free(key); + return dcrypt_openssl_error(error_r); + } + EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE); + + EVP_PKEY *pkey = EVP_PKEY_new(); + EVP_PKEY_set1_EC_KEY(pkey, key); + EC_KEY_free(key); + *key_r = i_new(struct dcrypt_private_key, 1); + (*key_r)->key = pkey; + (*key_r)->ref++; + return TRUE; + } else { + DCRYPT_SET_ERROR("Key type unsupported"); + } + + return FALSE; +} + +static bool +dcrypt_openssl_key_load_public_raw(struct dcrypt_public_key **key_r, + enum dcrypt_key_type type, + const ARRAY_TYPE(dcrypt_raw_key) *keys, + const char **error_r) +{ + int ec; + i_assert(keys != NULL && array_is_created(keys) && array_count(keys) > 1); + const struct dcrypt_raw_key *item; + + if (type == DCRYPT_KEY_RSA) { + DCRYPT_SET_ERROR("Not implemented"); + return FALSE; + } else if (type == DCRYPT_KEY_EC) { + /* get curve */ + if (array_count(keys) < 2) { + DCRYPT_SET_ERROR("Invalid parameters"); + return FALSE; + } + item = array_idx(keys, 0); + const unsigned char *oid = item->parameter; + ASN1_OBJECT *obj = d2i_ASN1_OBJECT(NULL, &oid, item->len); + if (obj == NULL) { + dcrypt_openssl_error(error_r); + return FALSE; + } + int nid = OBJ_obj2nid(obj); + ASN1_OBJECT_free(obj); + + /* set group */ + EC_GROUP *group = EC_GROUP_new_by_curve_name(nid); + if (group == NULL) { + dcrypt_openssl_error(error_r); + return FALSE; + } + + /* load point */ + item = array_idx(keys, 1); + EC_POINT *point = EC_POINT_new(group); + if (EC_POINT_oct2point(group, point, item->parameter, + item->len, NULL) != 1) { + EC_POINT_free(point); + EC_GROUP_free(group); + return dcrypt_openssl_error(error_r); + } + + EC_KEY *key = EC_KEY_new(); + ec = EC_KEY_set_group(key, group); + if (ec == 1) + ec = EC_KEY_set_public_key(key, point); + EC_POINT_free(point); + EC_GROUP_free(group); + + if (ec != 1 || EC_KEY_check_key(key) != 1) { + EC_KEY_free(key); + return dcrypt_openssl_error(error_r); + } + + EC_KEY_precompute_mult(key, NULL); + EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE); + EVP_PKEY *pkey = EVP_PKEY_new(); + EVP_PKEY_set1_EC_KEY(pkey, key); + EC_KEY_free(key); + *key_r = i_new(struct dcrypt_public_key, 1); + (*key_r)->key = pkey; + (*key_r)->ref++; + return TRUE; + } else { + DCRYPT_SET_ERROR("Key type unsupported"); + } + + return FALSE; +} + +static bool +dcrypt_openssl_key_get_curve_public(struct dcrypt_public_key *key, + const char **curve_r, const char **error_r) +{ + EVP_PKEY *pkey = key->key; + char objtxt[OID_TEXT_MAX_LEN]; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { + DCRYPT_SET_ERROR("Unsupported key type"); + return FALSE; + } + + ASN1_OBJECT *obj = OBJ_nid2obj(EC_GROUP_get_curve_name( + EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey)))); + + int len = OBJ_obj2txt(objtxt, sizeof(objtxt), obj, 1); + ASN1_OBJECT_free(obj); + + if (len < 1) { + return dcrypt_openssl_error(error_r); + } else if ((unsigned int)len > sizeof(objtxt)) { + DCRYPT_SET_ERROR("Object name too long"); + return FALSE; + } + + *curve_r = t_strndup(objtxt, len); + return TRUE; +} + +static const char * +dcrypt_openssl_key_get_id_public(struct dcrypt_public_key *key) +{ + return key->key_id; +} + +static const char * +dcrypt_openssl_key_get_id_private(struct dcrypt_private_key *key) +{ + return key->key_id; +} + +static void +dcrypt_openssl_key_set_id_public(struct dcrypt_public_key *key, const char *id) +{ + i_free(key->key_id); + key->key_id = i_strdup_empty(id); +} + +static void +dcrypt_openssl_key_set_id_private(struct dcrypt_private_key *key, const char *id) +{ + i_free(key->key_id); + key->key_id = i_strdup_empty(id); +} + +static enum dcrypt_key_usage +dcrypt_openssl_key_get_usage_public(struct dcrypt_public_key *key) +{ + return key->usage; +} + +static enum dcrypt_key_usage +dcrypt_openssl_key_get_usage_private(struct dcrypt_private_key *key) +{ + return key->usage; +} + +static void +dcrypt_openssl_key_set_usage_public(struct dcrypt_public_key *key, + enum dcrypt_key_usage usage) +{ + key->usage = usage; +} + +static void +dcrypt_openssl_key_set_usage_private(struct dcrypt_private_key *key, + enum dcrypt_key_usage usage) +{ + key->usage = usage; +} + + +static struct dcrypt_vfs dcrypt_openssl_vfs = { + .initialize = dcrypt_openssl_initialize, + .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, + .unref_keypair = dcrypt_openssl_unref_keypair, + .unref_public_key = dcrypt_openssl_unref_public_key, + .unref_private_key = dcrypt_openssl_unref_private_key, + .ref_public_key = dcrypt_openssl_ref_public_key, + .ref_private_key = dcrypt_openssl_ref_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, + .private_key_id = dcrypt_openssl_private_key_id, + .private_key_id_old = dcrypt_openssl_private_key_id_old, + .key_store_private_raw = dcrypt_openssl_key_store_private_raw, + .key_store_public_raw = dcrypt_openssl_key_store_public_raw, + .key_load_private_raw = dcrypt_openssl_key_load_private_raw, + .key_load_public_raw = dcrypt_openssl_key_load_public_raw, + .key_get_curve_public = dcrypt_openssl_key_get_curve_public, + .key_get_id_public = dcrypt_openssl_key_get_id_public, + .key_get_id_private = dcrypt_openssl_key_get_id_private, + .key_set_id_public = dcrypt_openssl_key_set_id_public, + .key_set_id_private = dcrypt_openssl_key_set_id_private, + .key_get_usage_public = dcrypt_openssl_key_get_usage_public, + .key_get_usage_private = dcrypt_openssl_key_get_usage_private, + .key_set_usage_public = dcrypt_openssl_key_set_usage_public, + .key_set_usage_private = dcrypt_openssl_key_set_usage_private, + .sign = dcrypt_openssl_sign, + .verify = dcrypt_openssl_verify, + .ecdh_derive_secret = dcrypt_openssl_ecdh_derive_secret, +}; + +void dcrypt_openssl_init(struct module *module ATTR_UNUSED) +{ + dovecot_openssl_common_global_ref(); + dcrypt_set_vfs(&dcrypt_openssl_vfs); +} + +void dcrypt_openssl_deinit(void) +{ + dovecot_openssl_common_global_unref(); +} |