Mercurial > dovecot > core-2.2
changeset 21861:e57b7745e081
var-expand-crypt: Encryption/decryption support for var-expand
Registers new encrypt and decrypt processors for
var-expand.
author | Aki Tuomi <aki.tuomi@dovecot.fi> |
---|---|
date | Fri, 18 Nov 2016 14:47:05 +0200 |
parents | f7cfff192280 |
children | 026af538e3dd |
files | configure.ac src/plugins/Makefile.am src/plugins/var-expand-crypt/Makefile.am src/plugins/var-expand-crypt/var-expand-crypt-plugin.c |
diffstat | 4 files changed, 370 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.ac Sun Dec 25 10:36:59 2016 +0200 +++ b/configure.ac Fri Nov 18 14:47:05 2016 +0200 @@ -3085,6 +3085,7 @@ src/plugins/zlib/Makefile src/plugins/imap-zlib/Makefile src/plugins/mail-crypt/Makefile +src/plugins/var-expand-crypt/Makefile stamp.h dovecot-config.in])
--- a/src/plugins/Makefile.am Sun Dec 25 10:36:59 2016 +0200 +++ b/src/plugins/Makefile.am Fri Nov 18 14:47:05 2016 +0200 @@ -45,4 +45,5 @@ $(FTS_LUCENE) \ $(FTS_SOLR) \ $(DICT_LDAP) \ - fs-compress + fs-compress \ + var-expand-crypt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/var-expand-crypt/Makefile.am Fri Nov 18 14:47:05 2016 +0200 @@ -0,0 +1,20 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dcrypt + +NOPLUGIN_LDFLAGS = +lib20_var_expand_crypt_la_LDFLAGS = -module -avoid-version + +auth_moduledir = $(moduledir)/auth + +module_LTLIBRARIES = \ + lib20_var_expand_crypt.la + +auth_module_LTLIBRARIES = \ + lib20_auth_var_expand_crypt.la + +lib20_auth_var_expand_crypt_la_SOURCES = \ + var-expand-crypt-plugin.c + +lib20_var_expand_crypt_la_SOURCES = \ + var-expand-crypt-plugin.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c Fri Nov 18 14:47:05 2016 +0200 @@ -0,0 +1,347 @@ +/* Copyright (c) 2003-2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hex-binary.h" +#include "base64.h" +#include "str.h" +#include "strescape.h" +#include "var-expand.h" +#include "var-expand-private.h" +#include "dcrypt.h" + +#define VAR_EXPAND_CRYPT_DEFAULT_ALGO "AES-256-CBC" + +struct module; + +enum crypt_field_format { + FORMAT_HEX, + FORMAT_BASE64 +}; + +struct var_expand_crypt_context { + struct var_expand_context *ctx; + const char *algo; + string_t *iv; + string_t *enckey; + enum crypt_field_format format; + bool enc_result_only:1; +}; + +static void var_expand_crypt_initialize(void); + +void var_expand_crypt_init(struct module *module); +void var_expand_crypt_deinit(void); +void auth_var_expand_crypt_init(struct module *module); +void auth_var_expand_crypt_deinit(void); + +static bool has_been_init; + +static int +var_expand_crypt_settings(struct var_expand_crypt_context *ctx, + const char *const *args, const char **error_r) +{ + while(args != NULL && *args != NULL) { + const char *k = t_strcut(*args, '='); + const char *value = strchr(*args, '='); + if (value == NULL) { + args++; + continue; + } else { + value++; + } + + if (strcmp(k, "iv") == 0) { + str_truncate(ctx->iv, 0); + var_expand_with_funcs(ctx->iv, value, ctx->ctx->table, + ctx->ctx->func_table, + ctx->ctx->context); + const char *hexiv = t_strdup(str_c(ctx->iv)); + /* try to decode IV */ + str_truncate(ctx->iv, 0); + hex_to_binary(hexiv, ctx->iv); + if (str_len(ctx->iv) == 0) { + *error_r = "iv is not valid hex encoded value"; + return -1; + } + } if (strcmp(k, "noiv") == 0) { + ctx->enc_result_only = strcasecmp(value, "yes")==0; + } if (strcmp(k, "algo") == 0) { + ctx->algo = value; + } else if (strcmp(k, "key") == 0) { + str_truncate(ctx->enckey, 0); + var_expand_with_funcs(ctx->enckey, value, + ctx->ctx->table, + ctx->ctx->func_table, + ctx->ctx->context); + const char *hexkey = t_strdup(str_c(ctx->enckey)); + str_truncate(ctx->enckey, 0); + hex_to_binary(hexkey, ctx->enckey); + if (str_len(ctx->enckey) == 0) { + *error_r = "key is not valid hex encoded value"; + return -1; + } + } else if (strcmp(k, "format") == 0) { + if (strcmp(value, "hex") == 0) { + ctx->format = FORMAT_HEX; + } else if (strcmp(value, "base64") == 0) { + ctx->format = FORMAT_BASE64; + } else { + *error_r = t_strdup_printf( + "Cannot parse hash arguments:" + "'%s' is not supported format", + value); + return -1; + } + } + args++; + } + + if (ctx->algo == NULL) { + ctx->algo = "AES-256-CBC"; + } + + return 0; +} + +static int +var_expand_crypt(struct dcrypt_context_symmetric *dctx, buffer_t *key, buffer_t *iv, + const buffer_t *input, buffer_t *output, const char **error_r) +{ + /* make sure IV is correct */ + if (iv->used == 0) { + dcrypt_ctx_sym_set_key_iv_random(dctx); + /* acquire IV */ + dcrypt_ctx_sym_get_iv(dctx, iv); + } else if (dcrypt_ctx_sym_get_iv_length(dctx) != iv->used) { + *error_r = t_strdup_printf("crypt: IV length invalid (%"PRIuSIZE_T" != %u)", + iv->used, + dcrypt_ctx_sym_get_iv_length(dctx)); + return -1; + } else { + dcrypt_ctx_sym_set_iv(dctx, iv->data, iv->used); + } + + if (dcrypt_ctx_sym_get_key_length(dctx) != key->used) { + *error_r = t_strdup_printf("crypt: Key length invalid (%"PRIuSIZE_T" != %u)", + key->used, + dcrypt_ctx_sym_get_key_length(dctx)); + return -1; + } else { + dcrypt_ctx_sym_set_key(dctx, key->data, key->used); + } + + if (!dcrypt_ctx_sym_init(dctx, error_r) || + !dcrypt_ctx_sym_update(dctx, input->data, + input->used, output, error_r) || + !dcrypt_ctx_sym_final(dctx, output, error_r)) + return -1; + return 0; +} + +static int +var_expand_encrypt(struct var_expand_context *_ctx, + const char *key, const char *field, + const char **result_r, const char **error_r) +{ + if (!has_been_init) + var_expand_crypt_initialize(); + + const char *p = strchr(key, ';'); + const char *const *args = NULL; + const char *value; + struct var_expand_crypt_context ctx; + int ret = 0; + + memset(&ctx, 0, sizeof(ctx)); + ctx.ctx = _ctx; + ctx.format = FORMAT_HEX; + + if (p != NULL) { + args = t_strsplit(p+1, ","); + } + + string_t *field_value = t_str_new(64); + ctx.iv = t_str_new(64); + ctx.enckey = t_str_new(64); + string_t *tmp = t_str_new(128); + + if ((ret = var_expand_long(_ctx, field, strlen(field), + &value, error_r)) < 1) { + return ret; + } + + if (*value == '\0') { + *result_r = value; + return ret; + } + + if (var_expand_crypt_settings(&ctx, args, error_r) < 0) + return -1; + + ret = 0; + + str_append(field_value, value); + + struct dcrypt_context_symmetric *dctx; + if (!dcrypt_ctx_sym_create(ctx.algo, DCRYPT_MODE_ENCRYPT, &dctx, error_r)) + return -1; + + ret = var_expand_crypt(dctx, ctx.enckey, ctx.iv, field_value, tmp, error_r); + dcrypt_ctx_sym_destroy(&dctx); + + if (ret == 0) { + /* makes compiler happy */ + const char *enciv = ""; + const char *res = ""; + + switch(ctx.format) { + case FORMAT_HEX: + enciv = binary_to_hex(ctx.iv->data, ctx.iv->used); + res = binary_to_hex(tmp->data, tmp->used); + break; + case FORMAT_BASE64: { + string_t *dest = t_str_new(32); + base64_encode(ctx.iv->data, ctx.iv->used, dest); + enciv = str_c(dest); + dest = t_str_new(32); + base64_encode(tmp->data, tmp->used, dest); + res = str_c(dest); + } + default: + i_unreached(); + } + if (ctx.enc_result_only) + *result_r = t_strdup(res); + else + *result_r = t_strdup_printf("%s$%s$", enciv, res); + ret = 1; + } + + dcrypt_ctx_sym_destroy(&dctx); + + return ret; +} + +static int +var_expand_decrypt(struct var_expand_context *_ctx, + const char *key, const char *field, + const char **result_r, const char **error_r) +{ + if (!has_been_init) + var_expand_crypt_initialize(); + + const char *p = strchr(key, ';'); + const char *const *args = NULL; + const char *value; + struct var_expand_crypt_context ctx; + int ret = 0; + + memset(&ctx, 0, sizeof(ctx)); + ctx.ctx = _ctx; + ctx.format = FORMAT_HEX; + + if (p != NULL) { + args = t_strsplit(p+1, ","); + } + + string_t *field_value = t_str_new(64); + ctx.iv = t_str_new(64); + ctx.enckey = t_str_new(64); + string_t *tmp = t_str_new(128); + + if ((ret = var_expand_long(_ctx, field, strlen(field), + &value, error_r)) < 1) { + return ret; + } + + if (*value == '\0') { + *result_r = value; + return ret; + } + + if (var_expand_crypt_settings(&ctx, args, error_r) < 0) + return -1; + + str_append(field_value, value); + + const char *encdata = str_c(field_value); + const char *enciv = NULL; + + /* make sure IV is correct */ + if (ctx.iv->used == 0 && (p = strchr(encdata, '$')) != NULL) { + /* see if IV can be taken from data */ + enciv = t_strcut(encdata, '$'); + encdata = t_strcut(p+1,'$'); + } else { + encdata = t_strdup(str_c(field_value)); + } + + str_truncate(field_value, 0); + + /* try to decode iv and encdata */ + switch(ctx.format) { + case FORMAT_HEX: + if (ctx.iv->used == 0) + hex_to_binary(enciv, ctx.iv); + hex_to_binary(encdata, field_value); + break; + case FORMAT_BASE64: + if (ctx.iv->used == 0) + str_append_str(ctx.iv, t_base64_decode_str(enciv)); + str_append_str(field_value, t_base64_decode_str(encdata)); + break; + } + + if (ctx.iv->used == 0) { + *error_r = t_strdup_printf("decrypt: IV missing"); + return -1; + } + + struct dcrypt_context_symmetric *dctx; + if (!dcrypt_ctx_sym_create(ctx.algo, DCRYPT_MODE_DECRYPT, &dctx, error_r)) + return -1; + ret = var_expand_crypt(dctx, ctx.enckey, ctx.iv, field_value, tmp, error_r); + dcrypt_ctx_sym_destroy(&dctx); + + if (ret == 0) + *result_r = str_c(tmp); + + return ret; +} + +static const struct var_expand_extension_func_table funcs[] = { + { "encrypt", var_expand_encrypt }, + { "decrypt", var_expand_decrypt }, + { NULL, NULL, } +}; + +static void var_expand_crypt_initialize(void) +{ + dcrypt_initialize(NULL, NULL, NULL); +} + +void var_expand_crypt_init(struct module *module ATTR_UNUSED) +{ + var_expand_register_func_array(funcs); + /* do not initialize dcrypt here - saves alot of memory + to not load openssl every time. Only load it if + needed */ +} + +void var_expand_crypt_deinit(void) +{ + var_expand_unregister_func_array(funcs); + if (has_been_init) + dcrypt_deinitialize(); +} + +void auth_var_expand_crypt_init(struct module *module) +{ + var_expand_crypt_init(module); +} + +void auth_var_expand_crypt_deinit(void) +{ + var_expand_crypt_deinit(); +}