mod_entropy.c 5.96 KB
/**
 * this filter generates a sha1 from the current microtime and request
 * useses this to fill the linux random source.
 *
 * inspired by timed_entropyd.
 *
 * ATTENTION: This module is not portable right now as i don't know
 * howto fill the random source for other systems. It is linux only.
 *
 * Most time was spend in figuring out how to write apache modules.
 *
 * \author Georg Hopp <georg@steffers.org>
 */
#define _POSIX_C_SOURCE 199309L

#include <apache2/httpd.h>
#include <apache2/http_core.h>
#include <apr-1/apu.h>
#include <apr-1/apr_general.h>
#include <apr-1/apr_sha1.h>

#include <time.h>
#include <math.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/random.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define min(x, y)    ((x)<(y)?(x):(y))


module AP_MODULE_DECLARE_DATA entropy_module;

char * getData(const char *, size_t);

/**
 * This is taken from timer_entropyd and modified so
 * that the constant 1/log(2.0) is not calculated but
 * set directly.
 *
 * As far as i can say this correlates to the shannon
 * entropy algorithm with equal probabilities
 * for entropy where the entropy units are bits.
 *
 * But actually i am no mathemacian and my analysis capabilities
 * are limited. Additionally i have not analysed the linux random
 * character device code, so i trusted the code in timer_entropyd.
 */
static
int
get_entropy(const unsigned char * data, size_t ndata)
{
	size_t        byte_count[256];
	size_t        iterator;
	static double log2inv = 1.442695; //!< 1 / log(2.0): the entropy unit size
	double        entropy = 0.0;

	memset(byte_count, 0, sizeof(byte_count));

	/**
	 * first get the amount each byte occurs in the array
	 */
	for (iterator = 0; iterator < ndata; iterator++) {
		byte_count[data[iterator]]++;
	}

	for (iterator = 0; iterator < 256; iterator++) {
		double probability = (double)byte_count[iterator] / (double)ndata;

		if (0.0 < probability) {
			entropy += probability * log2inv * (log(1.0 / probability));
		}
	}

	entropy *= (double)ndata;
	entropy = (entropy < 0.0)? 0.0 : entropy;
	entropy = min((double)(ndata * 8), entropy);

	return entropy;
}

static
int
header_do_print(void * rec, const char * key, const char * value)
{
	apr_sha1_ctx_t * sha1_ctx = rec;

	apr_sha1_update(sha1_ctx, value, strlen(value));

	return 1;
}

static
apr_status_t
entropy_filter_in(
		ap_filter_t        * filter,
		apr_bucket_brigade * brigade, 
		ap_input_mode_t      mode,
		apr_read_type_e      block,
		apr_off_t            readbytes)
{
	apr_bucket   *  bucket;
	apr_status_t    status;
	request_rec  *  request    = filter->r;
	conn_rec     *  connection = filter->c;
	apr_sha1_ctx_t  sha1_ctx;
	unsigned char  	digest[APR_SHA1_DIGESTSIZE];

	struct timespec ts;

	clock_gettime(CLOCK_REALTIME, &ts);

	apr_sha1_init(&sha1_ctx);

	/**
	 * add current microtime to sha1
	 */
	apr_sha1_update_binary(
			&sha1_ctx,
			(const unsigned char *)&ts,
			sizeof(ts));

	/**
	 * add client ip to sha1
	 */
	apr_sha1_update(
			&sha1_ctx,
			connection->client_ip,
			strlen(connection->client_ip));

	/**
	 * add request line to sha1
	 */
	apr_sha1_update(
			&sha1_ctx,
			request->the_request,
			strlen(request->the_request));

	/**
	 * add all header values to sha1
	 */
	apr_table_do(header_do_print, &sha1_ctx, request->headers_in, NULL);

	/**
	 * get the request body and add it to the sha1
	 */
	status = ap_get_brigade(filter->next, brigade, mode, block, readbytes);

	if (status == APR_SUCCESS) {
		for (
				bucket  = APR_BRIGADE_FIRST(brigade);
				bucket != APR_BRIGADE_SENTINEL(brigade);
				bucket  = APR_BUCKET_NEXT(bucket)) {

			if (!(APR_BUCKET_IS_METADATA(bucket))) {
				const char      * buffer;
				apr_size_t        nbuffer;

				status = apr_bucket_read(
						bucket,
						&buffer,
						&nbuffer,
						APR_BLOCK_READ);

				if (status == APR_SUCCESS) {
					apr_sha1_update(&sha1_ctx, buffer, nbuffer);
				}
			}
		}
	}

	/**
	 * get the sha1 digest
	 */
	apr_sha1_final(digest, &sha1_ctx);

	/**
	 * fill /dev/random with sha1 from current request
	 */
	{
		int                     i;
		int                     entropy = get_entropy(digest, APR_SHA1_DIGESTSIZE);
		int                     fd      = open("/dev/random", O_WRONLY|O_NONBLOCK);
		struct rand_pool_info * output;

		output = (struct rand_pool_info *)malloc(
				sizeof(struct rand_pool_info) + APR_SHA1_DIGESTSIZE); 

		output->entropy_count = entropy;
		output->buf_size      = APR_SHA1_DIGESTSIZE;
		memcpy(output->buf, digest, APR_SHA1_DIGESTSIZE);

		fprintf(stderr, "sha1 so far: ");
		for (i=0; i<APR_SHA1_DIGESTSIZE; i++) {
			fprintf(stderr, "%02x", digest[i]);
		}
		fprintf(stderr, "\n");
		fprintf(stderr, "entropy bits: %d\n", entropy);

		if (ioctl(fd, RNDADDENTROPY, output) == -1) {
			switch(errno) {
				case EBADF:
					fprintf(stderr, "ioctl failed: no valid file descriptor %d\n", fd);
					break;

				case EFAULT:
					fprintf(stderr, "ioctl failed: invalid argument: %p\n", output);
					break;

				case EINVAL:
					fprintf(stderr, "ioctl failed: invalid request\n", errno);
					break;

				case ENOTTY:
					fprintf(stderr, "ioctl failed: discriptor not associated to character device\n", errno);
					break;

				case EPERM:
					fprintf(stderr, "ioctl failed: invalid permissions\n", errno);
					break;

				default:
					fprintf(stderr, "ioctl(RNDADDENTROPY) failed: %d\n", errno);
					break;
			}
		}

		free(output);
		close(fd);
	}
	fflush(stderr);

	ap_remove_input_filter(filter);

	return status;
}

/**
 * apache module initialization
 */
static
void
entropy_register_hook(apr_pool_t *p)
{
	ap_register_input_filter(
			"ENTROPY",
			entropy_filter_in,
			NULL,
			AP_FTYPE_CONTENT_SET);
}

module AP_MODULE_DECLARE_DATA entropy_module = {
	STANDARD20_MODULE_STUFF,
	NULL,				/* create per-directory config structure */
	NULL,				/* merge per-directory config structures */
	NULL,				/* create per-server config structure */
	NULL,				/* merge per-server config structures */
	NULL,				/* command apr_table_t */
	entropy_register_hook	/* register hooks */
};

// vim: set ts=4 sw=4: