/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 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 General Public License along
 * with this program. If not, see .
 */
#include "RSA.h"
#include "HMAC.h"
#include 
#include 
#include 
#include 
#include 
namespace
{
struct BIODeleter
{
    void operator()(BIO* bio)
    {
        BIO_free(bio);
    }
};
struct HMAC_SHA256_MD
{
    struct CTX_DATA
    {
        Trinity::Crypto::HMAC_SHA256* hmac;
    };
#if TRINITY_COMPILER == TRINITY_COMPILER_GNU
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#else
#pragma warning(push)
#pragma warning(disable: 4996)
#endif
    HMAC_SHA256_MD()
    {
        _md = EVP_MD_meth_new(NID_sha256, NID_sha256WithRSAEncryption);
        EVP_MD_meth_set_result_size(_md, Trinity::Crypto::Constants::SHA256_DIGEST_LENGTH_BYTES);
        EVP_MD_meth_set_flags(_md, EVP_MD_FLAG_DIGALGID_ABSENT);
        EVP_MD_meth_set_init(_md, &Init);
        EVP_MD_meth_set_update(_md, &UpdateData);
        EVP_MD_meth_set_final(_md, &Finalize);
        EVP_MD_meth_set_copy(_md, &Copy);
        EVP_MD_meth_set_cleanup(_md, &Cleanup);
        EVP_MD_meth_set_input_blocksize(_md, SHA256_CBLOCK);
        EVP_MD_meth_set_app_datasize(_md, sizeof(EVP_MD*) + sizeof(CTX_DATA*));
    }
    HMAC_SHA256_MD(HMAC_SHA256_MD const&) = delete;
    HMAC_SHA256_MD(HMAC_SHA256_MD&&) = delete;
    HMAC_SHA256_MD& operator=(HMAC_SHA256_MD const&) = delete;
    HMAC_SHA256_MD& operator=(HMAC_SHA256_MD&&) = delete;
    ~HMAC_SHA256_MD()
    {
        EVP_MD_meth_free(_md);
        _md = nullptr;
    }
#if TRINITY_COMPILER == TRINITY_COMPILER_GNU
#pragma GCC diagnostic pop
#else
#pragma warning(pop)
#endif
    operator EVP_MD const* () const
    {
        return _md;
    }
    static int Init(EVP_MD_CTX* ctx)
    {
        Cleanup(ctx);
        return 1;
    }
    static int UpdateData(EVP_MD_CTX* ctx, const void* data, size_t count)
    {
        CTX_DATA* ctxData = reinterpret_cast(EVP_MD_CTX_md_data(ctx));
        if (!ctxData->hmac)
            return 0;
        ctxData->hmac->UpdateData(reinterpret_cast(data), count);
        return 1;
    }
    static int Finalize(EVP_MD_CTX* ctx, unsigned char* md)
    {
        CTX_DATA* ctxData = reinterpret_cast(EVP_MD_CTX_md_data(ctx));
        if (!ctxData->hmac)
            return 0;
        ctxData->hmac->Finalize();
        memcpy(md, ctxData->hmac->GetDigest().data(), ctxData->hmac->GetDigest().size());
        return 1;
    }
    // post-processing after openssl memcpys from source to dest (no need to cleanup dest)
    static int Copy(EVP_MD_CTX* to, EVP_MD_CTX const* from)
    {
        CTX_DATA const* ctxDataFrom = reinterpret_cast(EVP_MD_CTX_md_data(from));
        CTX_DATA* ctxDataTo = reinterpret_cast(EVP_MD_CTX_md_data(to));
        if (ctxDataFrom->hmac)
            ctxDataTo->hmac = new Trinity::Crypto::HMAC_SHA256(*ctxDataFrom->hmac);
        return 1;
    }
    static int Cleanup(EVP_MD_CTX* ctx)
    {
        CTX_DATA* data = reinterpret_cast(EVP_MD_CTX_md_data(ctx));
        if (data->hmac)
        {
            delete data->hmac;
            data->hmac = nullptr;
        }
        return 1;
    }
private:
    EVP_MD* _md;
} HmacSha256Md;
}
namespace Trinity::Crypto
{
EVP_MD const* RsaSignature::SHA256::GetGenerator() const
{
    return EVP_sha256();
}
void RsaSignature::SHA256::PostInitCustomizeContext(EVP_MD_CTX*)
{
}
EVP_MD const* RsaSignature::HMAC_SHA256::GetGenerator() const
{
    return HmacSha256Md;
}
void RsaSignature::HMAC_SHA256::PostInitCustomizeContext(EVP_MD_CTX* ctx)
{
    HMAC_SHA256_MD::CTX_DATA* ctxData = reinterpret_cast(EVP_MD_CTX_md_data(ctx));
    delete ctxData->hmac;
    ctxData->hmac = new Crypto::HMAC_SHA256(_key, _keyLength);
}
RsaSignature::RsaSignature() : _ctx(Impl::GenericHashImpl::MakeCTX())
{
}
RsaSignature::RsaSignature(RsaSignature const& other) : _ctx(Impl::GenericHashImpl::MakeCTX())
{
    *this = other;
}
RsaSignature::RsaSignature(RsaSignature&& other) noexcept
{
    *this = std::move(other);
}
RsaSignature::~RsaSignature()
{
    EVP_MD_CTX_free(_ctx);
    EVP_PKEY_free(_key);
}
RsaSignature& RsaSignature::operator=(RsaSignature const& right)
{
    if (this == &right)
        return *this;
    EVP_MD_CTX_copy_ex(_ctx, right._ctx);   // Allowed to fail if not yet initialized
    _key = right._key;                      // EVP_PKEY uses reference counting internally, just copy the pointer
    EVP_PKEY_up_ref(_key);                  // Bump reference count for PKEY, as every instance of this class holds two references to PKEY and destructor decrements it twice
    return *this;
}
RsaSignature& RsaSignature::operator=(RsaSignature&& right) noexcept
{
    if (this == &right)
        return *this;
    _ctx = std::exchange(right._ctx, Impl::GenericHashImpl::MakeCTX());
    _key = std::exchange(right._key, EVP_PKEY_new());
    return *this;
}
bool RsaSignature::LoadKeyFromFile(std::string const& fileName)
{
    if (_key)
    {
        EVP_PKEY_free(_key);
        _key = nullptr;
    }
    std::unique_ptr keyBIO(BIO_new_file(fileName.c_str(), "r"));
    if (!keyBIO)
        return false;
    _key = EVP_PKEY_new();
    if (!PEM_read_bio_PrivateKey(keyBIO.get(), &_key, nullptr, nullptr))
        return false;
    return true;
}
bool RsaSignature::LoadKeyFromString(std::string const& keyPem)
{
    if (_key)
    {
        EVP_PKEY_free(_key);
        _key = nullptr;
    }
    std::unique_ptr keyBIO(BIO_new_mem_buf(
        const_cast(keyPem.c_str()) /*api hack - this function assumes memory is readonly but lacks const modifier*/,
        keyPem.length() + 1));
    if (!keyBIO)
        return false;
    _key = EVP_PKEY_new();
    if (!PEM_read_bio_PrivateKey(keyBIO.get(), &_key, nullptr, nullptr))
        return false;
    return true;
}
bool RsaSignature::Sign(uint8 const* message, std::size_t messageLength, DigestGenerator& generator, std::vector& output)
{
    size_t signatureLength = 0;
    EVP_DigestSignInit(_ctx, nullptr, generator.GetGenerator(), nullptr, _key);
    generator.PostInitCustomizeContext(_ctx);
    EVP_DigestSignUpdate(_ctx, message, messageLength);
    int result = EVP_DigestSignFinal(_ctx, nullptr, &signatureLength);
    if (result == 0)
        return false;
    output.resize(signatureLength);
    result = EVP_DigestSignFinal(_ctx, output.data(), &signatureLength);
    std::reverse(output.begin(), output.end());
    return result != 0;
}
}