]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: Add generic hashing support to var-expand
authorAki Tuomi <aki.tuomi@dovecot.fi>
Thu, 27 Oct 2016 12:37:45 +0000 (15:37 +0300)
committerGitLab <gitlab@git.dovecot.net>
Wed, 2 Nov 2016 21:19:23 +0000 (23:19 +0200)
Enables use of

%{H;rounds=<rounds>,truncate=<bits>,salt=<salt>:field}

syntax in any variable expansion. H is any supported
hash algorithm or pkcs5 for PKCS#5 PBKDF2 using SHA256.

src/lib/var-expand.c

index dd31d583cdb7ec3623c9c59385df0e615323d7f5..cfbfd013bc632a55ab03c937cb026b7e73e40410 100644 (file)
@@ -5,7 +5,11 @@
 #include "md5.h"
 #include "hash.h"
 #include "hex-binary.h"
+#include "base64.h"
 #include "hostpid.h"
+#include "hmac.h"
+#include "pkcs5.h"
+#include "hash-method.h"
 #include "str.h"
 #include "strescape.h"
 #include "var-expand.h"
@@ -27,6 +31,12 @@ struct var_expand_modifier {
        const char *(*func)(const char *, struct var_expand_context *);
 };
 
+static int
+var_expand_long(const struct var_expand_table *table,
+               const struct var_expand_func_table *func_table,
+               const void *key_start, unsigned int key_len, void *context,
+               const char **var_r, const char **error_r);
+
 static const char *
 m_str_lcase(const char *str, struct var_expand_context *ctx ATTR_UNUSED)
 {
@@ -191,6 +201,160 @@ var_expand_short(const struct var_expand_table *table, char key,
        return 0;
 }
 
+static int
+var_expand_hash(const struct var_expand_table *table,
+               const struct var_expand_func_table *func_table,
+               const char *key, const char *field, void *context,
+               const char **result_r, const char **error_r)
+{
+       enum {
+               FORMAT_HEX,
+               FORMAT_HEX_UC,
+               FORMAT_BASE64
+       } format = FORMAT_HEX;
+
+       const char *p = strchr(key, ';');
+       const char *const *args = NULL;
+       const char *algo = key;
+       const char *value;
+
+       if (p != NULL) {
+               algo = t_strcut(key, ';');
+               args = t_strsplit(p+1, ",");
+       }
+
+       const struct hash_method *method;
+       if (strcmp(algo, "pkcs5") == 0) {
+               method = hash_method_lookup("sha256");
+       } else if ((method = hash_method_lookup(algo)) == NULL) {
+               return 0;
+       }
+
+       string_t *field_value = t_str_new(64);
+       string_t *salt = t_str_new(64);
+       string_t *tmp = t_str_new(method->digest_size);
+
+       if (var_expand_long(table, func_table, field, strlen(field),
+                           context, &value, error_r) < 0)
+               return -1;
+
+       str_append(field_value, value);
+
+       /* default values */
+       unsigned int rounds = 1;
+       unsigned int truncbits = 0;
+
+       if (strcmp(algo, "pkcs5") == 0) {
+               rounds = 2048;
+               str_append(salt, field);
+       }
+
+       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, "rounds") == 0) {
+                       if (str_to_uint(value, &rounds)<0) {
+                               *error_r = t_strdup_printf(
+                                       "Cannot parse hash arguments:"
+                                       "'%s' is not number for rounds",
+                                       value);
+                               return -1;
+                       }
+                       if (rounds < 1) {
+                               *error_r = t_strdup_printf(
+                                       "Cannot parse hash arguments:"
+                                       "rounds must be at least 1");
+                               return -1;
+                       }
+               } else if (strcmp(k, "truncate") == 0) {
+                       if (str_to_uint(value, &truncbits)<0) {
+                               *error_r = t_strdup_printf(
+                                       "Cannot parse hash arguments:"
+                                       "'%s' is not number for truncbits",
+                                       value);
+                               return -1;
+                       }
+                       truncbits = I_MIN(truncbits, method->digest_size*8);
+               } else if (strcmp(k, "salt") == 0) {
+                       str_truncate(salt, 0);
+                       if (var_expand_with_funcs(salt, value, table,
+                                             func_table, context, error_r) < 0) {
+                               return -1;
+                       }
+                       break;
+               } else if (strcmp(k, "format") == 0) {
+                       if (strcmp(value, "hex") == 0) {
+                               format = FORMAT_HEX;
+                       } else if (strcmp(value, "hexuc") == 0){
+                               format = FORMAT_HEX_UC;
+                       } else if (strcmp(value, "base64") == 0) {
+                               format = FORMAT_BASE64;
+                       } else {
+                               *error_r = t_strdup_printf(
+                                       "Cannot parse hash arguments:"
+                                       "'%s' is not supported format",
+                                       value);
+                               return -1;
+                       }
+               }
+               args++;
+       }
+
+       str_truncate(tmp, 0);
+
+       if (strcmp(algo, "pkcs5") == 0) {
+               if (pkcs5_pbkdf(PKCS5_PBKDF2, method,
+                               field_value->data, field_value->used,
+                               salt->data, salt->used,
+                               rounds, HMAC_MAX_CONTEXT_SIZE, tmp) != 0) {
+                       *error_r = "Cannot hash: PKCS5_PBKDF2 failed";
+                       return -1;
+               }
+       } else {
+               void *context = t_malloc_no0(method->context_size);
+
+               str_append_str(tmp, field_value);
+
+               for(;rounds>0;rounds--) {
+                       method->init(context);
+                       if (salt->used > 0)
+                               method->loop(context, salt->data, salt->used);
+                       method->loop(context, tmp->data, tmp->used);
+                       unsigned char *digest =
+                               buffer_get_modifiable_data(tmp, NULL);
+                       method->result(context, digest);
+                       if (tmp->used != method->digest_size)
+                               buffer_set_used_size(tmp, method->digest_size);
+               }
+       }
+
+       if (truncbits > 0)
+               buffer_truncate_rshift_bits(tmp, truncbits);
+
+       switch(format) {
+               case FORMAT_HEX:
+                       *result_r = binary_to_hex(tmp->data, tmp->used);
+                       return 1;
+               case FORMAT_HEX_UC:
+                       *result_r = binary_to_hex(tmp->data, tmp->used);
+                       return 1;
+               case FORMAT_BASE64: {
+                       string_t *dest = t_str_new(64);
+                       base64_encode(tmp->data, tmp->used, dest);
+                       *result_r = str_c(dest);
+                       return 1;
+               }
+       }
+
+       i_unreached();
+}
+
 static int
 var_expand_func(const struct var_expand_func_table *func_table,
                const char *key, const char *data, void *context,
@@ -264,7 +428,15 @@ var_expand_long(const struct var_expand_table *table,
                        key = t_strdup_until(key, data++);
                else
                        data = "";
-               ret = var_expand_func(func_table, key, data, context, &value, error_r);
+
+               /* try with hash first */
+               if ((ret = var_expand_hash(table, func_table, key,
+                                   data, context, &value, error_r)) < 0) {
+                       value = "";
+               } else if (ret == 0) {
+                       ret = var_expand_func(func_table, key, data, context,
+                                             &value, error_r);
+               }
        }
        *var_r = value;
        return ret;