// Copyright 2017-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include "SelfSignedCertificate.h"

#define SSL_STRING(x) QString::fromLatin1(x).toUtf8().data()

static int add_ext(X509 *crt, int nid, char *value) {
	X509V3_CTX ctx;
	X509V3_set_ctx_nodb(&ctx);
	X509V3_set_ctx(&ctx, crt, crt, nullptr, nullptr, 0);

	X509_EXTENSION *ex = X509V3_EXT_conf_nid(nullptr, &ctx, nid, value);
	if (!ex) {
		return 0;
	}

	if (X509_add_ext(crt, ex, -1) == 0) {
		X509_EXTENSION_free(ex);
		return 0;
	}

	X509_EXTENSION_free(ex);
	return 1;
}

EVP_PKEY *SelfSignedCertificate::generate_rsa_keypair() {
	EVP_PKEY *pkey = EVP_PKEY_new();
	if (!pkey) {
		return nullptr;
	}

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
	if (!ctx) {
		return nullptr;
	}
	if (EVP_PKEY_keygen_init(ctx) <= 0) {
		return nullptr;
	}
	if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) {
		return nullptr;
	}
	if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
		return nullptr;
	}
	EVP_PKEY_CTX_free(ctx);
#else
	RSA *rsa  = RSA_new();
	BIGNUM *e = BN_new();
	if (!rsa) {
		return nullptr;
	}
	if (!e) {
		return nullptr;
	}
	if (BN_set_word(e, 65537) == 0) {
		return nullptr;
	}
	if (RSA_generate_key_ex(rsa, 2048, e, nullptr) == 0) {
		return nullptr;
	}
	if (EVP_PKEY_assign_RSA(pkey, rsa) == 0) {
		return nullptr;
	}
	BN_free(e);
	RSA_free(rsa);
#endif
	return pkey;
}

#define CHECK(statement) \
	if (!(statement)) {  \
		ok = false;      \
		goto out;        \
	}


bool SelfSignedCertificate::generate(CertificateType certificateType, QString clientCertName, QString clientCertEmail,
									 QSslCertificate &qscCert, QSslKey &qskKey) {
	bool ok                    = true;
	EVP_PKEY *pkey             = nullptr;
	X509 *x509                 = nullptr;
	X509_NAME *name            = nullptr;
	ASN1_INTEGER *serialNumber = nullptr;
	ASN1_TIME *notBefore       = nullptr;
	ASN1_TIME *notAfter        = nullptr;
	QString commonName;
	bool isServerCert = certificateType == CertificateTypeServerCertificate;

	// In Qt 5.15, a class was added to wrap up the procedures of generating a self-signed certificate.
	// See https://doc.qt.io/qt-5/qopcuax509certificatesigningrequest.html.
	// We should consider migrating to this class after switching to Qt 5.15.

	CHECK(pkey = generate_rsa_keypair());

	CHECK(x509 = X509_new());
	CHECK(X509_set_version(x509, 2));
	CHECK(serialNumber = X509_get_serialNumber(x509));
	CHECK(ASN1_INTEGER_set(serialNumber, 1));
	CHECK(notBefore = X509_get_notBefore(x509));
	CHECK(X509_gmtime_adj(notBefore, 0));
	CHECK(notAfter = X509_get_notAfter(x509));
	CHECK(X509_gmtime_adj(notAfter, 60 * 60 * 24 * 365 * 20))
	CHECK(X509_set_pubkey(x509, pkey));
	CHECK(name = X509_get_subject_name(x509));

	if (isServerCert) {
		commonName = QLatin1String("Murmur Autogenerated Certificate v2");
	} else {
		if (!clientCertName.isEmpty()) {
			commonName = clientCertName;
		} else {
			commonName = QLatin1String("Mumble User");
		}
	}

	CHECK(X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_UTF8,
									 reinterpret_cast< unsigned char * >(commonName.toUtf8().data()), -1, -1, 0));

	CHECK(X509_set_issuer_name(x509, name));

	CHECK(add_ext(x509, NID_basic_constraints, SSL_STRING("critical,CA:FALSE")));

	if (isServerCert) {
		CHECK(add_ext(x509, NID_ext_key_usage, SSL_STRING("serverAuth,clientAuth")))
	} else {
		CHECK(add_ext(x509, NID_ext_key_usage, SSL_STRING("clientAuth")));
	}

	CHECK(add_ext(x509, NID_subject_key_identifier, SSL_STRING("hash")));

	if (isServerCert) {
		CHECK(add_ext(x509, NID_netscape_comment, SSL_STRING("Generated from murmur")));
	} else {
		CHECK(add_ext(x509, NID_netscape_comment, SSL_STRING("Generated by Mumble")));
	}

	if (!isServerCert) {
		if (!clientCertEmail.trimmed().isEmpty()) {
			CHECK(add_ext(x509, NID_subject_alt_name,
						  QString::fromLatin1("email:%1").arg(clientCertEmail).toUtf8().data()));
		}
	}

	CHECK(X509_sign(x509, pkey, EVP_sha1()));

	{
		QByteArray crt;
		int len = i2d_X509(x509, nullptr);
		CHECK(len > 0);
		crt.resize(len);

		unsigned char *dptr = reinterpret_cast< unsigned char * >(crt.data());
		CHECK(i2d_X509(x509, &dptr) == len);

		qscCert = QSslCertificate(crt, QSsl::Der);
		CHECK(!qscCert.isNull());
	}

	{
		QByteArray key;
		int len = i2d_PrivateKey(pkey, nullptr);
		CHECK(len > 0);
		key.resize(len);

		unsigned char *dptr = reinterpret_cast< unsigned char * >(key.data());
		CHECK(i2d_PrivateKey(pkey, &dptr) == len);

		qskKey = QSslKey(key, QSsl::Rsa, QSsl::Der);
		CHECK(!qskKey.isNull());
	}

out:
	if (pkey) {
		EVP_PKEY_free(pkey);
	}
	if (x509) {
		X509_free(x509);
	}

	if (!ok) {
		qscCert = QSslCertificate();
		qskKey  = QSslKey();
	}

	return ok;
}

bool SelfSignedCertificate::generateMumbleCertificate(QString name, QString email, QSslCertificate &qscCert,
													  QSslKey &qskKey) {
	return SelfSignedCertificate::generate(CertificateTypeClientCertificate, name, email, qscCert, qskKey);
}

bool SelfSignedCertificate::generateMurmurV2Certificate(QSslCertificate &qscCert, QSslKey &qskKey) {
	return SelfSignedCertificate::generate(CertificateTypeServerCertificate, QString(), QString(), qscCert, qskKey);
}

#undef SSL_STRING
