var-expand-crypt-plugin.c revision 9e38412ec12ae8dc4f67ade5b18c8b976de59bcb
/* 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);
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 (%"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;
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;
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:
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;
}
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();
}