/**
 * @file hmac.c
 * @brief HMAC (Keyed-Hashing for Message Authentication)
 *
 * @section License
 *
 * Copyright (C) 2010-2014 Oryx Embedded. All rights reserved.
 *
 * This file is part of CycloneCrypto Open.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @section Description
 *
 * HMAC is a mechanism for message authentication using cryptographic hash
 * functions. HMAC can be used with any iterative cryptographic hash
 * function (MD5, SHA-1 or SHA-256) in combination with a secret shared
 * key. Refer to RFC 2104 for more details
 *
 * @author Oryx Embedded (www.oryx-embedded.com)
 * @version 1.4.4
 **/

//Switch to the appropriate trace level
#define TRACE_LEVEL CRYPTO_TRACE_LEVEL

//Dependencies
#include <string.h>
#include "crypto.h"
#include "hmac.h"

//Check crypto library configuration
#if (HMAC_SUPPORT == ENABLED)

//HMAC with MD5 OID (1.3.6.1.5.5.8.1.1)
const uint8_t HMAC_WITH_MD5_OID[8] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x08, 0x01, 0x01};
//HMAC with Tiger OID (1.3.6.1.5.5.8.1.3)
const uint8_t HMAC_WITH_TIGER_OID[8] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x08, 0x01, 0x03};
//HMAC with RIPEMD-160 OID (1.3.6.1.5.5.8.1.4)
const uint8_t HMAC_WITH_RIPEMD160_OID[8] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x08, 0x01, 0x04};
//HMAC with SHA-1 OID (1.2.840.113549.2.7)
const uint8_t HMAC_WITH_SHA1_OID[8] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x07};
//HMAC with SHA-224 OID (1.2.840.113549.2.8)
const uint8_t HMAC_WITH_SHA224_OID[8] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x08};
//HMAC with SHA-256 OID (1.2.840.113549.2.9)
const uint8_t HMAC_WITH_SHA256_OID[8] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x09};
//HMAC with SHA-384 OID (1.2.840.113549.2.10)
const uint8_t HMAC_WITH_SHA384_OID[8] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x0A};
//HMAC with SHA-512 OID (1.2.840.113549.2.11)
const uint8_t HMAC_WITH_SHA512_OID[8] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x0B};


/**
 * @brief Compute HMAC using the specified hash function
 * @param[in] hash Hash algorithm used to compute HMAC
 * @param[in] key Key to use in the hash algorithm
 * @param[in] keyLength Length of the key
 * @param[in] data The input data for which to compute the hash code
 * @param[in] dataLength Length of the input data
 * @param[out] digest The computed HMAC value
 * @return Error code
 **/

error_t hmacCompute(const HashAlgo *hash, const void *key, size_t keyLength,
   const void *data, size_t dataLength, uint8_t *digest)
{
   //Allocate a memory buffer to hold the HMAC context
   HmacContext *context = osMemAlloc(sizeof(HmacContext));
   //Failed to allocate memory?
   if(!context) return ERROR_OUT_OF_MEMORY;

   //Initialize the HMAC context
   hmacInit(context, hash, key, keyLength);
   //Digest the message
   hmacUpdate(context, data, dataLength);
   //Finalize the HMAC computation
   hmacFinal(context, digest);

   //Free previously allocated memory
   osMemFree(context);
   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Initialize HMAC calculation
 * @param[in] context Pointer to the  HMAC context to initialize
 * @param[in] hash Hash algorithm used to compute HMAC
 * @param[in] key Key to use in the hash algorithm
 * @param[in] keyLength Length of the key
 **/

void hmacInit(HmacContext *context, const HashAlgo *hash,
   const void *key, size_t keyLength)
{
   uint_t i;

   //Hash algorithm used to compute HMAC
   context->hash = hash;

   //The key is longer than the block size?
   if(keyLength > hash->blockSize)
   {
      //Initialize the hash function context
      hash->init(context->hashContext);
      //Digest the original key
      hash->update(context->hashContext, key, keyLength);
      //Finalize the message digest computation
      hash->final(context->hashContext, context->key);
      //Key is padded to the right with extra zeros
      memset(context->key + hash->digestSize, 0, hash->blockSize - hash->digestSize);
   }
   else
   {
      //Copy the key
      memcpy(context->key, key, keyLength);
      //Key is padded to the right with extra zeros
      memset(context->key + keyLength, 0, hash->blockSize - keyLength);
   }

   //XOR the resulting key with ipad
   for(i = 0; i < hash->blockSize; i++)
      context->key[i] ^= HMAC_IPAD;

   //Initialize context for the first pass
   hash->init(context->hashContext);
   //Start with the inner pad
   hash->update(context->hashContext, context->key, hash->blockSize);
}


/**
 * @brief Update the HMAC context with a portion of the message being hashed
 * @param[in] context Pointer to the HMAC context
 * @param[in] data Pointer to the buffer being hashed
 * @param[in] length Length of the buffer
 **/

void hmacUpdate(HmacContext *context, const void *data, size_t length)
{
   //Hash algorithm used to compute HMAC
   const HashAlgo *hash = context->hash;
   //Digest the message (first pass)
   hash->update(context->hashContext, data, length);
}


/**
 * @brief Finish the HMAC calculation
 * @param[in] context Pointer to the HMAC context
 * @param[out] digest Calculated HMAC value (optional parameter)
 **/

void hmacFinal(HmacContext *context, uint8_t *digest)
{
   uint_t i;

   //Hash algorithm used to compute HMAC
   const HashAlgo *hash = context->hash;
   //Finish the first pass
   hash->final(context->hashContext, context->digest);

   //XOR the original key with opad
   for(i = 0; i < hash->blockSize; i++)
      context->key[i] ^= HMAC_IPAD ^ HMAC_OPAD;

   //Initialize context for the second pass
   hash->init(context->hashContext);
   //Start with outer pad
   hash->update(context->hashContext, context->key, hash->blockSize);
   //Then digest the result of the first hash
   hash->update(context->hashContext, context->digest, hash->digestSize);
   //Finish the second pass
   hash->final(context->hashContext, context->digest);

   //Copy the resulting HMAC value
   if(digest != NULL)
      memcpy(digest, context->digest, hash->digestSize);
}

#endif
