/*
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/

#include <string.h>
#include <openssl/ssl.h>
#include "Crypto.h"
#include "FS.h"
#include "Log.h"
#include "Family.h"
#include "Config.h"

namespace i2p
{
namespace data
{
	Families::Families ()
	{
	}

	Families::~Families ()
	{
		for (auto it : m_SigningKeys)
			if (it.second.first) EVP_PKEY_free (it.second.first);
	}

	void Families::LoadCertificate (const std::string& filename)
	{
		SSL_CTX * ctx = SSL_CTX_new (TLS_method ());
		int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
		if (ret)
		{
			SSL * ssl = SSL_new (ctx);
			X509 * cert = SSL_get_certificate (ssl);
			if (cert)
			{
				std::shared_ptr<i2p::crypto::Verifier> verifier;
				// extract issuer name
				char name[100];
				X509_NAME_oneline (X509_get_issuer_name(cert), name, 100);
				char * cn = strstr (name, "CN=");
				if (cn)
				{
					cn += 3;
					char * family = strstr (cn, ".family");
					if (family) family[0] = 0;
					auto pkey = X509_get_pubkey (cert);
					if (pkey)
					{	
						if (!m_SigningKeys.emplace (cn, std::make_pair(pkey, (int)m_SigningKeys.size () + 1)).second)
						{
							EVP_PKEY_free (pkey);
							LogPrint (eLogError, "Family: Duplicated family name ", cn);
						}	
					}	
				}
			}
			SSL_free (ssl);
		}
		else
			LogPrint (eLogError, "Family: Can't open certificate file ", filename);
		SSL_CTX_free (ctx);
	}

	void Families::LoadCertificates ()
	{
		std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "family";

		std::vector<std::string> files;
		int numCertificates = 0;

		if (!i2p::fs::ReadDir(certDir, files)) {
			LogPrint(eLogWarning, "Family: Can't load family certificates from ", certDir);
			return;
		}

		for (const std::string & file : files) {
			if (file.compare(file.size() - 4, 4, ".crt") != 0) {
				LogPrint(eLogWarning, "Family: ignoring file ", file);
				continue;
			}
			LoadCertificate (file);
			numCertificates++;
		}
		LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded");
	}

	bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
		const char * signature, const char * key) const
	{
		uint8_t buf[100], signatureBuf[64];
		size_t len = family.length (), signatureLen = strlen (signature);
		if (len + 32 > 100)
		{
			LogPrint (eLogError, "Family: ", family, " is too long");
			return false;
		}
		auto it = m_SigningKeys.find (family);
		if (it != m_SigningKeys.end () && it->second.first)
		{	
			memcpy (buf, family.c_str (), len);
			memcpy (buf + len, (const uint8_t *)ident, 32);
			len += 32;
			auto signatureBufLen = Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
			if (signatureBufLen)
			{
				EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
				EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, it->second.first);
				auto ret = EVP_DigestVerify (ctx, signatureBuf, signatureBufLen, buf, len);
				EVP_MD_CTX_destroy (ctx);
				return ret;
			}	
		}	
		// TODO: process key
		return true;
	}

	FamilyID Families::GetFamilyID (const std::string& family) const
	{
		auto it = m_SigningKeys.find (family);
		if (it != m_SigningKeys.end ())
			return it->second.second;
		return 0;
	}

	std::string CreateFamilySignature (const std::string& family, const IdentHash& ident)
	{
		auto filename = i2p::fs::DataDirPath("family", (family + ".key"));
		std::string sig;
		SSL_CTX * ctx = SSL_CTX_new (TLS_method ());
		int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
		if (ret)
		{
			SSL * ssl = SSL_new (ctx);
			EVP_PKEY * pkey = SSL_get_privatekey (ssl);
			EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
			if (ecKey)
			{
				auto group = EC_KEY_get0_group (ecKey);
				if (group)
				{
					int curve = EC_GROUP_get_curve_name (group);
					if (curve == NID_X9_62_prime256v1)
					{
						uint8_t signingPrivateKey[32], buf[50], signature[64];
						i2p::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32);
						i2p::crypto::ECDSAP256Signer signer (signingPrivateKey);
						size_t len = family.length ();
						memcpy (buf, family.c_str (), len);
						memcpy (buf + len, (const uint8_t *)ident, 32);
						len += 32;
						signer.Sign (buf, len, signature);
						len = Base64EncodingBufferSize (64);
						char * b64 = new char[len+1];
						len = ByteStreamToBase64 (signature, 64, b64, len);
						b64[len] = 0;
						sig = b64;
						delete[] b64;
					}
					else
						LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
				}
			}
			SSL_free (ssl);
		}
		else
			LogPrint (eLogError, "Family: Can't open keys file: ", filename);
		SSL_CTX_free (ctx);
		return sig;
	}
}
}
