Skip to content

Commit

Permalink
feat(VerifyCertificateMacOS): moves the HttpsVerifier class to separa…
Browse files Browse the repository at this point in the history
…te files
  • Loading branch information
Nicogp committed Jan 9, 2025
1 parent 1879a6d commit 3d12aac
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 156 deletions.
1 change: 1 addition & 0 deletions src/agent/communicator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ if(WIN32)
set(VERIFY_UTILS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/https_socket_verify_utils_win.cpp")
elseif(APPLE)
set(VERIFY_UTILS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/https_socket_verify_utils_mac.cpp")
list(APPEND VERIFY_UTILS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/https_verifier_mac.cpp")
else()
set(VERIFY_UTILS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/https_socket_verify_utils_lin.cpp")
endif()
Expand Down
63 changes: 63 additions & 0 deletions src/agent/communicator/include/https_verifier_mac.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include <icertificate_utils.hpp>

#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <boost/asio/ssl.hpp>

#include <memory>
#include <string>

namespace https_socket_verify_utils
{
class HttpsVerifier
{
public:
/// @brief Constructor to initialize the verifier object.
/// @param mode The verification mode to use
/// @param host The hostname to verify against
/// @param utils The certificate utilities object to use
HttpsVerifier(const std::string& mode, const std::string& host, std::unique_ptr<ICertificateUtils>& utils)
: m_mode(mode)
, m_host(host)
, m_utils(utils)
{
}

/// @brief Verifies the certificate of the HTTPS connection
/// @param ctx The verification context
/// @return True if the certificate is valid, false otherwise
bool Verify(boost::asio::ssl::verify_context& ctx);

private:
/// @brief Extracts the certificate from the verification context
/// @param ctx The verification context
/// @param certData The extracted certificate
/// @return True if the certificate was extracted successfully, false otherwise
bool ExtractCertificate(boost::asio::ssl::verify_context& ctx, CFDataRef& certData);

/// @brief Creates a trust object from the certificate
/// @param certData The certificate data
/// @param trust The created trust object
/// @return True if the trust object was created successfully, false otherwise
bool CreateTrustObject(CFDataRef certData, SecTrustRef& trust);

/// @brief Evaluates the trust status of the trust object
/// @param trust The trust object to evaluate
/// @return True if the trust evaluation succeeded, false otherwise
bool EvaluateTrust(SecTrustRef trust);

/// @brief Validates the hostname of the server certificate
/// @param cert The server certificate
/// @return True if the hostname is valid, false otherwise
bool ValidateHostname(SecCertificateRef cert);

/// @brief The verification mode to use
std::string m_mode;

/// @brief The hostname to verify against
std::string m_host;

/// @brief The certificate utilities object to use
std::unique_ptr<ICertificateUtils>& m_utils;
};
} // namespace https_socket_verify_utils
158 changes: 2 additions & 156 deletions src/agent/communicator/src/https_socket_verify_utils_mac.cpp
Original file line number Diff line number Diff line change
@@ -1,39 +1,15 @@
#include "certificate_utils.hpp"
#include <https_socket_verify_utils.hpp>
#include <https_verifier_mac.hpp>
#include <icertificate_utils.hpp>
#include <logger.hpp>

#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <boost/asio/ssl.hpp>

#include <memory>
#include <string>

namespace https_socket_verify_utils
{
class HttpsVerifier
{
public:
HttpsVerifier(const std::string& mode, const std::string& host, std::unique_ptr<ICertificateUtils>& utils)
: m_mode(mode)
, m_host(host)
, m_utils(utils)
{
}

bool Verify(boost::asio::ssl::verify_context& ctx);

private:
bool ExtractCertificate(boost::asio::ssl::verify_context& ctx, CFDataRef& certData);
bool CreateTrustObject(CFDataRef certData, SecTrustRef& trust);
bool EvaluateTrust(SecTrustRef trust);
bool ValidateHostname(SecCertificateRef cert);

std::string m_mode;
std::string m_host;
std::unique_ptr<ICertificateUtils>& m_utils;
};

bool VerifyCertificate([[maybe_unused]] bool preverified,
boost::asio::ssl::verify_context& ctx,
const std::string& mode,
Expand All @@ -45,134 +21,4 @@ namespace https_socket_verify_utils

return verifier.Verify(ctx);
}

bool HttpsVerifier::Verify(boost::asio::ssl::verify_context& ctx)
{
CFDataRef certData = nullptr;
if (!ExtractCertificate(ctx, certData))
{
return false;
}

SecTrustRef trust = nullptr;
if (!CreateTrustObject(certData, trust))
{
return false;
}

if (!EvaluateTrust(trust))
{
m_utils->ReleaseCFObject(certData);
m_utils->ReleaseCFObject(trust);
return false;
}

SecCertificateRef serverCert = m_utils->CreateCertificate(certData);
if (m_mode == "full" && !ValidateHostname(serverCert))
{
m_utils->ReleaseCFObject(certData);
m_utils->ReleaseCFObject(serverCert);
m_utils->ReleaseCFObject(trust);
return false;
}

m_utils->ReleaseCFObject(certData);
m_utils->ReleaseCFObject(serverCert);
m_utils->ReleaseCFObject(trust);
return true;
}

bool HttpsVerifier::ExtractCertificate(boost::asio::ssl::verify_context& ctx, CFDataRef& certData)
{
STACK_OF(X509)* certChain = m_utils->GetCertChain(ctx.native_handle());
if (!certChain || m_utils->GetCertificateCount(certChain) == 0)
{
LogError("No certificates in the chain.");
return false;
}

X509* cert = m_utils->GetCertificateFromChain(certChain, 0);
if (!cert)
{
LogError("The server certificate could not be obtained.");
return false;
}

unsigned char* certRawData = nullptr;
const int certLen = m_utils->EncodeCertificateToDER(cert, &certRawData);
if (certLen <= 0)
{
LogError("Failed to encode certificate to DER.");
return false;
}

certData = m_utils->CreateCFData(certRawData, certLen);
OPENSSL_free(certRawData);

return certData != nullptr;
}

bool HttpsVerifier::CreateTrustObject(CFDataRef certData, SecTrustRef& trust)
{
SecCertificateRef serverCert = m_utils->CreateCertificate(certData);

if (!serverCert)
{
LogError("Failed to create SecCertificateRef.");
return false;
}

const void* certArrayValues[] = {serverCert};

CFArrayRef certArray = m_utils->CreateCertArray(certArrayValues, 1);
SecPolicyRef policy = m_utils->CreateSSLPolicy(true, "");

OSStatus status = m_utils->CreateTrustObject(certArray, policy, &trust);
m_utils->ReleaseCFObject(certArray);
m_utils->ReleaseCFObject(policy);
m_utils->ReleaseCFObject(serverCert);

return (status == errSecSuccess && trust != nullptr);
}

bool HttpsVerifier::EvaluateTrust(SecTrustRef trust)
{
CFErrorRef error = nullptr;
const bool trustResult = m_utils->EvaluateTrust(trust, &error);
if (!trustResult && error)
{
CFStringRef errorDesc = m_utils->CopyErrorDescription(error);
std::string errorString = m_utils->GetStringCFString(errorDesc);
LogError("Trust evaluation failed: {}", errorString);
m_utils->ReleaseCFObject(errorDesc);
m_utils->ReleaseCFObject(error);
}
return trustResult;
}

bool HttpsVerifier::ValidateHostname(SecCertificateRef serverCert)
{
CFStringRef sanString = SecCertificateCopySubjectSummary(serverCert);
if (!sanString)
{
LogError("Failed to retrieve SAN or CN for hostname validation.");
return false;
}

bool hostnameMatches = false;
std::string sanStringStr = m_utils->GetStringCFString(sanString);
if (!sanStringStr.empty())
{
hostnameMatches = (m_host == sanStringStr);
}

m_utils->ReleaseCFObject(sanString);
if (!hostnameMatches)
{
LogError("The hostname '{}' does not match the certificate's SAN or CN '{}'.", m_host, sanStringStr);
}

return hostnameMatches;
}

} // namespace https_socket_verify_utils
145 changes: 145 additions & 0 deletions src/agent/communicator/src/https_verifier_mac.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include "certificate_utils.hpp"
#include <https_socket_verify_utils.hpp>
#include <https_verifier_mac.hpp>
#include <icertificate_utils.hpp>
#include <logger.hpp>

#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <boost/asio/ssl.hpp>

#include <memory>
#include <string>

namespace https_socket_verify_utils
{
bool HttpsVerifier::Verify(boost::asio::ssl::verify_context& ctx)
{
CFDataRef certData = nullptr;
if (!ExtractCertificate(ctx, certData))
{
return false;
}

SecTrustRef trust = nullptr;
if (!CreateTrustObject(certData, trust))
{
return false;
}

if (!EvaluateTrust(trust))
{
m_utils->ReleaseCFObject(certData);
m_utils->ReleaseCFObject(trust);
return false;
}

SecCertificateRef serverCert = m_utils->CreateCertificate(certData);
if (m_mode == "full" && !ValidateHostname(serverCert))
{
m_utils->ReleaseCFObject(certData);
m_utils->ReleaseCFObject(serverCert);
m_utils->ReleaseCFObject(trust);
return false;
}

m_utils->ReleaseCFObject(certData);
m_utils->ReleaseCFObject(serverCert);
m_utils->ReleaseCFObject(trust);
return true;
}

bool HttpsVerifier::ExtractCertificate(boost::asio::ssl::verify_context& ctx, CFDataRef& certData)
{
STACK_OF(X509)* certChain = m_utils->GetCertChain(ctx.native_handle());
if (!certChain || m_utils->GetCertificateCount(certChain) == 0)
{
LogError("No certificates in the chain.");
return false;
}

X509* cert = m_utils->GetCertificateFromChain(certChain, 0);
if (!cert)
{
LogError("The server certificate could not be obtained.");
return false;
}

unsigned char* certRawData = nullptr;
const int certLen = m_utils->EncodeCertificateToDER(cert, &certRawData);
if (certLen <= 0)
{
LogError("Failed to encode certificate to DER.");
return false;
}

certData = m_utils->CreateCFData(certRawData, certLen);
OPENSSL_free(certRawData);

return certData != nullptr;
}

bool HttpsVerifier::CreateTrustObject(CFDataRef certData, SecTrustRef& trust)
{
SecCertificateRef serverCert = m_utils->CreateCertificate(certData);

if (!serverCert)
{
LogError("Failed to create SecCertificateRef.");
return false;
}

const void* certArrayValues[] = {serverCert};

CFArrayRef certArray = m_utils->CreateCertArray(certArrayValues, 1);
SecPolicyRef policy = m_utils->CreateSSLPolicy(true, "");

OSStatus status = m_utils->CreateTrustObject(certArray, policy, &trust);
m_utils->ReleaseCFObject(certArray);
m_utils->ReleaseCFObject(policy);
m_utils->ReleaseCFObject(serverCert);

return (status == errSecSuccess && trust != nullptr);
}

bool HttpsVerifier::EvaluateTrust(SecTrustRef trust)
{
CFErrorRef error = nullptr;
const bool trustResult = m_utils->EvaluateTrust(trust, &error);
if (!trustResult && error)
{
CFStringRef errorDesc = m_utils->CopyErrorDescription(error);
std::string errorString = m_utils->GetStringCFString(errorDesc);
LogError("Trust evaluation failed: {}", errorString);
m_utils->ReleaseCFObject(errorDesc);
m_utils->ReleaseCFObject(error);
}
return trustResult;
}

bool HttpsVerifier::ValidateHostname(SecCertificateRef serverCert)
{
CFStringRef sanString = SecCertificateCopySubjectSummary(serverCert);
if (!sanString)
{
LogError("Failed to retrieve SAN or CN for hostname validation.");
return false;
}

bool hostnameMatches = false;
std::string sanStringStr = m_utils->GetStringCFString(sanString);
if (!sanStringStr.empty())
{
hostnameMatches = (m_host == sanStringStr);
}

m_utils->ReleaseCFObject(sanString);
if (!hostnameMatches)
{
LogError("The hostname '{}' does not match the certificate's SAN or CN '{}'.", m_host, sanStringStr);
}

return hostnameMatches;
}

} // namespace https_socket_verify_utils

0 comments on commit 3d12aac

Please sign in to comment.