/*
 * Copyright 2010 Trident Microsystems (Far East) Ltd. All rights reserved.
 * All prior copyright rights in this work and the accompanying software
 * products transferred to Trident Microsystems (Far East) Ltd. by written
 * agreement.
 */
/*****************************************************************************/
/* Copyright (c) 2010 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* 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.                                                                      */
/*                                                                           */
/*****************************************************************************/
/*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
  1 20040917    amccurdy    Original created.
  2 20070703    amccurdy    Update imga -> aimage, tweak for STB225
  3 20080215    batelaan    Update file copyright/license text
  4 20090225    amccurdy    Add --prependsize and IMG_TYPE_INITFS_SQUASHFS support
--------------------------------------------------------------------------------
*/

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

#include <openssl/bn.h>
#include <openssl/sha.h>
#include <openssl/aes.h>

#include "abn.h"
#include "aimage.h"
#include "aimage_keyfile.h"


#define DEFAULT_RELEASE_ID		0x585822B8	/* 0x585822B8 == 88.88-8888 */

#define DEFAULT_PLATFORM_ID		0x53B22500	/* 0x53B22500 == STB225 initial debug / development platform */

#ifndef REVISION_STRING
#define REVISION_STRING 		"UNKNOWN_REVISION"
#endif

#define IMGA_PUBLIC_KEY 		(&aimage_key)
#define MODULUS 			((unsigned int *) &aimage_key.mod_data[1])
#define MOD_DASH			(aimage_key.mod_data[0])
#define PRIVATE_EXPONENT		((unsigned int *) aimage_key_private_exponent)

#define AES_BLOCKSIZE			(16)		/* 128bits == 16 bytes */
#define AES_KEYSIZE			(16)		/* 128bits == 16 bytes */

#define PREPEND_SIZE_MIN		(sizeof(aimage_v1_header_t))
#define PREPEND_SIZE_MAX		(16 * 1024)

#define PADMODULO_MAX			(1 * 1024 * 1024)
#define PADDING_MAX			(1 + AES_BLOCKSIZE + PREPEND_SIZE_MAX + HOST_STRING_LENGTH_MAX + PADMODULO_MAX + 4096 + (20 * 1024))

#define ENCMODE_NONE			(0)
#define ENCMODE_CBC_ONEPASS		(1)
#define ENCMODE_CBC_4KBLOCKS		(2)

#define UNENCRYPTED_LIMIT_OFFSET	(0)	/* encryption begins this many bytes beyond the end of the aimage header */

#define IMAGE_LENGTH_MAX		(128 * 1024 * 1024)


/*******************************************************************************
*******************************************************************************/

typedef struct
{
	void *mbuf;				/* malloc'ed buffer */
	aimage_v1_header_t *header;		/* data follows directly after header */
}
image_t;

static int header_mode_is_prepend (unsigned int image_type);
static int RSA_prefactor_sign (unsigned int *m, unsigned int *d, unsigned int *p, int words);

static char *appname;


/*******************************************************************************
*******************************************************************************/

static void unaligned_write32_be (unsigned char *dst, unsigned int value)
{
	unsigned int bytes_remaining = 4;

	do {
		value = (value << 8) | (value >> 24);
		*dst++ = (unsigned char) value;
	}
	while (--bytes_remaining);
}

static int parse_hex_16b (unsigned char *out_bin, char *in_string)
{
	int i;
	char buf[4];
	unsigned int ch;

	for (i = 0; i < 16; i++) {
		buf[0] = in_string[(2 * i) + 0];
		buf[1] = in_string[(2 * i) + 1];
		buf[2] = 0;
		if (sscanf (buf, "%02x", &ch) != 1)
			return -1;
		out_bin[i] = ch;
	}

	return 0;
}

void sha1sum_buffer (unsigned char *hash, unsigned char *buf, unsigned int length)
{
	SHA_CTX context;

	SHA1_Init (&context);
	SHA1_Update (&context, buf, length);
	SHA1_Final (hash, &context);
}

void sha1sum_display (unsigned char *buf, unsigned int length)
{
	int i;
	unsigned char hash[SHA_DIGEST_LENGTH];

	sha1sum_buffer (hash, buf, length);

	printf ("sha1sum 0x%08lx..0x%08lx: " , (unsigned long) buf, (unsigned long) buf + length - 1);
	for (i = 0; i < SHA_DIGEST_LENGTH; i++)
		printf ("%02x", hash[i]);
	printf (" (%d bytes)\n", length);
}

static int image_display_details (image_t *image, unsigned int max_length)
{
	return aimage_v1_display_info (image->header, max_length, IMGA_PUBLIC_KEY);
}

static int image_set_defaults (image_t *image, unsigned int image_type)
{
	aimage_v1_header_t *header = image->header;

	switch (image_type)
	{
		/*
		   aimage header is prepended to original data
		*/
		case IMG_TYPE_DEBUG_MESSAGE:
		case IMG_TYPE_ENV:
		case IMG_TYPE_ETH0MAC:
		case IMG_TYPE_FIRMWARE_BLOB:
		case IMG_TYPE_INITFS_SQUASHFS:
		case IMG_TYPE_INITFS_ZEXT2:
		case IMG_TYPE_INITRD:
		case IMG_TYPE_JBC:
		case IMG_TYPE_RBF:
		case IMG_TYPE_ROPIC:
		case IMG_TYPE_SLAVELOADER:
		case IMG_TYPE_SPLASHSCREEN:
		case IMG_TYPE_UIMAGE:
		case IMG_TYPE_ZIMAGE:
			header->flags |= IMG_FLAG_PREPEND;
			break;

		/*
		   aimage header over-writes the start of the image data.
		*/
		case IMG_TYPE_APPFS_CRAMFS:
		case IMG_TYPE_APPFS_EXT2:
		case IMG_TYPE_BLOADER:
		case IMG_TYPE_BLOADER_DFU:
		case IMG_TYPE_BLOADER_TESTMODE:
		case IMG_TYPE_BOOTCOUNT:
		case IMG_TYPE_BOOTSELECTION:
		case IMG_TYPE_CEXAPP:
		case IMG_TYPE_GNFS_EXT2:
		case IMG_TYPE_INITFS_CRAMFS:
		case IMG_TYPE_SLAVEAPP:
		case IMG_TYPE_UBOOT_MIPSEB:
		case IMG_TYPE_UBOOT_MIPSEL:
			header->flags &= ~IMG_FLAG_PREPEND;
			break;

		default:
			fprintf (stderr, "Unrecognised image type 0x%08x\n", image_type);
			exit (1);
	}

	header->magic = IMG_MAGIC;
	header->magic2 = IMG_MAGIC2;
	header->type = image_type;

	return 0;
}

static int header_mode_is_prepend (unsigned int image_type)
{
	image_t dummy_image;
	aimage_v1_header_t dummy_header;

	memset (&dummy_header, 0, sizeof(dummy_header));
	dummy_image.header = &dummy_header;
	image_set_defaults (&dummy_image, image_type);
	return (dummy_header.flags & IMG_FLAG_PREPEND) ? 1 : 0;
}


/*******************************************************************************
*******************************************************************************/

static int image_load (image_t *image, char *filename, unsigned int image_type,
		       int unsigned force_overwrite, unsigned int prepend_size)
{
	int fd;
	struct stat file_info;
	unsigned char *buffer;
	unsigned int buffer_length;
	unsigned int data_start_offset;
	unsigned int data_length;
	unsigned char *data;

	if ((fd = open (filename, O_RDONLY)) < 0) {
		char buf[2048];
		memset (buf, 0, sizeof(buf));
		snprintf (buf, sizeof(buf) - 1, "\nError opening '%s' for reading ", filename);
		perror (buf);
		fprintf (stderr, "\n");
		exit (1);
	}

	fstat (fd, &file_info);
	data_length = file_info.st_size;						/* length of original input data */
	buffer_length = ((2 * sizeof(aimage_v1_header_t)) + data_length + PADDING_MAX);	/* allow for worst case padding... */

	buffer = malloc (buffer_length);
	if (buffer == NULL) {
		fprintf (stderr, "Unable to allocate %d bytes of buffer memory\n", buffer_length);
		exit (1);
	}

	memset (buffer, 0, buffer_length);

	image->mbuf = buffer;
	image->header = (aimage_v1_header_t *) buffer;

	if (header_mode_is_prepend (image_type)) {			/* header is prepended to image data */
		data_start_offset = prepend_size;
	}
	else {								/* header forms part of image data */
		if (prepend_size != PREPEND_SIZE_MIN) {
			fprintf (stderr, "\nError: Custom prependsize not applicable with 'header overwrite' imagetypes !!\n\n");
			exit (1);
		}
		if (data_length < sizeof(aimage_v1_header_t)) {
			fprintf (stderr, "\nError: Can't overwrite image: image is smaller than header !!\n\n");
			exit (1);
		}
		data_start_offset = 0;
	}

	data = &buffer[data_start_offset];

	fprintf (stderr, "Reading %d bytes from '%s'...\n", data_length, filename);
	if (read (fd, data, data_length) != data_length) {
		char buf[2048];
		memset (buf, 0, sizeof(buf));
		snprintf (buf, sizeof(buf) - 1, "\nError reading from '%s' ", filename);
		perror (buf);
		fprintf (stderr, "\n");
		exit (1);
	}
	close (fd);

	/*
	   If header mode is overwrite, we can safely tag an image multiple times with no special precautions.
	   If the header mode is prepend, we must check for an existing header before adding a new one (if
	   one exists, we must overwrite it).
	*/
	if (header_mode_is_prepend (image_type)) {
		if ((aimage_v1_verify_hash ((aimage_v1_header_t *) data, image_type, data_length) == 0) ||
		    (force_overwrite && (aimage_v1_sanity_check ((aimage_v1_header_t *) data, image_type, data_length) == 0)))
		{
			if (((aimage_v1_header_t *) data)->data_start_offset != data_start_offset) {
				fprintf (stderr, "\nError: Change in prependsize not supported with forceoverwrite\n\n");
				exit (1);
			}
			fprintf (stderr, "Existing header found in prepend mode - overwriting instead\n");
			image->header = (aimage_v1_header_t *) data;
			data_length = ((aimage_v1_header_t *) data)->data_length;
		}
		else if (force_overwrite) {
			fprintf (stderr, "\nError: Attempted to force overwrite... but had to give up\n\n");
			exit (1);
		}
	}

	image->header->magic = IMG_MAGIC;
	image->header->magic2 = IMG_MAGIC2;
	image->header->type = image_type;
	image->header->length = data_start_offset + data_length;		/* header + data (and eventually also + padding etc) */
	image->header->data_start_offset = data_start_offset;
	image->header->data_length = data_length;				/* length of original data file */
	image->header->build_host_offset = 0;
	image->header->build_time = (unsigned int) time (NULL);

#if 0
	printf ("total_length      : %8d\n", image->header->length);
	printf ("data_start_offset : %8d\n", image->header->data_start_offset);
	printf ("data_length       : %8d\n", image->header->data_length);
#endif

	return 0;
}

static int image_data_pad_extend (image_t *image, unsigned int min_padding, unsigned int modulo, unsigned char padvalue)
{
	unsigned int delta;
	unsigned int data_length_new;
	unsigned int data_length_orig;

	if ((image->header->data_length + image->header->data_start_offset) != (image->header->length)) {
		fprintf (stderr, "\n%s: Error: image data end != image end\n\n", __FUNCTION__);
		exit (1);
	}

	data_length_orig = image->header->data_length;
	data_length_new = data_length_orig + min_padding;
	data_length_new = (((data_length_new + modulo - 1) / modulo) * modulo);
	delta = (data_length_new - data_length_orig);

	if (delta != 0) {
		image->header->length += delta;
		image->header->data_length += delta;
		memset ((((char *) image->header) + image->header->data_start_offset + data_length_orig), padvalue, delta);
//		printf ("%s: %d (data_length: %d, length: %d)\n", __FUNCTION__, delta, image->header->data_length, image->header->length);
	}

	return 0;
}

static int image_pad_extend (image_t *image, unsigned int min_padding, unsigned int modulo, unsigned char padvalue)
{
	unsigned int delta;
	unsigned int total_length_new;
	unsigned int total_length_orig;

	total_length_orig = image->header->length;
	total_length_new = total_length_orig + min_padding;
	total_length_new = (((total_length_new + modulo - 1) / modulo) * modulo);
	delta = (total_length_new - total_length_orig);

	if (delta != 0) {
		image->header->length += delta;
		memset ((((char *) image->header) + total_length_orig), padvalue, delta);
//		printf ("%s: %d (data_length: %d, length: %d)\n", __FUNCTION__, delta, image->header->data_length, image->header->length);
	}

	return 0;
}

static int image_set_hname (image_t *image)
{
	unsigned int total_length;

	total_length = image->header->length;

	if (gethostname (((char *) image->header) + total_length, (HOST_STRING_LENGTH_MAX - 1)) == 0) {
		image->header->build_host_offset = total_length;
		total_length += (strlen (((char *) image->header) + total_length) + 1 + 0x03) & ~0x03;
		image->header->length = total_length;
	}

	return 0;
}

static int image_init_iv (image_t *image, unsigned int reserved_for_counter)
{
	int fd;
	int result;
	int bytes_read;
	int bytes_required;
	aimage_v1_header_t *header = image->header;

	if (reserved_for_counter > 8) {
		fprintf (stderr, "\n%s: Error: reserved_for_counter out of range\n\n", __FUNCTION__);
		exit (1);
	}

	memset (&header->iv, 0, sizeof(image->header->iv));
	bytes_required = (sizeof(header->iv) - reserved_for_counter);

	if (bytes_required) {
		if ((fd = open ("/dev/urandom", O_RDONLY)) < 0) {
			perror ("\nError opening '/dev/urandom' for reading");
			fprintf (stderr, "\n");
			exit (1);
		}
		bytes_read = 0;
		while (1) {
			result = read (fd, ((unsigned char *) &header->iv) + bytes_read, bytes_required);
			if ((result < 0) && (errno == EINTR))
				continue;
			if (result <= 0) {
				perror ("\nError reading from '/dev/urandom'");
				fprintf (stderr, "\n");
				exit (1);
			}
			bytes_read += result;
			bytes_required -= result;
			if (bytes_required == 0)
				break;
		}
		close (fd);
	}

	return 0;
}

static int image_encrypt_cbc_onepass (image_t *image, unsigned char *keydata)
{
	int enc_length;
	AES_KEY openssl_key;
	unsigned char *enc_start;
	unsigned char *enc_limit;
	unsigned char ivbuf[AES_BLOCKSIZE];
	unsigned char hash_pt1[SHA_DIGEST_LENGTH];
	unsigned char hash_ct1[SHA_DIGEST_LENGTH];
	unsigned char hash_pt2[SHA_DIGEST_LENGTH];
	unsigned char hash_ct2[SHA_DIGEST_LENGTH];
	aimage_v1_header_t *header = image->header;

	if ((header->data_length % AES_BLOCKSIZE) != 0) {
		fprintf (stderr, "\n%s: Error: image data_length is not an exact number of AES blocks\n\n", __FUNCTION__);
		exit (1);
	}

	if ((header->data_start_offset + header->data_length) != header->length) {
		fprintf (stderr, "\n%s: Error: image data end != image end\n\n", __FUNCTION__);
		exit (1);
	}

	enc_start = ((unsigned char *) header) + sizeof(aimage_v1_header_t);
	enc_limit = ((unsigned char *) header) + header->data_start_offset + header->data_length;
	enc_length = ((unsigned long) enc_limit) - ((unsigned long) enc_start);

	if ((enc_length < AES_BLOCKSIZE) != 0) {
		fprintf (stderr, "\n%s: Error: enc_length too short\n\n", __FUNCTION__);
		exit (1);
	}

	if ((enc_length % AES_BLOCKSIZE) != 0) {
		fprintf (stderr, "\n%s: Error: enc_length is not an exact number of AES blocks\n\n", __FUNCTION__);
		exit (1);
	}

//	printf ("%s: enc_length == %d\n", __FUNCTION__, enc_length);

	sha1sum_buffer (hash_pt1, enc_start, enc_length);
//	sha1sum_display (enc_start, enc_length);

	memcpy (ivbuf, &header->iv, sizeof(ivbuf));
	AES_set_encrypt_key (keydata, 128, &openssl_key);
	AES_cbc_encrypt ((const unsigned char *) enc_start, enc_start, enc_length, &openssl_key, ivbuf, AES_ENCRYPT);

	sha1sum_buffer (hash_ct1, enc_start, enc_length);
//	sha1sum_display (enc_start, enc_length);

	memcpy (ivbuf, &header->iv, sizeof(ivbuf));
	AES_set_decrypt_key (keydata, 128, &openssl_key);
	AES_cbc_encrypt ((const unsigned char *) enc_start, enc_start, enc_length, &openssl_key, ivbuf, AES_DECRYPT);

	sha1sum_buffer (hash_pt2, enc_start, enc_length);
//	sha1sum_display (enc_start, enc_length);

	memcpy (ivbuf, &header->iv, sizeof(ivbuf));
	AES_set_encrypt_key (keydata, 128, &openssl_key);
	AES_cbc_encrypt ((const unsigned char *) enc_start, enc_start, enc_length, &openssl_key, ivbuf, AES_ENCRYPT);

	sha1sum_buffer (hash_ct2, enc_start, enc_length);
//	sha1sum_display (enc_start, enc_length);

	if ((memcmp (hash_pt1, hash_pt2, SHA_DIGEST_LENGTH) != 0) ||
	    (memcmp (hash_ct1, hash_ct2, SHA_DIGEST_LENGTH) != 0))
	{
		fprintf (stderr, "\n%s: Error: encrypt -> decrypt -> encrypt sanity check failed\n\n", __FUNCTION__);
		exit (1);
	}

	return 0;
}

static int image_encrypt_cbc_groups_of_blocks (image_t *image, unsigned char *keydata, unsigned int group_size)
{
	int group, groups;
	unsigned int base;
	unsigned int limit;
	unsigned int start_offset;
	unsigned int start_offset_abs;
	unsigned int enc_start_block;
	unsigned char *enc_start;
	unsigned char *enc_limit;
	unsigned char ivbuf[AES_BLOCKSIZE];
	aimage_v1_header_t *header = image->header;
	AES_KEY openssl_key;

	if ((group_size % AES_BLOCKSIZE) != 0) {
		fprintf (stderr, "\n%s: Error: group size %d is not an exact number of AES blocks\n\n", __FUNCTION__, group_size);
		exit (1);
	}

	if ((header->data_length % group_size) != 0) {
		fprintf (stderr, "\n%s: Error: image data_length is not an exact number of %d byte block groups\n\n", __FUNCTION__, group_size);
		exit (1);
	}

	if ((header->data_length + header->data_start_offset) != (header->length)) {
		fprintf (stderr, "\n%s: Error: image data end != image end\n\n", __FUNCTION__);
		exit (1);
	}

	groups = (header->data_length / group_size);

	if (groups < 1) {
		fprintf (stderr, "\nError: image data_length too small... ?!?\n\n");
		exit (1);
	}

	if (header->iv[3] != 0) {
		fprintf (stderr, "\nError: least sig 32bits of IV should be zero when using AES in a 'groups-of-blocks' mode...\n\n");
		exit (1);
	}

//	sha1sum_display (enc_start, enc_length);

	AES_set_encrypt_key (keydata, (AES_KEYSIZE * 8), &openssl_key);

	for (group = 0; group < groups; group++) {
		if (header->data_start_offset == 0) {
			base = group * group_size;
			limit = base + group_size;
			start_offset = (group == 0) ? sizeof(aimage_v1_header_t) : 0;
			start_offset_abs = base + start_offset;
			enc_start_block = start_offset_abs / AES_BLOCKSIZE;
			enc_start = ((unsigned char *) header) + start_offset_abs;
			enc_limit = ((unsigned char *) header) + limit;
		}
		else {
			base = (group * group_size) + header->data_start_offset;
			limit = base + group_size;
			start_offset = 0;
			start_offset_abs = base + start_offset;
			enc_start_block = (start_offset_abs - header->data_start_offset) / AES_BLOCKSIZE;
			enc_start = ((unsigned char *) header) + start_offset_abs;
			enc_limit = ((unsigned char *) header) + limit;
		}

//		printf ("%s: start_offset_abs: 0x%08x, enc_start_block: %d\n", __FUNCTION__, start_offset_abs, enc_start_block);

		memcpy (ivbuf, &header->iv, 12);
		unaligned_write32_be (&ivbuf[12], enc_start_block);

		AES_cbc_encrypt (enc_start,			/* pt data in */
				 enc_start,			/* ct data out */
				 (enc_limit - enc_start),	/* bytecount */
				 &openssl_key,
				 ivbuf, 			/* initial IV in and final IV out */
				 AES_ENCRYPT);
	}

	return 0;
}

/*
   hash of entire image excluding header->hash and header->signature
*/
static int image_generate_hash (image_t *image)
{
	SHA_CTX context;
	aimage_v1_header_t *header = image->header;

	unsigned char *hash_part1_start  = (unsigned char *) header;
	unsigned int hash_part1_length = 60;
	unsigned char *hash_part2_start  = (unsigned char *) &header->reserved;
	unsigned int hash_part2_length = header->length - (hash_part2_start - ((unsigned char *) header));

	SHA1_Init (&context);
	SHA1_Update (&context, hash_part1_start, hash_part1_length);
	SHA1_Update (&context, hash_part2_start, hash_part2_length);
	SHA1_Final ((unsigned char *) header->hash, &context);

	return 0;
}

/*
   signature of hash of entire image excluding header->signature
*/
static int image_sign (image_t *image, int nullsignature)
{
	SHA_CTX context;
	aimage_v1_header_t *header = image->header;

	int i;
	int fd;
	unsigned int hash[5];

	unsigned char *hash_part1_start = (unsigned char *) header;
	unsigned int hash_part1_length = 60;
	unsigned char *hash_part2_start = (unsigned char *) &header->hash;
	unsigned int hash_part2_length = header->length - (hash_part2_start - ((unsigned char *) header));

	if (nullsignature) {
		memset (header->signature, 0, sizeof(header->signature));
		return 0;
	}

	SHA1_Init (&context);
	SHA1_Update (&context, hash_part1_start, hash_part1_length);
	SHA1_Update (&context, hash_part2_start, hash_part2_length);
	SHA1_Final ((unsigned char *) hash, &context);

	if ((fd = open ("/dev/urandom", O_RDONLY)) < 0) {
		perror ("\nError opening '/dev/urandom' for reading");
		fprintf (stderr, "\n");
		exit (1);
	}

	/*
	   Prefill signature with random data, leaving the least sig
	   160 bits empty (will hold value derived from hash information).
	*/
	read (fd, &header->signature[5], sizeof(header->signature) - 20);

	/*
	   Tweak signature most significant word to ensure that raw signature
	   is mod p correct before we try to exponentiate.
	*/
	while (header->signature[RSA_MOD_WORDS_1024 - 1] >= MODULUS[RSA_MOD_WORDS_1024 - 1])
		read (fd, &header->signature[RSA_MOD_WORDS_1024 - 1], 4);

	close (fd);

	/*
	   exor hash into raw signature
	*/
	for (i = 0; i < 5; i++)
		header->signature[i] = hash[i] ^ header->signature[RSA_MOD_WORDS_1024 - 5 + i];

#if 0
	for (i = 0; i < 5; i++)
		printf ("hash[%d] = 0x%08x%s", i, hash[i], (i == 4) ? "\n" : " ");
	for (i = 0; i < 5; i++)
		printf ("hash[%d] = 0x%08x%s", i, header->signature[i] ^ header->signature[RSA_MOD_WORDS_1024 - 5 + i], (i == 4) ? "\n" : " ");
#endif

	/*
	   Prefactor (multiply by R^-1 mod p, which saves a conversion from montgomery
	   space in the verifier) the raw signature and exponentiate mod p with the
	   private exponent. Write result directly into header->signature.
	*/
	return RSA_prefactor_sign (header->signature, PRIVATE_EXPONENT, MODULUS, RSA_MOD_WORDS_1024);
}

static int image_save (image_t *image, char *filename)
{
	int fd;
	struct stat file_info;
	unsigned int total_length = image->header->length;

	if ((fd = open (filename, (O_WRONLY | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR))) < 0) {
		char buf[2048];
		memset (buf, 0, sizeof(buf));
		snprintf (buf, sizeof(buf) - 1, "\nError opening '%s' for writing ", filename);
		perror (buf);
		fprintf (stderr, "\n");
		exit (1);
	}

#if 0
	fprintf (stderr, "Writing %8d bytes to   '%s'...\n", total_length, filename);
#endif

	if (write (fd, image->header, total_length) != total_length) {
		char buf[2048];
		memset (buf, 0, sizeof(buf));
		snprintf (buf, sizeof(buf) - 1, "\nError writing to '%s' ", filename);
		perror (buf);
		fprintf (stderr, "\n");
		exit (1);
	}

	fstat (fd, &file_info);
	close (fd);
	free (image->mbuf);

	if (file_info.st_size != total_length) {
		fprintf (stderr, "Fatal: Something went wrong while writing output file...\n");
		fflush (NULL);
		exit (1);
	}

	return 0;
}

static void syntax (void)
{
#if 0
	char spacer[32];
	int appname_length = strlen (appname);

	if (appname_length >= sizeof(spacer))
		appname_length = (sizeof(spacer) - 1);

	memset (spacer, 0, sizeof(spacer));
	memset (spacer, ' ', appname_length);
#endif

	fprintf (stderr, "\n");
	fprintf (stderr, "Usage: --type           <image_type>                    (no default)\n");
	fprintf (stderr, "       --releaseid      <hex value>                     (default: '0x%08x')\n", DEFAULT_RELEASE_ID);
	fprintf (stderr, "       --platformid     <hex value>                     (default: '0x%08x')\n", DEFAULT_PLATFORM_ID);
	fprintf (stderr, "       --encmode        <none|cbc_onepass|cbc_4kblocks> (default: none)\n");
	fprintf (stderr, "       --aeskey         <16 hex bytes>                  (no default)\n");
	fprintf (stderr, "       --forceiv        <16 hex bytes>                  (default: randomised IV)\n");
	fprintf (stderr, "       --padmodulo      <integer>[,<integer>]           (default: 4,0)\n");
	fprintf (stderr, "       --prependsize    <integer>                       (default: %u)\n", (unsigned int) PREPEND_SIZE_MIN);
	fprintf (stderr, "       --linkaddress    <hex value>                     (default: don't update)\n");
	fprintf (stderr, "       --entryoffset    <hex value>                     (default: don't update)\n");
	fprintf (stderr, "       --forceoverwrite\n");
	fprintf (stderr, "       --nullsignature                                  (format header etc but clear signature)\n");
	fprintf (stderr, "       <input file> [output file]\n");
	fprintf (stderr,"\n");

	fprintf (stderr, "If output file is not given, original input file will be overwritten\n\n");

	exit (1);
}

int main (int argc, char *argv[])
{
	char *s;
	image_t image;
	unsigned char iv[AES_KEYSIZE] = { 0 };
	unsigned char aeskey[AES_KEYSIZE] = { 0 };

#if defined (HARDCODED_RELEASE_ID)
	unsigned int release_id = HARDCODED_RELEASE_ID;
#else
	unsigned int release_id = DEFAULT_RELEASE_ID;
#endif
	unsigned int platform_id = DEFAULT_PLATFORM_ID;
	unsigned int image_type = IMG_TYPE_INVALID;
	unsigned int link_address = 0xFFFFFFFF;
	unsigned int entry_point_offset = 0xFFFFFFFF;
	unsigned int force_overwrite = 0;
	unsigned int nullsignature = 0;
	unsigned int padmodulo = 0;
	unsigned int padmodulo_offset = 0;
	unsigned int prepend_size = PREPEND_SIZE_MIN;
	unsigned int append_hname = 1;
	unsigned int iv_valid = 0;
	unsigned int aeskey_valid = 0;
	unsigned int encmode = ENCMODE_NONE;
	char *appendstring = NULL;
	char *input_filename = NULL;
	char *output_filename = NULL;
	int option_index = 0;

	struct option long_options[] =
	{
		{ "aeskey",         required_argument, 0, 'a' },
		{ "encmode",        required_argument, 0, 'c' },
		{ "entryoffset",    required_argument, 0, 'e' },
		{ "forceiv",        required_argument, 0, 'i' },
		{ "forceoverwrite", no_argument,       0, 'f' },
		{ "help",           no_argument,       0, 'h' },
		{ "nohname",        no_argument,       0, 'H' },
		{ "appendstring",   required_argument, 0, 's' },
		{ "linkaddress",    required_argument, 0, 'l' },
		{ "nullsignature",  no_argument,       0, 'n' },
		{ "padmodulo",      required_argument, 0, 'm' },
		{ "prependsize",    required_argument, 0, 'P' },
		{ "platformid",     required_argument, 0, 'p' },
		{ "releaseid",      required_argument, 0, 'r' },
		{ "type",           required_argument, 0, 't' },

		{ 0, 0, 0, 0 }
	};

	appname = argv[0];

	for (s = appname; *s != '\0'; )
		if (*s++ == '/')
			appname = s;

	if ((strncmp (appname, "aimage_sign_", 12) != 0) &&
	    (strncmp (appname, "aimage_sign.", 12) != 0))
	{
		appname = "aimage_sign";
	}

	printf ("\n%s: (c) Andre McCurdy, Trident Microsystems. Rev %s (%s)\n\n", appname, REVISION_STRING, __DATE__);

	while (1)
	{
		unsigned int major, minor, build;
		int c = getopt_long (argc, argv, "a:c:e:i:fhHl:nm:p:r:t:", long_options, &option_index);	/* ':' -> required, '::' -> optional */

		if (c == -1)
			break;

		switch (c)
		{
			case 0: 	/* option set a flag. no more processing required */
				break;

			case 'a':
				if (parse_hex_16b (aeskey, optarg) != 0) {
					fprintf (stderr, "Unable to parse AES key '%s'\n", optarg);
					syntax();
				}
				aeskey_valid = 1;
				break;

			case 'c':
				if (strcmp (optarg, "none") == 0) {
					encmode = ENCMODE_NONE;
				}
				else if (strcmp (optarg, "cbc_onepass") == 0) {
					encmode = ENCMODE_CBC_ONEPASS;
				}
				else if ((strcmp (optarg, "cbc_4kblks") == 0) ||
					 (strcmp (optarg, "cbc_4kblocks") == 0))
				{
					encmode = ENCMODE_CBC_4KBLOCKS;
				}
				else {
					fprintf (stderr, "Unable to parse encmode option '%s' (expecting cbc_onepass or cbc_4kblocks)\n", optarg);
					syntax();
				}
				break;

			case 'e':
				if (sscanf (optarg, "0x%x", &entry_point_offset) != 1) {
					fprintf (stderr, "Unable to parse entryoffset option '%s' (expecting a 32bit hex value)\n", optarg);
					syntax();
				}
				break;

			case 'f':
				force_overwrite = 1;
				break;

			case 'H':
				append_hname = 0;
				break;

			case 's':
				appendstring = optarg;
				break;

			case 'i':
				if (parse_hex_16b (iv, optarg) != 0) {
					fprintf (stderr, "Unable to parse IV '%s'\n", optarg);
					syntax();
				}
				iv_valid = 1;
				break;

			case 'l':
				if (sscanf (optarg, "0x%x", &link_address) != 1) {
					fprintf (stderr, "Unable to parse linkaddress option '%s' (expecting a 32bit hex value)\n", optarg);
					syntax();
				}
				break;

			case 'm':
				if (strstr (optarg, ",") != NULL) {
					if (sscanf (optarg, "%u,%u", &padmodulo, &padmodulo_offset) != 2) {
						fprintf (stderr, "Unable to parse padmodulo option '%s' (expecting an unsigned decimal value)\n", optarg);
						syntax();
					}
				}
				else {
					if (sscanf (optarg, "%u", &padmodulo) != 1) {
						fprintf (stderr, "Unable to parse padmodulo option '%s' (expecting an unsigned decimal value)\n", optarg);
						syntax();
					}
				}
				if (padmodulo > PADMODULO_MAX) {
					fprintf (stderr, "padmodulo value '%d' out of range...\n", padmodulo);
					syntax();
				}
				break;

			case 'n':
				nullsignature = 1;
				break;

			case 'P':
				if (sscanf (optarg, "%u", &prepend_size) != 1) {
					fprintf (stderr, "Unable to parse prependsize option '%s' (expecting an unsigned decimal value)\n", optarg);
					syntax();
				}
				break;

			case 'p':
				if (sscanf (optarg, "0x%x", &platform_id) != 1) {
					fprintf (stderr, "Unable to parse platformid option '%s' (expecting a 32bit hex value)\n", optarg);
					syntax();
				}
				break;

			case 'r':
				if (sscanf (optarg, "0x%x", &release_id) == 1)
					break;
				if (sscanf (optarg, "%u.%u-%u", &major, &minor, &build) == 3) {
					if ((major <= 0xFF) && (minor <= 0xFF) && (build <= 0xFFFF)) {
						release_id = ((major << 24) | (minor << 16) | build);
						break;
					}
					fprintf (stderr, "Unable to parse releaseid option '%s' (major, minor or build number out of range)\n", optarg);
				}
				else {
					fprintf (stderr, "Unable to parse releaseid option '%s' (expecting 32bit hex value or XX.YY-ZZZ)\n", optarg);
				}
				syntax();
				break;

			case 't':
				image_type = aimage_string_to_type (optarg);
				break;

			case '?':              /* missing argument */
			case 'h':
			default:
				syntax();
				break;
		}
	}

//	printf ("optind = %2d\n", optind);
//	printf ("argc   = %2d\n", argc);

	if (optind < argc)
		input_filename = argv[optind++];
	else {
		fprintf (stderr, "Error: Please specify a filename to use as the raw input image\n");
		syntax();
	}

	if (optind < argc)
		output_filename = argv[optind++];
	else
		output_filename = input_filename;

	if (optind < argc) {
		fprintf (stderr, "Error: Too many options...\n");
		syntax();
	}

	if (image_type == IMG_TYPE_INVALID) {
		fprintf (stderr, "Error: Please specify an image type...\n");
		syntax();
	}

	if ((prepend_size < PREPEND_SIZE_MIN) || (prepend_size > PREPEND_SIZE_MAX)) {
		fprintf (stderr, "Error: prependsize out of range...\n");
		syntax();
	}

	if ((encmode == ENCMODE_NONE) && (aeskey_valid)) {
		fprintf (stderr, "Error: --aeskey was given, but --encmode was not...\n");
		syntax();
	}

	if ((encmode != ENCMODE_NONE) && (aeskey_valid == 0)) {
		fprintf (stderr, "Error: --encmode was given, but --aeskey was not...\n");
		syntax();
	}

#if 0
	printf ("image type = 0x%08x\n", image_type);
	printf ("releaseid  = 0x%08x\n", release_id);
	printf ("platformid = 0x%08x\n", platform_id);

	if (aeskey_valid) {
		printf ("AES key: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
			aeskey[0], aeskey[1], aeskey[ 2], aeskey[ 3], aeskey[ 4], aeskey[ 5], aeskey[ 6], aeskey[ 7],
			aeskey[8], aeskey[9], aeskey[10], aeskey[11], aeskey[12], aeskey[13], aeskey[14], aeskey[15]);
	}

	if (aeskey_valid) {
		printf ("iv: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
			iv[0], iv[1], iv[ 2], iv[ 3], iv[ 4], iv[ 5], iv[ 6], iv[ 7],
			iv[8], iv[9], iv[10], iv[11], iv[12], iv[13], iv[14], iv[15]);
	}

	if (appendstring != NULL)
		printf ("appendstring: %s\n\n", appendstring);
#endif

	image_load (&image, input_filename, image_type, force_overwrite, prepend_size);
	image_set_defaults (&image, image_type);
#if defined (HARDCODED_RELEASE_ID)
	image.header->release_id = HARDCODED_RELEASE_ID;
#else
	image.header->release_id = release_id;
#endif
	image.header->platform_id = platform_id;

	if (link_address != 0xFFFFFFFF)
		image.header->data_link_address = link_address;

	if (entry_point_offset != 0xFFFFFFFF)
		image.header->data_entry_point_offset = entry_point_offset;

	if (encmode == ENCMODE_CBC_ONEPASS) {
		image.header->flags &= ~IMG_FLAG_ENCMODE_MASK;
		image.header->flags |= IMG_FLAG_ENCMODE_CBC_ONEPASS;
		image_data_pad_extend (&image, 0, AES_BLOCKSIZE, 0);
		if (iv_valid)
			memcpy (&image.header->iv, iv, sizeof(image.header->iv));
		else
			image_init_iv (&image, 0);
		image_encrypt_cbc_onepass (&image, aeskey);
	}
	else if (encmode == ENCMODE_CBC_4KBLOCKS) {
		image.header->flags &= ~IMG_FLAG_ENCMODE_MASK;
		image.header->flags |= IMG_FLAG_ENCMODE_CBC_4KBLOCKS;
		image_data_pad_extend (&image, 0, 4096, 0);
		if (iv_valid)
			memcpy (&image.header->iv, iv, sizeof(image.header->iv));
		else
			image_init_iv (&image, 4);
		image_encrypt_cbc_groups_of_blocks (&image, aeskey, 4096);
	}

	if (append_hname) {
		image_pad_extend (&image, 1, 16, 0);
		image_set_hname (&image);
	}

	image_pad_extend (&image, 0, 4, 0);

	if (padmodulo != 0) {
		unsigned int bytes_over = ((image.header->length + padmodulo_offset) % padmodulo);
		if (bytes_over != 0) {
			unsigned int padding_bytes = (padmodulo - bytes_over);
//			printf ("padding_bytes: %d\n", padding_bytes);
			image_pad_extend (&image, padding_bytes, 1, 0xFF);
		}
	}

	image_generate_hash (&image);
	image_sign (&image, nullsignature);
	image_display_details (&image, IMAGE_LENGTH_MAX);	/* fixme: use a realistic max length... */
	image_save (&image, output_filename);			/* saving the image free memory allocated to hold it, so no more access to image is possible !! */

	return 0;
}


/*******************************************************************************
*******************************************************************************/

static void *malloc_eoe (size_t size)
{
	void *result = malloc (size);

	if (result == NULL) {
		fprintf (stderr, "malloc failure\n");
		exit (1);
	}

	return result;
}

static int RSA_prefactor_sign (unsigned int *m, unsigned int *d, unsigned int *p, int words)
{
	unsigned int exp;
	unsigned int *tbn = malloc_eoe (4 * (words + 1));
	unsigned int *sig = malloc_eoe (4 * (words + 1));
	unsigned int *ver = malloc_eoe (4 * (words + 1));

	BIGNUM *openssl_m = abn_to_opensslbn (m, words);
	BIGNUM *openssl_d = abn_to_opensslbn (d, words);
	BIGNUM *openssl_p = abn_to_opensslbn (p, words);

	BIGNUM *R = BN_new();
	BIGNUM *Rinv = BN_new();
	BN_CTX *ctx = BN_CTX_new();
	BN_MONT_CTX *mont_ctx = BN_MONT_CTX_new();

	if ((R == NULL) || (Rinv == NULL) || (ctx == NULL) || (mont_ctx == NULL)) {
		fprintf (stderr, "openssl alloc failure\n");
		exit (1);
	}

	BN_MONT_CTX_set (mont_ctx, openssl_p, ctx);

#if 0
	abn_print ("mod_p : ", p, "\n", words);
	abn_print ("priv  : ", d, "\n", words);
	abn_print ("input : ", m, "\n", words);
#endif

	BN_one (R);
	BN_mod_mul_montgomery (Rinv, R, R, mont_ctx, ctx);               /* Rinv = 1 * 1 * R^-1 */
	BN_mod_inverse (R, Rinv, openssl_p, ctx);                        /* R    = Rinv^-1 mod p */

	BN_mod_mul (openssl_m, openssl_m, Rinv, openssl_p, ctx);         /* m = (m * R^-1)       mod p */
	BN_mod_exp (openssl_m, openssl_m, openssl_d, openssl_p, ctx);    /* m = (m * R^-1)^d     mod p */
	BN_mod_mul (openssl_m, openssl_m, R, openssl_p, ctx);            /* m = (m * R^-1)^d * R mod p */

	opensslbn_to_abn (sig, openssl_m, words);

#if 0
	abn_print ("sig   : ", sig, "\n", words);
#endif

	/*
	   verify by performing mont_mod_exp with public exponent and compare to original.
	*/

#if defined (AIMAGE_HARDCODED_EXP)
	 exp = AIMAGE_HARDCODED_EXP;
#else
	 exp = aimage_pubkey_exp (IMGA_PUBLIC_KEY);
#endif
	if (exp == 3) {
		BN_mod_mul_montgomery (openssl_d, openssl_m, openssl_m, mont_ctx, ctx);  /* d = m^2 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_m, mont_ctx, ctx);  /* d = m^3 mod p */
	}
	else if (exp == 17) {
		BN_mod_mul_montgomery (openssl_d, openssl_m, openssl_m, mont_ctx, ctx);  /* d = m^2 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^4 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^8 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^16 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_m, mont_ctx, ctx);  /* d = m^17 mod p */
	}
	else if (exp == 65537) {
		BN_mod_mul_montgomery (openssl_d, openssl_m, openssl_m, mont_ctx, ctx);  /* d = m^2 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^4 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^8 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^16 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^32 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^64 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^128 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^256 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^512 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^1024 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^2048 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^4096 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^8192 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^16384 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^32768 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_d, mont_ctx, ctx);  /* d = m^65536 mod p */
		BN_mod_mul_montgomery (openssl_d, openssl_d, openssl_m, mont_ctx, ctx);  /* d = m^65537 mod p */
	}
	else {
		printf ("Fatal: Unsupported public exponent !!\n");
		exit (1);
	}

	opensslbn_to_abn (ver, openssl_d, words);

#if 0
	abn_print ("verif1: ", ver, "\n", words);
#endif

	if (memcmp (ver, m, (words * 4)) != 0) {
		printf ("Fatal: Verified signature does not match original data !!\n");
		exit (1);
	}

	memcpy (m, sig, (words * 4));

	free (tbn);
	free (sig);
	free (ver);

	BN_free (R);
	BN_free (Rinv);
	BN_CTX_free (ctx);
	BN_MONT_CTX_free (mont_ctx);

	return 0;
}


/*******************************************************************************
*******************************************************************************/

