Skip to content

Commit

Permalink
Fix: excessive CPU usage (#400)
Browse files Browse the repository at this point in the history
- fixed excessive CPU usage by decreasing cert.pem certificate loading for TLS connections
- refactoring: using more safe std::string instead of CString
  • Loading branch information
dnzbk authored Sep 30, 2024
1 parent 76e2f8a commit 347dce3
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 54 deletions.
5 changes: 3 additions & 2 deletions daemon/connect/Connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*
* Copyright (C) 2004 Sven Henkel <[email protected]>
* Copyright (C) 2007-2019 Andrey Prygunkov <[email protected]>
* Copyright (C) 2024 Denis <[email protected]>
*
* 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
Expand All @@ -15,7 +16,7 @@
* 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/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/


Expand Down Expand Up @@ -715,7 +716,7 @@ bool Connection::DoConnect()
}

#ifndef DISABLE_TLS
if (m_tls && !StartTls(true, nullptr, nullptr))
if (m_tls && !StartTls(true, "", ""))
{
return false;
}
Expand Down
112 changes: 80 additions & 32 deletions daemon/connect/TlsSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* This file is part of nzbget. See <https://nzbget.com>.
*
* Copyright (C) 2008-2017 Andrey Prygunkov <[email protected]>
* Copyright (C) 2024 Denis <[email protected]>
*
* 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
Expand All @@ -14,7 +15,7 @@
* 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/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/


Expand All @@ -29,7 +30,11 @@
#include "FileSystem.h"
#include "Options.h"

CString TlsSocket::m_certStore;
std::string TlsSocket::m_certStore;

#ifdef HAVE_OPENSSL
X509_STORE* TlsSocket::m_X509Store = nullptr;
#endif

#ifdef HAVE_LIBGNUTLS
#ifdef NEED_GCRYPT_LOCKING
Expand Down Expand Up @@ -182,13 +187,54 @@ void TlsSocket::Init()
#endif /* HAVE_OPENSSL */
}

void TlsSocket::InitOptions(const char* certStore)
{
m_certStore = certStore;

#ifdef HAVE_OPENSSL
InitX509Store(m_certStore);
#endif
}

#ifdef HAVE_OPENSSL
void TlsSocket::InitX509Store(const std::string& certStore)
{
if (m_X509Store)
{
FreeX509Store(m_X509Store);
}

m_X509Store = X509_STORE_new();
if (!m_X509Store)
{
error("Could not create certificate store");
return;
}

if (!X509_STORE_load_locations(m_X509Store, certStore.c_str(), nullptr))
{
FreeX509Store(m_X509Store);
error("Could not load certificate store location");
return;
}
}

void TlsSocket::FreeX509Store(X509_STORE* store)
{
X509_STORE_free(m_X509Store);
m_X509Store = nullptr;
}
#endif

void TlsSocket::Final()
{
#ifdef HAVE_LIBGNUTLS
gnutls_global_deinit();
#endif /* HAVE_LIBGNUTLS */

#ifdef HAVE_OPENSSL
X509_STORE_free(m_X509Store);

#ifndef LIBRESSL_VERSION_NUMBER
#if OPENSSL_VERSION_NUMBER < 0x30000000L
FIPS_mode_set(0);
Expand Down Expand Up @@ -287,10 +333,10 @@ bool TlsSocket::Start()

m_context = cred;

if (m_certFile && m_keyFile)
if (!m_certFile.empty() && !m_keyFile.empty())
{
m_retCode = gnutls_certificate_set_x509_key_file((gnutls_certificate_credentials_t)m_context,
m_certFile, m_keyFile, GNUTLS_X509_FMT_PEM);
m_certFile.c_str(), m_keyFile.c_str(), GNUTLS_X509_FMT_PEM);
if (m_retCode != 0)
{
ReportError("Could not load certificate or key file", false);
Expand All @@ -312,8 +358,8 @@ bool TlsSocket::Start()

m_initialized = true;

const char* priority = !m_cipher.Empty() ? m_cipher.Str() :
(m_certFile && m_keyFile ? "NORMAL:!VERS-SSL3.0" : "NORMAL");
const char* priority = !m_cipher.empty() ? m_cipher.c_str() :
(!m_certFile.empty() && !m_keyFile.empty() ? "NORMAL:!VERS-SSL3.0" : "NORMAL");

m_retCode = gnutls_priority_set_direct((gnutls_session_t)m_session, priority, nullptr);
if (m_retCode != 0)
Expand All @@ -323,9 +369,9 @@ bool TlsSocket::Start()
return false;
}

if (m_host)
if (!m_host.empty())
{
m_retCode = gnutls_server_name_set((gnutls_session_t)m_session, GNUTLS_NAME_DNS, m_host, m_host.Length());
m_retCode = gnutls_server_name_set((gnutls_session_t)m_session, GNUTLS_NAME_DNS, m_host.c_str(), m_host.size());
if (m_retCode != 0)
{
ReportError("Could not set hostname for TLS");
Expand All @@ -348,12 +394,12 @@ bool TlsSocket::Start()
m_retCode = gnutls_handshake((gnutls_session_t)m_session);
if (m_retCode != 0)
{
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
ReportError(BString<1024>("TLS handshake failed for %s", m_host.c_str()));
Close();
return false;
}

if (m_isClient && !m_certStore.Empty() && !ValidateCert())
if (m_isClient && !m_certStore.empty() && !ValidateCert())
{
Close();
return false;
Expand All @@ -372,15 +418,15 @@ bool TlsSocket::Start()
return false;
}

if (m_certFile && m_keyFile)
if (!m_certFile.empty() && !m_keyFile.empty())
{
if (SSL_CTX_use_certificate_chain_file((SSL_CTX*)m_context, m_certFile) != 1)
if (SSL_CTX_use_certificate_chain_file((SSL_CTX*)m_context, m_certFile.c_str()) != 1)
{
ReportError("Could not load certificate file", false);
Close();
return false;
}
if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_context, m_keyFile, SSL_FILETYPE_PEM) != 1)
if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_context, m_keyFile.c_str(), SSL_FILETYPE_PEM) != 1)
{
ReportError("Could not load key file", false);
Close();
Expand Down Expand Up @@ -411,15 +457,17 @@ bool TlsSocket::Start()
EC_KEY_free(ecdh);
}

if (m_isClient && !m_certStore.Empty())
if (m_isClient && !m_certStore.empty())
{
// Enable certificate validation
if (SSL_CTX_load_verify_locations((SSL_CTX*)m_context, m_certStore, nullptr) != 1)
if (!m_X509Store)
{
ReportError("Could not set certificate store location", false);
error("Could not set certificate store location. Make sure the CertPath option is correct.");
Close();
return false;
}

SSL_CTX_set1_cert_store((SSL_CTX*)m_context, m_X509Store);

if (m_certVerifLevel > Options::ECertVerifLevel::cvNone)
{
SSL_CTX_set_verify((SSL_CTX*)m_context, SSL_VERIFY_PEER, nullptr);
Expand All @@ -438,14 +486,14 @@ bool TlsSocket::Start()
return false;
}

if (!m_cipher.Empty() && !SSL_set_cipher_list((SSL*)m_session, m_cipher))
if (!m_cipher.empty() && !SSL_set_cipher_list((SSL*)m_session, m_cipher.c_str()))
{
ReportError("Could not select cipher for TLS", false);
Close();
return false;
}

if (m_isClient && m_host && !SSL_set_tlsext_host_name((SSL*)m_session, m_host))
if (m_isClient && !m_host.empty() && !SSL_set_tlsext_host_name((SSL*)m_session, m_host.c_str()))
{
ReportError("Could not set host name for TLS");
Close();
Expand All @@ -467,17 +515,17 @@ bool TlsSocket::Start()
{
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
" For more info visit https://nzbget.com/documentation/certificate-verification/",
*m_host, X509_verify_cert_error_string(verifyRes)));
m_host.c_str(), X509_verify_cert_error_string(verifyRes)));
}
else
{
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
ReportError(BString<1024>("TLS handshake failed for %s", m_host.c_str()));
}
Close();
return false;
}

if (m_isClient && !m_certStore.Empty() && !ValidateCert())
if (m_isClient && !m_certStore.empty() && !ValidateCert())
{
Close();
return false;
Expand All @@ -493,9 +541,9 @@ bool TlsSocket::ValidateCert()
#ifdef HAVE_LIBGNUTLS
#if GNUTLS_VERSION_NUMBER >= 0x030104
#if GNUTLS_VERSION_NUMBER >= 0x030306
if (FileSystem::DirectoryExists(m_certStore))
if (FileSystem::DirectoryExists(m_certStore.c_str()))
{
if (gnutls_certificate_set_x509_trust_dir((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
if (gnutls_certificate_set_x509_trust_dir((gnutls_certificate_credentials_t)m_context, m_certStore.c_str(), GNUTLS_X509_FMT_PEM) < 0)
{
ReportError("Could not set certificate store location");
return false;
Expand All @@ -504,15 +552,15 @@ bool TlsSocket::ValidateCert()
else
#endif
{
if (gnutls_certificate_set_x509_trust_file((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
if (gnutls_certificate_set_x509_trust_file((gnutls_certificate_credentials_t)m_context, m_certStore.c_str(), GNUTLS_X509_FMT_PEM) < 0)
{
ReportError("Could not set certificate store location");
return false;
}
}

unsigned int status = 0;
if (gnutls_certificate_verify_peers3((gnutls_session_t)m_session, m_host, &status) != 0 ||
if (gnutls_certificate_verify_peers3((gnutls_session_t)m_session, m_host.c_str(), &status) != 0 ||
gnutls_certificate_type_get((gnutls_session_t)m_session) != GNUTLS_CRT_X509)
{
ReportError("Could not verify TLS certificate");
Expand All @@ -536,7 +584,7 @@ bool TlsSocket::ValidateCert()
if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn, &size) == 0)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
" For more info visit https://nzbget.com/documentation/certificate-verification/", *m_host, dn));
" For more info visit https://nzbget.com/documentation/certificate-verification/", m_host.c_str(), dn));
gnutls_x509_crt_deinit(cert);
return false;
}
Expand All @@ -548,13 +596,13 @@ bool TlsSocket::ValidateCert()
if (gnutls_certificate_verification_status_print(status, GNUTLS_CRT_X509, &msgdata, 0) == 0)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
" For more info visit https://nzbget.com/documentation/certificate-verification/", *m_host, msgdata.data));
" For more info visit https://nzbget.com/documentation/certificate-verification/", m_host.c_str(), msgdata.data));
gnutls_free(&msgdata);
}
else
{
ReportError(BString<1024>("TLS certificate verification failed for %s."
" For more info visit https://nzbget.com/documentation/certificate-verification/", *m_host));
" For more info visit https://nzbget.com/documentation/certificate-verification/", m_host.c_str()));
}
return false;
}
Expand All @@ -568,13 +616,13 @@ bool TlsSocket::ValidateCert()
if (!cert)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: no certificate provided by server."
" For more info visit https://nzbget.com/documentation/certificate-verification/", *m_host));
" For more info visit https://nzbget.com/documentation/certificate-verification/", m_host.c_str()));
return false;
}

#ifdef HAVE_X509_CHECK_HOST
// hostname verification
if (m_certVerifLevel > Options::ECertVerifLevel::cvMinimal && !m_host.Empty() && X509_check_host(cert, m_host, m_host.Length(), 0, nullptr) != 1)
if (m_certVerifLevel > Options::ECertVerifLevel::cvMinimal && !m_host.empty() && X509_check_host(cert, m_host.c_str(), m_host.size(), 0, nullptr) != 1)
{
const unsigned char* certHost = nullptr;
// Find the position of the CN field in the Subject field of the certificate
Expand All @@ -599,7 +647,7 @@ bool TlsSocket::ValidateCert()
}

PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
" For more info visit https://nzbget.com/documentation/certificate-verification/", *m_host, certHost));
" For more info visit https://nzbget.com/documentation/certificate-verification/", m_host.c_str(), certHost));
X509_free(cert);
return false;
}
Expand Down
Loading

0 comments on commit 347dce3

Please sign in to comment.