/*
	entropy.c 
	For 'sks' project	
	
	Copyright (C) 2004-2007  Manuel Pancorbo Castro

	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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

	Manuel Pancorbo Castro <mpancorbo@gmail.com>
*/

/********************
 ** Entropy functions
 **/

#include <unistd.h>
#include "mycrypt.h"
#include "sks.h"

static int rng_ansic(unsigned char *, unsigned long );
#ifdef WIN32
static int rng_windows(char *, int );
#endif

#define RANDOM_NUM	16
#define DIGEST_SIZE (HASH_SIZE*8)	/** Should be multiple of HASH_SIZE **/

static int parity(unsigned char a)
{
	int p = 0;
	while(a){
		p ^= (a & 1);
		a >>= 1;
	}
	return p;
}


static int rng_ansic(unsigned char *buf, unsigned long len)
/** Code taken from LibTomCrypt by Tom St Denis **/
{
   clock_t t1;
   int acc, bits, a, b;
   unsigned long l;

   l = len;
   bits = 8;
   acc  = a = b = 0;
   while (len--) {
       /*if (callback != NULL) callback();*/
	   fputs("ANSI-rng: ", stderr);
	   fprintf(stderr, "%ld\r", l-len);
       while (bits) {
          do {
             t1 = XCLOCK(); while (t1 == XCLOCK()) a ^= 1;
             t1 = XCLOCK(); while (t1 == XCLOCK()) b ^= 1;
          } while (a == b);
          acc = (acc << 1) | a;
		  acc = (acc << 1) | parity(t1 & 0xff);
		  bits -= 2;
		  b = a;
       }
       *buf++ = acc;
       acc  = 0;
	   
       bits = 8;
   }
   fputs("\n", stderr);
   acc = bits = a = b = 0;
   return l;
}

int get_entropy(unsigned char *seed, size_t nbytes)
{
	assert(nbytes <= HASH_SIZE);
	assert(seed != NULL);
	unsigned char junk[DIGEST_SIZE];
	unsigned char bin[DIGEST_SIZE];
	FILE *old;
	int i;
	
	
	/** entropy source: prng-file (perhaps good source?) **/
	old = open_prngfile();
	if( old != NULL){
		fread(bin, 1, DIGEST_SIZE, old);
	}
	else fprintf(stderr, "No se encuentra prng\n");
	
#ifdef DEVRANDOM
	#ifdef LINUX
	FILE *rnd;
	
	if((rnd = fopen("/dev/urandom", "rb")) == NULL)
		if((rnd = fopen("/dev/random", "rb")) == NULL) goto ansi_entropy;

	fread(junk, 1, DIGEST_SIZE, rnd);
	fclose(rnd);
	#endif /* LINUX */

	#ifdef WIN32
	if(!rng_windows(junk, DIGEST_SIZE)) goto ansi_entropy;
	#endif /* WIN32 */
	
	if(old != NULL){
		for(i = 0; i < DIGEST_SIZE; ++i)
			junk[i] ^= bin[i];
		zeromem(bin, DIGEST_SIZE);
	}
	
	hash_binary(junk, junk, DIGEST_SIZE);
	memcpy(seed, junk, nbytes);
	goto entropy_end;
	/*zeromem(junk, nbytes);
	return nbytes;*/

ansi_entropy:
#endif /* DEVRANDOM */
{	
	clock_t *t;
	
	if(old != NULL){
		memcpy(junk, bin, DIGEST_SIZE);
		zeromem(bin, DIGEST_SIZE);
	}
		
	/** low-quality entropy source: date **/
	t = (clock_t *) &(junk[0]);
	*t = XCLOCK();
	
	/** Hash it along old junk material **/
	hash_binary(junk + HASH_SIZE, junk, DIGEST_SIZE);
	
	/** Third: rng_ansic **/
	if (!rng_ansic(junk, RANDOM_NUM)) return 0;
	hash_binary(junk, junk, DIGEST_SIZE);
	memcpy(seed, junk, nbytes);
}

entropy_end:	
	/** Hide the result with more hashing. Keep it as junk material
		for further 'get_entropy' callings **/
	for(i = 0; i < DIGEST_SIZE; i += HASH_SIZE)
		hash_binary(junk + i, junk, DIGEST_SIZE);
	
	/** keep this material for as further junk bits **/	
	if(old != NULL){
		rewind(old);
		fwrite(junk, 1, DIGEST_SIZE, old);
		fclose(old);
	}
	
	return nbytes;
}

#ifdef WIN32
	
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include <wincrypt.h>

static int rng_windows(char *seed, int nbytes)
/** Code taken from LibTomCrypt by Tom St Denis **/
{
	
	HCRYPTPROV hProv = 0;
   	if (!CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, 
                            (CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET)) && 
       !CryptAcquireContext (&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, 
                            CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET | CRYPT_NEWKEYSET))
      return 0;

	if (CryptGenRandom(hProv, nbytes, seed) == TRUE) {
      CryptReleaseContext(hProv, 0);
      return nbytes;
   	} else {
      CryptReleaseContext(hProv, 0);
      return 0;
   }
}
#endif


