From e124a482161a6fa41d54b27449bc5611f4b5a979 Mon Sep 17 00:00:00 2001 From: masmartin <86951372+masmartin@users.noreply.github.com> Date: Thu, 26 Aug 2021 13:42:20 +0200 Subject: [PATCH 1/3] - Supports TLS encrypted connections (Windows client only) when using the referenced fork of the tacopie network module. - Visual Studio solution and project converted to VS2019 --- README.md | 2 +- includes/tacopie/network/tcp_client.hpp | 3 +- includes/tacopie/network/tcp_socket.hpp | 10 +- includes/tacopie/network/tls.hpp | 103 ++++ includes/tacopie/utils/logger.hpp | 8 +- msvc15/tacopie.vcxproj | 351 +++++++------ sources/network/common/tcp_socket.cpp | 34 +- sources/network/io_service.cpp | 7 +- sources/network/tcp_client.cpp | 6 +- sources/network/unix/unix_tcp_socket.cpp | 5 +- sources/network/windows/tls.cpp | 494 ++++++++++++++++++ .../network/windows/windows_tcp_socket.cpp | 6 +- 12 files changed, 829 insertions(+), 200 deletions(-) create mode 100644 includes/tacopie/network/tls.hpp create mode 100644 sources/network/windows/tls.cpp diff --git a/README.md b/README.md index 5aee3d3..575e1f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ESTOS - Readme -(no changes yet - just to divert from original README by now) +Supports TLS encrypted connections (Windows client only). # Important diff --git a/includes/tacopie/network/tcp_client.hpp b/includes/tacopie/network/tcp_client.hpp index 1b92f0a..c75db55 100644 --- a/includes/tacopie/network/tcp_client.hpp +++ b/includes/tacopie/network/tcp_client.hpp @@ -90,8 +90,9 @@ class tcp_client { //! \param host Hostname of the target server //! \param port Port of the target server //! \param timeout_msecs maximum time to connect (will block until connect succeed or timeout expire). 0 will block undefinitely. If timeout expires, connection fails + //! \param use_encryption enables TLS when set to true //! - void connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs = 0); + void connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs, bool use_encryption); //! //! Disconnect the tcp_client if it was currently connected. diff --git a/includes/tacopie/network/tcp_socket.hpp b/includes/tacopie/network/tcp_socket.hpp index 5f970d7..990b9a9 100644 --- a/includes/tacopie/network/tcp_socket.hpp +++ b/includes/tacopie/network/tcp_socket.hpp @@ -27,6 +27,7 @@ #include #include +#include namespace tacopie { @@ -115,8 +116,9 @@ class tcp_socket { //! \param host Hostname of the target server //! \param port Port of the target server //! \param timeout_msecs maximum time to connect (will block until connect succeed or timeout expire). 0 will block undefinitely. If timeout expires, connection fails + //! \param use_encryption enables TLS when set to true //! - void connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs = 0); + void connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs, bool use_encryption); //! //! Binds the socket to the given host and port. @@ -218,6 +220,12 @@ class tcp_socket { //! type of the socket //! type m_type; + + //! + //! optional tls (Windows only) + //! + tls m_tls; + }; } // namespace tacopie diff --git a/includes/tacopie/network/tls.hpp b/includes/tacopie/network/tls.hpp new file mode 100644 index 0000000..1b35a06 --- /dev/null +++ b/includes/tacopie/network/tls.hpp @@ -0,0 +1,103 @@ +// MIT License +// +// Copyright (c) 2016-2017 Simon Ninon +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#endif + +#pragma comment(lib, "Secur32.lib") +#pragma comment(lib, "Crypt32.Lib") + + +namespace tacopie { + +//! +//! used to force poll to wake up +//! simply make poll watch for read events on one side of the pipe and write to the other side +//! +class tls { +public: + //! ctor + tls(void); + //! dtor + ~tls(void); + + public: + + //! + //! connect + //! + void + establish_connection(const fd_t& socket, const std::string& host); + + //! + //! Encrypt and send data (synchronous) + //! + //! \param socket + //! \param unencrypted data + //! \return Returns amount of unencrypted data sent (caller does not know about encrypted size) + //! + std::size_t + send_encrypted(const fd_t& socket, const std::vector& unencrypted_data); + + //! + //! Receive data from the socket until able to decrypt completely (synchronous). + //! "Completely" here means at least a single encryptable block, may be more. + //! + //! \param socket + //! \return Returns vector with decrypted data. Size is as large as needed. You need to handle this. + //! + std::vector + recv_decrypt(const fd_t& socket); + + //! + //! is encryption active + //! + bool + is_encryption_active() { return m_encryption_active; } + + private: + +#ifdef _WIN32 + CredHandle m_h_credentials; + CtxtHandle m_ph_context; + SecPkgContext_StreamSizes m_stream_sizes; + bool m_encryption_active; + + void get_schannel_credentials(); + void handshake_loop(const fd_t& socket, const std::string &host); + std::string get_sspi_result_string(SECURITY_STATUS SecurityStatus); + +#else +#endif /* _WIN32 */ +}; + +} // namespace tacopie diff --git a/includes/tacopie/utils/logger.hpp b/includes/tacopie/utils/logger.hpp index 5395cf4..4a8f5a0 100644 --- a/includes/tacopie/utils/logger.hpp +++ b/includes/tacopie/utils/logger.hpp @@ -165,7 +165,7 @@ extern std::unique_ptr active_logger; //! //! debug logging -//! convenience function used internaly to call the logger +//! convenience function used internally to call the logger //! //! \param msg message to be logged //! \param file file from which the message is coming @@ -175,7 +175,7 @@ void debug(const std::string& msg, const std::string& file, std::size_t line); //! //! info logging -//! convenience function used internaly to call the logger +//! convenience function used internally to call the logger //! //! \param msg message to be logged //! \param file file from which the message is coming @@ -185,7 +185,7 @@ void info(const std::string& msg, const std::string& file, std::size_t line); //! //! warn logging -//! convenience function used internaly to call the logger +//! convenience function used internally to call the logger //! //! \param msg message to be logged //! \param file file from which the message is coming @@ -195,7 +195,7 @@ void warn(const std::string& msg, const std::string& file, std::size_t line); //! //! error logging -//! convenience function used internaly to call the logger +//! convenience function used internally to call the logger //! //! \param msg message to be logged //! \param file file from which the message is coming diff --git a/msvc15/tacopie.vcxproj b/msvc15/tacopie.vcxproj index 0653f53..4af8c0e 100644 --- a/msvc15/tacopie.vcxproj +++ b/msvc15/tacopie.vcxproj @@ -1,4 +1,4 @@ - + - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 15.0 - {5DC28D31-04A5-4509-8EE3-CF16DE77475B} - Win32Proj - tacopie - 8.1 - - - - StaticLibrary - true - v140 - Unicode - - - StaticLibrary - false - v140 - true - Unicode - - - StaticLibrary - true - v140 - Unicode - - - StaticLibrary - false - v140 - true - Unicode - - - - - - - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - - - - - - Level3 - Disabled - WIN32;_DEBUG;_LIB;_WIN32;%(PreprocessorDefinitions) - ..\includes;%(AdditionalIncludeDirectories) - true - - - Windows - - - - - - - Level3 - Disabled - _DEBUG;_LIB;_WIN32;_WIN64;%(PreprocessorDefinitions) - ..\includes;%(AdditionalIncludeDirectories) - true - - - Windows - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;_WIN32;%(PreprocessorDefinitions) - ..\includes;%(AdditionalIncludeDirectories) - true - - - Windows - true - true - - - - - Level3 - - - MaxSpeed - true - true - NDEBUG;_LIB;_WIN32;_WIN64;%(PreprocessorDefinitions) - ..\includes;%(AdditionalIncludeDirectories) - true - - - Windows - true - true - - - - - - +--> + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.0 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B} + Win32Proj + tacopie + 10.0.19041.0 + + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + StaticLibrary + true + v142 + Unicode + + + StaticLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;_WIN32;SECURITY_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + + + + + + + Level3 + Disabled + _DEBUG;_LIB;_WIN32;_WIN64;__TACOPIE_LOGGING_ENABLED;SECURITY_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + $(IntDir)$(ProjectName).pdb + + + Windows + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;_WIN32;SECURITY_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_LIB;_WIN32;_WIN64;__TACOPIE_LOGGING_ENABLED;SECURITY_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + + + + + + \ No newline at end of file diff --git a/sources/network/common/tcp_socket.cpp b/sources/network/common/tcp_socket.cpp index a83ce70..15307f8 100644 --- a/sources/network/common/tcp_socket.cpp +++ b/sources/network/common/tcp_socket.cpp @@ -24,6 +24,7 @@ #include #include + #ifdef _WIN32 #ifdef __GNUC__ # include // Mingw / gcc on windows @@ -74,7 +75,8 @@ tcp_socket::tcp_socket(void) : m_fd(__TACOPIE_INVALID_FD) , m_host("") , m_port(0) -, m_type(type::UNKNOWN) { __TACOPIE_LOG(debug, "create tcp_socket"); } +, m_type(type::UNKNOWN) + { __TACOPIE_LOG(debug, "create tcp_socket"); } //! //! custom ctor @@ -85,7 +87,8 @@ tcp_socket::tcp_socket(fd_t fd, const std::string& host, std::uint32_t port, typ : m_fd(fd) , m_host(host) , m_port(port) -, m_type(t) { __TACOPIE_LOG(debug, "create tcp_socket"); } +, m_type(t) + { __TACOPIE_LOG(debug, "create tcp_socket"); } //! //! Move constructor @@ -95,11 +98,12 @@ tcp_socket::tcp_socket(tcp_socket&& socket) : m_fd(std::move(socket.m_fd)) , m_host(socket.m_host) , m_port(socket.m_port) -, m_type(socket.m_type) { +, m_type(socket.m_type) +, m_tls(socket.m_tls) { socket.m_fd = __TACOPIE_INVALID_FD; socket.m_type = type::UNKNOWN; - __TACOPIE_LOG(debug, "moved tcp_socket"); + __TACOPIE_LOG(info, "moved tcp_socket"); } //! @@ -111,15 +115,19 @@ tcp_socket::recv(std::size_t size_to_read) { create_socket_if_necessary(); check_or_set_type(type::CLIENT); - std::vector data(size_to_read, 0); - - ssize_t rd_size = ::recv(m_fd, const_cast(data.data()), __TACOPIE_LENGTH(size_to_read), 0); + std::vector data; - if (rd_size == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } + if (m_tls.is_encryption_active()) + data = m_tls.recv_decrypt(m_fd); // ignoring size_to_read, delivering decryptable chunk + else { + data.resize(size_to_read, 0); + ssize_t rd_size = ::recv(m_fd, const_cast(data.data()), __TACOPIE_LENGTH(size_to_read), 0); - if (rd_size == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } + if (rd_size == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } + if (rd_size == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } - data.resize(rd_size); + data.resize(rd_size); + } return data; } @@ -129,7 +137,11 @@ tcp_socket::send(const std::vector& data, std::size_t size_to_write) { create_socket_if_necessary(); check_or_set_type(type::CLIENT); - ssize_t wr_size = ::send(m_fd, data.data(), __TACOPIE_LENGTH(size_to_write), 0); + std::size_t wr_size = SOCKET_ERROR; + if (m_tls.is_encryption_active()) + wr_size = m_tls.send_encrypted(m_fd, data); + else + wr_size = ::send(m_fd, data.data(), __TACOPIE_LENGTH(size_to_write), 0); if (wr_size == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } diff --git a/sources/network/io_service.cpp b/sources/network/io_service.cpp index e7da06e..8bb0b95 100644 --- a/sources/network/io_service.cpp +++ b/sources/network/io_service.cpp @@ -120,16 +120,17 @@ io_service::poll(void) { timeout_ptr = &timeout; #endif /* __TACOPIE_TIMEOUT */ - __TACOPIE_LOG(debug, "polling fds"); + // ex? __TACOPIE_LOG(debug, "polling fds"); if (select(ndfs, &m_rd_set, &m_wr_set, NULL, timeout_ptr) > 0) { process_events(); } else { - __TACOPIE_LOG(debug, "poll woke up, but nothing to process"); + ; + // ex? __TACOPIE_LOG(debug, "poll woke up, but nothing to process"); } } - __TACOPIE_LOG(debug, "stop poll() worker"); + // ex? __TACOPIE_LOG(debug, "stop poll() worker"); } //! diff --git a/sources/network/tcp_client.cpp b/sources/network/tcp_client.cpp index 234865e..4992c7c 100644 --- a/sources/network/tcp_client.cpp +++ b/sources/network/tcp_client.cpp @@ -74,11 +74,11 @@ tcp_client::get_port(void) const { //! void -tcp_client::connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs) { +tcp_client::connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs, bool use_encryption) { if (is_connected()) { __TACOPIE_THROW(warn, "tcp_client is already connected"); } try { - m_socket.connect(host, port, timeout_msecs); + m_socket.connect(host, port, timeout_msecs, use_encryption); m_io_service->track(m_socket); } catch (const tacopie_error& e) { @@ -88,7 +88,7 @@ tcp_client::connect(const std::string& host, std::uint32_t port, std::uint32_t t m_is_connected = true; - __TACOPIE_LOG(info, "tcp_client connected"); + __TACOPIE_LOG(info, "tcp_client connected to " + host + ":" + std::to_string(port)); } void diff --git a/sources/network/unix/unix_tcp_socket.cpp b/sources/network/unix/unix_tcp_socket.cpp index c02f751..c0ba763 100644 --- a/sources/network/unix/unix_tcp_socket.cpp +++ b/sources/network/unix/unix_tcp_socket.cpp @@ -42,10 +42,13 @@ namespace tacopie { void -tcp_socket::connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs) { +tcp_socket::connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs, bool use_encryption) { //! Reset host and port m_host = host; m_port = port; + + if (use_encryption) + __TACOPIE_THROW(error, "encryption not supported"); create_socket_if_necessary(); check_or_set_type(type::CLIENT); diff --git a/sources/network/windows/tls.cpp b/sources/network/windows/tls.cpp new file mode 100644 index 0000000..343d342 --- /dev/null +++ b/sources/network/windows/tls.cpp @@ -0,0 +1,494 @@ +// MIT License +// +// Copyright (c) 2016-2017 Simon Ninon +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! guard for bulk content integration depending on how user integrates the library +#ifdef _WIN32 + +#include +#include + +#include + +#include +#include +#include +#include +#include + +// There is no handling of connection loss or renegotiate here. It is left to the above +// wrapper to initialte a reconnect in such cases. + +namespace tacopie { + +//! +//! ctor & dtor +//! +tls::tls(void) +: m_encryption_active(false) { + __TACOPIE_LOG(debug, "tls constructed"); + } + +tls::~tls(void) { +} + +//! +//! establish a secure connection +//! +void +tls::establish_connection(const fd_t &socket, const std::string& host) { + get_schannel_credentials(); + handshake_loop(socket, host); + m_encryption_active = true; +} + +//! +//! get schannel credentials +//! +void +tls::get_schannel_credentials() { + TimeStamp lifetime; + SCHANNEL_CRED credentials_data; + + ZeroMemory(&credentials_data, sizeof(credentials_data)); + credentials_data.dwVersion = SCHANNEL_CRED_VERSION; + // opportunity to restrict used protocols on client side. Suggest to use this only for + // tests and implement needed restrictions on server. + // credData.grbitEnabledProtocols = SP_PROT_TLS1; + + SECURITY_STATUS security_status = AcquireCredentialsHandle( //gets the credentials necessary to make use of the ssp + NULL, //default principle + UNISP_NAME, //name of schannel ssp + SECPKG_CRED_OUTBOUND, //states that the client will use the returned credential + NULL, //use current logon id instead of searching for previous one + &credentials_data, //protocol specific data + NULL, //default + NULL, //default + &m_h_credentials, //where the handle will be stored + &lifetime //stores the time limit of the credential + ); + + if (security_status != SEC_E_OK) { + __TACOPIE_THROW(error, std::string("AcquireCredentialsHandle result: ") + get_sspi_result_string(security_status)); + } + else { + __TACOPIE_LOG(debug, "credentials acquired successfully"); + + } +} + +void +tls::handshake_loop(const fd_t& socket, const std::string& host) { + + TimeStamp lifetime; + SecBufferDesc out_buffer_desc; + SecBuffer out_buffer[1]; + SecBufferDesc in_buffer_desc; + SecBuffer in_buffer[2]; + ULONG ul_context_attributes; + DWORD flags; + + out_buffer_desc.ulVersion = SECBUFFER_VERSION; + out_buffer_desc.cBuffers = 1; + out_buffer_desc.pBuffers = out_buffer; + + out_buffer[0].cbBuffer = 0; // size(cbBuff) is 0 and data(pvBuff) is null because ISC_ALLOC_MEM was + out_buffer[0].BufferType = SECBUFFER_TOKEN; // was specified and will automatically create memory and fill the buffer + out_buffer[0].pvBuffer = NULL; + + int wchars_num = MultiByteToWideChar(CP_UTF8, 0, host.c_str(), -1, NULL, 0); + std::vector whost(wchars_num); + MultiByteToWideChar(CP_UTF8, 0, host.c_str(), -1, whost.data(), wchars_num); + + // Meaning of flags and error codes explained here: https://docs.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-initializesecuritycontextw + flags = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_STREAM | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR; + + SECURITY_STATUS security_status = InitializeSecurityContext( + &m_h_credentials, // credentials acquired by acquireCredentialsHandle + NULL, // in the first call this is NULL, afterwards use hcText parameter variable + whost.data(), // name of the server + flags, // bit flags that state how the security context will function + 0, // this argument is reserved and left as 0 + SECURITY_NATIVE_DREP, // how the data is represented. In schannel this argument is not used and set to 0 + NULL, // this is the buffer that will be received from the server. On the first call this is NULL + 0, // reserved and set to 0 + &m_ph_context, // receives the context handle. With Schannel, after the first call, this must be NULL and + // arg2 must take phContext + &out_buffer_desc, // buffer where the token will be stored. This will be sent to the server later + &ul_context_attributes, // this is where the set of bit flags will be received. These flags indicate the attributes of the context + &lifetime); + + if (security_status != SEC_I_CONTINUE_NEEDED) { + __TACOPIE_THROW(error, get_sspi_result_string(security_status)); + } + + bool process_extra_data = false; + int bytes_received_count = 0; + char* pc_token = NULL; + std::vector buffer(4000); + + while (security_status != SEC_E_OK) { + if (security_status == SEC_I_CONTINUE_NEEDED && !process_extra_data) { + if (out_buffer[0].cbBuffer > 0) { + pc_token = static_cast(out_buffer[0].pvBuffer); + ::send(socket, pc_token, out_buffer[0].cbBuffer, 0); + FreeContextBuffer(out_buffer[0].pvBuffer); + } + bytes_received_count = ::recv(socket, buffer.data(), static_cast (buffer.size()), 0); + } + else if (security_status == SEC_E_INCOMPLETE_MESSAGE) { + if (in_buffer[1].BufferType == SECBUFFER_MISSING) { + int missing_data_count = in_buffer[1].cbBuffer; + __TACOPIE_LOG(info, std::string("secbuffer_missing: " + missing_data_count)); + bytes_received_count = ::recv(socket, buffer.data(), missing_data_count, 0); + } + } + + in_buffer_desc.cBuffers = 2; + in_buffer_desc.pBuffers = in_buffer; + in_buffer_desc.ulVersion = SECBUFFER_VERSION; + + in_buffer[0].cbBuffer = bytes_received_count; + in_buffer[0].pvBuffer = buffer.data(); + in_buffer[0].BufferType = SECBUFFER_TOKEN; + + in_buffer[1].cbBuffer = 0; + in_buffer[1].pvBuffer = NULL; + in_buffer[1].BufferType = SECBUFFER_EMPTY; + + out_buffer_desc.cBuffers = 1; + out_buffer_desc.pBuffers = out_buffer; + out_buffer_desc.ulVersion = SECBUFFER_VERSION; + + out_buffer[0].cbBuffer = 0; + out_buffer[0].pvBuffer = NULL; + out_buffer[0].BufferType = SECBUFFER_VERSION; + + // https: //docs.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-initializesecuritycontextw + security_status = InitializeSecurityContext( + &m_h_credentials, + &m_ph_context, + NULL, + flags, + 0, + SECURITY_NATIVE_DREP, + &in_buffer_desc, + 0, + NULL, + &out_buffer_desc, + &ul_context_attributes, + &lifetime); + + process_extra_data = false; + + // This comment from curl is the best description I found of what can happen here with SECBUFFER_EXTRA: + // There are two cases where we could be getting extra data here: + // 1) If we're renegotiating a connection and the handshake is already + // complete (from the server perspective), it can [contain] encrypted app data + // (not handshake data) in an extra buffer at this point. + // 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a + // connection and this extra data is part of the handshake. + // We should process the data immediately; waiting for the socket to + // be ready may fail since the server is done sending handshake data. + switch (security_status) { + + case SEC_I_CONTINUE_NEEDED: + if (in_buffer[1].BufferType == SECBUFFER_EXTRA) { + __TACOPIE_LOG(info, std::string("CONTINUE_NEEDED: Extra data in in_buffer")); + // shift exta bytes to the beginning of the input buffer + if (in_buffer[1].cbBuffer > static_cast(bytes_received_count)) + __TACOPIE_THROW(error, "part of buffer larger than whole"); + memmove(buffer.data(), buffer.data() + (static_cast(bytes_received_count) - in_buffer[1].cbBuffer), in_buffer[1].cbBuffer); + bytes_received_count = in_buffer[1].cbBuffer; + process_extra_data = true; + } + break; + + case SEC_E_OK: + if (out_buffer[0].cbBuffer > 0) { + __TACOPIE_LOG(info, std::string("sending leftover bytes in output buffer")); + pc_token = static_cast(out_buffer[0].pvBuffer); + ::send(socket, pc_token, out_buffer[0].cbBuffer, 0); + FreeContextBuffer(out_buffer[0].pvBuffer); + } + if (in_buffer[1].BufferType == SECBUFFER_EXTRA) { + __TACOPIE_LOG(info, std::string("SEC_E_OK: Extra data in in_buffer")); + // These bytes need to be decrypted [currently not handled] + __TACOPIE_LOG(warn, std::string("Extra bytes to be decrypted") + std::to_string(in_buffer[1].cbBuffer)); + } + break; + + default: + __TACOPIE_THROW(error, get_sspi_result_string(security_status)); + break; + } + } // while !SEC_E_OK + + __TACOPIE_LOG(info, "secure connection successfully established"); +} + +//! +//! send encrypted data +//! +std::size_t +tls::send_encrypted(const fd_t& socket, const std::vector& unencrypted_data) { + + SECURITY_STATUS security_status = QueryContextAttributes(&m_ph_context, SECPKG_ATTR_STREAM_SIZES, &m_stream_sizes); + + if (security_status != SEC_E_OK) { + __TACOPIE_LOG(warn, std::string("QueryContextAttributes result: ") + get_sspi_result_string(security_status)); + } + + int max_out_size = m_stream_sizes.cbHeader + m_stream_sizes.cbMaximumMessage + m_stream_sizes.cbTrailer; + std::vector buffer(max_out_size); + + std::size_t unencrypted_bytes_written = 0; + + while (unencrypted_bytes_written < unencrypted_data.size()) { + + std::size_t unencrypted_bytes_left = unencrypted_data.size() - unencrypted_bytes_written; + std::size_t current_unencrypted_chunk = unencrypted_bytes_left > m_stream_sizes.cbMaximumMessage ? m_stream_sizes.cbMaximumMessage : unencrypted_bytes_left; + if (unencrypted_bytes_left > m_stream_sizes.cbMaximumMessage) + unencrypted_bytes_left += 0; + + //copy the unencrypted data just after the header bytes to the buffer + memcpy(buffer.data() + m_stream_sizes.cbHeader, unencrypted_data.data() + unencrypted_bytes_written, current_unencrypted_chunk); + + SecBufferDesc message_buffer; + SecBuffer buffers[4]; + + message_buffer.cBuffers = 4; + message_buffer.pBuffers = buffers; + message_buffer.ulVersion = SECBUFFER_VERSION; + + buffers[0].cbBuffer = m_stream_sizes.cbHeader; + buffers[0].pvBuffer = buffer.data(); + buffers[0].BufferType = SECBUFFER_STREAM_HEADER; + + buffers[1].cbBuffer = static_cast(current_unencrypted_chunk); + buffers[1].pvBuffer = buffer.data() + m_stream_sizes.cbHeader; + buffers[1].BufferType = SECBUFFER_DATA; + + buffers[2].cbBuffer = m_stream_sizes.cbTrailer; + buffers[2].pvBuffer = buffer.data() + m_stream_sizes.cbHeader + static_cast(current_unencrypted_chunk); + buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + + buffers[3].cbBuffer = 0; + buffers[3].pvBuffer = NULL; + buffers[3].BufferType = SECBUFFER_EMPTY; + + // https://docs.microsoft.com/en-us/windows/win32/secauthn/encryptmessage--schannel + security_status = EncryptMessage(&m_ph_context, 0, &message_buffer, 0); + if (security_status != SEC_E_OK) { + __TACOPIE_THROW(error, std::string("EncryptMessage result: ") + get_sspi_result_string(security_status)); + } + + int encrypted_size = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer; + + ssize_t send_result = ::send(socket, buffer.data(), encrypted_size, 0); + if (send_result == SOCKET_ERROR) + return send_result; + + unencrypted_bytes_written += current_unencrypted_chunk; + } // while bytes left to send + + return unencrypted_bytes_written; +} + +//! +//! receive and decrypt +//! +std::vector +tls::recv_decrypt(const fd_t& socket) { + + int encrypted_bytes = 0; + int decrypted_bytes = 0; + const std::size_t buffer_increment_size = 0x1000; + std::vector encrypted_data; + std::vector decrypted_data; + SECURITY_STATUS security_status = SEC_E_OK; + SecBufferDesc buffer_desc; + SecBuffer sec_buffer[4]; + + // read more data until able to decrypt or decryptable data block not complete (in + // case part of next block has already been received) + while (decrypted_bytes == 0 || security_status == SEC_E_INCOMPLETE_MESSAGE) { + encrypted_data.resize(encrypted_bytes + buffer_increment_size); // buffer needs to grow on incomplete messages + encrypted_bytes += ::recv(socket, const_cast(encrypted_data.data()) + encrypted_bytes, static_cast(buffer_increment_size), 0); + + if (encrypted_bytes == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } + if (encrypted_bytes == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } + + __TACOPIE_LOG(debug, std::string("encrypted bytes in buffer: ") + std::to_string(encrypted_bytes)); + security_status = SEC_E_OK; + + while (encrypted_bytes != 0 && security_status != SEC_E_INCOMPLETE_MESSAGE) { + buffer_desc.cBuffers = 4; + buffer_desc.pBuffers = sec_buffer; + buffer_desc.ulVersion = SECBUFFER_VERSION; + + sec_buffer[0].cbBuffer = encrypted_bytes; + sec_buffer[0].pvBuffer = encrypted_data.data(); // not decrypted in place as documented + sec_buffer[0].BufferType = SECBUFFER_DATA; + + sec_buffer[1].BufferType = SECBUFFER_EMPTY; + sec_buffer[2].BufferType = SECBUFFER_EMPTY; + sec_buffer[3].BufferType = SECBUFFER_EMPTY; + + // https://docs.microsoft.com/en-us/windows/win32/secauthn/decryptmessage--general + // https://docs.microsoft.com/en-us/windows/win32/secauthn/decryptmessage--schannel + security_status = DecryptMessage(&m_ph_context, &buffer_desc, 0, NULL); + + switch (security_status) { + case SEC_E_OK: { + __TACOPIE_LOG(debug, "data successfully decrypted"); + + encrypted_bytes = 0; // all bytes used + + for (int i = 0; i < 4; i++) { + switch (sec_buffer[i].BufferType) { + case SECBUFFER_DATA: + // append decrypted bytes to output buffer. May be 0 bytes according to doc. + if (sec_buffer[i].cbBuffer) { + decrypted_data.resize(decrypted_bytes + sec_buffer[i].cbBuffer); + memcpy(const_cast(decrypted_data.data()) + decrypted_bytes, sec_buffer[i].pvBuffer, sec_buffer[i].cbBuffer); + decrypted_bytes += sec_buffer[i].cbBuffer; + FreeContextBuffer(sec_buffer[i].pvBuffer); + } + break; + case SECBUFFER_EXTRA: + // When a block has been decrypted, there may be data already read for the next block in the buffer. + __TACOPIE_LOG(info, std::string("Data for next encryptable block: " + std::to_string(sec_buffer[i].cbBuffer))); + memmove(const_cast(encrypted_data.data()), sec_buffer[i].pvBuffer, sec_buffer[i].cbBuffer); + encrypted_bytes = sec_buffer[i].cbBuffer; + //FreeContextBuffer(sec_buffer[i].pvBuffer); // triggers exception + break; + default: + break; + } + } + break; + } + + case SEC_E_DECRYPT_FAILURE: + for (int i = 0; i < 4; i++) { + if (sec_buffer[i].BufferType == SECBUFFER_ALERT) { + __TACOPIE_LOG(warn, std::string("SECBUFFER_ALERT: ") + std::string((char*) sec_buffer[i].pvBuffer)); + } + } + __TACOPIE_THROW(warn, std::string("failed to decrypt: ") + get_sspi_result_string(security_status)); + break; + + case SEC_E_INCOMPLETE_MESSAGE: + // we need to read more data + __TACOPIE_LOG(debug, get_sspi_result_string(security_status)); + break; + + default: + __TACOPIE_THROW(warn, get_sspi_result_string(security_status)); + } // switch + } // while encrypted bytes && security_status != SEC_E_INCOMPLETE_MESSAGE + } // while decrypted_bytes == 0 || security_status == SEC_E_INCOMPLETE_MESSAGE + + return decrypted_data; +} + +std::string +tls::get_sspi_result_string(SECURITY_STATUS security_status) { + + std::stringstream hexval; + hexval << "0x" << std::setfill('0') << std::setw(8) << std::hex << security_status; + std::string str_message = hexval.str(); + + // https://docs.microsoft.com/en-us/windows/win32/secauthn/sspi-status-codes + std::pair security_status_as_string[] { + {CERT_E_CHAINING, "CERT_E_CHAINING - A certificate chain could not be build to a trusted root authority."}, + {CERT_E_CN_NO_MATCH, "CERT_E_CN_NO_MATCH - The certificate's CN name does not match the passed value."}, + {CERT_E_CRITICAL, "CERT_E_CRITICAL - A certificate contains an unknown extension that is marked 'critical'."}, + {CERT_E_EXPIRED, "CERT_E_EXPIRED - A required certificate is not within its validity period when verifying against the current system clock or the timestamp in the signed file."}, + {CERT_E_INVALID_NAME, "CERT_E_INVALID_NAME - The certificate has an invalid name. The name is not included in the permitted list or is explicitly excluded."}, + {CERT_E_INVALID_POLICY, "CERT_E_INVALID_POLICY - The certificate has invalid policy."}, + {CERT_E_ISSUERCHAINING, "CERT_E_ISSUERCHAINING - A parent of a given certificate in fact did not issue that child certificate."}, + {CERT_E_MALFORMED, "CERT_E_MALFORMED - A certificate is missing or has an empty value for an important field, such as a subject or issuer name."}, + {CERT_E_PATHLENCONST, "CERT_E_PATHLENCONST - A Path length constraint in the certification chain has been violated."}, + {CERT_E_PURPOSE, "CERT_E_PURPOSE - A certificate being used for a purpose other than the ones specified by its CA."}, + {CERT_E_REVOCATION_FAILURE, "CERT_E_REVOCATION_FAILURE - The revocation process could not continue - the certificate(s) could not be checked."}, + {CERT_E_REVOKED, "CERT_E_REVOKED - A certificate was explicitly revoked by its issuer."}, + {CERT_E_ROLE, "CERT_E_ROLE - A certificate that can only be used as an end-entity is being used as a CA or vice versa."}, + {CERT_E_UNTRUSTEDCA, "CERT_E_UNTRUSTEDCA - A certification chain processed correctly, but one of the CA certificates is not trusted by the policy provider."}, + {CERT_E_UNTRUSTEDTESTROOT, "CERT_E_UNTRUSTEDTESTROOT - The certification path terminates with the test root which is not trusted with the current policy settings."}, + {CERT_E_VALIDITYPERIODNESTING, "CERT_E_VALIDITYPERIODNESTING - The validity periods of the certification chain do not nest correctly."}, + {CERT_E_WRONG_USAGE, "CERT_E_WRONG_USAGE - The certificate is not valid for the requested usage."}, + + {SEC_E_BUFFER_TOO_SMALL, "SEC_E_BUFFER_TOO_SMALL - The message buffer is too small."}, + {SEC_I_CONTEXT_EXPIRED, "SEC_I_CONTEXT_EXPIRED - The message sender has finished using the connection and has initiated a shutdown."}, + {SEC_I_COMPLETE_AND_CONTINUE, "SEC_I_COMPLETE_AND_CONTINUE - The function completed successfully, but the application must call both CompleteAuthToken and then either InitializeSecurityContext or AcceptSecurityContext again to complete the context."}, + {SEC_I_COMPLETE_NEEDED, "SEC_I_COMPLETE_NEEDED - The function completed successfully, but you must call the CompleteAuthToken function on the final message."}, + {SEC_I_CONTINUE_NEEDED, "SEC_I_CONTINUE_NEEDED - The function completed successfully, but you must call this function again to complete the context."}, + {SEC_E_DECRYPT_FAILURE, "SEC_E_DECRYPT_FAILURE - The specified data could not be decrypted."}, + {SEC_E_ENCRYPT_FAILURE, "SEC_E_ENCRYPT_FAILURE - The specified data could not be encrypted."}, + {SEC_I_INCOMPLETE_CREDENTIALS, "SEC_I_INCOMPLETE_CREDENTIALS - The credentials supplied were not complete and could not be verified. Additional information can be returned from the context."}, + {SEC_E_INCOMPLETE_MESSAGE, "SEC_E_INCOMPLETE_MESSAGE - The data in the input buffer is incomplete. The application needs to read more data from the server and call DecryptMessage (General) again."}, + {SEC_E_INVALID_HANDLE, "SEC_E_INVALID_HANDLE - A context handle that is not valid was specified in the phContext parameter."}, + {SEC_E_INVALID_TOKEN, "SEC_E_INVALID_TOKEN - The buffers are of the wrong type or no buffer of type SECBUFFER_DATA was found."}, + {SEC_E_INSUFFICIENT_MEMORY, "SEC_E_INSUFFICIENT_MEMORY - Not enough memory is available to complete the request."}, + {SEC_E_INTERNAL_ERROR, "SEC_E_INTERNAL_ERROR - An error occurred that did not map to an SSPI error code."}, + {SEC_E_MESSAGE_ALTERED, "SEC_E_MESSAGE_ALTERED - The message has been altered."}, + {SEC_E_NO_AUTHENTICATING_AUTHORITY, "SEC_E_NO_AUTHENTICATING_AUTHORITY - No authority could be contacted for authentication."}, + {SEC_E_NO_CREDENTIALS, "SEC_E_NO_CREDENTIALS - No credentials are available."}, + {SEC_E_NOT_OWNER, "SEC_E_NOT_OWNER - The caller of the function does not own the credentials."}, + {SEC_E_OUT_OF_SEQUENCE, "SEC_E_OUT_OF_SEQUENCE - The message was not received in the correct sequence."}, + {SEC_I_RENEGOTIATE, "SEC_I_RENEGOTIATE - The remote party requires a new handshake sequence or the application has just initiated a shutdown."}, + {SEC_E_SECPKG_NOT_FOUND, "SEC_E_SECPKG_NOT_FOUND - The security package was not recognized."}, + {SEC_E_TARGET_UNKNOWN, "SEC_E_TARGET_UNKNOWN - The target was not recognized."}, + {SEC_E_UNKNOWN_CREDENTIALS, "SEC_E_UNKNOWN_CREDENTIALS - The credentials provided were not recognized."}, + {SEC_E_UNSUPPORTED_FUNCTION, "SEC_E_UNSUPPORTED_FUNCTION - The requested function is not supported."}, + {SEC_E_WRONG_PRINCIPAL, "SEC_E_WRONG_PRINCIPAL - Certificate check failed."}, + {SEC_E_OK, "SEC_E_OK - The operation completed successfully."}, + {TRUST_E_ACTION_UNKNOWN, "TRUST_E_ACTION_UNKNOWN - The trust verification action specified is not supported by the specified trust provider."}, + {TRUST_E_BAD_DIGEST, "TRUST_E_BAD_DIGEST - The digital signature of the object did not verify."}, + {TRUST_E_BASIC_CONSTRAINTS, "TRUST_E_BASIC_CONSTRAINTS - A certificate's basic constraint extension has not been observed."}, + {TRUST_E_CERT_SIGNATURE, "TRUST_E_CERT_SIGNATURE - The signature of the certificate cannot be verified."}, + {TRUST_E_COUNTER_SIGNER, "TRUST_E_COUNTER_SIGNER - One of the counter signatures was invalid."}, + {TRUST_E_EXPLICIT_DISTRUST, "TRUST_E_EXPLICIT_DISTRUST - The certificate was explicitly marked as untrusted by the user."}, + {TRUST_E_FAIL, "TRUST_E_FAIL - Generic trust failure."}, + {TRUST_E_FINANCIAL_CRITERIA, "TRUST_E_FINANCIAL_CRITERIA - The certificate does not meet or contain the Authenticode(tm) financial extensions."}, + {TRUST_E_NOSIGNATURE, "TRUST_E_NOSIGNATURE - No signature was present in the subject."}, + {TRUST_E_NO_SIGNER_CERT, "TRUST_E_NO_SIGNER_CERT - The certificate for the signer of the message is invalid or not found."}, + {TRUST_E_PROVIDER_UNKNOWN, "TRUST_E_PROVIDER_UNKNOWN - Unknown trust provider."}, + {TRUST_E_SUBJECT_FORM_UNKNOWN, "TRUST_E_SUBJECT_FORM_UNKNOWN - The form specified for the subject is not one supported or known by the specified trust provider."}, + {TRUST_E_SUBJECT_NOT_TRUSTED, "TRUST_E_SUBJECT_NOT_TRUSTED - The subject is not trusted for the specified action."}, + {TRUST_E_SYSTEM_ERROR, "TRUST_E_SYSTEM_ERROR - A system-level error occurred while verifying trust."}, + {TRUST_E_TIME_STAMP, "TRUST_E_TIME_STAMP - The timestamp signature and/or certificate could not be verified or is malformed."} + }; + + for (int i = 0; i < (sizeof(security_status_as_string) / sizeof(*security_status_as_string)); i++) { + if (security_status == security_status_as_string[i].first) { + str_message = security_status_as_string[i].second; + break; + } + } + return str_message; +}; + +} // namespace tacopie + +#endif /* _WIN32 */ diff --git a/sources/network/windows/windows_tcp_socket.cpp b/sources/network/windows/windows_tcp_socket.cpp index ad867c2..ce8930f 100644 --- a/sources/network/windows/windows_tcp_socket.cpp +++ b/sources/network/windows/windows_tcp_socket.cpp @@ -54,7 +54,7 @@ namespace tacopie { void -tcp_socket::connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs) { +tcp_socket::connect(const std::string& host, std::uint32_t port, std::uint32_t timeout_msecs, bool use_encryption) { //! Reset host and port m_host = host; m_port = port; @@ -163,6 +163,10 @@ tcp_socket::connect(const std::string& host, std::uint32_t port, std::uint32_t t __TACOPIE_THROW(error, "connect() timed out"); } } + if (use_encryption) { + // arriving here means successful connect + m_tls.establish_connection(m_fd, m_host); + } } //! From 54653b4c446fd8a7b70a1254d82c14f05fb6f4fe Mon Sep 17 00:00:00 2001 From: masmartin <86951372+masmartin@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:15:30 +0200 Subject: [PATCH 2/3] - fixed problem evaluating return of ::recv - clarify return value of ::send --- sources/network/common/tcp_socket.cpp | 6 +++--- sources/network/windows/tls.cpp | 29 ++++++++++++++++++--------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/sources/network/common/tcp_socket.cpp b/sources/network/common/tcp_socket.cpp index 15307f8..a5e8aae 100644 --- a/sources/network/common/tcp_socket.cpp +++ b/sources/network/common/tcp_socket.cpp @@ -140,10 +140,10 @@ tcp_socket::send(const std::vector& data, std::size_t size_to_write) { std::size_t wr_size = SOCKET_ERROR; if (m_tls.is_encryption_active()) wr_size = m_tls.send_encrypted(m_fd, data); - else + else { wr_size = ::send(m_fd, data.data(), __TACOPIE_LENGTH(size_to_write), 0); - - if (wr_size == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } + if (wr_size == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } + } return wr_size; } diff --git a/sources/network/windows/tls.cpp b/sources/network/windows/tls.cpp index 343d342..15d24ac 100644 --- a/sources/network/windows/tls.cpp +++ b/sources/network/windows/tls.cpp @@ -149,16 +149,23 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { if (security_status == SEC_I_CONTINUE_NEEDED && !process_extra_data) { if (out_buffer[0].cbBuffer > 0) { pc_token = static_cast(out_buffer[0].pvBuffer); - ::send(socket, pc_token, out_buffer[0].cbBuffer, 0); + int send_result = ::send(socket, pc_token, out_buffer[0].cbBuffer, 0); + if (send_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } FreeContextBuffer(out_buffer[0].pvBuffer); } - bytes_received_count = ::recv(socket, buffer.data(), static_cast (buffer.size()), 0); + int recv_result = bytes_received_count = ::recv(socket, buffer.data(), static_cast (buffer.size()), 0); + if (recv_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } + if (recv_result == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } + bytes_received_count = recv_result; } else if (security_status == SEC_E_INCOMPLETE_MESSAGE) { if (in_buffer[1].BufferType == SECBUFFER_MISSING) { int missing_data_count = in_buffer[1].cbBuffer; __TACOPIE_LOG(info, std::string("secbuffer_missing: " + missing_data_count)); - bytes_received_count = ::recv(socket, buffer.data(), missing_data_count, 0); + int recv_result = ::recv(socket, buffer.data(), missing_data_count, 0); + if (recv_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } + if (recv_result == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } + bytes_received_count = recv_result; } } @@ -226,7 +233,8 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { if (out_buffer[0].cbBuffer > 0) { __TACOPIE_LOG(info, std::string("sending leftover bytes in output buffer")); pc_token = static_cast(out_buffer[0].pvBuffer); - ::send(socket, pc_token, out_buffer[0].cbBuffer, 0); + int send_result = ::send(socket, pc_token, out_buffer[0].cbBuffer, 0); + if (send_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } FreeContextBuffer(out_buffer[0].pvBuffer); } if (in_buffer[1].BufferType == SECBUFFER_EXTRA) { @@ -304,8 +312,7 @@ tls::send_encrypted(const fd_t& socket, const std::vector& unencrypted_dat int encrypted_size = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer; ssize_t send_result = ::send(socket, buffer.data(), encrypted_size, 0); - if (send_result == SOCKET_ERROR) - return send_result; + if (send_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } unencrypted_bytes_written += current_unencrypted_chunk; } // while bytes left to send @@ -332,13 +339,15 @@ tls::recv_decrypt(const fd_t& socket) { // case part of next block has already been received) while (decrypted_bytes == 0 || security_status == SEC_E_INCOMPLETE_MESSAGE) { encrypted_data.resize(encrypted_bytes + buffer_increment_size); // buffer needs to grow on incomplete messages - encrypted_bytes += ::recv(socket, const_cast(encrypted_data.data()) + encrypted_bytes, static_cast(buffer_increment_size), 0); + int recv_result = ::recv(socket, const_cast(encrypted_data.data()) + encrypted_bytes, static_cast(buffer_increment_size), 0); - if (encrypted_bytes == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } - if (encrypted_bytes == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } + if (recv_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } + if (recv_result == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } + + encrypted_bytes += recv_result; __TACOPIE_LOG(debug, std::string("encrypted bytes in buffer: ") + std::to_string(encrypted_bytes)); - security_status = SEC_E_OK; + security_status = SEC_E_OK; // allow entry of decrypt loop while (encrypted_bytes != 0 && security_status != SEC_E_INCOMPLETE_MESSAGE) { buffer_desc.cBuffers = 4; From 9124e10899aee64dc7c571c45ddcdb6e3f788c64 Mon Sep 17 00:00:00 2001 From: masmartin <86951372+masmartin@users.noreply.github.com> Date: Mon, 30 Aug 2021 10:49:30 +0200 Subject: [PATCH 3/3] changes from review implemented - Changes implemented - Fixed a problem in recv_decrypt leading to buffer growth --- includes/tacopie/network/tls.hpp | 23 +- msvc15/tacopie.vcxproj | 351 +++++++++++++------------- msvc19/tacopie.sln | 28 ++ msvc19/tacopie.vcxproj | 198 +++++++++++++++ sources/network/common/tcp_socket.cpp | 10 +- sources/network/io_service.cpp | 7 +- sources/network/tcp_client.cpp | 4 +- sources/network/windows/tls.cpp | 153 ++++++----- 8 files changed, 517 insertions(+), 257 deletions(-) create mode 100644 msvc19/tacopie.sln create mode 100644 msvc19/tacopie.vcxproj diff --git a/includes/tacopie/network/tls.hpp b/includes/tacopie/network/tls.hpp index 1b35a06..fd14564 100644 --- a/includes/tacopie/network/tls.hpp +++ b/includes/tacopie/network/tls.hpp @@ -1,6 +1,11 @@ -// MIT License // -// Copyright (c) 2016-2017 Simon Ninon +// Copyright (c) 2021 Martin Unger for estos GmbH +// +// Based on code available at https://github.com/John-Ad/Schannel-https-implementation +// No license attached as of 2021-08-27. +// +// Code was substantially modified for our purpose and these changes are put under the +// following license. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -48,7 +53,7 @@ class tls { //! ctor tls(void); //! dtor - ~tls(void); + virtual ~tls(void); public: @@ -82,18 +87,24 @@ class tls { //! is encryption active //! bool - is_encryption_active() { return m_encryption_active; } + is_encryption_active() const { return m_encryption_active; } private: #ifdef _WIN32 - CredHandle m_h_credentials; - CtxtHandle m_ph_context; + CredHandle m_credentials; + CtxtHandle m_context; SecPkgContext_StreamSizes m_stream_sizes; bool m_encryption_active; + // we need to preserve encrypted data received over several calls of recv_decrypt + std::vector m_encrypted_data; + int m_encrypted_bytes = 0; + void get_schannel_credentials(); void handshake_loop(const fd_t& socket, const std::string &host); + int tls_receive(SOCKET socket, char* buffer, int length); + int tls_send(SOCKET socket, const char* buffer, int length); std::string get_sspi_result_string(SECURITY_STATUS SecurityStatus); #else diff --git a/msvc15/tacopie.vcxproj b/msvc15/tacopie.vcxproj index 4af8c0e..0653f53 100644 --- a/msvc15/tacopie.vcxproj +++ b/msvc15/tacopie.vcxproj @@ -1,4 +1,4 @@ - + - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 15.0 - {5DC28D31-04A5-4509-8EE3-CF16DE77475B} - Win32Proj - tacopie - 10.0.19041.0 - - - - StaticLibrary - true - v140 - Unicode - - - StaticLibrary - false - v140 - true - Unicode - - - StaticLibrary - true - v142 - Unicode - - - StaticLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - - - - - - Level3 - Disabled - WIN32;_DEBUG;_LIB;_WIN32;SECURITY_WIN32;%(PreprocessorDefinitions) - ..\includes;%(AdditionalIncludeDirectories) - true - - - Windows - - - - - - - Level3 - Disabled - _DEBUG;_LIB;_WIN32;_WIN64;__TACOPIE_LOGGING_ENABLED;SECURITY_WIN32;%(PreprocessorDefinitions) - ..\includes;%(AdditionalIncludeDirectories) - true - $(IntDir)$(ProjectName).pdb - - - Windows - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;_WIN32;SECURITY_WIN32;%(PreprocessorDefinitions) - ..\includes;%(AdditionalIncludeDirectories) - true - - - Windows - true - true - - - - - Level3 - - - MaxSpeed - true - true - NDEBUG;_LIB;_WIN32;_WIN64;__TACOPIE_LOGGING_ENABLED;SECURITY_WIN32;%(PreprocessorDefinitions) - ..\includes;%(AdditionalIncludeDirectories) - true - - - Windows - true - true - - - - - - \ No newline at end of file +--> + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.0 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B} + Win32Proj + tacopie + 8.1 + + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + + + + + + + Level3 + Disabled + _DEBUG;_LIB;_WIN32;_WIN64;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_LIB;_WIN32;_WIN64;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + + + + + + diff --git a/msvc19/tacopie.sln b/msvc19/tacopie.sln new file mode 100644 index 0000000..7008456 --- /dev/null +++ b/msvc19/tacopie.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tacopie", "tacopie.vcxproj", "{5DC28D31-04A5-4509-8EE3-CF16DE77475B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5DC28D31-04A5-4509-8EE3-CF16DE77475B}.Debug|x64.ActiveCfg = Debug|x64 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B}.Debug|x64.Build.0 = Debug|x64 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B}.Debug|x86.ActiveCfg = Debug|Win32 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B}.Debug|x86.Build.0 = Debug|Win32 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B}.Release|x64.ActiveCfg = Release|x64 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B}.Release|x64.Build.0 = Release|x64 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B}.Release|x86.ActiveCfg = Release|Win32 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/msvc19/tacopie.vcxproj b/msvc19/tacopie.vcxproj new file mode 100644 index 0000000..6713e1e --- /dev/null +++ b/msvc19/tacopie.vcxproj @@ -0,0 +1,198 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.0 + {5DC28D31-04A5-4509-8EE3-CF16DE77475B} + Win32Proj + tacopie + 10.0.19041.0 + + + + StaticLibrary + true + v142 + Unicode + + + StaticLibrary + false + v142 + true + Unicode + + + StaticLibrary + true + v142 + Unicode + + + StaticLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;_WIN32;SECURITY_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + + + + + + + Level3 + Disabled + _DEBUG;_LIB;_WIN32;_WIN64;__TACOPIE_LOGGING_ENABLED;SECURITY_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + $(IntDir)$(ProjectName).pdb + + + Windows + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;_WIN32;SECURITY_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_LIB;_WIN32;_WIN64;__TACOPIE_LOGGING_ENABLED;SECURITY_WIN32;%(PreprocessorDefinitions) + ..\includes;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + + + + + + \ No newline at end of file diff --git a/sources/network/common/tcp_socket.cpp b/sources/network/common/tcp_socket.cpp index a5e8aae..dba23b1 100644 --- a/sources/network/common/tcp_socket.cpp +++ b/sources/network/common/tcp_socket.cpp @@ -103,7 +103,7 @@ tcp_socket::tcp_socket(tcp_socket&& socket) socket.m_fd = __TACOPIE_INVALID_FD; socket.m_type = type::UNKNOWN; - __TACOPIE_LOG(info, "moved tcp_socket"); + __TACOPIE_LOG(debug, "moved tcp_socket"); } //! @@ -117,8 +117,10 @@ tcp_socket::recv(std::size_t size_to_read) { std::vector data; - if (m_tls.is_encryption_active()) + if (m_tls.is_encryption_active()) { + // called function throws errors from tls class data = m_tls.recv_decrypt(m_fd); // ignoring size_to_read, delivering decryptable chunk + } else { data.resize(size_to_read, 0); ssize_t rd_size = ::recv(m_fd, const_cast(data.data()), __TACOPIE_LENGTH(size_to_read), 0); @@ -138,8 +140,10 @@ tcp_socket::send(const std::vector& data, std::size_t size_to_write) { check_or_set_type(type::CLIENT); std::size_t wr_size = SOCKET_ERROR; - if (m_tls.is_encryption_active()) + if (m_tls.is_encryption_active()) { + // called function throws errors from tls class wr_size = m_tls.send_encrypted(m_fd, data); + } else { wr_size = ::send(m_fd, data.data(), __TACOPIE_LENGTH(size_to_write), 0); if (wr_size == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } diff --git a/sources/network/io_service.cpp b/sources/network/io_service.cpp index 8bb0b95..e7da06e 100644 --- a/sources/network/io_service.cpp +++ b/sources/network/io_service.cpp @@ -120,17 +120,16 @@ io_service::poll(void) { timeout_ptr = &timeout; #endif /* __TACOPIE_TIMEOUT */ - // ex? __TACOPIE_LOG(debug, "polling fds"); + __TACOPIE_LOG(debug, "polling fds"); if (select(ndfs, &m_rd_set, &m_wr_set, NULL, timeout_ptr) > 0) { process_events(); } else { - ; - // ex? __TACOPIE_LOG(debug, "poll woke up, but nothing to process"); + __TACOPIE_LOG(debug, "poll woke up, but nothing to process"); } } - // ex? __TACOPIE_LOG(debug, "stop poll() worker"); + __TACOPIE_LOG(debug, "stop poll() worker"); } //! diff --git a/sources/network/tcp_client.cpp b/sources/network/tcp_client.cpp index 4992c7c..dc0b32a 100644 --- a/sources/network/tcp_client.cpp +++ b/sources/network/tcp_client.cpp @@ -87,8 +87,8 @@ tcp_client::connect(const std::string& host, std::uint32_t port, std::uint32_t t } m_is_connected = true; - - __TACOPIE_LOG(info, "tcp_client connected to " + host + ":" + std::to_string(port)); + std::string encrypted = use_encryption ? " encrypted" : ""; + __TACOPIE_LOG(info, "tcp_client connected to " + host + ":" + std::to_string(port) + encrypted); } void diff --git a/sources/network/windows/tls.cpp b/sources/network/windows/tls.cpp index 15d24ac..eb2f207 100644 --- a/sources/network/windows/tls.cpp +++ b/sources/network/windows/tls.cpp @@ -1,6 +1,11 @@ -// MIT License // -// Copyright (c) 2016-2017 Simon Ninon +// Copyright (c) 2021 Martin Unger for estos GmbH +// +// Based on code available at https://github.com/John-Ad/Schannel-https-implementation +// No license attached as of 2021-08-27. +// +// Code was substantially modified for our purpose and these changes are put under the +// following license. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -35,7 +40,7 @@ #include // There is no handling of connection loss or renegotiate here. It is left to the above -// wrapper to initialte a reconnect in such cases. +// wrapper to initiate a reconnect in such cases. namespace tacopie { @@ -43,9 +48,12 @@ namespace tacopie { //! ctor & dtor //! tls::tls(void) -: m_encryption_active(false) { - __TACOPIE_LOG(debug, "tls constructed"); - } +: m_encryption_active(false), + m_encrypted_bytes(0) { + memset(&m_credentials, 0, sizeof(m_credentials)); + memset(&m_context, 0, sizeof(m_context)); + __TACOPIE_LOG(debug, "tls constructed"); +} tls::~tls(void) { } @@ -55,6 +63,11 @@ tls::~tls(void) { //! void tls::establish_connection(const fd_t &socket, const std::string& host) { + + // Data from old context can not be decrypted anymore + m_encrypted_data.clear(); + m_encrypted_bytes = 0; + get_schannel_credentials(); handshake_loop(socket, host); m_encryption_active = true; @@ -70,9 +83,10 @@ tls::get_schannel_credentials() { ZeroMemory(&credentials_data, sizeof(credentials_data)); credentials_data.dwVersion = SCHANNEL_CRED_VERSION; - // opportunity to restrict used protocols on client side. Suggest to use this only for + // Opportunity to restrict used protocols on client side. Suggest to use this only for // tests and implement needed restrictions on server. - // credData.grbitEnabledProtocols = SP_PROT_TLS1; + // Example: (TLS 1.3 currently not supported on Windows. Available with Windows Server 2022) + // credData.grbitEnabledProtocols = SP_PROT_TLS1_0 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2; SECURITY_STATUS security_status = AcquireCredentialsHandle( //gets the credentials necessary to make use of the ssp NULL, //default principle @@ -82,7 +96,7 @@ tls::get_schannel_credentials() { &credentials_data, //protocol specific data NULL, //default NULL, //default - &m_h_credentials, //where the handle will be stored + &m_credentials, //where the handle will be stored &lifetime //stores the time limit of the credential ); @@ -103,7 +117,7 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { SecBuffer out_buffer[1]; SecBufferDesc in_buffer_desc; SecBuffer in_buffer[2]; - ULONG ul_context_attributes; + ULONG context_attributes; DWORD flags; out_buffer_desc.ulVersion = SECBUFFER_VERSION; @@ -122,7 +136,7 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { flags = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_STREAM | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR; SECURITY_STATUS security_status = InitializeSecurityContext( - &m_h_credentials, // credentials acquired by acquireCredentialsHandle + &m_credentials, // credentials acquired by acquireCredentialsHandle NULL, // in the first call this is NULL, afterwards use hcText parameter variable whost.data(), // name of the server flags, // bit flags that state how the security context will function @@ -130,10 +144,10 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { SECURITY_NATIVE_DREP, // how the data is represented. In schannel this argument is not used and set to 0 NULL, // this is the buffer that will be received from the server. On the first call this is NULL 0, // reserved and set to 0 - &m_ph_context, // receives the context handle. With Schannel, after the first call, this must be NULL and + &m_context, // receives the context handle. With Schannel, after the first call, this must be NULL and // arg2 must take phContext &out_buffer_desc, // buffer where the token will be stored. This will be sent to the server later - &ul_context_attributes, // this is where the set of bit flags will be received. These flags indicate the attributes of the context + &context_attributes, // this is where the set of bit flags will be received. These flags indicate the attributes of the context &lifetime); if (security_status != SEC_I_CONTINUE_NEEDED) { @@ -143,29 +157,22 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { bool process_extra_data = false; int bytes_received_count = 0; char* pc_token = NULL; - std::vector buffer(4000); + std::vector buffer(0x1000); while (security_status != SEC_E_OK) { if (security_status == SEC_I_CONTINUE_NEEDED && !process_extra_data) { if (out_buffer[0].cbBuffer > 0) { pc_token = static_cast(out_buffer[0].pvBuffer); - int send_result = ::send(socket, pc_token, out_buffer[0].cbBuffer, 0); - if (send_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } + tls_send(socket, pc_token, out_buffer[0].cbBuffer); FreeContextBuffer(out_buffer[0].pvBuffer); } - int recv_result = bytes_received_count = ::recv(socket, buffer.data(), static_cast (buffer.size()), 0); - if (recv_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } - if (recv_result == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } - bytes_received_count = recv_result; + bytes_received_count = tls_receive(socket, buffer.data(), static_cast(buffer.size())); } else if (security_status == SEC_E_INCOMPLETE_MESSAGE) { if (in_buffer[1].BufferType == SECBUFFER_MISSING) { int missing_data_count = in_buffer[1].cbBuffer; __TACOPIE_LOG(info, std::string("secbuffer_missing: " + missing_data_count)); - int recv_result = ::recv(socket, buffer.data(), missing_data_count, 0); - if (recv_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } - if (recv_result == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } - bytes_received_count = recv_result; + bytes_received_count = tls_receive(socket, buffer.data(), missing_data_count); } } @@ -191,8 +198,8 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { // https: //docs.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-initializesecuritycontextw security_status = InitializeSecurityContext( - &m_h_credentials, - &m_ph_context, + &m_credentials, + &m_context, NULL, flags, 0, @@ -201,7 +208,7 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { 0, NULL, &out_buffer_desc, - &ul_context_attributes, + &context_attributes, &lifetime); process_extra_data = false; @@ -220,7 +227,7 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { case SEC_I_CONTINUE_NEEDED: if (in_buffer[1].BufferType == SECBUFFER_EXTRA) { __TACOPIE_LOG(info, std::string("CONTINUE_NEEDED: Extra data in in_buffer")); - // shift exta bytes to the beginning of the input buffer + // Shift extra bytes to the beginning of the input buffer if (in_buffer[1].cbBuffer > static_cast(bytes_received_count)) __TACOPIE_THROW(error, "part of buffer larger than whole"); memmove(buffer.data(), buffer.data() + (static_cast(bytes_received_count) - in_buffer[1].cbBuffer), in_buffer[1].cbBuffer); @@ -233,14 +240,14 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { if (out_buffer[0].cbBuffer > 0) { __TACOPIE_LOG(info, std::string("sending leftover bytes in output buffer")); pc_token = static_cast(out_buffer[0].pvBuffer); - int send_result = ::send(socket, pc_token, out_buffer[0].cbBuffer, 0); - if (send_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } + tls_send(socket, pc_token, out_buffer[0].cbBuffer); FreeContextBuffer(out_buffer[0].pvBuffer); } if (in_buffer[1].BufferType == SECBUFFER_EXTRA) { - __TACOPIE_LOG(info, std::string("SEC_E_OK: Extra data in in_buffer")); - // These bytes need to be decrypted [currently not handled] - __TACOPIE_LOG(warn, std::string("Extra bytes to be decrypted") + std::to_string(in_buffer[1].cbBuffer)); + // When a connection has been negotiated, there may be data already read in the buffer. + __TACOPIE_LOG(info, std::string("Encrypted data available after connect: " + std::to_string(in_buffer[1].cbBuffer))); + memmove(const_cast(m_encrypted_data.data()), in_buffer[1].pvBuffer, in_buffer[1].cbBuffer); + m_encrypted_bytes = in_buffer[1].cbBuffer; } break; @@ -259,7 +266,7 @@ tls::handshake_loop(const fd_t& socket, const std::string& host) { std::size_t tls::send_encrypted(const fd_t& socket, const std::vector& unencrypted_data) { - SECURITY_STATUS security_status = QueryContextAttributes(&m_ph_context, SECPKG_ATTR_STREAM_SIZES, &m_stream_sizes); + SECURITY_STATUS security_status = QueryContextAttributes(&m_context, SECPKG_ATTR_STREAM_SIZES, &m_stream_sizes); if (security_status != SEC_E_OK) { __TACOPIE_LOG(warn, std::string("QueryContextAttributes result: ") + get_sspi_result_string(security_status)); @@ -277,7 +284,7 @@ tls::send_encrypted(const fd_t& socket, const std::vector& unencrypted_dat if (unencrypted_bytes_left > m_stream_sizes.cbMaximumMessage) unencrypted_bytes_left += 0; - //copy the unencrypted data just after the header bytes to the buffer + // Copy the unencrypted data just after the header bytes to the buffer memcpy(buffer.data() + m_stream_sizes.cbHeader, unencrypted_data.data() + unencrypted_bytes_written, current_unencrypted_chunk); SecBufferDesc message_buffer; @@ -304,19 +311,19 @@ tls::send_encrypted(const fd_t& socket, const std::vector& unencrypted_dat buffers[3].BufferType = SECBUFFER_EMPTY; // https://docs.microsoft.com/en-us/windows/win32/secauthn/encryptmessage--schannel - security_status = EncryptMessage(&m_ph_context, 0, &message_buffer, 0); + security_status = EncryptMessage(&m_context, 0, &message_buffer, 0); if (security_status != SEC_E_OK) { __TACOPIE_THROW(error, std::string("EncryptMessage result: ") + get_sspi_result_string(security_status)); } int encrypted_size = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer; - ssize_t send_result = ::send(socket, buffer.data(), encrypted_size, 0); - if (send_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } + tls_send(socket, buffer.data(), encrypted_size); unencrypted_bytes_written += current_unencrypted_chunk; } // while bytes left to send + // Return amount of unencrypted data sent(caller does not know about encrypted size) return unencrypted_bytes_written; } @@ -326,36 +333,30 @@ tls::send_encrypted(const fd_t& socket, const std::vector& unencrypted_dat std::vector tls::recv_decrypt(const fd_t& socket) { - int encrypted_bytes = 0; int decrypted_bytes = 0; const std::size_t buffer_increment_size = 0x1000; - std::vector encrypted_data; std::vector decrypted_data; SECURITY_STATUS security_status = SEC_E_OK; SecBufferDesc buffer_desc; SecBuffer sec_buffer[4]; - // read more data until able to decrypt or decryptable data block not complete (in + // Read more data until able to decrypt or decryptable data block not complete (in // case part of next block has already been received) - while (decrypted_bytes == 0 || security_status == SEC_E_INCOMPLETE_MESSAGE) { - encrypted_data.resize(encrypted_bytes + buffer_increment_size); // buffer needs to grow on incomplete messages - int recv_result = ::recv(socket, const_cast(encrypted_data.data()) + encrypted_bytes, static_cast(buffer_increment_size), 0); - - if (recv_result == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } - if (recv_result == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } - - encrypted_bytes += recv_result; + while (decrypted_bytes == 0) { + m_encrypted_data.resize(m_encrypted_bytes + buffer_increment_size); // buffer needs to grow on incomplete messages + + m_encrypted_bytes += tls_receive(socket, const_cast(m_encrypted_data.data()) + m_encrypted_bytes, static_cast(buffer_increment_size)); - __TACOPIE_LOG(debug, std::string("encrypted bytes in buffer: ") + std::to_string(encrypted_bytes)); + __TACOPIE_LOG(debug, std::string("encrypted bytes in buffer: ") + std::to_string(m_encrypted_bytes)); security_status = SEC_E_OK; // allow entry of decrypt loop - while (encrypted_bytes != 0 && security_status != SEC_E_INCOMPLETE_MESSAGE) { + while (m_encrypted_bytes != 0 && security_status != SEC_E_INCOMPLETE_MESSAGE) { buffer_desc.cBuffers = 4; buffer_desc.pBuffers = sec_buffer; buffer_desc.ulVersion = SECBUFFER_VERSION; - sec_buffer[0].cbBuffer = encrypted_bytes; - sec_buffer[0].pvBuffer = encrypted_data.data(); // not decrypted in place as documented + sec_buffer[0].cbBuffer = m_encrypted_bytes; + sec_buffer[0].pvBuffer = m_encrypted_data.data(); // not decrypted in place as documented sec_buffer[0].BufferType = SECBUFFER_DATA; sec_buffer[1].BufferType = SECBUFFER_EMPTY; @@ -364,20 +365,20 @@ tls::recv_decrypt(const fd_t& socket) { // https://docs.microsoft.com/en-us/windows/win32/secauthn/decryptmessage--general // https://docs.microsoft.com/en-us/windows/win32/secauthn/decryptmessage--schannel - security_status = DecryptMessage(&m_ph_context, &buffer_desc, 0, NULL); + security_status = DecryptMessage(&m_context, &buffer_desc, 0, NULL); switch (security_status) { case SEC_E_OK: { __TACOPIE_LOG(debug, "data successfully decrypted"); - encrypted_bytes = 0; // all bytes used + m_encrypted_bytes = 0; // all bytes used for (int i = 0; i < 4; i++) { switch (sec_buffer[i].BufferType) { case SECBUFFER_DATA: - // append decrypted bytes to output buffer. May be 0 bytes according to doc. + // Append decrypted bytes to output buffer. May be 0 bytes according to doc. if (sec_buffer[i].cbBuffer) { - decrypted_data.resize(decrypted_bytes + sec_buffer[i].cbBuffer); + decrypted_data.resize(static_cast(decrypted_bytes) + sec_buffer[i].cbBuffer); memcpy(const_cast(decrypted_data.data()) + decrypted_bytes, sec_buffer[i].pvBuffer, sec_buffer[i].cbBuffer); decrypted_bytes += sec_buffer[i].cbBuffer; FreeContextBuffer(sec_buffer[i].pvBuffer); @@ -386,9 +387,9 @@ tls::recv_decrypt(const fd_t& socket) { case SECBUFFER_EXTRA: // When a block has been decrypted, there may be data already read for the next block in the buffer. __TACOPIE_LOG(info, std::string("Data for next encryptable block: " + std::to_string(sec_buffer[i].cbBuffer))); - memmove(const_cast(encrypted_data.data()), sec_buffer[i].pvBuffer, sec_buffer[i].cbBuffer); - encrypted_bytes = sec_buffer[i].cbBuffer; - //FreeContextBuffer(sec_buffer[i].pvBuffer); // triggers exception + memmove(const_cast(m_encrypted_data.data()), sec_buffer[i].pvBuffer, sec_buffer[i].cbBuffer); + m_encrypted_bytes = sec_buffer[i].cbBuffer; + //FreeContextBuffer(sec_buffer[i].pvBuffer); // triggers exception (reverse engineered) break; default: break; @@ -401,25 +402,47 @@ tls::recv_decrypt(const fd_t& socket) { for (int i = 0; i < 4; i++) { if (sec_buffer[i].BufferType == SECBUFFER_ALERT) { __TACOPIE_LOG(warn, std::string("SECBUFFER_ALERT: ") + std::string((char*) sec_buffer[i].pvBuffer)); + // FreeContextBuffer(sec_buffer[i].pvBuffer); // unable to test, may throw exception } } __TACOPIE_THROW(warn, std::string("failed to decrypt: ") + get_sspi_result_string(security_status)); break; case SEC_E_INCOMPLETE_MESSAGE: - // we need to read more data + // We need to read more data already available from socket. We are now in one of two possible situations: + // - no data has been decrypted yet: Continue reading. + // - decrypted data available: Return it. As more data is available, we will be called again + // to continue. __TACOPIE_LOG(debug, get_sspi_result_string(security_status)); break; default: __TACOPIE_THROW(warn, get_sspi_result_string(security_status)); } // switch - } // while encrypted bytes && security_status != SEC_E_INCOMPLETE_MESSAGE - } // while decrypted_bytes == 0 || security_status == SEC_E_INCOMPLETE_MESSAGE + } // while encrypted bytes != 0 && security_status != SEC_E_INCOMPLETE_MESSAGE + } // while decrypted_bytes == 0 return decrypted_data; } +int +tls::tls_receive(SOCKET socket, char* buffer, int length) { + int error_or_count = ::recv(socket, buffer, length, 0); + if (error_or_count == SOCKET_ERROR) { __TACOPIE_THROW(error, "recv() failure"); } + if (error_or_count == 0) { __TACOPIE_THROW(warn, "nothing to read, socket has been closed by remote host"); } + + return error_or_count; +} + +int +tls::tls_send(SOCKET socket, const char* buffer, int length) { + int error_or_count = ::send(socket, buffer, length, 0); + if (error_or_count == SOCKET_ERROR) { __TACOPIE_THROW(error, "send() failure"); } + + return error_or_count; +} + + std::string tls::get_sspi_result_string(SECURITY_STATUS security_status) { @@ -455,7 +478,7 @@ tls::get_sspi_result_string(SECURITY_STATUS security_status) { {SEC_E_DECRYPT_FAILURE, "SEC_E_DECRYPT_FAILURE - The specified data could not be decrypted."}, {SEC_E_ENCRYPT_FAILURE, "SEC_E_ENCRYPT_FAILURE - The specified data could not be encrypted."}, {SEC_I_INCOMPLETE_CREDENTIALS, "SEC_I_INCOMPLETE_CREDENTIALS - The credentials supplied were not complete and could not be verified. Additional information can be returned from the context."}, - {SEC_E_INCOMPLETE_MESSAGE, "SEC_E_INCOMPLETE_MESSAGE - The data in the input buffer is incomplete. The application needs to read more data from the server and call DecryptMessage (General) again."}, + {SEC_E_INCOMPLETE_MESSAGE, "SEC_E_INCOMPLETE_MESSAGE - The data in the input buffer is incomplete. The application needs to read more data from the server and call DecryptMessage again."}, {SEC_E_INVALID_HANDLE, "SEC_E_INVALID_HANDLE - A context handle that is not valid was specified in the phContext parameter."}, {SEC_E_INVALID_TOKEN, "SEC_E_INVALID_TOKEN - The buffers are of the wrong type or no buffer of type SECBUFFER_DATA was found."}, {SEC_E_INSUFFICIENT_MEMORY, "SEC_E_INSUFFICIENT_MEMORY - Not enough memory is available to complete the request."}, @@ -491,7 +514,7 @@ tls::get_sspi_result_string(SECURITY_STATUS security_status) { for (int i = 0; i < (sizeof(security_status_as_string) / sizeof(*security_status_as_string)); i++) { if (security_status == security_status_as_string[i].first) { - str_message = security_status_as_string[i].second; + str_message = hexval.str() + " - " + security_status_as_string[i].second; break; } }