/**
 * \file da_cert_gencer.c
 * \brief Certificates and ASN.1 representations generation routines.
 * \author Dmytro Podgornyi (d.podgornyi@samsung.com)
 * \version 0.1
 * \date Created May 28, 2013
 * \par In Samsung Ukraine R&D Center (SURC) under a contract between
 * \par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * \par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * \par Copyright: (c) Samsung Electronics Co, Ltd 2012. All rights reserved.
 **/

#include <stdint.h>
#include <time.h>

#include "CommLayerData.h"
#include "CryptoPlatform.h"
#include "x509v3.h"
#include "asn1gen.h"
#include "sec_alloc.h"
#include "ec/ec_wrapper.h"
#include "log.h"
#include "bn/bn_wrapper.h"

#include "da_cert_gencer.h"

/* Name that should be removed from DRK's UID while constructing SK's UID */
#define ROOT_SERVICE_NAME "ROOT"

static unsigned long sha1WithRSAEncryptionOID[] = {1, 2, 840, 113549, 1, 1, 5};
static unsigned long sha256WithRSAEncryptionOID[] = {1, 2, 840, 113549, 1, 1, 11};
static unsigned long sha512WithRSAEncryptionOID[] = {1, 2, 840, 113549, 1, 1, 13};
static unsigned long rsaEncryptionOID[] = {1, 2, 840, 113549, 1, 1, 1};
static unsigned long ecPublicKeyOID[] = {1, 2, 840, 10045, 2, 1};
static unsigned long secp256k1OID[] = {1, 3, 132, 0, 10};
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 uidOID[] = {0, 9, 2342, 19200300, 100, 1, 1};
static unsigned long keyUsageOID[] = {2, 5, 29, 15};
static unsigned long extKeyUsageOID[] = {2, 5, 29, 37};
static unsigned long authorityKeyIdentifierOID[] = {2, 5, 29, 35};

/* Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment */
static uint8_t keyUsageStringRaw[1] = {0xf0U};
static asn1_string_t keyUsageString = {1, (char*)keyUsageStringRaw, 4};
static uint8_t signatureValue[512] = {0};
static asn1_string_t signature = {sizeof(signatureValue), (char*)signatureValue, 0};

static asn1_object_t sha256WithRSAEncryption =
	{ARRAY_SIZE(sha256WithRSAEncryptionOID), sha256WithRSAEncryptionOID};
static asn1_object_t rsaEncryption =
	{ARRAY_SIZE(rsaEncryptionOID), rsaEncryptionOID};
static asn1_object_t ecPublicKey =
	{ARRAY_SIZE(ecPublicKeyOID), ecPublicKeyOID};
static asn1_object_t secp256k1 =
	{ARRAY_SIZE(secp256k1OID), secp256k1OID};
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 uidObject =
	{ARRAY_SIZE(uidOID), uidOID};
static asn1_object_t keyUsage =
	{ARRAY_SIZE(keyUsageOID), keyUsageOID};
static asn1_object_t extKeyUsage =
	{ARRAY_SIZE(extKeyUsageOID), extKeyUsageOID};
static asn1_object_t authorityKeyIdentifier =
	{ARRAY_SIZE(authorityKeyIdentifierOID), authorityKeyIdentifierOID};

static asn1_gen_t Version[] =
{
#define VERSION_IDX 0
	{ASN1_TYPE_LONG, NULL},
	{ASN1_TYPE_END, NULL},
};

/* algorithm used for signature */
static asn1_gen_t AlgorithmIdentifier[] =
{
	{ASN1_TYPE_OBJECT, &sha256WithRSAEncryption},
	{ASN1_TYPE_NULL, NULL},
	{ASN1_TYPE_END, NULL},
};

static asn1_gen_t AlgorithmIdentifierRSA[] =
{
	{ASN1_TYPE_OBJECT, &rsaEncryption},
	{ASN1_TYPE_NULL, NULL},
	{ASN1_TYPE_END, NULL},
};

static asn1_gen_t AlgorithmIdentifierEC[] =
{
	{ASN1_TYPE_OBJECT, &ecPublicKey},
	{ASN1_TYPE_OBJECT, &secp256k1},
	{ASN1_TYPE_END, NULL},
};

static asn1_gen_t keyUsageBitString =
	{ASN1_TYPE_BITSTRING, &keyUsageString};

static asn1_gen_t Validity[3] =
{
	ASN1GEN_INIT,
	ASN1GEN_INIT,
	ASN1GEN_INIT,
};

static asn1_gen_t authorityKeyIdentifier80[] =
{
	{ASN1_TYPE_RAW, NULL},
	{ASN1_TYPE_END, NULL},
};
static asn1_gen_t authorityKeyIdentifierInc[] =
{
	{ASN1_TYPE_IMPL_0, authorityKeyIdentifier80},
	{ASN1_TYPE_END, NULL},
};
static asn1_gen_t authorityKeyIdentifierExt =
	{ASN1_TYPE_SEQUENCE, authorityKeyIdentifierInc};

#define EXTENSION_KEYUSAGE_IDX 0
#define EXTENSION_AUTHORITY_ID_IDX 1
#define EXTENSION_EXT_KEYUSAGE_IDX 2
static asn1_gen_t Extensions[4] =
{
	ASN1GEN_INIT,
	ASN1GEN_INIT,
	ASN1GEN_INIT,
	ASN1GEN_INIT,
};
static asn1_gen_t ExtensionsA3[] =
{
	{ASN1_TYPE_SEQUENCE, Extensions},
	{ASN1_TYPE_END, NULL},
};

/* NULL fields must be set in genCertASN1() */
static asn1_gen_t TBSCertificate[] =
{
#define TBS_VERSION_IDX 0
	{ASN1_TYPE_EXPL_0, (void*)Version},
#define TBS_SERIAL_IDX 1
	{ASN1_TYPE_LONG, NULL},
#define TBS_ALGO_ID_IDX 2
	{ASN1_TYPE_SEQUENCE, (void*)AlgorithmIdentifier},
#define TBS_ISSUER_IDX 3
	{ASN1_TYPE_SEQUENCE, NULL},
#define TBS_VALIDITY_IDX 4
	{ASN1_TYPE_SEQUENCE, (void*)Validity},
#define TBS_SUBJECT_IDX 5
	{ASN1_TYPE_SEQUENCE, NULL},
#define TBS_PUBLIC_KEY_IDX 6
	{ASN1_TYPE_SEQUENCE, NULL},
#define TBS_EXTENSIONS_IDX 7
	{ASN1_TYPE_EXPL_3, (void*)ExtensionsA3},
	{ASN1_TYPE_END, NULL},
};

/* signature must be appended in genCertASN1() */
static asn1_gen_t Certificate[] =
{
#define CERT_TBS_IDX 0
	{ASN1_TYPE_SEQUENCE, (void*)TBSCertificate},
#define CERT_ALGO_ID_IDX 1
	{ASN1_TYPE_SEQUENCE, (void*)AlgorithmIdentifier},
#define CERT_SIGN_IDX 2
	{ASN1_TYPE_BITSTRING, (void*)&signature},
	{ASN1_TYPE_END, NULL},
};

/* substituteAttrs - attributes that can be substituted by generateServiceKeyEx
 * function, tlv argument */
static struct {
	asn1_string_t exponent;
	asn1_string_t subject;
	asn1_string_t keyusage;
	asn1_string_t extKeyusage;
	asn1_string_t signAlgo;
} substituteAttrs = {0};

static char uid[MAX_UID_SIZE] = {0};

int32_t setAttrSubst(TlvTag_t attr, void *raw, int size)
{
	asn1_string_t *ptr = NULL;

	switch (attr)
	{
		case TLV_EXPONENT:
			ptr = &substituteAttrs.exponent;
			break;
		case TLV_SUBJECT:
			ptr = &substituteAttrs.subject;
			break;
		case TLV_KEYUSAGE:
			ptr = &substituteAttrs.keyusage;
			break;
		case TLV_EXT_KEYUSAGE:
			ptr = &substituteAttrs.extKeyusage;
			break;
		case TLV_HASH_ALGO:
			ptr = &substituteAttrs.signAlgo;
			break;
		default:
			LOGE("setAttrSubst: unsupported attribute");
			return WRONG_DATA;
	}

	if (!ptr) {
		LOGE("setAttrSubst: ptr is NULL!");
		return PLATFORM_INTERNAL_ERROR;
	}

	ptr->data = (char *)raw;
	ptr->size = size;

	return NO_ERROR;
}

void initAttrSubst(void)
{
	memset(&substituteAttrs, 0, sizeof(substituteAttrs));
}

int getCertSignHash(const struct x509_certificate *cert)
{
	const unsigned long *oid = cert->signature.oid.oid;

	if (ARRAY_SIZE(sha1WithRSAEncryptionOID) > ASN1_MAX_OID_LEN || ARRAY_SIZE(sha256WithRSAEncryptionOID) > ASN1_MAX_OID_LEN || ARRAY_SIZE(sha512WithRSAEncryptionOID) > ASN1_MAX_OID_LEN) {
		return PLATFORM_INTERNAL_ERROR;
	}

	if (memcmp(sha256WithRSAEncryptionOID, oid, sizeof(sha256WithRSAEncryptionOID)) == 0) {
		return RSA_SHA256;
	}

	if (memcmp(sha512WithRSAEncryptionOID, oid, sizeof(sha512WithRSAEncryptionOID)) == 0) {
		return RSA_SHA512;
	}

	if (memcmp(sha1WithRSAEncryptionOID, oid, sizeof(sha1WithRSAEncryptionOID)) == 0) {
		return RSA_SHA1;
	}

	return UNSUPPORTED_CMD;
}

static uint16_t gen_serial_number()
{
	uint16_t result = 0;
	if (getRandBlock((uint8_t*)&result, 2) != NO_ERROR) {
		return 0x8000;
	}
	return result & 0x7fff;
}

static int32_t gen_uid(char *out, int out_len, const struct KeyInfo *keyinfo, const char *issuer_uid, int issuer_uid_len)
{
	size_t len = 0;

	if (!issuer_uid || issuer_uid_len == 0) {
		LOGE("Issuer UID must be specified");
		return WRONG_DATA;
	}

	if (out_len < issuer_uid_len) {
		return WRONG_DATA;
	}

	LOGD("Generating UID: issuer_uid = '%s', issuer_uid_len=%d, space available: %d", issuer_uid, issuer_uid_len, out_len);
	memset(out, 0, out_len);
	strncpy(out, issuer_uid, issuer_uid_len - len);
	out_len -= issuer_uid_len - len;
	len = strlen((char*)keyinfo->serviceName) + strlen((char*)keyinfo->model) + strlen((char*)keyinfo->serialno) + 2;
	if (out_len <= len) {
		LOGD("Additional space for UID required: %d", len);
		goto bad_space;
	}
	strcat(out, (char*)keyinfo->serviceName);
	strcat(out, ":");
	strcat(out, (char*)keyinfo->model);
	strcat(out, ":");
	strcat(out, (char*)keyinfo->serialno);

	LOGD("UID generated, length=%d", strlen(out));

	return NO_ERROR;

bad_space:
	LOGE("Not enough space to store UID");
	return WRONG_DATA;
}

static int32_t genCertStruct(struct x509_certificate* cert, const struct x509_certificate *issuer, const struct KeyInfo *keyinfo)
{
	unsigned long serial_number = 0;
	int i = 0;
	int mon = 0;
	int32_t res = PLATFORM_INTERNAL_ERROR;

	serial_number = gen_serial_number();
	if (serial_number & 0x8000) {
		/* Random generator error */
		return PLATFORM_INTERNAL_ERROR;
	}

	if ((issuer->exts.extensions_present & X509_EXT_SUBJECT_KEY_IDENTIFIER) == 0) {
		/* No subject key identifier */
		return WRONG_DATA;
	}

	cert->next = NULL;
	cert->version = X509_CERT_V3;
	cert->serial_number = serial_number;

	cert->signature = issuer->signature;

	cert->issuer = issuer->subject;
	cert->subject = issuer->subject;

	for (i = 0; i < cert->subject.num_attr; i++) {
		if (cert->subject.attr[i].type == X509_NAME_ATTR_UID) {
			break;
		}
	}

	if (i == cert->subject.num_attr) {
		if (i == X509_MAX_NAME_ATTRIBUTES) {
			return PLATFORM_INTERNAL_ERROR;
		}
		cert->subject.attr[i].type = X509_NAME_ATTR_UID;
		cert->subject.num_attr++;
		res = gen_uid(uid, sizeof(uid), keyinfo, NULL, 0);
	} else {
		res = gen_uid(uid, sizeof(uid), keyinfo, cert->subject.attr[i].value, cert->subject.attr[i].value_size);
	}

	if (res != NO_ERROR) {
		return PLATFORM_INTERNAL_ERROR;
	}

	cert->subject.attr[i].value = uid;
	cert->subject.attr[i].value_size = strlen(uid);

	cert->not_before.year = keyinfo->year;
	cert->not_before.mon = keyinfo->mon;
	cert->not_before.mday = keyinfo->mday;
	cert->not_before.hour = keyinfo->hour;
	cert->not_before.min = keyinfo->min;
	cert->not_before.sec = keyinfo->sec;
	cert->not_before.start = NULL;
	cert->not_before.end = NULL;

	cert->not_after = cert->not_before;

	mon = cert->not_before.mon + TIME_OF_CERT_USE;
	--mon;

	cert->not_after.year = cert->not_before.year + mon / 12;
	cert->not_after.mon = mon % 12 + 1;

	cert->signature_alg = issuer->signature_alg;

	cert->sign_value = NULL;
	cert->sign_value_len = 0;

	cert->exts = issuer->exts;
	cert->exts.authorityKeyIdentifier.length = issuer->exts.subjectKeyIdentifier.length;
	cert->exts.authorityKeyIdentifier.data = issuer->exts.subjectKeyIdentifier.data;

	cert->exts.extensions_present = X509_EXT_AUTHORITY_KEY_IDENTIFIER | X509_EXT_KEY_USAGE;

	cert->exts.key_usage = X509_KEY_USAGE_DIGITAL_SIGNATURE | X509_KEY_USAGE_NON_REPUDIATION
		| X509_KEY_USAGE_KEY_ENCIPHERMENT | X509_KEY_USAGE_DATA_ENCIPHERMENT;

	return 0;
}

static asn1_gen_t *asn1_build_attr(asn1_object_t *oid, asn1_type_t type, char *value, size_t value_size)
{
	asn1_gen_t *set = NULL;
	asn1_gen_t *seq = NULL;
	char *copy = NULL;

	set = sec_malloc(sizeof(asn1_gen_t) * 2);
	seq = sec_malloc(sizeof(asn1_gen_t) * 3);
	copy = sec_malloc(value_size + 1);
	if (!set || !seq || !copy) {
		sec_free(set);
		sec_free(seq);
		sec_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 asn1_gen_t *asn1_build_ext_helper(asn1_object_t *oid, uint8_t *value, uint32_t valueLen, int crit)
{
	asn1_gen_t *seq = NULL;
	asn1_gen_t *tmp = NULL;
	asn1_string_t *str = NULL;

	if (crit) {
		seq = sec_malloc(sizeof(asn1_gen_t) * 4);
	} else {
		seq = sec_malloc(sizeof(asn1_gen_t) * 3);
	}

	if (!seq) {
		return NULL;
	}

	tmp = seq;
	tmp->type = ASN1_TYPE_OBJECT;
	tmp->value = oid;
	++tmp;
	if (crit) {
		tmp->type = ASN1_TYPE_BOOLEAN;
		tmp->value = (void*)0xff;
		++tmp;
	}

	str = sec_malloc(sizeof(*str));
	if (!str) {
		sec_free(seq);
		return NULL;
	}
	str->size = valueLen;
	str->data = (char*)value;
	str->unused = 0;

	tmp->type = ASN1_TYPE_OCTETSTRING;
	tmp->value = (void*)str;
	++tmp;

	tmp->type = ASN1_TYPE_END;
	tmp->value = NULL;

	return seq;
}

static asn1_gen_t *asn1_build_ext_raw(asn1_object_t *oid, const uint8_t *value, uint32_t valueLen, int crit)
{
	uint8_t *tmp = NULL;
	asn1_gen_t *res = NULL;

	tmp = sec_malloc(valueLen);
	if (!tmp) {
		return NULL;
	}

	memcpy(tmp, value, valueLen);
	res = asn1_build_ext_helper(oid, tmp, valueLen, crit);
	if (!res) {
		sec_free(tmp);
	}
	return res;
}

static asn1_gen_t *asn1_build_ext(asn1_object_t *oid, asn1_gen_t *value, int crit)
{
	uint32_t len = 0;
	uint8_t *out = NULL;
	int32_t result = PLATFORM_INTERNAL_ERROR;
	asn1_gen_t *res = NULL;

	len = asn1_get_field_bytes(value);
	out = sec_malloc(len);
	if (!out) {
		return NULL;
	}
	result = asn1_gen_field(out, len, value);
	if (result <= 0) {
		return NULL;
	}

	if ((uint32_t)result != len) {
		LOGE("asn1_build_ext: incomplete value");
	}

	res = asn1_build_ext_helper(oid, out, len, crit);
	if (!res) {
		sec_free(out);
	}
	return res;
}

/* free sequence generated by asn1_build_pub_rsa_struct or asn1_build_ext */
static void asn1_free_ext(asn1_gen_t *seq)
{
	asn1_gen_t *tmp = seq;

	while (tmp->type != ASN1_TYPE_END) {
		switch (tmp->type)
		{
			case ASN1_TYPE_BITSTRING:
			case ASN1_TYPE_OCTETSTRING:
				sec_free(((asn1_string_t*)tmp->value)->data);
				sec_free(tmp->value);
				break;
			default:
				break;
		}
		++tmp;
	}

	sec_free(seq);
}

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_EXPL_0:
			case ASN1_TYPE_IMPL_0:
			case ASN1_TYPE_EXPL_1:
			case ASN1_TYPE_EXPL_3:
			case ASN1_TYPE_SEQUENCE:
				asn1_free((asn1_gen_t*)tmp->value);
				break;
			case ASN1_TYPE_UTF8STRING:
			case ASN1_TYPE_PRINTABLE_STRING:
				sec_free(tmp->value);
				break;

			default:
				break;
		}
		++tmp;
	}

	sec_free(seq);
}

static int32_t build_utc_time(char *notbefore, size_t notbefore_len, char *notafter, size_t notafter_len, const struct x509_certificate *cert)
{
	int year = cert->not_before.year;
	int month = cert->not_before.mon;
	int day = cert->not_before.mday;
	int hour = cert->not_before.hour;
	int minute = cert->not_before.min;
	int second = cert->not_before.sec;

	if (notbefore_len < 14 || notafter_len < 14) {
		return WRONG_DATA;
	}

	if (month < 1 || month > 12) return WRONG_DATA;
	if (day < 1 || day > 31) return WRONG_DATA;
	if (hour > 23) return WRONG_DATA;
	if (minute > 59) return WRONG_DATA;
	if (second > 59) return WRONG_DATA;

	notbefore[0]  = '0' + (year/10)%10;
	notbefore[1]  = '0' + year%10;
	notbefore[2]  = '0' + month/10;
	notbefore[3]  = '0' + month%10;
	notbefore[4]  = '0' + day/10;
	notbefore[5]  = '0' + day%10;
	notbefore[6]  = '0' + hour/10;
	notbefore[7]  = '0' + hour%10;
	notbefore[8]  = '0' + minute/10;
	notbefore[9]  = '0' + minute%10;
	notbefore[10] = '0' + second/10;
	notbefore[11] = '0' + second%10;
	notbefore[12] = 'Z';
	notbefore[13] = 0;

	year = cert->not_after.year;
	month = cert->not_after.mon;
	day = cert->not_after.mday;
	hour = cert->not_after.hour;
	minute = cert->not_after.min;
	second = cert->not_after.sec;

	if (month < 1 || month > 12) return WRONG_DATA;
	if (day < 1 || day > 31) return WRONG_DATA;
	if (hour > 23) return WRONG_DATA;
	if (minute > 59) return WRONG_DATA;
	if (second > 59) return WRONG_DATA;

	notafter[0]  = '0' + (year/10)%10;
	notafter[1]  = '0' + year%10;
	notafter[2]  = '0' + month/10;
	notafter[3]  = '0' + month%10;
	notafter[4]  = '0' + day/10;
	notafter[5]  = '0' + day%10;
	notafter[6]  = '0' + hour/10;
	notafter[7]  = '0' + hour%10;
	notafter[8]  = '0' + minute/10;
	notafter[9]  = '0' + minute%10;
	notafter[10] = '0' + second/10;
	notafter[11] = '0' + second%10;
	notafter[12] = 'Z';
	notafter[13] = 0;

	return NO_ERROR;
}

int32_t asn1_check_cert_expiration_time(const struct x509_certificate *cert, const uint8_t *localTime, uint32_t localTimeLen)
{
	int32_t res = PLATFORM_INTERNAL_ERROR;
	struct tm* tmLocalTime = (struct tm*)localTime;

	if (NULL == cert) {
		return PLATFORM_INTERNAL_ERROR;
	}

	LOGE("Local time = %d:%d:%d  %d:%d:%d\n", tmLocalTime->tm_hour, tmLocalTime->tm_min, tmLocalTime->tm_sec, tmLocalTime->tm_mday, tmLocalTime->tm_mon, tmLocalTime->tm_year + 1900);

	LOGE("Certificate expiration time = %d:%d:%d  %d:%d:%d\n", cert->not_after.hour, cert->not_after.min, cert->not_after.sec, cert->not_after.mday, cert->not_after.mon, cert->not_after.year);

	if (tmLocalTime->tm_year + 1900 > cert->not_after.year) {
		res = CERT_EXPIRATION_TIME_ERROR;
		goto end;
	} else if (tmLocalTime->tm_year + 1900 < cert->not_after.year) {
		res = NO_ERROR;
		goto end;
	}

	if (tmLocalTime->tm_mon > cert->not_after.mon) {
		res = CERT_EXPIRATION_TIME_ERROR;
		goto end;
	} else if (tmLocalTime->tm_mon < cert->not_after.mon) {
		res = NO_ERROR;
		goto end;
	}

	if (tmLocalTime->tm_mday > cert->not_after.mday) {
		res = CERT_EXPIRATION_TIME_ERROR;
		goto end;
	} else if (tmLocalTime->tm_mday < cert->not_after.mday) {
		res = NO_ERROR;
		goto end;
	}

	if (tmLocalTime->tm_hour > cert->not_after.hour) {
		res = CERT_EXPIRATION_TIME_ERROR;
		goto end;
	} else if (tmLocalTime->tm_hour < cert->not_after.hour) {
		res = NO_ERROR;
		goto end;
	}

	if (tmLocalTime->tm_min > cert->not_after.min) {
		res = CERT_EXPIRATION_TIME_ERROR;
		goto end;
	} else if (tmLocalTime->tm_min < cert->not_after.min) {
		res = NO_ERROR;
		goto end;
	}

	if (tmLocalTime->tm_sec > cert->not_after.sec) {
		res = CERT_EXPIRATION_TIME_ERROR;
		goto end;
	} else if (tmLocalTime->tm_sec < cert->not_after.sec) {
		res = NO_ERROR;
		goto end;
	}

end:
	return res;
}

static asn1_gen_t *asn1_build_name(const struct x509_name *name)
{
	asn1_gen_t *seq = NULL;
	asn1_gen_t *attr = NULL;
	void *value = NULL;
	size_t value_size = 0;
	int seq_size = 0;
	int i = 0;
	int idx = 0;

	seq_size = name->num_attr + 1;
	seq = sec_malloc(sizeof(asn1_gen_t) * seq_size);
	if (!seq) {
		return NULL;
	}

	for (i = 0; i < seq_size; i++) {
		seq[i].type = ASN1_TYPE_END;
		seq[i].value = NULL;
	}

	for (i = 0; i < name->num_attr; i++) {
		value = (void*)name->attr[i].value;
		value_size = name->attr[i].value_size;

		switch (name->attr[i].type)
		{
			case X509_NAME_ATTR_CN:
				attr = asn1_build_attr(&commonName, ASN1_TYPE_UTF8STRING, value, value_size);
				break;
			case X509_NAME_ATTR_C:
				attr = asn1_build_attr(&countryName, ASN1_TYPE_PRINTABLE_STRING, value, value_size);
				break;
			case X509_NAME_ATTR_L:
				attr = asn1_build_attr(&localityName, ASN1_TYPE_UTF8STRING, value, value_size);
				break;
			case X509_NAME_ATTR_OU:
				attr = asn1_build_attr(&organizationalUnitName, ASN1_TYPE_UTF8STRING, value, value_size);
				break;
			case X509_NAME_ATTR_UID:
				attr = asn1_build_attr(&uidObject, ASN1_TYPE_UTF8STRING, value, value_size);
				break;
			default:
				LOGD("Unknown attribute with id %d", name->attr[i].type);
				attr = NULL;
				break;
		}

		if (!attr) {
			LOGE("Error while generation of attribute with id %d", name->attr[i].type);
			goto err;
		} else {
			if (idx >= (seq_size - 1)) {
				LOGE("Not enough space to set all attributes");
				goto err;
			}

			seq[idx].type = ASN1_TYPE_SET;
			seq[idx].value = (void*)attr;
			++idx;
		}
	}

	/* self-check */
	if (idx >= seq_size || seq[idx].type != ASN1_TYPE_END) {
		LOGE("asn1_build_name() is broken");
		goto err;
	}

	return seq;

err:
	i = 0;
	while (i < seq_size && seq[i].type != ASN1_TYPE_END) {
		if (seq[i].value != NULL) {
			asn1_free((asn1_gen_t*)seq[i].value);
		}
		++i;
	}
	sec_free(seq);
	return NULL;
}

int32_t asn1_build_pub_rsa(const RSA *rsa, uint8_t *out, uint32_t *outLen)
{
	asn1_gen_t pub_key[3] = {ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT};
	asn1_gen_t pub_key_seq[2] = {ASN1GEN_INIT, ASN1GEN_INIT};
	asn1_gen_t nested_seq[3] = {ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT};
	asn1_gen_t seq[3] = {ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT};
	asn1_string_t str;
	int32_t result = PLATFORM_INTERNAL_ERROR;
	uint32_t len = 0;
	uint8_t *tmpOut = NULL;

	/* TODO: avoid copypaste */

	pub_key[0].type = ASN1_TYPE_BIGNUM;
	pub_key[0].value = (void*)rsa->n;
	pub_key[1].type = ASN1_TYPE_BIGNUM;
	pub_key[1].value = (void*)rsa->e;
	pub_key[2].type = ASN1_TYPE_END;
	pub_key[2].value = NULL;
	pub_key_seq[0].type = ASN1_TYPE_SEQUENCE;
	pub_key_seq[0].value = (void*)pub_key;
	pub_key_seq[1].type = ASN1_TYPE_END;
	pub_key_seq[1].value = NULL;

	len = asn1_get_field_bytes(pub_key_seq);
	tmpOut = sec_malloc(len);
	if (!tmpOut) {
		return 0;
	}

	result = asn1_gen_field(tmpOut, len, pub_key_seq);
	if (result < 0) {
		return result;
	}

	str.size = len;
	str.data = (char*)tmpOut;
	str.unused = 0;

	nested_seq[0].type = ASN1_TYPE_SEQUENCE;
	nested_seq[0].value = (void*)AlgorithmIdentifierRSA;
	nested_seq[1].type = ASN1_TYPE_BITSTRING;
	nested_seq[1].value = (void*)&str;
	nested_seq[2].type = ASN1_TYPE_END;
	nested_seq[2].value = NULL;
	seq[0].type = ASN1_TYPE_SEQUENCE;
	seq[0].value = (void*)nested_seq;
	seq[1].type = ASN1_TYPE_END;
	seq[1].value = NULL;

	len = asn1_get_field_bytes(seq);

	result = asn1_gen_field(out, len, seq);
	if (result < 0) {
		return result;
	}

	*outLen = len;
	if (len > *outLen) {
		LOGE("asn1_build_pub_rsa: buffer too small");
	}

	sec_free(tmpOut);

	return NO_ERROR;
}

static asn1_gen_t *asn1_build_pub_rsa_struct(const RSA *rsa)
{
	asn1_gen_t *seq = NULL;
	asn1_gen_t pub_key[3] = {ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT};
	asn1_gen_t pub_key_seq[2] = {ASN1GEN_INIT, ASN1GEN_INIT};
	asn1_string_t *str = NULL;
	uint32_t len = 0;
	uint8_t *out = NULL;
	int32_t result = PLATFORM_INTERNAL_ERROR;

	seq = sec_malloc(sizeof(asn1_gen_t) * 3);
	if (!seq) {
		return NULL;
	}

	pub_key[0].type = ASN1_TYPE_BIGNUM;
	pub_key[0].value = (void*)rsa->n;
	pub_key[1].type = ASN1_TYPE_BIGNUM;
	pub_key[1].value = (void*)rsa->e;
	pub_key[2].type = ASN1_TYPE_END;
	pub_key[2].value = NULL;
	pub_key_seq[0].type = ASN1_TYPE_SEQUENCE;
	pub_key_seq[0].value = (void*)pub_key;
	pub_key_seq[1].type = ASN1_TYPE_END;
	pub_key_seq[1].value = NULL;
	len = asn1_get_field_bytes(pub_key_seq);
	out = sec_malloc(len);
	if (!out) {
		goto err;
	}

	result = asn1_gen_field(out, len, pub_key_seq);
	if (result < 0) {
		goto err_out;
	}

	str = sec_malloc(sizeof(*str));
	if (!str) {
		goto err_out;
	}
	str->size = len;
	str->data = (char*)out;
	str->unused = 0;

	seq[0].type = ASN1_TYPE_SEQUENCE;
	seq[0].value = (void*)AlgorithmIdentifierRSA;
	seq[1].type = ASN1_TYPE_BITSTRING;
	seq[1].value = (void*)str;
	seq[2].type = ASN1_TYPE_END;
	seq[2].value = NULL;

	return seq;

err_out:
	sec_free(out);
err:
	sec_free(seq);
	return NULL;
}

static asn1_gen_t *asn1_build_pub_ec(const EC_KEY *ec)
{
	asn1_gen_t *seq = NULL;
	asn1_string_t *str = NULL;
	uint8_t *out = NULL;
	uint32_t x_len = 0, y_len = 0, len = 0;
	BIGNUM X = {0};
	BIGNUM Y = {0};

	BN_init(&X);
	BN_init(&Y);
	if (!EC_POINT_get_affine_coordinates_GFp(ec->group, ec->pub_key, &X, &Y, NULL)) {
		LOGE("asn1_build_pub_ec: EC_POINT_get_affine_coordinates_GFp failed");
		BN_free(&X);
		BN_free(&Y);
		return NULL;
	}

	/* XXX: 32 depends on key size */
	x_len = BN_num_bytes(&X);
	if (x_len < 32) {
		x_len = 32;
	}
	y_len = BN_num_bytes(&Y);
	if (y_len < 32) {
		y_len = 32;
	}
	len = x_len + y_len + 1;
	out = sec_malloc(len);
	if (!out) {
		BN_free(&X);
		BN_free(&Y);
		return NULL;
	}
	out[0] = POINT_CONVERSION_UNCOMPRESSED;
	if (bn_dump(&X, out + 1, x_len) != NO_ERROR) {
		LOGE("asn1_build_pub_ec: wrote incomplete BIGNUM ECPoint.X");
	}
	if (bn_dump(&Y, out + x_len + 1, y_len) != NO_ERROR) {
		LOGE("asn1_build_pub_ec: wrote incomplete BIGNUM ECPoint.Y");
	}

	BN_free(&X);
	BN_free(&Y);

	str = sec_malloc(sizeof(*str));
	if (!str) {
		goto cleanup;
	}
	str->size = len;
	str->data = (char*)out;
	str->unused = 0;

	seq = sec_malloc(sizeof(asn1_gen_t) * 3);
	if (!seq) {
		goto cleanup;
	}
	seq[0].type = ASN1_TYPE_SEQUENCE;
	seq[0].value = (void*)AlgorithmIdentifierEC;
	seq[1].type = ASN1_TYPE_BITSTRING;
	seq[1].value = (void*)str;
	seq[2].type = ASN1_TYPE_END;
	seq[2].value = NULL;

	return seq;

cleanup:
	if (str != NULL)
		sec_free(str);
	if (out != NULL)
		sec_free(out);
	if (seq != NULL)
		sec_free(seq);
	return NULL;
}

static int32_t genCertASN1(uint8_t *out, uint32_t *out_len, const struct x509_certificate *cert, const asn1_gen_t *public_key)
{
	int32_t res = PLATFORM_INTERNAL_ERROR;
	uint32_t len = 0;
	char notbefore[16] = {0};
	char notafter[16] = {0};
	int i = 0;
	asn1_string_t keyId = {0};

	Version[VERSION_IDX].value = (void*)cert->version;
	TBSCertificate[TBS_SERIAL_IDX].value = (void*)cert->serial_number;
	TBSCertificate[TBS_PUBLIC_KEY_IDX].value = (void*)public_key;

	/* AlgorithmIdentifier */
	if (substituteAttrs.signAlgo.data != NULL) {
		AlgorithmIdentifier[0].value = (void*)&substituteAttrs.signAlgo;
		AlgorithmIdentifier[0].type = ASN1_TYPE_RAW;
	}

	/* Names (issuer, subject) */
	TBSCertificate[TBS_ISSUER_IDX].value = (void*)asn1_build_name(&cert->issuer);
	if (!TBSCertificate[TBS_ISSUER_IDX].value) {
		return PLATFORM_INTERNAL_ERROR;
	}

	if (substituteAttrs.subject.data != NULL) {
		TBSCertificate[TBS_SUBJECT_IDX].value = (void*)&substituteAttrs.subject;
		TBSCertificate[TBS_SUBJECT_IDX].type = ASN1_TYPE_RAW;
	} else {
		TBSCertificate[TBS_SUBJECT_IDX].value = (void*)asn1_build_name(&cert->subject);
		if (!TBSCertificate[TBS_SUBJECT_IDX].value) {
			res = PLATFORM_INTERNAL_ERROR;
			goto free_issuer;
		}
	}

	/* Validity */
	if (ARRAY_SIZE(Validity) < 3) {
		LOGE("genCertASN1: Not enough space to set Validity");
		res = PLATFORM_INTERNAL_ERROR;
		goto free_subject;
	}
	res = build_utc_time(notbefore, sizeof(notbefore), notafter, sizeof(notafter), cert);
	if (res != NO_ERROR) {
		goto free_subject;
	}
	Validity[0].type = ASN1_TYPE_UTCTIME;
	Validity[0].value = (void*)notbefore;
	Validity[1].type = ASN1_TYPE_UTCTIME;

	Validity[1].value = (void*)notafter;
	Validity[2].type = ASN1_TYPE_END;
	Validity[2].value = NULL;

	/* Extensions */
	for (i = 0; i < ARRAY_SIZE(Extensions); i++) {
		Extensions[i].type = ASN1_TYPE_END;
	}

	Extensions[EXTENSION_KEYUSAGE_IDX].type = ASN1_TYPE_SEQUENCE;
	if (substituteAttrs.keyusage.data != NULL) {
		Extensions[EXTENSION_KEYUSAGE_IDX].value = (void*)asn1_build_ext_raw(&keyUsage, (uint8_t*)substituteAttrs.keyusage.data, substituteAttrs.keyusage.size, 0);
	} else {
		Extensions[EXTENSION_KEYUSAGE_IDX].value = (void*)asn1_build_ext(&keyUsage, &keyUsageBitString, 0);
	}

	if (!Extensions[EXTENSION_KEYUSAGE_IDX].value) {
		res = PLATFORM_INTERNAL_ERROR;
		goto free_subject;
	}

	keyId.size = cert->exts.authorityKeyIdentifier.length;
	keyId.data = (char *)cert->exts.authorityKeyIdentifier.data;
	authorityKeyIdentifier80[0].value = &keyId;
	Extensions[EXTENSION_AUTHORITY_ID_IDX].type = ASN1_TYPE_SEQUENCE;
	Extensions[EXTENSION_AUTHORITY_ID_IDX].value = (void*)asn1_build_ext(&authorityKeyIdentifier, &authorityKeyIdentifierExt, 0);
	if (!Extensions[EXTENSION_AUTHORITY_ID_IDX].value) {
		res = PLATFORM_INTERNAL_ERROR;
		goto free_keyusage;
	}

	if (substituteAttrs.extKeyusage.data != NULL) {
		Extensions[EXTENSION_EXT_KEYUSAGE_IDX].type = ASN1_TYPE_SEQUENCE;
		Extensions[EXTENSION_EXT_KEYUSAGE_IDX].value = (void*)asn1_build_ext_raw(&extKeyUsage, (uint8_t*)substituteAttrs.extKeyusage.data, substituteAttrs.extKeyusage.size, 0);
	}

	/* Set the size of signature (global variable) */
	signature.size = cert->sign_value_len;

	/* Generation */
	if (asn1_validate(Certificate) != NO_ERROR) {
		LOGE("Certificate has wrong ASN.1 structure!");
		res = PLATFORM_INTERNAL_ERROR;
		goto free_authority;
	}
	res = asn1_gen_sequence(out, *out_len, Certificate);
	if (res < 0) {
		goto free_authority;
	} else {	
		len = (uint32_t)res;
	}

	*out_len = len;
	res = NO_ERROR;

free_authority:
	asn1_free_ext((asn1_gen_t*)Extensions[EXTENSION_AUTHORITY_ID_IDX].value);
free_keyusage:
	if (Extensions[EXTENSION_KEYUSAGE_IDX].type != ASN1_TYPE_RAW)
		asn1_free_ext((asn1_gen_t*)Extensions[EXTENSION_KEYUSAGE_IDX].value);
free_subject:
	if (TBSCertificate[TBS_SUBJECT_IDX].type != ASN1_TYPE_RAW)
		asn1_free((asn1_gen_t*)TBSCertificate[TBS_SUBJECT_IDX].value);
free_issuer:
	asn1_free((asn1_gen_t*)TBSCertificate[TBS_ISSUER_IDX].value);
	return res;
}

static int32_t genCertHelper(uint8_t *out, uint32_t *out_len, const struct KeyInfo *keyinfo, const struct x509_certificate *issuer, asn1_gen_t *pub_key, RSA *ca)
{
	struct x509_certificate cert = {0};
	int32_t res = 0;
	uint8_t digest[SHA256_DIGEST_LEN] = {0};
	uint32_t digestLen = 0;
	unsigned char *sign = NULL;
	int algo = 0;
	hashCallback_t hash = NULL;

	LOGD("Begin of certificate generation, available space: %d B", *out_len);

	/* generate x509_certificate structure without public key */
	res = genCertStruct(&cert, issuer, keyinfo);
	if (res != NO_ERROR) {
		LOGE("genCertStruct() failed");
		return res;
	}

	cert.sign_value_len = BN_num_bytes(ca->n);

	/* generate certificate in DER format */
	res = genCertASN1(out, out_len, &cert, pub_key);
	if (res != NO_ERROR) {
		LOGE("genCertASN1() failed");
		return PLATFORM_INTERNAL_ERROR;
	}

	/* sign the certificate */
	memset(&cert, 0, sizeof(cert));
	if (x509_certificate_parse(out, *out_len, &cert) != X509_PARSE_OK) {
		LOGE("Couldn't parse generated certificate");
		return PLATFORM_INTERNAL_ERROR;
	}

	algo = getCertSignHash(&cert);
	if (algo < 0) {
		LOGE("genCertHelper: unable to recognize hash algorithm");
		return PLATFORM_INTERNAL_ERROR;
	}

	switch (algo)
	{
		case RSA_SHA256:
			hash = getSHA256Digest;
			digestLen = SHA256_DIGEST_LEN;
			break;
		case RSA_SHA1:
			hash = getSHA1Digest;
			digestLen = SHA1_DIGEST_LEN;
			break;
		default:
			LOGE("genCertHelper: unknown hash algorithm");
			return PLATFORM_INTERNAL_ERROR;
	}

	res = hash((uint8_t*)cert.tbs_cert_start, cert.tbs_cert_len, digest);
	if (res != NO_ERROR) {
		LOGE("genCertHelper: hash digest failed");
		return PLATFORM_INTERNAL_ERROR;
	}

	sign = (unsigned char *)cert.sign_value;
	res = mldap_rsa_pkcs1_sign(ca, algo, digestLen, digest, sign);
	if (res != NO_ERROR) {
		LOGE("mldap_rsa_pkcs1_sign() failed");
		return PLATFORM_INTERNAL_ERROR;
	}

#ifdef RUN_FUNC_TESTS
	res = mldap_rsa_pkcs1_verify(ca, algo, digestLen, digest, sign);
	if (res != NO_ERROR) {
		LOGE("FUNC TEST: Signature verification of signed certificate is failed");
		return PLATFORM_INTERNAL_ERROR;
	}
	LOGD("FUNC TEST: Signature of signed certificate is correct");
	if (x509_certificate_parse(out, *out_len, &cert) != X509_PARSE_OK) {
		LOGE("FUNC TEST: Generated certificate is invalid!");
		return PLATFORM_INTERNAL_ERROR;
	}
	LOGE("FUNC TEST: Generated certificate is parsed correctly");
#endif /* RUN_FUNC_TESTS */

	return NO_ERROR;
}

int32_t genCertRSA(uint8_t *out, uint32_t *out_len, const struct KeyInfo *keyinfo, const struct x509_certificate *issuer, RSA *rsa, RSA *ca)
{
	asn1_gen_t *key = NULL;
	int32_t res = PLATFORM_INTERNAL_ERROR;

	/* build public key as ASN.1 */
	key = asn1_build_pub_rsa_struct(rsa);
	if (!key) {
		LOGE("Error while generation ASN.1 RSA public key");
		return PLATFORM_INTERNAL_ERROR;
	}

	res = genCertHelper(out, out_len, keyinfo, issuer, key, ca);
	asn1_free_ext(key);

	return res;
}

int32_t genCertEC(uint8_t *out, uint32_t *out_len, const struct KeyInfo *keyinfo, const struct x509_certificate *issuer, EC_KEY *ec, RSA *ca)
{
	asn1_gen_t *key = NULL;
	int32_t res = PLATFORM_INTERNAL_ERROR;

	/* build public key as ASN.1 */
	key = asn1_build_pub_ec(ec);
	if (!key) {
		LOGE("Error while generation ASN.1 RSA public key");
		return PLATFORM_INTERNAL_ERROR;
	}

	res = genCertHelper(out, out_len, keyinfo, issuer, key, ca);
	asn1_free_ext(key);

	return res;
}

int32_t asn1_build_pri_rsa(uint8_t *out, uint32_t *out_len, RSA *rsa)
{
	asn1_gen_t seq[10] = {ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT,
				ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT,
				ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT,
				ASN1GEN_INIT};
	int i = 0;
	int32_t res = PLATFORM_INTERNAL_ERROR;

	/* version */
	seq[0].type = ASN1_TYPE_LONG;
	seq[0].value = (void*)0;

	for (i = 1; i < 9; i++) {
		seq[i].type = ASN1_TYPE_BIGNUM;
	}
	seq[1].value = (void*)rsa->n;
	seq[2].value = (void*)rsa->e;
	seq[3].value = (void*)rsa->d;
	seq[4].value = (void*)rsa->p;
	seq[5].value = (void*)rsa->q;
	seq[6].value = (void*)rsa->dmp1;
	seq[7].value = (void*)rsa->dmq1;
	seq[8].value = (void*)rsa->iqmp;

	seq[9].type = ASN1_TYPE_END;
	seq[9].value = NULL;

	res = asn1_validate(seq);
	if (res != NO_ERROR) {
		LOGE("asn1_build_pri_rsa: wrong ASN.1 format");
		return PLATFORM_INTERNAL_ERROR;
	}

	res = asn1_gen_sequence(out, *out_len, seq);
	if (res < 0) {
		return res;
	}

	*out_len = (uint32_t)res;

	return NO_ERROR;
}

int32_t asn1_build_pri_ec(uint8_t *out, uint32_t *out_len, EC_KEY *ec)
{
	asn1_gen_t a0[2] = {ASN1GEN_INIT, ASN1GEN_INIT};
	asn1_gen_t a1[2] = {ASN1GEN_INIT, ASN1GEN_INIT};
	asn1_gen_t seq[5] = {ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT};

	asn1_gen_t prk = ASN1GEN_INIT;
	asn1_gen_t *pbk = NULL;
	asn1_string_t prk_str = {0};
	uint32_t len = 0;
	int i = 0;
	int32_t res = PLATFORM_INTERNAL_ERROR;

	len = BN_num_bytes(ec->priv_key);
	prk_str.data = sec_malloc(len);
	if (!prk_str.data) {
		return PLATFORM_INTERNAL_ERROR;
	}
	prk_str.size = len;
	prk_str.unused = 0;
	if (BN_bn2bin(ec->priv_key, (uint8_t*)prk_str.data) != len) {
		LOGE("asn1_build_pri_ec: wrote incomplete BIGNUM ec->priv_key");
	}

	prk.type = ASN1_TYPE_SEQUENCE;
	prk.value = (void*)seq;

	/* version */
	seq[0].type = ASN1_TYPE_LONG;
	seq[0].value = (void*)1;

	seq[1].type = ASN1_TYPE_OCTETSTRING;
	seq[1].value = (void*)&prk_str;

	seq[2].type = ASN1_TYPE_EXPL_0;
	seq[2].value = (void*)a0;

	seq[3].type = ASN1_TYPE_EXPL_1;
	seq[3].value = (void*)a1;

	seq[4].type = ASN1_TYPE_END;
	seq[4].value = NULL;

	pbk = asn1_build_pub_ec(ec);
	if (!pbk) {
		goto cleanup;
	}
	i = 0;
	while (pbk[i].type != ASN1_TYPE_END) {
		++i;
	}
	if (i < 2 || pbk[1].type != ASN1_TYPE_BITSTRING) {
		goto cleanup;
	}

	a0[0].type = ASN1_TYPE_OBJECT;
	a0[0].value = (void*)&secp256k1;
	a0[1].type = ASN1_TYPE_END;
	a0[1].value = NULL;

	a1[0].type = ASN1_TYPE_BITSTRING;
	a1[0].value = pbk[1].value;
	a1[1].type = ASN1_TYPE_END;
	a1[1].value = NULL;

	if (asn1_validate(seq) != NO_ERROR) {
		LOGE("asn1_build_pri_ec: private key has wrong ASN.1 structure");
		goto cleanup;
	}

	len = asn1_get_field_bytes(&prk);
	if (*out_len < len) {
		LOGE("asn1_build_pri_ec: not enough space");
		goto cleanup;
	}
	if (asn1_gen_field(out, *out_len, &prk) != len) {
		LOGE("asn1_build_pri_ec: can't create ASN.1 from private key");
		goto cleanup;
	}
	*out_len = len;

	res = NO_ERROR;

cleanup:
	if (pbk != NULL) {
		asn1_free_ext(pbk);
	}
	sec_free(prk_str.data);

	return res;
}
