/* vi: set sw=4 ts=4:
 *
 * Copyright (C) 2015 Christian Hohnstaedt.
 *
 * All rights reserved.
 */

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

#include <QDir>
#include <QDebug>
#include <openssl/rand.h>
#include "func.h"
#include "xfile.h"
#include "entropy.h"
#include "openssl_compat.h"

/* Entropy sources for XCA
 *
 * Entropy is a very important topic for key generation.
 *
 * XCA uses the following sources for entropy:
 *
 * 1) During startup
 *    RAND_poll()
 *    The OpenSSL seeding mechanism.
 *    It uses /dev/urandom where possible and the
 *    Screen content on Windows.
 *
 *    If "/dev/random" exists, it will be used for additional
 *    256bit entropy. Same is true for "/dev/hwrng"
 *
 * 2) Before any key or parameter generation a "reseeding"
 *    is done. Some say reseeding is not neccessary, but
 *    all say it does not harm.
 *
 *    Entropy by Mouse and keyboard events
 *    main.cpp: bool XCA_application::eventFilter()
 *    256bit from /dev/urandom (unix/Mac)
 *
 * 3) A .rnd state file in the XCA application directory
 *    is written on exit and read on startup.
 *    After reading it, the file will be erased to avoid replays.
 *
 * 4) When managing a token that supports C_GenerateRandom
 *    and C_SeedRandom, XCA will seed the token and in return
 *    seed himself from the token.
 */

#undef DEBUG_ENTROPY

#define pool_siz (sizeof(pool)/sizeof(pool[0]))
unsigned char Entropy::pool[512];
unsigned Entropy::pool_pos = 0;
QTime Entropy::timer;
unsigned Entropy::seed_strength = 0;

void Entropy::add(int rand)
{
	unsigned char entropy = (rand ^ timer.elapsed()) & 0xff;
	pool[pool_pos++ % pool_siz] = entropy;
}

void Entropy::add_buf(const unsigned char *buf, int buflen)
{
	RAND_seed(buf, buflen);
	seed_strength += buflen;
}

int Entropy::get(unsigned char *buf, int num)
{
	seed_rng();
	return RAND_bytes(buf, num);
}

void Entropy::seed_rng()
{
	if (pool_pos > pool_siz)
		pool_pos = pool_siz;

	RAND_seed(pool, pool_pos);
	seed_strength += pool_pos;

#if !defined(Q_OS_WIN32)
	random_from_file("/dev/urandom", 32);
#endif
#ifdef DEBUG_ENTROPY
	{
		QDebug dbg = qDebug();
		dbg << QString("Seeding %1 bytes:").arg(pool_pos);
		for (unsigned i=0; i<pool_pos; i++)
			dbg << pool[i];
	}
	qDebug("Entropy strength: %d", seed_strength);
#endif
	pool_pos = 0;
}

int Entropy::random_from_file(QString fname, unsigned amount, int weakness)
{
	char buf[256];
	int fd, sum;

	/* OpenSSL: RAND_load_file() is blocking
	 * and does not support wchar_t */
	XFile file(fname);
	try {
		file.open_read();
	} catch (errorEx &e) {
		qDebug() << "random_from_file" << fname << e.getString();
		return 0;
	}
	fd = file.handle();
	if (fd == -1)
		return 0;
#if !defined(Q_OS_WIN32)
	if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
		return 0;
#endif
	for (sum=0; amount > 0;) {
		int len = read(fd, buf, amount > sizeof buf ?
					sizeof buf : amount);
		if (len > 0) {
			RAND_seed(buf, len);
			seed_strength += len / weakness;
			amount -= len;
			sum += len;
		}
		if (len == -1) {
			if (errno != EWOULDBLOCK)
				qWarning("Error '%s' while reading '%s'\n",
					strerror(errno), CCHAR(fname));
			len = 0;
		}
		if (len == 0)
			break;
	}
#ifdef DEBUG_ENTROPY
	qDebug("Entropy from file '%s' = %d bytes", CCHAR(fname), sum);
#endif
	return sum;
}

unsigned Entropy::strength()
{
	return seed_strength;
}

Entropy::Entropy()
{
	timer.start();

	rnd = getUserSettingsDir() + "/.rnd";
	random_from_file(rnd, 1024, 128);
	QFile::remove(rnd); // don't use it again

	RAND_poll();
	seed_strength += 8;

#if !defined(Q_OS_WIN32)
	random_from_file("/dev/random", 32);
	random_from_file("/dev/hwrng", 32);
#endif
}

Entropy::~Entropy()
{
#define RAND_BUF_SIZE 1024
	unsigned char buf[RAND_BUF_SIZE];

	if (RAND_bytes(buf, RAND_BUF_SIZE) == 1) {
		XFile file(rnd);
		try {
			file.open_key();
			file.write((char*)buf, RAND_BUF_SIZE);
		} catch (errorEx &e) {
			qDebug() << "random_from_file" << rnd
				 << e.getString();
		}
		file.close();
	}
	memset(buf, 0, RAND_BUF_SIZE);
#ifdef DEBUG_ENTROPY
	qDebug("Seed strength: %d", seed_strength);
#endif
}
