diff options
Diffstat (limited to 'src/plugins/var-expand-crypt/var-expand-crypt-plugin.c')
-rw-r--r-- | src/plugins/var-expand-crypt/var-expand-crypt-plugin.c | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c new file mode 100644 index 0000000..1f6cce7 --- /dev/null +++ b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c @@ -0,0 +1,335 @@ +/* Copyright (c) 2003-2018 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 bool var_expand_crypt_initialize(const char **error_r); + +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 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); + if (var_expand_with_funcs(ctx->iv, value, ctx->ctx->table, + ctx->ctx->func_table, + ctx->ctx->context, error_r) < 0) { + return -1; + } + 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 (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); + if (var_expand_with_funcs(ctx->enckey, value, + ctx->ctx->table, + ctx->ctx->func_table, + ctx->ctx->context, + error_r) < 0) { + return -1; + } + const char *hexkey = t_strdup(str_c(ctx->enckey)); + str_truncate(ctx->enckey, 0); + hex_to_binary(hexkey, ctx->enckey); + } 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 (%zu != %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 (%zu != %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 (!var_expand_crypt_initialize(error_r)) + return -1; + + const char *p = strchr(key, ';'); + const char *const *args = NULL; + const char *value; + struct var_expand_crypt_context ctx; + string_t *dest; + 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); + + 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: + 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); + break; + 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; + } + + 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 (!var_expand_crypt_initialize(error_r)) + return -1; + + 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; + + const char *encdata = value; + const char *enciv = ""; + + /* 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,'$'); + } + + 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); + ret = 1; + } + + return ret; +} + +static const struct var_expand_extension_func_table funcs[] = { + { "encrypt", var_expand_encrypt }, + { "decrypt", var_expand_decrypt }, + { NULL, NULL, } +}; + +static bool var_expand_crypt_initialize(const char **error_r) +{ + return dcrypt_initialize(NULL, NULL, error_r); +} + +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); +} + +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(); +} |