Add transport layer security to TCP sockets

This commit is contained in:
Lukas Dürrenberger committed 2026-10-04 22:12:01 +02:00
1 parent 917e811888
commit d95f00f212
5 files changed
+232

No files matched your search

+132
View File
@@ -34,9 +34,23 @@
#include <CSFML/Network/Types.h>
#include <CSFML/System/Time.h>
#include <stdbool.h>
#include <stddef.h>
////////////////////////////////////////////////////////////
/// \brief Transport layer security status codes
///
////////////////////////////////////////////////////////////
typedef enum
{
sfTlsNotConnected, ///< TCP connection not yet connected
sfTlsHandshakeStarted, ///< TLS handshake has been started
sfTlsHandshakeComplete, ///< TLS handshake is complete, stream is encrypted
sfTlsError ///< An unexpected error happened
} sfTlsStatus;
////////////////////////////////////////////////////////////
/// \brief Create a new TCP socket
///
@@ -229,3 +243,121 @@ CSFML_NETWORK_API sfSocketStatus sfTcpSocket_sendPacket(sfTcpSocket* socket, sfP
///
////////////////////////////////////////////////////////////
CSFML_NETWORK_API sfSocketStatus sfTcpSocket_receivePacket(sfTcpSocket* socket, sfPacket* packet);
////////////////////////////////////////////////////////////
/// \brief Set up transport layer security as a client
///
/// Once the TCP connection is connected, transport layer
/// security can be set up.
///
/// If this function is called before the TCP connection is
/// connected, it will return sfTlsNotConnected and must be
/// called again once the TCP connection is connected.
///
/// If this function started TLS setup but could not finish
/// it within this call e.g. because this socket was set to
/// non-blocking, it will return sfTlsHandshakeStarted and
/// this function will have to be called repeatedly until
/// sfTlsHandshakeComplete is returned. If this socket is
/// blocking, sfTlsHandshakeComplete should be returned
/// within the same function call if TLS setup was successful.
///
/// If sfTlsError is returned, something went wrong with TLS
/// setup and the connection must be reconnected and TLS setup
/// reattempted after it is connected again.
///
/// If verification is enabled, this function verifies the peer
/// using the system provided certificate store. If the peer
/// does not have a certificate that was signed by a certificate
/// authority i.e. a self-signed certificate, the entire certificate
/// chain can be provided using sfTcpSocket_setupTlsClientWithCertificate.
///
/// The hostname is sent to the server via server name indication
/// (SNI) and used to verify the certificate chain returned by the
/// server.
///
/// \param socket TCP socket object
/// \param hostname Hostname of the remote peer, encoded in UTF-8, used for verification
/// \param verifyPeer True to enable peer verification, false to disable it
///
/// \return TLS status code
///
////////////////////////////////////////////////////////////
CSFML_NETWORK_API sfTlsStatus sfTcpSocket_setupTlsClient(sfTcpSocket* socket, const char* hostname, bool verifyPeer);
////////////////////////////////////////////////////////////
/// \brief Set up transport layer security as a client with a given certificate chain
///
/// When calling this function, the certificate chain to verify
/// the host with has to be provided. Verification is always
/// enabled when calling this function.
///
/// The certificate data can be provided in PEM or DER format.
///
/// See sfTcpSocket_setupTlsClient for details on the returned
/// status codes.
///
/// \param socket TCP socket object
/// \param hostname Hostname of the remote peer, encoded in UTF-8, used for verification
/// \param certificateChainData Certificate chain data in PEM or DER encoding
/// \param certificateChainSize Size of the certificate chain data
///
/// \return TLS status code
///
////////////////////////////////////////////////////////////
CSFML_NETWORK_API sfTlsStatus sfTcpSocket_setupTlsClientWithCertificate(
sfTcpSocket* socket,
const char* hostname,
const void* certificateChainData,
size_t certificateChainSize);
////////////////////////////////////////////////////////////
/// \brief Set up transport layer security as a server
///
/// Once the TCP connection is connected, transport layer
/// security can be set up.
///
/// As a server, a certificate chain as well as a private key
/// must be provided. The certificate and private key data can
/// be provided in PEM or DER format. If the private key is
/// secured by a password, the password must be provided.
///
/// If sfTlsError is returned, something went wrong with TLS
/// setup and the connection must be disconnected. The client
/// must reconnect and reattempt TLS setup again.
///
/// See sfTcpSocket_setupTlsClient for details on the other
/// returned status codes.
///
/// \param socket TCP socket object
/// \param certificateChainData Certificate chain data in PEM or DER encoding
/// \param certificateChainSize Size of the certificate chain data
/// \param privateKeyData Private key data in PEM or DER encoding
/// \param privateKeySize Size of the private key data
/// \param privateKeyPasswordData Private key password data, can be NULL if there is no password
/// \param privateKeyPasswordSize Size of the private key password data
///
/// \return TLS status code
///
////////////////////////////////////////////////////////////
CSFML_NETWORK_API sfTlsStatus sfTcpSocket_setupTlsServer(
sfTcpSocket* socket,
const void* certificateChainData,
size_t certificateChainSize,
const void* privateKeyData,
size_t privateKeySize,
const void* privateKeyPasswordData,
size_t privateKeyPasswordSize);
////////////////////////////////////////////////////////////
/// \brief Get the name of the TLS ciphersuite currently in use
///
/// The returned string stays valid until the next call to
/// this function or until the socket is destroyed.
///
/// \param socket TCP socket object
///
/// \return TLS ciphersuite currently in use or NULL if TLS is not set up
///
////////////////////////////////////////////////////////////
CSFML_NETWORK_API const char* sfTcpSocket_getCurrentCiphersuiteName(const sfTcpSocket* socket);
+72
View File
@@ -31,7 +31,9 @@
#include <CSFML/Network/TcpSocketStruct.hpp>
#include <SFML/Network/IpAddress.hpp>
#include <SFML/System/String.hpp>
#include <cstddef>
#include <cstring>
@@ -170,3 +172,73 @@ sfSocketStatus sfTcpSocket_receivePacket(sfTcpSocket* socket, sfPacket* packet)
assert(packet);
return static_cast<sfSocketStatus>(socket->receive(*packet));
}
namespace
{
////////////////////////////////////////////////////////////
[[nodiscard]] sf::String fromUtf8(const char* string)
{
return sf::String::fromUtf8(string, string + std::strlen(string));
}
} // namespace
////////////////////////////////////////////////////////////
sfTlsStatus sfTcpSocket_setupTlsClient(sfTcpSocket* socket, const char* hostname, bool verifyPeer)
{
assert(socket);
assert(hostname);
return static_cast<sfTlsStatus>(socket->setupTlsClient(fromUtf8(hostname), verifyPeer));
}
////////////////////////////////////////////////////////////
sfTlsStatus sfTcpSocket_setupTlsClientWithCertificate(sfTcpSocket* socket,
const char* hostname,
const void* certificateChainData,
size_t certificateChainSize)
{
assert(socket);
assert(hostname);
assert(certificateChainData);
return static_cast<sfTlsStatus>(
socket->setupTlsClient(fromUtf8(hostname), static_cast<const std::byte*>(certificateChainData), certificateChainSize));
}
////////////////////////////////////////////////////////////
sfTlsStatus sfTcpSocket_setupTlsServer(
sfTcpSocket* socket,
const void* certificateChainData,
size_t certificateChainSize,
const void* privateKeyData,
size_t privateKeySize,
const void* privateKeyPasswordData,
size_t privateKeyPasswordSize)
{
assert(socket);
assert(certificateChainData);
assert(privateKeyData);
return static_cast<sfTlsStatus>(
socket->setupTlsServer(static_cast<const std::byte*>(certificateChainData),
certificateChainSize,
static_cast<const std::byte*>(privateKeyData),
privateKeySize,
static_cast<const std::byte*>(privateKeyPasswordData),
privateKeyPasswordData ? privateKeyPasswordSize : 0));
}
////////////////////////////////////////////////////////////
const char* sfTcpSocket_getCurrentCiphersuiteName(const sfTcpSocket* socket)
{
assert(socket);
const auto name = socket->getCurrentCiphersuiteName();
if (!name)
return nullptr;
socket->CiphersuiteName = *name;
return socket->CiphersuiteName.c_str();
}
+3
View File
@@ -29,10 +29,13 @@
////////////////////////////////////////////////////////////
#include <SFML/Network/TcpSocket.hpp>
#include <string>
////////////////////////////////////////////////////////////
// Internal structure of sfTcpSocket
////////////////////////////////////////////////////////////
struct sfTcpSocket : sf::TcpSocket
{
mutable std::string CiphersuiteName;
};
+1
View File
@@ -71,6 +71,7 @@ add_executable(test-csfml-network
Network/Http.test.cpp
Network/IpAddress.test.cpp
Network/SocketStatus.test.cpp
Network/TcpSocket.test.cpp
)
target_link_libraries(test-csfml-network PRIVATE csfml-network csfml-system Catch2::Catch2WithMain SFML::Network)
set_target_warnings(test-csfml-network)
+24
View File
@@ -0,0 +1,24 @@
#include <CSFML/Network/TcpSocket.h>
#include <SFML/Network/TcpSocket.hpp>
#include <catch2/catch_test_macros.hpp>
TEST_CASE("[Network] sfTcpSocket")
{
SECTION("sfTlsStatus")
{
STATIC_CHECK(sfTlsNotConnected == static_cast<int>(sf::TcpSocket::TlsStatus::NotConnected));
STATIC_CHECK(sfTlsHandshakeStarted == static_cast<int>(sf::TcpSocket::TlsStatus::HandshakeStarted));
STATIC_CHECK(sfTlsHandshakeComplete == static_cast<int>(sf::TcpSocket::TlsStatus::HandshakeComplete));
STATIC_CHECK(sfTlsError == static_cast<int>(sf::TcpSocket::TlsStatus::Error));
}
SECTION("TLS on unconnected socket")
{
sfTcpSocket* socket = sfTcpSocket_create();
CHECK(sfTcpSocket_setupTlsClient(socket, "localhost", true) == sfTlsNotConnected);
CHECK(sfTcpSocket_getCurrentCiphersuiteName(socket) == nullptr);
sfTcpSocket_destroy(socket);
}
}