Skip to content

Commit

Permalink
feat(communicator): separate certificate verification logic for linux…
Browse files Browse the repository at this point in the history
… agent and windows agent
  • Loading branch information
Nicogp committed Dec 23, 2024
1 parent 967804a commit bc909d7
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 10 deletions.
12 changes: 12 additions & 0 deletions src/agent/communicator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h")

add_library(Communicator src/communicator.cpp src/http_client.cpp src/http_request_params.cpp)

if(WIN32)
set(VERIFY_UTILS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/https_socket_verify_utils_win.cpp")
else()
set(VERIFY_UTILS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/https_socket_verify_utils_lin.cpp")
endif()

target_sources(Communicator PRIVATE ${VERIFY_UTILS_FILE})

target_include_directories(Communicator PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
SYSTEM PRIVATE
Expand All @@ -23,6 +31,10 @@ target_include_directories(Communicator PUBLIC
target_compile_definitions(Communicator PRIVATE -DJWT_DISABLE_PICOJSON=ON)
target_link_libraries(Communicator PUBLIC Config Boost::asio Boost::beast Boost::system Boost::url Logger PRIVATE OpenSSL::SSL OpenSSL::Crypto nlohmann_json::nlohmann_json)

if(WIN32)
target_link_libraries(Communicator PRIVATE Crypt32)
endif()

include(../../cmake/ConfigureTarget.cmake)
configure_target(Communicator)

Expand Down
17 changes: 17 additions & 0 deletions src/agent/communicator/include/https_socket_verify_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <boost/asio/ssl.hpp>

#include <string>

namespace https_socket_verify_utils
{
/// @brief Verifies the certificate of the HTTPS connection
/// @param preverified The result of the pre-verification
/// @param ctx The verification context
/// @param mode The verification mode to use
/// @param host The hostname to verify against
/// @return True if the certificate is valid, false otherwise
bool VerifyCertificate(bool preverified,
boost::asio::ssl::verify_context& ctx,
const std::string& mode,
const std::string& host);
} // namespace https_socket_verify_utils
24 changes: 14 additions & 10 deletions src/agent/communicator/src/https_socket.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <https_socket_verify_utils.hpp>
#include <ihttp_socket.hpp>
#include <logger.hpp>

Expand Down Expand Up @@ -32,26 +33,29 @@ namespace http_client
/// - "none": no verification is performed
void SetVerificationMode(const std::string& host, const std::string& verificationMode) override
{
if (verificationMode == "certificate")
if (verificationMode == "none")
{
m_ctx.set_default_verify_paths();
m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_peer);
m_ssl_socket.set_verify_callback([](bool preverified, boost::asio::ssl::verify_context&)
{ return preverified; });
m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_none);
return;
}
else if (verificationMode == "none")

m_ctx.set_default_verify_paths();
m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_peer);
if (verificationMode == "certificate")
{
m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_none);
m_ssl_socket.set_verify_callback(
[verificationMode, host](bool preverified, boost::asio::ssl::verify_context& ctx)
{ return https_socket_verify_utils::VerifyCertificate(preverified, ctx, verificationMode, host); });
}
else
{
if (verificationMode != "full")
{
LogWarn("Verification mode unknown, full mode is used.");
}
m_ctx.set_default_verify_paths();
m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_peer);
m_ssl_socket.set_verify_callback(boost::asio::ssl::rfc2818_verification(host));
m_ssl_socket.set_verify_callback(
[verificationMode, host](bool preverified, boost::asio::ssl::verify_context& ctx)
{ return https_socket_verify_utils::VerifyCertificate(preverified, ctx, "full", host); });
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/agent/communicator/src/https_socket_verify_utils_lin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <boost/asio/ssl.hpp>
#include <https_socket_verify_utils.hpp>

#include <string>

namespace https_socket_verify_utils
{
bool VerifyCertificate(bool preverified,
boost::asio::ssl::verify_context& ctx,
const std::string& mode,
const std::string& host)
{
if (mode == "certificate")
{
return preverified;
}
else if (mode == "full")
{
boost::asio::ssl::rfc2818_verification verifier(host);
return verifier(preverified, ctx);
}
return false;
}
} // namespace https_socket_verify_utils
135 changes: 135 additions & 0 deletions src/agent/communicator/src/https_socket_verify_utils_win.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include <https_socket_verify_utils.hpp>
#include <logger.hpp>

#include <boost/asio/ssl.hpp>
#include <wincrypt.h>
#include <windows.h>

namespace https_socket_verify_utils
{
bool VerifyCertificate([[maybe_unused]] bool preverified,
boost::asio::ssl::verify_context& ctx,
const std::string& mode,
const std::string& host)
{
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
if (!cert)
{
LogError("The server certificate could not be obtained.");
return false;
}

// Convert certificate to DER format
unsigned char* der = nullptr;
int derLen = i2d_X509(cert, &der);
if (derLen <= 0)
{
LogError("Certificate conversion to DER failed.");
return false;
}

// Create the certificate context
PCCERT_CONTEXT certCtx = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der, derLen);
OPENSSL_free(der);

if (!certCtx)
{
LogError("The certificate context could not be created.");
return false;
}

// Open the Windows certificate store
HCERTSTORE hStore = CertOpenSystemStoreA(NULL, "ROOT");
if (!hStore)
{
LogError("The Windows certificate store could not be opened.");
CertFreeCertificateContext(certCtx);
return false;
}

// Create the certificate chain context
CERT_CHAIN_PARA chainPara = {sizeof(CERT_CHAIN_PARA)};
PCCERT_CHAIN_CONTEXT chainCtx = nullptr;

bool result = CertGetCertificateChain(NULL, certCtx, NULL, hStore, &chainPara, 0, NULL, &chainCtx);

if (!result || !chainCtx)
{
LogError("The certificate chain could not be verified.");
}

// Validate the SSL policy of the chain
CERT_CHAIN_POLICY_PARA policyPara = {sizeof(CERT_CHAIN_POLICY_PARA)};
CERT_CHAIN_POLICY_STATUS policyStatus = {sizeof(CERT_CHAIN_POLICY_STATUS)};
policyPara.dwFlags = 0;

if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_BASE, chainCtx, &policyPara, &policyStatus))
{
LogError("Error verifying certificate chain policy.");
result = false;
}
else if (policyStatus.dwError != 0)
{
LogError("Certification policy error: {}", policyStatus.dwError);
result = false;
}

if (result && mode == "full")
{
// Obtain SAN from the certificate
STACK_OF(GENERAL_NAME)* sanNames =
static_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL));
bool hostValidated = false;

if (sanNames)
{
int sanCount = sk_GENERAL_NAME_num(sanNames);
for (int i = 0; i < sanCount; ++i)
{
const GENERAL_NAME* san = sk_GENERAL_NAME_value(sanNames, i);
if (san->type == GEN_DNS)
{
const char* dnsName = reinterpret_cast<const char*>(ASN1_STRING_get0_data(san->d.dNSName));
if (host == dnsName)
{
hostValidated = true;
break;
}
}
}
GENERAL_NAMES_free(sanNames);
}

// If no SAN was found, check the CN
if (!hostValidated)
{
X509_NAME* subjectName = X509_get_subject_name(cert);
char cn[256] = {0};
if (X509_NAME_get_text_by_NID(subjectName, NID_commonName, cn, sizeof(cn)) > 0)
{
if (host == cn)
{
hostValidated = true;
}
}
}

if (!hostValidated)
{
LogError("The host name does not match the SAN or the CN.");
result = false;
}
}

// Free resources
if (chainCtx)
{
CertFreeCertificateChain(chainCtx);
}
CertCloseStore(hStore, 0);
CertFreeCertificateContext(certCtx);

return result;
}

} // namespace https_socket_verify_utils

0 comments on commit bc909d7

Please sign in to comment.