]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
var-expand-crypt: Support key material generation with key and salt
authorAki Tuomi <aki.tuomi@open-xchange.com>
Sun, 17 Nov 2024 10:01:30 +0000 (12:01 +0200)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Fri, 17 Jan 2025 08:40:01 +0000 (10:40 +0200)
src/plugins/var-expand-crypt/test-var-expand-crypt.c
src/plugins/var-expand-crypt/var-expand-crypt-plugin.c

index a806db4f5b716571c87bc99598de39cf9ae3a49f..64598ee4c7ed5d8877a3ea639ba36617d40b804e 100644 (file)
@@ -24,6 +24,8 @@ static struct var_expand_table table[] = {
        },
        { .key = "decrypted", .value = "hello, world" },
        { .key = "encrypted2", .value = NULL },
+       { .key = "user", .value = "foo" },
+       { .key = "salt-encrypted", .value ="s=foo,r=1000$da793a4ae62eb1d415228b40c43b93a8$" },
        { NULL, NULL, NULL }
 };
 
@@ -67,6 +69,32 @@ static void test_var_expand_crypt(void)
                        "hello, world",
                        0
                },
+               {
+                       "%{user|encrypt(hash='sha1',rounds=16,key='bar',salt='bar',algorithm='aes-256-cbc',raw=1)}",
+                       "92da344a4456e112550c146d9b3e4334",
+                       0
+               },
+               {
+                       "%{literal('foo.20161103001007.SMTP.SEND')|"
+                       "encrypt(hash='sha1',rounds=16,key='secret',salt='secret',algorithm='aes-256-cbc',raw=1)}",
+                       "c23ce33218c955a4e791742c6a8eb38717c9b2dd6cc1cf670dff029e819cca81",
+                       0
+               },
+               {
+                       "%{user|encrypt(key='bar',salt='foo')}",
+                       "s=foo,r=10000$929803f6a290ec6f015bf1a921aa352a$",
+                       0
+               },
+               {
+                       "%{salt-encrypted|decrypt(key='bar')}",
+                       "foo",
+                       0
+               },
+               {
+                       "%{user|encrypt(key='bar',salt='foo')|decrypt(key='bar')}",
+                       "foo",
+                       0
+               },
        };
 
        unsigned int i;
@@ -117,6 +145,7 @@ static void test_var_expand_crypt_random(void)
                        "%{encrypted2|decrypt(algorithm='aes-128-cbc',key=key)}",
                                  &params, &error);
                test_assert_cmp_idx(ret, ==, 0, i);
+               if (ret != 0) i_error("%s: %s", error, str_c(input));
                struct var_expand_table *entry =
                        var_expand_table_get(table, "decrypted");
                test_assert_strcmp_idx(str_c(output), entry->value, i);
index 9bd97cd14912364f2cbec655db26cd4b88860287..d864edcbc09885116336076f5263fceff8e28a1c 100644 (file)
@@ -7,6 +7,9 @@
 #include "dcrypt.h"
 
 #define VAR_EXPAND_CRYPT_DEFAULT_ALGO "AES-256-CBC"
+#define VAR_EXPAND_CRYPT_DEFAULT_ROUNDS 10000
+#define VAR_EXPAND_CRYPT_DEFAULT_HASH "sha256"
+#define VAR_EXPAND_CRYPT_DEFAULT_SALT_LEN 8
 
 struct module;
 
@@ -14,6 +17,8 @@ struct var_expand_crypt_context {
        const char *algo;
        string_t *iv;
        string_t *enckey;
+       intmax_t rounds;
+       const char *salt;
        buffer_t *input;
        bool raw;
 };
@@ -31,6 +36,24 @@ static int parse_parameters(struct var_expand_crypt_context *ctx,
        if (ctx->iv != NULL) {
                *error_r = "Cannot have iv in parameter and input";
                return -1;
+       } else if (*parts[0] == 's' || *parts[0] == 'r') {
+               const char *const *params =
+                       t_strsplit(parts[0], ",");
+               for (; *params != NULL; params++) {
+                       const char *value;
+                       if (str_begins(*params, "s=", &ctx->salt)) {
+                               /* got salt */
+                       } else if (str_begins(*params, "r=", &value)) {
+                               if (str_to_intmax(value, &ctx->rounds) < 0 ||
+                                   ctx->rounds < 1) {
+                                       *error_r = "Invalid input";
+                                       return -1;
+                               }
+                       } else {
+                               *error_r = "Invalid input";
+                               return -1;
+                       }
+               }
        } else {
                ctx->iv = t_buffer_create(32);
                hex_to_binary(parts[0], ctx->iv);
@@ -75,6 +98,18 @@ var_expand_crypt(struct dcrypt_context_symmetric *dctx, buffer_t *key, buffer_t
        return 0;
 }
 
+static const char salt_chars[] =
+       "#&()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+       "[]^_`abcdefghijklmnopqrstuvwxyz{|}";
+
+static const char *make_salt(size_t len)
+{
+       string_t *tmp = t_str_new(len);
+       for (size_t i = 0; i < len; i++)
+               str_append_c(tmp, salt_chars[i_rand_limit(sizeof(salt_chars)-1)]);
+       return str_c(tmp);
+}
+
 static int var_expand_crypt_settings(struct var_expand_state *state,
                                     const struct var_expand_statement *stmt,
                                     struct var_expand_crypt_context *ctx,
@@ -82,6 +117,8 @@ static int var_expand_crypt_settings(struct var_expand_state *state,
 {
        const char *iv;
        const char *enckey = NULL;
+       const char *hash = VAR_EXPAND_CRYPT_DEFAULT_HASH;
+       ctx->rounds = VAR_EXPAND_CRYPT_DEFAULT_ROUNDS;
 
        ctx->algo = VAR_EXPAND_CRYPT_DEFAULT_ALGO;
 
@@ -103,6 +140,10 @@ static int var_expand_crypt_settings(struct var_expand_state *state,
                                                               error_r) < 0) {
                                return -1;
                        }
+                       if (ctx->salt != NULL) {
+                               *error_r = "Cannot use both salt and iv";
+                               return -1;
+                       }
                        ctx->iv = t_buffer_create(strlen(iv) / 2);
                        hex_to_binary(iv, ctx->iv);
                } else if (strcmp(key, "key") == 0) {
@@ -118,6 +159,28 @@ static int var_expand_crypt_settings(struct var_expand_state *state,
                        if (var_expand_parameter_bool_or_var(state, par, &ctx->raw,
                                                             error_r) < 0)
                                return -1;
+               } else if (strcmp(key, "salt") == 0) {
+                       if (var_expand_parameter_string_or_var(state, par, &ctx->salt,
+                                                              error_r) < 0) {
+                               return -1;
+                       }
+                       if (ctx->iv != NULL) {
+                               *error_r = "Cannot use both salt and iv";
+                               return -1;
+                       }
+               } else if (strcmp(key, "hash") == 0) {
+                       if (var_expand_parameter_string_or_var(state, par, &hash,
+                                                            error_r) < 0)
+                               return -1;
+               } else if (strcmp(key, "rounds") == 0) {
+                       if (var_expand_parameter_number_or_var(state, par,
+                                                              &ctx->rounds,
+                                                              error_r) < 0)
+                               return -1;
+                       if (ctx->rounds < 1) {
+                               *error_r = "rounds must be positive integer";
+                               return -1;
+                       }
                } else
                        ERROR_UNSUPPORTED_KEY(key);
        }
@@ -127,9 +190,6 @@ static int var_expand_crypt_settings(struct var_expand_state *state,
                return -1;
        }
 
-       ctx->enckey = t_buffer_create(strlen(enckey) / 2);
-       hex_to_binary(enckey, ctx->enckey);
-
        ERROR_IF_NO_TRANSFER_TO(stmt->function);
 
        ctx->input = state->transfer;
@@ -143,8 +203,37 @@ static int var_expand_crypt_settings(struct var_expand_state *state,
                        return -1;
        }
 
-       return 0;
+       if (ctx->iv == NULL) {
+               if (ctx->salt == NULL)
+                       ctx->salt = make_salt(VAR_EXPAND_CRYPT_DEFAULT_SALT_LEN);
+               buffer_t *keymaterial = t_buffer_create(48);
+               /* figure out how much material we need */
+               struct dcrypt_context_symmetric *sym_ctx;
+               if (!dcrypt_ctx_sym_create(ctx->algo, DCRYPT_MODE_ENCRYPT,
+                                          &sym_ctx, error_r))
+                       return -1;
+               size_t enckey_len = dcrypt_ctx_sym_get_key_length(sym_ctx);
+               size_t iv_len = dcrypt_ctx_sym_get_iv_length(sym_ctx);
+               dcrypt_ctx_sym_destroy(&sym_ctx);
+               if (!dcrypt_pbkdf2((unsigned char*)enckey, strlen(enckey),
+                                  (unsigned char*)ctx->salt, strlen(ctx->salt),
+                                  hash, ctx->rounds, keymaterial,
+                                  enckey_len + iv_len, error_r))
+                       return -1;
+               const unsigned char *data = keymaterial->data;
+               ctx->enckey = t_buffer_create(enckey_len);
+               ctx->iv = t_buffer_create(iv_len);
+               buffer_append(ctx->enckey, data, enckey_len);
+               buffer_append(ctx->iv, data + enckey_len, iv_len);
+       } else {
+               ctx->enckey = t_buffer_create(strlen(enckey) / 2);
+               hex_to_binary(enckey, ctx->enckey);
+       }
+
+       /* IV can be optional in some algorithms */
+       i_assert(ctx->enckey->used > 0);
 
+       return 0;
 }
 
 static int
@@ -174,7 +263,11 @@ var_expand_encrypt(const struct var_expand_statement *stmt,
                else {
                        state->transfer_set = TRUE;
                        str_truncate(state->transfer, 0);
-                       binary_to_hex_append(state->transfer, ctx.iv->data, ctx.iv->used);
+                       if (ctx.salt != NULL) {
+                               str_printfa(state->transfer, "s=%s,r=%jd",
+                                           ctx.salt, ctx.rounds);
+                       } else
+                               binary_to_hex_append(state->transfer, ctx.iv->data, ctx.iv->used);
                        str_append_c(state->transfer, '$');
                        binary_to_hex_append(state->transfer, dest->data, dest->used);
                        str_append_c(state->transfer, '$');