322 lines
6.8 KiB
C++
322 lines
6.8 KiB
C++
/*
|
|
* This file is part of the Monero P2Pool <https://github.com/SChernykh/p2pool>
|
|
* Copyright (c) 2021-2025 SChernykh <https://github.com/SChernykh>
|
|
*
|
|
* 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, version 3.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "tls.h"
|
|
|
|
LOG_CATEGORY(TLS)
|
|
|
|
namespace p2pool {
|
|
|
|
static bssl::UniquePtr<EVP_PKEY> init_evp_pkey()
|
|
{
|
|
bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new());
|
|
|
|
if (!evp_pkey.get()) {
|
|
return nullptr;
|
|
}
|
|
|
|
bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
|
|
|
|
if (!ec_key || !EC_KEY_generate_key(ec_key.get())) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!EVP_PKEY_assign_EC_KEY(evp_pkey.get(), ec_key.release())) {
|
|
return nullptr;
|
|
}
|
|
|
|
//FILE* fp;
|
|
//if (fopen_s(&fp, "cert_key.pem", "wb") == 0) {
|
|
// PEM_write_PrivateKey(fp, evp_pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
|
|
// fclose(fp);
|
|
//}
|
|
|
|
return evp_pkey;
|
|
}
|
|
|
|
static bssl::UniquePtr<EVP_PKEY> s_evp_pkey = init_evp_pkey();
|
|
|
|
static bssl::UniquePtr<X509> init_cert()
|
|
{
|
|
bssl::UniquePtr<X509> x509(X509_new());
|
|
|
|
if (!x509.get()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!X509_set_version(x509.get(), X509_VERSION_3)) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::mt19937_64 rng(RandomDeviceSeed::instance);
|
|
rng.discard(10000);
|
|
|
|
const uint64_t serial = rng();
|
|
|
|
if (!ASN1_INTEGER_set_uint64(X509_get_serialNumber(x509.get()), serial)) {
|
|
return nullptr;
|
|
}
|
|
|
|
constexpr int64_t YEAR = 31557600;
|
|
|
|
const time_t cur_time = time(nullptr);
|
|
|
|
const time_t t0 = cur_time - (cur_time % YEAR);
|
|
const time_t t1 = t0 - YEAR * 10;
|
|
const time_t t2 = t0 + YEAR * 10;
|
|
|
|
if (!ASN1_TIME_set(X509_get_notBefore(x509.get()), t1) || !ASN1_TIME_set(X509_get_notAfter(x509.get()), t2)) {
|
|
return nullptr;
|
|
}
|
|
|
|
X509_NAME* subject = X509_get_subject_name(x509.get());
|
|
|
|
if (!X509_NAME_add_entry_by_txt(subject, "C", MBSTRING_ASC, reinterpret_cast<const uint8_t*>("US"), -1, -1, 0) ||
|
|
!X509_NAME_add_entry_by_txt(subject, "O", MBSTRING_ASC, reinterpret_cast<const uint8_t*>("BoringSSL"), -1, -1, 0) ||
|
|
!X509_set_issuer_name(x509.get(), subject)) {
|
|
return nullptr;
|
|
}
|
|
|
|
bssl::UniquePtr<STACK_OF(ASN1_OBJECT)> ekus(sk_ASN1_OBJECT_new_null());
|
|
|
|
if (!ekus || !sk_ASN1_OBJECT_push(ekus.get(), OBJ_nid2obj(NID_server_auth)) || !X509_add1_ext_i2d(x509.get(), NID_ext_key_usage, ekus.get(), 1, 0)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!X509_set_pubkey(x509.get(), s_evp_pkey.get())) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!X509_sign(x509.get(), s_evp_pkey.get(), EVP_sha256())) {
|
|
return nullptr;
|
|
}
|
|
|
|
//FILE* fp;
|
|
//if (fopen_s(&fp, "cert.pem", "wb") == 0) {
|
|
// PEM_write_X509(fp, x509.get());
|
|
// fclose(fp);
|
|
//}
|
|
|
|
return x509;
|
|
}
|
|
|
|
static bssl::UniquePtr<X509> s_cert = init_cert();
|
|
|
|
static bssl::UniquePtr<SSL_CTX> init_ctx()
|
|
{
|
|
if (!s_evp_pkey.get() || !s_cert.get()) {
|
|
return nullptr;
|
|
}
|
|
|
|
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
|
|
|
|
if (!ctx.get()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!SSL_CTX_use_PrivateKey(ctx.get(), s_evp_pkey.get())) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!SSL_CTX_use_certificate(ctx.get(), s_cert.get())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
static bssl::UniquePtr<SSL_CTX> s_ctx = init_ctx();
|
|
|
|
bool ServerTls::load_from_files(const char* cert, const char* cert_key)
|
|
{
|
|
if (!cert) {
|
|
LOGERR(0, "No cert file specified");
|
|
return false;
|
|
}
|
|
|
|
if (!cert_key) {
|
|
LOGERR(0, "No cert_key file specified");
|
|
return false;
|
|
}
|
|
|
|
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
|
|
|
|
if (!ctx.get()) {
|
|
LOGERR(0, "Failed to create SSL context");
|
|
return false;
|
|
}
|
|
|
|
if (SSL_CTX_use_certificate_chain_file(ctx.get(), cert) <= 0) {
|
|
LOGERR(0, "Failed to load " << cert);
|
|
return false;
|
|
}
|
|
|
|
if (SSL_CTX_use_PrivateKey_file(ctx.get(), cert_key, SSL_FILETYPE_PEM) <= 0) {
|
|
LOGERR(0, "Failed to load " << cert_key);
|
|
return false;
|
|
}
|
|
|
|
SSL_CTX_set_options(ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE);
|
|
|
|
LOGINFO(1, log::LightCyan() << "Loaded " << cert << ", " << cert_key);
|
|
|
|
s_ctx.reset(ctx.release());
|
|
return true;
|
|
}
|
|
|
|
void ServerTls::reset()
|
|
{
|
|
m_ssl.reset(nullptr);
|
|
}
|
|
|
|
bool ServerTls::init()
|
|
{
|
|
if (!s_ctx.get()) {
|
|
static std::atomic<uint32_t> ctx_error_shown = 0;
|
|
if (ctx_error_shown.exchange(1) == 0) {
|
|
LOGERR(0, "Failed to initialize an SSL context");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
m_ssl.reset(SSL_new(s_ctx.get()));
|
|
|
|
if (!m_ssl.get()) {
|
|
return false;
|
|
}
|
|
|
|
SSL_set_accept_state(m_ssl.get());
|
|
|
|
BIO* rbio = BIO_new(BIO_s_mem());
|
|
BIO* wbio = BIO_new(BIO_s_mem());
|
|
|
|
if (!rbio || !wbio) {
|
|
BIO_free(rbio);
|
|
BIO_free(wbio);
|
|
|
|
m_ssl.reset(nullptr);
|
|
return false;
|
|
}
|
|
|
|
SSL_set_bio(m_ssl.get(), rbio, wbio);
|
|
return true;
|
|
}
|
|
|
|
bool ServerTls::on_read_internal(const char* data, uint32_t size, ReadCallback::Base&& read_callback, WriteCallback::Base&& write_callback)
|
|
{
|
|
SSL* ssl = m_ssl.get();
|
|
if (!ssl) {
|
|
return false;
|
|
}
|
|
|
|
if (!BIO_write_all(SSL_get_rbio(ssl), data, size)) {
|
|
return false;
|
|
}
|
|
|
|
if (!SSL_is_init_finished(ssl)) {
|
|
const int result = SSL_do_handshake(ssl);
|
|
|
|
if (!result) {
|
|
// EOF
|
|
return false;
|
|
}
|
|
|
|
// Send pending handshake data, if any
|
|
BIO* wbio = SSL_get_wbio(ssl);
|
|
if (!wbio) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t* bio_data;
|
|
size_t bio_len;
|
|
|
|
if (!BIO_mem_contents(wbio, &bio_data, &bio_len)) {
|
|
return false;
|
|
}
|
|
|
|
if (bio_len > 0) {
|
|
if (!write_callback(bio_data, bio_len)) {
|
|
return false;
|
|
}
|
|
if (!BIO_reset(wbio)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((result < 0) && (SSL_get_error(ssl, result) == SSL_ERROR_WANT_READ)) {
|
|
// Continue handshake, nothing to read yet
|
|
return true;
|
|
}
|
|
else if (result == 1) {
|
|
// Handshake finished, skip to "SSL_read" further down
|
|
}
|
|
else {
|
|
// Some other error
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int bytes_read;
|
|
char buf[1024];
|
|
|
|
while ((bytes_read = SSL_read(ssl, buf, sizeof(buf))) > 0) {
|
|
if (!read_callback(buf, static_cast<uint32_t>(bytes_read))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ServerTls::on_write_internal(const uint8_t* data, size_t size, WriteCallback::Base&& write_callback)
|
|
{
|
|
SSL* ssl = m_ssl.get();
|
|
if (!ssl) {
|
|
return false;
|
|
}
|
|
|
|
if (SSL_write(ssl, data, static_cast<int>(size)) <= 0) {
|
|
return false;
|
|
}
|
|
|
|
BIO* wbio = SSL_get_wbio(ssl);
|
|
if (!wbio) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t* bio_data;
|
|
size_t bio_len;
|
|
|
|
if (!BIO_mem_contents(wbio, &bio_data, &bio_len)) {
|
|
return false;
|
|
}
|
|
|
|
if (bio_len > 0) {
|
|
if (!write_callback(bio_data, bio_len)) {
|
|
return false;
|
|
}
|
|
if (!BIO_reset(wbio)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace p2pool
|