//------------------------------------------------------------------------------
/*
    This file is part of rippled: https://github.com/ripple/rippled
    Copyright (c) 2012, 2013 Ripple Labs Inc.

    Permission to use, copy, modify, and/or distribute this software for any
    purpose  with  or without fee is hereby granted, provided that the above
    copyright notice and this permission notice appear in all copies.

    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#include <ripple/basics/chrono.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/make_SSLContext.h>
#include <ripple/beast/container/aged_unordered_set.h>
#include <cstdint>
#include <sstream>
#include <stdexcept>

namespace ripple {
namespace openssl {
namespace detail {

// We limit the ciphers we request and allow to ensure that weak
// ciphers aren't used. While this isn't strictly necessary for
// the rippled server-server use case, where we only need MITM
// detection/prevention, we also have websocket and rpc scenarios
// and want to ensure weak ciphers can't be used.
std::string const defaultCipherList =
    "HIGH:MEDIUM:!aNULL:!MD5:!DSS:!3DES:!RC4:!EXPORT";

template <class>
struct custom_delete;

template <>
struct custom_delete <RSA>
{
    explicit custom_delete() = default;

    void operator() (RSA* rsa) const
    {
        RSA_free (rsa);
    }
};

template <>
struct custom_delete <EVP_PKEY>
{
    explicit custom_delete() = default;

    void operator() (EVP_PKEY* evp_pkey) const
    {
        EVP_PKEY_free (evp_pkey);
    }
};

template <>
struct custom_delete <X509>
{
    explicit custom_delete() = default;

    void operator() (X509* x509) const
    {
        X509_free (x509);
    }
};

template <>
struct custom_delete <DH>
{
    explicit custom_delete() = default;

    void operator() (DH* dh) const
    {
        DH_free (dh);
    }
};

template <class T>
using custom_delete_unique_ptr = std::unique_ptr <T, custom_delete <T>>;

// RSA

using rsa_ptr = custom_delete_unique_ptr <RSA>;

static rsa_ptr rsa_generate_key (int n_bits)
{
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
    BIGNUM *bn = BN_new();
    BN_set_word(bn, RSA_F4);

    RSA* rsa = RSA_new();
    if (RSA_generate_key_ex(rsa, n_bits, bn, nullptr) != 1)
    {
        RSA_free(rsa);
        rsa = nullptr;
    }

    BN_free(bn);
#else
    RSA* rsa = RSA_generate_key (n_bits, RSA_F4, nullptr, nullptr);
#endif

    if (rsa == nullptr)
        LogicError ("RSA_generate_key failed");

    return rsa_ptr (rsa);
}

// EVP_PKEY

using evp_pkey_ptr = custom_delete_unique_ptr <EVP_PKEY>;

static evp_pkey_ptr evp_pkey_new()
{
    EVP_PKEY* evp_pkey = EVP_PKEY_new();

    if (evp_pkey == nullptr)
        LogicError ("EVP_PKEY_new failed");

    return evp_pkey_ptr (evp_pkey);
}

static void evp_pkey_assign_rsa (EVP_PKEY* evp_pkey, rsa_ptr rsa)
{
    if (! EVP_PKEY_assign_RSA (evp_pkey, rsa.get()))
        LogicError ("EVP_PKEY_assign_RSA failed");

    rsa.release();
}

// X509

using x509_ptr = custom_delete_unique_ptr <X509>;

static x509_ptr x509_new()
{
    X509* x509 = X509_new();

    if (x509 == nullptr)
        LogicError ("X509_new failed");

    X509_set_version (x509, NID_X509);

    int const margin =                    60 * 60;  //      3600, one hour
    int const length = 10 * 365.25 * 24 * 60 * 60;  // 315576000, ten years

    X509_gmtime_adj (X509_get_notBefore (x509), -margin);
    X509_gmtime_adj (X509_get_notAfter  (x509),  length);

    return x509_ptr (x509);
}

static void x509_set_pubkey (X509* x509, EVP_PKEY* evp_pkey)
{
    X509_set_pubkey (x509, evp_pkey);
}

static void x509_sign (X509* x509, EVP_PKEY* evp_pkey)
{
    if (! X509_sign (x509, evp_pkey, EVP_sha1()))
        LogicError ("X509_sign failed");
}

static void ssl_ctx_use_certificate (SSL_CTX* const ctx, x509_ptr cert)
{
    if (SSL_CTX_use_certificate (ctx, cert.get()) <= 0)
        LogicError ("SSL_CTX_use_certificate failed");
}

static void ssl_ctx_use_privatekey (SSL_CTX* const ctx, evp_pkey_ptr key)
{
    if (SSL_CTX_use_PrivateKey (ctx, key.get()) <= 0)
        LogicError ("SSL_CTX_use_PrivateKey failed");
}

#ifdef SSL_FLAGS_NO_RENEGOTIATE_CIPHERS
static
bool
disallowRenegotiation (SSL const* ssl, bool isNew)
{
    // Track when SSL connections have last negotiated and
    // do not allow a connection to renegotiate more than
    // once every 4 minutes
    struct StaticData
    {
        std::mutex lock;
        beast::aged_unordered_set <SSL const*> set;

        StaticData()
            : set (ripple::stopwatch())
        {
        }
    };

    static StaticData sd;
    std::lock_guard <std::mutex> lock (sd.lock);
    auto const expired (sd.set.clock().now() - std::chrono::minutes(4));

    // Remove expired entries
    for (auto iter (sd.set.chronological.begin ());
        (iter != sd.set.chronological.end ()) && (iter.when () <= expired);
        iter = sd.set.chronological.begin ())
    {
        sd.set.erase (iter);
    }

    auto iter = sd.set.find (ssl);
    if (iter != sd.set.end ())
    {
        if (! isNew)
        {
            // This is a renegotiation and the last negotiation was recent
            return true;
        }

        sd.set.touch (iter);
    }
    else
    {
        sd.set.emplace (ssl);
    }

    return false;
}

static
void
info_handler (SSL const* ssl, int event, int)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    if ((ssl->s3) && (event & SSL_CB_HANDSHAKE_START))
    {
        if (disallowRenegotiation (ssl, SSL_in_before (ssl)))
            ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
    }
#else
    // empty, flag removed in OpenSSL 1.1
#endif
}
#endif

static
std::string
error_message (std::string const& what,
    boost::system::error_code const& ec)
{
    std::stringstream ss;
    ss <<
        what << ": " <<
        ec.message() <<
        " (" << ec.value() << ")";
    return ss.str();
}

static
void
initAnonymous (
    boost::asio::ssl::context& context)
{
    using namespace openssl;

    evp_pkey_ptr pkey = evp_pkey_new();
    evp_pkey_assign_rsa (pkey.get(), rsa_generate_key (2048));

    x509_ptr cert = x509_new();
    x509_set_pubkey (cert.get(), pkey.get());
    x509_sign       (cert.get(), pkey.get());

    SSL_CTX* const ctx = context.native_handle();
    ssl_ctx_use_certificate (ctx, std::move(cert));
    ssl_ctx_use_privatekey  (ctx, std::move(pkey));
}

static
void
initAuthenticated (
    boost::asio::ssl::context& context,
    std::string const& key_file,
    std::string const& cert_file,
    std::string const& chain_file)
{
    SSL_CTX* const ssl = context.native_handle ();

    bool cert_set = false;

    if (! cert_file.empty ())
    {
        boost::system::error_code ec;

        context.use_certificate_file (
            cert_file, boost::asio::ssl::context::pem, ec);

        if (ec)
        {
            LogicError (error_message (
                "Problem with SSL certificate file.", ec).c_str());
        }

        cert_set = true;
    }

    if (! chain_file.empty ())
    {
        // VFALCO Replace fopen() with RAII
        FILE* f = fopen (chain_file.c_str (), "r");

        if (!f)
        {
            LogicError (error_message (
                "Problem opening SSL chain file.", boost::system::error_code (errno,
                boost::system::generic_category())).c_str());
        }

        try
        {
            for (;;)
            {
                X509* const x = PEM_read_X509 (f, nullptr, nullptr, nullptr);

                if (x == nullptr)
                    break;

                if (! cert_set)
                {
                    if (SSL_CTX_use_certificate (ssl, x) != 1)
                        LogicError ("Problem retrieving SSL certificate from chain file.");

                    cert_set = true;
                }
                else if (SSL_CTX_add_extra_chain_cert (ssl, x) != 1)
                {
                    X509_free (x);
                    LogicError ("Problem adding SSL chain certificate.");
                }
            }

            fclose (f);
        }
        catch (std::exception const&)
        {
            fclose (f);
            LogicError ("Reading the SSL chain file generated an exception.");
        }
    }

    if (! key_file.empty ())
    {
        boost::system::error_code ec;

        context.use_private_key_file (key_file,
            boost::asio::ssl::context::pem, ec);

        if (ec)
        {
            LogicError (error_message (
                "Problem using the SSL private key file.", ec).c_str());
        }
    }

    if (SSL_CTX_check_private_key (ssl) != 1)
    {
        LogicError ("Invalid key in SSL private key file.");
    }
}

std::shared_ptr<boost::asio::ssl::context>
get_context (std::string const& cipherList)
{
    auto c = std::make_shared<boost::asio::ssl::context> (
        boost::asio::ssl::context::sslv23);

    c->set_options (
        boost::asio::ssl::context::default_workarounds |
        boost::asio::ssl::context::no_sslv2 |
        boost::asio::ssl::context::no_sslv3 |
        boost::asio::ssl::context::single_dh_use);

    {
        auto const& l = !cipherList.empty() ? cipherList : defaultCipherList;
        auto result = SSL_CTX_set_cipher_list (
            c->native_handle (), l.c_str());
        if (result != 1)
            LogicError ("SSL_CTX_set_cipher_list failed");
    }

    // These are the raw DH parameters that Ripple Labs has
    // chosen for Ripple, in the binary format needed by
    // d2i_DHparams.
    //
    unsigned char const params[] =
    {
        0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0x8f, 0xca, 0x66,
        0x85, 0x33, 0xcb, 0xcf, 0x36, 0x27, 0xb2, 0x4c, 0xb8, 0x50, 0xb8, 0xf9,
        0x53, 0xf8, 0xb9, 0x2d, 0x1c, 0xa2, 0xad, 0x86, 0x58, 0x29, 0x3b, 0x88,
        0x3e, 0xf5, 0x65, 0xb8, 0xda, 0x22, 0xf4, 0x8b, 0x21, 0x12, 0x18, 0xf7,
        0x16, 0xcd, 0x7c, 0xc7, 0x3a, 0x2d, 0x61, 0xb7, 0x11, 0xf6, 0xb0, 0x65,
        0xa0, 0x5b, 0xa4, 0x06, 0x95, 0x28, 0xa4, 0x4f, 0x76, 0xc0, 0xeb, 0xfa,
        0x95, 0xdf, 0xbf, 0x19, 0x90, 0x64, 0x8f, 0x60, 0xd5, 0x36, 0xba, 0xab,
        0x0d, 0x5a, 0x5c, 0x94, 0xd5, 0xf7, 0x32, 0xd6, 0x2a, 0x76, 0x77, 0x83,
        0x10, 0xc4, 0x2f, 0x10, 0x96, 0x3e, 0x37, 0x84, 0x45, 0x9c, 0xef, 0x33,
        0xf6, 0xd0, 0x2a, 0xa7, 0xce, 0x0a, 0xce, 0x0d, 0xa1, 0xa7, 0x44, 0x5d,
        0x18, 0x3f, 0x4f, 0xa4, 0x23, 0x9c, 0x5d, 0x74, 0x4f, 0xee, 0xdf, 0xaa,
        0x0d, 0x0a, 0x52, 0x57, 0x73, 0xb1, 0xe4, 0xc5, 0x72, 0x93, 0x9d, 0x03,
        0xe9, 0xf5, 0x48, 0x8c, 0xd1, 0xe6, 0x7c, 0x21, 0x65, 0x4e, 0x16, 0x51,
        0xa3, 0x16, 0x51, 0x10, 0x75, 0x60, 0x37, 0x93, 0xb8, 0x15, 0xd6, 0x14,
        0x41, 0x4a, 0x61, 0xc9, 0x1a, 0x4e, 0x9f, 0x38, 0xd8, 0x2c, 0xa5, 0x31,
        0xe1, 0x87, 0xda, 0x1f, 0xa4, 0x31, 0xa2, 0xa4, 0x42, 0x1e, 0xe0, 0x30,
        0xea, 0x2f, 0x9b, 0x77, 0x91, 0x59, 0x3e, 0xd5, 0xd0, 0xc5, 0x84, 0x45,
        0x17, 0x19, 0x74, 0x8b, 0x18, 0xb0, 0xc1, 0xe0, 0xfc, 0x1c, 0xaf, 0xe6,
        0x2a, 0xef, 0x4e, 0x0e, 0x8a, 0x5c, 0xc2, 0x91, 0xb9, 0x2b, 0xf8, 0x17,
        0x8d, 0xed, 0x44, 0xaa, 0x47, 0xaa, 0x52, 0xa2, 0xdb, 0xb6, 0xf5, 0xa1,
        0x88, 0x85, 0xa1, 0xd5, 0x87, 0xb8, 0x07, 0xd3, 0x97, 0xbe, 0x37, 0x74,
        0x72, 0xf1, 0xa8, 0x29, 0xf1, 0xa7, 0x7d, 0x19, 0xc3, 0x27, 0x09, 0xcf,
        0x23, 0x02, 0x01, 0x02
    };

    unsigned char const *data = &params[0];

    custom_delete_unique_ptr<DH> const dh {d2i_DHparams (nullptr, &data, sizeof(params))};
    if (!dh)
        LogicError ("d2i_DHparams returned nullptr.");

    SSL_CTX_set_tmp_dh (c->native_handle (), dh.get());

#ifdef SSL_FLAGS_NO_RENEGOTIATE_CIPHERS
    SSL_CTX_set_info_callback (c->native_handle (), info_handler);
#endif

    return c;
}

} // detail
} // openssl

//------------------------------------------------------------------------------
std::shared_ptr<boost::asio::ssl::context>
make_SSLContext(std::string const& cipherList)
{
    auto context = openssl::detail::get_context(cipherList);
    openssl::detail::initAnonymous(*context);
    // VFALCO NOTE, It seems the WebSocket context never has
    // set_verify_mode called, for either setting of WEBSOCKET_SECURE
    context->set_verify_mode(boost::asio::ssl::verify_none);
    return context;
}

std::shared_ptr<boost::asio::ssl::context>
make_SSLContextAuthed (
    std::string const& keyFile,
    std::string const& certFile,
    std::string const& chainFile,
    std::string const& cipherList)
{
    auto context = openssl::detail::get_context(cipherList);
    openssl::detail::initAuthenticated(*context,
        keyFile, certFile, chainFile);
    return context;
}

} // ripple
