#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <assert.h>

#include <openssl/crypto.h>
#include <openssl/engine.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/dh.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/ocsp.h>
#include <ssl_utils.h>
#include <tfe_utils.h>

/*
 * Print OpenSSL version and build-time configuration to standard error and
 * return.
 */
void ssl_openssl_version(void)
{
	fprintf(stderr, "compiled against %s (%lx)\n", OPENSSL_VERSION_TEXT, (long unsigned int) OPENSSL_VERSION_NUMBER);
	fprintf(stderr, "rtlinked against %s (%lx)\n", SSLeay_version(SSLEAY_VERSION), SSLeay());

	if ((OPENSSL_VERSION_NUMBER ^ SSLeay()) & 0xfffff000L)
	{
		fprintf(stderr, "---------------------------------------"
						"---------------------------------------\n");
		fprintf(stderr, "WARNING: OpenSSL version mismatch may "
						"lead to crashes or other problems!\n");
		fprintf(stderr, "If there are multiple versions of "
						"OpenSSL available, make sure to use\n");
		fprintf(stderr, "the same version of the library at "
						"runtime as well as for compiling against.\n");
		fprintf(stderr, "---------------------------------------"
						"---------------------------------------\n");
	}

#ifdef LIBRESSL_VERSION_NUMBER
    fprintf(stderr, "LibreSSL detected: %s (%lx)\n",
                    LIBRESSL_VERSION_TEXT,
                    (long unsigned int)LIBRESSL_VERSION_NUMBER);
#endif /* LIBRESSL_VERSION_NUMBER */
#ifdef OPENSSL_IS_BORINGSSL
	fprintf(stderr, "BoringSSL detected\n")
#endif /* OPENSSL_IS_BORINGSSL */

#ifndef OPENSSL_NO_TLSEXT
	fprintf(stderr, "OpenSSL has support for TLS extensions\n"
					"TLS Server Name Indication (SNI) supported\n");
#else /* OPENSSL_NO_TLSEXT */
    fprintf(stderr, "OpenSSL has no support for TLS extensions\n"
                    "TLS Server Name Indication (SNI) not supported\n");
#endif /* OPENSSL_NO_TLSEXT */
#ifdef OPENSSL_THREADS
#ifndef OPENSSL_NO_THREADID
	fprintf(stderr, "OpenSSL is thread-safe with THREADID\n");
#else /* OPENSSL_NO_THREADID */
	fprintf(stderr, "OpenSSL is thread-safe without THREADID\n");
#endif /* OPENSSL_NO_THREADID */
#else /* !OPENSSL_THREADS */
	fprintf(stderr, "OpenSSL is not thread-safe\n");
#endif /* !OPENSSL_THREADS */
#ifdef SSL_MODE_RELEASE_BUFFERS
	fprintf(stderr, "Using SSL_MODE_RELEASE_BUFFERS\n");
#else /* !SSL_MODE_RELEASE_BUFFERS */
	fprintf(stderr, "Not using SSL_MODE_RELEASE_BUFFERS\n");
#endif /* !SSL_MODE_RELEASE_BUFFERS */
#if (OPENSSL_VERSION_NUMBER == 0x0090819fL) || \
    (OPENSSL_VERSION_NUMBER == 0x100000bfL) || \
    (OPENSSL_VERSION_NUMBER == 0x1000105fL)
	fprintf(stderr, "Using direct access workaround when loading certs\n");
#endif /* OpenSSL 0.9.8y, 1.0.0k or 1.0.1e */

	fprintf(stderr, "SSL/TLS protocol availability: %s\n",
		SSL_PROTO_SUPPORT_S);

	fprintf(stderr, "SSL/TLS algorithm availability:");
#ifndef OPENSSL_NO_SHA0
	fprintf(stderr, " SHA0");
#else /* !OPENSSL_NO_SHA0 */
	fprintf(stderr, " !SHA0");
#endif /* !OPENSSL_NO_SHA0 */
#ifndef OPENSSL_NO_RSA
	fprintf(stderr, " RSA");
#else /* !OPENSSL_NO_RSA */
	fprintf(stderr, " !RSA");
#endif /* !OPENSSL_NO_RSA */
#ifndef OPENSSL_NO_DSA
	fprintf(stderr, " DSA");
#else /* !OPENSSL_NO_DSA */
	fprintf(stderr, " !DSA");
#endif /* !OPENSSL_NO_DSA */
#ifndef OPENSSL_NO_ECDSA
	fprintf(stderr, " ECDSA");
#else /* !OPENSSL_NO_ECDSA */
	fprintf(stderr, " !ECDSA");
#endif /* !OPENSSL_NO_ECDSA */
#ifndef OPENSSL_NO_DH
	fprintf(stderr, " DH");
#else /* !OPENSSL_NO_DH */
	fprintf(stderr, " !DH");
#endif /* !OPENSSL_NO_DH */
#ifndef OPENSSL_NO_ECDH
	fprintf(stderr, " ECDH");
#else /* !OPENSSL_NO_ECDH */
	fprintf(stderr, " !ECDH");
#endif /* !OPENSSL_NO_ECDH */
#ifndef OPENSSL_NO_EC
	fprintf(stderr, " EC");
#else /* !OPENSSL_NO_EC */
	fprintf(stderr, " !EC");
#endif /* !OPENSSL_NO_EC */
	fprintf(stderr, "\n");

	fprintf(stderr, "OpenSSL option availability:");
#ifdef SSL_OP_NO_COMPRESSION
	fprintf(stderr, " SSL_OP_NO_COMPRESSION");
#else /* !SSL_OP_NO_COMPRESSION */
	fprintf(stderr, " !SSL_OP_NO_COMPRESSION");
#endif /* SSL_OP_NO_COMPRESSION */
#ifdef SSL_OP_NO_TICKET
	fprintf(stderr, " SSL_OP_NO_TICKET");
#else /* !SSL_OP_NO_TICKET */
	fprintf(stderr, " !SSL_OP_NO_TICKET");
#endif /* SSL_OP_NO_TICKET */
#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
	fprintf(stderr, " SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION");
#else /* !SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION */
	fprintf(stderr, " !SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION");
#endif /* !SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION */
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
	fprintf(stderr, " SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS");
#else /* !SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */
	fprintf(stderr, " !SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS");
#endif /* !SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
	fprintf(stderr, " SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION");
#else /* !SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION */
	fprintf(stderr, " !SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION");
#endif /* !SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION */
#ifdef SSL_OP_TLS_ROLLBACK_BUG
	fprintf(stderr, " SSL_OP_TLS_ROLLBACK_BUG");
#else /* !SSL_OP_TLS_ROLLBACK_BUG */
	fprintf(stderr, " !SSL_OP_TLS_ROLLBACK_BUG");
#endif /* !SSL_OP_TLS_ROLLBACK_BUG */
	fprintf(stderr, "\n");
}


/*
 * 1 if OpenSSL has been initialized, 0 if not.  When calling a _load()
 * function the first time, OpenSSL will automatically be initialized.
 * Not protected by a mutex and thus not thread-safe.
 */
static int ssl_initialized = 0;

int ssl_init(void)
{
	int fd;
	char buf[256];

	if (ssl_initialized) return 0;

	/* general initialization */
	SSL_library_init();

#ifdef PURIFY
    CRYPTO_malloc_init();
    CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
#endif /* PURIFY */

	SSL_load_error_strings();
	OpenSSL_add_all_algorithms();

	if ((fd = open("/dev/urandom", O_RDONLY)) == -1)
	{
		TFE_LOG_ERROR(NULL, "Error opening /dev/urandom for reading: %s", strerror(errno));
		/* after log, reset errno */
		errno = 0;
		goto __errout;
	}

	while (!RAND_status())
	{
		if (read(fd, buf, sizeof(buf)) == -1)
		{
			TFE_LOG_ERROR(NULL, "Error reading from /dev/urandom: %s", strerror(errno));
			/* after log, reset errno */
			errno = 0;
			goto __errout;
		}

		RAND_seed(buf, sizeof(buf));
	}

	if (!RAND_poll())
	{
		TFE_LOG_ERROR(NULL, "RAND_poll() failed.");
		return -1;
	}

	ssl_initialized = 1;
	close(fd);

	return 0;

__errout:
	if (fd > 0) close(fd);
	return -1;
}

/*
 * Re-initialize OpenSSL after forking.  Returns 0 on success, -1 on failure.
 */
int ssl_reinit(void)
{
	if (!ssl_initialized)
		return 0;
	return 0;
}

/*
 * Deinitialize OpenSSL and free as much memory as possible.
 * Some 10k-100k will still remain resident no matter what.
 */
void
ssl_fini(void)
{
	if (!ssl_initialized)
		return;

	ENGINE_cleanup();
	CONF_modules_finish();
	CONF_modules_unload(1);
	CONF_modules_free();

	EVP_cleanup();
	ERR_free_strings();
	CRYPTO_cleanup_all_ex_data();
}

/*
 * Format raw SHA1 hash into newly allocated string, with or without colons.
 */
char * ssl_sha1_to_str(unsigned char * rawhash, int colons)
{
	char * str;
	int rv;

	rv = asprintf(&str, colons ?
						"%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X"
						"%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X" :
						"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X"
						"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
		rawhash[0], rawhash[1], rawhash[2], rawhash[3],
		rawhash[4], rawhash[5], rawhash[6], rawhash[7],
		rawhash[8], rawhash[9], rawhash[10], rawhash[11],
		rawhash[12], rawhash[13], rawhash[14], rawhash[15],
		rawhash[16], rawhash[17], rawhash[18], rawhash[19]);

	if (rv == -1)
		return NULL;
	return str;
}

/*
 * Format SSL state into newly allocated string.
 * Returns pointer to string that must be freed by caller, or NULL on error.
 */
char * ssl_ssl_state_to_str(SSL * ssl)
{
	char * str = NULL;
	int rv;

	rv = asprintf(&str, "%08x = %s%s%04x = %s (%s) [%s]",
		SSL_get_state(ssl),
		(SSL_get_state(ssl) & SSL_ST_CONNECT) ? "SSL_ST_CONNECT|" : "",
		(SSL_get_state(ssl) & SSL_ST_ACCEPT) ? "SSL_ST_ACCEPT|" : "",
		SSL_get_state(ssl) & SSL_ST_MASK,
		SSL_state_string(ssl),
		SSL_state_string_long(ssl),
		SSL_is_server(ssl) ? "accept socket" : "connect socket");

	return (rv < 0) ? NULL : str;
}

/*
 * Generates a NSS key log format compatible string containing the client
 * random and the master key, intended to be used to decrypt externally
 * captured network traffic using tools like Wireshark.
 *
 * Only supports the CLIENT_RANDOM method (SSL 3.0 - TLS 1.2).
 *
 * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
 */
char * ssl_ssl_masterkey_to_str(SSL * ssl)
{
	char * str = NULL;
	int rv;
	unsigned char * k, * r;
	//https://www.openssl.org/docs/man1.1.1/man3/SSL_SESSION_get_master_key.html
	//In current versions of the TLS protocols, the length of client_random (and also server_random) is
	//always SSL3_RANDOM_SIZE bytes.
	unsigned char kbuf[48]={0}, rbuf[32]={0};//
	k = &kbuf[0];
	r = &rbuf[0];
	SSL_SESSION* sess=SSL_get0_session(ssl);
	SSL_SESSION_get_master_key(sess, k, sizeof(kbuf));
	SSL_get_client_random(ssl, r, sizeof(rbuf));

	rv = asprintf(&str,
		"CLIENT_RANDOM "
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		" "
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"%02X%02X%02X%02X%02X%02X%02X%02X"
		"\n",
		r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7],
		r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15],
		r[16], r[17], r[18], r[19], r[20], r[21], r[22], r[23],
		r[24], r[25], r[26], r[27], r[28], r[29], r[30], r[31],
		k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7],
		k[8], k[9], k[10], k[11], k[12], k[13], k[14], k[15],
		k[16], k[17], k[18], k[19], k[20], k[21], k[22], k[23],
		k[24], k[25], k[26], k[27], k[28], k[29], k[30], k[31],
		k[32], k[33], k[34], k[35], k[36], k[37], k[38], k[39],
		k[40], k[41], k[42], k[43], k[44], k[45], k[46], k[47]);

	return (rv < 0) ? NULL : str;
}

#ifndef OPENSSL_NO_DH
static unsigned char dh_g[] = {0x02};
static unsigned char dh512_p[] = {
	0xAB, 0xC0, 0x34, 0x16, 0x95, 0x8B, 0x57, 0xE5, 0x5C, 0xB3, 0x4E, 0x6E,
	0x16, 0x0B, 0x35, 0xC5, 0x6A, 0xCC, 0x4F, 0xD3, 0xE5, 0x46, 0xE2, 0x23,
	0x6A, 0x5B, 0xBB, 0x5D, 0x3D, 0x52, 0xEA, 0xCE, 0x4F, 0x7D, 0xCA, 0xFF,
	0xB4, 0x8B, 0xC9, 0x78, 0xDC, 0xA0, 0xFC, 0xBE, 0xF3, 0xB5, 0xE6, 0x61,
	0xA6, 0x6D, 0x58, 0xFC, 0xA0, 0x0F, 0xF7, 0x9B, 0x97, 0xE6, 0xC7, 0xE8,
	0x1F, 0xCD, 0x16, 0x73};
static unsigned char dh1024_p[] = {
	0x99, 0x28, 0x34, 0x48, 0x9E, 0xB7, 0xD1, 0x4F, 0x0D, 0x17, 0x09, 0x97,
	0xB9, 0x9B, 0x20, 0xFE, 0xE5, 0x65, 0xE0, 0xE2, 0x56, 0x37, 0x80, 0xA2,
	0x9F, 0x2C, 0x2D, 0x87, 0x10, 0x58, 0x39, 0xAD, 0xF3, 0xC5, 0xA9, 0x08,
	0x24, 0xC7, 0xAA, 0xA9, 0x29, 0x3A, 0x13, 0xDF, 0x4E, 0x0A, 0x6D, 0x11,
	0x39, 0xB1, 0x1C, 0x3F, 0xFE, 0xFE, 0x0A, 0x5E, 0xAD, 0x2E, 0x5C, 0x10,
	0x97, 0x38, 0xAC, 0xE8, 0xEB, 0xAA, 0x4A, 0xA1, 0xC0, 0x5C, 0x1D, 0x27,
	0x65, 0x9C, 0xC8, 0x53, 0xAC, 0x35, 0xDD, 0x84, 0x1F, 0x47, 0x0E, 0x04,
	0xF1, 0x90, 0x61, 0x62, 0x2E, 0x29, 0x2C, 0xC6, 0x28, 0x91, 0x6D, 0xF0,
	0xE2, 0x5E, 0xCE, 0x60, 0x3E, 0xF7, 0xF8, 0x37, 0x99, 0x4D, 0x9F, 0xFB,
	0x68, 0xEC, 0x7F, 0x9D, 0x32, 0x74, 0xD1, 0xAA, 0xD4, 0x4C, 0xF5, 0xCD,
	0xC2, 0xD7, 0xD7, 0xAC, 0xDA, 0x69, 0xF5, 0x2B};
static unsigned char dh2048_p[] = {
	0xAB, 0x88, 0x97, 0xCA, 0xF1, 0xE1, 0x60, 0x39, 0xFA, 0xB1, 0xA8, 0x7D,
	0xB3, 0x7A, 0x38, 0x08, 0xF0, 0x7A, 0x3D, 0x21, 0xC4, 0xE6, 0xB8, 0x32,
	0x3D, 0xAB, 0x0F, 0xE7, 0x8C, 0xA1, 0x59, 0x47, 0xB2, 0x0A, 0x7A, 0x3A,
	0x20, 0x2A, 0x1B, 0xD4, 0xBA, 0xFC, 0x4C, 0xC5, 0xEE, 0xA2, 0xB9, 0xB9,
	0x65, 0x47, 0xCC, 0x13, 0x99, 0xD7, 0xA6, 0xCA, 0xFF, 0x23, 0x05, 0x91,
	0xAB, 0x5C, 0x82, 0xB8, 0xB4, 0xFD, 0xB1, 0x2E, 0x5B, 0x0F, 0x8E, 0x03,
	0x3C, 0x23, 0xD6, 0x6A, 0xE2, 0x83, 0x95, 0xD2, 0x8E, 0xEB, 0xDF, 0x3A,
	0xAF, 0x89, 0xF0, 0xA0, 0x14, 0x09, 0x12, 0xF6, 0x54, 0x54, 0x93, 0xF4,
	0xD4, 0x41, 0x56, 0x7A, 0x0E, 0x56, 0x20, 0x1F, 0x1D, 0xBA, 0x3F, 0x07,
	0xD2, 0x89, 0x1B, 0x40, 0xD0, 0x1C, 0x08, 0xDF, 0x00, 0x7F, 0x34, 0xF4,
	0x28, 0x4E, 0xF7, 0x53, 0x8D, 0x4A, 0x00, 0xC3, 0xC0, 0x89, 0x9E, 0x63,
	0x96, 0xE9, 0x52, 0xDF, 0xA5, 0x2C, 0x00, 0x4E, 0xB0, 0x82, 0x6A, 0x10,
	0x28, 0x8D, 0xB9, 0xE7, 0x7A, 0xCB, 0xC3, 0xD6, 0xC1, 0xC0, 0x4D, 0x91,
	0xC4, 0x6F, 0xD3, 0x99, 0xD1, 0x86, 0x71, 0x67, 0x0A, 0xA1, 0xFC, 0xF4,
	0x7D, 0x40, 0x88, 0x8D, 0xAC, 0xCB, 0xBC, 0xEA, 0x17, 0x85, 0x0B, 0xC6,
	0x12, 0x3E, 0x4A, 0xB9, 0x60, 0x74, 0x93, 0x54, 0x14, 0x39, 0x10, 0xBF,
	0x21, 0xB0, 0x8B, 0xB1, 0x55, 0x3F, 0xBB, 0x6A, 0x1F, 0x42, 0x82, 0x0A,
	0x40, 0x3A, 0x15, 0xCD, 0xD3, 0x79, 0xD0, 0x02, 0xA4, 0xF5, 0x79, 0x78,
	0x03, 0xBD, 0x47, 0xCC, 0xD5, 0x08, 0x6A, 0x46, 0xAE, 0x36, 0xE4, 0xCD,
	0xB1, 0x17, 0x48, 0x30, 0xB4, 0x02, 0xBC, 0x50, 0x68, 0xE3, 0xA2, 0x76,
	0xD0, 0x5C, 0xB9, 0xE6, 0xBE, 0x4C, 0xFD, 0x50, 0xEF, 0xD0, 0x3F, 0x39,
	0x4F, 0x53, 0x16, 0x3B};
static unsigned char dh4096_p[] = {
	0xB1, 0xCC, 0x09, 0x86, 0xEE, 0xF9, 0xB9, 0xC9, 0xB9, 0x87, 0xC4, 0xB9,
	0xD7, 0x31, 0x95, 0x84, 0x94, 0x65, 0xED, 0x82, 0x64, 0x11, 0xA7, 0x0A,
	0xFE, 0xC2, 0x60, 0xAE, 0x7C, 0x74, 0xFB, 0x72, 0x8F, 0x0D, 0xA6, 0xDD,
	0x02, 0x49, 0x5B, 0x69, 0xD6, 0x96, 0x05, 0xBE, 0x5E, 0x9B, 0x09, 0x83,
	0xD8, 0xF3, 0x91, 0x55, 0x30, 0x86, 0x97, 0x6C, 0x48, 0x7B, 0x99, 0x82,
	0xCC, 0x1E, 0x1E, 0x25, 0xE6, 0x25, 0xCC, 0xA3, 0x66, 0xDE, 0x8A, 0x78,
	0xEE, 0x7F, 0x4F, 0x86, 0x95, 0x06, 0xBE, 0x64, 0x86, 0xFD, 0x60, 0x6A,
	0x3F, 0x0D, 0x8F, 0x62, 0x17, 0x89, 0xDB, 0xE1, 0x01, 0xC1, 0x75, 0x3A,
	0x78, 0x42, 0xA8, 0x26, 0xEC, 0x00, 0x78, 0xF3, 0xDA, 0x40, 0x8D, 0x0D,
	0x4D, 0x53, 0x82, 0xD7, 0x21, 0xC8, 0x46, 0xC9, 0xE3, 0x80, 0xB4, 0xCF,
	0xEA, 0x46, 0x85, 0xE9, 0xC4, 0x9D, 0xD0, 0xC0, 0x4D, 0x27, 0x0F, 0xF8,
	0x34, 0x3B, 0x86, 0x8F, 0xFC, 0x40, 0x56, 0x49, 0x64, 0x76, 0x61, 0xBC,
	0x35, 0x6A, 0xB8, 0xC5, 0x32, 0x19, 0x00, 0x5E, 0x21, 0x1C, 0x34, 0xCB,
	0x74, 0x5B, 0x60, 0x85, 0x8C, 0x38, 0x52, 0x50, 0x4D, 0xAA, 0x25, 0xE4,
	0x1A, 0xE6, 0xE4, 0xDF, 0x0A, 0xD2, 0x8F, 0x2B, 0xD1, 0x35, 0xC7, 0x92,
	0x7D, 0x6F, 0x54, 0x61, 0x8E, 0x3F, 0xFB, 0xE2, 0xC8, 0x81, 0xD0, 0xAC,
	0x64, 0xE2, 0xA8, 0x30, 0xEA, 0x8E, 0xAD, 0xFE, 0xC0, 0x9E, 0x0B, 0xBF,
	0x34, 0xAC, 0x79, 0x96, 0x38, 0x31, 0x1E, 0xEA, 0xF2, 0x7E, 0xEE, 0x0A,
	0x10, 0x34, 0x7C, 0x1A, 0x30, 0x5F, 0xAF, 0x96, 0x2F, 0x7F, 0xB5, 0x1D,
	0xA7, 0x3D, 0x35, 0x7A, 0x30, 0x70, 0x40, 0xE7, 0xD6, 0x22, 0x1E, 0xD0,
	0x9A, 0x34, 0xC7, 0x6B, 0xE4, 0xF1, 0x78, 0xED, 0xD9, 0xCD, 0x18, 0xBF,
	0x2A, 0x1A, 0x98, 0xB7, 0x6C, 0x6E, 0x18, 0x40, 0xB5, 0xBE, 0xDF, 0xE4,
	0x78, 0x8E, 0x34, 0xB2, 0x7B, 0xE5, 0x88, 0xE6, 0xFD, 0x24, 0xBD, 0xBB,
	0x2E, 0x30, 0x72, 0x54, 0xC7, 0xF4, 0xA0, 0xF1, 0x25, 0xFF, 0xB1, 0x37,
	0x42, 0x07, 0x8C, 0xF2, 0xB9, 0xA1, 0xA4, 0xA7, 0x76, 0x39, 0xB8, 0x11,
	0x17, 0xF3, 0xA8, 0x2E, 0x78, 0x68, 0xF4, 0xBF, 0x98, 0x25, 0x59, 0x17,
	0x59, 0x9B, 0x0D, 0x0B, 0x9B, 0xE3, 0x0F, 0xFF, 0xDC, 0xC8, 0x47, 0x21,
	0xE1, 0x0B, 0x9A, 0x44, 0x79, 0xC7, 0x5F, 0x8E, 0x83, 0x1E, 0x04, 0xA1,
	0xB2, 0x9F, 0x9B, 0xFC, 0xB3, 0x4E, 0xD9, 0xF9, 0x8F, 0x03, 0xBC, 0x0A,
	0x04, 0x00, 0x5C, 0x59, 0xB7, 0x51, 0xAA, 0x75, 0xF8, 0x7A, 0x03, 0x07,
	0x81, 0x6D, 0x67, 0x3E, 0x28, 0x37, 0xE4, 0x74, 0x5B, 0x8C, 0x2A, 0x4B,
	0x6C, 0x10, 0x92, 0x75, 0xA5, 0x79, 0x4B, 0x6D, 0x30, 0xB7, 0x6E, 0xD6,
	0x9E, 0x16, 0xC2, 0x87, 0x69, 0x34, 0xFE, 0xD7, 0x2A, 0x4F, 0xD6, 0xC0,
	0xF3, 0xCD, 0x9C, 0x46, 0xED, 0xC0, 0xB2, 0x84, 0x8D, 0x7E, 0x93, 0xD2,
	0xE9, 0xBE, 0x59, 0x18, 0x92, 0xC1, 0x2C, 0xD6, 0x6C, 0x71, 0x50, 0xA1,
	0x98, 0xDA, 0xD1, 0xAC, 0xDB, 0x88, 0x40, 0x1F, 0x69, 0xDC, 0xDB, 0xB2,
	0xA0, 0x90, 0x01, 0x8E, 0x12, 0xD6, 0x40, 0x1A, 0x8E, 0xC5, 0x69, 0x9C,
	0x91, 0x67, 0xAC, 0xD8, 0x4C, 0x27, 0xCD, 0x08, 0xB8, 0x32, 0x97, 0xE1,
	0x13, 0x0C, 0xFF, 0xB1, 0x06, 0x65, 0x03, 0x98, 0x6F, 0x9E, 0xF7, 0xB8,
	0xA8, 0x75, 0xBA, 0x59, 0xFD, 0x23, 0x98, 0x94, 0x80, 0x9C, 0xA7, 0x46,
	0x32, 0x98, 0x28, 0x7A, 0x0A, 0x3A, 0xA6, 0x95, 0x16, 0x6A, 0x52, 0x8E,
	0x8F, 0x2C, 0xC9, 0x49, 0xB7, 0x59, 0x99, 0x2A, 0xE6, 0xCA, 0x82, 0x88,
	0x36, 0xD3, 0x2B, 0xA4, 0x73, 0xFA, 0x89, 0xBB,
};

/*
 * OpenSSL temporary DH callback which loads DH parameters from static memory.
 */
DH * ssl_tmp_dh_callback(SSL * s, int is_export, int keylength)
{
	DH * dh;
	int rv = 0;

	if (!(dh = DH_new()))
	{
		TFE_LOG_ERROR(NULL, "DH_new() failed\n");
		return NULL;
	}

	switch (keylength)
	{
		case 512:
			rv = DH_set0_pqg(dh, BN_bin2bn(dh512_p, sizeof(dh512_p), NULL), NULL,
				BN_bin2bn(dh_g, sizeof(dh_g), NULL));
			break;
		case 1024:
			rv = DH_set0_pqg(dh, BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL), NULL,
				BN_bin2bn(dh_g, sizeof(dh_g), NULL));
			break;
		case 2048:
			rv = DH_set0_pqg(dh, BN_bin2bn(dh2048_p, sizeof(dh2048_p), NULL), NULL,
				BN_bin2bn(dh_g, sizeof(dh_g), NULL));
			break;
		case 4096:
			rv = DH_set0_pqg(dh, BN_bin2bn(dh4096_p, sizeof(dh4096_p), NULL), NULL,
				BN_bin2bn(dh_g, sizeof(dh_g), NULL));
			break;
		default:
			TFE_LOG_ERROR(NULL, "Unhandled DH keylength %i%s", keylength, (is_export ? " (export)" : ""));
			DH_free(dh);
			return NULL;
	}

	if (!rv)
	{
		TFE_LOG_ERROR(NULL, "Failed to load DH p and g from memory\n");
		DH_free(dh);
		return NULL;
	}

	return (dh);
}

/*
 * Load DH parameters from a PEM file.
 * Not thread-safe.
 */
DH * ssl_dh_load(const char * filename)
{
	DH * dh;
	FILE * fh;

	if (ssl_init() == -1)
		return NULL;

	if (!(fh = fopen(filename, "r")))
	{
		return NULL;
	}
	dh = PEM_read_DHparams(fh, NULL, NULL, NULL);
	fclose(fh);
	return dh;
}
#endif /* !OPENSSL_NO_DH */

#ifndef OPENSSL_NO_EC
/*
 * Load an Elliptic Curve by name.  If curvename is NULL, a default curve is
 * loaded.
 */
EC_KEY * ssl_ec_by_name(const char * curvename)
{
	int nid;

	if (!curvename) curvename = "prime256v1";
	if ((nid = OBJ_sn2nid(curvename)) == NID_undef)
	{
		return NULL;
	}

	return EC_KEY_new_by_curve_name(nid);
}
#endif /* !OPENSSL_NO_EC */

/*
 * Add a X509v3 extension to a cert and handle errors.
 * Returns -1 on errors, 0 on success.
 */
int ssl_x509_v3ext_add(X509V3_CTX * ctx, X509 * crt, const char * k, const char * v)
{
	X509_EXTENSION * ext;

	if (!(ext = X509V3_EXT_conf(NULL, ctx, k, v)))
	{
		return -1;
	}
	if (X509_add_ext(crt, ext, -1) != 1)
	{
		X509_EXTENSION_free(ext);
		return -1;
	}
	X509_EXTENSION_free(ext);
	return 0;
}

/*
 * Copy a X509v3 extension from one cert to another.
 * If the extension is not present in the original cert,
 * the extension will not be added to the destination cert.
 * Returns 1 if ext was copied, 0 if not present in origcrt, -1 on error.
 */
int ssl_x509_v3ext_copy_by_nid(X509 * crt, X509 * origcrt, int nid)
{
	X509_EXTENSION * ext;
	int pos;

	pos = X509_get_ext_by_NID(origcrt, nid, -1);
	if (pos == -1)
		return 0;
	ext = X509_get_ext(origcrt, pos);
	if (!ext)
		return -1;
	if (X509_add_ext(crt, ext, -1) != 1)
		return -1;
	return 1;
}

/*
 * Best effort randomness generator.
 * Not for real life cryptography applications.
 * Returns 0 on success, -1 on failure.
 */
int ssl_rand(void * p, size_t sz)
{
	int rv;
	rv = RAND_bytes((unsigned char *) p, sz);

	if (rv == 1) return 0;
	return -1;
}

/*
 * Copy the serial number from src cert to dst cert
 * and modify it by a random offset.
 * If reading the serial fails for some reason, generate a new
 * random serial and store it in the dst cert.
 * Using the same serial is not a good idea since some SSL stacks
 * check for duplicate cert serials.
 * Returns 0 on success, -1 on error.
 */
int ssl_x509_serial_copyrand(X509 * dstcrt, X509 * srccrt)
{
	ASN1_INTEGER * srcptr, * dstptr;
	BIGNUM * bnserial;
	unsigned int rand;
	int rv;

#ifndef PURIFY
	rv = ssl_rand(&rand, sizeof(rand));
#else /* PURIFY */
    rand = 0xF001;
    rv = 0;
#endif /* PURIFY */
	dstptr = X509_get_serialNumber(dstcrt);
	srcptr = X509_get_serialNumber(srccrt);
	if ((rv == -1) || !dstptr || !srcptr)
		return -1;
	bnserial = ASN1_INTEGER_to_BN(srcptr, NULL);
	if (!bnserial)
	{
		/* random 32-bit serial */
		ASN1_INTEGER_set(dstptr, rand);
	}
	else
	{
		/* original serial plus random 32-bit offset */
		BN_add_word(bnserial, rand);
		BN_to_ASN1_INTEGER(bnserial, dstptr);
		BN_free(bnserial);
	}
	return 0;
}

static time_t ASN1_GetTimeT(ASN1_TIME* time){
    struct tm t;
    const char* str = (const char*) time->data;
    size_t i = 0;

    memset(&t, 0, sizeof(t));

    if (time->type == V_ASN1_UTCTIME) {/* two digit year */
        t.tm_year = (str[i++] - '0') * 10;
        t.tm_year += (str[i++] - '0');
        if (t.tm_year < 70)
            t.tm_year += 100;
    } else if (time->type == V_ASN1_GENERALIZEDTIME) {/* four digit year */
        t.tm_year = (str[i++] - '0') * 1000;
        t.tm_year+= (str[i++] - '0') * 100;
        t.tm_year+= (str[i++] - '0') * 10;
        t.tm_year+= (str[i++] - '0');
        t.tm_year -= 1900;
    }
    t.tm_mon  = (str[i++] - '0') * 10;
    t.tm_mon += (str[i++] - '0') - 1; // -1 since January is 0 not 1.
    t.tm_mday = (str[i++] - '0') * 10;
    t.tm_mday+= (str[i++] - '0');
    t.tm_hour = (str[i++] - '0') * 10;
    t.tm_hour+= (str[i++] - '0');
    t.tm_min  = (str[i++] - '0') * 10;
    t.tm_min += (str[i++] - '0');
    t.tm_sec  = (str[i++] - '0') * 10;
    t.tm_sec += (str[i++] - '0');

    /* Note: we did not adjust the time based on time zone information */
	setenv("TZ", "UTC", 1);
    return mktime(&t);
}

/*
 * Create a fake X509v3 cert, signed by the provided CA,
 * based on the original cert retrieved from the real server.
 * The returned cert is created using X509_new() and thus must
 * be freed by the caller using X509_free().
 * The optional argument extraname is added to subjectAltNames if provided.
 */
X509 * ssl_x509_forge(X509 * cacrt, EVP_PKEY * cakey, X509 * origcrt, EVP_PKEY * key,
	const char * extraname, const char * crlurl, int cert_expire_time)
{
	X509_NAME * subject, * issuer;
	GENERAL_NAMES * names;
	GENERAL_NAME * gn;
	X509 * crt;
	int rv;

	subject = X509_get_subject_name(origcrt);
	issuer = X509_get_subject_name(cacrt);
	if (!subject || !issuer)
		return NULL;

	crt = X509_new();
	if (!crt)
		return NULL;

	if (!X509_set_version(crt, 0x02) ||
		!X509_set_subject_name(crt, subject) ||
		!X509_set_issuer_name(crt, issuer) ||
		ssl_x509_serial_copyrand(crt, origcrt) == -1 ||
		!X509_set_pubkey(crt, key))
		goto errout;

	if(cert_expire_time == -1)
	{
		ASN1_TIME_set(X509_get_notBefore(crt), ASN1_GetTimeT(X509_get_notBefore(origcrt)));
		ASN1_TIME_set(X509_get_notAfter(crt), ASN1_GetTimeT(X509_get_notAfter(origcrt)));
	}
	else
	{
		if(!X509_gmtime_adj(X509_get_notBefore(crt), (long)(0 - cert_expire_time * 1800)) ||
		   !X509_gmtime_adj(X509_get_notAfter(crt), (long)(cert_expire_time * 1800)))
		{
			goto errout;
		}
	}

	/* add standard v3 extensions; cf. RFC 2459 */
	X509V3_CTX ctx;
	X509V3_set_ctx(&ctx, cacrt, crt, NULL, NULL, 0);
	if (ssl_x509_v3ext_add(&ctx, crt, "subjectKeyIdentifier", "hash") == -1 ||
		ssl_x509_v3ext_add(&ctx, crt, "authorityKeyIdentifier", "keyid,issuer:always") == -1)
		goto errout;

	rv = ssl_x509_v3ext_copy_by_nid(crt, origcrt,
		NID_basic_constraints);
	if (rv == 0)
		rv = ssl_x509_v3ext_add(&ctx, crt, "basicConstraints",
			"CA:FALSE");
	if (rv == -1)
		goto errout;

	//Not copy keyUsage, because RSA is not compatibel with "Key Agreement".
	rv = ssl_x509_v3ext_add(&ctx, crt, "keyUsage",
		"digitalSignature,"
		"keyEncipherment");
	if (rv == -1)
		goto errout;

	rv = ssl_x509_v3ext_copy_by_nid(crt, origcrt,
		NID_ext_key_usage);
	if (rv == 0)
		rv = ssl_x509_v3ext_add(&ctx, crt, "extendedKeyUsage",
			"serverAuth");
	if (rv == -1)
		goto errout;

	if (crlurl)
	{
		char * crlurlval;
		if (asprintf(&crlurlval, "URI:%s", crlurl) < 0)
			goto errout;
		if (ssl_x509_v3ext_add(&ctx, crt, "crlDistributionPoints",
			crlurlval) == -1)
		{
			free(crlurlval);
			goto errout;
		}
		free(crlurlval);
	}

	if (!extraname)
	{
		/* no extraname provided: copy original subjectAltName ext */
		if (ssl_x509_v3ext_copy_by_nid(crt, origcrt,
			NID_subject_alt_name) == -1)
			goto errout;
	}
	else
	{
		names = (GENERAL_NAMES *) X509_get_ext_d2i(origcrt, NID_subject_alt_name, 0, 0);
		if (!names)
		{
			/* no subjectAltName present: add new one */
			char * cfval;
			if (asprintf(&cfval, "DNS:%s", extraname) < 0)
				goto errout;
			if (ssl_x509_v3ext_add(&ctx, crt, "subjectAltName",
				cfval) == -1)
			{
				free(cfval);
				goto errout;
			}
			free(cfval);
		}
		else
		{
			/* add extraname to original subjectAltName
             * and add it to the new cert */
			gn = GENERAL_NAME_new();
			if (!gn)
				goto errout2;
			gn->type = GEN_DNS;
			gn->d.dNSName = ASN1_IA5STRING_new();
			if (!gn->d.dNSName)
				goto errout3;
			ASN1_STRING_set(gn->d.dNSName,
				(unsigned char *) extraname,
				strlen(extraname));
			sk_GENERAL_NAME_push(names, gn);
			X509_EXTENSION * ext = X509V3_EXT_i2d(
				NID_subject_alt_name, 0, names);
			if (!X509_add_ext(crt, ext, -1))
			{
				if (ext)
				{
					X509_EXTENSION_free(ext);
				}
				goto errout3;
			}
			X509_EXTENSION_free(ext);
			sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
		}
	}
#ifdef DEBUG_CERTIFICATE
	ssl_x509_v3ext_add(&ctx, crt, "nsComment", "Generated by " PKGLABEL);
#endif /* DEBUG_CERTIFICATE */

	const EVP_MD * md;
	switch (EVP_PKEY_type(EVP_PKEY_base_id(cakey)))
	{
#ifndef OPENSSL_NO_RSA
		case EVP_PKEY_RSA:
			switch (X509_get_signature_nid(origcrt))
			{
				case NID_md5WithRSAEncryption: md = EVP_md5();
					break;
				case NID_ripemd160WithRSA: md = EVP_ripemd160();
					break;
				case NID_sha1WithRSAEncryption: md = EVP_sha1();
					break;
				case NID_sha224WithRSAEncryption: md = EVP_sha224();
					break;
				case NID_sha256WithRSAEncryption: md = EVP_sha256();
					break;
				case NID_sha384WithRSAEncryption: md = EVP_sha384();
					break;
				case NID_sha512WithRSAEncryption: md = EVP_sha512();
					break;
#ifndef OPENSSL_NO_SHA0
                case NID_shaWithRSAEncryption: md = EVP_sha();
                    break;
#endif /* !OPENSSL_NO_SHA0 */
				default: md = EVP_sha256();
					break;
			}
			break;
#endif /* !OPENSSL_NO_RSA */
#ifndef OPENSSL_NO_DSA
		case EVP_PKEY_DSA:
			switch (X509_get_signature_nid(origcrt))
			{
				case NID_dsaWithSHA1:
				case NID_dsaWithSHA1_2: md = EVP_sha1();
					break;
				case NID_dsa_with_SHA224: md = EVP_sha224();
					break;
				case NID_dsa_with_SHA256: md = EVP_sha256();
					break;
#ifndef OPENSSL_NO_SHA0
                case NID_dsaWithSHA: md = EVP_sha();
                    break;
#endif /* !OPENSSL_NO_SHA0 */
				default: md = EVP_sha256();
					break;
			}
			break;
#endif /* !OPENSSL_NO_DSA */
#ifndef OPENSSL_NO_ECDSA
		case EVP_PKEY_EC:
			switch (X509_get_signature_nid(origcrt))
			{
				case NID_ecdsa_with_SHA1: md = EVP_sha1();
					break;
				case NID_ecdsa_with_SHA224: md = EVP_sha224();
					break;
				case NID_ecdsa_with_SHA256: md = EVP_sha256();
					break;
				case NID_ecdsa_with_SHA384: md = EVP_sha384();
					break;
				case NID_ecdsa_with_SHA512: md = EVP_sha512();
					break;
				default: md = EVP_sha256();
					break;
			}
			break;
#endif /* !OPENSSL_NO_ECDSA */
		default: goto errout;
	}
	if (!X509_sign(crt, cakey, md))
		goto errout;

	return crt;

errout3:
	GENERAL_NAME_free(gn);
errout2:
	sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
errout:
	X509_free(crt);
	return NULL;
}

/*
 * Load a X509 cert chain from a PEM file.
 * Returns the first cert in *crt and all subsequent certificates in
 * *chain.  If crt is NULL, the first cert is prepended to *chain
 * instead of returned separately.  If *chain is NULL, a new stack of X509*
 * is created in *chain, else the certs are pushed onto an existing stack.
 * Returns -1 on error.
 * Not thread-safe.
 *
 * By accessing (SSLCTX*)->extra_certs directly on OpenSSL before 1.0.2, we
 * depend on OpenSSL internals in this function.  OpenSSL 1.0.2 introduced
 * the SSL_get0_chain_certs() API for accessing the cert chain.
 */
int ssl_x509chain_load(X509 ** crt, STACK_OF(X509) ** chain, const char * filename)
{
	X509 * tmpcrt;
	SSL_CTX * tmpctx;
	SSL * tmpssl;
	STACK_OF(X509) * tmpchain;
	int rv;

	if (ssl_init() == -1)
		return -1;

	tmpctx = SSL_CTX_new(SSLv23_server_method());
	if (!tmpctx)
		goto leave1;

	rv = SSL_CTX_use_certificate_chain_file(tmpctx, filename);
	if (rv != 1)
		goto leave2;
	tmpssl = SSL_new(tmpctx);
	if (!tmpssl)
		goto leave2;

	tmpcrt = SSL_get_certificate(tmpssl);
	if (!tmpcrt)
		goto leave3;

	if (!*chain)
	{
		*chain = sk_X509_new_null();
		if (!*chain)
			goto leave3;
	}

#if (OPENSSL_VERSION_NUMBER < 0x1000200fL) || defined(LIBRESSL_VERSION_NUMBER)
	tmpchain = tmpctx->extra_certs;
#else /* OpenSSL >= 1.0.2 */
	rv = SSL_CTX_get0_chain_certs(tmpctx, &tmpchain);
	if (rv != 1)
		goto leave3;
#endif /* OpenSSL >= 1.0.2 */

	if (crt)
	{
		*crt = tmpcrt;
	}
	else
	{
		sk_X509_push(*chain, tmpcrt);
	}
	ssl_x509_refcount_inc(tmpcrt);

	for (int i = 0; i < sk_X509_num(tmpchain); i++)
	{
		tmpcrt = sk_X509_value(tmpchain, i);
		ssl_x509_refcount_inc(tmpcrt);
		sk_X509_push(*chain, tmpcrt);
	}
	SSL_free(tmpssl);
	SSL_CTX_free(tmpctx);
	return 0;

leave3:
	SSL_free(tmpssl);
leave2:
	SSL_CTX_free(tmpctx);
leave1:
	return -1;
}

/*
 * Use a X509 cert chain for an SSL context.
 * Copies the cert stack to the SSL_CTX internal data structures
 * and increases reference counts accordingly.
 */
void ssl_x509chain_use(SSL_CTX * sslctx, X509 * crt, STACK_OF(X509) * chain)
{
	SSL_CTX_use_certificate(sslctx, crt);

	for (int i = 0; i < sk_X509_num(chain); i++)
	{
		X509 * tmpcrt;

		tmpcrt = sk_X509_value(chain, i);
		ssl_x509_refcount_inc(tmpcrt);
		SSL_CTX_add_extra_chain_cert(sslctx, tmpcrt);
	}
}

/*
 * Load a X509 cert from a PEM file.
 * Returned X509 must be freed using X509_free() by the caller.
 * Not thread-safe.
 */
X509 * ssl_x509_load(const char * filename)
{
	X509 * crt = NULL;
	SSL_CTX * tmpctx;
	SSL * tmpssl;
	int rv;

	if (ssl_init() == -1)
		return NULL;

	tmpctx = SSL_CTX_new(SSLv23_server_method());
	if (!tmpctx)
		goto leave1;
	rv = SSL_CTX_use_certificate_file(tmpctx, filename, SSL_FILETYPE_PEM);
	if (rv != 1)
		goto leave2;
	tmpssl = SSL_new(tmpctx);
	if (!tmpssl)
		goto leave2;
	crt = SSL_get_certificate(tmpssl);
	if (crt)
		ssl_x509_refcount_inc(crt);
	SSL_free(tmpssl);
leave2:
	SSL_CTX_free(tmpctx);
leave1:
	return crt;
}

/*
 * Load a private key from a PEM file.
 * Returned EVP_PKEY must be freed using EVP_PKEY_free() by the caller.
 * Not thread-safe.
 */
EVP_PKEY * ssl_key_load(const char * filename)
{
	EVP_PKEY * key = NULL;
	SSL_CTX * tmpctx;
	SSL * tmpssl;
	int rv;

	if (ssl_init() == -1)
		return NULL;

	tmpctx = SSL_CTX_new(SSLv23_server_method());
	if (!tmpctx)
		goto leave1;
	rv = SSL_CTX_use_PrivateKey_file(tmpctx, filename, SSL_FILETYPE_PEM);
	if (rv != 1)
		goto leave2;
	tmpssl = SSL_new(tmpctx);
	if (!tmpssl)
		goto leave2;
	key = SSL_get_privatekey(tmpssl);
	if (key)
		ssl_key_refcount_inc(key);
	SSL_free(tmpssl);
leave2:
	SSL_CTX_free(tmpctx);
leave1:
	return key;
}

/*
 * Generate a new RSA key.
 * Returned EVP_PKEY must be freed using EVP_PKEY_free() by the caller.
 */
EVP_PKEY * ssl_key_genrsa(const int keysize)
{
	EVP_PKEY * pkey;
	RSA * rsa;

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	BIGNUM * bn;
	int rv;
	rsa = RSA_new();
	bn = BN_new();
	BN_dec2bn(&bn, "3");
	rv = RSA_generate_key_ex(rsa, keysize, bn, NULL);
	BN_free(bn);
	if (rv != 1)
	{
		RSA_free(rsa);
		return NULL;
	}
#else /* OPENSSL_VERSION_NUMBER < 0x10100000L */
    rsa = RSA_generate_key(keysize, 3, NULL, NULL);
    if (!rsa)
        return NULL;
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
	pkey = EVP_PKEY_new();
	EVP_PKEY_assign_RSA(pkey, rsa); /* does not increment refcount */
	return pkey;
}

/*
 * Returns the subjectKeyIdentifier compatible key id of the public key.
 * keyid will receive a binary SHA-1 hash of SSL_KEY_IDSZ bytes.
 * Returns 0 on success, -1 on failure.
 */
int ssl_key_identifier_sha1(EVP_PKEY * key, unsigned char * keyid)
{
	X509_PUBKEY * pubkey = NULL;
	const unsigned char * pk;
	int length;

	/* X509_PUBKEY_set() will attempt to free pubkey if != NULL */
	if (X509_PUBKEY_set(&pubkey, key) != 1 || !pubkey)
		return -1;
	if (!X509_PUBKEY_get0_param(NULL, &pk, &length, NULL, pubkey))
		goto errout;
	if (!EVP_Digest(pk, length, keyid, NULL, EVP_sha1(), NULL))
		goto errout;
	X509_PUBKEY_free(pubkey);
	return 0;

errout:
	X509_PUBKEY_free(pubkey);
	return -1;
}

char* ssl_X509_print_name(X509_NAME* name)
{
	//ref: https://kahdev.wordpress.com/2008/11/23/a-certificates-name-issuer-and-its-keyusage/
	// Set up a BIO to put the name line into.
	BIO *name_Bio = BIO_new(BIO_s_mem());

	// Now, put the name line into the BIO.
	X509_NAME_print_ex(name_Bio, name, 0, XN_FLAG_ONELINE);

	// Obtain a reference to the data and copy out
	// just the length of the data.
	char *data_start = NULL;
	char *name_string = NULL;
	long nameLength = BIO_get_mem_data(name_Bio, &data_start);

	name_string = ALLOC(char, nameLength + 1);
	memset(name_string, 0, nameLength + 1);
	memcpy(name_string, data_start, nameLength);
	BIO_vfree(name_Bio);
	return name_string;
}
/*
 * Returns the result of ssl_key_identifier_sha1() as hex characters with or
 * without colons in a newly allocated string.
 */
char * ssl_key_identifier(EVP_PKEY * key, int colons)
{
	unsigned char id[SSL_KEY_IDSZ];

	if (ssl_key_identifier_sha1(key, id) == -1)
		return NULL;

	return ssl_sha1_to_str(id, colons);
}

/*
 * Returns the one-line representation of the subject DN in a newly allocated
 * string which must be freed by the caller.
 */
char * ssl_x509_subject(const X509 * crt)
{
	return ssl_X509_print_name(X509_get_subject_name(crt));
}

/*
 * Returns the one-line representation of the issuer in a newly allocated
 * string which must be freed by the caller.
 */
char * ssl_x509_issuer(const X509 * crt)
{
	return ssl_X509_print_name(X509_get_issuer_name(crt));
}


/*
 * Parse the common name from the subject distinguished name.
 * Returns string allocated using malloc(), caller must free().
 * Returns NULL on errors.
 */
char *
ssl_x509_subject_cn(X509 * crt, size_t * psz)
{
	X509_NAME * ptr;
	char * cn;
	size_t sz;

	ptr = X509_get_subject_name(crt); /* does not inc refcounts */
	if (!ptr)
		return NULL;
	sz = (size_t) X509_NAME_get_text_by_NID(ptr, NID_commonName, NULL, 0) + 1;

	if ((sz == 0) || !(cn = (char *) malloc(sz)))
		return NULL;
	if (X509_NAME_get_text_by_NID(ptr, NID_commonName, cn, sz) == -1)
	{
		free(cn);
		return NULL;
	}
	*psz = sz;
	return cn;
}

/*
 * Write the SHA1 fingerprint of cert to fpr as SSL_X509_FPRSZ (20)
 * bytes long binary buffer.
 * Returns -1 on error, 0 on success.
 */
int
ssl_x509_fingerprint_sha1(X509 * crt, unsigned char * fpr)
{
	unsigned int sz = SSL_X509_FPRSZ;

	return X509_digest(crt, EVP_sha1(), fpr, &sz) ? 0 : -1;
}

/*
 * Returns the result of ssl_x509_fingerprint_sha1() as hex characters with or
 * without colons in a newly allocated string.
 */
char *
ssl_x509_fingerprint(X509 * crt, int colons)
{
	unsigned char fpr[SSL_X509_FPRSZ];

	if (ssl_x509_fingerprint_sha1(crt, fpr) == -1)
		return NULL;

	return ssl_sha1_to_str(fpr, colons);
}

#ifndef OPENSSL_NO_DH
/*
 * Increment the reference count of DH parameters in a thread-safe
 * manner.
 */
void
ssl_dh_refcount_inc(DH * dh)
{
#if defined(OPENSSL_THREADS) && OPENSSL_VERSION_NUMBER < 0x10100000L
	CRYPTO_add(&dh->references, 1, CRYPTO_LOCK_DH);
#else /* !OPENSSL_THREADS */
	DH_up_ref(dh);
#endif /* !OPENSSL_THREADS */
}
#endif /* !OPENSSL_NO_DH */

/*
 * Increment the reference count of an X509 cert in a thread-safe
 * manner.
 */
void
ssl_key_refcount_inc(EVP_PKEY * key)
{
#if defined(OPENSSL_THREADS) && OPENSSL_VERSION_NUMBER < 0x10100000L
	CRYPTO_add(&key->references, 1, CRYPTO_LOCK_EVP_PKEY);
#else /* !OPENSSL_THREADS */
	EVP_PKEY_up_ref(key);
#endif /* !OPENSSL_THREADS */
}

/*
 * Increment the reference count of an X509 cert in a thread-safe
 * manner.  This differs from X509_dup() in that it does not create new,
 * full copy of the cert, but only increases the reference count.
 */
void
ssl_x509_refcount_inc(X509 * crt)
{
#if defined(OPENSSL_THREADS) && OPENSSL_VERSION_NUMBER < 0x10100000L
	CRYPTO_add(&crt->references, 1, CRYPTO_LOCK_X509);
#else /* !OPENSSL_THREADS */
	X509_up_ref(crt);
#endif /* !OPENSSL_THREADS */
}

/*
 * Match a URL/URI hostname against a single cert DNS name
 * using RFC 6125 rules (6.4.3 Checking of Wildcard Certificates):
 *
 *   1.  The client SHOULD NOT attempt to match a presented identifier in
 *       which the wildcard character comprises a label other than the
 *       left-most label (e.g., do not match bar.*.example.net).
 *
 *   2.  If the wildcard character is the only character of the left-most
 *       label in the presented identifier, the client SHOULD NOT compare
 *       against anything but the left-most label of the reference
 *       identifier (e.g., *.example.com would match foo.example.com but
 *       not bar.foo.example.com or example.com).
 *
 *   3.  The client MAY match a presented identifier in which the wildcard
 *       character is not the only character of the label (e.g.,
 *       baz*.example.net and *baz.example.net and b*z.example.net would
 *       be taken to match baz1.example.net and foobaz.example.net and
 *       buzz.example.net, respectively).  However, the client SHOULD NOT
 *       attempt to match a presented identifier where the wildcard
 *       character is embedded within an A-label or U-label [IDNA-DEFS] of
 *       an internationalized domain name [IDNA-PROTO].
 *
 * The optional partial matching in rule 3 is not implemented.
 * Returns 1 on match, 0 on no match.
 */
int
ssl_dnsname_match(
	const char * certname, size_t certnamesz,
	const char * hostname, size_t hostnamesz)
{
	if (hostnamesz < certnamesz)
		return 0;
	if ((hostnamesz == certnamesz) &&
		!memcmp(certname, hostname, certnamesz))
		return 1;
	if (!memcmp(certname, "xn--", 4))
		return 0;
	if ((certnamesz == 1) && (certname[0] == '*') &&
		!memchr(hostname, '.', hostnamesz))
		return 1;
	if ((certnamesz > 2) && (certname[0] == '*') && (certname[1] == '.') &&
		!memcmp(&certname[1],
			&hostname[hostnamesz - (certnamesz - 1)],
			certnamesz - 1) &&
		(memchr(hostname, '.', hostnamesz) ==
			&hostname[hostnamesz - (certnamesz - 1)]))
		return 1;
	return 0;
}

/*
 * Transform a NULL-terminated hostname into a matching wildcard hostname,
 * e.g. "test.example.org" -> "*.example.org".
 * Returns string which must be free()'d by the caller, or NULL on error.
 */
char *
ssl_wildcardify(const char * hostname)
{
	char * dot, * wildcarded;
	size_t dotsz;

	if (!(dot = (char *) strchr(hostname, '.')))
		return strdup("*");
	dotsz = strlen(dot);
	if (!(wildcarded = (char *) malloc(dotsz + 2)))
		return NULL;
	wildcarded[0] = '*';
	for (size_t i = 0; i < dotsz; i++)
	{
		wildcarded[i + 1] = dot[i];
	}
	wildcarded[dotsz + 1] = '\0';
	return wildcarded;
}

/*
 * Match DNS name against cert subject CN and subjectAltNames DNS names.
 * Returns 1 if any name matches, 0 if none matches.
 */
int
ssl_x509_names_match(X509 * crt, const char * dnsname)
{
	GENERAL_NAMES * altnames;
	char * cn;
	size_t cnsz, dnsnamesz;

	dnsnamesz = strlen(dnsname);

	cn = ssl_x509_subject_cn(crt, &cnsz);
	if (cn && ssl_dnsname_match(cn, cnsz, dnsname, dnsnamesz))
	{
		free(cn);
		return 1;
	}
	if (cn)
	{
		free(cn);
	}

	altnames = (GENERAL_NAMES *) X509_get_ext_d2i(crt, NID_subject_alt_name, 0, 0);
	if (!altnames)
		return 0;
	for (int i = 0; i < sk_GENERAL_NAME_num(altnames); i++)
	{
		GENERAL_NAME * gn = sk_GENERAL_NAME_value(altnames, i);
		if (gn->type == GEN_DNS)
		{
			unsigned char * altname;
			int altnamesz;
			ASN1_STRING_to_UTF8(&altname, gn->d.dNSName);
			altnamesz = ASN1_STRING_length(gn->d.dNSName);
			if (altnamesz < 0)
				break;
			if (ssl_dnsname_match((char *) altname,
				(size_t) altnamesz,
				dnsname, dnsnamesz))
			{
				OPENSSL_free((char *) altname);
				GENERAL_NAMES_free(altnames);
				return 1;
			}
			OPENSSL_free((char *) altname);
		}
	}
	GENERAL_NAMES_free(altnames);
	return 0;
}

/*
 * Returns a NULL terminated array of pointers to all common names found
 * in the Subject DN CN and subjectAltNames extension (DNSName only).
 * Caller must free returned buffer and all pointers within.
 * Embedded NULL characters in hostnames are replaced with '!'.
 */
char ** ssl_x509_names(X509 * crt)
{
	GENERAL_NAMES * altnames;
	char * cn;
	size_t cnsz;
	char ** res, ** p;
	size_t count;

	cn = ssl_x509_subject_cn(crt, &cnsz);
	altnames = (GENERAL_NAMES *) X509_get_ext_d2i(crt, NID_subject_alt_name, NULL, NULL);

	count = (size_t) (altnames ? sk_GENERAL_NAME_num(altnames) : 0) + (cn ? 2 : 1);
	res = (char **) malloc(count * sizeof(char *));

	if (!res)
		return NULL;
	p = res;
	if (cn)
		*(p++) = cn;
	if (!altnames)
	{
		*p = NULL;
		return res;
	}
	for (int i = 0; i < sk_GENERAL_NAME_num(altnames); i++)
	{
		GENERAL_NAME * gn = sk_GENERAL_NAME_value(altnames, i);
		if (gn->type == GEN_DNS)
		{
			unsigned char * altname;
			int altnamesz;

			ASN1_STRING_to_UTF8(&altname, gn->d.dNSName);
			if (!altname)
				break;
			altnamesz = ASN1_STRING_length(gn->d.dNSName);
			if (altnamesz < 0)
			{
				OPENSSL_free((char *) altname);
				break;
			}
			*p = (char *) malloc(altnamesz + 1);
			if (!*p)
			{
				OPENSSL_free((char *) altname);
				GENERAL_NAMES_free(altnames);
				for (p = res; *p; p++)
					free(*p);
				free(res);
				return NULL;
			}
			for (int j = 0; j < altnamesz; j++)
			{
				(*p)[j] = altname[j] ? altname[j] : '!';
			}
			(*p)[altnamesz] = '\0';
			OPENSSL_free((char *) altname);
			p++;
		}
	}
	*p = NULL;
	GENERAL_NAMES_free(altnames);
	return res;
}

/*
 * Returns a printable representation of a cert's common names found
 * in the Subject DN CN and subjectAltNames extension, separated by slashes.
 * Caller must free returned buffer.
 * Embedded NULL characters in hostnames are replaced with '!'.
 * If no CN and no subjectAltNames are found, returns "-".
 */
char * ssl_x509_names_to_str(X509 * crt)
{
	char ** names;
	size_t sz;
	char * buf = NULL, * next;

	names = ssl_x509_names(crt);
	if (!names)
		return strdup("-");

	sz = 0;
	for (char ** p = names; *p; p++)
	{
		sz += strlen(*p) + 1;
	}
	if (!sz)
	{
		goto out1;
	}

	if (!(buf = (char *) malloc(sz)))
		goto out2;
	next = buf;
	for (char ** p = names; *p; p++)
	{
		char * src = *p;
		while (*src)
		{
			*next++ = *src++;
		}
		*next++ = '/';
	}
	*--next = '\0';
out2:
	for (char ** p = names; *p; p++)
		free(*p);
out1:
	free(names);
	return buf;
}

/*
 * Returns a zero-terminated buffer containing the ASN1 IA5 string.
 * Returned buffer must be free()'d by caller.
 */
static char * ssl_ia5string_strdup(ASN1_IA5STRING * ia5)
{
	char * str;

	if (!ia5 || !ia5->length)
		return NULL;

	str = (char *) malloc(ia5->length + 1);
	if (!str)
		return NULL;
	memcpy(str, ia5->data, ia5->length);
	str[ia5->length] = 0;
	return str;
}

/*
 * Returns a NULL terminated array of pointers to copies of Authority
 * Information Access (AIA) URLs of a given type contained in the cert.
 * Caller must free returned buffer and all pointers within.
 */
char ** ssl_x509_aias(X509 * crt, const int type)
{
	AUTHORITY_INFO_ACCESS * aias;
	char ** res;
	int count, i, j;

	aias = (AUTHORITY_INFO_ACCESS *) X509_get_ext_d2i(crt, NID_info_access, NULL, NULL);
	if (!aias || !(count = sk_ACCESS_DESCRIPTION_num(aias)))
		return NULL;

	res = (char **) malloc((count + 1) * sizeof(char *));
	if (!res)
	{
		sk_ACCESS_DESCRIPTION_pop_free(aias, ACCESS_DESCRIPTION_free);
		return NULL;
	}

	for (i = 0, j = 0; i < count; i++)
	{
		ACCESS_DESCRIPTION * aia;

		aia = sk_ACCESS_DESCRIPTION_value(aias, i);
		if (aia &&
			OBJ_obj2nid(aia->method) == type &&
			aia->location->type == GEN_URI)
		{
			res[j] = ssl_ia5string_strdup(aia->location->d.ia5);
			if (res[j])
				j++;
		}
	}
	res[j] = NULL;
	sk_ACCESS_DESCRIPTION_pop_free(aias, ACCESS_DESCRIPTION_free);
	return res;
}

/*
 * Returns a NULL terminated array of pointers to copies of Authority
 * Information Access (AIA) URLs of type OCSP contained in the cert.
 * Caller must free returned buffer and all pointers within.
 */
char ** ssl_x509_ocsps(X509 * crt)
{
	return ssl_x509_aias(crt, NID_ad_OCSP);
}

/*
 * Check whether the cert is valid based on current time.
 * Return 1 if valid, 0 otherwise.
 */
int ssl_x509_is_valid(X509 * crt)
{
	if (X509_cmp_current_time(X509_get_notAfter(crt)) <= 0)
		return 0;
	if (X509_cmp_current_time(X509_get_notBefore(crt)) > 0)
		return 0;
	return 1;
}

/*
 * Print X509 cert data to a newly allocated string.
 * Caller must free returned string.
 * Returns NULL on errors.
 */
char *
ssl_x509_to_str(X509 * crt)
{
	BIO * bio;
	char * p, * ret;
	size_t sz;

	bio = BIO_new(BIO_s_mem());
	if (!bio)
		return NULL;

	X509_print(bio, crt);
	sz = (size_t) BIO_get_mem_data(bio, &p);

	if (!(ret = (char *) malloc(sz + 1)))
	{
		BIO_free(bio);
		return NULL;
	}
	memcpy(ret, p, sz);
	ret[sz] = '\0';
	BIO_free(bio);
	return ret;
}

/*
 * Convert X509 cert to PEM format in a newly allocated string.
 * Caller must free returned string.
 * Returns NULL on errors.
 */
char * ssl_x509_to_pem(X509 * crt)
{
	BIO * bio;
	char * p, * ret;
	size_t sz;

	bio = BIO_new(BIO_s_mem());
	if (!bio)
		return NULL;

	PEM_write_bio_X509(bio, crt);
	sz = (size_t) BIO_get_mem_data(bio, &p);

	if (!(ret = (char *) malloc(sz + 1)))
	{
		BIO_free(bio);
		return NULL;
	}

	memcpy(ret, p, sz);
	ret[sz] = '\0';
	BIO_free(bio);
	return ret;
}
char* ssl_x509_get_extension(X509 *cert, int ext_NID)
{
	int loc=0;
	X509_EXTENSION *ex=NULL;
	ASN1_OBJECT *obj=NULL;
	BUF_MEM *bptr = NULL;
	char *buf = NULL;
	BIO *bio=NULL;
	char buff[1024];
	int ret=0;

	loc=X509_get_ext_by_NID(cert, ext_NID, -1);
	if(loc<0)
	{
		goto leave;
	}
	ex = X509_get_ext(cert, loc);
	if(ex==NULL)
	{
		goto leave;
	}
	obj = X509_EXTENSION_get_object(ex);
	OBJ_obj2txt(buff, 1024, obj, 0);

	bio = BIO_new(BIO_s_mem());
	if (!X509V3_EXT_print(bio, ex, 0, 0))
	{
		fprintf(stderr, "Error in reading extensions");
		goto leave;
	}
	ret=BIO_flush(bio);
	if(ret!=1)
	{
		goto leave;
	}
	BIO_get_mem_ptr(bio, &bptr);

	//bptr->data is not NULL terminated - add null character
	buf = (char *) malloc((bptr->length + 1) * sizeof(char));
	memcpy(buf, bptr->data, bptr->length);
	buf[bptr->length] = '\0';
leave:
	BIO_free_all(bio);
	return buf;
}


/*
 * Print SSL_SESSION data to a newly allocated string.
 * Caller must free returned string.
 * Returns NULL on errors.
 */
char * ssl_session_to_str(SSL_SESSION * sess)
{
	BIO * bio;
	char * p, * ret;
	size_t sz;

	bio = BIO_new(BIO_s_mem());
	if (!bio)
		return NULL;

	SSL_SESSION_print(bio, sess);
	sz = (size_t) BIO_get_mem_data(bio, &p); /* sets p to internal buffer */

	if (!(ret = (char *) malloc(sz + 1)))
	{
		BIO_free(bio);
		return NULL;
	}
	memcpy(ret, p, sz);
	ret[sz] = '\0';
	BIO_free(bio);
	return ret;
}

/*
 * Returns non-zero if the session timeout has not expired yet,
 * zero if the session has expired or an error occured.
 */
int ssl_session_is_valid(SSL_SESSION * sess)
{
	time_t curtimet;
	long curtime, timeout;

	curtimet = time(NULL);
	if (curtimet == (time_t) -1)
		return 0;
	curtime = curtimet;
	if ((curtime < 0) || ((time_t) curtime != curtimet))
		return 0;
	timeout = SSL_SESSION_get_timeout(sess);
	if (curtime < timeout)
		return 0;
	return (SSL_SESSION_get_time(sess) > curtime - timeout);
}

/*
 * Returns 1 if buf contains a DER encoded OCSP request which can be parsed.
 * Returns 0 otherwise.
 */
int ssl_is_ocspreq(const unsigned char * buf, size_t sz)
{
	OCSP_REQUEST * req;
	const unsigned char * p;

	p = (const unsigned char *) buf;
	req = d2i_OCSP_REQUEST(NULL, &p, sz); /* increments p */
	if (!req)
		return 0;
	OCSP_REQUEST_free(req);
	return 1;
}

void ssl_chello_free(struct ssl_chello* chello)
{
	if(chello == NULL)
	{
		return;
	}
	FREE(&(chello->sni));
	chello->sni = NULL;
	FREE(&(chello->alpn));
	chello->alpn = NULL;
	FREE(&(chello->sign_algos));
	chello->sign_algos = NULL;
	FREE(&(chello->supported_groups));
	chello->supported_groups = NULL;
	FREE(&(chello->cipher_suites));
	chello->cipher_suites = NULL;
	FREE(&chello);
}
static int cipher_is_grease(uint16_t cipher)
{
	uint16_t a=cipher>>8;
	uint16_t b=cipher&0x00ff;
	//https://tools.ietf.org/html/draft-davidben-tls-grease-01#section-5
	if(a==b && (a&0x0f)==0x0a)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

static int  parse_server_name_extension(const unsigned char *buff, uint16_t buff_len, struct ssl_chello* chello)
{
    size_t pos = 2; /* skip server name list length */
    uint16_t len;
	char *sni = NULL;
    while (pos + 3 < buff_len)
	{
        len = (buff[pos + 1] << 8) | buff[pos + 2];
        if (pos + 3 + len > buff_len)
		{
			chello->sni = sni;
			return CHELLO_PARSE_INVALID_FORMAT;
		}
        switch (buff[pos])
		{
            case 0x00: /* host_name */
				sni = ALLOC(char, len + 1);
                strncpy(sni, (const char*)buff + pos + 3, len);
                sni[len] = '\0';
				chello->sni = sni;
				return CHELLO_PARSE_SUCCESS;
        }
        pos += 3 + len;
    }
	chello->sni = sni;
    if (pos != buff_len)
	{
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	return CHELLO_PARSE_SUCCESS;
}

static int parse_alpn_extension(const unsigned char* buff, uint16_t buff_len, struct ssl_chello* chello)
{
	int pos = 0;
	uint16_t len = (buff[pos] << 8) | buff[pos + 1];
	char *alpn = NULL;
	if(2 + len != buff_len)
	{
		chello->alpn = alpn;
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	alpn = ALLOC(char, len + 1);
	strncpy((char*)alpn, (const char*)buff + 2, len);
	alpn[len] = '\0';
	chello->alpn = alpn;
	return CHELLO_PARSE_SUCCESS;
}

static int parse_supported_versions_extension(const unsigned char* buff, uint16_t buff_len, struct ssl_chello* chello)
{
	if(buff_len < 1)
	{
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	uint8_t len = buff[0];
	if(len + 1 > buff_len || len % 2 != 0)
	{
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	uint16_t max_version = 0x0000, min_version = 0xffff;
	for(int i = 1; i < len; i+=2)
	{
		uint16_t version = (buff[i] << 8) | buff[i + 1];
		//unknown version
		if(version < 0x0300 || version > 0x0304)
		{
			continue;
		}
		if(version > max_version)
		{
			max_version = version;
		}
		if(version < min_version)
		{
			min_version = version;
		}
	}
	if(max_version != 0x0000)
	{
		chello->max_version.ossl_format = max_version;
		chello->max_version.major = (max_version >> 8);
		chello->max_version.minor = max_version & 0x00ff;
		chello->min_version.ossl_format = min_version;
		chello->min_version.major = (min_version >> 8);
		chello->min_version.minor = min_version & 0x00ff;
	}
	return CHELLO_PARSE_SUCCESS;
}

static int parse_sign_algos_extension(const unsigned char* buff, uint16_t buff_len, struct ssl_chello* chello)
{
	if(buff_len < 2)
	{
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	uint16_t len = (buff[0] << 8) | buff[1];
	if(len + 2 > buff_len)
	{
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	char *sign_algos = ALLOC(char, len);
	memcpy(sign_algos, (void*)(buff + 2), len);
	chello->sign_algos = sign_algos;
	chello->sign_algos_len = len;
	return CHELLO_PARSE_SUCCESS;
}

static int parse_supported_groups_extension(const unsigned char* buff, uint16_t buff_len, struct ssl_chello* chello)
{
	if(buff_len < 2)
	{
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	uint16_t len = (buff[0] << 8) | buff[1];
	if(len + 2 > buff_len)
	{
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	chello->supported_groups = ALLOC(char, len);
	uint16_t* known_groups = (uint16_t*) chello->supported_groups;
	uint16_t* raw_groups= (uint16_t*) (buff + 2);
	size_t i=0, j=0;
	for(i=0; i<len/2; i++)
	{
		if(!cipher_is_grease(raw_groups[i]))
		{
			known_groups[j++]=raw_groups[i];
		}
	}
	chello->supported_groups_len = j*2;
	return CHELLO_PARSE_SUCCESS;
}

static int parse_extensions(const unsigned char *buff, uint16_t buff_len, struct ssl_chello *chello) {
    uint16_t pos = 0;
    /* Parse each 4 bytes for the extension header */
    while (pos + 4 <= buff_len)
	{
        uint16_t len = (buff[pos + 2] << 8) | buff[pos + 3];
		if (pos + 4 + len > buff_len)
		{
			return CHELLO_PARSE_INVALID_FORMAT;
		}
		uint16_t extension_type = (buff[pos] << 8) | buff[pos + 1];
		int result = CHELLO_PARSE_SUCCESS;
		switch(extension_type){
			//server name extension
			case 0x0000:
				result = parse_server_name_extension(buff + pos + 4, len, chello);
				break;
			//alpn extension
			case 0x0010:
				result = parse_alpn_extension(buff + pos + 4, len, chello);
				break;
			//supported versions extension
			case 0x002b:
				result = parse_supported_versions_extension(buff + pos + 4, len, chello);
				break;
			//signature algorithms extension
			case 0x000d:
				result = parse_sign_algos_extension(buff + pos + 4, len, chello);
				break;
			//supported groups extensions
			case 0x000a:
				result = parse_supported_groups_extension(buff + pos + 4, len, chello);
				break;
			default:
				break;
		}
		if(result != CHELLO_PARSE_SUCCESS)
		{
			return result;
		}
        pos += (4 + len);
    }
    /* Check we ended where we expected to */
    if (pos != buff_len)
	{
		return CHELLO_PARSE_INVALID_FORMAT;
	}
	return CHELLO_PARSE_SUCCESS;
}


struct cipher_suite
{
	uint16_t value;
	const char* name;
};

static struct cipher_suite cipher_suite_list[] =
{
	{0xC030, "ECDHE-RSA-AES256-GCM-SHA384"},
	{0xC02C, "ECDHE-ECDSA-AES256-GCM-SHA384"},
	{0xC028, "ECDHE-RSA-AES256-SHA384"},
	{0xC024, "ECDHE-ECDSA-AES256-SHA384"},
	{0xC014, "ECDHE-RSA-AES256-SHA"},
	{0xC00A, "ECDHE-ECDSA-AES256-SHA"},
	{0x00A5, "DH-DSS-AES256-GCM-SHA384"},
	{0x00A3, "DHE-DSS-AES256-GCM-SHA384"},
	{0x00A1, "DH-RSA-AES256-GCM-SHA384"},
	{0x009F, "DHE-RSA-AES256-GCM-SHA384"},
	{0x006B, "DHE-RSA-AES256-SHA256"},
	{0x006A, "DHE-DSS-AES256-SHA256"},
	{0x0069, "DH-RSA-AES256-SHA256"},
	{0x0068, "DH-DSS-AES256-SHA256"},
	{0x0039, "DHE-RSA-AES256-SHA"},
	{0x0038, "DHE-DSS-AES256-SHA"},
	{0x0037, "DH-RSA-AES256-SHA"},
	{0x0036, "DH-DSS-AES256-SHA"},
	{0x0088, "DHE-RSA-CAMELLIA256-SHA"},
	{0x0087, "DHE-DSS-CAMELLIA256-SHA"},
	{0x0086, "DH-RSA-CAMELLIA256-SHA"},
	{0x0085, "DH-DSS-CAMELLIA256-SHA"},
	{0xC019, "AECDH-AES256-SHA"},
	{0x00A7, "ADH-AES256-GCM-SHA384"},
	{0x006D, "ADH-AES256-SHA256"},
	{0x003A, "ADH-AES256-SHA"},
	{0x0089, "ADH-CAMELLIA256-SHA"},
	{0xC032, "ECDH-RSA-AES256-GCM-SHA384"},
	{0xC02E, "ECDH-ECDSA-AES256-GCM-SHA384"},
	{0xC02A, "ECDH-RSA-AES256-SHA384"},
	{0xC026, "ECDH-ECDSA-AES256-SHA384"},
	{0xC00F, "ECDH-RSA-AES256-SHA"},
	{0xC005, "ECDH-ECDSA-AES256-SHA"},
	{0x009D, "AES256-GCM-SHA384"},
	{0x003D, "AES256-SHA256"},
	{0x0035, "AES256-SHA"},
	{0x0084, "CAMELLIA256-SHA"},
	{0x008D, "PSK-AES256-CBC-SHA"},
	{0xC02F, "ECDHE-RSA-AES128-GCM-SHA256"},
	{0xC02B, "ECDHE-ECDSA-AES128-GCM-SHA256"},
	{0xC027, "ECDHE-RSA-AES128-SHA256"},
	{0xC023, "ECDHE-ECDSA-AES128-SHA256"},
	{0xC013, "ECDHE-RSA-AES128-SHA"},
	{0xC009, "ECDHE-ECDSA-AES128-SHA"},
	{0x00A4, "DH-DSS-AES128-GCM-SHA256"},
	{0x00A2, "DHE-DSS-AES128-GCM-SHA256"},
	{0x00A0, "DH-RSA-AES128-GCM-SHA256"},
	{0x009E, "DHE-RSA-AES128-GCM-SHA256"},
	{0x0067, "DHE-RSA-AES128-SHA256"},
	{0x0040, "DHE-DSS-AES128-SHA256"},
	{0x003F, "DH-RSA-AES128-SHA256"},
	{0x003E, "DH-DSS-AES128-SHA256"},
	{0x0033, "DHE-RSA-AES128-SHA"},
	{0x0032, "DHE-DSS-AES128-SHA"},
	{0x0031, "DH-RSA-AES128-SHA"},
	{0x0030, "DH-DSS-AES128-SHA"},
	{0x009A, "DHE-RSA-SEED-SHA"},
	{0x0099, "DHE-DSS-SEED-SHA"},
	{0x0098, "DH-RSA-SEED-SHA"},
	{0x0097, "DH-DSS-SEED-SHA"},
	{0x0045, "DHE-RSA-CAMELLIA128-SHA"},
	{0x0044, "DHE-DSS-CAMELLIA128-SHA"},
	{0x0043, "DH-RSA-CAMELLIA128-SHA"},
	{0x0042, "DH-DSS-CAMELLIA128-SHA"},
	{0xC018, "AECDH-AES128-SHA"},
	{0x00A6, "ADH-AES128-GCM-SHA256"},
	{0x006C, "ADH-AES128-SHA256"},
	{0x0034, "ADH-AES128-SHA"},
	{0x009B, "ADH-SEED-SHA"},
	{0x0046, "ADH-CAMELLIA128-SHA"},
	{0xC031, "ECDH-RSA-AES128-GCM-SHA256"},
	{0xC02D, "ECDH-ECDSA-AES128-GCM-SHA256"},
	{0xC029, "ECDH-RSA-AES128-SHA256"},
	{0xC025, "ECDH-ECDSA-AES128-SHA256"},
	{0xC00E, "ECDH-RSA-AES128-SHA"},
	{0xC004, "ECDH-ECDSA-AES128-SHA"},
	{0x009C, "AES128-GCM-SHA256"},
	{0x003C, "AES128-SHA256"},
	{0x002F, "AES128-SHA"},
	{0x0096, "SEED-SHA"},
	{0x0041, "CAMELLIA128-SHA"},
	{0x008C, "PSK-AES128-CBC-SHA"},
	{0xC012, "ECDHE-RSA-DES-CBC3-SHA"},
	{0xC008, "ECDHE-ECDSA-DES-CBC3-SHA"},
	{0x0016, "EDH-RSA-DES-CBC3-SHA"},
	{0x0013, "EDH-DSS-DES-CBC3-SHA"},
	{0x0010, "DH-RSA-DES-CBC3-SHA"},
	{0x000D, "DH-DSS-DES-CBC3-SHA"},
	{0xC017, "AECDH-DES-CBC3-SHA"},
	{0x001B, "ADH-DES-CBC3-SHA"},
	{0xC00D, "ECDH-RSA-DES-CBC3-SHA"},
	{0xC003, "ECDH-ECDSA-DES-CBC3-SHA"},
	{0x000A, "DES-CBC3-SHA"},
	{0x0007, "IDEA-CBC-SHA"},
	{0x008B, "PSK-3DES-EDE-CBC-SHA"},
	{0x0021, "KRB5-IDEA-CBC-SHA"},
	{0x001F, "KRB5-DES-CBC3-SHA"},
	{0x0025, "KRB5-IDEA-CBC-MD5"},
	{0x0023, "KRB5-DES-CBC3-MD5"},
	{0xC011, "ECDHE-RSA-RC4-SHA"},
	{0xC007, "ECDHE-ECDSA-RC4-SHA"},
	{0xC016, "AECDH-RC4-SHA"},
	{0x0018, "ADH-RC4-MD5"},
	{0xC00C, "ECDH-RSA-RC4-SHA"},
	{0xC002, "ECDH-ECDSA-RC4-SHA"},
	{0x0005, "RC4-SHA"},
	{0x0004, "RC4-MD5"},
	{0x008A, "PSK-RC4-SHA"},
	{0x0020, "KRB5-RC4-SHA"},
	{0x0024, "KRB5-RC4-MD5"},
	{0xC010, "ECDHE-RSA-NULL-SHA"},
	{0xC006, "ECDHE-ECDSA-NULL-SHA"},
	{0xC015, "AECDH-NULL-SHA"},
	{0xC00B, "ECDH-RSA-NULL-SHA"},
	{0xC001, "ECDH-ECDSA-NULL-SHA"},
	{0x003B, "NULL-SHA256"},
	{0x0002, "NULL-SHA"},
	{0x0001, "NULL-MD5"}
};

static struct cipher_suite cipher_suite_list_tls13[] =
{
	{0x1301, "TLS_AES_128_GCM_SHA256"},
	{0x1302, "TLS_AES_256_GCM_SHA384"},
	{0x1303, "TLS_CHACHA20_POLY1305_SHA256"},
	{0x1304, "TLS_AES_128_CCM_SHA256"},
	{0x1305, "TLS_AES_128_CCM_8_SHA256"}
};

static int cipher_suites_convert_helper(uint16_t value, char *name, size_t name_sz)
{
	int n1 = sizeof(cipher_suite_list) / sizeof(struct cipher_suite);
	int n2 = sizeof(cipher_suite_list_tls13) / sizeof(struct cipher_suite);
	for(int i = 0; i < n1; i++)
	{
		if(value == cipher_suite_list[i].value)
		{
			if(name) memcpy(name, cipher_suite_list[i].name, strnlen(cipher_suite_list[i].name, name_sz));
			return 1;
		}
	}
	for(int i = 0; i < n2; i++)
	{
		if(value == cipher_suite_list_tls13[i].value)
		{
			if(name) memcpy(name, cipher_suite_list_tls13[i].name, strnlen(cipher_suite_list_tls13[i].name, name_sz));
			return 2;
		}
	}
	return 0;
}

void ssl_cipher_suites_to_name(const char *source, int source_len, char *result_common, size_t sz_common, char *result_tls13, size_t sz_tls13)
{
	int target_common_reach_max = 0;
	int target_tls13_reach_max = 0;
	char name[TFE_SYMBOL_MAX] = "";
	for(int i = 0; i < source_len - 1;)
	{
		uint16_t val = (source[i] << 8) | source[i + 1];
		memset(name, 0, sizeof(name));
		int ret = cipher_suites_convert_helper(val, name, sizeof(name));
		//target common
		if(ret == 1 && target_common_reach_max == 0)
		{
			if(strnlen(name, sizeof(name)) + strnlen(result_common, sz_common) + 1 > sz_common)
			{
				target_common_reach_max = 1;
			}
			else
			{
				strncat(result_common, name, sz_common);
				strncat(result_common, ":", sz_common);
			}
		}
		//result_tls13
		if(ret == 2 && target_tls13_reach_max == 0)
		{
			if(strnlen(name, sizeof(name)) + strnlen(result_tls13, sz_tls13) + 1 > sz_tls13)
			{
				target_tls13_reach_max = 1;
			}
			else
			{
				strncat(result_tls13, name, sz_tls13);
				strncat(result_tls13, ":", sz_tls13);
			}
		}
		i += 2;
	}
	int len1 = strnlen(result_common, sz_common);
	if(len1 > 0)
	{
		result_common[len1 - 1] = '\0';
	}
	int len2 = strnlen(result_tls13, sz_tls13);
	if(len2 > 0)
	{
		result_tls13[len2 - 1] = '\0';
	}
	return;
}

struct ssl_chello* ssl_chello_parse(const unsigned char* buff, size_t buff_len, enum chello_parse_result* result)
{
	if(buff == NULL)
	{
		*result = CHELLO_PARSE_INVALID_FORMAT;
		return NULL;
	}
	if(buff_len < 1)
	{
		*result = CHELLO_PARSE_NOT_ENOUGH_BUFF;
		return NULL;
	}
	if(buff[0] != 0x80 && buff[0] != 0x16)
	{
		*result = CHELLO_PARSE_INVALID_FORMAT;
		return NULL;
	}
	 /* SSL 2.0 compatible Client Hello
     * High bit of first byte (length) and content type is Client Hello
     * See RFC5246 Appendix E.2
	 * if it is SSL 2.0, only parse version
     */
	if(buff[0] == 0x80)
	{
		struct ssl_chello* _chello = (struct ssl_chello*)ALLOC(struct ssl_chello, 1);
		_chello->min_version.major = 0x02;
		if(buff_len < 2)
		{
			*result = CHELLO_PARSE_NOT_ENOUGH_BUFF;
			return _chello;
		}
		size_t len = (size_t)buff[1];
		if (buff_len < len + 2)
		{
			*result = CHELLO_PARSE_NOT_ENOUGH_BUFF;
        	return _chello;
		}
		buff_len = len + 2;
		size_t pos = 2;
		/* Handshark Message Type: Client Hello */
		if (pos + 1 > buff_len)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
    	}
		if (buff[pos] != 0x01)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
    	}
		pos += 1;
		/*  Version */
		if(pos + 2 > buff_len)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
		}
		_chello->max_version.major = buff[pos];
		_chello->max_version.minor = buff[pos + 1];
		_chello->max_version.ossl_format=(uint16_t)_chello->max_version.major<<8|_chello->max_version.minor;
		*result = CHELLO_PARSE_SUCCESS;
		return _chello;
	}
	else
	{
		if (buff_len < 5)
		{
        	*result = CHELLO_PARSE_NOT_ENOUGH_BUFF;
			return NULL;
		}
		if(buff[1] != 3 || buff[2] > 4 || buff[2] < 0)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return NULL;
		}

		struct ssl_chello* _chello = (struct ssl_chello*)ALLOC(struct ssl_chello, 1);
		_chello->min_version.major = buff[1];
		_chello->min_version.minor = buff[2];
		_chello->min_version.ossl_format=(uint16_t)_chello->min_version.major<<8|_chello->min_version.minor;
		_chello->max_version.major = -1;
		_chello->max_version.minor = -1;
		_chello->sni = NULL;
		_chello->alpn = NULL;
		_chello->cipher_suites = NULL;
		_chello->cipher_suites_len = 0;
		_chello->sign_algos = NULL;
		_chello->sign_algos_len = 0;
		_chello->supported_groups = NULL;
		_chello->supported_groups_len = 0;

		/* TLS record length */
    	size_t len = ((size_t)buff[3] << 8) + (size_t)buff[4] + 5;
		if (buff_len < len)
		{
			*result = CHELLO_PARSE_NOT_ENOUGH_BUFF;
        	return _chello;
		}
		buff_len = len;
		size_t pos = 5;
    	if (pos + 1 > buff_len)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
    	}
		if (buff[pos] != 0x01)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
    	}
		pos += 4;
		if(pos + 2 > buff_len)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
		}
		_chello->max_version.major = buff[pos];
		_chello->max_version.minor = buff[pos+1];
		_chello->max_version.ossl_format=(uint16_t)_chello->max_version.major<<8|_chello->max_version.minor;

		pos += 34;
		/* Session ID */
    	if (pos + 1 > buff_len)
		{
        	*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
		}
    	len = (size_t)buff[pos];
    	pos += 1 + len;
		/* Cipher Suites */
    	if (pos + 2 > buff_len)
		{
        	*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
		}
    	len = ((size_t)buff[pos] << 8) + (size_t)buff[pos + 1];
		pos += 2;
		if(pos + len > buff_len || len % 2 != 0)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
		}
		size_t i=0, j=0;
		_chello->cipher_suites = ALLOC(char, len);
		uint16_t* known_cipher=(uint16_t*)_chello->cipher_suites;
		uint16_t* raw_cipher=(uint16_t*)(buff + pos);
		for(i=0, j=0; i<len/2; i++)
		{
			//https://security.stackexchange.com/questions/176951/google-chrome-weird-random-cipher-suite
			if(!cipher_is_grease(raw_cipher[i]))
			{
				known_cipher[j++]=raw_cipher[i];
			}
		}
		_chello->cipher_suites_len = j*2;

		pos += len;

		/* Compression Methods */
    	if (pos >= buff_len)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
		}
    	len = (size_t)buff[pos];
    	pos += 1 + len;

    	/* No extension */
        if(pos == buff_len)
        {
            *result = CHELLO_PARSE_SUCCESS;
            return _chello;
        }

		/* Extensions */
    	if (pos + 2 > buff_len)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
		}
    	len = ((size_t)buff[pos] << 8) + (size_t)buff[pos + 1];
    	pos += 2;
		if (pos + len > buff_len)
		{
			*result = CHELLO_PARSE_INVALID_FORMAT;
        	return _chello;
		}
		int ret = parse_extensions(buff + pos, len, _chello);
		*result = (enum chello_parse_result)ret;
		return _chello;
	}
}

void ssl_chain_dump(const char *sni, const char *chain_type, STACK_OF(X509) *chain)
{
	int j = 0;
	char *subj = NULL;
	char *issuer = NULL;
	char *fingerprint = NULL;
	X509 *cert = NULL;

	if (chain == NULL)
	{
		return;
	}

	for (j = 0; j < sk_X509_num(chain); j++)
	{
		cert = sk_X509_value(chain, j);
		if (cert == NULL)
			continue;

		subj = ssl_x509_subject(cert);
		issuer = ssl_x509_issuer(cert);
		fingerprint = ssl_x509_fingerprint(cert, 0);

		TFE_LOG_DEBUG(g_default_logger, "sni:%s chain_type:%s depth=%d: subject:%s issuer:%s fingerprint:%s",
			sni ? sni : "null", chain_type, j, subj ? subj : "null", issuer ? issuer : "null", fingerprint ? fingerprint : "null");
		if (subj)
		{
			free(subj);
			subj = NULL;
		}
		if (issuer)
		{
			free(issuer);
			issuer = NULL;
		}
		if (fingerprint)
		{
			free(fingerprint);
			fingerprint = NULL;
		}
	}
}