From cda6b5965faf37fa1d179c38e6cd728b1ac0dfd3 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 14 Feb 2024 22:41:47 -0500 Subject: [PATCH] add support for optional TCP SSL/TLS sockets; --- CMakeLists.txt | 13 ++ src/CompilerOptions.cmake | 4 + src/common/network/tcp/SecureTcpClient.cpp | 26 +++ src/common/network/tcp/SecureTcpClient.h | 189 +++++++++++++++++++++ src/common/network/tcp/SecureTcpListener.h | 144 ++++++++++++++++ src/common/network/tcp/TcpListener.h | 2 +- 6 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 src/common/network/tcp/SecureTcpClient.cpp create mode 100644 src/common/network/tcp/SecureTcpClient.h create mode 100644 src/common/network/tcp/SecureTcpListener.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c88bf3dd..86046f84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,14 @@ else () message(CHECK_PASS "no") endif (ENABLE_TUI_SUPPORT) +option(ENABLE_TCP_SSL "Enable TCP SSL support" off) +message(CHECK_START "Enable TCP SSL support") +if (ENABLE_TCP_SSL) + message(CHECK_PASS "yes") +else () + message(CHECK_PASS "no") +endif (ENABLE_TCP_SSL) + # Cross-compile options option(CROSS_COMPILE_ARM "Cross-compile for 32-bit ARM" off) option(CROSS_COMPILE_AARCH64 "Cross-compile for 64-bit ARM" off) @@ -155,6 +163,11 @@ if (ENABLE_TUI_SUPPORT AND NOT FC_INCLUDED) set(FINALCUT_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/_deps/finalcut-src/src) endif (ENABLE_TUI_SUPPORT AND NOT FC_INCLUDED) +if (ENABLE_TCP_SSL) + find_package(OpenSSL REQUIRED) + include_directories("${OPENSSL_INCLUDE_DIR}") +endif (ENABLE_TCP_SSL) + # # Set GIT_VER compiler directive # diff --git a/src/CompilerOptions.cmake b/src/CompilerOptions.cmake index 6d1b7449..dadd9c42 100644 --- a/src/CompilerOptions.cmake +++ b/src/CompilerOptions.cmake @@ -20,6 +20,10 @@ else() set(ENABLE_SETUP_TUI off) endif (ENABLE_TUI_SUPPORT) +if (ENABLE_TCP_SSL) + add_definitions(-DENABLE_TCP_SSL) +endif (ENABLE_TCP_SSL) + option(DISABLE_MONITOR "Disable dvmmon compilation" off) if (DISABLE_MONITOR) message(CHECK_START "Disable dvmmon compilation - enabled") diff --git a/src/common/network/tcp/SecureTcpClient.cpp b/src/common/network/tcp/SecureTcpClient.cpp new file mode 100644 index 00000000..9331a8ce --- /dev/null +++ b/src/common/network/tcp/SecureTcpClient.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** +* Digital Voice Modem - Common Library +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Common Library +* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) +* +* Copyright (C) 2024 Bryan Biedenkapp, N2PLL +* +*/ +#include "common/Defines.h" +#include "common/network/tcp/SecureTcpClient.h" + +#if defined(ENABLE_TCP_SSL) + +using namespace network::tcp; + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::string SecureTcpClient::m_sslHostname = std::string(); + +#endif // ENABLE_TCP_SSL \ No newline at end of file diff --git a/src/common/network/tcp/SecureTcpClient.h b/src/common/network/tcp/SecureTcpClient.h new file mode 100644 index 00000000..cceee58a --- /dev/null +++ b/src/common/network/tcp/SecureTcpClient.h @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** +* Digital Voice Modem - Common Library +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Common Library +* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) +* +* Copyright (C) 2024 Bryan Biedenkapp, N2PLL +* +*/ +#if !defined(__SECURE_TCP_CLIENT_H__) +#define __SECURE_TCP_CLIENT_H__ + +#if defined(ENABLE_TCP_SSL) + +#include "Defines.h" +#include "common/Log.h" +#include "common/network/tcp/Socket.h" + +#include + +#include +#include +#include + +namespace network +{ + namespace tcp + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a secure TCP client. + // --------------------------------------------------------------------------- + + class HOST_SW_API SecureTcpClient : public Socket + { + public: + auto operator=(SecureTcpClient&) -> SecureTcpClient& = delete; + auto operator=(SecureTcpClient&&) -> SecureTcpClient& = delete; + SecureTcpClient(SecureTcpClient&) = delete; + + /// Initializes a new instance of the SecureTcpClient class. + /// + /// + /// + /// + SecureTcpClient(const int fd, SSL_CTX* sslCtx, sockaddr_in& client, int clientLen) : Socket(fd), + m_sockaddr(), + m_pSSL(nullptr), + m_pSSLCtx(nullptr) + { + ::memcpy(reinterpret_cast(&m_sockaddr), reinterpret_cast(&client), clientLen); + + initSsl(sslCtx); + if (SSL_accept(m_pSSL) <= 0) { + LogError(LOG_NET, "Cannot accept SSL client, %s err: %d", ERR_error_string(ERR_get_error(), NULL), errno); + throw std::runtime_error("Cannot accept SSL client"); + } + } + /// Initializes a new instance of the SecureTcpClient class. + /// + /// + SecureTcpClient(const std::string& address, const uint16_t port) : + m_pSSL(nullptr), + m_pSSLCtx(nullptr) + { + assert(!address.empty()); + assert(port > 0U); + + OpenSSL_add_ssl_algorithms(); + const SSL_METHOD* method = SSLv23_client_method(); + SSL_load_error_strings(); + m_pSSLCtx = SSL_CTX_new(method); + + init(); + + sockaddr_in addr = {}; + initAddr(address, port, addr); + + ::memcpy(reinterpret_cast(&m_sockaddr), reinterpret_cast(&addr), sizeof(addr)); + + ssize_t ret = ::connect(m_fd, reinterpret_cast(&addr), sizeof(addr)); + if (ret < 0) { + LogError(LOG_NET, "Failed to connect to server, err: %d", errno); + } + + initSsl(m_pSSLCtx); + if (SSL_connect(m_pSSL) <= 0) { + LogError(LOG_NET, "Failed to connect to server, %s err: %d", ERR_error_string(ERR_get_error(), NULL), errno); + throw std::runtime_error("Failed to SSL connect to server"); + } + } + /// Finalizes a instance of the SecureTcpClient class. + ~SecureTcpClient() override + { + if (m_pSSL != nullptr) { + SSL_shutdown(m_pSSL); + SSL_free(m_pSSL); + } + + if (m_pSSLCtx != nullptr) + SSL_CTX_free(m_pSSLCtx); + } + + /// + /// Read data from the socket. + /// + /// Buffer to read data into. + /// Length of data to read. + /// + [[nodiscard]] ssize_t read(uint8_t* buffer, size_t len) noexcept override + { + return SSL_read(m_pSSL, buffer, (int)len); + } + + /// + /// Write data to the socket. + /// + /// Buffer containing data to write to socket. + /// Length of data to write. + /// + ssize_t write(const uint8_t* buffer, size_t len) noexcept override + { + return SSL_write(m_pSSL, buffer, (int)len); + } + + /// + /// + sockaddr_storage getAddress() const { return m_sockaddr; } + + /// + /// + static void setHostname(std::string hostname) { m_sslHostname = hostname; } + + private: + sockaddr_storage m_sockaddr; + static std::string m_sslHostname; + + SSL* m_pSSL; + SSL_CTX* m_pSSLCtx; + + /// + /// + /// + void init() noexcept(false) + { + m_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); + throw std::runtime_error("Cannot create the TCP socket"); + } + + int reuse = 1; + if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char*)& reuse, sizeof(reuse)) != 0) { + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + throw std::runtime_error("Cannot set the TCP socket option"); + } + } + + /// + /// + /// + /// + void initSsl(SSL_CTX* sslCtx) noexcept(false) + { + m_pSSL = SSL_new(sslCtx); + if (m_pSSL == nullptr) { + LogError(LOG_NET, "Failed to create SSL client, %s err: %d", ERR_error_string(ERR_get_error(), NULL), errno); + throw std::runtime_error("Failed to create SSL client"); + } + + SSL_set_hostflags(m_pSSL, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (!SSL_set1_host(m_pSSL, SecureTcpClient::m_sslHostname.c_str())) { + LogError(LOG_NET, "Failed to set SSL hostname, %s err: %d", ERR_error_string(ERR_get_error(), NULL), errno); + throw std::runtime_error("Failed to set SSL hostname"); + } + + SSL_set_verify(m_pSSL, SSL_VERIFY_NONE, NULL); + SSL_set_fd(m_pSSL, m_fd); + } + }; + } // namespace tcp +} // namespace network + +#endif // ENABLE_TCP_SSL + +#endif // __SECURE_TCP_CLIENT_H__ diff --git a/src/common/network/tcp/SecureTcpListener.h b/src/common/network/tcp/SecureTcpListener.h new file mode 100644 index 00000000..6017d851 --- /dev/null +++ b/src/common/network/tcp/SecureTcpListener.h @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** +* Digital Voice Modem - Common Library +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Common Library +* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) +* +* Copyright (C) 2024 Bryan Biedenkapp, N2PLL +* +*/ +#if !defined(__SECURE_TCP_SERVER_H__) +#define __SECURE_TCP_SERVER_H__ + +#if defined(ENABLE_TCP_SSL) + +#include "Defines.h" +#include "common/network/tcp/Socket.h" +#include "common/Log.h" +#include "common/network/tcp/SecureTcpClient.h" + +#include + +#include +#include + +namespace network +{ + namespace tcp + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a secure TCP server listener. + // --------------------------------------------------------------------------- + + class HOST_SW_API SecureTcpListener : public Socket + { + public: + auto operator=(SecureTcpListener&) -> SecureTcpListener& = delete; + auto operator=(SecureTcpListener&&) -> SecureTcpListener& = delete; + SecureTcpListener(SecureTcpListener&) = delete; + + /// Initializes a new instance of the SecureTcpListener class. + /// + /// + SecureTcpListener(const std::string& keyFile, const std::string& certFile) : + m_pSSLCtx(nullptr), + m_keyFile(keyFile), + m_certFile(certFile) + { + assert(!m_certFile.empty()); + assert(!m_keyFile.empty()); + + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + const SSL_METHOD* method = SSLv23_server_method(); + m_pSSLCtx = SSL_CTX_new(method); + if (m_pSSLCtx == nullptr) { + LogError(LOG_NET, "Cannot create server SSL context, %s err: %d", ERR_error_string(ERR_get_error(), NULL), errno); + throw std::runtime_error("Cannot create server SSL context"); + } + + m_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + LogError(LOG_NET, "Cannot create the TCP socket, err: %d", errno); + throw std::runtime_error("Cannot create the TCP socket"); + } + + int reuse = 1; + if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char*)& reuse, sizeof(reuse)) != 0) { + LogError(LOG_NET, "Cannot set the TCP socket option, err: %d", errno); + throw std::runtime_error("Cannot set the TCP socket option"); + } + + initSecureFiles(); + } + /// Initializes a new instance of the SecureTcpListener class. + /// + /// + /// + /// + SecureTcpListener(const std::string& keyFile, const std::string& certFile, const uint16_t port, const std::string& address = "0.0.0.0") : SecureTcpListener(keyFile, certFile) + { + if (!bind(address, port)) { + LogError(LOG_NET, "Cannot to bind secure TCP server, err: %d", errno); + throw std::runtime_error("Cannot to bind secure TCP server"); + } + } + /// Finalizes a instance of the SecureTcpListener class. + ~SecureTcpListener() override + { + if (m_pSSLCtx != nullptr) + SSL_CTX_free(m_pSSLCtx); + } + + /// + /// Accept a new TCP connection either secure or unsecure. + /// + /// Newly accepted TCP connection + [[nodiscard]] SecureTcpClient* accept() + { + sockaddr_in client = {}; + socklen_t clientLen = sizeof(client); + int fd = Socket::accept(reinterpret_cast(&client), &clientLen); + if (fd < 0) { + return nullptr; + } + + return new SecureTcpClient(fd, m_pSSLCtx, client, clientLen); + } + + private: + SSL_CTX* m_pSSLCtx; + const std::string m_keyFile; + const std::string m_certFile; + + /// + /// + /// + void initSecureFiles() + { + if (SSL_CTX_use_certificate_file(m_pSSLCtx, m_certFile.c_str(), SSL_FILETYPE_PEM) != 1) { + ERR_print_errors_fp(stderr); + throw std::runtime_error("Failed to use PEM certificate file"); + } + + if (SSL_CTX_use_PrivateKey_file(m_pSSLCtx, m_keyFile.c_str(), SSL_FILETYPE_PEM) != 1) { + ERR_print_errors_fp(stderr); + throw std::runtime_error("Failed to use PEM private key file"); + } + + if (SSL_CTX_check_private_key(m_pSSLCtx) != 1) { + ERR_print_errors_fp(stderr); + throw std::runtime_error("Keys do not match!"); + } + } + }; + } // namespace tcp +} // namespace network + +#endif // ENABLE_TCP_SSL + +#endif // __SECURE_TCP_SERVER_H__ diff --git a/src/common/network/tcp/TcpListener.h b/src/common/network/tcp/TcpListener.h index 41f9863f..9316e0d5 100644 --- a/src/common/network/tcp/TcpListener.h +++ b/src/common/network/tcp/TcpListener.h @@ -60,7 +60,7 @@ namespace network /// TcpListener(const std::string& ipAddr, const uint16_t port, const int backlog) noexcept(false) : TcpListener(port, ipAddr) { - if (listen(backlog) < 0) { + if (listen(ipAddr, port, backlog) < 0) { LogError(LOG_NET, "Failed to listen on TCP server, err: %d", errno); throw std::runtime_error("Failed to listen on TCP server."); }