/*
 * 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.
  3 20080215    batelaan    Update file copyright/license text
  4 20090225    amccurdy    Add IMG_TYPE_INITFS_SQUASHFS image type
--------------------------------------------------------------------------------
*/

#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 "aimage.h"
#include "abn.h"


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

static const struct
{
    unsigned int type;
    char *string;
}
typestring_table[] =
{
    { IMG_TYPE_DEBUG_MESSAGE,       "debug_message"     },
    { IMG_TYPE_BLOADER,             "bloader"           },
    { IMG_TYPE_BLOADER_TESTMODE,    "bloader_testmode"  },
    { IMG_TYPE_BLOADER_DFU,         "bloader_dfu"       },
    { IMG_TYPE_SLAVELOADER,         "slaveloader"       },
    { IMG_TYPE_ENV,                 "env"               },
    { IMG_TYPE_RBF,                 "rbf"               },
    { IMG_TYPE_JBC,                 "jbc"               },
    { IMG_TYPE_CEXAPP,              "cxapp"             },
    { IMG_TYPE_SLAVEAPP,            "slaveapp"          },
    { IMG_TYPE_ZIMAGE,              "zimage"            },
    { IMG_TYPE_INITFS_CRAMFS,       "initfs_cramfs"     },
    { IMG_TYPE_INITFS_ZEXT2,        "initfs_zext2"      },
    { IMG_TYPE_APPFS_EXT2,          "appfs_ext2"        },
    { IMG_TYPE_APPFS_CRAMFS,        "appfs_cramfs"      },
    { IMG_TYPE_FIRMWARE_BLOB,       "firmware_blob"     },
    { IMG_TYPE_BOOTCOUNT,           "bootcount"         },
    { IMG_TYPE_BOOTSELECTION,       "bootselection"     },
    { IMG_TYPE_GNFS_EXT2,           "gnfs"              },
    { IMG_TYPE_ETH0MAC,             "eth0mac"           },
    { IMG_TYPE_ROPIC,               "ropic"             },
    { IMG_TYPE_INITRD,              "initrd"            },
    { IMG_TYPE_UIMAGE,              "uimage"            },
    { IMG_TYPE_UBOOT_MIPSEL,        "uboot_mipsel"      },
    { IMG_TYPE_UBOOT_MIPSEB,        "uboot_mipseb"      },
    { IMG_TYPE_INITFS_SQUASHFS,     "initfs_squashfs"   },
};

char *aimage_type_to_string (unsigned int type)
{
   int i;

   for (i = 0; i < (sizeof(typestring_table) / sizeof(typestring_table[0])); i++)
      if (typestring_table[i].type == type)
         return typestring_table[i].string;

   return "UNKNOWN";
}

unsigned int aimage_string_to_type (char *image_name)
{
   int i;

   for (i = 0; i < (sizeof(typestring_table) / sizeof(typestring_table[0])); i++)
      if (strcasecmp (image_name, typestring_table[i].string) == 0)
         return typestring_table[i].type;

   printf ("\nUnrecognised image type: '%s'\n", image_name);
   exit (1);
}


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

static const struct
{
	unsigned int encmode_flags;
	char *string;
}
encmode_string_table[] =
{
	{ IMG_FLAG_ENCMODE_NONE,            "none"          },
	{ IMG_FLAG_ENCMODE_CBC_ONEPASS,     "cbc_onepass"   },
	{ IMG_FLAG_ENCMODE_CBC_4KBLOCKS,    "cbc_4kblocks"  },
	{ IMG_FLAG_ENCMODE_CTR,             "ctr"           }
};

char *aimage_flags_to_encmode_string (unsigned int flags)
{
	int i;
	unsigned int encmode_flags = (flags & IMG_FLAG_ENCMODE_MASK);

	for (i = 0; i < (sizeof(encmode_string_table) / sizeof(encmode_string_table[0])); i++)
		if (encmode_string_table[i].encmode_flags == encmode_flags)
			return encmode_string_table[i].string;

	return "UNKNOWN";
}


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

/*
   WARNING: Result returned in a static buffer... not MT safe.
*/
char *aimage_release_id_to_string (unsigned int release_id)
{
    static char buf[20];    /* worst case: 255.255-65536 (ie 13 + nul) */

    sprintf (buf, "%d.%d-%d", RELEASE_ID_MAJOR(release_id),
                              RELEASE_ID_MINOR(release_id),
                              RELEASE_ID_BUILD(release_id));

    return buf;
}


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

unsigned int aimage_pubkey_sbdata (const aimage_keypub_t *key)
{
	int i;
	unsigned int sbdata = 0;

	for (i = 0; i < 4; i++)
		sbdata ^= (key->mod_data[i + 1] >> i);

	return sbdata;
}

unsigned int aimage_pubkey_exp (const aimage_keypub_t *key)
{
	switch ((aimage_pubkey_sbdata (key) >> 0) & 0x03)
	{
		case 0: return 3;
		case 1: return 17;
		case 2: return 65537;

		default:
			return 0;
	}
}

unsigned int aimage_pubkey_mod_words (const aimage_keypub_t *key)
{
	switch ((aimage_pubkey_sbdata (key) >> 2) & 0x07)
	{
		case 0: return RSA_MOD_WORDS_512;
		case 1: return RSA_MOD_WORDS_640;
		case 2: return RSA_MOD_WORDS_1024;
		case 3: return RSA_MOD_WORDS_1536;
		case 4: return RSA_MOD_WORDS_2048;

		default:
			return RSA_MOD_WORDS_RESERVED;
	}
}

unsigned int aimage_pubkey_mod_bits (const aimage_keypub_t *key)
{
	return aimage_pubkey_mod_words (key) * 32;
}


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

int aimage_v1_display_info (aimage_v1_header_t *header, unsigned int image_length_max, const aimage_keypub_t *key)
{
   int signature_ok = 0;
   int hash_ok = 0;
   time_t build_time = (time_t) header->build_time;

   if (aimage_v1_verify_signature_with_given_key (header, key, 0, image_length_max) == 0)
      signature_ok = 1;

   if (aimage_v1_verify_hash (header, 0, image_length_max) == 0)
      hash_ok = 1;

   printf ("Image type                    : %s\n",     aimage_type_to_string (header->type));
// printf ("Image flags                   : 0x%08x\n", header->flags);
   printf ("Image encmode                 : %s\n",     aimage_flags_to_encmode_string (header->flags));
   printf ("Image platform ID             : 0x%08x\n", header->platform_id);
   printf ("Image release ID              : 0x%08x : %s\n", header->release_id, aimage_release_id_to_string (header->release_id));
   printf ("Image length                  : %d\n",     header->length);
   printf ("Image data_start_offset       : %d\n",     header->data_start_offset);
   printf ("Image data length             : %d\n",     header->data_length);
   printf ("Image data link address       : 0x%08x\n", header->data_link_address);
   printf ("Image data entry point offset : 0x%08x\n", header->data_entry_point_offset);
   if (signature_ok)
   {
   printf ("Image build host              : %s\n",     (header->build_host_offset != 0) ? ((char *) header) + header->build_host_offset : "(unknown)");
   }
   printf ("Image build time              : %s",       ctime (&build_time));
   printf ("Image signature check         : %s\n",     (signature_ok) ? "PASSED :-)" : "FAILED !!" );
   printf ("Image hash check              : %s\n",     (hash_ok) ? "PASSED :-)" : "FAILED !!" );
   printf ("\n");

   return 0;
}


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

int aimage_v1_sanity_check (aimage_v1_header_t *header, unsigned int type, unsigned int length)
{
    if (((unsigned long) header) & 0x03)
        return -1;

    if ((header->magic != IMG_MAGIC) || (header->magic2 != IMG_MAGIC2) ||
        (type && (header->type != type)) || (header->length > length) ||
        (header->length < sizeof(aimage_v1_header_t)))
    {
        return -1;
    }

    return 0;       /* success */
}

int aimage_v1_verify_hash (aimage_v1_header_t *header, unsigned int type, unsigned int length)
{
    int i;
    SHA_CTX context;
    unsigned int t[5];
    unsigned char *hash_part2_start;
    unsigned int hash_part2_length;

    if ((i = aimage_v1_sanity_check (header, type, length)) != 0)
        return i;

    SHA1_Init (&context);
    SHA1_Update (&context, (unsigned char *) header, 60);
    hash_part2_start = (unsigned char *) &header->reserved;
    hash_part2_length = header->length - (hash_part2_start - ((unsigned char *) header));
    SHA1_Update (&context, hash_part2_start, hash_part2_length);
    SHA1_Final ((unsigned char *) t, &context);

    for (i = 0; i < 5; i++)
        if (t[i] != header->hash[i])
            return -1;

    return 0;       /* success */
}

int aimage_v1_verify_signature_with_given_key (aimage_v1_header_t *header, const aimage_keypub_t *key, unsigned int type, unsigned int length)
{
    int i;
    SHA_CTX context;
    unsigned int t[2][RSA_MOD_WORDS_1024 + 1];
    unsigned char *hash_part2_start;
    unsigned int hash_part2_length;

    if ((i = aimage_v1_sanity_check (header, type, length)) != 0)
        return i;

    if ((i = abn_mont_exp (t[1], t[0], header->signature, key)) != 0)
        return i;

    for (i = 0; i < 5; i++)
        t[1][i] ^= t[1][RSA_MOD_WORDS_1024 - 5 + i];

    SHA1_Init (&context);
    SHA1_Update (&context, (unsigned char *) header, 60);
    hash_part2_start = (unsigned char *) &header->hash;
    hash_part2_length = header->length - (hash_part2_start - ((unsigned char *) header));
    SHA1_Update (&context, hash_part2_start, hash_part2_length);
    SHA1_Final ((unsigned char *) t[0], &context);

    for (i = 0; i < 5; i++)
        if (t[0][i] != t[1][i])
            return -1;

    return 0;       /* success */
}


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

