]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
var-expand-crypt: Encryption/decryption support for var-expand
authorAki Tuomi <aki.tuomi@dovecot.fi>
Fri, 18 Nov 2016 12:47:05 +0000 (14:47 +0200)
committerAki Tuomi <aki.tuomi@dovecot.fi>
Fri, 31 Mar 2017 11:08:45 +0000 (14:08 +0300)
Registers new encrypt and decrypt processors for
var-expand.

configure.ac
src/plugins/Makefile.am
src/plugins/var-expand-crypt/Makefile.am [new file with mode: 0644]
src/plugins/var-expand-crypt/var-expand-crypt-plugin.c [new file with mode: 0644]

index a4c444726e03ca1eacac216fcacf2eea45708ccf..055acd5d863f114ad4c8b927d00f4d07d9e7fc11 100644 (file)
@@ -3085,6 +3085,7 @@ src/plugins/welcome/Makefile
 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])
 
index 86917a11b483683b417724dc249bc3813b126355..4565163f714646b483412e05c91b52da22161c44 100644 (file)
@@ -45,4 +45,5 @@ SUBDIRS = \
        $(FTS_LUCENE) \
        $(FTS_SOLR) \
        $(DICT_LDAP) \
-       fs-compress
+       fs-compress \
+       var-expand-crypt
diff --git a/src/plugins/var-expand-crypt/Makefile.am b/src/plugins/var-expand-crypt/Makefile.am
new file mode 100644 (file)
index 0000000..2217779
--- /dev/null
@@ -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
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 (file)
index 0000000..5260110
--- /dev/null
@@ -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();
+}