/*
    Copyright (C) 2008-2024, Millistream Market Data <support@millistream.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "common.h"
#include "crypt.h"

/* The Millistream server's public RSA key */
static const char rsa_publickey[] = 
	"-----BEGIN RSA PUBLIC KEY-----\n"
	"MIICCgKCAgEArRXTTnI8lLeRPOkhYzvVT4AI2mrFbiIvPErWHFsDxKmlQB7qMOOt\n"
	"CGeA8sMRvUPo3XrRhUWibUfusqxHE+b2Bj+8HCxMoF9Ao8PCxudQrX/2ufvP6kY2\n"
	"ObgqpjmbWbUKUIm9l/UzmeS1qjQ5eKQKKreHHLFz9HHKLavFwkdPop8g+p3JlJXr\n"
	"pqiDkgdKl0+gD1s9ZY3aLib7eUGvCwcR5bXxajMlOzSt8TQWok1G5pWBSoJ1wHjg\n"
	"mY2/n0RF88ZK2Gl+zD7++hnnktTM2hTjTNQ0ULGKWmx1bcG45EHgx+w8E+0ERAfp\n"
	"wL4CNwVpb0VwzjX43R7OMepOP1PpkVaN1hqJmWLH22bsHPboV7t9IW3HTmfSnVt9\n"
	"xH4Zm2530Yl3LEP/HsjwpjQb5zA3bHaiOtQHDeCB9hASARIo7Mm6EE1uLwqM3EKH\n"
	"RveQABf+N6+bA13RmrXajhrMmT8uAgGQDh4MjU24cReSUfnS2lVGu3674RLqvgcf\n"
	"ZOZ5voPTYt6MkOCpn5KCw22Cp3x8IgyIE+q0S26aYXqY5OA5ZXHSOttUIYmkc6rY\n"
	"vq/YRKUNfxuVdgsIRh3f44IsBs4DPZ1nFmG8FbXDTAjGVw6P6LvUvNXjhF5CnxZg\n"
	"qX3LIvf/qEniPfZSrHJwWIXICAYVBOrQE0QpEAxHnB1HAWbH31SJQRECAwEAAQ==\n"
	"-----END RSA PUBLIC KEY-----\n";

#if OPENSSL_VERSION_PREREQ(3,0)
EVP_PKEY *mdf_int_load_rsapublickey ()
{
	EVP_PKEY *pkey = NULL;
	OSSL_DECODER_CTX *dctx = OSSL_DECODER_CTX_new_for_pkey (&pkey, "PEM", NULL, "RSA", EVP_PKEY_PUBLIC_KEY, NULL, NULL);

	if (dctx == NULL)
		return NULL;

	const unsigned char *data = (const unsigned char *) rsa_publickey;
	size_t datalen = sizeof (rsa_publickey);

	if (OSSL_DECODER_from_data (dctx, &data, &datalen) == 0) {
		OSSL_DECODER_CTX_free (dctx);
		EVP_PKEY_free (pkey);
		return NULL;
	}

	OSSL_DECODER_CTX_free (dctx);
	return pkey;
}
#else
RSA *mdf_int_load_rsapublickey ()
{
	RSA *rsa;
# if OPENSSL_VERSION_NUMBER < 0x1000207fL /* 1.0.2g */
	BIO *bio = BIO_new_mem_buf ((void *)rsa_publickey, -1);
# else
	BIO *bio = BIO_new_mem_buf (rsa_publickey, -1);
# endif
	rsa = PEM_read_bio_RSAPublicKey (bio, NULL, NULL, NULL);
	BIO_free_all (bio);

	return rsa;
}
#endif

int mdf_int_set_keys (struct crypto *crypto, const enum enc_or_dec enc_or_dec, unsigned char *nonce, int nonce_len)
{
	unsigned char md[EVP_MAX_MD_SIZE];
	memset (md, 0, sizeof md);
	memcpy (md, nonce, nonce_len);

	int rc = 1;

	EVP_MD_CTX *digest_ctx = EVP_MD_CTX_create ();

	/* first we generate the authentication (HMAC) key */
	if (EVP_DigestInit_ex (digest_ctx, crypto->digest, NULL) == 0)
		rc = 0;

	if (EVP_DigestUpdate (digest_ctx, crypto->master_secret, crypto->digest_length) == 0)
		rc = 0;

	if (EVP_DigestUpdate (digest_ctx, nonce, nonce_len) == 0)
		rc = 0;

	/* this string is added to the digest so that the encryption key and the authentication key
	 * will differ, that they are both known does not make any difference for the security of
	 * the system. If the attacker got his hands on the encryption key, he still has to perform a
	 * brute-force search on the master_key to get to the authentication key, and even if the
	 * attacker got both keys, he still has to brute-force to get the master_key. And since the
	 * master_key is random and the full length of the digest method used it's still as secure
	 * as the underlying digest method allows us */
	if (EVP_DigestUpdate (digest_ctx, "gv28ffES1nzPNy8YRxfvbo6UPJITTzr4S9FVJkFyW61u6OPDFDjk7y6jBKBatyb3", 64) == 0)
		rc = 0;

	if (EVP_DigestFinal_ex (digest_ctx, md, NULL) == 0)
		rc = 0;
#if OPENSSL_VERSION_PREREQ(3,0)
	OSSL_PARAM params[2];
	params[0] = OSSL_PARAM_construct_utf8_string (OSSL_MAC_PARAM_DIGEST, (char *)EVP_MD_get0_name (crypto->digest), 0);
	params[1] = OSSL_PARAM_construct_end ();

	if (enc_or_dec == DECRYPT)
		EVP_MAC_init (crypto->dec_mctx, md, crypto->digest_length, params);
	else
		EVP_MAC_init (crypto->enc_mctx, md, crypto->digest_length, params);

#else
	EVP_PKEY *pkey = EVP_PKEY_new_mac_key (EVP_PKEY_HMAC, NULL, md, crypto->digest_length);

	if (pkey == NULL)
		rc = 0;
#endif

	/* next we generate the encryption key */
	if (EVP_DigestInit_ex (digest_ctx, crypto->digest, NULL) == 0)
		rc = 0;

	if (EVP_DigestUpdate (digest_ctx, crypto->master_secret, crypto->digest_length) == 0)
		rc = 0;

	if (EVP_DigestUpdate (digest_ctx, nonce, nonce_len) == 0)
		rc = 0;

	/* this string is added to the digest so that the encryption key and the authentication key
	 * will differ, that they are both known does not make any difference for the security of
	 * the system. If the attacker got his hands on the encryption key, he still has to perform a
	 * brute-force search on the master_key to get to the authentication key, and even if the
	 * attacker got both keys, he still has to brute-force to get the master_key. And since the
	 * master_key is random and the full length of the digest method used it's still as secure
	 * as the underlying digest method allows us */
	if (EVP_DigestUpdate (digest_ctx, "C6INKrSnGoox4i7v9omJAvezRjDfBTG3s1nnzxzOnkJXIhOlhVE0ArmCHiWXSZVY", 64) == 0)
		rc = 0;

	if (EVP_DigestFinal_ex (digest_ctx, md, NULL) == 0)
		rc = 0;

	EVP_MD_CTX_free (digest_ctx);

	/* we use a zero iv since it only have to be unique per key and message, since the counter will
	 * increase with every message this is automatically handled by the cipher */
	unsigned char iv[EVP_MAX_IV_LENGTH];
	memset (iv, 0, sizeof iv);

	if (enc_or_dec == DECRYPT) {
		if (EVP_DecryptInit_ex (crypto->cipher_decrypt_ctx, NULL, NULL, md, iv) == 0)
			rc = 0;

#if OPENSSL_VERSION_PREREQ(3,0)
#else
		if (EVP_DigestSignInit (crypto->digest_decrypt_ctx, NULL, crypto->digest, NULL, pkey) == 0)
			rc = 0;
# ifdef EVP_MD_CTX_FLAG_FINALISE
		EVP_MD_CTX_set_flags (crypto->digest_decrypt_ctx, EVP_MD_CTX_FLAG_FINALISE);
# endif
#endif
		crypto->decrypt_cnt = 0;
	} else {
		if (EVP_EncryptInit_ex (crypto->cipher_encrypt_ctx, NULL, NULL, md, iv) == 0)
			rc = 0;

#if OPENSSL_VERSION_PREREQ(3,0)
#else
		if (EVP_DigestSignInit (crypto->digest_encrypt_ctx, NULL, crypto->digest, NULL, pkey) == 0)
			rc = 0;
# ifdef EVP_MD_CTX_FLAG_FINALISE
		EVP_MD_CTX_set_flags (crypto->digest_encrypt_ctx, EVP_MD_CTX_FLAG_FINALISE);
# endif
#endif
		crypto->encrypt_cnt = 0;
		crypto->encrypt_num = 0;
	}

#if OPENSSL_VERSION_PREREQ(3,0)
#else
	EVP_PKEY_free (pkey);
#endif
	return rc;
}

int mdf_int_handle_server_rekey (struct crypto *crypto, uint8_t *data, const size_t tlen)
{
	uint32_t val;
	memcpy (&val, data, sizeof val);
	size_t used = le32_to_cpu (val);

	/* it's not possible to turn encryption back on */
	if ((crypto->cstate & CSTATE_DECRYPT) == 0)
		return 1;

	/* simple buffer overrun protection */
	if (used > 1024*1024)
		return 0;

	size_t offset = sizeof (uint32_t);

	if (offset + used > tlen)
		return 0;

	/* this is the signal to disable encryption */
	if (used == 0) {
		crypto->cstate &= ~CSTATE_DECRYPT;

		EVP_CIPHER_CTX_free (crypto->cipher_decrypt_ctx);
		crypto->cipher_decrypt_ctx = NULL;

#if OPENSSL_VERSION_PREREQ(3,0)
		EVP_MAC_CTX_free (crypto->dec_mctx);
		crypto->dec_mctx = NULL;
#else
		EVP_MD_CTX_free (crypto->digest_decrypt_ctx);
		crypto->digest_decrypt_ctx = NULL;
#endif
		return 1;
	}

	if (used < (size_t) crypto->digest_length)
		return 0;

	return mdf_int_set_keys (crypto, DECRYPT, data + offset, used);
}

uint32_t mdf_int_check_digest (int i)
{
#if OPENSSL_VERSION_PREREQ(3,0)
	EVP_MD *md = EVP_MD_fetch (NULL, DIGESTS[i].name, NULL);
	if (md != NULL) {
		EVP_MD_free (md);
		return (1 << DIGESTS[i].id);
	}
#else
	const EVP_MD *md = EVP_get_digestbyname (DIGESTS[i].name);
	if (md != NULL)
		return (1 << DIGESTS[i].id);
#endif
	return 0;
}

uint32_t mdf_int_check_cipher (int i)
{
#if OPENSSL_VERSION_PREREQ(3,0)
	EVP_CIPHER *cipher = EVP_CIPHER_fetch (NULL, CIPHERS[i].name, NULL);
	if (cipher != NULL) {
		EVP_CIPHER_free (cipher);
		return (1 << CIPHERS[i].id);
	}
#else
	const EVP_CIPHER *cipher = EVP_get_cipherbyname (CIPHERS[i].name);
	if (cipher != NULL)
		return (1 << CIPHERS[i].id);
#endif
	return 0;
}

uint32_t mdf_int_get_default_digests ()
{
	uint32_t v = 0;
	for (int i = 0; i < NELEMS (DIGESTS); i++)
		v |= mdf_int_check_digest (i);

	return v;
}

uint32_t mdf_int_get_default_ciphers ()
{
	uint32_t v = 0;
	for (int i = 0; i < NELEMS (CIPHERS); i++)
		v |= mdf_int_check_cipher (i);

	return v;
}

int mdf_int_asn1_verify (EVP_PKEY_CTX *ctx, const unsigned char *sig, size_t siglen, const unsigned char *tbs, size_t tbslen, const int choosen_digest)
{
	unsigned char md[EVP_MAX_MD_SIZE + 21];
	int added_len;

	if (choosen_digest == 5) {
		memcpy (md, "\x30\x4e\x30\x0a\x06\x06\x28\xCF\x06\x03\x00\x37\x05\x00\x04\x40", 16);
		added_len = 16;
	} else if (choosen_digest == 10) {
		memcpy (md, "\x30\x2d\x30\x0a\x06\x06\x28\xCF\x06\x03\x00\x41\x05\x00\x04\x20", 16);
		added_len = 16;
	} else if (choosen_digest == 11) {
		memcpy (md, "\x30\x33\x30\x0f\x06\x0B\x2B\x06\x01\x04\x01\x8D\x3A\x0C\x02\x02\x08\x05\x00\x04\x20", 21);
		added_len = 21;
	} else if (choosen_digest == 12) {
		memcpy (md, "\x30\x53\x30\x0f\x06\x0B\x2B\x06\x01\x04\x01\x8D\x3A\x0C\x02\x01\x10\x05\x00\x04\x40", 21);
		added_len = 21;
	} else {
#ifdef __GNUC__
		__builtin_unreachable();
#endif
	}

	memcpy (md + added_len, tbs, tbslen);

	return EVP_PKEY_verify (ctx, sig, siglen, md, tbslen + added_len);
}

