/* 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(); }