summaryrefslogtreecommitdiffstats
path: root/src/lib/hmac.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/hmac.c')
-rw-r--r--src/lib/hmac.c152
1 files changed, 152 insertions, 0 deletions
diff --git a/src/lib/hmac.c b/src/lib/hmac.c
new file mode 100644
index 0000000..0138a4a
--- /dev/null
+++ b/src/lib/hmac.c
@@ -0,0 +1,152 @@
+/*
+ * HMAC (RFC-2104) implementation.
+ *
+ * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
+ * Copyright (c) 2011-2016 Florian Zeitz <florob@babelmonkeys.de>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "lib.h"
+#include "hmac.h"
+#include "safe-memset.h"
+#include "buffer.h"
+
+#include "hex-binary.h"
+
+void hmac_init(struct hmac_context *_ctx, const unsigned char *key,
+ size_t key_len, const struct hash_method *meth)
+{
+ struct hmac_context_priv *ctx = &_ctx->u.priv;
+ unsigned int i;
+ unsigned char k_ipad[meth->block_size];
+ unsigned char k_opad[meth->block_size];
+ unsigned char hashedkey[meth->digest_size];
+
+ i_assert(meth->context_size <= HMAC_MAX_CONTEXT_SIZE);
+
+ ctx->hash = meth;
+
+ if (key_len > meth->block_size) {
+ meth->init(ctx->ctx);
+ meth->loop(ctx->ctx, key, key_len);
+ meth->result(ctx->ctx, hashedkey);
+ key = hashedkey;
+ key_len = meth->digest_size;
+ }
+
+ memcpy(k_ipad, key, key_len);
+ memset(k_ipad + key_len, 0, meth->block_size - key_len);
+ memcpy(k_opad, k_ipad, meth->block_size);
+
+ for (i = 0; i < meth->block_size; i++) {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+
+ meth->init(ctx->ctx);
+ meth->loop(ctx->ctx, k_ipad, meth->block_size);
+ meth->init(ctx->ctxo);
+ meth->loop(ctx->ctxo, k_opad, meth->block_size);
+
+ safe_memset(k_ipad, 0, meth->block_size);
+ safe_memset(k_opad, 0, meth->block_size);
+}
+
+void hmac_final(struct hmac_context *_ctx, unsigned char *digest)
+{
+ struct hmac_context_priv *ctx = &_ctx->u.priv;
+
+ ctx->hash->result(ctx->ctx, digest);
+
+ ctx->hash->loop(ctx->ctxo, digest, ctx->hash->digest_size);
+ ctx->hash->result(ctx->ctxo, digest);
+}
+
+buffer_t *t_hmac_data(const struct hash_method *meth,
+ const unsigned char *key, size_t key_len,
+ const void *data, size_t data_len)
+{
+ struct hmac_context ctx;
+ i_assert(meth != NULL);
+ i_assert(key != NULL && key_len > 0);
+ i_assert(data != NULL || data_len == 0);
+
+ buffer_t *res = t_buffer_create(meth->digest_size);
+ hmac_init(&ctx, key, key_len, meth);
+ if (data_len > 0)
+ hmac_update(&ctx, data, data_len);
+ unsigned char *buf = buffer_get_space_unsafe(res, 0, meth->digest_size);
+ hmac_final(&ctx, buf);
+ return res;
+}
+
+buffer_t *t_hmac_buffer(const struct hash_method *meth,
+ const unsigned char *key, size_t key_len,
+ const buffer_t *data)
+{
+ return t_hmac_data(meth, key, key_len, data->data, data->used);
+}
+
+buffer_t *t_hmac_str(const struct hash_method *meth,
+ const unsigned char *key, size_t key_len,
+ const char *data)
+{
+ return t_hmac_data(meth, key, key_len, data, strlen(data));
+}
+
+void hmac_hkdf(const struct hash_method *method,
+ const unsigned char *salt, size_t salt_len,
+ const unsigned char *ikm, size_t ikm_len,
+ const unsigned char *info, size_t info_len,
+ buffer_t *okm_r, size_t okm_len)
+{
+ i_assert(method != NULL);
+ i_assert(okm_len < 255*method->digest_size);
+ struct hmac_context key_mac;
+ struct hmac_context info_mac;
+ size_t remain = okm_len;
+ unsigned char prk[method->digest_size];
+ unsigned char okm[method->digest_size];
+ /* N = ceil(L/HashLen) */
+ unsigned int rounds = (okm_len + method->digest_size - 1)/method->digest_size;
+
+ /* salt and info can be NULL */
+ i_assert(salt != NULL || salt_len == 0);
+ i_assert(info != NULL || info_len == 0);
+
+ i_assert(ikm != NULL && ikm_len > 0);
+ i_assert(okm_r != NULL && okm_len > 0);
+
+ /* but they still need valid pointer, reduces
+ complains from static analysers */
+ if (salt == NULL)
+ salt = &uchar_nul;
+ if (info == NULL)
+ info = &uchar_nul;
+
+ /* extract */
+ hmac_init(&key_mac, salt, salt_len, method);
+ hmac_update(&key_mac, ikm, ikm_len);
+ hmac_final(&key_mac, prk);
+
+ /* expand */
+ for (unsigned int i = 0; remain > 0 && i < rounds; i++) {
+ unsigned char round = (i+1);
+ size_t amt = remain;
+ if (amt > method->digest_size)
+ amt = method->digest_size;
+ hmac_init(&info_mac, prk, method->digest_size, method);
+ if (i > 0)
+ hmac_update(&info_mac, okm, method->digest_size);
+ hmac_update(&info_mac, info, info_len);
+ hmac_update(&info_mac, &round, 1);
+ memset(okm, 0, method->digest_size);
+ hmac_final(&info_mac, okm);
+ buffer_append(okm_r, okm, amt);
+ remain -= amt;
+ }
+
+ safe_memset(prk, 0, sizeof(prk));
+ safe_memset(okm, 0, sizeof(okm));
+}