/*
 * The Cryptonit security software suite is developped by IDEALX
 * Cryptonit Team (http://IDEALX.org/ and http://cryptonit.org).
 *
 * Copyright 2003-2006 IDEALX
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 *
 * 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 St, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include <stdio.h>
#include <string.h>

#include "sha2.h"

#ifdef WIN32
typedef unsigned __int64 spc_uint64_t;
#else
typedef unsigned long long spc_uint64_t;
#endif

#define SHA256_DIGEST_LENGTH 32

typedef struct {
  sha256_ctx md;
  sha256_ctx in;
  sha256_ctx out;
  char key[SHA256_DIGEST_LENGTH];
  uint32 length;
} hmac_ctx;

void sha256_ctx_copy( sha256_ctx *dest, sha256_ctx *src )
{
  int i;
  if((src != NULL) && (dest != NULL)) {
    dest->bitcount = src->bitcount;
    for( i = 0; i < 8; i++ ) {
      dest->state[i] = src->state[i];
    }

    for( i = 0; i < SHA256_BLOCK_LENGTH; i++ ) {
      dest->buffer[i] = src->buffer[i];
    }
  }
}

void hmac_init( const int8 *key, uint32 len, hmac_ctx *ctx )
{
  int i, j, reset = 0;
  unsigned char pad[SHA256_DIGEST_LENGTH];

  if (key != NULL) {
    if (len > SHA256_DIGEST_LENGTH) {
      sha256_begin( &(ctx->md) );
      sha256_hash( key, len, &(ctx->md) );
      sha256_end( ctx->key, &(ctx->md) );
      ctx->length = SHA256_DIGEST_LENGTH;
    } else {
      memcpy(ctx->key, key, len);
      ctx->length = len;
      memset( ctx->key + ctx->length, 0,
	      SHA256_DIGEST_LENGTH - ctx->length);
    }
    
    for (i = 0; i < SHA256_DIGEST_LENGTH; i++) {
      pad[i] = 0x36 ^ ctx->key[i];
    }
    
    sha256_begin( &(ctx->in) );
    sha256_hash( pad, SHA256_DIGEST_LENGTH, &(ctx->in) );
	
    for (i = 0; i < SHA256_DIGEST_LENGTH; i++) {
      pad[i] = 0x5c ^ ctx->key[i];
    }
	    
    sha256_begin( &(ctx->out) );
    sha256_hash( pad, SHA256_DIGEST_LENGTH, &ctx->out );
  } else {
    sha256_begin( &(ctx->md) );
    sha256_begin( &(ctx->in) );
    sha256_begin( &(ctx->out) );    
  }

  sha256_ctx_copy( &(ctx->md), &(ctx->in) );
}

void hmac_update(const unsigned char *data, int len, hmac_ctx *ctx)
{
  sha256_hash( data, len, &(ctx->md) );
}

void hmac_final(unsigned char *md, hmac_ctx *ctx)
{
  unsigned char buf[SHA256_DIGEST_LENGTH];

  sha256_end( buf, &(ctx->md) );
  sha256_ctx_copy( &(ctx->md), &(ctx->in) );

  sha256_hash( buf, SHA256_DIGEST_LENGTH, &(ctx->md) );
  sha256_end( md, &(ctx->md) );
}

void spc_pbkdf2( const uint8 *pw,
		 const uint32 pwlen, 
		 const uint8 *salt,
		 spc_uint64_t saltlen, 
		 uint32 iter, 
		 uint8 *key,
		 spc_uint64_t keylen ) 
{
  unsigned char buf[SHA256_DIGEST_LENGTH], itmp[4];
  int len;
  unsigned long i = 1, j, k;
  hmac_ctx ctx;

  while(keylen) {
    if(keylen > SHA256_DIGEST_LENGTH) {
      len = SHA256_DIGEST_LENGTH;
    } else {
      len = keylen;
    }
    
    itmp[0] = (unsigned char)((i >> 24) & 0xff);
    itmp[1] = (unsigned char)((i >> 16) & 0xff);
    itmp[2] = (unsigned char)((i >> 8) & 0xff);
    itmp[3] = (unsigned char)(i & 0xff);
    hmac_init(pw, pwlen, &ctx);
    hmac_update(salt, saltlen, &ctx);
    hmac_update(itmp, 4, &ctx);
    hmac_final(buf, &ctx);
    memcpy(key, buf, len);
    for(j = 1; j < iter; j++) {
      hmac_ctx ctx2;
      hmac_init(pw, pwlen, &ctx2);
      hmac_update(buf, SHA256_DIGEST_LENGTH, &ctx2);
      hmac_final(buf, &ctx2);
      for(k = 0; k < len; k++) {
	key[k] ^= buf[k];
      }
    }
    keylen -= len;
    i++;
    key += len;
  }
}

static void derive_key(const char *password, int len,
			      const char *salt, int saltlen,
			      char *key)
{
  spc_pbkdf2(password, len, salt, saltlen, 10000,
	     key, SHA256_DIGEST_LENGTH);
}
