/*
Copyright 2006 - 2018 Intel Corporation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#if defined(WIN32) && !defined(_WIN32_WCE)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif


#include "ILibCrypto.h"

#ifndef MICROSTACK_NOTLS
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/pkcs12.h>
#include <openssl/conf.h>
#include <openssl/x509v3.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#else
#ifdef WIN32
void BCRYPT_INIT(BCRYPT_CTX* ctx, void* alg)
{
	memset(ctx, 0, sizeof(BCRYPT_CTX));
	BCryptOpenAlgorithmProvider(&(ctx->hAlg), (LPCWSTR)alg, NULL, 0);
	BCryptGetProperty(ctx->hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&(ctx->cbHashObject), sizeof(DWORD), &(ctx->cbData), 0);
	ctx->pbHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, ctx->cbHashObject);
	BCryptGetProperty(ctx->hAlg, BCRYPT_HASH_LENGTH, (PBYTE)&(ctx->cbHash), sizeof(DWORD), &(ctx->cbData), 0);
	BCryptCreateHash(ctx->hAlg, &(ctx->hHash), ctx->pbHashObject, ctx->cbHashObject, NULL, 0, 0);
}
void BCRYPT_UPDATE(BCRYPT_CTX* ctx, void* data, size_t dataLen)
{
	BCryptHashData(ctx->hHash, (PBYTE)data, (ULONG)dataLen, 0);
}
void BCRYPT_FINAL(char *h, BCRYPT_CTX* ctx)
{
	BCryptFinishHash(ctx->hHash, (PUCHAR)h, ctx->cbHash, 0);
	BCryptCloseAlgorithmProvider(ctx->hAlg, 0);
	BCryptDestroyHash(ctx->hHash);
	HeapFree(GetProcessHeap(), 0, ctx->pbHashObject);
}
#else
#include "microstack/nossl/md5.h"
#include "microstack/nossl/sha1.h"
#include "microstack/nossl/sha.h"
#include <time.h>
#endif
#endif

char utils_HexTable[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
char utils_HexTable2[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };


void  __fastcall util_md5(char* data, int datalen, char* result)
{
	MD5_CTX c;
	MD5_Init(&c);
	MD5_Update(&c, data, datalen);
	MD5_Final((unsigned char*)result, &c);
}
void  __fastcall util_md5hex(char* data, int datalen, char *out)
{
	int i = 0;
	unsigned char *temp = (unsigned char*)out;
	MD5_CTX mdContext;
	unsigned char digest[16];

	MD5_Init(&mdContext);
	MD5_Update(&mdContext, (unsigned char *)data, datalen);
	MD5_Final(digest, &mdContext);

	for (i = 0; i < sizeof(digest); i++)
	{
		*(temp++) = utils_HexTable2[(unsigned char)digest[i] >> 4];
		*(temp++) = utils_HexTable2[(unsigned char)digest[i] & 0x0F];
	}

	*temp = '\0';
}
void  __fastcall util_sha1(char* data, int datalen, char* result)
{
	SHA_CTX c;
	SHA1_Init(&c);
	SHA1_Update(&c, data, datalen);
	SHA1_Final((unsigned char*)result, &c);
	result[20] = 0;
}
void  __fastcall util_sha256(char* data, int datalen, char* result)
{
	SHA256_CTX c;
	SHA256_Init(&c);
	SHA256_Update(&c, data, datalen);
	SHA256_Final((unsigned char*)result, &c);
}
void  __fastcall util_sha384(char* data, int datalen, char* result)
{
	SHA512_CTX c;
	SHA384_Init(&c);
	SHA384_Update(&c, data, datalen);
	SHA384_Final((unsigned char*)result, &c);
}
int   __fastcall util_sha384file(char* filename, char* result)
{
	FILE *pFile = NULL;
	SHA512_CTX c;
	size_t len = 0;
	char *buf = NULL;

	if (filename == NULL) return -1;
#ifdef WIN32 
	fopen_s(&pFile, filename, "rbN");
#else
	pFile = fopen(filename, "rb");
#endif
	if (pFile == NULL) goto error;
	SHA384_Init(&c);
	if ((buf = (char*)malloc(4096)) == NULL) goto error;
	while ((len = fread(buf, 1, 4096, pFile)) > 0) SHA384_Update(&c, buf, len);
	free(buf);
	buf = NULL;
	fclose(pFile);
	pFile = NULL;
	SHA384_Final((unsigned char*)result, &c);
	return 0;

error:
	if (buf != NULL) free(buf);
	if (pFile != NULL) fclose(pFile);
	return -1;
}

// Frees a block of memory returned from this module.
void __fastcall util_free(char* ptr)
{
	free(ptr);
	//ptr = NULL;
}
char* __fastcall util_tohex(char* data, int len, char* out)
{
	int i;
	char *p = out;
	if (data == NULL || len == 0) { *p = 0; return NULL; }
	for (i = 0; i < len; i++)
	{
		*(p++) = utils_HexTable[((unsigned char)data[i]) >> 4];
		*(p++) = utils_HexTable[((unsigned char)data[i]) & 0x0F];
	}
	*p = 0;
	return out;
}
char* __fastcall util_tohex_lower(char* data, int len, char* out)
{
	int i;
	char *p = out;
	if (data == NULL || len == 0) { *p = 0; return NULL; }
	for (i = 0; i < len; i++)
	{
		*(p++) = utils_HexTable2[((unsigned char)data[i]) >> 4];
		*(p++) = utils_HexTable2[((unsigned char)data[i]) & 0x0F];
	}
	*p = 0;
	return out;
}
char* __fastcall util_tohex2(char* data, int len, char* out)
{
	int i;
	char *p = out;
	if (data == NULL || len == 0) { *p = 0; return NULL; }
	for (i = 0; i < len; i++)
	{
		*(p++) = utils_HexTable[((unsigned char)data[i]) >> 4];
		*(p++) = utils_HexTable[((unsigned char)data[i]) & 0x0F];
		if (i + 1<len)
		{
			*(p++) = ':';
		}
	}
	*p = 0;
	return out;
}
// Convert hex string to int
int __fastcall util_hexToint(char *hexString, int hexStringLength)
{
	int i, res = 0;

	// Ignore the leading zeroes
	while (*hexString == '0' && hexStringLength > 0) { hexString++; hexStringLength--; }

	// Process the rest of the string
	for (i = 0; i < hexStringLength; i++)
	{
		if (hexString[i] >= '0' && hexString[i] <= '9') { res = (res << 4) + (hexString[i] - '0'); }
		else if (hexString[i] >= 'a' && hexString[i] <= 'f') { res = (res << 4) + (hexString[i] - 'a' + 10); }
		else if (hexString[i] >= 'A' && hexString[i] <= 'F') { res = (res << 4) + (hexString[i] - 'A' + 10); }
	}
	return res;
}

// Convert hex string to int 
int __fastcall util_hexToBuf(char *hexString, int hexStringLength, char* output)
{
	int i, x = hexStringLength / 2;
	for (i = 0; i < x; i++) { output[i] = (char)util_hexToint(hexString + (i * 2), 2); }
	return i;
}


// Generates a random string of data. TODO: Use Hardware RNG if possible
#ifdef MICROSTACK_NOTLS
int util_random_seeded = 0;
#endif
void __fastcall util_random(int length, char* result)
{
#ifndef MICROSTACK_NOTLS
	RAND_bytes((unsigned char*)result, length);
#else
	short val;
	int i;

	if (util_random_seeded == 0)
	{
		time_t t;
		srand((unsigned int)time(&t));
		util_random_seeded = 1;
	}

	for (i = 0; i < length; i += 2)
	{
		val = rand();
		memcpy_s(result + i, length - i, &val, (length - i) >= 2 ? 2 : (length - i));
	}
#endif
}

// Generates a random text string, useful for HTTP nonces.
void __fastcall util_randomtext(int length, char* result)
{
	int l;
	util_random(length, result);
	for (l = 0; l<length; l++) result[l] = (unsigned char)((((unsigned char)result[l]) % 10) + '0');
}


size_t __fastcall util_writefile(char* filename, char* data, int datalen)
{
	FILE * pFile = NULL;
	size_t count = 0;

#ifdef WIN32 
	fopen_s(&pFile, filename, "wbN");
#else
	pFile = fopen(filename, "wb");
#endif

	if (pFile != NULL)
	{
		count = fwrite(data, datalen, 1, pFile);
		fclose(pFile);
	}
	return count;
}

size_t __fastcall util_appendfile(char* filename, char* data, int datalen)
{
	FILE * pFile = NULL;
	size_t count = 0;

#ifdef WIN32 
	fopen_s(&pFile, filename, "abN");
#else
	pFile = fopen(filename, "ab");
#endif

	if (pFile != NULL)
	{
		count = fwrite(data, datalen, 1, pFile);
		fclose(pFile);
	}
	return count;
}

// Read a file into memory up to maxlen. If *data is NULL, a new buffer is allocated otherwise, the given one is used.
size_t __fastcall util_readfile(char* filename, char** data, size_t maxlen)
{
	FILE *pFile = NULL;
	size_t count = 0;
	size_t len = 0;
	size_t r = 1;
	if (filename == NULL) return 0;

#ifdef WIN32 
	fopen_s(&pFile, filename, "rbN");
#else
	pFile = fopen(filename, "rb");
#endif

	if (pFile != NULL)
	{
		// If *data is null, we need to allocate memory to read the data. Start by getting the size of the file.
		if (*data == NULL)
		{
			fseek(pFile, 0, SEEK_END);
			count = ftell(pFile);
			if (count > maxlen) count = maxlen;
			fseek(pFile, 0, SEEK_SET);
			*data = (char*)malloc(count + 1);
			if (*data == NULL) { fclose(pFile); return 0; }
		}
		else { count = maxlen - 1; }
		while (r != 0 && len < count)
		{
			r = fread(*data, 1, count - len, pFile);
			len += r;
		}
		(*data)[len] = 0;
		fclose(pFile);
	}
	return len;
}

#ifdef _POSIX
// This method reads a stream where the length of the file can't be determined. Useful in POSIX only
int __fastcall util_readfile2(char* filename, char** data)
{
	FILE * pFile;
	int count = 0;
	int len = 0;
	*data = NULL;
	if (filename == NULL) return 0;

	pFile = fopen(filename, "rb");
	if (pFile != NULL)
	{
		*data = malloc(1024);
		if (*data == NULL) { fclose(pFile); return 0; }
		do
		{
			len = (int)fread((*data) + count, 1, 1023, pFile);
			count += len;
			if (len == 1023) *data = realloc(*data, count + 1024);
		} while (len == 100);
		(*data)[count] = 0;
		fclose(pFile);
	}

	return count;
}
#endif

int __fastcall util_deletefile(char* filename)
{
	if (filename == NULL) return 0;
	return remove(filename);
}

#ifdef WIN32
// Really fast CRC-like method. Used for the KVM.
int __fastcall util_crc(unsigned char *buffer, int len, int initial_value)
{
	int hval = initial_value;
	int *bp = (int*)buffer;
	int *be = bp + (len >> 2);
	while (bp < be)
	{
		//hval *= 0x01000193;
		hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
		hval ^= *bp++;
	}
	// TODO: Handle left over bytes (len % 4)
	return hval;
}
#ifdef _MINCORE
BOOL util_MoveFile(_In_ LPCTSTR lpExistingFileName, _In_  LPCTSTR lpNewFileName)
{
	size_t convertedChars = 0;
	wchar_t lpExistingFileNameW[MAX_PATH];
	wchar_t lpNewFileNameW[MAX_PATH];
	mbstowcs_s(&convertedChars, lpExistingFileNameW, MAX_PATH, (const char*)lpExistingFileName, MAX_PATH);
	mbstowcs_s(&convertedChars, lpNewFileNameW, MAX_PATH, (const char*)lpNewFileName, MAX_PATH);
	return MoveFileExW(lpExistingFileNameW, lpNewFileNameW, 0);
}

BOOL util_CopyFile(_In_ LPCSTR lpExistingFileName, _In_ LPCSTR lpNewFileName, _In_ BOOL bFailIfExists)
{
	size_t convertedChars = 0;
	wchar_t lpExistingFileNameW[MAX_PATH];
	wchar_t lpNewFileNameW[MAX_PATH];
	mbstowcs_s(&convertedChars, lpExistingFileNameW, MAX_PATH, (const char*)lpExistingFileName, MAX_PATH);
	mbstowcs_s(&convertedChars, lpNewFileNameW, MAX_PATH, (const char*)lpNewFileName, MAX_PATH);
	return (CopyFile2(lpExistingFileNameW, lpNewFileNameW, NULL) == S_OK);
}
#else
BOOL util_MoveFile(_In_ LPCSTR lpExistingFileName, _In_  LPCSTR lpNewFileName) { return MoveFileA(lpExistingFileName, lpNewFileName); }
BOOL util_CopyFile(_In_ LPCSTR lpExistingFileName, _In_ LPCSTR lpNewFileName, _In_ BOOL bFailIfExists) { return CopyFileA(lpExistingFileName, lpNewFileName, bFailIfExists); }
#endif
#endif



#ifndef MICROSTACK_NOTLS
// Setup OpenSSL
int InitCounter = 0;
void __fastcall util_openssl_init()
{
	char* tbuf[64];
#if defined(WIN32)
	HMODULE g_hAdvLib = NULL;
	BOOLEAN(APIENTRY *g_CryptGenRandomPtr)(void*, ULONG) = NULL;
#endif
#ifdef _POSIX
	int l;
#endif

	++InitCounter;
	if (InitCounter > 1) { return; }

	SSL_library_init(); // TWO LEAKS COMING FROM THIS LINE. Seems to be a well known OpenSSL problem.
	SSL_load_error_strings();
	ERR_load_crypto_strings(); // ONE LEAK IN LINUX

	OpenSSL_add_all_algorithms(); // OpenSSL 1.1
	OpenSSL_add_all_ciphers(); // OpenSSL 1.1
	OpenSSL_add_all_digests(); // OpenSSL 1.1

	// Add more random seeding in Windows (This is probably useful since OpenSSL in Windows has weaker seeding)
#if defined(WIN32) && !defined(_MINCORE)
	//RAND_screen(); // On Windows, add more random seeding using a screen dump (this is very expensive).
	if ((g_hAdvLib = LoadLibrary(TEXT("ADVAPI32.DLL"))) != 0) g_CryptGenRandomPtr = (BOOLEAN(APIENTRY *)(void*, ULONG))GetProcAddress(g_hAdvLib, "SystemFunction036");
	if (g_CryptGenRandomPtr != 0 && g_CryptGenRandomPtr(tbuf, 64) != 0) RAND_add(tbuf, 64, 64); // Use this high quality random as added seeding
	if (g_hAdvLib != NULL) FreeLibrary(g_hAdvLib);
#endif

	// Add more random seeding in Linux (May be overkill since OpenSSL already uses /dev/urandom)
#ifdef _POSIX
	// Under Linux we use "/dev/urandom" if available. This is the best source of random on Linux & variants
	FILE *pFile = fopen("/dev/urandom", "rb");
	if (pFile != NULL)
	{
		l = (int)fread(tbuf, 1, 64, pFile);
		fclose(pFile);
		if (l > 0) RAND_add(tbuf, l, l);
	}
#endif
}

// Cleanup OpenSSL
void __fastcall util_openssl_uninit()
{
	--InitCounter;
	if (InitCounter > 0) { return; }

	//RAND_cleanup();											// Does nothing.
	//CRYPTO_set_dynlock_create_callback(NULL);					// Does nothing.
	//CRYPTO_set_dynlock_destroy_callback(NULL);				// Does nothing.
	//CRYPTO_set_dynlock_lock_callback(NULL);					// Does nothing.
	//CRYPTO_set_locking_callback(NULL);						// Does nothing.
	//CRYPTO_set_id_callback(NULL);								// Does nothing.
	CRYPTO_cleanup_all_ex_data();
	//sk_SSL_COMP_free(SSL_COMP_get_compression_methods());		// Does something, but it causes heap corruption...
	//CONF_modules_unload(1);									// Does nothing.
	CONF_modules_free();
	EVP_cleanup();
	ERR_free_strings();
	//ERR_remove_state(0);										// Deprecated in OpenSSL/1.1.x

	OPENSSL_cleanup();
}

// Add extension using V3 code: we can set the config file as NULL because we wont reference any other sections.
int __fastcall util_add_ext(X509 *cert, int nid, char *value)
{
	X509_EXTENSION *ex;
	X509V3_CTX ctx;
	// This sets the 'context' of the extensions. No configuration database
	X509V3_set_ctx_nodb(&ctx);
	// Issuer and subject certs: both the target since it is self signed, no request and no CRL
	X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
	ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
	if (!ex) return 0;

	X509_add_ext(cert, ex, -1);
	X509_EXTENSION_free(ex);
	return 1;
}

void __fastcall util_freecert(struct util_cert* cert)
{
	if ((cert->flags & ILibCrypto_Cert_Ownership_Other) == 0)
	{
		if (cert->x509 != NULL) X509_free(cert->x509);
		if (cert->pkey != NULL) EVP_PKEY_free(cert->pkey);
	}
	cert->x509 = NULL;
	cert->pkey = NULL;
	cert->flags = 0;
}

int __fastcall util_to_cer(struct util_cert cert, char** data)
{
	*data = NULL;
	return i2d_X509(cert.x509, (unsigned char**)data);
}

int __fastcall util_from_cer(char* data, int datalen, struct util_cert* cert)
{
	cert->pkey = NULL;
	cert->x509 = d2i_X509(NULL, (const unsigned char**)&data, datalen);
	return ((cert->x509) != NULL);
}

int __fastcall util_from_pkcs7b_string(char *data, int datalen, char *out, int outLen)
{
	BIO* bio = BIO_new_mem_buf((void*)data, datalen);
	BIO* bio_out = BIO_new(BIO_s_mem());
	BUF_MEM *outBuffer;
	int retVal = 0, i = 0;
	PKCS7 *p7 = NULL;
	STACK_OF(X509) *certs = NULL;
	STACK_OF(X509_ALGOR) *md_algs = NULL;
	STACK_OF(PKCS7_SIGNER_INFO) *sinfo = NULL;

	BIO_get_mem_ptr(bio_out, &outBuffer);
	p7 = d2i_PKCS7_bio(bio, NULL);

	md_algs = p7->d.sign->md_algs;
	sinfo = p7->d.sign->signer_info;

	for (i = 0; i < sk_PKCS7_SIGNER_INFO_num(sinfo); i++)
	{
		printf("Hash Algorithm: %s\n", OBJ_nid2ln(OBJ_obj2nid(sk_PKCS7_SIGNER_INFO_value(sinfo, i)->digest_alg->algorithm)));
		printf("Enc Algorithm: %s\n", OBJ_nid2ln(OBJ_obj2nid(sk_PKCS7_SIGNER_INFO_value(sinfo, i)->digest_enc_alg->algorithm)));
	}

	for (i = 0; i < sk_X509_ALGOR_num(md_algs); i++)
	{
		printf("Algorithm: %s\n", OBJ_nid2ln(OBJ_obj2nid(sk_X509_ALGOR_value(md_algs, i)->algorithm)));
	}

	certs = p7->d.sign->cert;
	for (i = 0; certs && i < sk_X509_num(certs); i++) 
	{
		PEM_write_bio_X509(bio_out, sk_X509_value(certs, i));
	}
	
	if (outLen >= (int)outBuffer->length)
	{
		memcpy_s(out, outLen, outBuffer->data, outBuffer->length);
		out[outBuffer->length] = 0;
	}
	else
	{
		retVal = (int)(outBuffer->length) + 1;
	}

	BIO_free(bio);
	BIO_free(bio_out);

	return(retVal);
}

int __fastcall util_from_pem_string(char *data, int datalen, struct util_cert* cert)
{
	BIO* bio = BIO_new_mem_buf((void*)data, datalen);
	int retVal = 0;

	if ((cert->pkey = PEM_read_bio_PrivateKey(bio, NULL, 0, NULL)) == NULL) { retVal = -1; }
	if ((cert->x509 = PEM_read_bio_X509(bio, NULL, 0, NULL)) == NULL) { retVal = -1; }

	BIO_free(bio);
	return retVal;
}

int __fastcall util_from_pem(char* filename, struct util_cert* cert)
{
	FILE *pFile = NULL;

	if (filename == NULL) return -1;
#ifdef WIN32 
	fopen_s(&pFile, filename, "rbN");
#else
	pFile = fopen(filename, "rb");
#endif
	if (pFile == NULL) goto error;

	if ((cert->pkey = PEM_read_PrivateKey(pFile, NULL, 0, NULL)) == NULL) goto error;
	if ((cert->x509 = PEM_read_X509(pFile, NULL, 0, NULL)) == NULL) goto error;

	fclose(pFile);
	return 0;
error:
	if (pFile != NULL) fclose(pFile);
	return -1;
}

int __fastcall util_to_p12(struct util_cert cert, char *password, char** data)
{
	PKCS12 *p12;
	int len;
	p12 = PKCS12_create(password, "Certificate", cert.pkey, cert.x509, NULL, 0, 0, 0, 0, 0);
	*data = NULL;
	len = i2d_PKCS12(p12, (unsigned char**)data);
	PKCS12_free(p12);
	return len;
}

int __fastcall util_from_p12(char* data, int datalen, char* password, struct util_cert* cert)
{
	int r = 0;
	PKCS12 *p12 = NULL;
	if (data == NULL || datalen == 0) return 0;
	cert->x509 = NULL;
	cert->pkey = NULL;
	p12 = d2i_PKCS12(&p12, (const unsigned char**)&data, datalen);
	r = PKCS12_parse(p12, password, &(cert->pkey), &(cert->x509), NULL);
	PKCS12_free(p12);
	return r;
}
void __fastcall util_printcert(struct util_cert cert)
{
	if (cert.x509 == NULL) return;
	X509_print_fp(stdout, cert.x509);
}

void __fastcall util_printcert_pk(struct util_cert cert)
{
	if (cert.pkey == NULL) return;
	//RSA_print_fp(stdout, cert.pkey->pkey.rsa, 0);
	RSA_print_fp(stdout, EVP_PKEY_get1_RSA(cert.pkey), 0);
}

// Creates a X509 certificate, if rootcert is NULL this creates a root (self-signed) certificate.
// Is the name parameter is NULL, the hex value of the hash of the public key will be the subject name.
int __fastcall util_mkCert(struct util_cert *rootcert, struct util_cert* cert, int bits, int days, char* name, enum CERTIFICATE_TYPES certtype, struct util_cert* initialcert)
{
	X509 *x = NULL;
	X509_EXTENSION *ex = NULL;
	EVP_PKEY *pk = NULL;
	RSA *rsa = NULL;
	X509_NAME *cname = NULL;
	X509 **x509p = NULL;
	EVP_PKEY **pkeyp = NULL;
	int hashlen = UTIL_SHA384_HASHSIZE;
	char hash[UTIL_SHA384_HASHSIZE];
	char serial[8];
	char nameStr[(UTIL_SHA384_HASHSIZE * 2) + 2];
	BIGNUM *oBigNbr;

	CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);

	if (initialcert != NULL)
	{
		pk = X509_get_pubkey(initialcert->x509);
		rsa = EVP_PKEY_get1_RSA(initialcert->pkey);
		if ((x = X509_new()) == NULL) goto err;
	}
	else
	{
		if ((pkeyp == NULL) || (*pkeyp == NULL)) { if ((pk = EVP_PKEY_new()) == NULL) return 0; }
		else pk = *pkeyp;
		if ((x509p == NULL) || (*x509p == NULL)) { if ((x = X509_new()) == NULL) goto err; }
		else x = *x509p;
		oBigNbr = BN_new();
		rsa = RSA_new();
		BN_set_word(oBigNbr, RSA_F4);
		if (RSA_generate_key_ex(rsa, bits, oBigNbr, NULL) == -1)
		{
			RSA_free(rsa);
			BN_free(oBigNbr);
			abort();
		}
		BN_free(oBigNbr);
	}

	if (!EVP_PKEY_assign_RSA(pk, rsa))
	{
		RSA_free(rsa);
		abort();
	}
	rsa = NULL;

	util_randomtext(8, serial);
	X509_set_version(x, 2);
	ASN1_STRING_set(X509_get_serialNumber(x), serial, 8);
	X509_gmtime_adj(X509_get_notBefore(x), (long)60 * 60 * 24 * -10);
	X509_gmtime_adj(X509_get_notAfter(x), (long)60 * 60 * 24 * days);
	X509_set_pubkey(x, pk);

	// Set the subject name
	cname = X509_get_subject_name(x);

	if (name == NULL)
	{
		// Computer the hash of the public key
		//util_sha256((char*)x->cert_info->key->public_key->data, x->cert_info->key->public_key->length, hash); // OpenSSL 1.0
		X509_pubkey_digest(x, EVP_sha384(), (unsigned char*)hash, (unsigned int*)&hashlen); // OpenSSL 1.1

		util_tohex(hash, UTIL_SHA384_HASHSIZE, nameStr);
		X509_NAME_add_entry_by_txt(cname, "CN", MBSTRING_ASC, (unsigned char*)nameStr, -1, -1, 0);
	}
	else
	{
		// This function creates and adds the entry, working out the correct string type and performing checks on its length. Normally we'd check the return value for errors...
		X509_NAME_add_entry_by_txt(cname, "CN", MBSTRING_ASC, (unsigned char*)name, -1, -1, 0);
	}

	if (rootcert == NULL)
	{
		// Its self signed so set the issuer name to be the same as the subject.
		X509_set_issuer_name(x, cname);

		// Add various extensions: standard extensions
		util_add_ext(x, NID_basic_constraints, "critical,CA:TRUE");
		util_add_ext(x, NID_key_usage, "critical,keyCertSign,cRLSign");

		util_add_ext(x, NID_subject_key_identifier, "hash");
		//util_add_ext(x, NID_netscape_cert_type, "sslCA");
		//util_add_ext(x, NID_netscape_comment, "example comment extension");

		if (!X509_sign(x, pk, EVP_sha384())) goto err;
	}
	else
	{
		// This is a sub-certificate
		cname = X509_get_subject_name(rootcert->x509);
		X509_set_issuer_name(x, cname);

		// Add usual cert stuff
		ex = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, "digitalSignature, keyEncipherment, keyAgreement");
		X509_add_ext(x, ex, -1);
		X509_EXTENSION_free(ex);

		// Add usages: TLS server, TLS client, Intel(R) AMT Console
		//ex = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, "TLS Web Server Authentication, TLS Web Client Authentication, 2.16.840.1.113741.1.2.1, 2.16.840.1.113741.1.2.2");
		if (certtype == CERTIFICATE_TLS_SERVER)
		{
			// TLS server
			ex = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, "TLS Web Server Authentication");
			X509_add_ext(x, ex, -1);
			X509_EXTENSION_free(ex);
		}
		else if (certtype == CERTIFICATE_TLS_CLIENT)
		{
			// TLS client
			ex = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, "TLS Web Client Authentication");
			X509_add_ext(x, ex, -1);
			X509_EXTENSION_free(ex);
		}

		if (!X509_sign(x, rootcert->pkey, EVP_sha384())) goto err;
	}

	cert->x509 = x;
	cert->pkey = pk;
	cert->flags = 0;

	return 1;
err:
	return 0;
}

int __fastcall util_certhash(struct util_cert cert, char* result) {
	int hashlen = UTIL_SHA384_HASHSIZE;
	if (cert.x509 == NULL) return -1;
	X509_digest(cert.x509, EVP_sha384(), (unsigned char*)result, (unsigned int *)&hashlen); // OpenSSL 1.1
	return 0;
}

int __fastcall util_certhash2(X509* cert, char* result) {
	int hashlen = UTIL_SHA384_HASHSIZE;
	if (cert == NULL) return -1;
	X509_digest(cert, EVP_sha384(), (unsigned char*)result, (unsigned int*)&hashlen); // OpenSSL 1.1
	return 0;
}

int __fastcall util_keyhash(struct util_cert cert, char* result)
{
	int hashlen = UTIL_SHA384_HASHSIZE;
	if (cert.x509 == NULL) return -1;
	X509_pubkey_digest(cert.x509, EVP_sha384(), (unsigned char*)result,(unsigned int *) &hashlen); // OpenSSL 1.1
	return 0;
}

int __fastcall util_keyhash2(X509* cert, char* result)
{
	int hashlen = UTIL_SHA384_HASHSIZE;
	if (cert == NULL) return -1;
	X509_pubkey_digest(cert, EVP_sha384(), (unsigned char*)result, (unsigned int*)&hashlen); // OpenSSL 1.1
	return 0;
}

// Sign this block of data, the first 32 bytes of the block must be avaialble to add the certificate hash.
int __fastcall util_sign(struct util_cert cert, char* data, int datalen, char** signature)
{
	int size = 0;
	unsigned int hashsize = UTIL_SHA384_HASHSIZE;
	BIO *in = NULL;
	PKCS7 *message = NULL;
	*signature = NULL;
	if (datalen <= UTIL_SHA384_HASHSIZE) return 0;

	// Add hash of the certificate to start of data
	X509_digest(cert.x509, EVP_sha384(), (unsigned char*)data, &hashsize);

	// Sign the block
	in = BIO_new_mem_buf(data, datalen);
	message = PKCS7_sign(cert.x509, cert.pkey, NULL, in, PKCS7_BINARY);
	if (message == NULL) goto error;
	size = i2d_PKCS7(message, (unsigned char**)signature);

error:
	if (message != NULL) PKCS7_free(message);
	if (in != NULL) BIO_free(in);
	return size;
}

// Verify the signed block, the first 32 bytes of the data must be the certificate hash to work.
int __fastcall util_verify(char* signature, int signlen, struct util_cert* cert, char** data)
{
	unsigned int size, r;
	BIO *out = NULL;
	PKCS7 *message = NULL;
	char* data2 = NULL;
	char hash[UTIL_SHA256_HASHSIZE];
	STACK_OF(X509) *st = NULL;

	cert->x509 = NULL;
	cert->pkey = NULL;
	*data = NULL;
	message = d2i_PKCS7(NULL, (const unsigned char**)&signature, signlen);
	if (message == NULL) goto error;
	out = BIO_new(BIO_s_mem());

	// Lets rebuild the original message and check the size
	size = i2d_PKCS7(message, NULL);
	if (size < (unsigned int)signlen) goto error;

	// Check the PKCS7 signature, but not the certificate chain.
	r = PKCS7_verify(message, NULL, NULL, NULL, out, PKCS7_NOVERIFY);
	if (r == 0) goto error;

	// If data block contains less than 32 bytes, fail.
	size = (unsigned int)BIO_get_mem_data(out, &data2);
	if (size <= UTIL_SHA256_HASHSIZE) goto error;

	// Copy the data block
	*data = (char*)malloc(size + 1);
	if (*data == NULL) goto error;
	memcpy_s(*data, size + 1, data2, size);
	(*data)[size] = 0;

	// Get the certificate signer
	st = PKCS7_get0_signers(message, NULL, PKCS7_NOVERIFY);
	cert->x509 = X509_dup(sk_X509_value(st, 0));
	sk_X509_free(st);

	// Get a full certificate hash of the signer
	r = UTIL_SHA256_HASHSIZE;
	X509_digest(cert->x509, EVP_sha256(), (unsigned char*)hash, &r);

	// Check certificate hash with first 32 bytes of data.
	if (memcmp(hash, *data, UTIL_SHA256_HASHSIZE) != 0) goto error;

	// Approved, cleanup and return.
	BIO_free(out);
	PKCS7_free(message);

	return size;

error:
	if (out != NULL) BIO_free(out);
	if (message != NULL) PKCS7_free(message);
	if (*data != NULL) free(*data);
	if (cert->x509 != NULL) { X509_free(cert->x509); cert->x509 = NULL; }

	return 0;
}

// Encrypt a block of data for a target certificate
int __fastcall util_encrypt(struct util_cert cert, char* data, int datalen, char** encdata)
{
	int size = 0;
	BIO *in = NULL;
	PKCS7 *message = NULL;
	STACK_OF(X509) *encerts = NULL;
	*encdata = NULL;
	if (datalen == 0) return 0;

	// Setup certificates
	encerts = sk_X509_new_null();
	sk_X509_push(encerts, cert.x509);

	// Encrypt the block
	*encdata = NULL;
	in = BIO_new_mem_buf(data, datalen);
	message = PKCS7_encrypt(encerts, in, EVP_aes_128_cbc(), PKCS7_BINARY);
	if (message == NULL) return 0;
	size = i2d_PKCS7(message, (unsigned char**)encdata);
	BIO_free(in);
	PKCS7_free(message);
	sk_X509_free(encerts);
	return size;
}

// Encrypt a block of data using multiple target certificates
int __fastcall util_encrypt2(STACK_OF(X509) *certs, char* data, int datalen, char** encdata)
{
	int size = 0;
	BIO *in = NULL;
	PKCS7 *message = NULL;
	*encdata = NULL;
	if (datalen == 0) return 0;

	// Encrypt the block
	*encdata = NULL;
	in = BIO_new_mem_buf(data, datalen);
	message = PKCS7_encrypt(certs, in, EVP_aes_128_cbc(), PKCS7_BINARY);
	if (message == NULL) return 0;
	size = i2d_PKCS7(message, (unsigned char**)encdata);
	BIO_free(in);
	PKCS7_free(message);
	return size;
}

// Decrypt a block of data using the specified certificate. The certificate must have a private key.
int __fastcall util_decrypt(char* encdata, int encdatalen, struct util_cert cert, char** data)
{
	unsigned int size, r;
	BIO *out = NULL;
	PKCS7 *message = NULL;
	char* data2 = NULL;

	*data = NULL;
	if (cert.pkey == NULL) return 0;

	message = d2i_PKCS7(NULL, (const unsigned char**)&encdata, encdatalen);
	if (message == NULL) goto error;
	out = BIO_new(BIO_s_mem());

	// Lets rebuild the original message and check the size
	size = i2d_PKCS7(message, NULL);
	if (size < (unsigned int)encdatalen) goto error;

	// Decrypt the PKCS7
	r = PKCS7_decrypt(message, cert.pkey, cert.x509, out, 0);
	if (r == 0) goto error;

	// If data block contains 0 bytes, fail.
	size = (unsigned int)BIO_get_mem_data(out, &data2);
	if (size == 0) goto error;

	// Copy the data block
	*data = (char*)malloc(size + 1);
	if (*data == NULL) goto error;
	memcpy_s(*data, size + 1, data2, size);
	(*data)[size] = 0;

	// Cleanup and return.
	BIO_free(out);
	PKCS7_free(message);

	return size;

error:
	if (out != NULL) BIO_free(out);
	if (message != NULL) PKCS7_free(message);
	if (*data != NULL) free(*data);
	if (data2 != NULL) free(data2);
	return 0;
}

// Encrypt a block of data using raw RSA. This is used to handle data in the most compact possible way.
int __fastcall util_rsaencrypt(X509 *cert, char* data, int datalen, char** encdata)
{
	int len;
	RSA *rsa;
	EVP_PKEY *pkey;

	pkey = X509_get_pubkey(cert);
	rsa = EVP_PKEY_get1_RSA(pkey);
	if (datalen > RSA_size(rsa)) { EVP_PKEY_free(pkey); RSA_free(rsa); return 0; }
	*encdata = (char*)malloc(RSA_size(rsa));
	len = RSA_public_encrypt(datalen, (const unsigned char*)data, (unsigned char*)*encdata, rsa, RSA_PKCS1_OAEP_PADDING);
	EVP_PKEY_free(pkey);
	RSA_free(rsa);
	if (len == RSA_size(rsa)) return len;
	free(*encdata);
	*encdata = NULL;
	return 0;
}

// Decrypt a block of data using raw RSA. This is used to handle data in the most compact possible way.
int __fastcall util_rsadecrypt(struct util_cert cert, char* data, int datalen, char** decdata)
{
	int len;
	RSA *rsa;

	rsa = EVP_PKEY_get1_RSA(cert.pkey);
	*decdata = (char*)malloc(RSA_size(rsa));
	len = RSA_private_decrypt(datalen, (const unsigned char*)data, (unsigned char*)*decdata, rsa, RSA_PKCS1_OAEP_PADDING);
	RSA_free(rsa);
	if (len != 0) return len;
	free(*decdata);
	*decdata = NULL;
	return 0;
}

// Verify the RSA signature of a block using SHA1 hash
int __fastcall util_rsaverify(X509 *cert, char* data, int datalen, char* sign, int signlen)
{
	int r;
	RSA *rsa = NULL;
	EVP_PKEY *pkey = NULL;
	SHA_CTX c;
	char hash[20];

	SHA1_Init(&c);
	SHA1_Update(&c, data, datalen);
	SHA1_Final((unsigned char*)hash, &c);
	pkey = X509_get_pubkey(cert);
	rsa = EVP_PKEY_get1_RSA(pkey);
	//rsa->pad = RSA_PKCS1_PADDING; // OPENSSL 1.0
#ifdef WIN32
	r = RSA_verify(NID_sha1, (const unsigned char*)hash, 20, (const unsigned char*)sign, signlen, rsa);
#else
	r = RSA_verify(NID_sha1, (const unsigned char*)hash, 20, (unsigned char*)sign, signlen, rsa);
#endif
	EVP_PKEY_free(pkey);
	RSA_free(rsa);
	return r;
}

#ifdef _DEBUG
// Saves the SSL/TLS session private keys to file.
// Because we do lots of DTLS, we will be saving both client and server randoms pointing to the same key.
// WARNING: THIS IS FOR DEBUG ONLY, NEVER RELEASE THIS. For safety, comment this out.
void  __fastcall util_savekeys(SSL* ssl) {
	/*
	int len;
	char clientRandom[32], serverRandom[32], sessionSecret[48], clientRandomHex[65], serverRandomHex[65], sessionSecretHex[97], text[2000];

	// Get the client random and session key.
	if (ssl == NULL) return;
	if (SSL_get_client_random(ssl, clientRandom, 32) != 32) return;
	if (SSL_get_server_random(ssl, serverRandom, 32) != 32) return;
	if (SSL_SESSION_get_master_key(SSL_get_session(ssl), sessionSecret, 48) != 48) return;

	// Convert the randoms and key into hex
	util_tohex(clientRandom, 32, clientRandomHex);
	util_tohex(serverRandom, 32, serverRandomHex);
	util_tohex(sessionSecret, 48, sessionSecretHex);

	// Append the client random and key to the log file.
	len = snprintf(text, 1000, "CLIENT_RANDOM %s %s\r\nCLIENT_RANDOM %s %s\r\n", clientRandomHex, sessionSecretHex, serverRandomHex, sessionSecretHex);
	util_appendfile("meshagentkeys.log", text, len);
	*/
}
#endif

#endif
