/****************************************************************************
 * crypto/hmac.c
 * $OpenBSD: hmac.c,v 1.4 2016/09/19 18:09:40 tedu Exp $
 *
 * Copyright (c) 2008 Damien Bergamini <damien.bergamini@free.fr>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 ****************************************************************************/

/* This code implements the HMAC algorithm described in RFC 2104 using
 * the MD5, SHA1 and SHA-256 hash functions.
 */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <string.h>
#include <strings.h>
#include <sys/param.h>

#include <crypto/md5.h>
#include <crypto/sha1.h>
#include <crypto/sha2.h>
#include <crypto/hmac.h>

/****************************************************************************
 * Public Functions
 ****************************************************************************/

void hmca_md5_init(FAR HMAC_MD5_CTX *ctx,
                   FAR const uint8_t *key,
                   u_int key_len)
{
  uint8_t k_ipad[MD5_BLOCK_LENGTH];
  int i;

  if (key_len > MD5_BLOCK_LENGTH)
    {
      md5init(&ctx->ctx);
      md5update(&ctx->ctx, key, key_len);
      md5final(ctx->key, &ctx->ctx);
      ctx->key_len = MD5_DIGEST_LENGTH;
    }
  else
    {
      bcopy(key, ctx->key, key_len);
      ctx->key_len = key_len;
    }

  bzero(k_ipad, MD5_BLOCK_LENGTH);
  memcpy(k_ipad, ctx->key, ctx->key_len);
  for (i = 0; i < MD5_BLOCK_LENGTH; i++)
    {
      k_ipad[i] ^= 0x36;
    }

  md5init(&ctx->ctx);
  md5update(&ctx->ctx, k_ipad, MD5_BLOCK_LENGTH);

  explicit_bzero(k_ipad, sizeof k_ipad);
}

void hmac_md5_update(FAR HMAC_MD5_CTX *ctx,
                     FAR const uint8_t *data,
                     u_int len)
{
  md5update(&ctx->ctx, data, len);
}

void hmac_md5_final(FAR uint8_t *digest, FAR HMAC_MD5_CTX *ctx)
{
  uint8_t k_opad[MD5_BLOCK_LENGTH];
  int i;

  md5final(digest, &ctx->ctx);

  bzero(k_opad, MD5_BLOCK_LENGTH);
  memcpy(k_opad, ctx->key, ctx->key_len);
  for (i = 0; i < MD5_BLOCK_LENGTH; i++)
    {
      k_opad[i] ^= 0x5c;
    }

  md5init(&ctx->ctx);
  md5update(&ctx->ctx, k_opad, MD5_BLOCK_LENGTH);
  md5update(&ctx->ctx, digest, MD5_DIGEST_LENGTH);
  md5final(digest, &ctx->ctx);

  explicit_bzero(k_opad, sizeof k_opad);
}

void hmac_sha1_init(FAR HMAC_SHA1_CTX *ctx,
                    FAR const uint8_t *key,
                    u_int key_len)
{
  uint8_t k_ipad[SHA1_BLOCK_LENGTH];
  int i;

  if (key_len > SHA1_BLOCK_LENGTH)
    {
      sha1init(&ctx->ctx);
      sha1update(&ctx->ctx, key, key_len);
      sha1final(ctx->key, &ctx->ctx);
      ctx->key_len = SHA1_DIGEST_LENGTH;
    }
  else
    {
      bcopy(key, ctx->key, key_len);
      ctx->key_len = key_len;
    }

  bzero(k_ipad, SHA1_BLOCK_LENGTH);
  memcpy(k_ipad, ctx->key, ctx->key_len);
  for (i = 0; i < SHA1_BLOCK_LENGTH; i++)
    {
      k_ipad[i] ^= 0x36;
    }

  sha1init(&ctx->ctx);
  sha1update(&ctx->ctx, k_ipad, SHA1_BLOCK_LENGTH);

  explicit_bzero(k_ipad, sizeof k_ipad);
}

void hmac_sha1_update(FAR HMAC_SHA1_CTX *ctx,
                      FAR const uint8_t *data,
                      u_int len)
{
  sha1update(&ctx->ctx, data, len);
}

void hmca_sha1_final(FAR uint8_t *digest, FAR HMAC_SHA1_CTX *ctx)
{
  uint8_t k_opad[SHA1_BLOCK_LENGTH];
  int i;

  sha1final(digest, &ctx->ctx);

  bzero(k_opad, SHA1_BLOCK_LENGTH);
  memcpy(k_opad, ctx->key, ctx->key_len);
  for (i = 0; i < SHA1_BLOCK_LENGTH; i++)
    {
      k_opad[i] ^= 0x5c;
    }

  sha1init(&ctx->ctx);
  sha1update(&ctx->ctx, k_opad, SHA1_BLOCK_LENGTH);
  sha1update(&ctx->ctx, digest, SHA1_DIGEST_LENGTH);
  sha1final(digest, &ctx->ctx);

  explicit_bzero(k_opad, sizeof k_opad);
}

void hmac_sha256_init(FAR HMAC_SHA256_CTX *ctx,
                      FAR const uint8_t *key,
                      u_int key_len)
{
  uint8_t k_ipad[SHA256_BLOCK_LENGTH];
  int i;

  if (key_len > SHA256_BLOCK_LENGTH)
    {
      sha256init(&ctx->ctx);
      sha256update(&ctx->ctx, key, key_len);
      sha256final(ctx->key, &ctx->ctx);
      ctx->key_len = SHA256_DIGEST_LENGTH;
    }
  else
    {
      bcopy(key, ctx->key, key_len);
      ctx->key_len = key_len;
    }

  bzero(k_ipad, SHA256_BLOCK_LENGTH);
  memcpy(k_ipad, ctx->key, ctx->key_len);
  for (i = 0; i < SHA256_BLOCK_LENGTH; i++)
    {
      k_ipad[i] ^= 0x36;
    }

  sha256init(&ctx->ctx);
  sha256update(&ctx->ctx, k_ipad, SHA256_BLOCK_LENGTH);

  explicit_bzero(k_ipad, sizeof k_ipad);
}

void hmac_sha256_update(FAR HMAC_SHA256_CTX *ctx,
                        FAR const uint8_t *data,
                        u_int len)
{
  sha256update(&ctx->ctx, data, len);
}

void hmac_sha256_final(FAR uint8_t *digest,
                       FAR HMAC_SHA256_CTX *ctx)
{
  uint8_t k_opad[SHA256_BLOCK_LENGTH];
  int i;

  sha256final(digest, &ctx->ctx);

  bzero(k_opad, SHA256_BLOCK_LENGTH);
  memcpy(k_opad, ctx->key, ctx->key_len);
  for (i = 0; i < SHA256_BLOCK_LENGTH; i++)
    {
      k_opad[i] ^= 0x5c;
    }

  sha256init(&ctx->ctx);
  sha256update(&ctx->ctx, k_opad, SHA256_BLOCK_LENGTH);
  sha256update(&ctx->ctx, digest, SHA256_DIGEST_LENGTH);
  sha256final(digest, &ctx->ctx);

  explicit_bzero(k_opad, sizeof k_opad);
}