/*
 * Copyright (c) 1997-2007  The Stanford SRP Authentication Project
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
 * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Redistributions in source or binary form must retain an intact copy
 * of this copyright notice.
 */

#include "t_defines.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#ifdef WIN32
#include <process.h>
#include <io.h>
#endif

#include "t_sha.h"

#ifndef NULL
#define NULL 0
#endif

#ifdef OPENSSL
#include <openssl/opensslv.h>
#include <openssl/rand.h>
#elif defined(TOMCRYPT)
#include "tomcrypt.h"
static prng_state g_rng;
static unsigned char entropy[32];
#elif defined(CRYPTOLIB)
# include "libcrypt.h"
static unsigned char crpool[64];
#else
static unsigned char randpool[SHA_DIGESTSIZE], randout[SHA_DIGESTSIZE];
static unsigned long randcnt = 0;
static unsigned int outpos = 0;
SHA1_CTX randctxt;
#endif /* OPENSSL */

/*
 * t_envhash - Generate a 160-bit SHA hash of the environment
 *
 * This routine performs an SHA hash of all the "name=value" pairs
 * in the environment concatenated together and dumps them in the
 * output.  While it is true that anyone on the system can see
 * your environment, someone not on the system will have a very
 * difficult time guessing it, especially since some systems play
 * tricks with variable ordering and sometimes define quirky
 * environment variables like $WINDOWID or $_.
 */
extern char ** environ;

static void
t_envhash(out)
     unsigned char * out;
{
  char ** ptr;
  char ebuf[256];
  SHA1_CTX ctxt;

  SHA1Init(&ctxt);
  for(ptr = environ; *ptr; ++ptr) {
    strncpy(ebuf, *ptr, 255);
    ebuf[255] = '\0';
    SHA1Update(&ctxt, ebuf, strlen(ebuf));
  }
  SHA1Final(out, &ctxt);
}

/*
 * t_fshash - Generate a 160-bit SHA hash from the file system
 *
 * This routine climbs up the directory tree from the current
 * directory, running stat() on each directory until it hits the
 * root directory.  This information is sensitive to the last
 * access/modification times of all the directories above you,
 * so someone who lists one of those directories injects some
 * entropy into the system.  Obviously, this hash is very sensitive
 * to your current directory when the program is run.
 *
 * For good measure, it also performs an fstat on the standard input,
 * usually your tty, throws that into the buffer, creates a file in
 * /tmp (the inode is unpredictable on a busy system), and runs stat()
 * on that before deleting it.
 *
 * The entire buffer is run once through SHA to obtain the final result.
 */
static void
t_fshash(out)
     unsigned char * out;
{
  char dotpath[128];
  struct stat st;
  SHA1_CTX ctxt;
  int i, pinode;
  dev_t pdev;

  SHA1Init(&ctxt);
  if(stat(".", &st) >= 0) {
    SHA1Update(&ctxt, (unsigned char *) &st, sizeof(st));
    pinode = st.st_ino;
    pdev = st.st_dev;
    strcpy(dotpath, "..");
    for(i = 0; i < 40; ++i) {
      if(stat(dotpath, &st) < 0)
	break;
      if(st.st_ino == pinode && st.st_dev == pdev)
	break;
      SHA1Update(&ctxt, (unsigned char *) &st, sizeof(st));
      pinode = st.st_ino;
      pdev = st.st_dev;
      strcat(dotpath, "/..");
    }
  }

  if(fstat(0, &st) >= 0)
    SHA1Update(&ctxt, (unsigned char *) &st, sizeof(st));

  sprintf(dotpath, "/tmp/rnd.%d", getpid());
  if(creat(dotpath, 0600) >= 0 && stat(dotpath, &st) >= 0)
    SHA1Update(&ctxt, (unsigned char *) &st, sizeof(st));
  unlink(dotpath);

  SHA1Final(out, &ctxt);
}

/*
 * Generate a high-entropy seed for the strong random number generator.
 * This uses a wide variety of quickly gathered and somewhat unpredictable
 * system information.  The 'preseed' structure is assembled from:
 *
 *   The system time in seconds
 *   The system time in microseconds
 *   The current process ID
 *   The parent process ID
 *   A hash of the user's environment
 *   A hash gathered from the file system
 *   Input from a random device, if available
 *   Timings of system interrupts
 *
 * The entire structure (60 bytes on most systems) is fed to SHA to produce
 * a 160-bit seed for the strong random number generator.  It is believed
 * that in the worst case (on a quiet system with no random device versus
 * an attacker who has access to the system already), the seed contains at
 * least about 80 bits of entropy.  Versus an attacker who does not have
 * access to the system, the entropy should be slightly over 128 bits.
 */
static char initialized = 0;

static struct {
  unsigned int trand1;
  time_t sec;
  time_t subsec;
  short pid;
  short ppid;
  unsigned char envh[SHA_DIGESTSIZE];
  unsigned char fsh[SHA_DIGESTSIZE];
  unsigned char devrand[20];
  unsigned int trand2;
} preseed;

unsigned long raw_truerand();

static void
t_initrand()
{
  SHA1_CTX ctxt;
#ifdef USE_FTIME
  struct timeb t;
#else
  struct timeval t;
#endif
  int i, r=0;

  if(initialized)
    return;

  initialized = 1;

#if defined(OPENSSL)	/* OpenSSL has nifty win32 entropy-gathering code */
#if OPENSSL_VERSION_NUMBER >= 0x00905100
  r = RAND_status();
#if defined(WINDOWS) || defined(WIN32)
  if(r)		/* Don't do the Unix-y stuff on Windows if possible */
    return;
#else
#endif
#endif

#elif defined(TOMCRYPT)
  yarrow_start(&g_rng);
  r = rng_get_bytes(entropy, sizeof(entropy), NULL);
  if(r > 0) {
    yarrow_add_entropy(entropy, r, &g_rng);
    memset(entropy, 0, sizeof(entropy));
# if defined(WINDOWS) || defined(WIN32)
    /* Don't do the Unix-y stuff on Windows if possible */
    yarrow_ready(&g_rng);
    return;
# endif
  }
#endif

#if !defined(WINDOWS) && !defined(WIN32)
  i = open("/dev/urandom", O_RDONLY);
  if(i > 0) {
    r += read(i, preseed.devrand, sizeof(preseed.devrand));
    close(i);
  }
#endif /* !WINDOWS && !WIN32 */

  /* Resort to truerand only if desperate for some Real entropy */
  if(r == 0)
    preseed.trand1 = raw_truerand();

#ifdef USE_FTIME
  ftime(&t);
  preseed.sec = t.time;
  preseed.subsec = t.millitm;
#else
  gettimeofday(&t, NULL);
  preseed.sec = t.tv_sec;
  preseed.subsec = t.tv_usec;
#endif
  preseed.pid = getpid();
#ifndef WIN32
  preseed.ppid = getppid();
#endif
  t_envhash(preseed.envh);
  t_fshash(preseed.fsh);

  if(r == 0)
    preseed.trand2 = raw_truerand();

#ifdef OPENSSL
  RAND_seed((unsigned char *)&preseed, sizeof(preseed));
#elif defined(TOMCRYPT)
  yarrow_add_entropy((unsigned char *)&preseed, sizeof(preseed), &g_rng);
  yarrow_ready(&g_rng);
#elif defined(CRYPTOLIB)
  t_mgf1(crpool, sizeof(crpool), (unsigned char *) &preseed, sizeof(preseed));
  seedDesRandom(crpool, sizeof(crpool));
  memset(crpool, 0, sizeof(crpool));
#elif defined(GCRYPT)
  gcry_random_add_bytes((unsigned char *)&preseed, sizeof(preseed), -1);
#else
  SHA1Init(&ctxt);
  SHA1Update(&ctxt, (unsigned char *) &preseed, sizeof(preseed));
  SHA1Final(randpool, &ctxt);
  memset((unsigned char *) &ctxt, 0, sizeof(ctxt));
  outpos = 0;
#endif /* OPENSSL */
  memset((unsigned char *) &preseed, 0, sizeof(preseed));
}

#define NUM_RANDOMS 12

_TYPE( void )
t_stronginitrand()
{
#if 1	/* t_initrand() has been improved enough to make this unnecessary */
  t_initrand();
#else
  SHA1_CTX ctxt;
  unsigned int rawrand[NUM_RANDOMS];
  int i;

  if(!initialized)
    t_initrand();
  for(i = 0; i < NUM_RANDOMS; ++i)
    rawrand[i] = raw_truerand();
  SHA1Init(&ctxt);
  SHA1Update(&ctxt, (unsigned char *) rawrand, sizeof(rawrand));
  SHA1Final(randkey2, &ctxt);
  memset(rawrand, 0, sizeof(rawrand));
#endif
}

/*
 * The strong random number generator.  This uses a 160-bit seed
 * and uses SHA-1 in a feedback configuration to generate successive
 * outputs.  If S[0] is set to the initial seed, then:
 *
 *         S[i+1] = SHA-1(i || S[i])
 *         A[i] = SHA-1(S[i])
 *
 * where the A[i] are the output blocks starting with i=0.
 * Each cycle generates 20 bytes of new output.
 */
_TYPE( void )
t_random(data, size)
     unsigned char * data;
     unsigned size;
{
  if(!initialized)
    t_initrand();

  if(size <= 0)		/* t_random(NULL, 0) forces seed initialization */
    return;

#ifdef OPENSSL
  RAND_bytes(data, size);
#elif defined(TOMCRYPT)
  yarrow_read(data, size, &g_rng);
#elif defined(GCRYPT)
  gcry_randomize(data, size, GCRY_STRONG_RANDOM);
#elif defined(CRYPTOLIB)
  randomBytes(data, size, PSEUDO);
#else
  while(size > outpos) {
    if(outpos > 0) {
      memcpy(data, randout + (sizeof(randout) - outpos), outpos);
      data += outpos;
      size -= outpos;
    }

    /* Recycle */
    SHA1Init(&randctxt);
    SHA1Update(&randctxt, randpool, sizeof(randpool));
    SHA1Final(randout, &randctxt);
    SHA1Init(&randctxt);
    SHA1Update(&randctxt, (unsigned char *) &randcnt, sizeof(randcnt));
    SHA1Update(&randctxt, randpool, sizeof(randpool));
    SHA1Final(randpool, &randctxt);
    ++randcnt;
    outpos = sizeof(randout);
  }

  if(size > 0) {
    memcpy(data, randout + (sizeof(randout) - outpos), size);
    outpos -= size;
  }
#endif
}

/*
 * The interleaved session-key hash.  This separates the even and the odd
 * bytes of the input (ignoring the first byte if the input length is odd),
 * hashes them separately, and re-interleaves the two outputs to form a
 * single 320-bit value.
 */
_TYPE( unsigned char * )
t_sessionkey(key, sk, sklen)
     unsigned char * key;
     unsigned char * sk;
     unsigned sklen;
{
  unsigned i, klen;
  unsigned char * hbuf;
  unsigned char hout[SHA_DIGESTSIZE];
  SHA1_CTX ctxt;

  while(sklen > 0 && *sk == 0) {	/* Skip leading 0's */
    --sklen;
    ++sk;
  }

  klen = sklen / 2;
  if((hbuf = malloc(klen * sizeof(char))) == 0)
    return 0;

  for(i = 0; i < klen; ++i)
    hbuf[i] = sk[sklen - 2 * i - 1];
  SHA1Init(&ctxt);
  SHA1Update(&ctxt, hbuf, klen);
  SHA1Final(hout, &ctxt);
  for(i = 0; i < sizeof(hout); ++i)
    key[2 * i] = hout[i];

  for(i = 0; i < klen; ++i)
    hbuf[i] = sk[sklen - 2 * i - 2];
  SHA1Init(&ctxt);
  SHA1Update(&ctxt, hbuf, klen);
  SHA1Final(hout, &ctxt);
  for(i = 0; i < sizeof(hout); ++i)
    key[2 * i + 1] = hout[i];

  memset(hout, 0, sizeof(hout));
  memset(hbuf, 0, klen);
  free(hbuf);
  return key;
}

_TYPE( void )
t_mgf1(mask, masklen, seed, seedlen)
     unsigned char * mask;
     unsigned masklen;
     const unsigned char * seed;
     unsigned seedlen;
{
  SHA1_CTX ctxt;
  unsigned i = 0;
  unsigned pos = 0;
  unsigned char cnt[4];
  unsigned char hout[SHA_DIGESTSIZE];

  while(pos < masklen) {
    cnt[0] = (i >> 24) & 0xFF;
    cnt[1] = (i >> 16) & 0xFF;
    cnt[2] = (i >> 8) & 0xFF;
    cnt[3] = i & 0xFF;
    SHA1Init(&ctxt);
    SHA1Update(&ctxt, seed, seedlen);
    SHA1Update(&ctxt, cnt, 4);

    if(pos + SHA_DIGESTSIZE > masklen) {
      SHA1Final(hout, &ctxt);
      memcpy(mask + pos, hout, masklen - pos);
      pos = masklen;
    }
    else {
      SHA1Final(mask + pos, &ctxt);
      pos += SHA_DIGESTSIZE;
    }

    ++i;
  }

  memset(hout, 0, sizeof(hout));
  memset((unsigned char *)&ctxt, 0, sizeof(ctxt));
}
