/*
 *  Copyright 2002 Tobias Ringstrom <tobias@ringstrom.mine.nu>
 *  Authentication Copyright 2002 Arcturus Networks Inc.
 *      by Norman Shulman <norm@arcturusnetworks.com>
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

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

#include "ipsecadm.h"

#define MAX(a,b)  ((b) > (a) ? (b) : (a))
#define STRUCT_COUNT(s)  (sizeof((s)) / sizeof((s)[0]))

/* All weak DES keys including parity */
static const unsigned char weak_des_keys[64][8] =
{
	{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 },
	{ 0x01, 0x01, 0x1f, 0x1f, 0x01, 0x01, 0x0e, 0x0e },
	{ 0x01, 0x01, 0xe0, 0xe0, 0x01, 0x01, 0xf1, 0xf1 },
	{ 0x01, 0x01, 0xfe, 0xfe, 0x01, 0x01, 0xfe, 0xfe },
	{ 0x01, 0x1f, 0x01, 0x1f, 0x01, 0x0e, 0x01, 0x0e },
	{ 0x01, 0x1f, 0x1f, 0x01, 0x01, 0x0e, 0x0e, 0x01 },
	{ 0x01, 0x1f, 0xe0, 0xfe, 0x01, 0x0e, 0xf1, 0xfe },
	{ 0x01, 0x1f, 0xfe, 0xe0, 0x01, 0x0e, 0xfe, 0xf1 },
	{ 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xf1, 0x01, 0xf1 },
	{ 0x01, 0xe0, 0x1f, 0xfe, 0x01, 0xf1, 0x0e, 0xfe },
	{ 0x01, 0xe0, 0xe0, 0x01, 0x01, 0xf1, 0xf1, 0x01 },
	{ 0x01, 0xe0, 0xfe, 0x1f, 0x01, 0xf1, 0xfe, 0x0e },
	{ 0x01, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0xfe },
	{ 0x01, 0xfe, 0x1f, 0xe0, 0x01, 0xfe, 0x0e, 0xf1 },
	{ 0x01, 0xfe, 0xe0, 0x1f, 0x01, 0xfe, 0xf1, 0x0e },
	{ 0x01, 0xfe, 0xfe, 0x01, 0x01, 0xfe, 0xfe, 0x01 },
	{ 0x0e, 0x0e, 0x0e, 0x0e, 0xf1, 0xf1, 0xf1, 0xf1 },
	{ 0x1f, 0x01, 0x01, 0x1f, 0x0e, 0x01, 0x01, 0x0e },
	{ 0x1f, 0x01, 0x1f, 0x01, 0x0e, 0x01, 0x0e, 0x01 },
	{ 0x1f, 0x01, 0xe0, 0xfe, 0x0e, 0x01, 0xf1, 0xfe },
	{ 0x1f, 0x01, 0xfe, 0xe0, 0x0e, 0x01, 0xfe, 0xf1 },
	{ 0x1f, 0x1f, 0x01, 0x01, 0x0e, 0x0e, 0x01, 0x01 },
	{ 0x1f, 0x1f, 0x1f, 0x1f, 0x0e, 0x0e, 0x0e, 0x0e },
	{ 0x1f, 0x1f, 0xe0, 0xe0, 0x0e, 0x0e, 0xf1, 0xf1 },
	{ 0x1f, 0x1f, 0xfe, 0xfe, 0x0e, 0x0e, 0xfe, 0xfe },
	{ 0x1f, 0xe0, 0x01, 0xfe, 0x0e, 0xf1, 0x01, 0xfe },
	{ 0x1f, 0xe0, 0x1f, 0xe0, 0x0e, 0xf1, 0x0e, 0xf1 },
	{ 0x1f, 0xe0, 0xe0, 0x1f, 0x0e, 0xf1, 0xf1, 0x0e },
	{ 0x1f, 0xe0, 0xfe, 0x01, 0x0e, 0xf1, 0xfe, 0x01 },
	{ 0x1f, 0xfe, 0x01, 0xe0, 0x0e, 0xfe, 0x01, 0xf1 },
	{ 0x1f, 0xfe, 0x1f, 0xfe, 0x0e, 0xfe, 0x0e, 0xfe },
	{ 0x1f, 0xfe, 0xe0, 0x01, 0x0e, 0xfe, 0xf1, 0x01 },
	{ 0x1f, 0xfe, 0xfe, 0x1f, 0x0e, 0xfe, 0xfe, 0x0e },
	{ 0xe0, 0x01, 0x01, 0xe0, 0xf1, 0x01, 0x01, 0xf1 },
	{ 0xe0, 0x01, 0x1f, 0xfe, 0xf1, 0x01, 0x0e, 0xfe },
	{ 0xe0, 0x01, 0xe0, 0x01, 0xf1, 0x01, 0xf1, 0x01 },
	{ 0xe0, 0x01, 0xfe, 0x1f, 0xf1, 0x01, 0xfe, 0x0e },
	{ 0xe0, 0x1f, 0x01, 0xfe, 0xf1, 0x0e, 0x01, 0xfe },
	{ 0xe0, 0x1f, 0x1f, 0xe0, 0xf1, 0x0e, 0x0e, 0xf1 },
	{ 0xe0, 0x1f, 0xe0, 0x1f, 0xf1, 0x0e, 0xf1, 0x0e },
	{ 0xe0, 0x1f, 0xfe, 0x01, 0xf1, 0x0e, 0xfe, 0x01 },
	{ 0xe0, 0xe0, 0x01, 0x01, 0xf1, 0xf1, 0x01, 0x01 },
	{ 0xe0, 0xe0, 0x1f, 0x1f, 0xf1, 0xf1, 0x0e, 0x0e },
	{ 0xe0, 0xe0, 0xfe, 0xfe, 0xf1, 0xf1, 0xfe, 0xfe },
	{ 0xe0, 0xfe, 0x01, 0x1f, 0xf1, 0xfe, 0x01, 0x0e },
	{ 0xe0, 0xfe, 0x1f, 0x01, 0xf1, 0xfe, 0x0e, 0x01 },
	{ 0xe0, 0xfe, 0xe0, 0xfe, 0xf1, 0xfe, 0xf1, 0xfe },
	{ 0xe0, 0xfe, 0xfe, 0xe0, 0xf1, 0xfe, 0xfe, 0xf1 },
	{ 0xfe, 0x01, 0x01, 0xfe, 0xfe, 0x01, 0x01, 0xfe },
	{ 0xfe, 0x01, 0x1f, 0xe0, 0xfe, 0x01, 0x0e, 0xf1 },
	{ 0xfe, 0x01, 0xe0, 0x1f, 0xfe, 0x01, 0xf1, 0x0e },
	{ 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01 },
	{ 0xfe, 0x1f, 0x01, 0xe0, 0xfe, 0x0e, 0x01, 0xf1 },
	{ 0xfe, 0x1f, 0x1f, 0xfe, 0xfe, 0x0e, 0x0e, 0xfe },
	{ 0xfe, 0x1f, 0xe0, 0x01, 0xfe, 0x0e, 0xf1, 0x01 },
	{ 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x0e, 0xfe, 0x0e },
	{ 0xfe, 0xe0, 0x01, 0x1f, 0xfe, 0xf1, 0x01, 0x0e },
	{ 0xfe, 0xe0, 0x1f, 0x01, 0xfe, 0xf1, 0x0e, 0x01 },
	{ 0xfe, 0xe0, 0xe0, 0xfe, 0xfe, 0xf1, 0xf1, 0xfe },
	{ 0xfe, 0xe0, 0xfe, 0xe0, 0xfe, 0xf1, 0xfe, 0xf1 },
	{ 0xfe, 0xfe, 0x01, 0x01, 0xfe, 0xfe, 0x01, 0x01 },
	{ 0xfe, 0xfe, 0x1f, 0x1f, 0xfe, 0xfe, 0x0e, 0x0e },
	{ 0xfe, 0xfe, 0xe0, 0xe0, 0xfe, 0xfe, 0xf1, 0xf1 },
	{ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }
};

static void
key_usage(void)
{
	fputs("Usage:\n", stderr);
	fputs("    ipsecadm key help\n", stderr);
	fputs("    ipsecadm key create <algorithm> --file=<filename>\n", stderr);
	fputs("                        [--bits=<keysize>]\n", stderr);
	fputs("The following algorithms are known:\n", stderr);
	fputs("    generic, des, 3des, md5, sha1\n", stderr);

	exit(1);
}

static void
read_random_bytes(void *data, int bytes)
{
	int fd = open("/dev/random", O_RDONLY);
	int n, i = 0;

	if (fd == -1)
		error("Cannot open /dev/random! [%s]", strerror(errno));

	while (i < bytes)
	{
		n = read(fd, data + i, bytes - i);
		if (n == -1)
		{
			if (errno == EAGAIN || errno == EINTR)
				continue;
			error("Cannot read /dev/random! [%s]", strerror(errno));
		}
		i += n;
	}
	close(fd);
}

static void
write_key_binary(const char *filename, const void *key, int bytes)
{
	int fd, n;

	fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
	if (fd == -1)
		error("Cannot create %s! [%s]", filename, strerror(errno));

	n = write(fd, key, bytes);
	if (n != bytes)
		error("Cannot write to %s! [%s]", filename, strerror(errno));

	close(fd);
}

static void
create_key_generic(int bits, const char *filename)
{
	char key[1024];
	int bytes = bits / 8;

	if (bits <= 0 || bytes > sizeof(key) || bits % 8 != 0)
		error("Invalid key size!");

	read_random_bytes(key, bytes);
	write_key_binary(filename, key, bytes);
}

static void
calculate_des_key_parity(unsigned char *key, int bytes)
{
	unsigned char parity;
	int i;

	for (i = 0; i < bytes; i++)
	{
		parity = key[i] | 1;
		parity ^= (parity >> 4);
		parity ^= (parity >> 2);
		parity ^= (parity >> 1);
		key[i] = (key[i] & 0xfe) | (parity & 1);
	}
}

static int
is_weak_des_key(unsigned char *key)
{
	int i;

	for (i = 0; i < STRUCT_COUNT(weak_des_keys); ++i)
	{
		if (memcmp(key, weak_des_keys[i], 8) == 0)
			return 1;
	}

	return 0;
}

static int
is_weak_3des_key(unsigned char *key)
{
	return (is_weak_des_key(key) ||
			is_weak_des_key(key + 8) ||
			is_weak_des_key(key + 16) ||
			memcmp(key, key + 8, 8) == 0 ||
			memcmp(key, key + 16, 8) == 0 ||
			memcmp(key + 8, key + 16, 8) == 0);
}

static void
create_key_des(int bits, const char *filename)
{
	char key[1024];

	if (bits != 0 && bits != 56 && bits != 64)
		error("Invalid key size!");

	do {
		read_random_bytes(key, 8);
		calculate_des_key_parity(key, 8);
	} while (is_weak_des_key(key));
	write_key_binary(filename, key, 8);
}

static void
create_key_3des(int bits, const char *filename)
{
	char key[1024];

	if (bits != 0 && bits != 168 && bits != 192)
		error("Invalid key size!");

	do {
		read_random_bytes(key, 24);
		calculate_des_key_parity(key, 24);
	} while (is_weak_3des_key(key));
	write_key_binary(filename, key, 24);
}

static int
key_create(int argc, char *argv[])
{
	const char *algos[] = {
		"generic", "des-cbc", "3des-cbc", "md5", "sha1", NULL };
	char *file = NULL;
	int algo = -1, bits = 0;
	int c;

	static struct option opts[] = {
		{"bits", 1, 0, 'b'},
		{"file", 1, 0, 'f'},
		{0, 0, 0, 0}
	};

	if (argc < 1)
			key_usage();

	for (;;)
	{
		c = getopt_long(argc - 1, argv + 1, "", opts, NULL);
		if (c == -1)
			break;

		switch (c)
		{
		case 'b':
			bits = atoi(optarg);
			break;
		case 'f':
			file = optarg;
			break;
		case '?':
			key_usage();
			break;
		default:
			printf("default: %d %c\n", c, c);
			break;
		}
	}

	if (argc - optind != 1)
		key_usage();

	algo = find_unambiguous_string(algos, argv[1]);
	if (algo < 0)
		error("Unknown algorithm!");

	if (file == NULL)
		error("No filename specified!");

	switch (algo)
	{
	case 0:
		create_key_generic(bits, file);
		break;
	case 1:
		create_key_des(bits, file);
		break;
	case 2:
		create_key_3des(bits, file);
		break;
	case 3: /* MD5 */
		create_key_generic(bits ? bits : 128, file);
		break;
	case 4: /* SHA-1 */
		create_key_generic(bits ? bits : 160, file);
		break;
	default:
		error("Internal error!!!");
	}

	return 0;
}

int
key_main(int argc, char *argv[])
{
	const char *modes[] = { "help", "usage", "create", NULL };
	int mode;

	if (argc < 2)
		key_usage();

	mode = find_unambiguous_string(modes, argv[1]);
	switch (mode)
	{
	case 0:
	case 1:
		key_usage();
		return 0;
	case 2:
		return key_create(argc - 1, argv + 1);
	}

	key_usage();

	return 1;
}
