/*!
 * \file    gen_cert.c
 * \brief   pebble certificate generation
 *
 * <hr>
 * \section LICENSE
 * Copyright Samsung Electronics, Co. Ltd.
 * Samsung Research America B2B team
 * <hr>
 */

/* Common header files */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/system_properties.h>
//#include <openssl/sha.h>
//#include <openssl/aes.h>
#include <ctype.h>
#include <errno.h>
#include "base64.h"

/* SKMM related header files */
#define LOG_TAG "TZ_GEN_CERT: "
//#include "android/log.h"
#include "libdk_native_client.h"
#include "asn1gen.h"
#include "x509v3.h"

/* Global variables and defines */
typedef uint8_t flag;
#define false 0
#define true 1
#define NUM_ASN_SEQ 8
#define MAX_SERVICE_NAME_LEN 32
#define MAX_PATH_LEN 256
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define RETRY_COUNT 3
#define MAC_LENGTH		17
static char SERVICE_NAME[MAX_SERVICE_NAME_LEN];
static char CERT_PATH[MAX_PATH_LEN];
#define MAX_KEY_BUFFER_SIZE 8192

/* redefine LOG_* functions to printf */
#define LOG_I(...) printf(__VA_ARGS__)
#define LOG_E(...) printf(__VA_ARGS__)

/* Function prototypes */
static uint8_t rotate_octet(
	uint8_t oct
);
static void asn1_free(
	asn1_gen_t * seq
);
static asn1_gen_t *asn1_build_attr(
	asn1_object_t * oid,
	asn1_type_t type,
	char *value
);

static void check_mac(
	char *mac,
	flag * is_mac_good
)
{
	int i;
	char c;
	if (strcmp(mac, "INVALID MAC") == 0) {
		*is_mac_good = false;
		return;
	}
	for (i = 0; i < MAC_LENGTH; i++) {
		if ((i + 1) % 3 == 0) {
			if (mac[i] != ':') {
				strcpy(mac, "INVALID MAC");
				*is_mac_good = false;
			}
		} else {
			c = toupper(mac[i]);
			if (!(c >= '0' && c <= '9') && !(c >= 'A' && c <= 'F')) {
				strcpy(mac, "INVALID MAC");
				*is_mac_good = false;
				return;
			} else {
				mac[i] = c;	// upper case only
			}
		}
	}
	*is_mac_good = true;
}

// Read from a file
static int read_file(
	const char *file_name,
	unsigned char *buffer,
	uint16_t size
)
{
	int fd = 0;
	int err = 0;

	memset(buffer, 0, size);

	fd = open(file_name, O_RDONLY);
	if (fd < 0) {
		LOG_E("File %s open failed", file_name);
		return fd;
	} else {
		err = read(fd, buffer, size);
		if (err < 0) {
			LOG_E("File %s read failed", file_name);
			close(fd);
			return -1;
		}
	}
	close(fd);
	return 0;
}

static void print_error_str(
	int res
)
{
	switch (res) {
	case INVALID_DATA:
		LOG_E("wrong input data");
		break;
	case NOT_SUPPORTED_VERSION:
		LOG_E(" NOT_SUPPORTED_VERSION : internal SKM error");
		break;
	case NOT_SUPPORTED_FUNCTION:
		LOG_E(" NOT_SUPPORTED_FUNCTION : internal SKM error");
		break;
	case NOT_EXIST_DRK:
		LOG_E("Root key missing: No DRK on the device");
		break;
	case OPERATION_FAILED:
		LOG_E("OPERATION_FAILED error");
		break;
	case ACCESS_DENIED:
		LOG_E("ACCESS_DENIED error");
		break;
	case TOO_SMALL_BUFFER:
		LOG_E("TOO_SMALL_BUFFER error");
		break;
	default:
		LOG_E("Unknown error code :%d", res);
		break;
	}
}

/* createServiceKeySession requires that arguments are in ASN.1 representation
 * like part of certificate. Below functions implement generation of such
 * ASN.1 representations of arguments. These functions are provided as
 * examples.
 *
 * Now supported:
 *  - RSA public exponent
 *  - KeyUsage
 *  - Extended KeyUsage
 *  - Signing hash algorithm
 *  - Subject field
 *
 * asn1gen.c provides API:
 *  asn1_gen_field() - generates ASN.1 from one structure asn1_gen_t
 *  asn1_gen_sequence() - generates sequence from array of asn1_gen_t. The last
 *                        structure in the sequence must have type
 *                        ASN1_TYPE_END. This is because size of the sequence
 *                        isn't passed.
 *
 * Be aware, generation of Subject uses malloc for memory allocation in helper
 * function asn1_build_attr()
 */

/* getTLVExp - generate TLV attribute for RSA exponent
 */
static void getTLVExp(
	unsigned long exp,
	uint8_t ** outPtr,
	uint32_t * outLen
)
{
	asn1_gen_t field = { ASN1_TYPE_LONG, (void *)exp };
	static uint8_t buf[256] = { 0 };
	int32_t size = 0;

	size = asn1_gen_field(buf, sizeof(buf), &field);
	if (size < 0) {
		*outPtr = NULL;
	} else {
		*outPtr = buf;
		*outLen = (uint32_t) size;
	}
}

/* getTLVKeyUsage - generate TLV argument for KeyUsage
 * keyUsage is ORed values from x509v3.h
 */
static void getTLVKeyUsage(
	unsigned long keyUsage,
	uint8_t ** outPtr,
	uint32_t * outLen
)
{
	uint8_t keyUsageStringRaw[2] = { 0 };
	asn1_string_t keyUsageString = { 1, (char *)keyUsageStringRaw, 0 };
	asn1_gen_t keyUsageBitString = { ASN1_TYPE_BITSTRING, &keyUsageString };
	int unused = 0;
	int32_t size = 0;
	static uint8_t buf[256] = { 0 };

	keyUsageStringRaw[0] = rotate_octet((uint8_t) (keyUsage & 0xff));
	keyUsage >>= 8;
	if (keyUsage != 0) {
		keyUsageString.size = 2;
		keyUsageStringRaw[1] =
		    rotate_octet((uint8_t) (keyUsage & 0xff));
	}

	keyUsage = keyUsageStringRaw[keyUsageString.size - 1];
	while ((keyUsage & 1) == 0 && unused < 8) {
		++unused;
		keyUsage >>= 1;
	}
	keyUsageString.unused = unused;

	size = asn1_gen_field(buf, sizeof(buf), &keyUsageBitString);
	if (size <= 0) {
		*outPtr = NULL;
	} else {
		*outPtr = buf;
		*outLen = (uint32_t) size;
	}
}

/* getTLVSubjectDeviceID - generates TLV attribute for subject with device ID
 * This function generates subject with device ID
 */
static void getTLVSubjectDeviceID(uint8_t ** outPtr, uint32_t * outLen,
		flag *status)
{
	asn1_gen_t seq[NUM_ASN_SEQ];
	int32_t size = 0;
	static uint8_t buf[1024] = { 0 };
	int i;
	char sn_prop[] = "ro.serialno";
	char sn_value[PROP_VALUE_MAX];
	uint32_t sn_len = 0;
	char sn_base64[25];
	uint32_t sn_base64_len = sizeof(sn_base64);

	sn_len = __system_property_get(sn_prop, sn_value);

	if (sn_len != 16) {
		LOG_E("cannot get ro.serialno");
		strcpy(sn_value, "Unknown SN");
		*status = false;
		return;
	}
	if (base64_encode((uint8_t *)sn_value, sn_len, sn_base64, &sn_base64_len)) {
		LOG_E("base64 encode for sn faied");
		strcpy(sn_base64, "base64 encode failed");
		*status = false;
		return;
	}
	sn_base64[22] = '0';
	sn_base64[23] = '0';

	/* OIDs */
	static unsigned long commonNameOID[] = { 2, 5, 4, 3 };
	static unsigned long countryNameOID[] = { 2, 5, 4, 6 };
	static unsigned long localityNameOID[] = { 2, 5, 4, 7 };
	static unsigned long organizationalUnitNameOID[] = { 2, 5, 4, 11 };
	static unsigned long uniqueIdentifierOID[] = { 2, 5, 4, 45 };
	static unsigned long uidOID[] = { 0, 9, 2342, 19200300, 100, 1, 1 };
	static unsigned long domainComponentOID[] =
	    { 0, 9, 2342, 19200300, 100, 1, 25 };
	/* Objects */
	static asn1_object_t commonName =
	{ ARRAY_SIZE(commonNameOID), commonNameOID };
	static asn1_object_t countryName =
	{ ARRAY_SIZE(countryNameOID), countryNameOID };
	static asn1_object_t localityName =
	{ ARRAY_SIZE(localityNameOID), localityNameOID };
	static asn1_object_t organizationalUnitName =
	{ ARRAY_SIZE(organizationalUnitNameOID),
		organizationalUnitNameOID };
	static asn1_object_t uniqueSN =
	{ ARRAY_SIZE(uniqueIdentifierOID), uniqueIdentifierOID };
	static asn1_object_t uidObject = { ARRAY_SIZE(uidOID), uidOID };
	static asn1_object_t domainComponent =
	{ ARRAY_SIZE(domainComponentOID), domainComponentOID };

	memset(seq, 0, sizeof(seq));
	for (i = 0; i < NUM_ASN_SEQ; i++) {
		seq[i].type = ASN1_TYPE_SET;
	}
	seq[0].value =
	    asn1_build_attr(&countryName, ASN1_TYPE_PRINTABLE_STRING, "KR");
	seq[1].value =
	    asn1_build_attr(&localityName, ASN1_TYPE_UTF8STRING, "Suwon city");
	seq[2].value =
	    asn1_build_attr(&commonName, ASN1_TYPE_UTF8STRING,
			    "Samsung Corporation");
	seq[3].value =
	    asn1_build_attr(&organizationalUnitName, ASN1_TYPE_UTF8STRING,
			    "Samsung Mobile");
	if (strncmp(SERVICE_NAME, "VISA_PAY", strlen("VISA_PAY")) == 0){
		seq[4].value =
		    asn1_build_attr(&uidObject, ASN1_TYPE_UTF8STRING,
				    "PHN-P:20141025:00:00:00101796:VISA_PAY");
	} else {
		seq[4].value =
		    asn1_build_attr(&uidObject, ASN1_TYPE_UTF8STRING,
				    "PHN-P:20141025:00:00:00101796:UID");
	}
	seq[5].value =
	    asn1_build_attr(&domainComponent, ASN1_TYPE_IA5STRING,
			    "samsung.com");
	seq[6].value =
	    asn1_build_attr(&uniqueSN, ASN1_TYPE_PRINTABLE_STRING,
			    sn_base64);
	seq[7].type = ASN1_TYPE_END;

	size = asn1_gen_sequence(buf, sizeof(buf), seq);
	if (size < 0) {
		*outPtr = NULL;
	} else {
		*outPtr = buf;
		*outLen = (uint32_t) size;
	}

	asn1_free(seq);
	*status = true;
}

int generate_pebble_cert(
	uint8_t *key_buffer,
	uint32_t *len
)
{
	int res = OPERATION_FAILED;
	uint8_t *exp = NULL;
	uint32_t expLen = 0;
	uint8_t *keyusage = NULL;
	uint32_t keyusageLen = 0;
	unsigned long keyusageVal = 0;
	int i;
	uint32_t DEF_CERT_PUB_EXP = 65537;

	memset(SERVICE_NAME, 0, sizeof(SERVICE_NAME));
        //micmic visa_pay
	//strncpy(SERVICE_NAME, "VISA_PAY", strlen("VISA_PAY"));
	strncpy(SERVICE_NAME, "PEBBLE", strlen("PEBBLE"));

        keyusageVal = X509_KEY_USAGE_NON_REPUDIATION;

	keyusageVal |= X509_KEY_USAGE_DIGITAL_SIGNATURE;

        keyusageVal |= X509_KEY_USAGE_KEY_ENCIPHERMENT;
	keyusageVal |= X509_KEY_USAGE_DATA_ENCIPHERMENT;

	LOG_I(" check DRK is exist or not..");

	res = isExistDeviceRootKey(KEY_TYPE_RSA);

	switch(res)
	{
		case DRK_IS_EXIST :
			LOG_I("DRK is exist ..");
			break;
		case DRK_IS_NOT_EXIST :
			LOG_E("DRK is not exist ..");
			return NOT_EXIST_DRK;
			break;
		default :
			LOG_E("Failed to check DRK status. Error code is = %d",res);
			return res;
			break;
	}

	LOG_I(" Get DRK UID ...");

	if ((res = getDrkUID(KEY_TYPE_RSA, key_buffer, *len)) == NO_ERROR)
		LOG_I(" DRK UUID = %s", key_buffer);
	else {
		LOG_E(" Failed to read DRK. Error code is = %d",res);
		return res;
	}

	LOG_I(" Generate service key for CCM with TLV ..");
	
        if ((res = initTlv()) == NO_ERROR)
		LOG_I("initTlv success\n");
	else {
		LOG_E(" Failed to initTlv. Error code is = %d",res);
		return res;
	}

	getTLVExp(DEF_CERT_PUB_EXP, &exp, &expLen);
	getTLVKeyUsage(keyusageVal, &keyusage, &keyusageLen);

	if (!exp || !keyusage) {
		LOG_E("Some tlv argument is NULL\n");
	}

	LOG_I(" addTlv for TLV_TAG_EXPONENT,TLV_TAG_KEYUSAGE..");

	if ( ((res = addTlv(TLV_TAG_EXPONENT, (char *)exp, expLen)) != NO_ERROR) ||
	     ((res = addTlv(TLV_TAG_KEYUSAGE, (char *)keyusage,keyusageLen)) != NO_ERROR)) {
			LOG_E("addTlv failed\n");
			return res;
	}

	LOG_I("createServiceKeySession with TLV for CCM ..");
	for (i = 0; i < RETRY_COUNT; i++) {
		LOG_I("CCM: Attempt %d", i + 1);
		if((res = createServiceKeySession(SERVICE_NAME, KEY_TYPE_RSA, 1, key_buffer, *len)) > NO_ERROR) {
		//if((res = verifyProvServiceBlob(SERVICE_NAME, KEY_TYPE_RSA, 0, key_buffer, *len)) > NO_ERROR) {
			LOG_E("createServiceKeySession with TLV for CCM is success. Returned key length is %d ",res);
                        *len = res;
			break;
		} else {
			LOG_E("createServiceKeySession with TLV for CCM is failed");
			print_error_str(res);
		}
	}

	return res;
}

/* Below helper functions */

static uint8_t rotate_octet(
	uint8_t oct
)
{
	uint8_t res = 0;
	int i = 0;

	for (i = 0; i < 8; i++) {
		res <<= 1;
		if (oct & 1) {
			res |= 1;
		}
		oct >>= 1;
	}

	return res;
}

static asn1_gen_t *asn1_build_attr(
	asn1_object_t * oid,
	asn1_type_t type,
	char *value
)
{
	asn1_gen_t *set = NULL;
	asn1_gen_t *seq = NULL;
	char *copy = NULL;
	size_t value_size = strlen(value);

	set = malloc(sizeof(asn1_gen_t) * 2);
	seq = malloc(sizeof(asn1_gen_t) * 3);
	copy = malloc(value_size + 1);
	if (!set || !seq || !copy) {
		free(set);
		free(seq);
		free(copy);
		return NULL;
	}

	memcpy(copy, value, value_size);
	copy[value_size] = '\0';

	seq[0].type = ASN1_TYPE_OBJECT;
	seq[0].value = (void *)oid;
	seq[1].type = type;
	seq[1].value = (void *)copy;
	seq[2].type = ASN1_TYPE_END;
	seq[2].value = NULL;

	set[0].type = ASN1_TYPE_SEQUENCE;
	set[0].value = (void *)seq;
	set[1].type = ASN1_TYPE_END;
	set[1].value = NULL;

	return set;
}

static void asn1_free(
	asn1_gen_t * seq
)
{
	asn1_gen_t *tmp = seq;

	while (tmp->type != ASN1_TYPE_END) {
		switch (tmp->type) {
		case ASN1_TYPE_SET:
		case ASN1_TYPE_SEQUENCE:
			asn1_free((asn1_gen_t *) tmp->value);
			free(tmp->value);
			break;
		case ASN1_TYPE_UTF8STRING:
		case ASN1_TYPE_PRINTABLE_STRING:
			free(tmp->value);
			break;
		default:
			break;
		}
		++tmp;
	}
}

