/***************************************************************************
** $Id: crypto.cpp,v 1.11 2008/07/31 19:56:26 hoganrobert Exp $
 *   Copyright (C) 2006 - 2008 Robert Hogan                                *
 *   robert@roberthogan.net                                                *
 *                                                                         *
 *   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 St, Fifth Floor, Boston, MA 02110-1301, USA.              *
 *
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.  If you delete this exception statement from all source
 * files in the program, then also delete it here.
 ***************************************************************************/
/* Copyright (c) 2001 Matej Pfajfar.
 * Copyright (c) 2001-2004, Roger Dingledine.
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. */
/* See Tor LICENSE for licensing information */
/* $Id: crypto.cpp,v 1.11 2008/07/31 19:56:26 hoganrobert Exp $ */
#include <string.h>
#include <kdebug.h>  
#include "crypto.h"
#include "../config.h"

#ifndef USE_OPENSSL
#include <gnutls/gnutls.h>
#include <gcrypt.h>
#else
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/opensslv.h>
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/rsa.h>
#include <openssl/dh.h>
#include <openssl/conf.h>
#endif

#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <limits.h>

#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdint.h>

/* random numbers */

/* This is how much entropy OpenSSL likes to add right now, so maybe it will
 * work for us too. */
#define ADD_ENTROPY 32


typedef TQMap<TQString, TQString> servermap;
servermap serverTofp_identity;
servermap fp_identityToServer;

/** Intermediate information about the digest of a stream of data. */
struct crypto_digest_env_t {
#ifndef USE_OPENSSL
  gcry_md_hd_t d;
#else
  SHA_CTX d;
#endif
};


void clearServers()
{
    serverTofp_identity.clear();
    fp_identityToServer.clear();
}

TQString fp_identity(const TQString &server)
{

    return serverTofp_identity[server];
}

TQString server(const TQString &fp_identity)
{

    return fp_identityToServer[fp_identity];
}

void storeServer(const TQString &server,const TQString &fp_identity)
{

    serverTofp_identity[server] = fp_identity;
    fp_identityToServer[fp_identity] = server;

}



TQString getFPDigestFromFP(const TQString &fp)
{
    char identity64[BASE64_DIGEST_LEN+1];
    char digest[DIGEST_LEN];
    TQString FP = fp;
    FP.replace("$","");

    base16_decode(digest, DIGEST_LEN, FP, strlen(FP));
    digest_to_base64(identity64, digest);
    return identity64;
}

TQString getNickNameFromFPDigest(const TQString &fpdigest)
{

    return fp_identityToServer[fpdigest];

}

TQString getNickNameFromFP(const TQString &fp)
{
    TQString fpdigest = getFPDigestFromFP(fp);

    return fp_identityToServer[fpdigest];
}



TQString getFPFromNickName(const TQString &nickname)
{
    char buf[256];
    char hexdigest[HEX_DIGEST_LEN+1];

    TQString fp = serverTofp_identity[nickname];

    if (fp.isEmpty())
        return TQString();
    if (!digest_from_base64(buf, fp))
        base16_encode(hexdigest, HEX_DIGEST_LEN+1, buf, DIGEST_LEN);

    return hexdigest;
}

TQString getFPFromFPDigest(const TQString &fp)
{
    char buf[256];
    char hexdigest[HEX_DIGEST_LEN+1];

    digest_from_base64(buf, fp);
    base16_encode(hexdigest, HEX_DIGEST_LEN+1, buf, DIGEST_LEN);

    return hexdigest;
}


/** Seed OpenSSL's random number generator with bytes from the
 * operating system.  Return 0 on success, -1 on failure.
 */
int
crypto_seed_rng(void)
{
  char buf[ADD_ENTROPY];
  int rand_poll_status;

  /* local variables */
  static const char *filenames[] = {
    "/dev/srandom", "/dev/urandom", "/dev/random", NULL
  };
  int fd;
  int i, n;

  rand_poll_status = 0;

  for (i = 0; filenames[i]; ++i) {
    fd = open(filenames[i], O_RDONLY, 0);
    if (fd<0) continue;
    n = read_all(fd, buf, sizeof(buf), 0);
    close(fd);
    if (n != sizeof(buf)) {
      return -1;
    }
#ifndef USE_OPENSSL
    gcry_create_nonce(buf, sizeof(buf));
#else
    RAND_seed(buf, sizeof(buf));
#endif
    return 0;
  }

  return rand_poll_status ? 0 : -1;
}

/** Write n bytes of strong random data to <b>to</b>. Return 0 on
 * success, -1 on failure.
 */
int
crypto_rand(char *to, size_t n)
{
  assert(to);
#ifndef USE_OPENSSL
  gcry_randomize((unsigned char*)to,n,GCRY_STRONG_RANDOM);
  return 0;
#else
  int r;
  r = RAND_bytes((unsigned char*)to, n);
  return (r == 1) ? 0 : -1;
#endif
}

/** Return a pseudorandom integer, chosen uniformly from the values
 * between 0 and max-1. */
int
crypto_rand_int(unsigned int max)
{
  unsigned int val;
  unsigned int cutoff;
  assert(max < UINT_MAX);
  assert(max > 0); /* don't div by 0 */

  /* We ignore any values that are >= 'cutoff,' to avoid biasing the
   * distribution with clipping at the upper end of unsigned int's
   * range.
   */
  cutoff = UINT_MAX - (UINT_MAX%max);
  while (1) {
    crypto_rand((char*)&val, sizeof(val));
    if (val < cutoff)
      return val % max;
  }
}

/** Generates a pseudorandom string of length <b>len</b> containing printable
 * ASCII characters from the range '!' (0x21) to '~' (0x7e). */
TQString
crypto_rand_string(int len)
{
  TQString str;
  Q_ASSERT(len >= 0);
  
  for (int i = 0; i < len; i++)
    str += TQChar('!' + crypto_rand_int('~'-'!'+1));
  return str;
}

/** Read from <b>fd</b> to <b>buf</b>, until we get <b>count</b> bytes
 * or reach the end of the file. <b>isSocket</b> must be 1 if fd
 * was returned by socket() or accept(), and 0 if fd was returned by
 * open().  Return the number of bytes read, or -1 on error. Only use
 * if fd is a blocking fd. */
int
read_all(int fd, char *buf, size_t count, int isSocket)
{
  size_t numread = 0;
  int result;

  if (count > SIZE_T_CEILING)
    return -1;

  while (numread != count) {
    if (isSocket)
      result = recv(fd, buf+numread, count-numread, 0);
    else
      result = read(fd, buf+numread, count-numread);
    if (result<0)
      return -1;
    else if (result == 0)
      break;
    numread += result;
  }
  return numread;
}


int
digest_from_base64(char *digest, const char *d64)
{

  char buf_in[BASE64_DIGEST_LEN+3];
  char buf[256];
  if (strlen(d64) != BASE64_DIGEST_LEN)
    return -1;
  memcpy(buf_in, d64, BASE64_DIGEST_LEN);
#ifndef USE_OPENSSL
  memcpy(buf_in+BASE64_DIGEST_LEN, "=\0", 2);
  if (base64_decode(buf, sizeof(buf), buf_in, strlen(buf_in)) != DIGEST_LEN)
    return -1;
#else
  memcpy(buf_in+BASE64_DIGEST_LEN, "=\n\0", 3);
  if (base64_decode(buf, sizeof(buf), buf_in, strlen(buf_in)) != DIGEST_LEN)
    return -1;

#endif


  memcpy(digest, buf, DIGEST_LEN);
  return 0;
}

int
base64_decode(char *dest, size_t destlen, const char *src, size_t srclen)
{

#ifndef USE_OPENSSL
//   gnutls_datum_t data_in;
	base64_decodestate state;
#else
  EVP_ENCODE_CTX ctx;
  int len;
#endif
  int ret;

  /* 64 bytes of input -> *up to* 48 bytes of output.
     Plus one more byte, in case I'm wrong.
  */
  if (destlen < ((srclen/64)+1)*49)
    return -1;
  if (destlen > SIZE_T_CEILING)
    return -1;

#ifndef USE_OPENSSL
/*  data_in.data = (unsigned char*)src;
  data_in.size = srclen;*/
	base64_init_decodestate(&state);
#endif

#ifndef USE_OPENSSL
//   if (gnutls_srp_base64_decode(&data_in, dest, &destlen) 
//       == GNUTLS_E_SHORT_MEMORY_BUFFER)
//       kdDebug() << "error decoding " << endl;
//   kdDebug() << "decoded " << dest << "len" << destlen << endl;

  ret = base64_decode_block(src, srclen, dest, &state);
  return ret;
#else
  EVP_DecodeInit(&ctx);
  EVP_DecodeUpdate(&ctx, (unsigned char*)dest, &len,
                   (unsigned char*)src, srclen);
  EVP_DecodeFinal(&ctx, (unsigned char*)dest, &ret);
  ret += len;
  return ret;
#endif

}

int
digest_to_base64(char *d64, const char *digest)
{
  char buf[256];
  base64_encode(buf, sizeof(buf), digest, DIGEST_LEN);
  buf[BASE64_DIGEST_LEN] = '\0';
  memcpy(d64, buf, BASE64_DIGEST_LEN+1);
  return 0;
}

int
base64_encode(char *dest, size_t destlen, const char *src, size_t srclen)
{
#ifndef USE_OPENSSL
//   gnutls_datum_t data_in;
  base64_encodestate state;
#else
  EVP_ENCODE_CTX ctx;
  int len;
#endif
  int ret;


  /* 48 bytes of input -> 64 bytes of output plus newline.
     Plus one more byte, in case I'm wrong.
  */
  if (destlen < ((srclen/48)+1)*66)
    return -1;
  if (destlen > SIZE_T_CEILING)
    return -1;

#ifndef USE_OPENSSL
/*  data_in.data = (unsigned char*)src;
  data_in.size = srclen;*/
  base64_init_encodestate(&state);
#endif

#ifndef USE_OPENSSL
//   gnutls_srp_base64_encode(&data_in, dest, &destlen);
//   kdDebug() << "encoded " << dest << "len" << destlen << endl;
//   return destlen;
  ret = base64_encode_block(src, srclen, dest, &state);
  ret += base64_encode_blockend(dest+ret, &state);

  return ret;
#else
  EVP_EncodeInit(&ctx);
  EVP_EncodeUpdate(&ctx, (unsigned char*)dest, &len,
                   (unsigned char*)src, srclen);
  EVP_EncodeFinal(&ctx, (unsigned char*)(dest+len), &ret);
  ret += len;
  return ret;
#endif

}

static const char HEX_DIGITS[] = "0123456789ABCDEFabcdef";

static int hex_decode_digit(char c)
{
  const char *cp;
  int n;
  cp = strchr(HEX_DIGITS, c);
  if (!cp)
    return -1;
  n = cp-HEX_DIGITS;
  if (n<=15)
    return n; /* digit or uppercase */
  else
    return n-6; /* lowercase */
}


void
base16_encode(char *dest, size_t destlen, const char *src, size_t srclen)
{
  const char *end;
  char *cp;

  assert(destlen >= srclen*2+1);
  assert(destlen < SIZE_T_CEILING);

  cp = dest;
  end = src+srclen;
  while (src<end) {
    sprintf(cp,"%02X",*(const uint8_t*)src);
    ++src;
    cp += 2;
  }
  *cp = '\0';
}


int
base16_decode(char *dest, size_t destlen, const char *src, size_t srclen)
{
  const char *end;
  int v1,v2;
  if ((srclen % 2) != 0)
    return -1;
  if (destlen < srclen/2 || destlen > SIZE_T_CEILING)
    return -1;
  end = src+srclen;
  while (src<end) {
    v1 = hex_decode_digit(*src);
    v2 = hex_decode_digit(*(src+1));
    if (v1<0||v2<0)
      return -1;
    *(uint8_t*)dest = (v1<<4)|v2;
    ++dest;
    src+=2;
  }
  return 0;
}


/** Deallocate a digest object.
 */
void
crypto_free_digest_env(crypto_digest_env_t *digest)
{
  free(digest);
}

/** Allocate and return a new digest object.
 */
  crypto_digest_env_t *
crypto_new_digest_env(void)
{
  crypto_digest_env_t *r;
  r = (crypto_digest_env_t *)malloc(sizeof(crypto_digest_env_t));

#ifndef USE_OPENSSL
  gcry_md_open(&r->d,GCRY_MD_SHA1, 0);
#else
  SHA1_Init(&r->d);
#endif
  return r;
}

/** Compute the hash of the data that has been passed to the digest
 * object; write the first out_len bytes of the result to <b>out</b>.
 * <b>out_len</b> must be \<= DIGEST_LEN.
 */
void
crypto_digest_get_digest(crypto_digest_env_t *digest,
                         char *out, size_t out_len)
{

#ifdef USE_OPENSSL
  static unsigned char r[DIGEST_LEN];
#else
  unsigned char* r;
#endif
  assert(digest);
  assert(out);
  assert(out_len <= DIGEST_LEN);
#ifdef USE_OPENSSL
  SHA_CTX tmpctx;
#endif

#ifndef USE_OPENSSL
  r = gcry_md_read(digest->d, GCRY_MD_SHA1);
  memcpy(out, r, out_len);
  gcry_md_close(digest->d);
#else
  /* memcpy into a temporary ctx, since SHA1_Final clears the context */
  memcpy(&tmpctx, &digest->d, sizeof(SHA_CTX));
  SHA1_Final(r, &tmpctx);
  memcpy(out, r, out_len);
#endif

}

/** Add <b>len</b> bytes from <b>data</b> to the digest object.
 */
void
crypto_digest_add_bytes(crypto_digest_env_t *digest, const char *data,
                        size_t len)
{
  assert(digest);
  assert(data);
  /* Using the SHA1_*() calls directly means we don't support doing
   * sha1 in hardware. But so far the delay of getting the question
   * to the hardware, and hearing the answer, is likely higher than
   * just doing it ourselves. Hashes are fast.
   */
#ifndef USE_OPENSSL
  gcry_md_write(digest->d, data, len);
#else
  SHA1_Update(&digest->d, (void*)data, len);
#endif

}

/** Implement RFC2440-style iterated-salted S2K conversion: convert the
 * <b>secret_len</b>-byte <b>secret</b> into a <b>key_out_len</b> byte
 * <b>key_out</b>.  As in RFC2440, the first 8 bytes of s2k_specifier
 * are a salt; the 9th byte describes how much iteration to do.
 * Does not support <b>key_out_len</b> &gt; DIGEST_LEN.
 */
void
secret_to_key(char *key_out, size_t key_out_len, const char *secret,
              size_t secret_len, const char *s2k_specifier)
{
  crypto_digest_env_t *d;
  uint8_t c;
  size_t count;
  char *tmp;
  assert(key_out_len < SIZE_T_CEILING);

#define EXPBIAS 6
  c = s2k_specifier[8];
  count = ((uint32_t)16 + (c & 15)) << ((c >> 4) + EXPBIAS);
#undef EXPBIAS

  assert(key_out_len <= DIGEST_LEN);

  d = crypto_new_digest_env();
  tmp = (char *)malloc(8+secret_len);
  memcpy(tmp,s2k_specifier,8);
  memcpy(tmp+8,secret,secret_len);
  secret_len += 8;
  while (count) {
    if (count >= secret_len) {
      crypto_digest_add_bytes(d, tmp, secret_len);
      count -= secret_len;
    } else {
      crypto_digest_add_bytes(d, tmp, count);
      count = 0;
    }
  }
  crypto_digest_get_digest(d, key_out, key_out_len);
  free(tmp);
  crypto_free_digest_env(d);
}


/** Entry point for password hashing: take the desired password from
 * the command line, and print its salted hash to stdout. **/
TQString hashPassword(const char* secret)
{

  char output[256];
  char key[S2K_SPECIFIER_LEN+DIGEST_LEN];

  crypto_rand(key, S2K_SPECIFIER_LEN-1);
  key[S2K_SPECIFIER_LEN-1] = (uint8_t)96; /* Hash 64 K of data. */
  secret_to_key(key+S2K_SPECIFIER_LEN, DIGEST_LEN,
                secret, strlen(secret),
                key);
  base16_encode(output, sizeof(output), key, sizeof(key));
  kdDebug() << output << endl;
  return output;
}

/*
cdecoder.c - c source to a base64 decoding algorithm implementation

This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/

int base64_decode_value(char value_in)
{
	static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
	static const char decoding_size = sizeof(decoding);
	value_in -= 43;
	if (value_in < 0 || value_in > decoding_size) return -1;
	return decoding[(int)value_in];
}

void base64_init_decodestate(base64_decodestate* state_in)
{
	state_in->step = step_a;
	state_in->plainchar = 0;
}

int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in)
{
	const char* codechar = code_in;
	char* plainchar = plaintext_out;
	char fragment;
	
	*plainchar = state_in->plainchar;
	
	switch (state_in->step)
	{
		while (1)
		{
	case step_a:
			do {
				if (codechar == code_in+length_in)
				{
					state_in->step = step_a;
					state_in->plainchar = *plainchar;
					return plainchar - plaintext_out;
				}
				fragment = (char)base64_decode_value(*codechar++);
			} while (fragment < 0);
			*plainchar    = (fragment & 0x03f) << 2;
	case step_b:
			do {
				if (codechar == code_in+length_in)
				{
					state_in->step = step_b;
					state_in->plainchar = *plainchar;
					return plainchar - plaintext_out;
				}
				fragment = (char)base64_decode_value(*codechar++);
			} while (fragment < 0);
			*plainchar++ |= (fragment & 0x030) >> 4;
			*plainchar    = (fragment & 0x00f) << 4;
	case step_c:
			do {
				if (codechar == code_in+length_in)
				{
					state_in->step = step_c;
					state_in->plainchar = *plainchar;
					return plainchar - plaintext_out;
				}
				fragment = (char)base64_decode_value(*codechar++);
			} while (fragment < 0);
			*plainchar++ |= (fragment & 0x03c) >> 2;
			*plainchar    = (fragment & 0x003) << 6;
	case step_d:
			do {
				if (codechar == code_in+length_in)
				{
					state_in->step = step_d;
					state_in->plainchar = *plainchar;
					return plainchar - plaintext_out;
				}
				fragment = (char)base64_decode_value(*codechar++);
			} while (fragment < 0);
			*plainchar++   |= (fragment & 0x03f);
		}
	}
	/* control should not reach here */
	return plainchar - plaintext_out;
}

/*
cencoder.c - c source to a base64 encoding algorithm implementation

This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/


const int CHARS_PER_LINE = 72;

void base64_init_encodestate(base64_encodestate* state_in)
{
	state_in->step = step_A;
	state_in->result = 0;
	state_in->stepcount = 0;
}

char base64_encode_value(char value_in)
{
	static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	if (value_in > 63) return '=';
	return encoding[(int)value_in];
}

int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
{
	const char* plainchar = plaintext_in;
	const char* const plaintextend = plaintext_in + length_in;
	char* codechar = code_out;
	char result;
	char fragment;
	
	result = state_in->result;
	
	switch (state_in->step)
	{
		while (1)
		{
	case step_A:
			if (plainchar == plaintextend)
			{
				state_in->result = result;
				state_in->step = step_A;
				return codechar - code_out;
			}
			fragment = *plainchar++;
			result = (fragment & 0x0fc) >> 2;
			*codechar++ = base64_encode_value(result);
			result = (fragment & 0x003) << 4;
	case step_B:
			if (plainchar == plaintextend)
			{
				state_in->result = result;
				state_in->step = step_B;
				return codechar - code_out;
			}
			fragment = *plainchar++;
			result |= (fragment & 0x0f0) >> 4;
			*codechar++ = base64_encode_value(result);
			result = (fragment & 0x00f) << 2;
	case step_C:
			if (plainchar == plaintextend)
			{
				state_in->result = result;
				state_in->step = step_C;
				return codechar - code_out;
			}
			fragment = *plainchar++;
			result |= (fragment & 0x0c0) >> 6;
			*codechar++ = base64_encode_value(result);
			result  = (fragment & 0x03f) >> 0;
			*codechar++ = base64_encode_value(result);
			
			++(state_in->stepcount);
			if (state_in->stepcount == CHARS_PER_LINE/4)
			{
				*codechar++ = '\n';
				state_in->stepcount = 0;
			}
		}
	}
	/* control should not reach here */
	return codechar - code_out;
}

int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
{
	char* codechar = code_out;
	
	switch (state_in->step)
	{
	case step_B:
		*codechar++ = base64_encode_value(state_in->result);
/*		*codechar++ = '=';
		*codechar++ = '=';*/
		break;
	case step_C:
		*codechar++ = base64_encode_value(state_in->result);
/*		*codechar++ = '=';*/
		break;
	case step_A:
		break;
	}
/*	*codechar++ = '\n';*/
	
	return codechar - code_out;
}