/* Copyright 1999 Enhanced Software Technologies Inc.
 * 
 * Licensed for public use under a BSD-style license. 
 * See the file "LICENSE.ocotillo" for licensing information
 * 
 * ocrandom.c -- Ocotillo pseudo-random number generator, by Eric Green
 *
 * Feeds two entropy pools into an RC6 block cipher, which then stirs an
 * output pool. See README.ocotillo for more info.
 *
 * RCS CHANGE LOG
 * $Log$
 * Revision 1.1  2001/01/06 10:46:00  eric
 * Initial checkin of ocotillo.
 *
 * Revision 1.5  1999/10/28 15:46:00  eric
 * Make cserv compile under Linux. Continued writing of testclient stuff.
 *
 * Revision 1.4  1999/09/17 21:19:04  eric
 * Initial checkin?
 *
 * Revision 1.3  1999/09/13 15:31:33  eric
 * Added BSD-style licensing info.
 *
 * Revision 1.2  1999/09/10 19:17:18  eric
 * Altered the input stirring somewhat. Made the output stirring optional.
 *
 * Revision 1.1  1999/09/10 00:06:05  eric
 * Initial checkin, Ocotillo pseudo-random number generator.
 *
 *
 * $Date$
 *
 */

#include "md5.h"
#include "ocrandom.h"
#include "twofish.h"
#include <string.h>
/* #include <time.h> */
#include <sys/time.h>
#include <stdio.h>

#ifdef NO_OUTPUT_POOL
#include "dstring.h" 
#endif



/********************* Local Variables *********************************/
static ent_pool input_pool;
static ent_pool key_pool;
static ent_pool output_pool;

static unsigned char *rand_buf; /* output buffer */
static int buf_count;              /* and pointer into output buffer. */
static unsigned char *buf_ptr;

/****************** Entropy Pool Functions *****************************/

int init_ent_pool(ent_pool *pool) {
  MD5Init(&(pool->pool));         /* initialize the MD5 hash. */
  return 0;                       /* okay. */
}

int stir_ent_pool(ent_pool *pool, unsigned char *entropy, unsigned int numbytes) {
  MD5Update(&(pool->pool),entropy,numbytes);
  return 0;
}

  /* we will copy the current MD5 pool to the output buffer: */
unsigned char *get_pool_value(ent_pool *pool) {
  unsigned char *output;

  output=(unsigned char *)malloc(16); /* the 16 bytes that will be returned */
  if (!output) {  /* out of memory? */
    return NULL;
  } 
  /* copy the current pool into the output buffer */
  memcpy(&(pool->output_buffer),&(pool->pool), sizeof(ent_pool));
  MD5Final(output,&(pool->output_buffer));
  return output;  /* and voila! */
}

/* Add a file's entropy to the pool. Used for things we can't easily do
 * otherwise, like the output of 'ps'. 
 */

int add_file_to_pool(ent_pool *pool, char *filename) {
  FILE *fh;
  char buffer[4096];
  char *result;

  fh=fopen(filename,"r");
  if (!fh) {
    return -1; /* could not open! */
  }
  
  while (result=fgets(buffer,4095,fh)) {
    stir_ent_pool(pool, buffer, strlen(buffer));
  }
  fclose(fh);           /* close the filehandle... */
  return 0;
}

/* Add a command's output entropy to the pool. Uses 'popen' etc. to get
 * us a command's output. 
 */

int add_command_to_pool(ent_pool *pool, char *command) {
  FILE *fh;
  char buffer[4096];
  char *result;

  fh=popen(command,"r");
  if (!fh) {
    return -1; /* sorry! */
  }

  while (result=fgets(buffer,4095,fh)) {
    stir_ent_pool(pool, buffer, strlen(buffer));
  }
  pclose(fh);
  return 0;
}


/************************ ocrandom functions: **************************/


int oc_start(void) {
  struct timeval tval;
  struct timezone tz;
  int result;
  clock_t c;
  char *temp; 
  u4byte *cryptkey; /* for the rc6... */

  init_ent_pool(&input_pool);
  init_ent_pool(&key_pool);
  init_ent_pool(&output_pool);

  /* Let's stir in the system date and the clock time, which is a couple 
   * of bits of entropy...
   */

  result=gettimeofday(&tval,&tz);

  /* Only the 'microseconds' field is entropy, sigh. */ 
  stir_ent_pool(&key_pool,(unsigned char *)&tval, sizeof(tval));
  c=clock();  /* this isn't much entropy, sigh. */
  stir_ent_pool(&key_pool,(unsigned char *)&c, sizeof(clock_t));
  
  add_file_to_pool(&key_pool,"/var/log/messages");  /* accumulated over days... */
  add_file_to_pool(&key_pool,"/usr/adm/messages");  /* is one or the other ...  */

  /* Now let's use various Unix commands to find a little randomness... */

  /* the next two seem to work the same on all in-house Unixes, and should
     be able to capture network state at a semi-random time. including
     number of packets/bytes that have gone thru the interface...
  */
  
  add_command_to_pool(&key_pool,"netstat -a");
  add_command_to_pool(&key_pool,"netstat -i");

  /* the 'ps' command, alas, does not have any such consistency... 
   * Linux and FreeBSD use the BSD style 'ps' command. HPUX, Solaris, and
   * SCO Unix use the SysV style 'ps' command. 
   */ 
#ifdef BSD_PS
  add_command_to_pool(&key_pool,"ps -eaxlw");
#else
  add_command_to_pool(&key_pool,"ps -eafl"); /* Sys V 'ps' command... */
#endif

  /* Some of these don't exist on various Unixes. If they don't exist, they
   * don't exist... we'll just return from them... 
   */ 
  add_command_to_pool(&key_pool,"dmesg"); /* boot time is in there on hp/ux */
  add_command_to_pool(&key_pool,"uptime"); /* and time up w/load average */

  /* Now to stir a little entropy into the input pool: */
  temp=get_pool_value(&key_pool);
  cryptkey=set_key(temp,128); /* rc6 key schedule, a 128-byte key. */
  

  stir_ent_pool(&input_pool,temp,16); /* add 16 bytes... */
  free(temp);                        /* free that storage so we don't leak :-( */
  
  result=gettimeofday(&tval,&tz);  /* we should have a few bits of entropy here... */
  stir_ent_pool(&input_pool,(unsigned char *)&tval,sizeof(tval));

  /* clear our encryption buffers: */
  buf_ptr=NULL;
  buf_count=0;
  rand_buf=NULL;

  /* and end! */
  return 0;
}

unsigned char *oc_random(int numbytes) {
  unsigned char *temp;
  unsigned char *outbuf;
  int i;
  char rbuf[16];
  int result;
  struct timeval tval;
  struct timezone tz;
  clock_t c;

  static unsigned char counter; /* we don't pre-clear it, sigh */

  outbuf=(unsigned char *)malloc(numbytes);
  
  /** Okay, first let's pre-stir the input: the md5 ought to permutate the
  * bits of entropy okay here. We are still succeptible to timing attacks if
  * asked to give lots of random bits in a small period of time tho. 
  ***/
  result=gettimeofday(&tval,&tz);
  stir_ent_pool(&input_pool,(unsigned char *)&tval.tv_usec, sizeof(long));
  c=clock();  /* not much entropy, but worth using to stir input, sigh */
  stir_ent_pool(&input_pool,(unsigned char *)&c, sizeof(clock_t));
  

  for (i=0;i<numbytes;i++) {
    if (buf_count) {
      outbuf[i]=*buf_ptr++;
      buf_count--;
    } else {
      /* Now in counter mode for counter, so we have something happening on
	 the input pool even if we got no entropy from the above: */
      counter++;
      stir_ent_pool(&input_pool,&counter,1); /* add single char... */
      temp=get_pool_value(&input_pool);

      encrypt((u4byte *)temp,(u4byte *)rbuf);  /* we already seeded with key. */
      free(temp);                              /* sigh. */
      if (rand_buf) {
	free(rand_buf);
      }
#ifndef NO_OUTPUT_POOL
      stir_ent_pool(&output_pool,rbuf,16);
      rand_buf=get_pool_value(&output_pool);
#else
      rand_buf=d_dup(rbuf); /* use the encrypted values directly. */
#endif
      buf_count=16;
      buf_ptr=rand_buf;
      outbuf[i]=*buf_ptr++;
      buf_count--;
    }
  }
  return outbuf;
}      
      
/* And now the oc_stir business... */
int oc_stir(char *entropy, int numbytes) {
  return stir_ent_pool(&input_pool,entropy,numbytes);
}

#ifdef OCRANDOM_TEST

/* we need the hexify_string routine from do_md5.c, which is not part of
 * Ocotillo, thus not part of the test build. Thus I duplicate it here,
 * sigh. 
 */ 

static char HEXSTR[]="0123456789ABCDEF";

/* Do binary to hex conversion */
unsigned char *hexify_string( unsigned char *resultbuf, int length )
{
  unsigned char *resultstr;
  int i;
  int leftnib;
  int rightnib;
  unsigned int ch;

  int endian_test;
  unsigned char *endian_ptr;
  unsigned int endian_char;
  int big_endian=0;

  /* First test to see whether we are on a big-endian or little-endian box */
  endian_test=1;
  endian_ptr=(unsigned char *)&endian_test;
  endian_char=(unsigned int) *endian_ptr;  /* get the highest or lowest byte... */
  if (endian_char == 1) {
    big_endian=0;
    /* printf("little endian\n"); */
  } else if (endian_char == 0) {
    /* printf("big endian\n"); */
    big_endian=1; 
  } else {
    /* argh! a MIDDLE-endian?! */
    fprintf(stderr,"Error: endian_test: failed!\n");
    exit(1);
  }

  resultstr= (unsigned char *)malloc(length*2+1);  /* 16 bytes * 2 + terminator */


  if (big_endian) { /* if big endian, start at beginning & go up */
    for (i=0;i<length;i++) {
      ch=(unsigned int) *resultbuf++;
      leftnib= (ch >> 4) & 0x0f ;
      rightnib = ch & 0x0f;
      resultstr[i*2]=HEXSTR[leftnib];
      resultstr[i*2+1]=HEXSTR[rightnib];
    }
  } else { /* if little-endian, start at end and go down. */
    endian_ptr=resultstr;
    for (i=length-1;i>=0;i--) {
      ch=(unsigned int) resultbuf[i];
      leftnib= (ch >> 4) & 0x0f ;
      rightnib = ch & 0x0f;
      *endian_ptr++=HEXSTR[leftnib];
      *endian_ptr++=HEXSTR[rightnib];
    }
  }
  resultstr[length*2]=0;  /* terminate! */
  return resultstr;  /* and return the result. */
}

/******************************************************/

int main(int argc, char *argv[]) {
  FILE *f1;
  unsigned char *val1, *val2;
  int i,j;
  time_t t1,t2;
  clock_t c1,c2;
  /* we'll give'em something to talk about: */

  printf("Initializing PRNG:\n");
  oc_start();
#ifdef OC_TIMING_TEST
  printf("Timing test: 128-bit words, 1,000,000 iterations: \n");
  t1=time(NULL);
  c1=clock();
  j=0;
  for (i=0;i<1000000;i++) {
    j++;
    if (j==10000) {
      putchar('%');
      fflush(stdout);
      j=0;
    }
    val1=oc_random(16);
    free(val1); /* if we were doing true oc_random timing we would not need this */
  }
  t2=time(NULL);
  c2=clock();
  printf("\nWall Time: %d   Clock Time: %d\n\n",(int)(t2-t1),(int)(c2-c1));
#endif
  printf("Creating file /usr/tmp/output with some sample text:\n");
  fflush(stdout);
  f1=fopen("/usr/tmp/output","w");
  j=0;
  for (i=0;i<5000000;i++) {
    j++;
    if (j==10000) {
      putchar('*');
      fflush(stdout);
      j=0;
    }
      
    val1=oc_random(16);
    val2=hexify_string(val1,16);
    fprintf(f1,"%s\n",val2);
    free(val1);
    free(val2);
  }
  printf("\nDone.\n");
  
}

#endif
