/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2026 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#define WIN32_LEAN_AND_MEAN
#include "private-lib-core.h"
#include "private-lib-tls-openssl.h"

#if OPENSSL_VERSION_NUMBER >= 0x40000000L
#define CAST_X509_EXTENSION(x)	(x)
#else
#define CAST_X509_EXTENSION(x)	((X509_EXTENSION *)(x))
#endif

#if !defined(LWS_PLAT_OPTEE)
static int
dec(char c)
{
	return c - '0';
}
#endif

static time_t
lws_tls_openssl_asn1time_to_unix(ASN1_TIME *as)
{
#if !defined(LWS_PLAT_OPTEE)

#if defined(USE_WOLFSSL)
	const char *p = (const char *)ASN1_STRING_get0_data((const WOLFSSL_ASN1_STRING *)as);
#else
	const char *p = (const char *)ASN1_STRING_get0_data(as);
#endif
	struct tm t;

	/* [YY]YYMMDDHHMMSSZ */

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

	if (strlen(p) == 13) {
		t.tm_year = (dec(p[0]) * 10) + dec(p[1]);
		if (t.tm_year < 50) /* RFC5280: 13 char dates will break after 2049 */
			t.tm_year += 100; /* struct tm year is -1900, this gives 2000..2049 */
		p += 2;
	} else {
		t.tm_year = ((dec(p[0]) * 1000) + (dec(p[1]) * 100) +
			    (dec(p[2]) * 10) + dec(p[3])) - 1900; /* struct tm year is -1900 */
		p += 4;
	}
	t.tm_mon = (dec(p[0]) * 10) + dec(p[1]) - 1;
	p += 2;
	t.tm_mday = (dec(p[0]) * 10) + dec(p[1]) - 1;
	p += 2;
	t.tm_hour = (dec(p[0]) * 10) + dec(p[1]);
	p += 2;
	t.tm_min = (dec(p[0]) * 10) + dec(p[1]);
	p += 2;
	t.tm_sec = (dec(p[0]) * 10) + dec(p[1]);
	t.tm_isdst = 0;

	return mktime(&t);
#else
	return (time_t)-1;
#endif
}

#if defined(USE_WOLFSSL)
#define AUTHORITY_KEYID WOLFSSL_AUTHORITY_KEYID
#endif

int
lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type,
			  union lws_tls_cert_info_results *buf, size_t len)
{
#ifndef USE_WOLFSSL
	const unsigned char *dp;
	ASN1_OCTET_STRING *val;
	const ASN1_OCTET_STRING *val2;
	AUTHORITY_KEYID *akid;
	const X509_EXTENSION *ext;
	int tag, xclass, r = 1;
	long xlen, loc;
#endif
	X509_NAME *xn;
#if !defined(LWS_PLAT_OPTEE)
	char *p, *p1;
	size_t rl;
#endif

	buf->ns.len = 0;

	if (!x509)
		return -1;
	if (!len)
		len = sizeof(buf->ns.name);

#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(X509_get_notBefore)
#define X509_get_notBefore(x)	X509_getm_notBefore(x)
#define X509_get_notAfter(x)	X509_getm_notAfter(x)
#endif

	switch (type) {
	case LWS_TLS_CERT_INFO_VALIDITY_FROM:
		buf->time = lws_tls_openssl_asn1time_to_unix(
					X509_get_notBefore(x509));
		if (buf->time == (time_t)-1)
			return -1;
		break;

	case LWS_TLS_CERT_INFO_VALIDITY_TO:
		buf->time = lws_tls_openssl_asn1time_to_unix(
					X509_get_notAfter(x509));
		if (buf->time == (time_t)-1)
			return -1;
		break;

	case LWS_TLS_CERT_INFO_COMMON_NAME:
#if defined(LWS_PLAT_OPTEE)
		return -1;
#else
		xn = X509_get_subject_name(x509);
		if (!xn)
			return -1;
		X509_NAME_oneline(xn, buf->ns.name, (int)len - 2);
		p = (char *)strstr(buf->ns.name, "/CN=");
		if (p) {
			p += 4;
			p1 = (char *)strchr(p, '/');
			if (p1)
				rl = lws_ptr_diff_size_t(p1, p);
			else
				rl = strlen(p);
			memmove(buf->ns.name, p, rl);
			buf->ns.name[rl] = '\0';
		}
		buf->ns.len = (int)strlen(buf->ns.name);
		return 0;
#endif
	case LWS_TLS_CERT_INFO_ISSUER_NAME:
		xn = X509_get_issuer_name(x509);
		if (!xn)
			return -1;
		X509_NAME_oneline(xn, buf->ns.name, (int)len - 1);
		buf->ns.len = (int)strlen(buf->ns.name);
		return 0;

	case LWS_TLS_CERT_INFO_USAGE:
#if defined(LWS_HAVE_X509_get_key_usage)
		buf->usage = X509_get_key_usage(x509);
		break;
#else
		return -1;
#endif

	case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY:
	{
#ifndef USE_WOLFSSL
		size_t klen = (unsigned int)i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509), NULL);
		uint8_t *tmp, *ptmp;

		if (!klen || klen > len)
			return -1;

		tmp = (uint8_t *)OPENSSL_malloc(klen);
		if (!tmp)
			return -1;

		ptmp = tmp;
		if (i2d_X509_PUBKEY(
			      X509_get_X509_PUBKEY(x509), &ptmp) != (int)klen ||
		    !ptmp || lws_ptr_diff(ptmp, tmp) != (int)klen) {
			lwsl_info("%s: cert public key extraction failed\n",
				  __func__);
			if (ptmp)
				OPENSSL_free(tmp);

			return -1;
		}

		buf->ns.len = (int)klen;
		memcpy(buf->ns.name, tmp, klen);
		OPENSSL_free(tmp);
#endif
		return 0;
	}
	case LWS_TLS_CERT_INFO_DER_RAW:
	{
		int der_len = i2d_X509(x509, NULL);
		uint8_t *tmp = (uint8_t *)buf->ns.name;

		buf->ns.len = der_len < 0 ? 0 : der_len;

		if (der_len < 0 || (size_t)der_len > len)
			return -1;

		der_len = i2d_X509(x509, &tmp);
		if (der_len < 0)
			return -1;

		return 0;
	}
	case LWS_TLS_CERT_INFO_DER_SPKI:
	{
#ifndef USE_WOLFSSL
		int der_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509), NULL);
		uint8_t *tmp = (uint8_t *)buf->ns.name;

		buf->ns.len = der_len < 0 ? 0 : der_len;

		if (der_len < 0 || (size_t)der_len > len)
			return -1;

		der_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509), &tmp);
		if (der_len < 0)
			return -1;

		return 0;
#else
		return -1;
#endif
	}

#ifndef USE_WOLFSSL

	case LWS_TLS_CERT_INFO_AUTHORITY_KEY_ID:
		loc = X509_get_ext_by_NID(x509, NID_authority_key_identifier, -1);
		if (loc < 0)
			return 1;

		ext = X509_get_ext(x509, (int)loc);
		if (!ext)
			return 1;
#ifndef USE_WOLFSSL
		akid = (AUTHORITY_KEYID *)X509V3_EXT_d2i(CAST_X509_EXTENSION(ext));
#else
		akid = (AUTHORITY_KEYID *)wolfSSL_X509V3_EXT_d2i(ext);
#endif
		if (!akid || !akid->keyid)
			return 1;
		val = akid->keyid;
		dp = ASN1_STRING_get0_data(val);
		xlen = ASN1_STRING_length(val);

		buf->ns.len = (int)xlen;
		if (len < (size_t)buf->ns.len)
			return -1;

		memcpy(buf->ns.name, dp, (size_t)buf->ns.len);

		AUTHORITY_KEYID_free(akid);
		break;

	case LWS_TLS_CERT_INFO_AUTHORITY_KEY_ID_ISSUER:
		loc = X509_get_ext_by_NID(x509, NID_authority_key_identifier, -1);
		if (loc < 0)
			return 1;

		ext = X509_get_ext(x509, (int)loc);
		if (!ext)
			return 1;

#ifndef USE_WOLFSSL
		akid = (AUTHORITY_KEYID *)X509V3_EXT_d2i(CAST_X509_EXTENSION(ext));
#else
		akid = (AUTHORITY_KEYID *)wolfSSL_X509V3_EXT_d2i(ext);
#endif
		if (!akid || !akid->issuer)
			return 1;

#if defined(LWS_HAVE_OPENSSL_STACK)
		{
			const X509V3_EXT_METHOD* method = X509V3_EXT_get(CAST_X509_EXTENSION(ext));
			STACK_OF(CONF_VALUE) *cv;
		#if defined(LWS_WITH_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
			size_t j;
		#else
			int j;
		#endif

			cv = i2v_GENERAL_NAMES((X509V3_EXT_METHOD*)method, akid->issuer, NULL);
			if (!cv)
				goto bail_ak_l;

		        for (j = 0; j < OPENSSL_sk_num((const OPENSSL_STACK *)cv); j++) {
		            CONF_VALUE *nval = OPENSSL_sk_value((const OPENSSL_STACK *)cv, j);
		            size_t ln = (nval->name ? strlen(nval->name) : 0),
		        	   lv = (nval->value ? strlen(nval->value) : 0),
		        	   l = ln + lv;

		            if (len > l) {
		        	    if (nval->name)
		        		    memcpy(buf->ns.name + buf->ns.len, nval->name, ln);
		        	    if (nval->value)
		        		    memcpy(buf->ns.name + buf->ns.len + ln, nval->value, lv);
		        	    buf->ns.len = (int)((size_t)buf->ns.len + l);
		        	    len -= l;
		        	    buf->ns.name[buf->ns.len] = '\0';

		        	    r = 0;
		            }
		        }
		}

bail_ak_l:
#endif
		AUTHORITY_KEYID_free(akid);

		return r;

	case LWS_TLS_CERT_INFO_AUTHORITY_KEY_ID_SERIAL:
		loc = X509_get_ext_by_NID(x509, NID_authority_key_identifier, -1);
		if (loc < 0)
			return 1;

		ext = X509_get_ext(x509, (int)loc);
		if (!ext)
			return 1;
		akid = (AUTHORITY_KEYID *)X509V3_EXT_d2i(CAST_X509_EXTENSION(ext));
		if (!akid || !akid->serial)
			return 1;

#if 0
		// need to handle blobs, and ASN1_INTEGER_get_uint64 not
		// available on older openssl
		{
			uint64_t res;
			if (ASN1_INTEGER_get_uint64(&res, akid->serial) != 1)
				break;
			buf->ns.len = lws_snprintf(buf->ns.name, len, "%llu",
					(unsigned long long)res);
		}
#endif
		break;

	case LWS_TLS_CERT_INFO_SUBJECT_KEY_ID:

		loc = X509_get_ext_by_NID(x509, NID_subject_key_identifier, -1);
		if (loc < 0)
			return 1;

		ext = X509_get_ext(x509, (int)loc);
		if (!ext)
			return 1;

		val2 = X509_EXTENSION_get_data(CAST_X509_EXTENSION(ext));
		if (!val2)
			return 1;

#if defined(USE_WOLFSSL)
		return 1;
#else
		dp = ASN1_STRING_get0_data(val2);

		if (ASN1_get_object(&dp, &xlen,
				    &tag, &xclass, ASN1_STRING_length(val2)) & 0x80)
			return -1;

		if (tag != V_ASN1_OCTET_STRING) {
			lwsl_notice("not octet string %d\n", (int)tag);
			return 1;
		}
#endif
		buf->ns.len = (int)xlen;
		if (len < (size_t)buf->ns.len)
			return -1;

		memcpy(buf->ns.name, dp, (size_t)buf->ns.len);
		break;
#endif

	default:
		return -1;
	}

	return 0;
}

int
lws_x509_info(struct lws_x509_cert *x509, enum lws_tls_cert_info type,
	      union lws_tls_cert_info_results *buf, size_t len)
{
	return lws_tls_openssl_cert_info(x509->cert, type, buf, len);
}

#if defined(LWS_WITH_NETWORK)
int
lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type,
		        union lws_tls_cert_info_results *buf, size_t len)
{
#if defined(LWS_HAVE_SSL_CTX_get0_certificate)
	X509 *x509 = SSL_CTX_get0_certificate(vhost->tls.ssl_ctx);

	return lws_tls_openssl_cert_info(x509, type, buf, len);
#else
	lwsl_notice("openssl is too old to support %s\n", __func__);

	return -1;
#endif
}



int
lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
		       union lws_tls_cert_info_results *buf, size_t len)
{
	int rc = 0;
	X509 *x509;

	wsi = lws_get_network_wsi(wsi);

	x509 = SSL_get_peer_certificate(wsi->tls.ssl);

	if (!x509) {
		lwsl_debug("no peer cert\n");

		return -1;
	}

	switch (type) {
	case LWS_TLS_CERT_INFO_VERIFIED:
		buf->verified = SSL_get_verify_result(wsi->tls.ssl) ==
					X509_V_OK;
		break;
	default:
		rc = lws_tls_openssl_cert_info(x509, type, buf, len);
	}

	X509_free(x509);

	return rc;
}
#endif

int
lws_x509_create(struct lws_x509_cert **x509)
{
	*x509 = lws_malloc(sizeof(**x509), __func__);
	if (*x509)
		(*x509)->cert = NULL;

	return !(*x509);
}

int
lws_x509_parse_from_pem(struct lws_x509_cert *x509, const void *pem, size_t len)
{
	BIO* bio = BIO_new(BIO_s_mem());

	BIO_write(bio, pem, (int)len);
	x509->cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
	BIO_free(bio);
	if (!x509->cert) {
		lwsl_err("%s: unable to parse PEM cert\n", __func__);
		lws_tls_err_describe_clear();

		return -1;
	}

	return 0;
}

int
lws_x509_verify(struct lws_x509_cert *x509, struct lws_x509_cert *trusted,
		const char *common_name)
{
	char c[128];
	int ret;

	if (common_name) {
		X509_NAME *xn = X509_get_subject_name(x509->cert);
		if (!xn)
			return -1;
		
		if (X509_NAME_get_text_by_NID(xn, NID_commonName, c, sizeof(c)) < 0)
			return -1;

		if (strcmp(c, common_name)) {
			lwsl_err("%s: common name mismatch\n", __func__);
			return -1;
		}
	}

	ret = X509_check_issued(trusted->cert, x509->cert);
	if (ret != X509_V_OK) {
		lwsl_err("%s: unable to verify cert relationship\n", __func__);
		lws_tls_err_describe_clear();

		return -1;
	}

	return 0;
}

#if defined(LWS_WITH_JOSE)
int
lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509,
		       const char *curves, int rsa_min_bits)
{
	int id, n, ret = -1, count;
	ASN1_OBJECT *obj = NULL;
	const EC_POINT *ecpoint;
	const EC_GROUP *ecgroup;
	EC_KEY *ecpub = NULL;
	X509_PUBKEY *pubkey;
	RSA *rsapub = NULL;
	BIGNUM *mpi[4];
	EVP_PKEY *pkey;

	memset(jwk, 0, sizeof(*jwk));

	pubkey = X509_get_X509_PUBKEY(x509->cert);
	if (!pubkey) {
		lwsl_err("%s: missing pubkey alg in cert\n", __func__);

		goto bail;
	}

	if (X509_PUBKEY_get0_param(&obj, NULL, NULL, NULL, pubkey) != 1) {
		lwsl_err("%s: missing pubkey alg in cert\n", __func__);

		goto bail;
	}

	id = OBJ_obj2nid(obj);
	if (id == NID_undef) {
		lwsl_err("%s: missing pubkey alg in cert\n", __func__);

		goto bail;
	}

	lwsl_debug("%s: key type %d \"%s\"\n", __func__, id, OBJ_nid2ln(id));

	pkey = X509_get_pubkey(x509->cert);
	if (!pkey) {
		lwsl_notice("%s: unable to extract pubkey", __func__);

		goto bail;
	}

	switch (id) {
	case NID_X9_62_id_ecPublicKey:
		lwsl_debug("%s: EC key\n", __func__);
		jwk->kty = LWS_GENCRYPTO_KTY_EC;

		if (!curves) {
			lwsl_err("%s: ec curves not allowed\n", __func__);

			goto bail1;
		}

		ecpub = EVP_PKEY_get1_EC_KEY(pkey);
		if (!ecpub) {
			lwsl_notice("%s: missing EC pubkey\n", __func__);

			goto bail1;
		}

		ecpoint = EC_KEY_get0_public_key(ecpub);
		if (!ecpoint) {
			lwsl_err("%s: EC_KEY_get0_public_key failed\n", __func__);
			goto bail2;
		}

		ecgroup = EC_KEY_get0_group(ecpub);
		if (!ecgroup) {
			lwsl_err("%s: EC_KEY_get0_group failed\n", __func__);
			goto bail2;
		}

		/* validate the curve against ones we allow */

		if (lws_genec_confirm_curve_allowed_by_tls_id(curves,
				EC_GROUP_get_curve_name(ecgroup), jwk))
			/* already logged */
			goto bail2;

		mpi[LWS_GENCRYPTO_EC_KEYEL_CRV] = NULL;
		mpi[LWS_GENCRYPTO_EC_KEYEL_X] = BN_new(); /* X */
		mpi[LWS_GENCRYPTO_EC_KEYEL_D] = NULL;
		mpi[LWS_GENCRYPTO_EC_KEYEL_Y] = BN_new(); /* Y */

#if defined(LWS_HAVE_EC_POINT_get_affine_coordinates)
		if (EC_POINT_get_affine_coordinates(ecgroup, ecpoint,
#else
		if (EC_POINT_get_affine_coordinates_GFp(ecgroup, ecpoint,
#endif
						  mpi[LWS_GENCRYPTO_EC_KEYEL_X],
						  mpi[LWS_GENCRYPTO_EC_KEYEL_Y],
							  NULL) != 1) {
			BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]);
			BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]);
			lwsl_err("%s: EC_POINT_get_aff failed\n", __func__);
			goto bail2;
		}
		count = LWS_GENCRYPTO_EC_KEYEL_COUNT;
		n = LWS_GENCRYPTO_EC_KEYEL_X;
		break;

	case NID_rsaEncryption:
		lwsl_debug("%s: rsa key\n", __func__);
		jwk->kty = LWS_GENCRYPTO_KTY_RSA;

		rsapub = EVP_PKEY_get1_RSA(pkey);
		if (!rsapub) {
			lwsl_notice("%s: missing RSA pubkey\n", __func__);

			goto bail1;
		}

		if ((size_t)RSA_size(rsapub) * 8 < (size_t)rsa_min_bits) {
			lwsl_err("%s: key bits %d less than minimum %d\n",
				 __func__, RSA_size(rsapub) * 8, rsa_min_bits);

			goto bail2;
		}

#if defined(LWS_HAVE_RSA_SET0_KEY)
		/* we don't need d... but the api wants to write it */
		RSA_get0_key(rsapub,
			    (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_N],
			    (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_E],
			    (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_D]);
#else
		mpi[LWS_GENCRYPTO_RSA_KEYEL_E] = rsapub->e;
		mpi[LWS_GENCRYPTO_RSA_KEYEL_N] = rsapub->n;
		mpi[LWS_GENCRYPTO_RSA_KEYEL_D] = NULL;
#endif
		count = LWS_GENCRYPTO_RSA_KEYEL_D;
		n = LWS_GENCRYPTO_RSA_KEYEL_E;
		break;
	default:
		lwsl_err("%s: unknown NID\n", __func__);
		goto bail2;
	}

	for (; n < count; n++) {
		if (!mpi[n])
			continue;
		jwk->e[n].len = (unsigned int)BN_num_bytes(mpi[n]);
		jwk->e[n].buf = lws_malloc(jwk->e[n].len, "certkeyimp");
		if (!jwk->e[n].buf) {
			if (id == NID_X9_62_id_ecPublicKey) {
				BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]);
				BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]);
			}
			goto bail2;
		}
		BN_bn2bin(mpi[n], jwk->e[n].buf);
	}

	if (id == NID_X9_62_id_ecPublicKey) {
		BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]);
		BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]);
	}

	ret = 0;

bail2:
	if (id == NID_X9_62_id_ecPublicKey)
		EC_KEY_free(ecpub);
	else
		RSA_free(rsapub);

bail1:
	EVP_PKEY_free(pkey);
bail:
	/* jwk destroy will clean any partial state */
	if (ret)
		lws_jwk_destroy(jwk);

	return ret;
}

static int
lws_x509_jwk_privkey_pem_pp_cb(char *buf, int size, int rwflag, void *u)
{
	const char *pp = (const char *)u;
	size_t n = strlen(pp);

	if ((int)n > size - 1)
		return -1;

	memcpy(buf, pp, n + 1);

	return (int)n;
}

int
lws_x509_jwk_privkey_pem(struct lws_context *cx, struct lws_jwk *jwk,
			 void *pem, size_t len, const char *passphrase)
{
	BIO* bio = BIO_new(BIO_s_mem());
	BIGNUM *mpi, *dummy[6];
	EVP_PKEY *pkey = NULL;
	EC_KEY *ecpriv = NULL;
	RSA *rsapriv = NULL;
	const BIGNUM *cmpi;
	int n, m, ret = -1;

	BIO_write(bio, pem, (int)len);
	PEM_read_bio_PrivateKey(bio, &pkey, lws_x509_jwk_privkey_pem_pp_cb,
				(void *)passphrase);
	BIO_free(bio);
	lws_explicit_bzero((void *)pem, len);
	if (!pkey) {
		lwsl_err("%s: unable to parse PEM privkey\n", __func__);
		lws_tls_err_describe_clear();

		return -1;
	}

	/* confirm the key type matches the existing jwk situation */

	switch (jwk->kty) {
	case LWS_GENCRYPTO_KTY_EC:
		if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_EC) {
			lwsl_err("%s: jwk is EC but privkey isn't\n", __func__);

			goto bail;
		}
		ecpriv = EVP_PKEY_get1_EC_KEY(pkey);
		if (!ecpriv) {
			lwsl_notice("%s: missing EC key\n", __func__);

			goto bail;
		}

		cmpi = EC_KEY_get0_private_key(ecpriv);

		/* quick size check first */

		n = (int)BN_num_bytes(cmpi);
		if (jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].len != (uint32_t)n) {
			lwsl_err("%s: jwk key size doesn't match\n", __func__);

			goto bail1;
		}

		/* TODO.. check public curve / group + point */

		jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len = (unsigned int)n;
		jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf = lws_malloc((unsigned int)n, "ec");
		if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf)
			goto bail1;

		m = BN_bn2binpad(cmpi, jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf,
				      (int32_t)jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len);
		if ((unsigned int)m != (unsigned int)BN_num_bytes(cmpi))
			goto bail1;

		break;

	case LWS_GENCRYPTO_KTY_RSA:
		if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_RSA) {
			lwsl_err("%s: RSA jwk, non-RSA privkey\n", __func__);

			goto bail;
		}
		rsapriv = EVP_PKEY_get1_RSA(pkey);
		if (!rsapriv) {
			lwsl_notice("%s: missing RSA key\n", __func__);

			goto bail;
		}

#if defined(LWS_HAVE_RSA_SET0_KEY) && !defined(USE_WOLFSSL)
		RSA_get0_key(rsapriv, (const BIGNUM **)&dummy[0], /* n */
				      (const BIGNUM **)&dummy[1], /* e */
				      (const BIGNUM **)&mpi);	  /* d */
		RSA_get0_factors(rsapriv, (const BIGNUM **)&dummy[4],  /* p */
					  (const BIGNUM **)&dummy[5]); /* q */
#else
		dummy[0] = rsapriv->n;
		dummy[1] = rsapriv->e;
		dummy[4] = rsapriv->p;
		dummy[5] = rsapriv->q;
		mpi = rsapriv->d;
#endif

		/* quick size check first */

		n = (int)BN_num_bytes(mpi);
		if (jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len != (uint32_t)n) {
			lwsl_err("%s: jwk key size doesn't match\n", __func__);

			goto bail1;
		}

		/* then check that n & e match what we got from the cert */

		dummy[2] = BN_bin2bn(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].buf,
				     SSL_SIZE_T_CAST(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len),
				     NULL);
		dummy[3] = BN_bin2bn(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf,
				     SSL_SIZE_T_CAST(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].len),
				     NULL);

		m = BN_cmp(dummy[2], dummy[0]) | BN_cmp(dummy[3], dummy[1]);
		BN_clear_free(dummy[2]);
		BN_clear_free(dummy[3]);
		if (m) {
			lwsl_err("%s: privkey doesn't match jwk pubkey\n",
				 __func__);

			goto bail1;
		}

		/* accept d from the PEM privkey into the JWK */

		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].len = (unsigned int)n;
		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf = lws_malloc((unsigned int)n, "privjk");
		if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf)
			goto bail1;

		BN_bn2bin(mpi, jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);

		/* accept p and q from the PEM privkey into the JWK */

		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].len = (unsigned int)BN_num_bytes(dummy[4]);
		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf = lws_malloc((unsigned int)n, "privjk");
		if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf) {
			lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);
			goto bail1;
		}
		BN_bn2bin(dummy[4], jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf);

		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].len = (unsigned int)BN_num_bytes(dummy[5]);
		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf = lws_malloc((unsigned int)n, "privjk");
		if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf) {
			lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);
			lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf);
			goto bail1;
		}
		BN_bn2bin(dummy[5], jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf);
		break;
	default:
		lwsl_err("%s: JWK has unknown kty %d\n", __func__, jwk->kty);
		return -1;
	}

	ret = 0;

bail1:
	if (jwk->kty == LWS_GENCRYPTO_KTY_EC)
		EC_KEY_free(ecpriv);
	else
		RSA_free(rsapriv);

bail:
	EVP_PKEY_free(pkey);

	return ret;
}
#endif

void
lws_x509_destroy(struct lws_x509_cert **x509)
{
	if (!*x509)
		return;

	if ((*x509)->cert) {
		X509_free((*x509)->cert);
		(*x509)->cert = NULL;
	}

	lws_free_set_NULL(*x509);
}

#if defined(LWS_WITH_BORINGSSL) || defined(LWS_WITH_AWSLC)
#ifndef X509V3_EXT_conf_nid
#define X509V3_EXT_conf_nid	X509V3_EXT_nconf_nid
#endif
#endif

#if !defined(USE_WOLFSSL)
static int
X509_extension_helper(X509 *x, X509V3_CTX *ctx, int nid, const char *value)
{
	X509_EXTENSION *ex;

	ex = X509V3_EXT_conf_nid(NULL, ctx, nid, (char *)(lws_intptr_t)value);
	if (!ex)
		return 1;

	X509_add_ext(x, ex, -1);
	X509_EXTENSION_free(ex);

	return 0;
}
#endif

int
lws_x509_create_cert(struct lws_context *context,
		     uint8_t **cert_buf, size_t *cert_len,
		     uint8_t **key_buf, size_t *key_len,
		     const struct lws_x509_cert_gen_info *info)
{
#if defined(USE_WOLFSSL)
	lwsl_err("%s: not supported on wolfssl\n", __func__);

	return 1;
#else
	EVP_PKEY *pkey = EVP_PKEY_new();
	X509 *x509 = NULL;
	X509_NAME *name;
	int ret = 1;
	unsigned char *p;
	int n;
	size_t len;
	X509 *issuer_x509 = NULL;
	EVP_PKEY *issuer_pkey = NULL;
	BIO *bio;

	if (!info || !info->san)
		return 1;

	/* 1. Generate or load the subject key */
	if (info->curve_name) {
		int nid = OBJ_sn2nid(info->curve_name);
		if (nid == NID_undef)
			nid = OBJ_ln2nid(info->curve_name);
		if (nid == NID_undef) {
			/* Common fallback map if OBJ_sn2nid fails for standard names */
			if (!strcmp(info->curve_name, "P-521")) nid = NID_secp521r1;
			else if (!strcmp(info->curve_name, "P-384")) nid = NID_secp384r1;
			else if (!strcmp(info->curve_name, "P-256")) nid = NID_X9_62_prime256v1;
		}

		if (nid == NID_undef) {
			lwsl_err("%s: unknown curve %s\n", __func__, info->curve_name);
			goto bail;
		}

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
		{
			EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
			if (!pctx) goto bail;
			if (EVP_PKEY_keygen_init(pctx) <= 0 ||
			    EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid) <= 0 ||
			    EVP_PKEY_keygen(pctx, &pkey) <= 0) {
				EVP_PKEY_CTX_free(pctx);
				goto bail;
			}
			EVP_PKEY_CTX_free(pctx);
		}
#else
		/* Legacy OpenSSL 1.0.2 fallback */
		{
			EC_KEY *ec = EC_KEY_new_by_curve_name(nid);
			if (!ec) goto bail;
			EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE);
			if (EC_KEY_generate_key(ec) <= 0) {
				EC_KEY_free(ec);
				goto bail;
			}
			EVP_PKEY_assign_EC_KEY(pkey, ec);
		}
#endif
	} else {
		/* RSA */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
		{
			EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
			if (!pctx) goto bail;
			if (EVP_PKEY_keygen_init(pctx) <= 0 ||
			    EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, info->key_bits ? info->key_bits : 2048) <= 0 ||
			    EVP_PKEY_keygen(pctx, &pkey) <= 0) {
				EVP_PKEY_CTX_free(pctx);
				goto bail;
			}
			EVP_PKEY_CTX_free(pctx);
		}
#else
		{
			RSA *rsa = RSA_generate_key(info->key_bits ? info->key_bits : 2048, RSA_F4, NULL, NULL);
			if (!rsa) goto bail;
			EVP_PKEY_assign_RSA(pkey, rsa);
		}
#endif
	}

	/* 2. Create Cert */
	x509 = X509_new();
	if (!x509) goto bail;

	X509_set_version(x509, 2); /* X.509 v3 */

	/* Random Serial */
	{
		ASN1_INTEGER *serial = X509_get_serialNumber(x509);
		BIGNUM *bn = BN_new();
		BN_pseudo_rand(bn, 64, 0, 0);
		BN_to_ASN1_INTEGER(bn, serial);
		BN_free(bn);
	}

	X509_gmtime_adj(X509_get_notBefore(x509), (long)-86400);
	X509_gmtime_adj(X509_get_notAfter(x509), (long)(info->validity_days ? info->validity_days : 365) * 24 * 3600);

	X509_set_pubkey(x509, pkey);

	name = X509_get_subject_name(x509);
	X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
				   (unsigned char *)info->san, -1, -1, 0);

	/* 3. Load Issuer if provided, else self-sign */
	if (info->ca_cert_pem && info->ca_key_pem) {
		bio = BIO_new_mem_buf(info->ca_cert_pem, -1);
		issuer_x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
		BIO_free(bio);
		if (!issuer_x509) goto bail;

		bio = BIO_new_mem_buf(info->ca_key_pem, -1);
		issuer_pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
		BIO_free(bio);
		if (!issuer_pkey) goto bail;

		X509_set_issuer_name(x509, X509_get_subject_name(issuer_x509));
	} else {
		X509_set_issuer_name(x509, name);
		issuer_x509 = x509;
		issuer_pkey = pkey;
	}

	/* 4. Add Extensions */
#if !defined(USE_WOLFSSL)
	{
		X509V3_CTX ctx;
		X509V3_set_ctx(&ctx, issuer_x509, x509, NULL, NULL, 0);

		if (info->is_ca) {
			X509_extension_helper(x509, &ctx, NID_basic_constraints, "critical,CA:TRUE");
			X509_extension_helper(x509, &ctx, NID_key_usage, "critical,keyCertSign,cRLSign");
		} else {
			X509_extension_helper(x509, &ctx, NID_basic_constraints, "critical,CA:FALSE");
			X509_extension_helper(x509, &ctx, NID_key_usage, "critical,digitalSignature,keyEncipherment");
			if (info->is_server)
				X509_extension_helper(x509, &ctx, NID_ext_key_usage, "serverAuth,clientAuth");
			else
				X509_extension_helper(x509, &ctx, NID_ext_key_usage, "clientAuth");
		}

		X509_extension_helper(x509, &ctx, NID_subject_key_identifier, "hash");

		if (issuer_x509 != x509)
			X509_extension_helper(x509, &ctx, NID_authority_key_identifier, "keyid:always,issuer:always");
		else
			X509_extension_helper(x509, &ctx, NID_authority_key_identifier, "keyid:always");

		if (info->san && info->is_server) {
			char alt[256];
			int is_ip = (strspn(info->san, "0123456789.") == strlen(info->san)) ||
				    (strspn(info->san, "0123456789abcdefABCDEF:") == strlen(info->san) && (char *)strchr(info->san, ':'));
			lws_snprintf(alt, sizeof(alt), "%s:%s", is_ip ? "IP" : "DNS", info->san);
			X509_extension_helper(x509, &ctx, NID_subject_alt_name, alt);
		}
	}
#endif

	/* 5. Sign */
	if (!X509_sign(x509, issuer_pkey, EVP_sha256()))
		goto bail;

	/* 6. Export to DER buffers */

	/* Cert */
	n = i2d_X509(x509, NULL);
	if (n < 0) goto bail;
	len = (size_t)n;
	*cert_buf = malloc(len); /* Use standard malloc for caller to free */
	if (!*cert_buf) goto bail;
	p = *cert_buf;
	*cert_len = (size_t)i2d_X509(x509, &p);

	/* Private Key */
	n = i2d_PrivateKey(pkey, NULL);
	if (n < 0) {
		free(*cert_buf);
		goto bail;
	}
	len = (size_t)n;
	*key_buf = malloc(len);
	if (!*key_buf) {
		free(*cert_buf);
		goto bail;
	}
	p = *key_buf;
	*key_len = (size_t)i2d_PrivateKey(pkey, &p);

	ret = 0;

bail:
	if (issuer_x509 && issuer_x509 != x509) X509_free(issuer_x509);
	if (issuer_pkey && issuer_pkey != pkey) EVP_PKEY_free(issuer_pkey);
	if (x509) X509_free(x509);
	if (pkey) EVP_PKEY_free(pkey);

	return ret;
#endif
}

int
lws_x509_create_self_signed(struct lws_context *context,
			    uint8_t **cert_buf, size_t *cert_len,
			    uint8_t **key_buf, size_t *key_len,
			    const char *san, int key_bits)
{
	struct lws_x509_cert_gen_info info;

	memset(&info, 0, sizeof(info));
	info.san = san ? san : "localhost";
	info.key_bits = key_bits;
	info.is_server = 1;

	return lws_x509_create_cert(context, cert_buf, cert_len, key_buf, key_len, &info);
}
