/*
* Copyright (c) 2013-2023, 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/evp.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 ()
	{
	}

	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);
				int keyType = EVP_PKEY_base_id (pkey);
				switch (keyType)
				{
					case EVP_PKEY_DSA:
						// TODO:
					break;
					case EVP_PKEY_EC:
					{
						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 signingKey[64];
									BIGNUM * x = BN_new(), * y = BN_new();
									EC_POINT_get_affine_coordinates_GFp (group,
										EC_KEY_get0_public_key (ecKey), x, y, NULL);
									i2p::crypto::bn2buf (x, signingKey, 32);
									i2p::crypto::bn2buf (y, signingKey + 32, 32);
									BN_free (x); BN_free (y);
									verifier = std::make_shared<i2p::crypto::ECDSAP256Verifier>();
									verifier->SetPublicKey (signingKey);
								}
								else
									LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
							}
							EC_KEY_free (ecKey);
						}
						break;
					}
					default:
						LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported");
				}
				EVP_PKEY_free (pkey);
				if (verifier && cn)
					m_SigningKeys.emplace (cn, std::make_pair(verifier, (int)m_SigningKeys.size () + 1));
			}
			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;
		}

		memcpy (buf, family.c_str (), len);
		memcpy (buf + len, (const uint8_t *)ident, 32);
		len += 32;
		Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
		auto it = m_SigningKeys.find (family);
		if (it != m_SigningKeys.end ())
			return it->second.first->Verify (buf, len, signatureBuf);
		// 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;
	}
}
}