From 6f4705cd977932f0bc5f8ce126de0ffec9113dae Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:48:39 +0000 Subject: [PATCH 01/76] [WebSocket/WebSocketLink] : Enable unsollicited 'Pong' and rename enum item - Rename 'WEBSERVER' to 'WEBSERVICE' as both client and server utilize state information - Unsollicited 'Pong' may act as a heart beat --- Source/websocket/WebSocketLink.h | 42 ++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/Source/websocket/WebSocketLink.h b/Source/websocket/WebSocketLink.h index c1f3f14ad..3125b3ffe 100644 --- a/Source/websocket/WebSocketLink.h +++ b/Source/websocket/WebSocketLink.h @@ -35,6 +35,10 @@ namespace Web { CLOSE = 0x08, PING = 0x09, PONG = 0x0A, + // Reserved ranges + // 0x3-0x7 + // 0xB-0xF + // Following are outside reseved 4-bit ranges VIOLATION = 0x10, // e.g. a control package without a FIN flag TOO_BIG = 0x20, // Protocol max support for 2^16 message per chunk INCONSISTENT = 0x30 // e.g. Protocol defined as Text, but received a binary. @@ -182,7 +186,7 @@ namespace Web { class WebSocketLinkType { public: enum EnumlinkState : uint8_t { - WEBSERVER = 0x01, + WEBSERVICE = 0x01, UPGRADING = 0x02, WEBSOCKET = 0x04, SUSPENDED = 0x08, @@ -373,7 +377,7 @@ PUSH_WARNING(DISABLE_WARNING_THIS_IN_MEMBER_INITIALIZER_LIST) , _handler(binary, masking) , _parent(parent) , _adminLock() - , _state(WEBSERVER) + , _state(WEBSERVICE) , _serializerImpl(*this, queueSize) , _deserialiserImpl(*this, queueSize) , _path() @@ -390,7 +394,7 @@ PUSH_WARNING(DISABLE_WARNING_THIS_IN_MEMBER_INITIALIZER_LIST) , _handler(binary, masking) , _parent(parent) , _adminLock() - , _state(WEBSERVER) + , _state(WEBSERVICE) , _serializerImpl(*this, queueSize) , _deserialiserImpl(*this, allocator) , _path() @@ -419,7 +423,7 @@ POP_WARNING() } bool IsWebServer() const { - return ((State() & WEBSERVER) != 0); + return ((State() & WEBSERVICE) != 0); } bool IsUpgrading() const { @@ -477,6 +481,18 @@ POP_WARNING() ACTUALLINK::Trigger(); } + void Pong() + { + _pingFireTime = Core::Time::Now().Ticks(); + + _adminLock.Lock(); + + _handler.Pong(); + + _adminLock.Unlock(); + + ACTUALLINK::Trigger(); + } bool Masking() const { return (_handler.Masking()); @@ -775,7 +791,7 @@ POP_WARNING() // Multiple message might be coming in, protect the state before we make assumptions on it value. _adminLock.Lock(); - if ((_state & WEBSERVER) == 0) { + if ((_state & WEBSERVICE) == 0) { _webSocketMessage->ErrorCode = Web::STATUS_INTERNAL_SERVER_ERROR; _webSocketMessage->Message = _T("State of the link can not be upgraded."); } else { @@ -794,7 +810,7 @@ POP_WARNING() _parent.StateChange(); if (_webSocketMessage->ErrorCode != Web::STATUS_SWITCH_PROTOCOL) { - _state = (_state & 0xF0) | WEBSERVER; + _state = (_state & 0xF0) | WEBSERVICE; _path.clear(); _query.clear(); _protocol.Clear(); @@ -845,7 +861,7 @@ POP_WARNING() _adminLock.Lock(); - if ((_state & WEBSERVER) != 0) { + if ((_state & WEBSERVICE) != 0) { result = true; _state = (_state & 0xF0) | UPGRADING; _origin = (origin.empty() ? ACTUALLINK::LocalId() : origin); @@ -1076,6 +1092,10 @@ POP_WARNING() { _channel.Ping(); } + void Pong() + { + _channel.Pong(); + } void Trigger() { _channel.Trigger(); @@ -1272,6 +1292,10 @@ POP_WARNING() { _channel.Ping(); } + void Pong() + { + _channel.Pong(); + } virtual bool IsIdle() const = 0; virtual void StateChange() = 0; @@ -1430,6 +1454,10 @@ POP_WARNING() { _channel.Ping(); } + void Pong() + { + _channel.Pong(); + } virtual bool IsIdle() const = 0; virtual void StateChange() = 0; From aa8c25dac888767515c326a0616f5b235d0ce5b4 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:24:54 +0000 Subject: [PATCH 02/76] [WebSocket/WebSocketLink] : METROL-1087 --- Source/websocket/WebSocketLink.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/websocket/WebSocketLink.h b/Source/websocket/WebSocketLink.h index 3125b3ffe..b5ddd7fba 100644 --- a/Source/websocket/WebSocketLink.h +++ b/Source/websocket/WebSocketLink.h @@ -193,6 +193,8 @@ namespace Web { ACTIVITY = 0x10 }; + DEPRECATED constexpr static EnumlinkState WEBSERVER { EnumlinkState::WEBSERVICE }; + typedef WebSocketLinkType ParentClass; private: @@ -377,7 +379,7 @@ PUSH_WARNING(DISABLE_WARNING_THIS_IN_MEMBER_INITIALIZER_LIST) , _handler(binary, masking) , _parent(parent) , _adminLock() - , _state(WEBSERVICE) + , _state(WEBSERVER) , _serializerImpl(*this, queueSize) , _deserialiserImpl(*this, queueSize) , _path() From e923fcfdc67d04b8780f4b537ac77d36ceac55fe Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 7 Nov 2024 08:18:58 +0000 Subject: [PATCH 03/76] [WebSocket/WebSocketLink] : rename forgotten 'WEBSERVER' to 'WEBSERVICE' --- Source/websocket/WebSocketLink.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/websocket/WebSocketLink.h b/Source/websocket/WebSocketLink.h index b5ddd7fba..598f19bbc 100644 --- a/Source/websocket/WebSocketLink.h +++ b/Source/websocket/WebSocketLink.h @@ -379,7 +379,7 @@ PUSH_WARNING(DISABLE_WARNING_THIS_IN_MEMBER_INITIALIZER_LIST) , _handler(binary, masking) , _parent(parent) , _adminLock() - , _state(WEBSERVER) + , _state(WEBSERVICE) , _serializerImpl(*this, queueSize) , _deserialiserImpl(*this, queueSize) , _path() From 47697662e9c3d305054e99716d3c5e766bcb4937 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 7 Nov 2024 08:29:36 +0000 Subject: [PATCH 04/76] [Tests/unit/core] : add 'test_websocket' --- Tests/unit/core/test_websocket.cpp | 1006 ++++++++++++++++++++++++++++ 1 file changed, 1006 insertions(+) create mode 100644 Tests/unit/core/test_websocket.cpp diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp new file mode 100644 index 000000000..2442085b0 --- /dev/null +++ b/Tests/unit/core/test_websocket.cpp @@ -0,0 +1,1006 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#ifndef MODULE_NAME +#include "../Module.h" +#endif + +#include +#include +#include + +#include "../IPTestAdministrator.h" + +namespace Thunder { +namespace Tests { +namespace Core { + + class CustomSocketStream : public ::Thunder::Core::SocketStream { + public: + CustomSocketStream( + const SOCKET& socket + , const ::Thunder::Core::NodeId& localNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : SocketStream(false, socket, localNode, sendBufferSize, receiveBufferSize) + , _prefix { prefix } + { + } + + CustomSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : SocketStream(false, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + , _prefix { prefix } + { + } + + ~CustomSocketStream() + { + std::cout.flush(); + } + + // Raw TCP data + int32_t Read(uint8_t buffer[], const uint16_t length) const override + { +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + int32_t count = SocketPort::Read(buffer, length); + +#ifdef _VERBOSE + if (count > 0) { + std::cout << " |--> buffer ( " << count << "/" << length << " ) = "; + for (int32_t index = 0; index < count; index++) { + std::cout << std::hex << static_cast(buffer[index]) << " "; + } + std::cout << "\n"; + } +#endif + + return count; + } + + // Raw TCP data + int32_t Write(const uint8_t buffer[], const uint16_t length) override + { +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + int32_t count = SocketPort::Write(buffer, length); + +#ifdef _VERBOSE + if (count > 0) { + std::cout << " |--> buffer ( " << count << "/"<< length <<" ) = "; + for (int32_t index = 0; index < count; index++) { + std::cout << std::hex << static_cast(buffer[index]) << " "; + } + std::cout << "\n"; + } +#endif + + return count; + } + + private: + + const std::string _prefix; + }; + + template + class WebSocketClient : public ::Thunder::Web::WebSocketClientType { + public : + + template + WebSocketClient( + const string& path + , const string& protocol + , const string& query + , const string& origin + , const bool binary + , const bool masking + , Args&&... args + ) + : ::Thunder::Web::WebSocketClientType(path, protocol, query, origin, binary, masking, /* */ std::forward(args)... /**/) + { + } + + ~WebSocketClient() override = default; + + // Non-idle then data available to send + bool IsIdle() const override { return _post.size() == 0; } + + // Allow for eventfull state updates in this class + void StateChange() override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + + // Socket port open AND upgraded to WebSocket + std::cout << " |--> IsOpen() = " << this->IsOpen() << "\n"; + // Link will not accept new messages as they arrive + std::cout << " |--> IsSuspended() = " << this->IsSuspended() << "\n"; + // Socket has been closed (removed), link cannot receive new TCP data + std::cout << " |--> IsClosed() = " << this->IsClosed() << "\n"; + // Regular HTTP connection, no upgraded connection + std::cout << " |--> IsWebServer() = " << this->IsWebServer() << "\n"; + // Upgrade in progress + std::cout << " |--> IsUpgrading() = " << this->IsUpgrading() << "\n"; + // Upgraded connection + std::cout << " |--> IsWebSocket() = " << this->IsWebSocket() << "\n"; + // Finishing frame received + std::cout << " |--> IsCompleted() = " << this->IsCompleted() << "\n"; +#endif + } + + // Reflects payload, effective after upgrade + uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + size_t count = 0; + + if ( dataFrame != nullptr + && maxSendSize > 0 + && !IsIdle() + && ::Thunder::Web::WebSocketClientType::IsOpen() + && ::Thunder::Web::WebSocketClientType::IsWebSocket() // Redundant, covered by IsOpen + && ::Thunder::Web::WebSocketClientType::IsCompleted() + ) { + std::basic_string& message = _post.front(); + + count = std::min(message.size(), static_cast(maxSendSize)); + + /* void* */ memcpy(dataFrame, message.data(), count); + +#ifdef _VERBOSE + std::cout << " |--> dataFrame ( " << count << "/" << maxSendSize << " ) = "; + for (int32_t index = 0; index < count; index++) { + std::cout << std::hex << static_cast(dataFrame[index]) << " "; + } + std::cout << "\n"; +#endif + + if (count == message.size()) { + /* iterator */ _post.erase(_post.begin()); + } else { + /* this */ message.erase(0, count); + + // Trigger a call to SendData for remaining data + ::Thunder::Web::WebSocketClientType::Link().Trigger(); + } + } + + return count; + } + + // Reflects payload, effective after upgrade + uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + + if (receivedSize > 0) { + std::cout << " |--> dataFrame ( " << receivedSize << " ) = "; + for (int32_t index = 0; index < receivedSize; index++) { + std::cout << std::hex << static_cast(dataFrame[index]) << " "; + } + std::cout << "\n"; + } +#endif + + // Echo the data in reverse order + + std::basic_string message{ dataFrame, receivedSize }; + + std::reverse(message.begin(), message.end()); + + return Submit(message) ? message.size() : 0; + } + + bool Submit(const std::basic_string& message) + { + size_t count = _post.size(); + + _post.emplace_back(message); + + ::Thunder::Web::WebSocketClientType::Link().Trigger(); + + return count < _post.size(); + } + + private: + + std::vector> _post; // Send message queue + }; + + template + class WebSocketServer : public ::Thunder::Web::WebSocketServerType { + public : + + // SocketServerType defines SocketHandler of type SocketListener. SocketListener triggers Accept on StateChange, effectively, calling SocketServerType::Accept(SOCKET, NodeId) which creates a WebSocketServer with these parameters + WebSocketServer(const SOCKET& socket, const ::Thunder::Core::NodeId remoteNode, ::Thunder::Core::SocketServerType>*) + // Initially this should be defined as a regular TCP socket + : ::Thunder::Web::WebSocketServerType(false /* binary*/, false /*masking */, socket, remoteNode, SENDBUFFERSIZE /* send buffer size */, RECEIVEBUFFERSIZE /* receive buffer size */, "WebSocketServerType") + { + } + + ~WebSocketServer() override = default; + + // Non-idle then data available to send + bool IsIdle() const override { return _post.size() == 0; } + + // Allow for eventfull state updates in this class + void StateChange() override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + + // Socket port open AND upgraded to WebSocket + std::cout << " |--> IsOpen() = " << this->IsOpen() << "\n"; + // Link will not accept new messages as they arrive + std::cout << " |--> IsSuspended() = " << this->IsSuspended() << "\n"; + // Socket has been closed (removed), link cannot receive new TCP data + std::cout << " |--> IsClosed() = " << this->IsClosed() << "\n"; + // Regular HTTP connection, no upgraded connection + std::cout << " |--> IsWebServer() = " << this->IsWebServer() << "\n"; + // Upgrade in progress + std::cout << " |--> IsUpgrading() = " << this->IsUpgrading() << "\n"; + // Upgraded connection + std::cout << " |--> IsWebSocket() = " << this->IsWebSocket() << "\n"; + // Finishing frame received + std::cout << " |--> IsCompleted() = " << this->IsCompleted() << "\n"; +#endif + } + + // Reflects payload, effective after upgrade + uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + size_t count = 0; + + if ( dataFrame != nullptr + && maxSendSize > 0 + && !IsIdle() + && ::Thunder::Web::WebSocketServerType::IsOpen() + && ::Thunder::Web::WebSocketServerType::IsWebSocket() // Redundant, covered by IsOpen + && ::Thunder::Web::WebSocketServerType::IsCompleted() + ) { + std::basic_string& message = _post.front(); + + count = std::min(message.size(), static_cast(maxSendSize)); + + /* void* */ memcpy(dataFrame, message.data(), count); + +#ifdef _VERBOSE + std::cout << " |--> dataFrame (" << count << " ) = "; + for (int32_t index = 0; index < count; index++) { + std::cout << std::hex << static_cast(dataFrame[index]) << " "; + } + std::cout << "\n"; +#endif + + if (count == message.size()) { + /* iterator */ _post.erase(_post.begin()); + } else { + /* this */ message.erase(0, count); + + // Trigger a call to SendData for remaining data + ::Thunder::Web::WebSocketServerType::Link().Trigger(); + } + } + + return count; + } + + // Reflects payload, effective after upgrade + uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) override { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + if (receivedSize > 0) { + _response.emplace_back(std::basic_string{ dataFrame, receivedSize }); + +#ifdef _VERBOSE + std::cout << " |--> dataFrame ( " << receivedSize << " ) = "; + for (int32_t index = 0; index < receivedSize; index++) { + std::cout << std::hex << static_cast(dataFrame[index]) << " "; + } + std::cout << "\n"; +#endif + } + + return receivedSize; + } + + // Put data in the queue to send (to the (connected) client) + bool Submit(const std::basic_string& message) + { + size_t count = _post.size(); + + _post.emplace_back(message); + + // Trigger a call to SendData + ::Thunder::Web::WebSocketServerType::Link().Trigger(); + + return count < _post.size(); + } + + std::basic_string Response() + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + std::basic_string message; + + if (_response.size() > 0) { + message = _response.front(); + _response.erase(_response.begin()); + +#ifdef _VERBOSE + std::cout << " |--> message ( " << message.size() << " ) = "; + for (int32_t index = 0; index < message.size(); index++) { + std::cout << std::hex << static_cast(message[index]) << " "; + } + std::cout << "\n"; +#endif + } + + return message; + } + + private: + + std::vector> _post; // Send message queue + std::vector> _response; // Receive message queue + }; + + TEST(WebSocket, DISABLED_OpeningServerPort) + { + const TCHAR localHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + // No data should be transferred to the remote client + } + } + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, DISABLED_OpeningClientPort) + { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + const TCHAR remoteHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + constexpr bool rawSocket {false}; + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + SleepMs(maxWaitTimeMs); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketUpgrade) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketServerPingClientPong) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + /* void */ it.Client()->Ping(); + } + } + + // Allow some time to receive the PONG response + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketServerUnsollicitedPong) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + // Allow some time to receive the PONG + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + /* void */ it.Client()->Pong(); + } + } + + // Avoid premature shutdown() at the receiving side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketClientUnsollicitedPong) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + client.Pong(); + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + // Allow some time to receive the PONG + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketDataExchange) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + constexpr uint8_t data[] = { 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x3, 0x2, 0x1, 0x0 }; + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + /* bool */ it.Client()->Submit(std::basic_string{ data, sizeof(data) }); + } + } + + // Allow some time to receive the response + SleepMs(maxWaitTimeMs); + + std::basic_string response{ data, sizeof(data) }; + std::reverse(response.begin(), response.end()); + + // A simple poll to keep it simple + EXPECT_TRUE( it.IsValid() + && (it.Client()->Response() == response) + ); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, UnsecuredSocketMultiFrameDataExchange) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {75}; + constexpr uint16_t receiveBufferSize {75}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + constexpr uint16_t nagglesTimeoutMs = 250; // Typical is 200 milliseconds + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + std::cout << " |--> SendBufferSize = " << client.Link().SendBufferSize() << "\n"; + std::cout << " |--> ReceiveBufferSize = " << client.Link().ReceiveBufferSize() << "\n"; + std::cout << " |--> SocketSendBufferSize = " << client.Link().SocketSendBufferSize() << "\n"; + std::cout << " |--> SocketReceiveBufferSize = " << client.Link().SocketReceiveBufferSize() << "\n"; +#endif + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + // Do not use '\0' as a marker as std::basic_strng<> assumes it is an end of string + + constexpr uint8_t data[] = { 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x3, 0x2, 0x1, 0x10 }; + + std::basic_string message; + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + // Construct a message larger than the buffer size to force use of continuation frames + size_t count = (it.Client()->Link().SendBufferSize() / sizeof(data) + 1 ) * sizeof(data); + + ASSERT_LE(count, message.max_size()); + + message.resize(count); + + for (size_t index = 0; index < count; index += sizeof(data) ) { + message.replace(index, sizeof(data), data); + } + + /* bool */ it.Client()->Submit(std::basic_string{ message.data(), count }); + } + } + + // Allow some time to receive the response + SleepMs(maxWaitTimeMs); + + std::reverse(message.begin(), message.end()); + + std::basic_string response; + + response.reserve( message.size() ); + + // A simple poll to keep it simple + for (int8_t retry = 0; retry < maxRetries; ++retry) { + SleepMs(nagglesTimeoutMs); // Naggle's typical delay, perhaps a bit more + + if (it.IsValid()) { + response = it.Client()->Response() + response; + } + } + + EXPECT_TRUE( response.size() == message.size() + && response == message + ); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + +} // Core +} // Tests +} // Thunder From 45bfec56f7bc95c1dd26b676b1d9a7b3dae82d16 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 21 Nov 2024 08:28:58 +0000 Subject: [PATCH 05/76] [Tests/unit/core] : Add missing VERBOSE preprocessor directive to 'test_websocket' --- Tests/unit/core/test_websocket.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 2442085b0..794875e69 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -62,7 +62,9 @@ namespace Core { ~CustomSocketStream() { +#ifdef _VERBOSE std::cout.flush(); +#endif } // Raw TCP data From a206e194452e7a098eb937b3865bc2de1314a2c2 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 21 Nov 2024 08:42:09 +0000 Subject: [PATCH 06/76] [Tests/unit/core] : Add 'SecureSocketPort' tests to 'test_websocket' --- Tests/unit/core/test_websocket.cpp | 145 ++++++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 794875e69..a2cb1f4f7 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -391,6 +391,75 @@ namespace Core { std::vector> _response; // Receive message queue }; + class CustomSecureSocketStream : public ::Thunder::Crypto::SecureSocketPort { + private : + + // Validat eclient certificate + class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidator { + public: + + Validator() = default; + ~Validator() = default; + + bool Validate(const Certificate& certificate) const override { + // Print certificate properties +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + std::cout << " |--> Issuer = " << certificate.Issuer() << "\n"; + std::cout << " |--> Subject = " << certificate.Subject() << "\n"; + std::cout << " |--> Valid from = " << certificate.ValidFrom().ToRFC1123() << "\n"; + std::cout << " |--> Valid until = " << certificate.ValidTill().ToRFC1123() << "\n"; +#endif + return true; // Always accept + } + }; + + public : + + // In essence, all parameters to SecureSocket are passed to a base class SocketPort + CustomSecureSocketStream( + const SOCKET& socket + , const ::Thunder::Core::NodeId& localNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + , _prefix{ prefix } + , _validator{} + { + // Validate custom (sefl signed) certificates + uint32_t result = Callback(&_validator); + } + + CustomSecureSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + , _prefix{ prefix } + , _validator{} + { + // Validate custom (self signed) client certificates + uint32_t result = Callback(&_validator); + } + + ~CustomSecureSocketStream() + { + #ifdef _VERBOSE + std::cout.flush(); +#endif + } + + private: + const std::string _prefix; + Validator _validator; + }; + TEST(WebSocket, DISABLED_OpeningServerPort) { const TCHAR localHostName[] {"127.0.0.1"}; @@ -874,7 +943,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, UnsecuredSocketMultiFrameDataExchange) + TEST(WebSocket, DISABLED_UnsecuredSocketMultiFrameDataExchange) { const TCHAR hostName[] {"127.0.0.1"}; @@ -1003,6 +1072,80 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } + TEST(WebSocket, DISABLED_OpeningSecuredServerPort) + { + const TCHAR localHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + +// SleepMs(maxWaitTimeMs); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + if (it.Client()->IsOpen()) { + // No data should be transferred to the remote client + } else { + } + } + + SleepMs(4*maxWaitTimeMs); + + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, OpeningSecuredClientPort) + { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + const TCHAR remoteHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + constexpr bool rawSocket {false}; + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + +// SleepMs(maxWaitTimeMs); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + } // Core } // Tests } // Thunder From 426161696810252a8730c26277f2e14bbde259e2 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:15:08 +0000 Subject: [PATCH 07/76] [Source/crypto/SecureSocketPort] : Initialize when context and SSL structure are not yet contructed, and, improve SSL read and write use - SSL read and write methods should only be used if the handshake is successfull - Do not return error values for SSL read and write methods as number of read or written bytes --- Source/cryptalgo/SecureSocketPort.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 764b94398..6188954aa 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -151,16 +151,25 @@ uint32_t SecureSocketPort::Handler::Initialize() { } int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { - int32_t result = SSL_read(static_cast(_ssl), buffer, length); + int32_t result = 0; - if (_handShaking != CONNECTED) { - const_cast(*this).Update(); + if (_handShaking == CONNECTED) { + int value = SSL_read(static_cast(_ssl), buffer, length); + result = (value > 0 ? value : 0); } + return (result); } int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { - return (SSL_write(static_cast(_ssl), buffer, length)); + int32_t result = 0; + + if (_handShaking == CONNECTED) { + int value = (SSL_write(static_cast(_ssl), buffer, length)); + result = (value > 0 ? value : 0); + } + + return (result); } From d9d1dd406b4d081a4a417ce32588405b96177abb Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:40:17 +0000 Subject: [PATCH 08/76] [Source/cryptalgo/SecureSocketPort] : (Re)initialize if socket has been opened but context has not been contructed. --- Source/cryptalgo/SecureSocketPort.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 280a44ae3..052333263 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -105,10 +105,14 @@ namespace Crypto { // Signal a state change, Opened, Closed or Accepted void StateChange() override { + if (IsOpen() && _context != nullptr) { // Initialize may not yet have happened + Initialize(); + } - ASSERT(_context != nullptr); - Update(); - }; + if (_ssl != nullptr) { + Update(); + } + }; inline uint32_t Callback(IValidator* callback) { uint32_t result = Core::ERROR_ILLEGAL_STATE; From 41fde68e40caa418086c94916101e15c1726506b Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:43:21 +0000 Subject: [PATCH 09/76] [Source/cryptalgo/SecureSocketPort] : Context settings do not (necessarily) propagate to SSL structure after construction. - Context should exist - Options should be set --- Source/cryptalgo/SecureSocketPort.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 6188954aa..73f216c74 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -135,16 +135,21 @@ uint32_t SecureSocketPort::Handler::Initialize() { _context = SSL_CTX_new(TLS_method()); - _ssl = SSL_new(static_cast(_context)); - SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); - SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + if (_context != nullptr) { + if ( // Returns bit-mask after adding options + ((SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3) & (SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)) == (SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)) + && // Trust the same certificates as any other application + (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) + ) { + _ssl = SSL_new(static_cast(_context)); - // Trust the same certificates as any other application - if (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) { - success = Core::SocketPort::Initialize(); - } else { - TRACE_L1("OpenSSL failed to load certificate store"); - success = Core::ERROR_GENERAL; + SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); + + success = Core::SocketPort::Initialize(); + } else { + TRACE_L1("OpenSSL failed to set protocol level or load certificate store"); + success = Core::ERROR_GENERAL; + } } return success; From efe82e0ad50b8257603cb8b00212d52f34a52d67 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:59:13 +0000 Subject: [PATCH 10/76] [Source/cryptalgo/SecureSocketPort] : check return value for setting hostname. --- Source/cryptalgo/SecureSocketPort.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 73f216c74..9dbabc840 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -226,29 +226,26 @@ void SecureSocketPort::Handler::ValidateHandShake() { } void SecureSocketPort::Handler::Update() { - if (IsOpen() == true) { - int result; - + if (IsOpen() == true && _ssl != nullptr) { if (_handShaking == IDLE) { - SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()); - result = SSL_connect(static_cast(_ssl)); - if (result == 1) { + int result{0}; + + if( (result = SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()) == 1) + && (result = SSL_connect(static_cast(_ssl)) == 1) + ) { ValidateHandShake(); - } - else { + } else { // Support non-blocking SocketPorts, result taken from SSL_connect result = SSL_get_error(static_cast(_ssl), result); if ((result == SSL_ERROR_WANT_READ) || (result == SSL_ERROR_WANT_WRITE)) { _handShaking = EXCHANGE; } } - } - else if (_handShaking == EXCHANGE) { + } else if (_handShaking == EXCHANGE) { if (SSL_do_handshake(static_cast(_ssl)) == 1) { ValidateHandShake(); } } - } - else if (_ssl != nullptr) { + } else if (_ssl != nullptr) { _handShaking = IDLE; _parent.StateChange(); } From 4a8b4b1d21736ecb2ab03ecdebf33af48fa72cee Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:22:43 +0000 Subject: [PATCH 11/76] [Source/cryptalgo/SecureSocketPort] : improve return value checking in 'Handler::Initialize' --- Source/cryptalgo/SecureSocketPort.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 9dbabc840..5299331d3 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -131,20 +131,16 @@ SecureSocketPort::Handler::~Handler() { } uint32_t SecureSocketPort::Handler::Initialize() { - uint32_t success = Core::ERROR_NONE; + uint32_t success = Core::ERROR_GENERAL; - _context = SSL_CTX_new(TLS_method()); - - if (_context != nullptr) { + if ((_context = SSL_CTX_new(TLS_method())) != nullptr) { if ( // Returns bit-mask after adding options ((SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3) & (SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)) == (SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)) && // Trust the same certificates as any other application (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) - ) { - _ssl = SSL_new(static_cast(_context)); - - SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); - + && ((_ssl = SSL_new(static_cast(_context))) != nullptr) + && (SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()) == 1) + ) { success = Core::SocketPort::Initialize(); } else { TRACE_L1("OpenSSL failed to set protocol level or load certificate store"); From bdaff2986d2d5b27e0939f93e4e550194db01fe9 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:34:33 +0000 Subject: [PATCH 12/76] [Source/cryptalgo/SecureSocketPort] : correct check for invalid context --- Source/cryptalgo/SecureSocketPort.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 052333263..49c5d5ed1 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -105,7 +105,7 @@ namespace Crypto { // Signal a state change, Opened, Closed or Accepted void StateChange() override { - if (IsOpen() && _context != nullptr) { // Initialize may not yet have happened + if (IsOpen() && _context == nullptr) { // Initialize may not yet have happened Initialize(); } From 25634c61c665a8629285590690a51b29c91a06c8 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 09:42:50 +0000 Subject: [PATCH 13/76] [Source/core/SocketPort Source/cryptalgo/SecureSocketPort Source/websocket/WebSocketLink] : cherry pick from 'master' --- Source/core/SocketPort.cpp | 12 ++- Source/core/SocketPort.h | 8 +- Source/cryptalgo/SecureSocketPort.cpp | 123 +++++++++++++++----------- Source/cryptalgo/SecureSocketPort.h | 18 ++-- Source/websocket/WebSocketLink.h | 8 +- 5 files changed, 96 insertions(+), 73 deletions(-) diff --git a/Source/core/SocketPort.cpp b/Source/core/SocketPort.cpp index 5a487e84f..c8e953396 100644 --- a/Source/core/SocketPort.cpp +++ b/Source/core/SocketPort.cpp @@ -496,9 +496,15 @@ namespace Thunder { m_SendOffset = 0; if ((m_State.load(Core::memory_order::memory_order_relaxed) & (SocketPort::LINK | SocketPort::OPEN | SocketPort::MONITOR)) == (SocketPort::LINK | SocketPort::OPEN)) { - // Open up an accepted socket, but not yet added to the monitor. - m_State.fetch_or(SocketPort::UPDATE, Core::memory_order::memory_order_relaxed); - nStatus = Core::ERROR_NONE; + + if (Initialize() != Core::ERROR_NONE) { + nStatus = Core::ERROR_ABORTED; + } + else { + // Open up an accepted socket, but not yet added to the monitor. + m_State.fetch_or(SocketPort::UPDATE, Core::memory_order::memory_order_relaxed); + nStatus = Core::ERROR_INPROGRESS; + } } else { ASSERT((m_Socket == INVALID_SOCKET) && (m_State.load(Core::memory_order::memory_order_relaxed) == 0)); diff --git a/Source/core/SocketPort.h b/Source/core/SocketPort.h index efadf89bb..2f3f51b31 100644 --- a/Source/core/SocketPort.h +++ b/Source/core/SocketPort.h @@ -250,6 +250,10 @@ namespace Thunder { NodeId Accept(); void Listen(); SOCKET Accept(NodeId& remoteId); + IResource::handle Descriptor() const override + { + return (static_cast(m_Socket)); + } protected: virtual uint32_t Initialize(); @@ -267,10 +271,6 @@ namespace Thunder { private: string Identifier(const NodeId& node) const; - IResource::handle Descriptor() const override - { - return (static_cast(m_Socket)); - } inline uint32_t SocketMode() const { return (((m_SocketType == LISTEN) || (m_SocketType == STREAM)) ? SOCK_STREAM : ((m_SocketType == DATAGRAM) ? SOCK_DGRAM : (m_SocketType == SEQUENCED ? SOCK_SEQPACKET : SOCK_RAW))); diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 5299331d3..073414867 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -122,55 +122,51 @@ bool SecureSocketPort::Certificate::Verify(string& errorMsg) const { SecureSocketPort::Handler::~Handler() { - if(_ssl != nullptr) { - SSL_free(static_cast(_ssl)); - } - if(_context != nullptr) { - SSL_CTX_free(static_cast(_context)); - } + ASSERT(IsClosed() == true); + Close(0); } uint32_t SecureSocketPort::Handler::Initialize() { - uint32_t success = Core::ERROR_GENERAL; - - if ((_context = SSL_CTX_new(TLS_method())) != nullptr) { - if ( // Returns bit-mask after adding options - ((SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3) & (SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)) == (SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)) - && // Trust the same certificates as any other application - (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) - && ((_ssl = SSL_new(static_cast(_context))) != nullptr) - && (SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()) == 1) - ) { - success = Core::SocketPort::Initialize(); - } else { - TRACE_L1("OpenSSL failed to set protocol level or load certificate store"); - success = Core::ERROR_GENERAL; + uint32_t success = Core::ERROR_NONE; + + if (IsOpen() == true) { + _context = SSL_CTX_new(TLS_server_method()); + _handShaking = ACCEPTING; + } + else { + _context = SSL_CTX_new(TLS_method()); + _handShaking = CONNECTING; + } + + _ssl = SSL_new(static_cast(_context)); + SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); + SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2); + + // Trust the same certificates as any other application + if (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) { + success = Core::SocketPort::Initialize(); + + if (success == Core::ERROR_NONE) { + SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()); } } + else { + TRACE_L1("OpenSSL failed to load certificate store"); + success = Core::ERROR_GENERAL; + } return success; } int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { - int32_t result = 0; - - if (_handShaking == CONNECTED) { - int value = SSL_read(static_cast(_ssl), buffer, length); - result = (value > 0 ? value : 0); + if (_handShaking != CONNECTED) { + const_cast(*this).Update(); } - - return (result); + return (SSL_read(static_cast(_ssl), buffer, length)); } int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { - int32_t result = 0; - - if (_handShaking == CONNECTED) { - int value = (SSL_write(static_cast(_ssl), buffer, length)); - result = (value > 0 ? value : 0); - } - - return (result); + return (SSL_write(static_cast(_ssl), buffer, length)); } @@ -181,8 +177,14 @@ uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { if (_ssl != nullptr) { SSL_shutdown(static_cast(_ssl)); + SSL_free(static_cast(_ssl)); + _ssl = nullptr; + } + if (_context != nullptr) { + SSL_CTX_free(static_cast(_context)); + _context = nullptr; } - _handShaking = IDLE; + return(Core::SocketPort::Close(waitTime)); } @@ -209,40 +211,53 @@ void SecureSocketPort::Handler::ValidateHandShake() { if (!validationError.empty()) { TRACE_L1("OpenSSL certificate validation error for %s: %s", certificate.Subject().c_str(), validationError.c_str()); } - _handShaking = IDLE; + _handShaking = ERROR; Core::SocketPort::Unlock(); SetError(); } X509_free(x509cert); } else { - _handShaking = IDLE; + _handShaking = ERROR; SetError(); } } void SecureSocketPort::Handler::Update() { - if (IsOpen() == true && _ssl != nullptr) { - if (_handShaking == IDLE) { - int result{0}; - if( (result = SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()) == 1) - && (result = SSL_connect(static_cast(_ssl)) == 1) - ) { - ValidateHandShake(); - } else { // Support non-blocking SocketPorts, result taken from SSL_connect - result = SSL_get_error(static_cast(_ssl), result); - if ((result == SSL_ERROR_WANT_READ) || (result == SSL_ERROR_WANT_WRITE)) { - _handShaking = EXCHANGE; - } + if (IsOpen() == true) { + int result; + + ASSERT(_ssl != nullptr); + + if (_handShaking == CONNECTING) { + if ((result = SSL_connect(static_cast(_ssl))) == 1) { + _handShaking = EXCHANGE; } - } else if (_handShaking == EXCHANGE) { - if (SSL_do_handshake(static_cast(_ssl)) == 1) { + } + else if (_handShaking == ACCEPTING) { + if ((result = SSL_accept(static_cast(_ssl))) == 1) { + _handShaking = EXCHANGE; + } + } + + if (_handShaking == EXCHANGE) { + if ((result = SSL_do_handshake(static_cast(_ssl))) == 1) { ValidateHandShake(); } } - } else if (_ssl != nullptr) { - _handShaking = IDLE; + + if (result != 1) { + result = SSL_get_error(static_cast(_ssl), result); + if ((result != SSL_ERROR_WANT_READ) && (result != SSL_ERROR_WANT_WRITE)) { + _handShaking = ERROR; + } + else if (result == SSL_ERROR_WANT_WRITE) { + Trigger(); + } + } + } + else { _parent.StateChange(); } } diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 49c5d5ed1..5fd037b40 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -64,9 +64,11 @@ namespace Crypto { class EXTERNAL Handler : public Core::SocketPort { private: enum state : uint8_t { - IDLE, + ACCEPTING, + CONNECTING, EXCHANGE, - CONNECTED + CONNECTED, + ERROR }; public: @@ -81,7 +83,7 @@ namespace Crypto { , _context(nullptr) , _ssl(nullptr) , _callback(nullptr) - , _handShaking(IDLE) { + , _handShaking(CONNECTING) { } ~Handler(); @@ -105,14 +107,8 @@ namespace Crypto { // Signal a state change, Opened, Closed or Accepted void StateChange() override { - if (IsOpen() && _context == nullptr) { // Initialize may not yet have happened - Initialize(); - } - - if (_ssl != nullptr) { - Update(); - } - }; + Update(); + }; inline uint32_t Callback(IValidator* callback) { uint32_t result = Core::ERROR_ILLEGAL_STATE; diff --git a/Source/websocket/WebSocketLink.h b/Source/websocket/WebSocketLink.h index 98e3d2dac..f074ed136 100644 --- a/Source/websocket/WebSocketLink.h +++ b/Source/websocket/WebSocketLink.h @@ -408,7 +408,13 @@ PUSH_WARNING(DISABLE_WARNING_THIS_IN_MEMBER_INITIALIZER_LIST) { } POP_WARNING() - ~HandlerType() override = default; + ~HandlerType() override { + // If this assert fires, it means the socket was not closed + // by the one who opened it. That is unexpected. The creater + // of this link, should (besides opening it) also close it. + ASSERT(ACTUALLINK::IsClosed() == true); + ACTUALLINK::Close(Core::infinite); + } public: bool IsOpen() const From f6e297e857a232295b955078d8de77f8f81a93bd Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 09:58:22 +0000 Subject: [PATCH 14/76] [Source/cryptalgo/SecureSocketPort] : use the generic method --- Source/cryptalgo/SecureSocketPort.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 073414867..bc21d6ece 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -130,14 +130,16 @@ uint32_t SecureSocketPort::Handler::Initialize() { uint32_t success = Core::ERROR_NONE; if (IsOpen() == true) { - _context = SSL_CTX_new(TLS_server_method()); _handShaking = ACCEPTING; - } - else { - _context = SSL_CTX_new(TLS_method()); + } else { _handShaking = CONNECTING; } + // Client and server use + _context = SSL_CTX_new(TLS_method()); + + ASSERT(_context != nullptr) + _ssl = SSL_new(static_cast(_context)); SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2); From 980e387eb49a23ee9bb086d0626012085683ea51 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:33:04 +0000 Subject: [PATCH 15/76] [Source/cryptalgo/SecureSocketPort] : Re-order creation of structures Context settings do not (necessarily) propagate to the SSL structure --- Source/cryptalgo/SecureSocketPort.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index bc21d6ece..18f08ffaf 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -138,22 +138,23 @@ uint32_t SecureSocketPort::Handler::Initialize() { // Client and server use _context = SSL_CTX_new(TLS_method()); - ASSERT(_context != nullptr) + ASSERT(_context != nullptr); - _ssl = SSL_new(static_cast(_context)); - SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); - SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2); + constexpr uint64_t options = SSL_OP_ALL | SSL_OP_NO_SSLv2; + + VARIABLE_IS_NOT_USED uint64_t bitmask = SSL_CTX_set_options(static_cast(_context), options); + + ASSERT((bitmask & options) == options); // Trust the same certificates as any other application - if (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) { + if ( // Trust the same certificates as any other application + (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) + && ((_ssl = SSL_new(static_cast(_context))) != nullptr) + && (SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()) == 1) + ) { success = Core::SocketPort::Initialize(); - - if (success == Core::ERROR_NONE) { - SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()); - } - } - else { - TRACE_L1("OpenSSL failed to load certificate store"); + } else { + TRACE_L1("OpenSSL failed to initialize: ssl structure / certificate store"); success = Core::ERROR_GENERAL; } From 25a61a33d0cfa313ed16b94d901280b1238a608e Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:55:09 +0000 Subject: [PATCH 16/76] [Source/cryptalgo/SecureSocketPort] : do not return error value for SSL read and write Only read or written bytes have values >= 0, otherwise it indicates an error and does not reflect the actual amount --- Source/cryptalgo/SecureSocketPort.cpp | 15 +- .../cryptalgo/SecureSocketPort.cpp.modified | 284 ++++++++++++++++++ Source/cryptalgo/SecureSocketPort.h.modified | 231 ++++++++++++++ .../cryptalgo/SecureSocketPort.modifications | 249 +++++++++++++++ 4 files changed, 774 insertions(+), 5 deletions(-) create mode 100644 Source/cryptalgo/SecureSocketPort.cpp.modified create mode 100644 Source/cryptalgo/SecureSocketPort.h.modified create mode 100644 Source/cryptalgo/SecureSocketPort.modifications diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 18f08ffaf..b220711ac 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -162,14 +162,19 @@ uint32_t SecureSocketPort::Handler::Initialize() { } int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { - if (_handShaking != CONNECTED) { - const_cast(*this).Update(); - } - return (SSL_read(static_cast(_ssl), buffer, length)); + ASSERT(_handShaking != CONNECTED); + + int result = SSL_read(static_cast(_ssl), buffer, length); + + return (result > 0 ? result : 0); } int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { - return (SSL_write(static_cast(_ssl), buffer, length)); + ASSERT(_handShaking != CONNECTED); + + int result = SSL_write(static_cast(_ssl), buffer, length); + + return (result > 0 ? result : 0); } diff --git a/Source/cryptalgo/SecureSocketPort.cpp.modified b/Source/cryptalgo/SecureSocketPort.cpp.modified new file mode 100644 index 000000000..e52d22c99 --- /dev/null +++ b/Source/cryptalgo/SecureSocketPort.cpp.modified @@ -0,0 +1,284 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SecureSocketPort.h" + +#include +#include + +#ifndef __WINDOWS__ +namespace { + + class OpenSSL { + public: + OpenSSL(OpenSSL&&) = delete; + OpenSSL(const OpenSSL&) = delete; + OpenSSL& operator=(OpenSSL&&) = delete; + OpenSSL& operator=(const OpenSSL&) = delete; + + OpenSSL() + { + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + } + ~OpenSSL() + { + } + }; + + static OpenSSL _initialization; +} +#endif + +namespace Thunder { + +namespace Crypto { + + static Core::Time ASN1_ToTime(const ASN1_TIME* input) + { + Core::Time result; + + if (input != nullptr) { + uint16_t year = 0; + const char* textVersion = reinterpret_cast(input->data); + + if (input->type == V_ASN1_UTCTIME) + { + year = (textVersion[0] - '0') * 10 + (textVersion[1] - '0'); + year += (year < 70 ? 2000 : 1900); + textVersion = &textVersion[2]; + } + else if (input->type == V_ASN1_GENERALIZEDTIME) + { + year = (textVersion[0] - '0') * 1000 + (textVersion[1] - '0') * 100 + (textVersion[2] - '0') * 10 + (textVersion[3] - '0'); + textVersion = &textVersion[4]; + } + uint8_t month = ((textVersion[0] - '0') * 10 + (textVersion[1] - '0')) - 1; + uint8_t day = (textVersion[2] - '0') * 10 + (textVersion[3] - '0'); + uint8_t hour = (textVersion[4] - '0') * 10 + (textVersion[5] - '0'); + uint8_t minutes = (textVersion[6] - '0') * 10 + (textVersion[7] - '0'); + uint8_t seconds = (textVersion[8] - '0') * 10 + (textVersion[9] - '0'); + + /* Note: we did not adjust the time based on time zone information */ + result = Core::Time(year, month, day, hour, minutes, seconds, 0, false); + } + return (result); + } + +string SecureSocketPort::Certificate::Issuer() const { + char buffer[1024]; + buffer[0] = '\0'; + X509_NAME_oneline(X509_get_issuer_name(_certificate), buffer, sizeof(buffer)); + + return (string(buffer)); +} + +string SecureSocketPort::Certificate::Subject() const { + char buffer[1024]; + buffer[0] = '\0'; + X509_NAME_oneline(X509_get_subject_name(_certificate), buffer, sizeof(buffer)); + + return (string(buffer)); +} + +Core::Time SecureSocketPort::Certificate::ValidFrom() const { + return(ASN1_ToTime(X509_get0_notBefore(_certificate))); +} + +Core::Time SecureSocketPort::Certificate::ValidTill() const { + return(ASN1_ToTime(X509_get0_notAfter(_certificate))); +} + +bool SecureSocketPort::Certificate::ValidHostname(const string& expectedHostname) const { + return (X509_check_host(_certificate, expectedHostname.data(), expectedHostname.size(), 0, nullptr) == 1); +} + +bool SecureSocketPort::Certificate::Verify(string& errorMsg) const { + long error = SSL_get_verify_result(_context); + + if (error != X509_V_OK) { + errorMsg = X509_verify_cert_error_string(error); + } + + return error == X509_V_OK; +} + +SecureSocketPort::Handler::~Handler() { + if(_ssl != nullptr) { + SSL_free(static_cast(_ssl)); + } + if(_context != nullptr) { + SSL_CTX_free(static_cast(_context)); + } +} + +uint32_t SecureSocketPort::Handler::Initialize() { + uint32_t success = Core::ERROR_NONE; +//implicitly sets handshake method +// This may require set state or explicit connect or accept + _context = SSL_CTX_new(TLS_method()); + // +// _context = SSL_CTX_new(TLS_server_method()); + +// Load the trusted root +int result = SSL_CTX_load_verify_locations(static_cast(_context), "RootCA.pem", nullptr); +// Specify server certificate + result = SSL_CTX_use_certificate_chain_file(static_cast(_context), "localhost.pem"); +// Load private key for server certificate +result = SSL_CTX_use_PrivateKey_file(static_cast(_context), "localhost.key", SSL_FILETYPE_PEM); + + + // CTX setting have no effect after, then, use SSL setters instead + _ssl = SSL_new(static_cast(_context)); + + + SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); +// SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + +// Load the trusted root +// result = SSL_CTX_load_verify_locations(static_cast(_context), "RootCA.pem", nullptr); +// Specify server certificate +//int result = SSL_CTX_use_certificate_chain_file(static_cast(_context), "localhost.pem"); +// Load private key for server certificate +//result = SSL_CTX_use_PrivateKey_file(static_cast(_context), "localhost.key", SSL_FILETYPE_PEM); + + if (result == 1) { + // Trust the same certificates as any other application + // Actually the wrong place +// if (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) { + success = Core::SocketPort::Initialize(); + } else { + TRACE_L1("OpenSSL failed to load certificate store"); + success = Core::ERROR_GENERAL; + } + + return success; +} + +int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { + int32_t result = 0; + + if (_handShaking == CONNECTED) { + int value = SSL_read(static_cast(_ssl), buffer, length); + result = (value > 0 ? value : 0); + } + + return (result); +} + +int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { + int32_t result = 0; + + if (_handShaking == CONNECTED) { + int value = (SSL_write(static_cast(_ssl), buffer, length)); + result = (value > 0 ? value : 0); + } + + return (result); +} + +uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { + uint32_t result = Core::SocketPort::Open(waitTime); + + if (result == Core::ERROR_NONE) { + result = Initialize(); + } + + return result; +} + +uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { + if (_ssl != nullptr) { + SSL_shutdown(static_cast(_ssl)); + } + _handShaking = IDLE; + return(Core::SocketPort::Close(waitTime)); +} + +void SecureSocketPort::Handler::ValidateHandShake() { + // Step 1: verify a server certificate was presented during the negotiation + X509* x509cert = SSL_get_peer_certificate(static_cast(_ssl)); + if (x509cert != nullptr) { + Core::SocketPort::Lock(); + + Certificate certificate(x509cert, static_cast(_ssl)); + + // Step 2: Validate certificate - use custom IValidator instance if available or if self signed + // certificates are needed :-) + string validationError; + if (_callback && _callback->Validate(certificate) == true) { + _handShaking = CONNECTED; + Core::SocketPort::Unlock(); + _parent.StateChange(); + } else if (certificate.Verify(validationError) && certificate.ValidHostname(RemoteNode().HostName())) { + _handShaking = CONNECTED; + Core::SocketPort::Unlock();\ + _parent.StateChange(); + } else { + if (!validationError.empty()) { + TRACE_L1("OpenSSL certificate validation error for %s: %s", certificate.Subject().c_str(), validationError.c_str()); + } + _handShaking = IDLE; + Core::SocketPort::Unlock(); + SetError(); + } + + X509_free(x509cert); + } else { + _handShaking = IDLE; + SetError(); + } +} + +void SecureSocketPort::Handler::Update() { + if (IsOpen() == true) { + int result; + + if (_handShaking == IDLE) { +// SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()); + +//this is the next step, for server ssl_acept is required +result = SSL_accept(static_cast(_ssl)); + +// result = SSL_connect(static_cast(_ssl)); + if (result == 1) { +// ValidateHandShake(); + } + else { + result = SSL_get_error(static_cast(_ssl), result); + if ((result == SSL_ERROR_WANT_READ) || (result == SSL_ERROR_WANT_WRITE)) { + _handShaking = EXCHANGE; + } + } + } + else if (_handShaking == EXCHANGE) { +// This is alrady triggert by connet and accept, this should be used when setting sttae +// if (SSL_do_handshake(static_cast(_ssl)) == 1) { +// ValidateHandShake(); +// } + } + } + else if (_ssl != nullptr) { + _handShaking = IDLE; + _parent.StateChange(); + } +} + +} } // namespace Thunder::Crypto diff --git a/Source/cryptalgo/SecureSocketPort.h.modified b/Source/cryptalgo/SecureSocketPort.h.modified new file mode 100644 index 000000000..b787845fe --- /dev/null +++ b/Source/cryptalgo/SecureSocketPort.h.modified @@ -0,0 +1,231 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" + +struct x509_store_ctx_st; +struct x509_st; +struct ssl_st; + +namespace Thunder { +namespace Crypto { + + class EXTERNAL SecureSocketPort : public Core::IResource { + public: + class EXTERNAL Certificate { + public: + Certificate() = delete; + Certificate(Certificate&&) = delete; + Certificate(const Certificate&) = delete; + + Certificate(x509_st* certificate, const ssl_st* context) + : _certificate(certificate) + , _context(context) { + } + ~Certificate() = default; + + public: + string Issuer() const; + string Subject() const; + Core::Time ValidFrom() const; + Core::Time ValidTill() const; + bool ValidHostname(const string& expectedHostname) const; + bool Verify(string& errorMsg) const; + + private: + x509_st* _certificate; + const ssl_st* _context; + }; + struct IValidator { + virtual ~IValidator() = default; + + virtual bool Validate(const Certificate& certificate) const = 0; + }; + + private: + class EXTERNAL Handler : public Core::SocketPort { + private: + enum state : uint8_t { + IDLE, + EXCHANGE, + CONNECTED + }; + + Handler(Handler&&) = delete; + Handler(const Handler&) = delete; + Handler& operator=(const Handler&) = delete; + + template + Handler(SecureSocketPort& parent, Args&&... args) + : Core::SocketPort(args...) + , _parent(parent) + , _context(nullptr) + , _ssl(nullptr) + , _callback(nullptr) + , _handShaking(IDLE) + , _mode{mode_t::CLIENT} + {} + ~Handler(); + + public: + uint32_t Initialize() override; + + int32_t Read(uint8_t buffer[], const uint16_t length) const override; + int32_t Write(const uint8_t buffer[], const uint16_t length) override; + + uint32_t Open(const uint32_t waitTime); + uint32_t Close(const uint32_t waitTime); + + // Methods to extract and insert data into the socket buffers + uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) override { + return (_parent.SendData(dataFrame, maxSendSize)); + } + + uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) override { + return (_parent.ReceiveData(dataFrame, receivedSize)); + } + + // Signal a state change, Opened, Closed or Accepted + void StateChange() override { + if (_context != nullptr) { // Initialize may not yet have happened + Update(); + } + }; + inline uint32_t Callback(IValidator* callback) { + uint32_t result = Core::ERROR_ILLEGAL_STATE; + + Core::SocketPort::Lock(); + + ASSERT((callback == nullptr) || (_callback == nullptr)); + + if ((callback == nullptr) || (_callback == nullptr)) { + _callback = callback; + result = Core::ERROR_NONE; + } + Core::SocketPort::Unlock(); + + return (result); + } + + private: + void Update(); + void ValidateHandShake(); + + private: + SecureSocketPort& _parent; + void* _context; + void* _ssl; + IValidator* _callback; + mutable state _handShaking; + mode_t _mode; + }; + + private: + public: + SecureSocketPort(SecureSocketPort&&) = delete; + SecureSocketPort(const SecureSocketPort&) = delete; + SecureSocketPort& operator=(const SecureSocketPort&) = delete; + + template + SecureSocketPort(Args&&... args) + : _handler(*this, args...) { + } + ~SecureSocketPort() override { + } + + public: + inline bool IsOpen() const + { + return (_handler.IsOpen()); + } + inline bool IsClosed() const + { + return (_handler.IsClosed()); + } + inline bool IsSuspended() const { + return (_handler.IsSuspended()); + } + inline bool HasError() const + { + return (_handler.HasError()); + } + inline string LocalId() const + { + return (_handler.LocalId()); + } + inline string RemoteId() const + { + return (_handler.RemoteId()); + } + inline void LocalNode(const Core::NodeId& node) + { + _handler.LocalNode(node); + } + inline void RemoteNode(const Core::NodeId& node) + { + _handler.RemoteNode(node); + } + inline const Core::NodeId& RemoteNode() const + { + return (_handler.RemoteNode()); + } + + inline uint32_t Open(const uint32_t waitTime) { + return(_handler.Open(waitTime)); + } + inline uint32_t Close(const uint32_t waitTime) { + return(_handler.Close(waitTime)); + } + inline void Trigger() { + _handler.Trigger(); + } + inline uint32_t Callback(IValidator* callback) { + return (_handler.Callback(callback)); + } +public: + // + // Core::IResource interface + // ------------------------------------------------------------------------ + IResource::handle Descriptor() const override + { + return (static_cast(_handler).Descriptor()); + } + uint16_t Events() override + { + return (static_cast(_handler).Events()); + } + void Handle(const uint16_t events) override + { + static_cast(_handler).Handle(events); + } + + // Methods to extract and insert data into the socket buffers + virtual uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) = 0; + virtual uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) = 0; + + // Signal a state change, Opened, Closed or Accepted + virtual void StateChange() = 0; + + private: + Handler _handler; + }; +} +} diff --git a/Source/cryptalgo/SecureSocketPort.modifications b/Source/cryptalgo/SecureSocketPort.modifications new file mode 100644 index 000000000..e2f721406 --- /dev/null +++ b/Source/cryptalgo/SecureSocketPort.modifications @@ -0,0 +1,249 @@ +commit b392a4b6f175241ff71febaae618150ef3385991 +Merge: feef8577 48054e39 +Author: msieben <4319079+msieben@users.noreply.github.com> +Date: Fri Nov 22 08:53:08 2024 +0000 + + WIP on development/websocket: feef8577 [Source/crypto/SecureSocket] : correct check for invalid context + +diff --cc Source/cryptalgo/SecureSocketPort.h +index 49c5d5ed,49c5d5ed..72fb4f6b +--- a/Source/cryptalgo/SecureSocketPort.h ++++ b/Source/cryptalgo/SecureSocketPort.h +@@@ -70,18 -70,18 +70,26 @@@ namespace Crypto + }; + + public: +++ // Type of underlying socket +++ enum class sockettype_t : uint8_t { +++ CLIENT_SOCKET +++ , SERVER_SOCKET +++ }; +++ + Handler(Handler&&) = delete; + Handler(const Handler&) = delete; + Handler& operator=(const Handler&) = delete; + + template +-- Handler(SecureSocketPort& parent, Args&&... args) +++ Handler(SecureSocketPort& parent, sockettype_t type, Args&&... args) + : Core::SocketPort(args...) + , _parent(parent) + , _context(nullptr) + , _ssl(nullptr) + , _callback(nullptr) +-- , _handShaking(IDLE) { +++ , _handShaking(IDLE) +++ , _type{type} +++ { + } + ~Handler(); + +@@@ -139,6 -139,6 +147,7 @@@ + void* _ssl; + IValidator* _callback; + mutable state _handShaking; +++ sockettype_t _type; + }; + + public: +@@@ -146,10 -146,10 +155,25 @@@ + SecureSocketPort(const SecureSocketPort&) = delete; + SecureSocketPort& operator=(const SecureSocketPort&) = delete; + +++ protected: +++ // Operational context +++ enum class context_t : uint8_t { +++ CLIENT_CONTEXT +++ , SERVER_CONTEXT +++ }; +++ +++ // For the bravehearted, and, those who derive +++ template +++ SecureSocketPort(context_t context, Args&&... args) +++ : _handler{*this, context == context_t::SERVER_CONTEXT ? Handler::sockettype_t::SERVER_SOCKET : Handler::sockettype_t::CLIENT_SOCKET, args...} +++ {} +++ +++ public: + template + SecureSocketPort(Args&&... args) +-- : _handler(*this, args...) { +-- } +++ : SecureSocketPort{context_t::CLIENT_CONTEXT, args...} +++ {} +++ + ~SecureSocketPort() override { + } + +diff --cc Tests/unit/core/CMakeLists.txt +index eb8aa882,eb8aa882..cc4e499d +--- a/Tests/unit/core/CMakeLists.txt ++++ b/Tests/unit/core/CMakeLists.txt +@@@ -21,120 -21,120 +21,10 @@@ if(LINUX + # IPTestAdministrator only supported on LINUX platform + add_executable(${TEST_RUNNER_NAME} + ../IPTestAdministrator.cpp +-- test_cyclicbuffer.cpp +-- test_cyclicbuffer_dataexchange.cpp +-- test_databuffer.cpp +-- test_dataelement.cpp +-- test_dataelementfile.cpp +-- test_doorbell.cpp +-- test_enumerate.cpp +-- test_event.cpp +-- test_filesystem.cpp +-- test_frametype.cpp +-- test_hash.cpp +-- test_hex2strserialization.cpp +-- #test_ipc.cpp +-- test_ipcclient.cpp +-- test_iso639.cpp +-- test_iterator.cpp +-- test_jsonparser.cpp +-- test_keyvalue.cpp +-- test_library.cpp +-- test_lockablecontainer.cpp +-- test_measurementtype.cpp +-- test_memberavailability.cpp +-- test_message_dispatcher.cpp +-- test_messageException.cpp +-- test_networkinfo.cpp +-- test_nodeid.cpp +-- test_numbertype.cpp +-- test_optional.cpp +-- test_parser.cpp +-- test_portability.cpp +-- test_processinfo.cpp +-- test_queue.cpp +-- test_rangetype.cpp +-- test_readwritelock.cpp +-- test_rectangle.cpp +-- test_rpc.cpp +-- test_semaphore.cpp +-- test_sharedbuffer.cpp +-- test_singleton.cpp +-- test_socketstreamjson.cpp +-- test_socketstreamtext.cpp +-- test_statetrigger.cpp +-- test_stopwatch.cpp +-- test_synchronize.cpp +-- test_synchronous.cpp +-- test_systeminfo.cpp +-- test_textfragment.cpp +-- test_textreader.cpp +-- test_thread.cpp +-- test_threadpool.cpp +-- test_time.cpp +-- test_timer.cpp +-- test_tristate.cpp +-- #test_valuerecorder.cpp +-- test_weblinkjson.cpp +-- test_weblinktext.cpp +-- test_websocketjson.cpp +-- test_websockettext.cpp +-- test_workerpool.cpp +-- test_xgetopt.cpp +++ test_websocket.cpp + ) +--#[[ +--target_sources(${TEST_RUNNER_NAME} PRIVATE test_message_unit.cpp) +--target_link_libraries(${TEST_RUNNER_NAME} +-- ThunderMessaging +--) +--]] + else() + add_executable(${TEST_RUNNER_NAME} +-- test_databuffer.cpp +-- test_dataelement.cpp +-- test_dataelementfile.cpp +-- test_enumerate.cpp +-- test_event.cpp +-- test_filesystem.cpp +-- test_frametype.cpp +-- test_hash.cpp +-- test_hex2strserialization.cpp +-- test_iso639.cpp +-- test_iterator.cpp +-- test_jsonparser.cpp +-- test_keyvalue.cpp +-- test_library.cpp +-- test_lockablecontainer.cpp +-- test_measurementtype.cpp +-- test_memberavailability.cpp +-- test_messageException.cpp +-- test_networkinfo.cpp +-- test_nodeid.cpp +-- test_numbertype.cpp +-- test_optional.cpp +-- test_parser.cpp +-- test_portability.cpp +-- test_processinfo.cpp +-- test_queue.cpp +-- test_rangetype.cpp +-- test_readwritelock.cpp +-- test_rectangle.cpp +-- test_semaphore.cpp +-- test_singleton.cpp +-- test_statetrigger.cpp +-- test_stopwatch.cpp +-- test_synchronize.cpp +-- test_systeminfo.cpp +-- test_textfragment.cpp +-- test_textreader.cpp +-- test_thread.cpp +-- test_threadpool.cpp +-- test_time.cpp +-- test_timer.cpp +-- test_tristate.cpp +-- #test_valuerecorder.cpp +-- test_workerpool.cpp +-- test_xgetopt.cpp + ) + endif() + +diff --cc Tests/unit/core/test_websocket.cpp +index a2cb1f4f,a2cb1f4f..ebe33267 +--- a/Tests/unit/core/test_websocket.cpp ++++ b/Tests/unit/core/test_websocket.cpp +@@@ -424,7 -424,7 +424,7 @@@ namespace Core + , const uint16_t receiveBufferSize + , const std::string& prefix + ) +-- : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) +++ : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + , _prefix{ prefix } + , _validator{} + { +@@@ -440,7 -440,7 +440,7 @@@ + , const uint16_t receiveBufferSize + , const std::string& prefix + ) +-- : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) +++ : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + , _prefix{ prefix } + , _validator{} + { +@@@ -1138,8 -1138,8 +1138,7 @@@ + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + // SleepMs(maxWaitTimeMs); +-- +-- ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); +++ EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + +diff --cc mkdocs.yml +index 8c4a46d1,8c4a46d1..18e6890b +--- a/mkdocs.yml ++++ b/mkdocs.yml +@@@ -79,7 -79,7 +79,9 @@@ nav + - Documentation: docs.md + - References: references/references.md + - Filing an Issue: issuetemplate/issuetemplate.md +-- +++ - RFC : +++ - WebSocket protocol: rfc/websocket.md +++ - WebSocket implementation: rfc/websocketimpl.md + + markdown_extensions: + - admonition From d351981ebfe5d42cd285ae3493cfa4379be0f67f Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:04:36 +0000 Subject: [PATCH 17/76] [Source/cryptalgo/SecureSocketPort] : add assert for invalid context and sll structure in 'Update' --- Source/cryptalgo/SecureSocketPort.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 5fd037b40..6eeae6ded 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -107,6 +107,8 @@ namespace Crypto { // Signal a state change, Opened, Closed or Accepted void StateChange() override { + ASSERT(_context != nullptr && _ssl != nullptr); + Update(); }; inline uint32_t Callback(IValidator* callback) { From 03a69dcedd40290d017283dc2bf49cd0ae149774 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:37:53 +0100 Subject: [PATCH 18/76] Delete Source/cryptalgo/SecureSocketPort.cpp.modified Whoops .... --- .../cryptalgo/SecureSocketPort.cpp.modified | 284 ------------------ 1 file changed, 284 deletions(-) delete mode 100644 Source/cryptalgo/SecureSocketPort.cpp.modified diff --git a/Source/cryptalgo/SecureSocketPort.cpp.modified b/Source/cryptalgo/SecureSocketPort.cpp.modified deleted file mode 100644 index e52d22c99..000000000 --- a/Source/cryptalgo/SecureSocketPort.cpp.modified +++ /dev/null @@ -1,284 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2020 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SecureSocketPort.h" - -#include -#include - -#ifndef __WINDOWS__ -namespace { - - class OpenSSL { - public: - OpenSSL(OpenSSL&&) = delete; - OpenSSL(const OpenSSL&) = delete; - OpenSSL& operator=(OpenSSL&&) = delete; - OpenSSL& operator=(const OpenSSL&) = delete; - - OpenSSL() - { - SSL_library_init(); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - } - ~OpenSSL() - { - } - }; - - static OpenSSL _initialization; -} -#endif - -namespace Thunder { - -namespace Crypto { - - static Core::Time ASN1_ToTime(const ASN1_TIME* input) - { - Core::Time result; - - if (input != nullptr) { - uint16_t year = 0; - const char* textVersion = reinterpret_cast(input->data); - - if (input->type == V_ASN1_UTCTIME) - { - year = (textVersion[0] - '0') * 10 + (textVersion[1] - '0'); - year += (year < 70 ? 2000 : 1900); - textVersion = &textVersion[2]; - } - else if (input->type == V_ASN1_GENERALIZEDTIME) - { - year = (textVersion[0] - '0') * 1000 + (textVersion[1] - '0') * 100 + (textVersion[2] - '0') * 10 + (textVersion[3] - '0'); - textVersion = &textVersion[4]; - } - uint8_t month = ((textVersion[0] - '0') * 10 + (textVersion[1] - '0')) - 1; - uint8_t day = (textVersion[2] - '0') * 10 + (textVersion[3] - '0'); - uint8_t hour = (textVersion[4] - '0') * 10 + (textVersion[5] - '0'); - uint8_t minutes = (textVersion[6] - '0') * 10 + (textVersion[7] - '0'); - uint8_t seconds = (textVersion[8] - '0') * 10 + (textVersion[9] - '0'); - - /* Note: we did not adjust the time based on time zone information */ - result = Core::Time(year, month, day, hour, minutes, seconds, 0, false); - } - return (result); - } - -string SecureSocketPort::Certificate::Issuer() const { - char buffer[1024]; - buffer[0] = '\0'; - X509_NAME_oneline(X509_get_issuer_name(_certificate), buffer, sizeof(buffer)); - - return (string(buffer)); -} - -string SecureSocketPort::Certificate::Subject() const { - char buffer[1024]; - buffer[0] = '\0'; - X509_NAME_oneline(X509_get_subject_name(_certificate), buffer, sizeof(buffer)); - - return (string(buffer)); -} - -Core::Time SecureSocketPort::Certificate::ValidFrom() const { - return(ASN1_ToTime(X509_get0_notBefore(_certificate))); -} - -Core::Time SecureSocketPort::Certificate::ValidTill() const { - return(ASN1_ToTime(X509_get0_notAfter(_certificate))); -} - -bool SecureSocketPort::Certificate::ValidHostname(const string& expectedHostname) const { - return (X509_check_host(_certificate, expectedHostname.data(), expectedHostname.size(), 0, nullptr) == 1); -} - -bool SecureSocketPort::Certificate::Verify(string& errorMsg) const { - long error = SSL_get_verify_result(_context); - - if (error != X509_V_OK) { - errorMsg = X509_verify_cert_error_string(error); - } - - return error == X509_V_OK; -} - -SecureSocketPort::Handler::~Handler() { - if(_ssl != nullptr) { - SSL_free(static_cast(_ssl)); - } - if(_context != nullptr) { - SSL_CTX_free(static_cast(_context)); - } -} - -uint32_t SecureSocketPort::Handler::Initialize() { - uint32_t success = Core::ERROR_NONE; -//implicitly sets handshake method -// This may require set state or explicit connect or accept - _context = SSL_CTX_new(TLS_method()); - // -// _context = SSL_CTX_new(TLS_server_method()); - -// Load the trusted root -int result = SSL_CTX_load_verify_locations(static_cast(_context), "RootCA.pem", nullptr); -// Specify server certificate - result = SSL_CTX_use_certificate_chain_file(static_cast(_context), "localhost.pem"); -// Load private key for server certificate -result = SSL_CTX_use_PrivateKey_file(static_cast(_context), "localhost.key", SSL_FILETYPE_PEM); - - - // CTX setting have no effect after, then, use SSL setters instead - _ssl = SSL_new(static_cast(_context)); - - - SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); -// SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); - -// Load the trusted root -// result = SSL_CTX_load_verify_locations(static_cast(_context), "RootCA.pem", nullptr); -// Specify server certificate -//int result = SSL_CTX_use_certificate_chain_file(static_cast(_context), "localhost.pem"); -// Load private key for server certificate -//result = SSL_CTX_use_PrivateKey_file(static_cast(_context), "localhost.key", SSL_FILETYPE_PEM); - - if (result == 1) { - // Trust the same certificates as any other application - // Actually the wrong place -// if (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) { - success = Core::SocketPort::Initialize(); - } else { - TRACE_L1("OpenSSL failed to load certificate store"); - success = Core::ERROR_GENERAL; - } - - return success; -} - -int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { - int32_t result = 0; - - if (_handShaking == CONNECTED) { - int value = SSL_read(static_cast(_ssl), buffer, length); - result = (value > 0 ? value : 0); - } - - return (result); -} - -int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { - int32_t result = 0; - - if (_handShaking == CONNECTED) { - int value = (SSL_write(static_cast(_ssl), buffer, length)); - result = (value > 0 ? value : 0); - } - - return (result); -} - -uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { - uint32_t result = Core::SocketPort::Open(waitTime); - - if (result == Core::ERROR_NONE) { - result = Initialize(); - } - - return result; -} - -uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { - if (_ssl != nullptr) { - SSL_shutdown(static_cast(_ssl)); - } - _handShaking = IDLE; - return(Core::SocketPort::Close(waitTime)); -} - -void SecureSocketPort::Handler::ValidateHandShake() { - // Step 1: verify a server certificate was presented during the negotiation - X509* x509cert = SSL_get_peer_certificate(static_cast(_ssl)); - if (x509cert != nullptr) { - Core::SocketPort::Lock(); - - Certificate certificate(x509cert, static_cast(_ssl)); - - // Step 2: Validate certificate - use custom IValidator instance if available or if self signed - // certificates are needed :-) - string validationError; - if (_callback && _callback->Validate(certificate) == true) { - _handShaking = CONNECTED; - Core::SocketPort::Unlock(); - _parent.StateChange(); - } else if (certificate.Verify(validationError) && certificate.ValidHostname(RemoteNode().HostName())) { - _handShaking = CONNECTED; - Core::SocketPort::Unlock();\ - _parent.StateChange(); - } else { - if (!validationError.empty()) { - TRACE_L1("OpenSSL certificate validation error for %s: %s", certificate.Subject().c_str(), validationError.c_str()); - } - _handShaking = IDLE; - Core::SocketPort::Unlock(); - SetError(); - } - - X509_free(x509cert); - } else { - _handShaking = IDLE; - SetError(); - } -} - -void SecureSocketPort::Handler::Update() { - if (IsOpen() == true) { - int result; - - if (_handShaking == IDLE) { -// SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()); - -//this is the next step, for server ssl_acept is required -result = SSL_accept(static_cast(_ssl)); - -// result = SSL_connect(static_cast(_ssl)); - if (result == 1) { -// ValidateHandShake(); - } - else { - result = SSL_get_error(static_cast(_ssl), result); - if ((result == SSL_ERROR_WANT_READ) || (result == SSL_ERROR_WANT_WRITE)) { - _handShaking = EXCHANGE; - } - } - } - else if (_handShaking == EXCHANGE) { -// This is alrady triggert by connet and accept, this should be used when setting sttae -// if (SSL_do_handshake(static_cast(_ssl)) == 1) { -// ValidateHandShake(); -// } - } - } - else if (_ssl != nullptr) { - _handShaking = IDLE; - _parent.StateChange(); - } -} - -} } // namespace Thunder::Crypto From a596018915ee6eb1a77b199f3add41cc47320c3d Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:38:14 +0100 Subject: [PATCH 19/76] Delete Source/cryptalgo/SecureSocketPort.h.modified Whoops --- Source/cryptalgo/SecureSocketPort.h.modified | 231 ------------------- 1 file changed, 231 deletions(-) delete mode 100644 Source/cryptalgo/SecureSocketPort.h.modified diff --git a/Source/cryptalgo/SecureSocketPort.h.modified b/Source/cryptalgo/SecureSocketPort.h.modified deleted file mode 100644 index b787845fe..000000000 --- a/Source/cryptalgo/SecureSocketPort.h.modified +++ /dev/null @@ -1,231 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2020 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "Module.h" - -struct x509_store_ctx_st; -struct x509_st; -struct ssl_st; - -namespace Thunder { -namespace Crypto { - - class EXTERNAL SecureSocketPort : public Core::IResource { - public: - class EXTERNAL Certificate { - public: - Certificate() = delete; - Certificate(Certificate&&) = delete; - Certificate(const Certificate&) = delete; - - Certificate(x509_st* certificate, const ssl_st* context) - : _certificate(certificate) - , _context(context) { - } - ~Certificate() = default; - - public: - string Issuer() const; - string Subject() const; - Core::Time ValidFrom() const; - Core::Time ValidTill() const; - bool ValidHostname(const string& expectedHostname) const; - bool Verify(string& errorMsg) const; - - private: - x509_st* _certificate; - const ssl_st* _context; - }; - struct IValidator { - virtual ~IValidator() = default; - - virtual bool Validate(const Certificate& certificate) const = 0; - }; - - private: - class EXTERNAL Handler : public Core::SocketPort { - private: - enum state : uint8_t { - IDLE, - EXCHANGE, - CONNECTED - }; - - Handler(Handler&&) = delete; - Handler(const Handler&) = delete; - Handler& operator=(const Handler&) = delete; - - template - Handler(SecureSocketPort& parent, Args&&... args) - : Core::SocketPort(args...) - , _parent(parent) - , _context(nullptr) - , _ssl(nullptr) - , _callback(nullptr) - , _handShaking(IDLE) - , _mode{mode_t::CLIENT} - {} - ~Handler(); - - public: - uint32_t Initialize() override; - - int32_t Read(uint8_t buffer[], const uint16_t length) const override; - int32_t Write(const uint8_t buffer[], const uint16_t length) override; - - uint32_t Open(const uint32_t waitTime); - uint32_t Close(const uint32_t waitTime); - - // Methods to extract and insert data into the socket buffers - uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) override { - return (_parent.SendData(dataFrame, maxSendSize)); - } - - uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) override { - return (_parent.ReceiveData(dataFrame, receivedSize)); - } - - // Signal a state change, Opened, Closed or Accepted - void StateChange() override { - if (_context != nullptr) { // Initialize may not yet have happened - Update(); - } - }; - inline uint32_t Callback(IValidator* callback) { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - Core::SocketPort::Lock(); - - ASSERT((callback == nullptr) || (_callback == nullptr)); - - if ((callback == nullptr) || (_callback == nullptr)) { - _callback = callback; - result = Core::ERROR_NONE; - } - Core::SocketPort::Unlock(); - - return (result); - } - - private: - void Update(); - void ValidateHandShake(); - - private: - SecureSocketPort& _parent; - void* _context; - void* _ssl; - IValidator* _callback; - mutable state _handShaking; - mode_t _mode; - }; - - private: - public: - SecureSocketPort(SecureSocketPort&&) = delete; - SecureSocketPort(const SecureSocketPort&) = delete; - SecureSocketPort& operator=(const SecureSocketPort&) = delete; - - template - SecureSocketPort(Args&&... args) - : _handler(*this, args...) { - } - ~SecureSocketPort() override { - } - - public: - inline bool IsOpen() const - { - return (_handler.IsOpen()); - } - inline bool IsClosed() const - { - return (_handler.IsClosed()); - } - inline bool IsSuspended() const { - return (_handler.IsSuspended()); - } - inline bool HasError() const - { - return (_handler.HasError()); - } - inline string LocalId() const - { - return (_handler.LocalId()); - } - inline string RemoteId() const - { - return (_handler.RemoteId()); - } - inline void LocalNode(const Core::NodeId& node) - { - _handler.LocalNode(node); - } - inline void RemoteNode(const Core::NodeId& node) - { - _handler.RemoteNode(node); - } - inline const Core::NodeId& RemoteNode() const - { - return (_handler.RemoteNode()); - } - - inline uint32_t Open(const uint32_t waitTime) { - return(_handler.Open(waitTime)); - } - inline uint32_t Close(const uint32_t waitTime) { - return(_handler.Close(waitTime)); - } - inline void Trigger() { - _handler.Trigger(); - } - inline uint32_t Callback(IValidator* callback) { - return (_handler.Callback(callback)); - } -public: - // - // Core::IResource interface - // ------------------------------------------------------------------------ - IResource::handle Descriptor() const override - { - return (static_cast(_handler).Descriptor()); - } - uint16_t Events() override - { - return (static_cast(_handler).Events()); - } - void Handle(const uint16_t events) override - { - static_cast(_handler).Handle(events); - } - - // Methods to extract and insert data into the socket buffers - virtual uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) = 0; - virtual uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) = 0; - - // Signal a state change, Opened, Closed or Accepted - virtual void StateChange() = 0; - - private: - Handler _handler; - }; -} -} From f7dbccee4c127a3bc5171bcbab9184ea3dfb3b2a Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:38:32 +0100 Subject: [PATCH 20/76] Delete Source/cryptalgo/SecureSocketPort.modifications Whoops --- .../cryptalgo/SecureSocketPort.modifications | 249 ------------------ 1 file changed, 249 deletions(-) delete mode 100644 Source/cryptalgo/SecureSocketPort.modifications diff --git a/Source/cryptalgo/SecureSocketPort.modifications b/Source/cryptalgo/SecureSocketPort.modifications deleted file mode 100644 index e2f721406..000000000 --- a/Source/cryptalgo/SecureSocketPort.modifications +++ /dev/null @@ -1,249 +0,0 @@ -commit b392a4b6f175241ff71febaae618150ef3385991 -Merge: feef8577 48054e39 -Author: msieben <4319079+msieben@users.noreply.github.com> -Date: Fri Nov 22 08:53:08 2024 +0000 - - WIP on development/websocket: feef8577 [Source/crypto/SecureSocket] : correct check for invalid context - -diff --cc Source/cryptalgo/SecureSocketPort.h -index 49c5d5ed,49c5d5ed..72fb4f6b ---- a/Source/cryptalgo/SecureSocketPort.h -+++ b/Source/cryptalgo/SecureSocketPort.h -@@@ -70,18 -70,18 +70,26 @@@ namespace Crypto - }; - - public: -++ // Type of underlying socket -++ enum class sockettype_t : uint8_t { -++ CLIENT_SOCKET -++ , SERVER_SOCKET -++ }; -++ - Handler(Handler&&) = delete; - Handler(const Handler&) = delete; - Handler& operator=(const Handler&) = delete; - - template --- Handler(SecureSocketPort& parent, Args&&... args) -++ Handler(SecureSocketPort& parent, sockettype_t type, Args&&... args) - : Core::SocketPort(args...) - , _parent(parent) - , _context(nullptr) - , _ssl(nullptr) - , _callback(nullptr) --- , _handShaking(IDLE) { -++ , _handShaking(IDLE) -++ , _type{type} -++ { - } - ~Handler(); - -@@@ -139,6 -139,6 +147,7 @@@ - void* _ssl; - IValidator* _callback; - mutable state _handShaking; -++ sockettype_t _type; - }; - - public: -@@@ -146,10 -146,10 +155,25 @@@ - SecureSocketPort(const SecureSocketPort&) = delete; - SecureSocketPort& operator=(const SecureSocketPort&) = delete; - -++ protected: -++ // Operational context -++ enum class context_t : uint8_t { -++ CLIENT_CONTEXT -++ , SERVER_CONTEXT -++ }; -++ -++ // For the bravehearted, and, those who derive -++ template -++ SecureSocketPort(context_t context, Args&&... args) -++ : _handler{*this, context == context_t::SERVER_CONTEXT ? Handler::sockettype_t::SERVER_SOCKET : Handler::sockettype_t::CLIENT_SOCKET, args...} -++ {} -++ -++ public: - template - SecureSocketPort(Args&&... args) --- : _handler(*this, args...) { --- } -++ : SecureSocketPort{context_t::CLIENT_CONTEXT, args...} -++ {} -++ - ~SecureSocketPort() override { - } - -diff --cc Tests/unit/core/CMakeLists.txt -index eb8aa882,eb8aa882..cc4e499d ---- a/Tests/unit/core/CMakeLists.txt -+++ b/Tests/unit/core/CMakeLists.txt -@@@ -21,120 -21,120 +21,10 @@@ if(LINUX - # IPTestAdministrator only supported on LINUX platform - add_executable(${TEST_RUNNER_NAME} - ../IPTestAdministrator.cpp --- test_cyclicbuffer.cpp --- test_cyclicbuffer_dataexchange.cpp --- test_databuffer.cpp --- test_dataelement.cpp --- test_dataelementfile.cpp --- test_doorbell.cpp --- test_enumerate.cpp --- test_event.cpp --- test_filesystem.cpp --- test_frametype.cpp --- test_hash.cpp --- test_hex2strserialization.cpp --- #test_ipc.cpp --- test_ipcclient.cpp --- test_iso639.cpp --- test_iterator.cpp --- test_jsonparser.cpp --- test_keyvalue.cpp --- test_library.cpp --- test_lockablecontainer.cpp --- test_measurementtype.cpp --- test_memberavailability.cpp --- test_message_dispatcher.cpp --- test_messageException.cpp --- test_networkinfo.cpp --- test_nodeid.cpp --- test_numbertype.cpp --- test_optional.cpp --- test_parser.cpp --- test_portability.cpp --- test_processinfo.cpp --- test_queue.cpp --- test_rangetype.cpp --- test_readwritelock.cpp --- test_rectangle.cpp --- test_rpc.cpp --- test_semaphore.cpp --- test_sharedbuffer.cpp --- test_singleton.cpp --- test_socketstreamjson.cpp --- test_socketstreamtext.cpp --- test_statetrigger.cpp --- test_stopwatch.cpp --- test_synchronize.cpp --- test_synchronous.cpp --- test_systeminfo.cpp --- test_textfragment.cpp --- test_textreader.cpp --- test_thread.cpp --- test_threadpool.cpp --- test_time.cpp --- test_timer.cpp --- test_tristate.cpp --- #test_valuerecorder.cpp --- test_weblinkjson.cpp --- test_weblinktext.cpp --- test_websocketjson.cpp --- test_websockettext.cpp --- test_workerpool.cpp --- test_xgetopt.cpp -++ test_websocket.cpp - ) ---#[[ ---target_sources(${TEST_RUNNER_NAME} PRIVATE test_message_unit.cpp) ---target_link_libraries(${TEST_RUNNER_NAME} --- ThunderMessaging ---) ---]] - else() - add_executable(${TEST_RUNNER_NAME} --- test_databuffer.cpp --- test_dataelement.cpp --- test_dataelementfile.cpp --- test_enumerate.cpp --- test_event.cpp --- test_filesystem.cpp --- test_frametype.cpp --- test_hash.cpp --- test_hex2strserialization.cpp --- test_iso639.cpp --- test_iterator.cpp --- test_jsonparser.cpp --- test_keyvalue.cpp --- test_library.cpp --- test_lockablecontainer.cpp --- test_measurementtype.cpp --- test_memberavailability.cpp --- test_messageException.cpp --- test_networkinfo.cpp --- test_nodeid.cpp --- test_numbertype.cpp --- test_optional.cpp --- test_parser.cpp --- test_portability.cpp --- test_processinfo.cpp --- test_queue.cpp --- test_rangetype.cpp --- test_readwritelock.cpp --- test_rectangle.cpp --- test_semaphore.cpp --- test_singleton.cpp --- test_statetrigger.cpp --- test_stopwatch.cpp --- test_synchronize.cpp --- test_systeminfo.cpp --- test_textfragment.cpp --- test_textreader.cpp --- test_thread.cpp --- test_threadpool.cpp --- test_time.cpp --- test_timer.cpp --- test_tristate.cpp --- #test_valuerecorder.cpp --- test_workerpool.cpp --- test_xgetopt.cpp - ) - endif() - -diff --cc Tests/unit/core/test_websocket.cpp -index a2cb1f4f,a2cb1f4f..ebe33267 ---- a/Tests/unit/core/test_websocket.cpp -+++ b/Tests/unit/core/test_websocket.cpp -@@@ -424,7 -424,7 +424,7 @@@ namespace Core - , const uint16_t receiveBufferSize - , const std::string& prefix - ) --- : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) -++ : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) - , _prefix{ prefix } - , _validator{} - { -@@@ -440,7 -440,7 +440,7 @@@ - , const uint16_t receiveBufferSize - , const std::string& prefix - ) --- : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) -++ : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) - , _prefix{ prefix } - , _validator{} - { -@@@ -1138,8 -1138,8 +1138,7 @@@ - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); - - // SleepMs(maxWaitTimeMs); --- --- ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); -++ EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); - - SleepMs(maxWaitTimeMs); - -diff --cc mkdocs.yml -index 8c4a46d1,8c4a46d1..18e6890b ---- a/mkdocs.yml -+++ b/mkdocs.yml -@@@ -79,7 -79,7 +79,9 @@@ nav - - Documentation: docs.md - - References: references/references.md - - Filing an Issue: issuetemplate/issuetemplate.md --- -++ - RFC : -++ - WebSocket protocol: rfc/websocket.md -++ - WebSocket implementation: rfc/websocketimpl.md - - markdown_extensions: - - admonition From d2fcaab57a88a0c66b611f398c136175fc23775b Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:37:25 +0000 Subject: [PATCH 21/76] [Source/cryptalgo/SecureSocketPort Tests/unit/core] : Prepare for server side SSL Users should explicitly derive from teh base to make it happen as it is not the default (most common) scenario --- Source/cryptalgo/SecureSocketPort.cpp | 37 +++++++++---- Source/cryptalgo/SecureSocketPort.h | 33 +++++++++--- Tests/unit/core/test_websocket.cpp | 78 +++++++++++++++++++++++++-- 3 files changed, 126 insertions(+), 22 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index b220711ac..7f5a7a565 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -146,9 +146,16 @@ uint32_t SecureSocketPort::Handler::Initialize() { ASSERT((bitmask & options) == options); - // Trust the same certificates as any other application - if ( // Trust the same certificates as any other application - (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) + if ( ( ( (_handShaking == ACCEPTING) + // Load server certifiate and private key + && (SSL_CTX_load_verify_locations(static_cast(_context), "RootCA.pem", nullptr) == 1) + && (SSL_CTX_use_certificate_chain_file(static_cast(_context), "localhost.pem") == 1) + && (SSL_CTX_use_PrivateKey_file(static_cast(_context), "localhost.key", SSL_FILETYPE_PEM) == 1) + ) + || (_handShaking == CONNECTING) + ) + // Default location from which CA certificates are loaded + && (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) && ((_ssl = SSL_new(static_cast(_context))) != nullptr) && (SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()) == 1) ) { @@ -162,15 +169,17 @@ uint32_t SecureSocketPort::Handler::Initialize() { } int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { - ASSERT(_handShaking != CONNECTED); + int result = 0; - int result = SSL_read(static_cast(_ssl), buffer, length); + if (_handShaking == CONNECTED) { // Avoid reading while still in CONNECTING or ACCEPTING + result = SSL_read(static_cast(_ssl), buffer, length); + } return (result > 0 ? result : 0); } int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { - ASSERT(_handShaking != CONNECTED); + ASSERT(_handShaking == CONNECTED); int result = SSL_write(static_cast(_ssl), buffer, length); @@ -238,20 +247,25 @@ void SecureSocketPort::Handler::Update() { ASSERT(_ssl != nullptr); + // Client if (_handShaking == CONNECTING) { if ((result = SSL_connect(static_cast(_ssl))) == 1) { - _handShaking = EXCHANGE; + _handShaking = CONNECTED; + // If server has sent a certificate, and, we want to do 'our' own check + ValidateHandShake(); } - } + } // Server else if (_handShaking == ACCEPTING) { if ((result = SSL_accept(static_cast(_ssl))) == 1) { - _handShaking = EXCHANGE; + _handShaking = CONNECTED; + // Client has sent a certificate (optional, on request only) + ValidateHandShake(); } - } - + } // Re-initialie a previous session if (_handShaking == EXCHANGE) { if ((result = SSL_do_handshake(static_cast(_ssl))) == 1) { ValidateHandShake(); + _handShaking = CONNECTED; } } @@ -266,6 +280,7 @@ void SecureSocketPort::Handler::Update() { } } else { + /// _handShaking = CLOSED _parent.StateChange(); } } diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 9e9705623..03069e7d9 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -72,19 +72,26 @@ namespace Crypto { }; public: + // Type of underlying socket + enum class sockettype_t : uint8_t { + CLIENT_SOCKET + , SERVER_SOCKET + }; + Handler(Handler&&) = delete; Handler(const Handler&) = delete; Handler& operator=(const Handler&) = delete; template - Handler(SecureSocketPort& parent, Args&&... args) + Handler(SecureSocketPort& parent, sockettype_t type, Args&&... args) : Core::SocketPort(args...) , _parent(parent) , _context(nullptr) , _ssl(nullptr) , _callback(nullptr) - , _handShaking(CONNECTING) { - } + , _handShaking(type == sockettype_t::CLIENT_SOCKET ? CONNECTING : ACCEPTING) + , _type{type} + {} ~Handler(); public: @@ -107,7 +114,7 @@ namespace Crypto { // Signal a state change, Opened, Closed or Accepted void StateChange() override { - ASSERT(_context != nullptr && _ssl != nullptr); +// ASSERT(_context != nullptr && _ssl != nullptr); Update(); }; inline uint32_t Callback(IValidator* callback) { @@ -136,6 +143,7 @@ namespace Crypto { void* _ssl; IValidator* _callback; mutable state _handShaking; + const sockettype_t _type; }; public: @@ -143,10 +151,23 @@ namespace Crypto { SecureSocketPort(const SecureSocketPort&) = delete; SecureSocketPort& operator=(const SecureSocketPort&) = delete; + protected: + // Operational context + enum class context_t : uint8_t { + CLIENT_CONTEXT + , SERVER_CONTEXT + }; + + template + SecureSocketPort(context_t context, Args&&... args) + : _handler(*this, context == context_t:: SERVER_CONTEXT ? Handler::sockettype_t::SERVER_SOCKET : Handler::sockettype_t::CLIENT_SOCKET, args...) + {} + + public: template SecureSocketPort(Args&&... args) - : _handler(*this, args...) { - } + : SecureSocketPort(context_t::CLIENT_CONTEXT, args...) + {} ~SecureSocketPort() override { } diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index a2cb1f4f7..771abe55f 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -424,7 +424,7 @@ namespace Core { , const uint16_t receiveBufferSize , const std::string& prefix ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _prefix{ prefix } , _validator{} { @@ -440,7 +440,7 @@ namespace Core { , const uint16_t receiveBufferSize , const std::string& prefix ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _prefix{ prefix } , _validator{} { @@ -460,6 +460,75 @@ namespace Core { Validator _validator; }; + class CustomSecureServerSocketStream : public ::Thunder::Crypto::SecureSocketPort { + private : + + // Validat eclient certificate + class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidator { + public: + + Validator() = default; + ~Validator() = default; + + bool Validate(const Certificate& certificate) const override { + // Print certificate properties +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + std::cout << " |--> Issuer = " << certificate.Issuer() << "\n"; + std::cout << " |--> Subject = " << certificate.Subject() << "\n"; + std::cout << " |--> Valid from = " << certificate.ValidFrom().ToRFC1123() << "\n"; + std::cout << " |--> Valid until = " << certificate.ValidTill().ToRFC1123() << "\n"; +#endif + return true; // Always accept + } + }; + + public : + + // In essence, all parameters to SecureSocket are passed to a base class SocketPort + CustomSecureServerSocketStream( + const SOCKET& socket + , const ::Thunder::Core::NodeId& localNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + , _prefix{ prefix } + , _validator{} + { + // Validate custom (sefl signed) certificates + uint32_t result = Callback(&_validator); + } + + CustomSecureServerSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + , _prefix{ prefix } + , _validator{} + { + // Validate custom (self signed) client certificates + uint32_t result = Callback(&_validator); + } + + ~CustomSecureServerSocketStream() + { +#ifdef _VERBOSE + std::cout.flush(); +#endif + } + + private: + const std::string _prefix; + Validator _validator; + }; + TEST(WebSocket, DISABLED_OpeningServerPort) { const TCHAR localHostName[] {"127.0.0.1"}; @@ -1088,7 +1157,7 @@ namespace Core { const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; // This is a listening socket as result of using SocketServerType which enables listening - ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); @@ -1138,8 +1207,7 @@ namespace Core { WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); // SleepMs(maxWaitTimeMs); - - ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); SleepMs(maxWaitTimeMs); From f53b24847d130722ac2c39c4c72b3f90df59bc06 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:55:49 +0000 Subject: [PATCH 22/76] [Source/cryptalgo/SecureSocketPort] : peer certificate validation only if no callbacks have registered 'SSL_accept' and 'SSL_connect' implicitly validate. Client certificates can be validated if the server had sent a client certificate request --- Source/cryptalgo/SecureSocketPort.cpp | 81 +++++++++++++++++---------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 7f5a7a565..c944f0a17 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -206,37 +206,52 @@ uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { } void SecureSocketPort::Handler::ValidateHandShake() { - // Step 1: verify a server certificate was presented during the negotiation - X509* x509cert = SSL_get_peer_certificate(static_cast(_ssl)); - if (x509cert != nullptr) { - Core::SocketPort::Lock(); - - Certificate certificate(x509cert, static_cast(_ssl)); - - // Step 2: Validate certificate - use custom IValidator instance if available or if self signed - // certificates are needed :-) - string validationError; - if (_callback && _callback->Validate(certificate) == true) { - _handShaking = CONNECTED; - Core::SocketPort::Unlock(); - _parent.StateChange(); - } else if (certificate.Verify(validationError) && certificate.ValidHostname(RemoteNode().HostName())) { - _handShaking = CONNECTED; - Core::SocketPort::Unlock();\ - _parent.StateChange(); - } else { - if (!validationError.empty()) { - TRACE_L1("OpenSSL certificate validation error for %s: %s", certificate.Subject().c_str(), validationError.c_str()); + // SSL handshake does an implicit verification, its result is: + if (SSL_get_verify_result(static_cast(_ssl)) != X509_V_OK) { + _handShaking = ERROR; + SetError(); + } else if ( (SSL_CTX_get_verify_mode(static_cast(_context)) != SSL_VERIFY_NONE) + && (SSL_CTX_get_verify_callback(static_cast(_context)) != nullptr) + && (SSL_get_verify_mode(static_cast(_ssl)) != SSL_VERIFY_NONE) + && (SSL_get_verify_callback(static_cast(_ssl)) != nullptr) + ) + { + // Only relevant if the server has sent a client certificate request + // AND + // No callback has been set to do this job for us + + // Step 1: verify a certificate was presented during the negotiation + X509* x509cert = SSL_get_peer_certificate(static_cast(_ssl)); + if (x509cert != nullptr) { + Core::SocketPort::Lock(); + + Certificate certificate(x509cert, static_cast(_ssl)); + + // Step 2: Validate certificate - use custom IValidator instance if available or if self signed + // certificates are needed :-) + string validationError; + if (_callback && _callback->Validate(certificate) == true) { + _handShaking = CONNECTED; + Core::SocketPort::Unlock(); + _parent.StateChange(); + } else if (certificate.Verify(validationError) && certificate.ValidHostname(RemoteNode().HostName())) { + _handShaking = CONNECTED; + Core::SocketPort::Unlock();\ + _parent.StateChange(); + } else { + if (!validationError.empty()) { + TRACE_L1("OpenSSL certificate validation error for %s: %s", certificate.Subject().c_str(), validationError.c_str()); + } + _handShaking = ERROR; + Core::SocketPort::Unlock(); + SetError(); } + + X509_free(x509cert); + } else { _handShaking = ERROR; - Core::SocketPort::Unlock(); SetError(); } - - X509_free(x509cert); - } else { - _handShaking = ERROR; - SetError(); } } @@ -271,12 +286,16 @@ void SecureSocketPort::Handler::Update() { if (result != 1) { result = SSL_get_error(static_cast(_ssl), result); - if ((result != SSL_ERROR_WANT_READ) && (result != SSL_ERROR_WANT_WRITE)) { - _handShaking = ERROR; - } - else if (result == SSL_ERROR_WANT_WRITE) { + + if ((result == SSL_ERROR_WANT_READ) || (result == SSL_ERROR_WANT_WRITE)) { + // Non-blocking I/O: select may be used to check if condition has changed + ValidateHandShake(); + _handShaking = CONNECTED; Trigger(); } + else { + _handShaking = ERROR; + } } } else { From 845181550c7133cd7667d276208fef0d96aec28c Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:35:48 +0000 Subject: [PATCH 23/76] [Source/cryptalgo/SecureSocketPort] : remove handshake value from initialize and add ASSERT --- Source/cryptalgo/SecureSocketPort.cpp | 12 +++--------- Source/cryptalgo/SecureSocketPort.h | 3 +-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index c944f0a17..17a809f12 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -129,12 +129,6 @@ SecureSocketPort::Handler::~Handler() { uint32_t SecureSocketPort::Handler::Initialize() { uint32_t success = Core::ERROR_NONE; - if (IsOpen() == true) { - _handShaking = ACCEPTING; - } else { - _handShaking = CONNECTING; - } - // Client and server use _context = SSL_CTX_new(TLS_method()); @@ -206,6 +200,8 @@ uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { } void SecureSocketPort::Handler::ValidateHandShake() { + ASSERT(_ssl != nullptr && _context != nullptr); + // SSL handshake does an implicit verification, its result is: if (SSL_get_verify_result(static_cast(_ssl)) != X509_V_OK) { _handShaking = ERROR; @@ -256,7 +252,6 @@ void SecureSocketPort::Handler::ValidateHandShake() { } void SecureSocketPort::Handler::Update() { - if (IsOpen() == true) { int result; @@ -279,8 +274,8 @@ void SecureSocketPort::Handler::Update() { } // Re-initialie a previous session if (_handShaking == EXCHANGE) { if ((result = SSL_do_handshake(static_cast(_ssl))) == 1) { - ValidateHandShake(); _handShaking = CONNECTED; + ValidateHandShake(); } } @@ -299,7 +294,6 @@ void SecureSocketPort::Handler::Update() { } } else { - /// _handShaking = CLOSED _parent.StateChange(); } } diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 03069e7d9..32fa38e80 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -114,9 +114,8 @@ namespace Crypto { // Signal a state change, Opened, Closed or Accepted void StateChange() override { -// ASSERT(_context != nullptr && _ssl != nullptr); Update(); - }; + } inline uint32_t Callback(IValidator* callback) { uint32_t result = Core::ERROR_ILLEGAL_STATE; From 42a5a0c65829cb772de6cda53f832d268bd07470 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:02:09 +0000 Subject: [PATCH 24/76] [Spurce/cryptalgo/SecuresocketPort] : reverse order handshake and validation --- Source/cryptalgo/SecureSocketPort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 17a809f12..298079014 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -284,8 +284,8 @@ void SecureSocketPort::Handler::Update() { if ((result == SSL_ERROR_WANT_READ) || (result == SSL_ERROR_WANT_WRITE)) { // Non-blocking I/O: select may be used to check if condition has changed - ValidateHandShake(); _handShaking = CONNECTED; + ValidateHandShake(); Trigger(); } else { From f095be44a9df4e20146c9953ced217ae0a7bf673 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:45:19 +0000 Subject: [PATCH 25/76] [Source/cryptalgo/SecureSocketPort] : add 'Handler's' move assignment operator --- Source/cryptalgo/SecureSocketPort.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 32fa38e80..5121aeec2 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -81,6 +81,7 @@ namespace Crypto { Handler(Handler&&) = delete; Handler(const Handler&) = delete; Handler& operator=(const Handler&) = delete; + Handler& operator=(Handler&&) = delete; template Handler(SecureSocketPort& parent, sockettype_t type, Args&&... args) From 821bdfbe52e285e1c25b5140b27a019d809ca3f8 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:23:57 +0000 Subject: [PATCH 26/76] [Source/cryptalgo/SecureSocketPort] : remove enum class 'sockettype_t' --- Source/cryptalgo/SecureSocketPort.h | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 5121aeec2..97d56d150 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -72,26 +72,19 @@ namespace Crypto { }; public: - // Type of underlying socket - enum class sockettype_t : uint8_t { - CLIENT_SOCKET - , SERVER_SOCKET - }; - Handler(Handler&&) = delete; Handler(const Handler&) = delete; Handler& operator=(const Handler&) = delete; Handler& operator=(Handler&&) = delete; template - Handler(SecureSocketPort& parent, sockettype_t type, Args&&... args) + Handler(SecureSocketPort& parent, bool clientSocketType, Args&&... args) : Core::SocketPort(args...) , _parent(parent) , _context(nullptr) , _ssl(nullptr) , _callback(nullptr) - , _handShaking(type == sockettype_t::CLIENT_SOCKET ? CONNECTING : ACCEPTING) - , _type{type} + , _handShaking{clientSocketType ? CONNECTING : ACCEPTING} {} ~Handler(); @@ -143,7 +136,6 @@ namespace Crypto { void* _ssl; IValidator* _callback; mutable state _handShaking; - const sockettype_t _type; }; public: @@ -160,7 +152,7 @@ namespace Crypto { template SecureSocketPort(context_t context, Args&&... args) - : _handler(*this, context == context_t:: SERVER_CONTEXT ? Handler::sockettype_t::SERVER_SOCKET : Handler::sockettype_t::CLIENT_SOCKET, args...) + : _handler(*this, context == context_t::CLIENT_CONTEXT, args...) {} public: From a05570f792c7b6e7f8103822ac230ebaf8870ce7 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:06:32 +0000 Subject: [PATCH 27/76] [Source/cryptalgo/SecureSocketPort] : enable setting a custom (server) certificate 'test_websocket' is updated accordingly --- Source/cryptalgo/SecureSocketPort.cpp | 7 ++++--- Source/cryptalgo/SecureSocketPort.h | 21 +++++++++++++++------ Tests/unit/core/test_websocket.cpp | 12 ++++++------ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 298079014..bf4e705e3 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -142,9 +142,10 @@ uint32_t SecureSocketPort::Handler::Initialize() { if ( ( ( (_handShaking == ACCEPTING) // Load server certifiate and private key - && (SSL_CTX_load_verify_locations(static_cast(_context), "RootCA.pem", nullptr) == 1) - && (SSL_CTX_use_certificate_chain_file(static_cast(_context), "localhost.pem") == 1) - && (SSL_CTX_use_PrivateKey_file(static_cast(_context), "localhost.key", SSL_FILETYPE_PEM) == 1) + && !_certificatePath.empty() + && (SSL_CTX_use_certificate_chain_file(static_cast(_context), _certificatePath.c_str()) == 1) + && !_privateKeyPath.empty() + && (SSL_CTX_use_PrivateKey_file(static_cast(_context), _privateKeyPath.c_str(), SSL_FILETYPE_PEM) == 1) ) || (_handShaking == CONNECTING) ) diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 97d56d150..de3272368 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -78,13 +78,15 @@ namespace Crypto { Handler& operator=(Handler&&) = delete; template - Handler(SecureSocketPort& parent, bool clientSocketType, Args&&... args) - : Core::SocketPort(args...) + Handler(SecureSocketPort& parent, bool isClientSocketType, const std::string& certPath, const std::string& keyPath, Args&&... args) + : Core::SocketPort(std::forward(args)...) , _parent(parent) , _context(nullptr) , _ssl(nullptr) , _callback(nullptr) - , _handShaking{clientSocketType ? CONNECTING : ACCEPTING} + , _handShaking{isClientSocketType ? CONNECTING : ACCEPTING} + , _certificatePath{certPath} + , _privateKeyPath{keyPath} {} ~Handler(); @@ -136,6 +138,8 @@ namespace Crypto { void* _ssl; IValidator* _callback; mutable state _handShaking; + const std::string _certificatePath; // (PEM formatted chain, including root CA) certificate file path + const std::string _privateKeyPath; // (PEM formatted) Private key file path }; public: @@ -151,14 +155,19 @@ namespace Crypto { }; template - SecureSocketPort(context_t context, Args&&... args) - : _handler(*this, context == context_t::CLIENT_CONTEXT, args...) + SecureSocketPort(context_t context, Args&&... args) + : SecureSocketPort(context_t::CLIENT_CONTEXT, static_cast(std::string{""}), static_cast(std::string{""}), std::forward(args)...) + {} + + template + SecureSocketPort(context_t context, const std::string& certPath, const std::string& keyPath, Args&&... args) + : _handler(*this, context == context_t::CLIENT_CONTEXT, certPath, keyPath, std::forward(args)...) {} public: template SecureSocketPort(Args&&... args) - : SecureSocketPort(context_t::CLIENT_CONTEXT, args...) + : SecureSocketPort(context_t::CLIENT_CONTEXT, std::forward(args)...) {} ~SecureSocketPort() override { } diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 771abe55f..27e3f339e 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -424,7 +424,7 @@ namespace Core { , const uint16_t receiveBufferSize , const std::string& prefix ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _prefix{ prefix } , _validator{} { @@ -440,7 +440,7 @@ namespace Core { , const uint16_t receiveBufferSize , const std::string& prefix ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _prefix{ prefix } , _validator{} { @@ -493,7 +493,7 @@ namespace Core { , const uint16_t receiveBufferSize , const std::string& prefix ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _prefix{ prefix } , _validator{} { @@ -509,7 +509,7 @@ namespace Core { , const uint16_t receiveBufferSize , const std::string& prefix ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _prefix{ prefix } , _validator{} { @@ -1141,7 +1141,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, DISABLED_OpeningSecuredServerPort) + TEST(WebSocket, OpeningSecuredServerPort) { const TCHAR localHostName[] {"127.0.0.1"}; @@ -1180,7 +1180,7 @@ namespace Core { EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } - TEST(WebSocket, OpeningSecuredClientPort) + TEST(WebSocket, DISABLED_OpeningSecuredClientPort) { const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol From 3a99e1f19150a60de95b92134b30d196c306a07b Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:01:10 +0000 Subject: [PATCH 28/76] [Source/cryptalgo/SecureSocketPort] : eanble setting a client (custom) certificate --- Source/cryptalgo/SecureSocketPort.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index bf4e705e3..de9e3c340 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -147,9 +147,25 @@ uint32_t SecureSocketPort::Handler::Initialize() { && !_privateKeyPath.empty() && (SSL_CTX_use_PrivateKey_file(static_cast(_context), _privateKeyPath.c_str(), SSL_FILETYPE_PEM) == 1) ) - || (_handShaking == CONNECTING) + || + ( (_handShaking == CONNECTING) + && ( + _certificatePath.empty() + || + ( !_certificatePath.empty() + && (SSL_CTX_use_certificate_file(static_cast(_context), _certificatePath.c_str(), SSL_FILETYPE_PEM) == 1) + ) + ) + && ( + _privateKeyPath.empty() + || + ( !_privateKeyPath.empty() + && (SSL_CTX_use_PrivateKey_file(static_cast(_context), _privateKeyPath.c_str(), SSL_FILETYPE_PEM) == 1) + ) + ) + ) ) - // Default location from which CA certificates are loaded + // Default location from which CA certificates are loaded && (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) && ((_ssl = SSL_new(static_cast(_context))) != nullptr) && (SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()) == 1) From 2449ec364c47c7bda4979ff1fbe5d6b546188f93 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:01:45 +0000 Subject: [PATCH 29/76] [Source/cryptalgo/SecureSocketPort] : prepare for client server certificate request --- Source/cryptalgo/SecureSocketPort.cpp | 42 +++++++++++++++++++++++++++ Source/cryptalgo/SecureSocketPort.h | 18 ++++++++---- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index de9e3c340..1df7d14af 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -146,6 +146,7 @@ uint32_t SecureSocketPort::Handler::Initialize() { && (SSL_CTX_use_certificate_chain_file(static_cast(_context), _certificatePath.c_str()) == 1) && !_privateKeyPath.empty() && (SSL_CTX_use_PrivateKey_file(static_cast(_context), _privateKeyPath.c_str(), SSL_FILETYPE_PEM) == 1) + && (EnableClientCertificateRequest() == Core::ERROR_NONE) ) || ( (_handShaking == CONNECTING) @@ -268,6 +269,47 @@ void SecureSocketPort::Handler::ValidateHandShake() { } } +uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() +{ + uint32_t result{Core::ERROR_NONE}; + + if (_requestCertificate) { + STACK_OF(X509_NAME)* nameList = _certificatePath.empty() ? nullptr : SSL_load_client_CA_file(_certificatePath.c_str()); + const std::string paths = X509_get_default_cert_dir(); + + std::string::size_type head = paths.empty() ? std::string::npos : 0; + + const char* separator = OPENSSL_info(OPENSSL_INFO_LIST_SEPARATOR); + + while (head != std::string::npos && separator != nullptr) { + std::string::size_type tail = paths.find(separator[0], head); + + std::string path = paths.substr(head, tail != std::string::npos ? tail - 1 : tail); + + if ( !( nameList != nullptr + && !path.empty() + && (SSL_add_dir_cert_subjects_to_stack(nameList, path.c_str()) == 1) + ) ) + { + result = Core::ERROR_GENERAL; + break; + } + + head = tail == std::string::npos ? tail : tail + 1; + } + + if (nameList != nullptr && result == Core::ERROR_NONE) { + // Takes ownership of nameList + SSL_CTX_set_client_CA_list(static_cast(_context), nameList); + SSL_CTX_set_verify(static_cast(_context), SSL_VERIFY_PEER, /* cb_verify_callback */ nullptr); + } else if (nameList != nullptr) { + sk_X509_NAME_pop_free(nameList, X509_NAME_free); + } + } + + return result; +} + void SecureSocketPort::Handler::Update() { if (IsOpen() == true) { int result; diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index de3272368..4a5ab3e21 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -78,7 +78,7 @@ namespace Crypto { Handler& operator=(Handler&&) = delete; template - Handler(SecureSocketPort& parent, bool isClientSocketType, const std::string& certPath, const std::string& keyPath, Args&&... args) + Handler(SecureSocketPort& parent, bool isClientSocketType, const std::string& certPath, const std::string& keyPath, bool requestCert, Args&&... args) : Core::SocketPort(std::forward(args)...) , _parent(parent) , _context(nullptr) @@ -87,6 +87,7 @@ namespace Crypto { , _handShaking{isClientSocketType ? CONNECTING : ACCEPTING} , _certificatePath{certPath} , _privateKeyPath{keyPath} + , _requestCertificate{requestCert} {} ~Handler(); @@ -131,6 +132,7 @@ namespace Crypto { private: void Update(); void ValidateHandShake(); + uint32_t EnableClientCertificateRequest(); private: SecureSocketPort& _parent; @@ -140,6 +142,7 @@ namespace Crypto { mutable state _handShaking; const std::string _certificatePath; // (PEM formatted chain, including root CA) certificate file path const std::string _privateKeyPath; // (PEM formatted) Private key file path + const bool _requestCertificate; }; public: @@ -155,13 +158,18 @@ namespace Crypto { }; template - SecureSocketPort(context_t context, Args&&... args) - : SecureSocketPort(context_t::CLIENT_CONTEXT, static_cast(std::string{""}), static_cast(std::string{""}), std::forward(args)...) + SecureSocketPort(context_t, Args&&... args) + : SecureSocketPort(context_t::CLIENT_CONTEXT, static_cast(std::string{""}), static_cast(std::string{""}), false, std::forward(args)...) {} template - SecureSocketPort(context_t context, const std::string& certPath, const std::string& keyPath, Args&&... args) - : _handler(*this, context == context_t::CLIENT_CONTEXT, certPath, keyPath, std::forward(args)...) + SecureSocketPort(context_t context, const std::string& certPath, const std::string& keyPath, Args&&... args) + : SecureSocketPort(context, certPath, keyPath, false, std::forward(args)...) + {} + + template + SecureSocketPort(context_t context, const std::string& certPath, const std::string& keyPath, bool requestPeerCert, Args&&... args) + : _handler(*this, context == context_t::CLIENT_CONTEXT, certPath, keyPath, requestPeerCert && context == context_t::SERVER_CONTEXT, std::forward(args)...) {} public: From 3c80b5b2b72805af4f7c2f7e81bc4faf5a693991 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:20:13 +0000 Subject: [PATCH 30/76] [Source/cryptalgo/SecureSocketPort Tests/unit/core] : Use OpenSSL provided callback mechanism(s) to verify (self-signed) custom (peer) certificates - Adjusted 'test_websocket' to accommodate client (peer) certificate request - Remove the use of '_prefix' in 'test_websocket' - Call the registered user provided callback from the OpenSSL provided callback via application data sharing using the 'ex data' part on OpenSLL structures --- Source/cryptalgo/SecureSocketPort.cpp | 190 ++++++++++++++++++++------ Tests/unit/core/test_websocket.cpp | 145 ++++++++++++++++---- 2 files changed, 264 insertions(+), 71 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 1df7d14af..c85eee379 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -22,6 +22,8 @@ #include #include +#include + #ifndef __WINDOWS__ namespace { @@ -82,6 +84,81 @@ namespace Crypto { return (result); } + +// Placeholder for custom data 'appended' to SSL structures and sharing within the OpenSSL API +class ApplicationData { +public : + + ApplicationData(const ApplicationData&) = delete; + ApplicationData( ApplicationData&&) = delete; + + ApplicationData& operator=(const ApplicationData&) = delete; + ApplicationData& operator=(ApplicationData&&) = delete; + + static ApplicationData& Instance() + { + static ApplicationData data; + return data; + } + + int Index(const SSL* const ssl, int index) + { + ASSERT(index != -1 && ssl != nullptr); + + int result = -1; + + _lock.Lock(); + + result = _map.insert({ssl, index}).second ? index : -1; + + _lock.Unlock(); + + return result; + } + + int Index(const SSL* const ssl) const + { + ASSERT(ssl != nullptr); + + int result = -1; + + _lock.Lock(); + + auto it = _map.find(ssl); + + result = it != _map.end() ? it->second : -1; + + _lock.Unlock(); + + return result; + } + + bool Reset(const SSL* const ssl) + { + ASSERT(ssl != nullptr); + + bool result = false; + + _lock.Lock(); + + result = _map.erase(ssl) == 1; + + _lock.Unlock(); + + return result; + } + +private : + + ApplicationData() + {} + ~ApplicationData() = default; + + std::unordered_map _map; + + mutable Core::CriticalSection _lock; +}; + string SecureSocketPort::Certificate::Issuer() const { char buffer[1024]; buffer[0] = '\0'; @@ -140,6 +217,8 @@ uint32_t SecureSocketPort::Handler::Initialize() { ASSERT((bitmask & options) == options); + int exDataIndex = -1; + if ( ( ( (_handShaking == ACCEPTING) // Load server certifiate and private key && !_certificatePath.empty() @@ -168,10 +247,17 @@ uint32_t SecureSocketPort::Handler::Initialize() { ) // Default location from which CA certificates are loaded && (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) + // Create a new SSL connection structure for storing the custom certification method for use in the available callback mechanism && ((_ssl = SSL_new(static_cast(_context))) != nullptr) && (SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()) == 1) + && ((exDataIndex = SSL_get_ex_new_index(/* number of callback arguments */ 0, /* pointer to callback arguments */ nullptr, /* allocation and initialization of exdata */ nullptr, /* duplication of exdata in copy operations */ nullptr, /* deallocaton of exdata */ nullptr)) != -1) + // The custom method to do validation of certificates with issues + && (SSL_set_ex_data(static_cast(_ssl), exDataIndex, _callback) == 1) ) { - success = Core::SocketPort::Initialize(); + // Placeholder to refer to the validation method within the context of SSL connection + ApplicationData::Instance().Index(static_cast(_ssl), exDataIndex); + + success = Core::SocketPort::Initialize(); } else { TRACE_L1("OpenSSL failed to initialize: ssl structure / certificate store"); success = Core::ERROR_GENERAL; @@ -207,6 +293,9 @@ uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { if (_ssl != nullptr) { SSL_shutdown(static_cast(_ssl)); SSL_free(static_cast(_ssl)); + + /* bool */ ApplicationData::Instance().Reset(static_cast(_ssl)); + _ssl = nullptr; } if (_context != nullptr) { @@ -221,59 +310,71 @@ void SecureSocketPort::Handler::ValidateHandShake() { ASSERT(_ssl != nullptr && _context != nullptr); // SSL handshake does an implicit verification, its result is: - if (SSL_get_verify_result(static_cast(_ssl)) != X509_V_OK) { + if (SSL_get_verify_result(static_cast(_ssl)) != X509_V_OK) { // this is what remains because our callback can also return, actually it is the smae as verify_cb! _handShaking = ERROR; SetError(); - } else if ( (SSL_CTX_get_verify_mode(static_cast(_context)) != SSL_VERIFY_NONE) - && (SSL_CTX_get_verify_callback(static_cast(_context)) != nullptr) - && (SSL_get_verify_mode(static_cast(_ssl)) != SSL_VERIFY_NONE) - && (SSL_get_verify_callback(static_cast(_ssl)) != nullptr) - ) - { - // Only relevant if the server has sent a client certificate request - // AND - // No callback has been set to do this job for us + } +} - // Step 1: verify a certificate was presented during the negotiation - X509* x509cert = SSL_get_peer_certificate(static_cast(_ssl)); - if (x509cert != nullptr) { - Core::SocketPort::Lock(); +extern "C" +{ +int VerifyCallbackWrapper(int checkOK, X509_STORE_CTX* ctx); +int PeerCertificateCallbackWrapper(X509_STORE_CTX* ctx, void* arg); +} - Certificate certificate(x509cert, static_cast(_ssl)); +int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) +{ // This is callled for certificates with issues to allow a custom validation + int result { verifyStatus }; - // Step 2: Validate certificate - use custom IValidator instance if available or if self signed - // certificates are needed :-) - string validationError; - if (_callback && _callback->Validate(certificate) == true) { - _handShaking = CONNECTED; - Core::SocketPort::Unlock(); - _parent.StateChange(); - } else if (certificate.Verify(validationError) && certificate.ValidHostname(RemoteNode().HostName())) { - _handShaking = CONNECTED; - Core::SocketPort::Unlock();\ - _parent.StateChange(); - } else { - if (!validationError.empty()) { - TRACE_L1("OpenSSL certificate validation error for %s: %s", certificate.Subject().c_str(), validationError.c_str()); - } - _handShaking = ERROR; - Core::SocketPort::Unlock(); - SetError(); - } + switch(verifyStatus) { + case 0 : { + X509* x509Cert = nullptr; + int exDataIndex = -1; + SSL* ssl = nullptr; + SecureSocketPort::IValidator* validator = nullptr; - X509_free(x509cert); - } else { - _handShaking = ERROR; - SetError(); - } + // Retireve and call the registered callback + + if ( ctx != nullptr + && (exDataIndex = SSL_get_ex_data_X509_STORE_CTX_idx()) != -1 + && (ssl = static_cast(X509_STORE_CTX_get_ex_data(ctx, exDataIndex))) != nullptr + && (exDataIndex = ApplicationData::Instance().Index(static_cast(ssl))) != -1 + && (validator = static_cast(SSL_get_ex_data(ssl, exDataIndex))) != nullptr + && (x509Cert = X509_STORE_CTX_get_current_cert(ctx)) != nullptr + ) { + X509_up_ref(x509Cert); + + SecureSocketPort::Certificate certificate(x509Cert, static_cast(ssl)); + + result = validator->Validate(certificate); + + X509_free(x509Cert); + } + + break; + } + case 1 : // No error + break; + default : ASSERT(false); // Not within set of defined values } + + return result; // 0 - Failurre, 1 - OK +} + +int PeerCertificateCallbackWrapper(X509_STORE_CTX* ctx, void* arg) +{ // This is called if the complete certificate validation procedure has its custom implementation + // This is typically not what is intended or required, and, a complex to do right + + ASSERT(false); + + return 0; // 0 - Failurre, 1 - OK } uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() { uint32_t result{Core::ERROR_NONE}; - if (_requestCertificate) { + if (_requestCertificate || true) { STACK_OF(X509_NAME)* nameList = _certificatePath.empty() ? nullptr : SSL_load_client_CA_file(_certificatePath.c_str()); const std::string paths = X509_get_default_cert_dir(); @@ -301,7 +402,12 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() if (nameList != nullptr && result == Core::ERROR_NONE) { // Takes ownership of nameList SSL_CTX_set_client_CA_list(static_cast(_context), nameList); - SSL_CTX_set_verify(static_cast(_context), SSL_VERIFY_PEER, /* cb_verify_callback */ nullptr); + + // Callback is triggered if certiifcates have errors + SSL_CTX_set_verify(static_cast(_context), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, VerifyCallbackWrapper); + + // Typically, SSL_CTX_set_cert_verify_callback is the most complex as it should replace the complete verification process procedure +// SSL_CTX_set_cert_verify_callback(static_cast(_context), PeerCertificateCallbackWrapper, static_cast(_ssl)); } else if (nameList != nullptr) { sk_X509_NAME_pop_free(nameList, X509_NAME_free); } diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 27e3f339e..593c4e077 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -40,10 +40,8 @@ namespace Core { , const ::Thunder::Core::NodeId& localNode , const uint16_t sendBufferSize , const uint16_t receiveBufferSize - , const std::string& prefix ) : SocketStream(false, socket, localNode, sendBufferSize, receiveBufferSize) - , _prefix { prefix } { } @@ -53,10 +51,8 @@ namespace Core { , const ::Thunder::Core::NodeId& remoteNode , const uint16_t sendBufferSize , const uint16_t receiveBufferSize - , const std::string& prefix ) : SocketStream(false, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) - , _prefix { prefix } { } @@ -110,10 +106,6 @@ namespace Core { return count; } - - private: - - const std::string _prefix; }; template @@ -252,7 +244,7 @@ namespace Core { // SocketServerType defines SocketHandler of type SocketListener. SocketListener triggers Accept on StateChange, effectively, calling SocketServerType::Accept(SOCKET, NodeId) which creates a WebSocketServer with these parameters WebSocketServer(const SOCKET& socket, const ::Thunder::Core::NodeId remoteNode, ::Thunder::Core::SocketServerType>*) // Initially this should be defined as a regular TCP socket - : ::Thunder::Web::WebSocketServerType(false /* binary*/, false /*masking */, socket, remoteNode, SENDBUFFERSIZE /* send buffer size */, RECEIVEBUFFERSIZE /* receive buffer size */, "WebSocketServerType") + : ::Thunder::Web::WebSocketServerType(false /* binary*/, false /*masking */, socket, remoteNode, SENDBUFFERSIZE /* send buffer size */, RECEIVEBUFFERSIZE /* receive buffer size */) { } @@ -422,10 +414,8 @@ namespace Core { , const ::Thunder::Core::NodeId& localNode , const uint16_t sendBufferSize , const uint16_t receiveBufferSize - , const std::string& prefix ) : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) - , _prefix{ prefix } , _validator{} { // Validate custom (sefl signed) certificates @@ -438,10 +428,8 @@ namespace Core { , const ::Thunder::Core::NodeId& remoteNode , const uint16_t sendBufferSize , const uint16_t receiveBufferSize - , const std::string& prefix ) : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) - , _prefix{ prefix } , _validator{} { // Validate custom (self signed) client certificates @@ -461,6 +449,37 @@ namespace Core { }; class CustomSecureServerSocketStream : public ::Thunder::Crypto::SecureSocketPort { + public : + + // In essence, all parameters to SecureSocket are passed to a base class SocketPort + CustomSecureServerSocketStream( + const SOCKET& socket + , const ::Thunder::Core::NodeId& localNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + ) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + {} + + CustomSecureServerSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + ) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + {} + + ~CustomSecureServerSocketStream() + { +#ifdef _VERBOSE + std::cout.flush(); +#endif + } + }; + + class CustomSecureServerSocketStreamClientValidation : public ::Thunder::Crypto::SecureSocketPort { private : // Validat eclient certificate @@ -486,38 +505,34 @@ namespace Core { public : // In essence, all parameters to SecureSocket are passed to a base class SocketPort - CustomSecureServerSocketStream( + CustomSecureServerSocketStreamClientValidation( const SOCKET& socket , const ::Thunder::Core::NodeId& localNode , const uint16_t sendBufferSize , const uint16_t receiveBufferSize - , const std::string& prefix ) : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) - , _prefix{ prefix } , _validator{} { // Validate custom (sefl signed) certificates uint32_t result = Callback(&_validator); } - CustomSecureServerSocketStream( + CustomSecureServerSocketStreamClientValidation( const bool , const ::Thunder::Core::NodeId& localNode , const ::Thunder::Core::NodeId& remoteNode , const uint16_t sendBufferSize , const uint16_t receiveBufferSize - , const std::string& prefix ) : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) - , _prefix{ prefix } , _validator{} { // Validate custom (self signed) client certificates uint32_t result = Callback(&_validator); } - ~CustomSecureServerSocketStream() + ~CustomSecureServerSocketStreamClientValidation() { #ifdef _VERBOSE std::cout.flush(); @@ -591,7 +606,7 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); SleepMs(maxWaitTimeMs); @@ -632,7 +647,7 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); @@ -706,7 +721,7 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); @@ -789,7 +804,7 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); @@ -872,7 +887,7 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); @@ -949,7 +964,7 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); @@ -1044,7 +1059,7 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); @@ -1141,7 +1156,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, OpeningSecuredServerPort) + TEST(WebSocket, DISABLED_OpeningSecuredServerPort) { const TCHAR localHostName[] {"127.0.0.1"}; @@ -1204,7 +1219,79 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + +// SleepMs(maxWaitTimeMs); + EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, DISABLED_SecuredServerPortCertificateRequest) + { + const TCHAR localHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + +// SleepMs(maxWaitTimeMs); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + if (it.Client()->IsOpen()) { + // No data should be transferred to the remote client + } else { + } + } + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, OpeningSecuredClientPortCertificateRequest) + { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + const TCHAR remoteHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + constexpr bool rawSocket {false}; + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); // SleepMs(maxWaitTimeMs); EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); From 492bb0490ee099a2a8bd448ea06b4aae7b5512f1 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:31:49 +0000 Subject: [PATCH 31/76] [Source/cryptalgo/SecureSocketPort] : remove 'always on' client certificate request --- Source/cryptalgo/SecureSocketPort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index c85eee379..f7a47d74c 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -374,7 +374,7 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() { uint32_t result{Core::ERROR_NONE}; - if (_requestCertificate || true) { + if (_requestCertificate) { STACK_OF(X509_NAME)* nameList = _certificatePath.empty() ? nullptr : SSL_load_client_CA_file(_certificatePath.c_str()); const std::string paths = X509_get_default_cert_dir(); From 3996adade010a9f3c400a4cad406e717936127ce Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:06:24 +0000 Subject: [PATCH 32/76] [Source/cryptalgo/SecureSocketPort] : explicitly include header 'openssl/crypt.h' --- Source/cryptalgo/SecureSocketPort.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index f7a47d74c..1bd926872 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -21,6 +21,7 @@ #include #include +#include #include From 0744a7581381a9cca668a1f5d89a80d07f1089b9 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:14:40 +0000 Subject: [PATCH 33/76] [Source/cryptalgo/SecureSocketPort] : Check for minimum OpenSSL version Uses compile time asserts --- Source/cryptalgo/SecureSocketPort.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 6dc88cb5b..5dd78b9c5 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -381,6 +381,15 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() std::string::size_type head = paths.empty() ? std::string::npos : 0; + // OPENSSL_info requires at least version 3.0 + + static_assert( ((OPENSSL_VERSION_NUMBER >> 28) & 0xF) >= 3 // Major + && ((OPENSSL_VERSION_NUMBER >> 20) & 0xFF) >= 0 // Minor + && ((OPENSSL_VERSION_NUMBER >> 4) & 0xF) >= 0 // Patch + && ((OPENSSL_VERSION_NUMBER) & 0xF) >= 0 // Pre-release + , "OpenSSL version (pre-release) unsupported" + ); + const char* separator = OPENSSL_info(OPENSSL_INFO_LIST_SEPARATOR); while (head != std::string::npos && separator != nullptr) { From be628415ce058883a2e6337d24884cf86f8496c9 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:33:25 +0000 Subject: [PATCH 34/76] [Source/cryptalgo/SecureSocketPort] : Make error message more expresssive --- Source/cryptalgo/SecureSocketPort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 5dd78b9c5..6b1ffd16d 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -387,7 +387,7 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() && ((OPENSSL_VERSION_NUMBER >> 20) & 0xFF) >= 0 // Minor && ((OPENSSL_VERSION_NUMBER >> 4) & 0xF) >= 0 // Patch && ((OPENSSL_VERSION_NUMBER) & 0xF) >= 0 // Pre-release - , "OpenSSL version (pre-release) unsupported" + , "OpenSSL version unsupported. Expected version 3.0.0.0 and higher" ); const char* separator = OPENSSL_info(OPENSSL_INFO_LIST_SEPARATOR); From 63793ac95b4adfafd4789fb4aa3ef85707884053 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:46:42 +0000 Subject: [PATCH 35/76] [Tests/unit/core] : Prefix certificate and private key paths with environment variable 'VOLATILE_PATH' --- Tests/unit/core/CMakeLists.txt | 10 ++++++- Tests/unit/core/test_websocket.cpp | 48 +++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Tests/unit/core/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index eb8aa8826..52e515233 100644 --- a/Tests/unit/core/CMakeLists.txt +++ b/Tests/unit/core/CMakeLists.txt @@ -77,6 +77,7 @@ add_executable(${TEST_RUNNER_NAME} #test_valuerecorder.cpp test_weblinkjson.cpp test_weblinktext.cpp + test_websocket.cpp test_websocketjson.cpp test_websockettext.cpp test_workerpool.cpp @@ -141,7 +142,9 @@ endif() set_source_files_properties(test_systeminfo.cpp PROPERTIES COMPILE_OPTIONS "-fexceptions") target_compile_definitions(${TEST_RUNNER_NAME} - PRIVATE BUILD_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" + PRIVATE + BUILD_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" + VOLATILE_PATH=${CMAKE_INSTALL_PREFIX}/${VOLATILE_PATH} ) target_compile_definitions(${TEST_RUNNER_NAME} @@ -164,6 +167,11 @@ target_link_libraries(${TEST_RUNNER_NAME} ${NAMESPACE}Cryptalgo::${NAMESPACE}Cryptalgo ) +# SSL certifictaes for testing +install(FILES localhostClient.pem localhostClient.key localhostServer.pem localhostServer.key rootCA.pem + DESTINATION ${VOLATILE_PATH} +) + install( TARGETS ${TEST_RUNNER_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Test) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 593c4e077..38ee65f9b 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -29,6 +29,14 @@ #include "../IPTestAdministrator.h" +#ifdef VOLATILE_PATH +#define XSTR(s) STR(s) +#define STR(s) #s "/" +#else +#define XSTR(s) +#define STR(s) +#endif + namespace Thunder { namespace Tests { namespace Core { @@ -386,6 +394,8 @@ namespace Core { class CustomSecureSocketStream : public ::Thunder::Crypto::SecureSocketPort { private : + static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); + // Validat eclient certificate class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidator { public: @@ -415,7 +425,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostClient.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostClient.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _validator{} { // Validate custom (sefl signed) certificates @@ -429,7 +439,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostClient.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostClient.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _validator{} { // Validate custom (self signed) client certificates @@ -448,7 +458,13 @@ namespace Core { Validator _validator; }; + /* static */ constexpr char CustomSecureSocketStream::volatilePath[]; + class CustomSecureServerSocketStream : public ::Thunder::Crypto::SecureSocketPort { + private : + + static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); + public : // In essence, all parameters to SecureSocket are passed to a base class SocketPort @@ -458,7 +474,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostServer.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostServer.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) {} CustomSecureServerSocketStream( @@ -468,7 +484,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostServer.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostServer.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) {} ~CustomSecureServerSocketStream() @@ -479,9 +495,13 @@ namespace Core { } }; + /* static */ constexpr char CustomSecureServerSocketStream::volatilePath[]; + class CustomSecureServerSocketStreamClientValidation : public ::Thunder::Crypto::SecureSocketPort { private : + static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); + // Validat eclient certificate class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidator { public: @@ -511,7 +531,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostServer.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostServer.key"}), true, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _validator{} { // Validate custom (sefl signed) certificates @@ -525,7 +545,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{"localhost.pem"}), static_cast(std::string{"localhost.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostServer.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostServer.key"}), true, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _validator{} { // Validate custom (self signed) client certificates @@ -544,6 +564,8 @@ namespace Core { Validator _validator; }; + /* static */ constexpr char CustomSecureServerSocketStreamClientValidation::volatilePath[]; + TEST(WebSocket, DISABLED_OpeningServerPort) { const TCHAR localHostName[] {"127.0.0.1"}; @@ -1249,7 +1271,7 @@ namespace Core { ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); -// SleepMs(maxWaitTimeMs); + SleepMs(maxWaitTimeMs); // Obtain the endpoint at the server side for each (remotely) connected client auto it = server.Clients(); @@ -1258,8 +1280,7 @@ namespace Core { // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality if (it.Client()->IsOpen()) { // No data should be transferred to the remote client - } else { - } + } } SleepMs(maxWaitTimeMs); @@ -1267,7 +1288,7 @@ namespace Core { EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } - TEST(WebSocket, OpeningSecuredClientPortCertificateRequest) + TEST(WebSocket, DISABLED_OpeningSecuredClientPortCertificateRequest) { const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol @@ -1294,7 +1315,7 @@ namespace Core { WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); // SleepMs(maxWaitTimeMs); - EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); // Fails in non-websocket server context SleepMs(maxWaitTimeMs); @@ -1304,3 +1325,8 @@ namespace Core { } // Core } // Tests } // Thunder + +#ifdef VOLATILE_PATH +#undef STR +#undef XSTR +#endif From 050117b7360c15654c97d5cebb671bd08502c97c Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:07:02 +0000 Subject: [PATCH 36/76] [Tests/unit/core] : add certificates and private keys for testing --- Tests/unit/core/localhostClient.key | 28 ++++++++++++++++++++++++++++ Tests/unit/core/localhostClient.pem | 22 ++++++++++++++++++++++ Tests/unit/core/localhostServer.key | 28 ++++++++++++++++++++++++++++ Tests/unit/core/localhostServer.pem | 22 ++++++++++++++++++++++ Tests/unit/core/rootCA.key | 28 ++++++++++++++++++++++++++++ Tests/unit/core/rootCA.pem | 20 ++++++++++++++++++++ 6 files changed, 148 insertions(+) create mode 100644 Tests/unit/core/localhostClient.key create mode 100644 Tests/unit/core/localhostClient.pem create mode 100644 Tests/unit/core/localhostServer.key create mode 100644 Tests/unit/core/localhostServer.pem create mode 100644 Tests/unit/core/rootCA.key create mode 100644 Tests/unit/core/rootCA.pem diff --git a/Tests/unit/core/localhostClient.key b/Tests/unit/core/localhostClient.key new file mode 100644 index 000000000..8672f5755 --- /dev/null +++ b/Tests/unit/core/localhostClient.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXbsR/G5tQ6+HZ +icVN4b90MZqdIIoUa2PIxWgfjBPnleHGbzyPhmPOLpeQ4bG6FCtncZr0114lXHAX +ViRIiUWWKthU3+eC4Si9tYfBSE/0h2p5hF2AHT1NlG8HUvoQpRxQXmhNghzR1RTc +2fVL4+TD6sBBEskuYDAeEzTAmnhRa3zfX6O1CTs+oxXfp10szyaN4nqfIv1iJktn +me7X5BxwiJMbs0/mmarqa/R0LoXsNFOS38ldoQ0nr2sB32QzRhBHTXyroTrGSctw +3BZug+tJlm0X0R9cqzlNdns9GK10o2+zpF1tvRnqFZs7N0BOjCpoQqC0vMEmtIJO +EPP8LpPfAgMBAAECggEAAm0zId71NgPpn7zuQp66R32J/uLoGNwAV9XDTYocQYrs +eMEMj+I1PuG5P7LrevywFggYgGbjv9EuN07ZOv5UH/a6IQtgytSafLES0VKRfBd1 +adK+ACZidpfz0C1mC2vnOxR1iGnBgM1GIySCTT/Zc8LoZ201IxSbK3LfAV0YTiRW +pogUqn7zBL+tDuq2m9rBchTYVzyPTqIyCk2ifjJXue61NGSG2ea1cw3UtMrhiPsq +jmiMPB96r/5dsogIRhxPI+9LuEbX4EvFS33FhGc5VwW9btht6z6mjHrKdfNElm5i +I0aMoOS5unDRMsqqJBSthq03OO0paayOsrH7sKGAzQKBgQDJzzG/spZOYf+n2KRA +RGjP23eVBAVFbXzpUSbibhEJw5BVOfQEJJ/CcauovVOEylKEZ4cJAZWn/371hPKb +Qhjn+ekK/8gCFmkbN7YVnhib/Okn5mIRzEt4Zdh5FYklhxoB9U6CGTzMGUKP9Zvj +op3SshZcd1YeDVixgiU0Pbv2NQKBgQDAGJLYSjYZ1HccTQeym3EdAOwS4wMYpRxH +Wr5sYNhVMttH5H6D7H/lheMXb5zNuvYyFNqQqSVSWkVq0+UnU+yJoRpCZxb65dZO +U3WiBqPYkE3DfHeu23BCJx40rZ6YSxIWB0yTg0zWsXK4fDRN0rGy6tmH7tAl10Nt +2GPqVd0UQwKBgG7iGhqTHIT60Ya+wRjSvagflSfaaq8IBo8H2M6m3VO5EU/SpOG1 +4dXrsP7o77/Rjt9TJt3q8fi5qF9sagSmn5quNL6nZZTIDX438SMVl731i4Ix0oam +8ny3sOZuz8k/3yleSIGxLjeSVYFV2Q6NJhxDX4f0xeuDN81ojdqTZPhVAoGAA986 +7oMobgLbV8WxtwbtE8GWAJd004VYeZO5rOOS2LzKsLtJVY1p0o2NU0abqYXwOngz +I6FVMEDDj3Cv+Mf3R5rotZfwXaROWovSHi72FIJsHtmea/beX2b8c+FgBf/VYH5L +K9oErXssLc3LHBp4HHwhYF0O8wRQxqEK+ok6iJMCgYBdKf8igm6wiMzqpqQd7xGd +AEOJ6pCmtCqnSPAX2ShMSn5XLuz+1OUet+U0XgzvRXjcN2inBIpQGrcV+WWrASY9 +uJ6WIAOSvs4DAdaFKdd9brVklGvI2KesJFEpTPPXpKMU7H2s/mX9Vv7KcF6tG36y +2+8mI+ST6K6pMuEYuzoN9Q== +-----END PRIVATE KEY----- diff --git a/Tests/unit/core/localhostClient.pem b/Tests/unit/core/localhostClient.pem new file mode 100644 index 000000000..6c8d2f104 --- /dev/null +++ b/Tests/unit/core/localhostClient.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDmjCCAoKgAwIBAgIUfHsbxXz/YcKx+5V3TZT4PujHpOMwDQYJKoZIhvcNAQEL +BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD1RodW5kZXItUm9vdC1DQTAeFw0y +NDEyMDQwOTUzNDFaFw0yNzA5MjQwOTUzNDFaMHUxCzAJBgNVBAYTAlVTMRUwEwYD +VQQIDAxUaHVuZGVyUmVhbG0xFjAUBgNVBAcMDVRodW5kZXJDbGllbnQxHDAaBgNV +BAoME1RodW5kZXJDZXJ0aWZpY2F0ZXMxGTAXBgNVBAMMEGxvY2FsaG9zdC5jbGll +bnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXbsR/G5tQ6+HZicVN +4b90MZqdIIoUa2PIxWgfjBPnleHGbzyPhmPOLpeQ4bG6FCtncZr0114lXHAXViRI +iUWWKthU3+eC4Si9tYfBSE/0h2p5hF2AHT1NlG8HUvoQpRxQXmhNghzR1RTc2fVL +4+TD6sBBEskuYDAeEzTAmnhRa3zfX6O1CTs+oxXfp10szyaN4nqfIv1iJktnme7X +5BxwiJMbs0/mmarqa/R0LoXsNFOS38ldoQ0nr2sB32QzRhBHTXyroTrGSctw3BZu +g+tJlm0X0R9cqzlNdns9GK10o2+zpF1tvRnqFZs7N0BOjCpoQqC0vMEmtIJOEPP8 +LpPfAgMBAAGjcDBuMB8GA1UdIwQYMBaAFNrzIVAm/Lbw7FvB5lZuLJkD4ymvMAkG +A1UdEwQCMAAwCwYDVR0PBAQDAgTwMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAdBgNV +HQ4EFgQU/tr8ZVIg6b8JLXwMDhjqM4gEcJEwDQYJKoZIhvcNAQELBQADggEBAJzk +8hwm1yHSH6BmLZ4MKfhOoQ03UI/ihbJMGT4tgKQMsEP5pSMjQcsH0yTGDEjxRSz+ +YfPQHV8nRUNMS5Gs+flhQ3FGUgxtSzcq/NKIB/x5B8IzPYAPAxU42b33gfNCjVSV ++7aw0brxmI8eha+K0+r9mHF2PkVGXf2IvbQb3aNeHXksWnqDJCeoP6nf6kr9bZAD +j/UjZobaK1m5BhlvK4aFIKALCLUjh9g2dM8IFhZLNBbYzJWU42WUIYpto9zNlv1a +OwRW6b/tZM14j6b51bjlatlYUN6xev96DLn6mAk41YAGagSPSuGK1VceKoq3Ovvj +gQnJdXtOVXBYx/jzJgw= +-----END CERTIFICATE----- diff --git a/Tests/unit/core/localhostServer.key b/Tests/unit/core/localhostServer.key new file mode 100644 index 000000000..5ada930c5 --- /dev/null +++ b/Tests/unit/core/localhostServer.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVmZirJHA3tccr +j1yjibqKDarIkChDuaC+JvJBpNEFKLJ1z/1yEpOcST06nhnjsOf84ESr2MGENCl+ +gB49jPSTHQ2IUC3YKb1sEhaC0Xo/WInbQP+XEYjSshkCHvmyMsKhj/exWRfOwrvm +voQbt9tDnI1mu+FRbTH8r8b2A2Dxqfuaur0x+obRz0EbqWBmsKddIlPydjXnyqAe +vLABhear2Il+vRpjJVhCelGcLsEDn+E5kGuEMT3OfgLn+zX5kJ8plKD89n0UGY2T +edRYSIbxVu9WUnHWwMlyR/V+RE62OQ/lJdP7DgY+4nBuurdNuJ4sHPQFrXL6j/jQ +2rp6a4GvAgMBAAECggEACDXCJ3ncElgbUrJM9kA5+kpyMQlRcuD0q7plf15tZkxd +J6gZ7sOGBvDNuK4RAq5vDEo2eYB4V7OFkMYt42HCmND6Dy49xNw92qSLVvXkRAEM +M3A5Ir5ayvZrfahhIpdO4dTdyWRKRPk5LbJO853KgLvIt5UrIO0YicTb2eLhXAiB +i6KubpJTUEIncfczB+Qee/VTP4NcoMeK5r34NxFN44W1jgq+W7Cfep28y+GxcWuy +yPS8mb+UtJLrGDnndoTM+M8sc6ED9HH0wdaun0ZitDO1RTTEkOnKUUBnkZwhFpGZ +DeH20KRosLuBF1FH7Rfp62lKnjOAJ7hYfECjBkc9EQKBgQD73P+sETaIjO+gN6hz +6yuEBIcZvo5DPge7imwr1SlLqZZRMwpbi4XPg5nFaTm2c4OZQ/4iXf+Sv4kQILg6 +Q5SOwzPLU56D4CCRyZJyic37BcO1mFYSzhL3nXwF9heo2v6y69AD4s7i6cKmz52L +ASNprf/L2oi0qCEI0wkaAgBzFwKBgQDZG7aWpBfZA5TCUF/yKhnh/d9MAQe+NXth +JVmqybrrnf45Ik4x8Tw6D8AwKcLN4THoKNch+SePmm2dMXZwPBs2wM/jGK4YJxMu +052Ntsx16LAKMGom98XHxA5rHBLDde/oj74DDNdxrE0Hoev14+wybVLqOiwgRMOs +jTP5ZzdlKQKBgQDPuMY5AH1Mg4hCSIUrDYL3P9C439tvA+LWvuRWBlknqPdrgsAB +HoI+0pfpI87QdlbL+jLH32SggE2nuoSWsRP95mp6QD3VH+1cr7WTt6nlZSyzQa+D +lOg5xm36cKu0vOEhabFG8zGUHh1G/KY/dbHiP/pfA56J+Lw+DedMxufeAwKBgBzd +M1w5urXuZPOkjez7Le++udY3+NiP8bRLq+0p3sD+g1MDPZQkN1acy3dbxftrKiBs +dZWds2XDKTmR3uYzB4czATB3EoZBg6phFfxGRk6SvfzMzQAbRt81MJmK5O+5mUi4 ++5EaPvZs6tzN6ToKsFdP84sSatVrbvxc1YEd+N5pAoGBALm06/rqyQU8+LEIRufA +9+zvk8kUm3kWiFLrw1er0e9ueAGVnshw9LumgKu8VUiI2lGN5f+iur4GeGzKi0u8 +9cMLQEtigs0INMuRXysERZSPBryg4AZyZf0/IF+WHwf5Ayw4qGtD/b3cgqGl2A8h +xxbqUmN3iHf1V8stXVKu46Cn +-----END PRIVATE KEY----- diff --git a/Tests/unit/core/localhostServer.pem b/Tests/unit/core/localhostServer.pem new file mode 100644 index 000000000..b89c656ef --- /dev/null +++ b/Tests/unit/core/localhostServer.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDmjCCAoKgAwIBAgIUfHsbxXz/YcKx+5V3TZT4PujHpOIwDQYJKoZIhvcNAQEL +BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD1RodW5kZXItUm9vdC1DQTAeFw0y +NDEyMDQwOTUzNDFaFw0yNzA5MjQwOTUzNDFaMHUxCzAJBgNVBAYTAlVTMRUwEwYD +VQQIDAxUaHVuZGVyUmVhbG0xFjAUBgNVBAcMDVRodW5kZXJTZXJ2ZXIxHDAaBgNV +BAoME1RodW5kZXJDZXJ0aWZpY2F0ZXMxGTAXBgNVBAMMEGxvY2FsaG9zdC5zZXJ2 +ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVmZirJHA3tccrj1yj +ibqKDarIkChDuaC+JvJBpNEFKLJ1z/1yEpOcST06nhnjsOf84ESr2MGENCl+gB49 +jPSTHQ2IUC3YKb1sEhaC0Xo/WInbQP+XEYjSshkCHvmyMsKhj/exWRfOwrvmvoQb +t9tDnI1mu+FRbTH8r8b2A2Dxqfuaur0x+obRz0EbqWBmsKddIlPydjXnyqAevLAB +hear2Il+vRpjJVhCelGcLsEDn+E5kGuEMT3OfgLn+zX5kJ8plKD89n0UGY2TedRY +SIbxVu9WUnHWwMlyR/V+RE62OQ/lJdP7DgY+4nBuurdNuJ4sHPQFrXL6j/jQ2rp6 +a4GvAgMBAAGjcDBuMB8GA1UdIwQYMBaAFNrzIVAm/Lbw7FvB5lZuLJkD4ymvMAkG +A1UdEwQCMAAwCwYDVR0PBAQDAgTwMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAdBgNV +HQ4EFgQUpt5n4pietwywkdftPnUbnMHGfo0wDQYJKoZIhvcNAQELBQADggEBAF8K +adyN/bfqjDqxjUCDixX4tlMBHY0qiPQLIL/ExbswuyAlU2QlgphbloSfJcFEVqSC +ezBwbwUTjATWHt2j7l3JF+kEl/mbf8lNbEW+2jsZMGT0mDGFTjr8oi1zXkMVU2Cs +jNfLSjhZmWf3CIDfDPNON2pAndtGtt5lDhkVbuJiqjPx0sX3rRX4NHpTxwVMJFE0 +ymfjBG7PA1hPCeJ4TQ8QD+5dkwtuzmXLAAeQuEiLOtg7sxWYsKLS5Vtv5/gk6+Rg +yZWYJ8PcivgKm7fYsBaFz3UBMdLrMLTcnZkxsisTkdksJY26fcNsH1vFgJRgvX7l +Nek7c2vrDgdeZu0X/Hg= +-----END CERTIFICATE----- diff --git a/Tests/unit/core/rootCA.key b/Tests/unit/core/rootCA.key new file mode 100644 index 000000000..7e915e095 --- /dev/null +++ b/Tests/unit/core/rootCA.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCkSz+BmvG9I/u/ +kr2SwC5VRR4S4cEm1jTIG8IFQiyBkOaNwfF90taEKl5Wh7EeMaINLlUBhkjv8ECW +ITJj2pgSTr4Qk8GIhvzBLQFi49kMA7XBeRA8+qYMe/vV2+T990FBKG3VkZsv2sWe +V5OC/0woQS9quXM41Fo91MIPJe/xJSFX88JJIa1bcjsJS7f8kGXPodS8G5S29NN4 +f2G6dJiT0ZHgbJCWbJI/4cHhQMsWbzQEX9ARkU+BO16e+QeYVKQVUvXNp2kpuggd +cDoT5ed59rhNcWR5GIh0w8yADrRvS55Sy8UjUDIAReiDX0MHgHT9UMRLdV75iJ9r +cooezlq7AgMBAAECggEAAU6t+zCJ8ujZ5Hzc8FGCLOCHbDwKpu32bo81/Ie5xfAq +vX8JFmi6l0OlC4g54xUfBn7TY1c3tl0RjoBGH+BJ7H9e6M/cLuNwGHNsuuY4QG25 +IQh/FhUihB/qcZm12UWB6exR72ygsQFLKEbnIBg6+WkHYRCHt7CM5UgPoUI6hg8z +z3wXDlEBhYVd6YeKcllmtTMI0F7brtjpFzfEaUEtv2Juf/bI/boMJFAZG22I5ow3 +zcDCfqZ5T2QySbT8x9+WNdIgc+J/Zw6RqlSVzyF7DI36n9LlwaNNIZr20JrD0538 +i2Zn/vME5DK9xK9orjQMoA9mA8T42xC/PK5gbXUslQKBgQDYSZB1zB9L33jyknBR +FeMcQlwEZdYdVYbTb98tM5KNBnB3IDinzCLtSrxl9oP/b9+kjCn9tKo4HfzrOTNy +lbOtBT26lUZ6VGMHSIcpZgPReGlSIyQ6fhKoEbA5eVFnAB4BGv5pA3qegoJ9Hplm +jzYMQgcpKllW7tePT+q5jh8IXwKBgQDCdcT3XVEAw/G1AJNAql81IWcwG4Z7sqBY +0nO3VrtDe56F0P+mwmobk5YO1Y86D42rdvOs2pYdGVyNdlOzg42IqFbgZk0n/tWL +UABQY53DhNoiw7HB5OeN9ormcbb0ZTTl7PcjAaMXrg0R1433Kxuc9TN6peb0JGm8 +M0hvsgP7JQKBgQDJ+kDGUJ11TDZ1SF1My4Sv8iReEv+VmzXyI4mle4DC452JEXT2 +9cI0GFPBYCk6FC4kSqQ4AUvoZdC3lU4/Fh+ZVsijgh0zxbRIq+lUtqigJ7Mq+hgt +62fevc30jh9/cXOTkrK8PHx4o+XZlAaq6NgPMGXhgmO1tAtnELlhGKBQPwKBgQCd +cEQcEg03FW1oIiMWQ9n5ZiXpKR/knmZ8A0d0tF/A7yEVLnUNSnImCYOAVx8y3szQ +eeonlIHc5V+tmJODz4qTjddorurg6s1xkT/v1fcxCSqi4tXUKcPfiDBFCuQZUqdV +UFl2mii1T1F9lIt4BgrBNTSMpC0slR6WJN8Mr4/RkQKBgQCUdjBZ2o6O6J9mDjWb +bcpWONgLoOX7POTs6P0X3JTLIRcTF/U1i0318IYxQZOsB9QgNABt+RRMCgMek/wo +MHmJNokFiajynq++RcvdUd8W9VSQ5GGiMiyyCtduL+sEhQ+3Mu3wywleYTTh5Pbi +DoqGiujbBOvJG/BjZe08o0CPiA== +-----END PRIVATE KEY----- diff --git a/Tests/unit/core/rootCA.pem b/Tests/unit/core/rootCA.pem new file mode 100644 index 000000000..ad357f8fe --- /dev/null +++ b/Tests/unit/core/rootCA.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLzCCAhegAwIBAgIUBaJfWYD/oiuTinQRWOE5iCB1YdcwDQYJKoZIhvcNAQEL +BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD1RodW5kZXItUm9vdC1DQTAeFw0y +NDEyMDQwOTUzNDBaFw0yNzA5MjQwOTUzNDBaMCcxCzAJBgNVBAYTAlVTMRgwFgYD +VQQDDA9UaHVuZGVyLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCkSz+BmvG9I/u/kr2SwC5VRR4S4cEm1jTIG8IFQiyBkOaNwfF90taEKl5W +h7EeMaINLlUBhkjv8ECWITJj2pgSTr4Qk8GIhvzBLQFi49kMA7XBeRA8+qYMe/vV +2+T990FBKG3VkZsv2sWeV5OC/0woQS9quXM41Fo91MIPJe/xJSFX88JJIa1bcjsJ +S7f8kGXPodS8G5S29NN4f2G6dJiT0ZHgbJCWbJI/4cHhQMsWbzQEX9ARkU+BO16e ++QeYVKQVUvXNp2kpuggdcDoT5ed59rhNcWR5GIh0w8yADrRvS55Sy8UjUDIAReiD +X0MHgHT9UMRLdV75iJ9rcooezlq7AgMBAAGjUzBRMB0GA1UdDgQWBBTa8yFQJvy2 +8OxbweZWbiyZA+MprzAfBgNVHSMEGDAWgBTa8yFQJvy28OxbweZWbiyZA+MprzAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB5BP7tp9x3JqIShr8A +ZrxRx5jTTZ64IYF34PYhyArEZowk++mmJnLTQ+vp9nRjLv28brmlJtU7v6ytYxwZ +gIg5gU9d/Qbyv9pgwfOpkOL9LOy4gxy85ZK3bMkS8wjH5FPdbL5vZovCTjzQ3LW3 +vn6t1uLBz7YitIauSTvsET5shLhRL/5mu7z0ZcixO0Lbj8y94er0yl9w6HvP93i/ +Tld+g/FqgQndlnsl/ci9KWerFCPaKM5GgPD2oi8YdA3/rcE/X0/ClWqEJbtOuGLo +yBTh2+152VBl3pqUR2RU+OWue9yaK4euQqOezqNWNpSl0geQa/4oeOQgugb2XsyK +l9f6 +-----END CERTIFICATE----- From c8af2f06ff092c06575cd371caca408fa1e6f038 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:16:45 +0000 Subject: [PATCH 37/76] [Tests/unit/core] : check 'SecureSocketPort' is enabled. --- Tests/unit/core/test_websocket.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 38ee65f9b..c9aa843c1 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -27,6 +27,10 @@ #include #include +#ifndef SECURESOCKETS_ENABLED +#error "This unit test requires SecureSocketPort" +#endif + #include "../IPTestAdministrator.h" #ifdef VOLATILE_PATH From 64413b1c913b75ef0f9b958d46c9d6a01f91b17c Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:12:02 +0000 Subject: [PATCH 38/76] [Tests/unit/core] : Try to 'force' 'SECURESOCKETS_ENABLED' --- Tests/unit/core/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/unit/core/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index 52e515233..b63807b14 100644 --- a/Tests/unit/core/CMakeLists.txt +++ b/Tests/unit/core/CMakeLists.txt @@ -144,6 +144,7 @@ set_source_files_properties(test_systeminfo.cpp PROPERTIES COMPILE_OPTIONS "-fex target_compile_definitions(${TEST_RUNNER_NAME} PRIVATE BUILD_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" + SECURESOCKETS_ENABLED=1 VOLATILE_PATH=${CMAKE_INSTALL_PREFIX}/${VOLATILE_PATH} ) From 92baf2000eec91bc28f7275d17c67de22990cebc Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:39:29 +0000 Subject: [PATCH 39/76] [Tests/unit/core] : adjust timings --- Tests/unit/core/test_websocket.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index c9aa843c1..1ffafe804 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -41,6 +41,8 @@ #define STR(s) #endif +//#define _VERBOSE + namespace Thunder { namespace Tests { namespace Core { @@ -990,10 +992,10 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); - EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); @@ -1013,7 +1015,7 @@ namespace Core { ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); // A small delay so the child can be set up -// SleepMs(maxInitTime); + SleepMs(maxInitTime); EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); @@ -1085,10 +1087,10 @@ namespace Core { const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; - WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); - EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); @@ -1116,7 +1118,7 @@ namespace Core { ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); // A small delay so the child can be set up -// SleepMs(maxInitTime); + SleepMs(maxInitTime); EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); @@ -1248,6 +1250,7 @@ namespace Core { WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); // SleepMs(maxWaitTimeMs); + EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); SleepMs(maxWaitTimeMs); From e5d430463fa94e3ccd78589d8d678b5eb88278fb Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:49:30 +0000 Subject: [PATCH 40/76] [Tests] : add 'README' --- Tests/README | 1 + Tests/unit/core/test_websocket.cpp | 95 ++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 Tests/README diff --git a/Tests/README b/Tests/README new file mode 100644 index 000000000..e726e8ea7 --- /dev/null +++ b/Tests/README @@ -0,0 +1 @@ +All files in this directory and its subdirectories are for test purpose only diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 1ffafe804..92e96414a 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -1258,6 +1258,101 @@ namespace Core { EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } + TEST(WebSocket, SecuredSocketDataExchange) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + + EXPECT_EQ(client.Open(4*maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + // Avoid premature shutdown() at the other side + SleepMs(2*maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + EXPECT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up + SleepMs(2*maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + constexpr uint8_t data[] = { 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x3, 0x2, 0x1, 0x0 }; + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + /* bool */ it.Client()->Submit(std::basic_string{ data, sizeof(data) }); + } + } + + // Allow some time to receive the response + SleepMs(maxWaitTimeMs); + + std::basic_string response{ data, sizeof(data) }; + std::reverse(response.begin(), response.end()); + + // A simple poll to keep it simple + EXPECT_TRUE( it.IsValid() + && (it.Client()->Response() == response) + ); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + + TEST(WebSocket, DISABLED_SecuredServerPortCertificateRequest) { const TCHAR localHostName[] {"127.0.0.1"}; From f47bf18075e89f537da5c954817eff6a0340c25d Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:43:51 +0000 Subject: [PATCH 41/76] [Source/cryptalgo/SecureSocketPort] : 'Read()' and 'Write()' map to a different error value Minor change in 'Update'. 'Trigger' is not needed. --- Source/cryptalgo/SecureSocketPort.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 6b1ffd16d..776cf6dad 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -268,13 +268,11 @@ uint32_t SecureSocketPort::Handler::Initialize() { } int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { - int result = 0; + ASSERT(_handShaking == CONNECTED); - if (_handShaking == CONNECTED) { // Avoid reading while still in CONNECTING or ACCEPTING - result = SSL_read(static_cast(_ssl), buffer, length); - } + int result = SSL_read(static_cast(_ssl), buffer, length); - return (result > 0 ? result : 0); + return (result > 0 ? result : /* error */ -1); } int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { @@ -282,7 +280,7 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t int result = SSL_write(static_cast(_ssl), buffer, length); - return (result > 0 ? result : 0); + return (result > 0 ? result : /* error */ -1); } @@ -447,7 +445,7 @@ void SecureSocketPort::Handler::Update() { ValidateHandShake(); } } // Re-initialie a previous session - if (_handShaking == EXCHANGE) { + else if (_handShaking == EXCHANGE) { if ((result = SSL_do_handshake(static_cast(_ssl))) == 1) { _handShaking = CONNECTED; ValidateHandShake(); @@ -461,16 +459,14 @@ void SecureSocketPort::Handler::Update() { // Non-blocking I/O: select may be used to check if condition has changed _handShaking = CONNECTED; ValidateHandShake(); - Trigger(); } else { _handShaking = ERROR; } } } - else { - _parent.StateChange(); - } + + _parent.StateChange(); } } } // namespace Thunder::Crypto From 9e8d462e1972c1c9f1908f4aa3cc8c2ce248ed41 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:20:36 +0000 Subject: [PATCH 42/76] [Source/cryptalgo/SecureSocketPort] : add additional checks --- Source/cryptalgo/SecureSocketPort.cpp | 2 ++ Tests/unit/core/test_websocket.cpp | 14 ++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 776cf6dad..fb96852c8 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -270,6 +270,7 @@ uint32_t SecureSocketPort::Handler::Initialize() { int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { ASSERT(_handShaking == CONNECTED); + ASSERT(_ssl != nullptr); int result = SSL_read(static_cast(_ssl), buffer, length); return (result > 0 ? result : /* error */ -1); @@ -278,6 +279,7 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { ASSERT(_handShaking == CONNECTED); + ASSERT(_ssl != nullptr); int result = SSL_write(static_cast(_ssl), buffer, length); return (result > 0 ? result : /* error */ -1); diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 92e96414a..6d08931e4 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -41,7 +41,7 @@ #define STR(s) #endif -//#define _VERBOSE +#define _VERBOSE namespace Thunder { namespace Tests { @@ -1258,7 +1258,7 @@ namespace Core { EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } - TEST(WebSocket, SecuredSocketDataExchange) + TEST(WebSocket, DISABLED_SecuredSocketDataExchange) { const TCHAR hostName[] {"127.0.0.1"}; @@ -1292,12 +1292,12 @@ namespace Core { WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); - EXPECT_EQ(client.Open(4*maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); // Avoid premature shutdown() at the other side - SleepMs(2*maxWaitTimeMs); + SleepMs(maxWaitTimeMs); EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); }; @@ -1308,10 +1308,10 @@ namespace Core { // This is a listening socket as result of using SocketServerType which enables listening ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); - EXPECT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); // A small delay so the child can be set up - SleepMs(2*maxInitTime); + SleepMs(maxInitTime); EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); @@ -1351,8 +1351,6 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - - TEST(WebSocket, DISABLED_SecuredServerPortCertificateRequest) { const TCHAR localHostName[] {"127.0.0.1"}; From 85eea3522175f572a9abd1ae1332d3a16e0c8990 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 17 Dec 2024 08:56:46 +0000 Subject: [PATCH 43/76] [Source/cryptalgo/SecureSocketPort] : wait for the socket to become ready --- Source/cryptalgo/SecureSocketPort.cpp | 106 ++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index fb96852c8..6fb2893ef 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -264,23 +264,86 @@ uint32_t SecureSocketPort::Handler::Initialize() { success = Core::ERROR_GENERAL; } + ASSERT(success == 0); + return success; } int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { ASSERT(_handShaking == CONNECTED); - ASSERT(_ssl != nullptr); - int result = SSL_read(static_cast(_ssl), buffer, length); + + int fd = SSL_get_fd(static_cast(_ssl)); + + ASSERT(fd >= 0); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); + +// TODO: extract from Open() + struct timeval tv {5, 0}; + + int result = -1; + + switch (SSL_want(static_cast(_ssl))) { + case SSL_NOTHING : // No data to be written or read + result = SSL_read(static_cast(_ssl), buffer, length); + break; + case SSL_WRITING : // More data to be written to complete + result = (select(fd + 1, &fds, nullptr, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? SSL_read(static_cast(_ssl), buffer, length) : -1; + break; + case SSL_READING : // More data to be read to complete + result = (select(fd + 1, nullptr, &fds, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? SSL_read(static_cast(_ssl), buffer, length) : -1; + break; + case SSL_X509_LOOKUP : // Callback should be called again, see SSL_CTX_set_client_cert_cb() + case SSL_RETRY_VERIFY : // Callback should be called again, see SSL_set_retry_verify() + case SSL_ASYNC_PAUSED : // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds + case SSL_ASYNC_NO_JOBS : // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() + case SSL_CLIENT_HELLO_CB : // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() + default : // Error not processed + result = -1; + } return (result > 0 ? result : /* error */ -1); } int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { ASSERT(_handShaking == CONNECTED); - ASSERT(_ssl != nullptr); - int result = SSL_write(static_cast(_ssl), buffer, length); + ASSERT(length > 0); + + int fd = SSL_get_fd(static_cast(_ssl)); + + ASSERT(fd >= 0); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); + +// TODO: extract from Open() + struct timeval tv {5, 0}; + + int result = -1; + + switch (SSL_want(static_cast(_ssl))) { + case SSL_NOTHING : // No data to be written or read + result = SSL_write(static_cast(_ssl), buffer, length); + break; + case SSL_WRITING : // More data to be written to complete + result = (select(fd + 1, &fds, nullptr, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? SSL_write(static_cast(_ssl), buffer, length) : -1; + break; + case SSL_READING : // More data to be read to complete + result = (select(fd + 1, nullptr, &fds, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? SSL_write(static_cast(_ssl), buffer, length) : -1; + break; + case SSL_X509_LOOKUP : // Callback should be called again, see SSL_CTX_set_client_cert_cb() + case SSL_RETRY_VERIFY : // Callback should be called again, see SSL_set_retry_verify() + case SSL_ASYNC_PAUSED : // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds + case SSL_ASYNC_NO_JOBS : // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() + case SSL_CLIENT_HELLO_CB : // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() + default : // Error not processed + result = -1; + } return (result > 0 ? result : /* error */ -1); } @@ -455,20 +518,33 @@ void SecureSocketPort::Handler::Update() { } if (result != 1) { - result = SSL_get_error(static_cast(_ssl), result); - - if ((result == SSL_ERROR_WANT_READ) || (result == SSL_ERROR_WANT_WRITE)) { - // Non-blocking I/O: select may be used to check if condition has changed - _handShaking = CONNECTED; - ValidateHandShake(); - } - else { - _handShaking = ERROR; + int fd = SSL_get_fd(static_cast(_ssl)); + + ASSERT(fd >= 0); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); +// TODO: extract from Open() + struct timeval tv {5, 0}; + + switch (SSL_get_error(static_cast(_ssl), result)) { + case SSL_ERROR_WANT_READ : // Wait until ready to read + _handShaking = (select(fd + 1, &fds, nullptr, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? CONNECTED : ERROR; + ValidateHandShake(); + break; + case SSL_ERROR_WANT_WRITE : // Wait until ready to write + _handShaking = (select(fd + 1, nullptr, &fds, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? CONNECTED : ERROR; + ValidateHandShake(); + break; + default : // Error not processed + _handShaking = ERROR; + ASSERT(false); } } - } - _parent.StateChange(); + _parent.StateChange(); + } } } } // namespace Thunder::Crypto From d2c4a7640729845ddb06ba066e654438f51b849d Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:57:17 +0000 Subject: [PATCH 44/76] [Source/cryptalgo/SecureSocketPort] : refactor 'Update' --- Source/cryptalgo/SecureSocketPort.cpp | 77 +++++++++++++++------------ 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 6fb2893ef..753ef8d1a 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -491,30 +491,28 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() void SecureSocketPort::Handler::Update() { if (IsOpen() == true) { - int result = 1; - ASSERT(_ssl != nullptr); - // Client - if (_handShaking == CONNECTING) { - if ((result = SSL_connect(static_cast(_ssl))) == 1) { - _handShaking = CONNECTED; - // If server has sent a certificate, and, we want to do 'our' own check - ValidateHandShake(); - } - } // Server - else if (_handShaking == ACCEPTING) { - if ((result = SSL_accept(static_cast(_ssl))) == 1) { - _handShaking = CONNECTED; - // Client has sent a certificate (optional, on request only) - ValidateHandShake(); - } - } // Re-initialie a previous session - else if (_handShaking == EXCHANGE) { - if ((result = SSL_do_handshake(static_cast(_ssl))) == 1) { - _handShaking = CONNECTED; - ValidateHandShake(); - } + switch (_handShaking) { + case CONNECTING : // Client + SSL_set_connect_state(static_cast(_ssl)); + break; + case ACCEPTING : // Server + SSL_set_accept_state(static_cast(_ssl)); + break; + case EXCHANGE : // Re-initialie a previous session + break; + default : ASSERT(false); + } + + int result = 1; + + if ((result = SSL_do_handshake(static_cast(_ssl))) == 1) { + _handShaking = CONNECTED; + // If server has sent a certificate, and, we want to do 'our' own check + // or + // Client has sent a certificate (optional, on request only) + ValidateHandShake(); } if (result != 1) { @@ -528,18 +526,29 @@ void SecureSocketPort::Handler::Update() { // TODO: extract from Open() struct timeval tv {5, 0}; - switch (SSL_get_error(static_cast(_ssl), result)) { - case SSL_ERROR_WANT_READ : // Wait until ready to read - _handShaking = (select(fd + 1, &fds, nullptr, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? CONNECTED : ERROR; - ValidateHandShake(); - break; - case SSL_ERROR_WANT_WRITE : // Wait until ready to write - _handShaking = (select(fd + 1, nullptr, &fds, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? CONNECTED : ERROR; - ValidateHandShake(); - break; - default : // Error not processed - _handShaking = ERROR; - ASSERT(false); + switch (result = SSL_get_error(static_cast(_ssl), result)) { + case SSL_ERROR_WANT_READ : // Wait until ready to read + case SSL_ERROR_WANT_WRITE : // Wait until ready to write + if ( (select(fd + 1, &fds, &fds, nullptr, &tv) > 0) + && FD_ISSET(fd, &fds) + && (SSL_do_handshake(static_cast(_ssl)) == 1) + ) { + _handShaking = CONNECTED; + ValidateHandShake(); + break; + } + case SSL_ERROR_SYSCALL : result = errno; + case SSL_ERROR_NONE : + case SSL_ERROR_ZERO_RETURN : + case SSL_ERROR_WANT_CONNECT : + case SSL_ERROR_WANT_ACCEPT : + case SSL_ERROR_WANT_ASYNC : + case SSL_ERROR_WANT_ASYNC_JOB : + case SSL_ERROR_WANT_CLIENT_HELLO_CB : + case SSL_ERROR_SSL : + default : // Error + _handShaking = ERROR; + ASSERT(false); } } From dcd450c459ead4a78d87f866d5cb1f426e2ef7a0 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:45:29 +0000 Subject: [PATCH 45/76] [Source/cryptalgo/SecureSocketPort] : Improve non-blocking I/O handling --- Source/cryptalgo/SecureSocketPort.cpp | 130 ++++++++++++++++++-------- Source/cryptalgo/SecureSocketPort.h | 2 + 2 files changed, 95 insertions(+), 37 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 753ef8d1a..c84213155 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -21,10 +21,14 @@ #include #include +#include #include +#include #include +#include + #ifndef __WINDOWS__ namespace { @@ -272,6 +276,7 @@ uint32_t SecureSocketPort::Handler::Initialize() { int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { ASSERT(_handShaking == CONNECTED); ASSERT(_ssl != nullptr); + ASSERT(length > 0); int fd = SSL_get_fd(static_cast(_ssl)); @@ -281,8 +286,10 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) FD_ZERO(&fds); FD_SET(fd, &fds); -// TODO: extract from Open() - struct timeval tv {5, 0}; + struct timeval tv { + _waitTime / (Core::Time::MilliSecondsPerSecond) + , (_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond) + }; int result = -1; @@ -321,8 +328,10 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t FD_ZERO(&fds); FD_SET(fd, &fds); -// TODO: extract from Open() - struct timeval tv {5, 0}; + struct timeval tv { + _waitTime / (Core::Time::MilliSecondsPerSecond) + , (_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond) + }; int result = -1; @@ -350,6 +359,8 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { + _waitTime = waitTime; + return (Core::SocketPort::Open(waitTime)); } @@ -373,8 +384,8 @@ uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { void SecureSocketPort::Handler::ValidateHandShake() { ASSERT(_ssl != nullptr && _context != nullptr); - // SSL handshake does an implicit verification, its result is: - if (SSL_get_verify_result(static_cast(_ssl)) != X509_V_OK) { // this is what remains because our callback can also return, actually it is the smae as verify_cb! + // Internal (partial) validation result if no callback is set + if (SSL_get_verify_result(static_cast(_ssl)) != X509_V_OK) { _handShaking = ERROR; SetError(); } @@ -397,7 +408,7 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) SSL* ssl = nullptr; SecureSocketPort::IValidator* validator = nullptr; - // Retireve and call the registered callback + // Retrieve and call the registered callback if ( ctx != nullptr && (exDataIndex = SSL_get_ex_data_X509_STORE_CTX_idx()) != -1 @@ -412,6 +423,13 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) result = validator->Validate(certificate); + // Reflect the verification result in the error member of X509_STORE_CTX + if (result) { + X509_STORE_CTX_set_error(ctx, X509_V_OK); + + ASSERT(X509_STORE_CTX_get_error(ctx) == X509_V_OK); + } + X509_free(x509Cert); } @@ -422,12 +440,12 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) default : ASSERT(false); // Not within set of defined values } - return result; // 0 - Failurre, 1 - OK + return result; // 0 - Failure, 1 - OK } int PeerCertificateCallbackWrapper(X509_STORE_CTX* ctx, void* arg) { // This is called if the complete certificate validation procedure has its custom implementation - // This is typically not what is intended or required, and, a complex to do right + // This is typically not what is intended or required, and, a complex process to do correct ASSERT(false); @@ -476,7 +494,7 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() // Takes ownership of nameList SSL_CTX_set_client_CA_list(static_cast(_context), nameList); - // Callback is triggered if certiifcates have errors + // Callback is triggered if certificates have errors SSL_CTX_set_verify(static_cast(_context), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, VerifyCallbackWrapper); // Typically, SSL_CTX_set_cert_verify_callback is the most complex as it should replace the complete verification process procedure @@ -491,8 +509,15 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() void SecureSocketPort::Handler::Update() { if (IsOpen() == true) { + errno = 0; + ASSERT(_ssl != nullptr); + const char* file = nullptr, *func = nullptr, *data = nullptr; + int line = 0, flag = 0; + + ASSERT(ERR_get_error_all(&file, &line, &func, &data, &flag) == 0); + switch (_handShaking) { case CONNECTING : // Client SSL_set_connect_state(static_cast(_ssl)); @@ -505,40 +530,55 @@ void SecureSocketPort::Handler::Update() { default : ASSERT(false); } - int result = 1; + const int fd = SSL_get_fd(static_cast(_ssl)); - if ((result = SSL_do_handshake(static_cast(_ssl))) == 1) { - _handShaking = CONNECTED; - // If server has sent a certificate, and, we want to do 'our' own check - // or - // Client has sent a certificate (optional, on request only) - ValidateHandShake(); - } + ASSERT(fd >= 0); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); + + struct timeval tv { + _waitTime / (Core::Time::MilliSecondsPerSecond) + , (_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond) + }; - if (result != 1) { - int fd = SSL_get_fd(static_cast(_ssl)); + do { + int result = SSL_do_handshake(static_cast(_ssl)); - ASSERT(fd >= 0); + switch (SSL_get_error(static_cast(_ssl), result)) { + case SSL_ERROR_NONE : _handShaking = CONNECTED; + break; + case SSL_ERROR_SYSCALL : // Some syscall failed + if (errno != EAGAIN) { + // Last error without removing it from the queue + unsigned long result = 0; - fd_set fds; - FD_ZERO(&fds); - FD_SET(fd, &fds); -// TODO: extract from Open() - struct timeval tv {5, 0}; +#ifdef VERBOSE + if ((result = ERR_peek_last_error_all(&file, &line, &func, &data, &flag)) != 0) { + int lib = ERR_GET_LIB(result); + int reason = ERR_GET_REASON(result); - switch (result = SSL_get_error(static_cast(_ssl), result)) { + TRACE_L1(_T("ERROR: OpenSSL SYSCALL error in LIB %d with REASON %d"), lib, reason); + } +#endif + } + // Fallthrough for select case SSL_ERROR_WANT_READ : // Wait until ready to read case SSL_ERROR_WANT_WRITE : // Wait until ready to write - if ( (select(fd + 1, &fds, &fds, nullptr, &tv) > 0) - && FD_ISSET(fd, &fds) - && (SSL_do_handshake(static_cast(_ssl)) == 1) - ) { - _handShaking = CONNECTED; - ValidateHandShake(); - break; + switch (select(fd + 1, &fds, &fds, nullptr, &tv)) { + default : if (!FD_ISSET(fd, &fds)) { + // Only file descriptors in the set should be set + _handShaking = ERROR; + break; + } + case 0 : // Timeout +// TODO: redo SSL_do_handshake or consider it an error? + continue; + case -1 : // Select failed, consider it an error + _handShaking = ERROR; } - case SSL_ERROR_SYSCALL : result = errno; - case SSL_ERROR_NONE : + break; case SSL_ERROR_ZERO_RETURN : case SSL_ERROR_WANT_CONNECT : case SSL_ERROR_WANT_ACCEPT : @@ -548,8 +588,24 @@ void SecureSocketPort::Handler::Update() { case SSL_ERROR_SSL : default : // Error _handShaking = ERROR; - ASSERT(false); } + + ASSERT(_handShaking != ERROR); + + } while (_handShaking != CONNECTED); + + // If server has sent a certificate, and, we want to do 'our' own check + // or + // Client has sent a certificate (optional, on request only) + + // Only if a callback has not been set + if ( /*(_callback == nullptr) + &&*/ (SSL_get_verify_callback(static_cast(_ssl)) != nullptr) + && (SSL_get_verify_callback(static_cast(_ssl)) != &VerifyCallbackWrapper) + && (SSL_CTX_get_verify_callback(static_cast(_context)) != nullptr) + && (SSL_CTX_get_verify_callback(static_cast(_context)) != &VerifyCallbackWrapper) + ) { + ValidateHandShake(); } _parent.StateChange(); diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 4a5ab3e21..a802a4b17 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -88,6 +88,7 @@ namespace Crypto { , _certificatePath{certPath} , _privateKeyPath{keyPath} , _requestCertificate{requestCert} + , _waitTime{0} {} ~Handler(); @@ -143,6 +144,7 @@ namespace Crypto { const std::string _certificatePath; // (PEM formatted chain, including root CA) certificate file path const std::string _privateKeyPath; // (PEM formatted) Private key file path const bool _requestCertificate; + mutable uint32_t _waitTime; // Extracted from Open for use in I/O blocking operations }; public: From d3a5f8caaa32458c5bcc227650a69de70b64dee7 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:06:41 +0000 Subject: [PATCH 46/76] [Tests/unit/core] : amend ' dcd450c' --- Tests/unit/core/test_websocket.cpp | 101 +++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 4 deletions(-) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 6d08931e4..ddcd4f786 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -41,7 +41,7 @@ #define STR(s) #endif -#define _VERBOSE +//#define _VERBOSE namespace Thunder { namespace Tests { @@ -1351,7 +1351,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, DISABLED_SecuredServerPortCertificateRequest) + TEST(WebSocket, DISABLED_SecuredSocketServerCertificateRequest) { const TCHAR localHostName[] {"127.0.0.1"}; @@ -1388,7 +1388,7 @@ namespace Core { EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } - TEST(WebSocket, DISABLED_OpeningSecuredClientPortCertificateRequest) + TEST(WebSocket, DISABLED_OpeningSecuredSocketClientCertificateRequest) { const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol @@ -1415,13 +1415,106 @@ namespace Core { WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); // SleepMs(maxWaitTimeMs); - EXPECT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); // Fails in non-websocket server context + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); // Fails in non-websocket server context SleepMs(maxWaitTimeMs); EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } + TEST(WebSocket, SecuredSocketCertificateRequestDataExchange) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {4096}; + constexpr uint16_t receiveBufferSize {4096}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up + SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + constexpr uint8_t data[] = { 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x3, 0x2, 0x1, 0x0 }; + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + /* bool */ it.Client()->Submit(std::basic_string{ data, sizeof(data) }); + } + } + + // Allow some time to receive the response + SleepMs(maxWaitTimeMs); + + std::basic_string response{ data, sizeof(data) }; + std::reverse(response.begin(), response.end()); + + // A simple poll to keep it simple + EXPECT_TRUE( it.IsValid() + && (it.Client()->Response() == response) + ); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + } // Core } // Tests } // Thunder From 675b64f4c8199f97f2a98bd6bc151286545c8761 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:05:26 +0000 Subject: [PATCH 47/76] [Source/core/SocketServer / Source/cryptalgo/SecureSocketPort] : partial cherrypick from '7b93e6' --- Source/core/SocketServer.h | 45 +- Source/cryptalgo/SecureSocketPort.cpp | 693 +++++++++++++++++++++----- Source/cryptalgo/SecureSocketPort.h | 148 ++++-- Tests/unit/core/test_websocket.cpp | 109 +++- 4 files changed, 763 insertions(+), 232 deletions(-) diff --git a/Source/core/SocketServer.h b/Source/core/SocketServer.h index 0b439618c..2a2230e4a 100644 --- a/Source/core/SocketServer.h +++ b/Source/core/SocketServer.h @@ -30,7 +30,7 @@ namespace Core { template class SocketServerType { private: - typedef std::map> ClientMap; + using ClientMap = std::map>; public: template @@ -69,9 +69,7 @@ namespace Core { { move._atHead = true; } - ~IteratorType() - { - } + ~IteratorType() = default; IteratorType& operator=(const IteratorType& RHS) { @@ -130,38 +128,35 @@ namespace Core { typename std::list::iterator _iterator; }; - typedef IteratorType> Iterator; + using Iterator = IteratorType>; private: template class SocketHandler : public SocketListner { - private: + public: SocketHandler() = delete; + SocketHandler(SocketHandler&&) = delete; SocketHandler(const SocketHandler&) = delete; + SocketHandler& operator=(SocketHandler&&) = delete; SocketHandler& operator=(const SocketHandler&) = delete; - public: - SocketHandler(SocketServerType* parent) + SocketHandler(SocketServerType& parent) : SocketListner() , _nextClient(1) , _lock() , _clients() - , _parent(*parent) + , _parent(parent) { - - ASSERT(parent != nullptr); } - SocketHandler(const NodeId& listenNode, SocketServerType* parent) + SocketHandler(const NodeId& listenNode, SocketServerType& parent) : SocketListner(listenNode) , _nextClient(1) , _lock() , _clients() - , _parent(*parent) + , _parent(parent) { - - ASSERT(parent != nullptr); } - ~SocketHandler() + ~SocketHandler() override { SocketListner::Close(Core::infinite); CloseClients(0); @@ -267,7 +262,7 @@ namespace Core { // Do not change the Close() duration to a value >0. We should just test, but not wait for a statechange. // Waiting for a Statwchange might require, in the SocketPort imlementation of Close, WaitForCloseure with // parameter Core::infinite in case we have a faulthy socket. This call will than only return if the - // ResourceMonitor thread does report on CLosure of the socket. However, the ResourceMonitor thread might + // ResourceMonitor thread does report on Closure of the socket. However, the ResourceMonitor thread might // also be calling into here for an Accept. // In that case, the Accept will block on the _lock from this object as it is taken by this Cleanup call // running on a different thread but also this lock will not be freed as this cleanup thread is waiting @@ -353,23 +348,23 @@ namespace Core { SocketServerType& _parent; }; + public: + SocketServerType(SocketServerType&&) = delete; SocketServerType(const SocketServerType&) = delete; + SocketServerType& operator=(SocketServerType&&) = delete; SocketServerType& operator=(const SocketServerType&) = delete; - public: -PUSH_WARNING(DISABLE_WARNING_THIS_IN_MEMBER_INITIALIZER_LIST) + PUSH_WARNING(DISABLE_WARNING_THIS_IN_MEMBER_INITIALIZER_LIST) SocketServerType() - : _handler(this) + : _handler(*this) { } SocketServerType(const NodeId& listeningNode) - : _handler(listeningNode, this) - { - } -POP_WARNING() - ~SocketServerType() + : _handler(listeningNode, *this) { } + POP_WARNING() + ~SocketServerType() = default; public: inline uint32_t Open(const uint32_t waitTime) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index c84213155..ceac09e49 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -32,25 +32,25 @@ #ifndef __WINDOWS__ namespace { - class OpenSSL { - public: - OpenSSL(OpenSSL&&) = delete; - OpenSSL(const OpenSSL&) = delete; - OpenSSL& operator=(OpenSSL&&) = delete; - OpenSSL& operator=(const OpenSSL&) = delete; - - OpenSSL() - { - SSL_library_init(); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - } - ~OpenSSL() - { - } - }; +class OpenSSL { +public: + OpenSSL(OpenSSL&&) = delete; + OpenSSL(const OpenSSL&) = delete; + OpenSSL& operator=(OpenSSL&&) = delete; + OpenSSL& operator=(const OpenSSL&) = delete; + + OpenSSL() + { + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + } + ~OpenSSL() + { + } +}; - static OpenSSL _initialization; +static OpenSSL _initialization; } #endif @@ -58,38 +58,64 @@ namespace Thunder { namespace Crypto { - static Core::Time ASN1_ToTime(const ASN1_TIME* input) +class X509Certificate : public Certificate { +public: + X509Certificate(const Certificate& certificate) + : Certificate{ certificate } + {} + + X509Certificate(const X509* certificate) + : Certificate { certificate } + {} + + operator const X509* () const { - Core::Time result; + return Certificate::operator const X509* (); + } +}; - if (input != nullptr) { - uint16_t year = 0; - const char* textVersion = reinterpret_cast(input->data); +class X509Key : public Key { +public : + X509Key(const Key& key) + : Key{ key} + {} - if (input->type == V_ASN1_UTCTIME) - { - year = (textVersion[0] - '0') * 10 + (textVersion[1] - '0'); - year += (year < 70 ? 2000 : 1900); - textVersion = &textVersion[2]; - } - else if (input->type == V_ASN1_GENERALIZEDTIME) - { - year = (textVersion[0] - '0') * 1000 + (textVersion[1] - '0') * 100 + (textVersion[2] - '0') * 10 + (textVersion[3] - '0'); - textVersion = &textVersion[4]; - } - uint8_t month = ((textVersion[0] - '0') * 10 + (textVersion[1] - '0')) - 1; - uint8_t day = (textVersion[2] - '0') * 10 + (textVersion[3] - '0'); - uint8_t hour = (textVersion[4] - '0') * 10 + (textVersion[5] - '0'); - uint8_t minutes = (textVersion[6] - '0') * 10 + (textVersion[7] - '0'); - uint8_t seconds = (textVersion[8] - '0') * 10 + (textVersion[9] - '0'); - - /* Note: we did not adjust the time based on time zone information */ - result = Core::Time(year, month, day, hour, minutes, seconds, 0, false); - } - return (result); + X509Key(const EVP_PKEY* key) + : Key{ key } + {} + + operator const EVP_PKEY* () const + { + return Key::operator const EVP_PKEY* (); } +}; +class X509CertificateStore : public CertificateStore { +public: + X509CertificateStore(const CertificateStore& store) + : CertificateStore{ store } + {} + + operator X509_STORE* () const + { + VARIABLE_IS_NOT_USED X509_STORE* store = CertificateStore::operator X509_STORE* (); + + ASSERT(store != nullptr); + + return store; + } + + operator STACK_OF(X509_NAME)* () const + { + VARIABLE_IS_NOT_USED STACK_OF(X509_NAME)* store = CertificateStore::operator STACK_OF(X509_NAME)* (); + + ASSERT(store != nullptr); + + return store; + } +}; + // Placeholder for custom data 'appended' to SSL structures and sharing within the OpenSSL API class ApplicationData { public : @@ -112,12 +138,8 @@ public : int result = -1; - _lock.Lock(); - result = _map.insert({ssl, index}).second ? index : -1; - _lock.Unlock(); - return result; } @@ -127,14 +149,10 @@ public : int result = -1; - _lock.Lock(); - auto it = _map.find(ssl); result = it != _map.end() ? it->second : -1; - _lock.Unlock(); - return result; } @@ -164,44 +182,416 @@ private : mutable Core::CriticalSection _lock; }; -string SecureSocketPort::Certificate::Issuer() const { - char buffer[1024]; - buffer[0] = '\0'; - X509_NAME_oneline(X509_get_issuer_name(_certificate), buffer, sizeof(buffer)); +static Core::Time ASN1_ToTime(const ASN1_TIME* input) +{ + Core::Time result; + + if (input != nullptr) { + uint16_t year = 0; + const char* textVersion = reinterpret_cast(input->data); + + if (input->type == V_ASN1_UTCTIME) + { + year = (textVersion[0] - '0') * 10 + (textVersion[1] - '0'); + year += (year < 70 ? 2000 : 1900); + textVersion = &textVersion[2]; + } + else if (input->type == V_ASN1_GENERALIZEDTIME) + { + year = (textVersion[0] - '0') * 1000 + (textVersion[1] - '0') * 100 + (textVersion[2] - '0') * 10 + (textVersion[3] - '0'); + textVersion = &textVersion[4]; + } + uint8_t month = ((textVersion[0] - '0') * 10 + (textVersion[1] - '0')) - 1; + uint8_t day = (textVersion[2] - '0') * 10 + (textVersion[3] - '0'); + uint8_t hour = (textVersion[4] - '0') * 10 + (textVersion[5] - '0'); + uint8_t minutes = (textVersion[6] - '0') * 10 + (textVersion[7] - '0'); + uint8_t seconds = (textVersion[8] - '0') * 10 + (textVersion[9] - '0'); + + /* Note: we did not adjust the time based on time zone information */ + result = Core::Time(year, month, day, hour, minutes, seconds, 0, false); + } + return (result); +} + +// ----------------------------------------------------------------------------- +// class Certificate +// ----------------------------------------------------------------------------- +Certificate::Certificate(const X509* certificate) + : _certificate{ const_cast(certificate) } +{ + if (certificate != nullptr) { + VARIABLE_IS_NOT_USED int result = X509_up_ref(_certificate); + ASSERT(result == 1); + } +} + +Certificate::Certificate(const string& fileName) + : _certificate{ nullptr } +{ + BIO* bioFile{ !fileName.empty() ? BIO_new_file(fileName.c_str(), "r") : nullptr }; + + if (bioFile != nullptr) { + _certificate = PEM_read_bio_X509(bioFile, nullptr, nullptr, nullptr); + + BIO_free(bioFile); + } +} + +Certificate::Certificate(Certificate&& certificate) noexcept + : _certificate{ certificate._certificate } +{ + certificate._certificate = nullptr; +} + +Certificate::Certificate(const Certificate& certificate) + : _certificate{ certificate._certificate } +{ + if (_certificate != nullptr) { + VARIABLE_IS_NOT_USED int result = X509_up_ref(_certificate); + ASSERT(result == 1); + } +} + +Certificate::~Certificate() +{ + if (_certificate != nullptr) { + X509_free(_certificate); + } +} + +const std::string Certificate::Issuer() const +{ + ASSERT(_certificate != nullptr); + + std::string result; + char* buffer = nullptr; + + // Do not free + X509_NAME* name = X509_get_issuer_name(_certificate); + + if ((buffer = X509_NAME_oneline(name, nullptr, 0)) != nullptr) { + result = buffer; + + free(buffer); + } + + return (result); +} + +const std::string Certificate::Subject() const +{ + ASSERT(_certificate != nullptr); + + std::string result; + char* buffer = nullptr; + + // Do not free + X509_NAME* name = X509_get_subject_name(_certificate); + + if ((buffer = X509_NAME_oneline(name, nullptr, 0)) != nullptr) { + result = buffer; + + free(buffer); + } + + return (result); +} + +Core::Time Certificate::ValidFrom() const +{ + ASSERT(_certificate != nullptr); + + // Do not free + return (ASN1_ToTime(X509_get0_notBefore(_certificate))); +} + +Core::Time Certificate::ValidTill() const +{ + ASSERT(_certificate != nullptr); + + // Do not free + return (ASN1_ToTime(X509_get0_notAfter(_certificate))); +} + +bool Certificate::ValidHostname(const std::string& expectedHostname) const +{ + ASSERT(_certificate != nullptr); + + return (X509_check_host(_certificate, expectedHostname.c_str(), expectedHostname.size(), 0, nullptr) == 1); +} + +Certificate::operator const X509* () const +{ + if (_certificate != nullptr) { + int result = X509_up_ref(_certificate); + ASSERT(result == 1); + } + + return _certificate; +} + +// ----------------------------------------------------------------------------- +// class Key +// ----------------------------------------------------------------------------- +Key::Key(const EVP_PKEY* key) + : _key{ const_cast(key) } +{ + if (_key != nullptr) { + VARIABLE_IS_NOT_USED int result = EVP_PKEY_up_ref(_key); + ASSERT(result == 1); + } +} + +Key::Key(Key&& key) noexcept + : _key{ key._key } +{ + key._key = nullptr; +} + +Key::Key(const Key& key) + : _key{ key._key } +{ + if (_key != nullptr) { + EVP_PKEY_up_ref(_key); + } +} + +Key::Key(const string& fileName) + : _key{ nullptr } +{ + BIO* bioFile{ !fileName.empty() ? BIO_new_file(fileName.c_str(), "r") : nullptr }; + + if (bioFile != nullptr) { + _key = PEM_read_bio_PrivateKey(bioFile, nullptr, nullptr, nullptr); + + BIO_free(bioFile); + } +} + +extern "C" +{ +int PasswdCallback(char* buffer, int size, int rw, void* password) +{ + // if rw == 0 then request to supply passphrase + // if rw == 1, something else, possibly verify the supplied passphrase in a second prompt + ASSERT(rw == 0); + + ASSERT(size < 0); + + size_t copied = std::min(strlen(static_cast(password)), static_cast(size)); + /* void* */ memcpy(buffer, password, copied); - return (string(buffer)); + // 0 - error, 0> - number of characaters in passphrase + return copied; } +} + +Key::Key(const string& fileName, const string& password) + : _key(nullptr) +{ + BIO* bioFile{ !fileName.empty() ? BIO_new_file(fileName.c_str(), "r") : nullptr }; -string SecureSocketPort::Certificate::Subject() const { - char buffer[1024]; - buffer[0] = '\0'; - X509_NAME_oneline(X509_get_subject_name(_certificate), buffer, sizeof(buffer)); + if (bioFile != nullptr) { + _key = PEM_read_bio_PrivateKey(bioFile, nullptr, PasswdCallback, const_cast(password.c_str())); - return (string(buffer)); + BIO_free(bioFile); + } } -Core::Time SecureSocketPort::Certificate::ValidFrom() const { - return(ASN1_ToTime(X509_get0_notBefore(_certificate))); +Key::~Key() +{ + if (_key != nullptr) { + EVP_PKEY_free(_key); + } } -Core::Time SecureSocketPort::Certificate::ValidTill() const { - return(ASN1_ToTime(X509_get0_notAfter(_certificate))); +Key::operator const EVP_PKEY* () const +{ + return _key; } -bool SecureSocketPort::Certificate::ValidHostname(const string& expectedHostname) const { - return (X509_check_host(_certificate, expectedHostname.data(), expectedHostname.size(), 0, nullptr) == 1); +// ----------------------------------------------------------------------------- +// class CertificateStore +// ----------------------------------------------------------------------------- +CertificateStore::CertificateStore(bool defaultStore) + : _list{} + , _defaultStore{ defaultStore && CreateDefaultStore() } +{} + +CertificateStore::CertificateStore(CertificateStore&& move) noexcept + : _list { move._list } + , _defaultStore{ move._defaultStore } +{} + +CertificateStore::CertificateStore(const CertificateStore& copy) + : _list { copy._list } + , _defaultStore{ copy._defaultStore } +{} + +CertificateStore::~CertificateStore() +{} + +uint32_t CertificateStore::Add(const Certificate& certificate) +{ + uint32_t result = Core::ERROR_GENERAL; + + const X509* x509Certificate = X509Certificate{ certificate }; + + if ( x509Certificate != nullptr + && std::find_if(_list.begin(), _list.end(), [&x509Certificate](const X509Certificate item){ + const X509* x509 = item; + return X509_cmp(x509Certificate, x509) == 0; + } + ) == _list.end() + ) { + _list.push_back(certificate); + + result = Core::ERROR_NONE; + } + + return result; } -bool SecureSocketPort::Certificate::Verify(string& errorMsg) const { - long error = SSL_get_verify_result(_context); +uint32_t CertificateStore::Remove(const Certificate& certificate) +{ + uint32_t result = Core::ERROR_GENERAL; + + std::vector::iterator it; + + const X509* x509Certificate = X509Certificate{ certificate }; + + if ((it = std::find_if(_list.begin(), _list.end(), [&x509Certificate](const X509Certificate item){ + const X509* x509 = item; + return X509_cmp(x509Certificate, x509) == 0; + } + )) == _list.end() + ) { + size_t position = std::distance(_list.begin(), it); + + static_assert( !std::has_virtual_destructor::value + , "new placement without support for virtual base classes" + ); + + if (_list.size() > 1) { + Certificate& item = _list[position]; + item.~Certificate(); + new (&(item)) Crypto::Certificate(_list.back()); + } + + _list.pop_back(); - if (error != X509_V_OK) { - errorMsg = X509_verify_cert_error_string(error); + result = Core::ERROR_NONE; } - return error == X509_V_OK; + return (result); } +uint32_t CertificateStore::CreateDefaultStore() +{ + // Intended use only at object construction + + uint32_t result = Core::ERROR_NONE; + + const char* dir = getenv(X509_get_default_cert_dir_env()); + + if (dir == nullptr) { + dir = X509_get_default_cert_dir(); + } + + const std::string paths = dir; + + std::string::size_type head = paths.empty() ? std::string::npos : 0; + + // OPENSSL_info requires at least version 3.0 + + static_assert( ((OPENSSL_VERSION_NUMBER >> 28) & 0xF) >= 3 // Major + && ((OPENSSL_VERSION_NUMBER >> 20) & 0xFF) >= 0 // Minor + && ((OPENSSL_VERSION_NUMBER >> 4) & 0xF) >= 0 // Patch + && ((OPENSSL_VERSION_NUMBER) & 0xF) >= 0 // Pre-release + , "OpenSSL version unsupported. Expected version 3.0.0.0 and higher" + ); + + const char* separator = OPENSSL_info(OPENSSL_INFO_LIST_SEPARATOR); + + while (head != std::string::npos && separator != nullptr) { + std::string::size_type tail = paths.find(separator[0], head); + + Core::Directory path{ paths.substr(head, tail != std::string::npos ? tail - 1 : tail).c_str() }; + + while (path.Next()) { + const Certificate certificate{ Certificate{ path.Current() } }; + + if (X509Certificate { certificate } != nullptr) { + result = Add(certificate); + } + + if (result != Core::ERROR_NONE) { + tail = std::string::npos; + break; + } + } + + head = tail == std::string::npos ? tail : tail + 1; + } + + return (result); +} + +CertificateStore::operator X509_STORE* () const +{ + X509_STORE* store { X509_STORE_new() }; + + if (store != nullptr) { + for (auto head = _list.begin(), index = head, tail = _list.end(); index != tail; index++) { + // Do not X509_free + X509* certificate = const_cast(X509Certificate{ *index }.operator const X509*()); + + if ( certificate == nullptr + || X509_STORE_add_cert(store, certificate) != 1 + ) { + X509_STORE_free(store); + + store = nullptr; + + break; + } + } + } + + return (store); +} + +CertificateStore::operator STACK_OF(X509_NAME)* () const +{ + STACK_OF(X509_NAME)* store = sk_X509_NAME_new_null(); + + if (store != nullptr) { + for (auto head = _list.begin(), index = head, tail = _list.end(); index != tail; index++) { + // Do not X509_free + X509* certificate = const_cast(X509Certificate{ *index }.operator const X509*()); + + // name must not be freed + X509_NAME* name = certificate != nullptr ? X509_get_subject_name(certificate) : nullptr; + + if ( certificate == nullptr + || sk_X509_NAME_push(store, name) == 0 + ) { + sk_X509_NAME_free(store); + + store = nullptr; + + break; + } + } + } + + return (store); +} + +bool CertificateStore::IsDefaultStore() const +{ + return _defaultStore; +} SecureSocketPort::Handler::~Handler() { ASSERT(IsClosed() == true); @@ -209,6 +599,8 @@ SecureSocketPort::Handler::~Handler() { } uint32_t SecureSocketPort::Handler::Initialize() { + ASSERT(_context == nullptr); + uint32_t success = Core::ERROR_NONE; // Client and server use @@ -224,34 +616,40 @@ uint32_t SecureSocketPort::Handler::Initialize() { int exDataIndex = -1; + // Do not X509_free + X509* x509Certificate = const_cast(X509Certificate{ _certificate }.operator const X509*()); + // Do not EVP_PKEY_free + EVP_PKEY* x509Key = const_cast(X509Key{ _privateKey }.operator const EVP_PKEY*()); + if ( ( ( (_handShaking == ACCEPTING) - // Load server certifiate and private key - && !_certificatePath.empty() - && (SSL_CTX_use_certificate_chain_file(static_cast(_context), _certificatePath.c_str()) == 1) - && !_privateKeyPath.empty() - && (SSL_CTX_use_PrivateKey_file(static_cast(_context), _privateKeyPath.c_str(), SSL_FILETYPE_PEM) == 1) + // Load server certificate and private key + && x509Certificate != nullptr + && (SSL_CTX_use_certificate(static_cast(_context), x509Certificate) == 1) + && x509Key != nullptr + && (SSL_CTX_use_PrivateKey(static_cast(_context), x509Key) == 1) + && (SSL_CTX_check_private_key(static_cast(_context)) == 1) && (EnableClientCertificateRequest() == Core::ERROR_NONE) ) - || + || + // Load client certificate and private key if present ( (_handShaking == CONNECTING) && ( - _certificatePath.empty() + x509Certificate == nullptr || - ( !_certificatePath.empty() - && (SSL_CTX_use_certificate_file(static_cast(_context), _certificatePath.c_str(), SSL_FILETYPE_PEM) == 1) - ) + ( x509Certificate != nullptr + && (SSL_CTX_use_certificate(static_cast(_context), x509Certificate) == 1) + ) ) && ( - _privateKeyPath.empty() + x509Key == nullptr || - ( !_privateKeyPath.empty() - && (SSL_CTX_use_PrivateKey_file(static_cast(_context), _privateKeyPath.c_str(), SSL_FILETYPE_PEM) == 1) + ( x509Key != nullptr + && (SSL_CTX_use_PrivateKey(static_cast(_context), x509Key) == 1) + && (SSL_CTX_check_private_key(static_cast(_context)) == 1) ) ) ) ) - // Default location from which CA certificates are loaded - && (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) // Create a new SSL connection structure for storing the custom certification method for use in the available callback mechanism && ((_ssl = SSL_new(static_cast(_context))) != nullptr) && (SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()) == 1) @@ -357,7 +755,6 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t return (result > 0 ? result : /* error */ -1); } - uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { _waitTime = waitTime; @@ -381,6 +778,49 @@ uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { return(Core::SocketPort::Close(waitTime)); } +uint32_t SecureSocketPort::Handler::Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key) { + // Load server / client certificate and private key + + uint32_t result = Core::ERROR_BAD_REQUEST; + + // Do not free + const X509* x509Certificate = X509Certificate{ certificate }; + // Do not free + const EVP_PKEY* x509Key = X509Key{ key }; + + if ( x509Certificate != nullptr + && x509Key != nullptr + ) + { + static_assert( !std::has_virtual_destructor::value + && !std::has_virtual_destructor::value + , "new placement without support for virtual base classes" + ); + + this->_certificate.~Certificate(); + new (&(this->_certificate)) Crypto::Certificate(certificate); + + this->_privateKey.~Key(); + new (&(this->_privateKey)) Crypto::Key(key); + + // Verification of the pair at Initialize() + + result = Core::ERROR_NONE; + } + + return (result); +} + +uint32_t SecureSocketPort::Handler::CustomStore(const CertificateStore& certStore) +{ + static_assert(!std::has_virtual_destructor::value, "new placement without support for virtual base classes"); + + this->_store.~CertificateStore(); + new (&(this->_store)) CertificateStore(certStore); + + return (Core::ERROR_NONE); +} + void SecureSocketPort::Handler::ValidateHandShake() { ASSERT(_ssl != nullptr && _context != nullptr); @@ -406,7 +846,7 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) X509* x509Cert = nullptr; int exDataIndex = -1; SSL* ssl = nullptr; - SecureSocketPort::IValidator* validator = nullptr; + SecureSocketPort::IValidate* validator = nullptr; // Retrieve and call the registered callback @@ -414,12 +854,12 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) && (exDataIndex = SSL_get_ex_data_X509_STORE_CTX_idx()) != -1 && (ssl = static_cast(X509_STORE_CTX_get_ex_data(ctx, exDataIndex))) != nullptr && (exDataIndex = ApplicationData::Instance().Index(static_cast(ssl))) != -1 - && (validator = static_cast(SSL_get_ex_data(ssl, exDataIndex))) != nullptr + && (validator = static_cast(SSL_get_ex_data(ssl, exDataIndex))) != nullptr && (x509Cert = X509_STORE_CTX_get_current_cert(ctx)) != nullptr ) { X509_up_ref(x509Cert); - SecureSocketPort::Certificate certificate(x509Cert, static_cast(ssl)); + X509Certificate certificate(x509Cert); result = validator->Validate(certificate); @@ -457,41 +897,11 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() uint32_t result{Core::ERROR_NONE}; if (_requestCertificate) { - STACK_OF(X509_NAME)* nameList = _certificatePath.empty() ? nullptr : SSL_load_client_CA_file(_certificatePath.c_str()); - const std::string paths = X509_get_default_cert_dir(); - - std::string::size_type head = paths.empty() ? std::string::npos : 0; - - // OPENSSL_info requires at least version 3.0 - - static_assert( ((OPENSSL_VERSION_NUMBER >> 28) & 0xF) >= 3 // Major - && ((OPENSSL_VERSION_NUMBER >> 20) & 0xFF) >= 0 // Minor - && ((OPENSSL_VERSION_NUMBER >> 4) & 0xF) >= 0 // Patch - && ((OPENSSL_VERSION_NUMBER) & 0xF) >= 0 // Pre-release - , "OpenSSL version unsupported. Expected version 3.0.0.0 and higher" - ); - - const char* separator = OPENSSL_info(OPENSSL_INFO_LIST_SEPARATOR); - - while (head != std::string::npos && separator != nullptr) { - std::string::size_type tail = paths.find(separator[0], head); - - std::string path = paths.substr(head, tail != std::string::npos ? tail - 1 : tail); - - if ( !( nameList != nullptr - && !path.empty() - && (SSL_add_dir_cert_subjects_to_stack(nameList, path.c_str()) == 1) - ) ) - { - result = Core::ERROR_GENERAL; - break; - } - - head = tail == std::string::npos ? tail : tail + 1; - } + STACK_OF(X509_NAME)* nameList = X509CertificateStore{ _store }; - if (nameList != nullptr && result == Core::ERROR_NONE) { + if (nameList != nullptr) { // Takes ownership of nameList + // CA list to send to the client against the client's certificate is vlidated SSL_CTX_set_client_CA_list(static_cast(_context), nameList); // Callback is triggered if certificates have errors @@ -499,8 +909,8 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() // Typically, SSL_CTX_set_cert_verify_callback is the most complex as it should replace the complete verification process procedure // SSL_CTX_set_cert_verify_callback(static_cast(_context), PeerCertificateCallbackWrapper, static_cast(_ssl)); - } else if (nameList != nullptr) { - sk_X509_NAME_pop_free(nameList, X509_NAME_free); + } else { + result = Core::ERROR_GENERAL; } } @@ -509,14 +919,20 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() void SecureSocketPort::Handler::Update() { if (IsOpen() == true) { - errno = 0; - ASSERT(_ssl != nullptr); + ASSERT(_context != nullptr); + + if (_handShaking != CONNECTED) { + // The old store is automatically X509_store_free()ed + /* void */ SSL_CTX_set_cert_store(static_cast(_context), X509CertificateStore{ _store }); + } const char* file = nullptr, *func = nullptr, *data = nullptr; int line = 0, flag = 0; - ASSERT(ERR_get_error_all(&file, &line, &func, &data, &flag) == 0); +// ASSERT(ERR_get_error_all(&file, &line, &func, &data, &flag) == 0); + + errno = 0; switch (_handShaking) { case CONNECTING : // Client @@ -546,7 +962,7 @@ void SecureSocketPort::Handler::Update() { do { int result = SSL_do_handshake(static_cast(_ssl)); - switch (SSL_get_error(static_cast(_ssl), result)) { + switch ((result = SSL_get_error(static_cast(_ssl), result))) { case SSL_ERROR_NONE : _handShaking = CONNECTED; break; case SSL_ERROR_SYSCALL : // Some syscall failed @@ -566,10 +982,13 @@ void SecureSocketPort::Handler::Update() { // Fallthrough for select case SSL_ERROR_WANT_READ : // Wait until ready to read case SSL_ERROR_WANT_WRITE : // Wait until ready to write + case SSL_ERROR_WANT_CONNECT : // Operation did not complete. Redo if the connection has been established + case SSL_ERROR_WANT_ACCEPT : // Idem switch (select(fd + 1, &fds, &fds, nullptr, &tv)) { default : if (!FD_ISSET(fd, &fds)) { // Only file descriptors in the set should be set _handShaking = ERROR; + ASSERT(_handShaking != ERROR); break; } case 0 : // Timeout @@ -577,21 +996,18 @@ void SecureSocketPort::Handler::Update() { continue; case -1 : // Select failed, consider it an error _handShaking = ERROR; + ASSERT(_handShaking != ERROR); } break; case SSL_ERROR_ZERO_RETURN : - case SSL_ERROR_WANT_CONNECT : - case SSL_ERROR_WANT_ACCEPT : case SSL_ERROR_WANT_ASYNC : case SSL_ERROR_WANT_ASYNC_JOB : case SSL_ERROR_WANT_CLIENT_HELLO_CB : - case SSL_ERROR_SSL : + case SSL_ERROR_SSL : // Unrecoverable error default : // Error _handShaking = ERROR; } - ASSERT(_handShaking != ERROR); - } while (_handShaking != CONNECTED); // If server has sent a certificate, and, we want to do 'our' own check @@ -607,9 +1023,12 @@ void SecureSocketPort::Handler::Update() { ) { ValidateHandShake(); } + ASSERT(_handShaking != ERROR); _parent.StateChange(); } } -} } // namespace Thunder::Crypto +} + +} // namespace Thunder::Crypto diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index a802a4b17..d9a8b56f9 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -19,43 +19,96 @@ #pragma once -#include "Module.h" +#include + +#include -struct x509_store_ctx_st; -struct x509_st; -struct ssl_st; +#include "Module.h" namespace Thunder { namespace Crypto { - class EXTERNAL SecureSocketPort : public Core::IResource { + class EXTERNAL Certificate { public: - class EXTERNAL Certificate { - public: - Certificate() = delete; - Certificate(Certificate&&) = delete; - Certificate(const Certificate&) = delete; + Certificate() = delete; + Certificate& operator=(Certificate&&) = delete; + Certificate& operator=(const Certificate&) = delete; - Certificate(x509_st* certificate, const ssl_st* context) - : _certificate(certificate) - , _context(context) { - } - ~Certificate() = default; + Certificate(const std::string& fileName); + Certificate(Certificate&& move) noexcept; + Certificate(const Certificate& copy); + ~Certificate(); - public: - string Issuer() const; - string Subject() const; - Core::Time ValidFrom() const; - Core::Time ValidTill() const; - bool ValidHostname(const string& expectedHostname) const; - bool Verify(string& errorMsg) const; + public: + const std::string Issuer() const; + const std::string Subject() const; + Core::Time ValidFrom() const; + Core::Time ValidTill() const; + bool ValidHostname(const std::string& expectedHostname) const; - private: - x509_st* _certificate; - const ssl_st* _context; - }; - struct IValidator { - virtual ~IValidator() = default; + protected: + Certificate(const X509* certificate); + operator const X509* () const; + + private: + mutable X509* _certificate; + }; + + class EXTERNAL Key { + public: + Key() = delete; + Key& operator=(Key&&) = delete; + Key& operator=(const Key&) = delete; + + Key(const string& fileName); + Key(const string& fileName, const string& password); + Key(Key&& move) noexcept; + Key(const Key& copy); + ~Key(); + + protected: + Key(const EVP_PKEY* key); + operator const EVP_PKEY* () const; + + private: + mutable EVP_PKEY* _key; + }; + + class EXTERNAL CertificateStore { + public: + CertificateStore() = delete; + CertificateStore& operator=(CertificateStore&&) = delete; + CertificateStore& operator=(const CertificateStore&) = delete; + + CertificateStore(bool defaultStore); + CertificateStore(CertificateStore&&) noexcept; + CertificateStore(const CertificateStore&); + ~CertificateStore(); + + public: + uint32_t Add(const Certificate& certificate); + uint32_t Remove(const Certificate& certificate); + + bool IsDefaultStore() const; + + protected: + + operator X509_STORE* () const; + operator STACK_OF(X509_NAME)* () const; + + private: + uint32_t CreateDefaultStore(); + + // (Extra) added certificates + std::vector _list; + + const bool _defaultStore; + }; + + class EXTERNAL SecureSocketPort : public Core::IResource { + public: + struct IValidate { + virtual ~IValidate() = default; virtual bool Validate(const Certificate& certificate) const = 0; }; @@ -78,17 +131,18 @@ namespace Crypto { Handler& operator=(Handler&&) = delete; template - Handler(SecureSocketPort& parent, bool isClientSocketType, const std::string& certPath, const std::string& keyPath, bool requestCert, Args&&... args) + Handler(SecureSocketPort& parent, bool isClientSocketType, bool requestCert, Args&&... args) : Core::SocketPort(std::forward(args)...) , _parent(parent) , _context(nullptr) , _ssl(nullptr) , _callback(nullptr) , _handShaking{isClientSocketType ? CONNECTING : ACCEPTING} - , _certificatePath{certPath} - , _privateKeyPath{keyPath} + , _certificate{""} + , _privateKey{""} , _requestCertificate{requestCert} , _waitTime{0} + , _store{ true } {} ~Handler(); @@ -114,7 +168,7 @@ namespace Crypto { void StateChange() override { Update(); } - inline uint32_t Callback(IValidator* callback) { + inline uint32_t Callback(IValidate* callback) { uint32_t result = Core::ERROR_ILLEGAL_STATE; Core::SocketPort::Lock(); @@ -129,6 +183,8 @@ namespace Crypto { return (result); } + uint32_t Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key); + uint32_t CustomStore( const CertificateStore& store); private: void Update(); @@ -139,12 +195,13 @@ namespace Crypto { SecureSocketPort& _parent; void* _context; void* _ssl; - IValidator* _callback; + IValidate* _callback; mutable state _handShaking; - const std::string _certificatePath; // (PEM formatted chain, including root CA) certificate file path - const std::string _privateKeyPath; // (PEM formatted) Private key file path + mutable Crypto::Certificate _certificate; // (PEM formatted ccertificate (chain) + mutable Crypto::Key _privateKey; // (PEM formatted) private key const bool _requestCertificate; mutable uint32_t _waitTime; // Extracted from Open for use in I/O blocking operations + CertificateStore _store; }; public: @@ -160,18 +217,13 @@ namespace Crypto { }; template - SecureSocketPort(context_t, Args&&... args) - : SecureSocketPort(context_t::CLIENT_CONTEXT, static_cast(std::string{""}), static_cast(std::string{""}), false, std::forward(args)...) + SecureSocketPort(context_t context, Args&&... args) + : SecureSocketPort(context, false, std::forward(args)...) {} template - SecureSocketPort(context_t context, const std::string& certPath, const std::string& keyPath, Args&&... args) - : SecureSocketPort(context, certPath, keyPath, false, std::forward(args)...) - {} - - template - SecureSocketPort(context_t context, const std::string& certPath, const std::string& keyPath, bool requestPeerCert, Args&&... args) - : _handler(*this, context == context_t::CLIENT_CONTEXT, certPath, keyPath, requestPeerCert && context == context_t::SERVER_CONTEXT, std::forward(args)...) + SecureSocketPort(context_t context, bool requestPeerCert, Args&&... args) + : _handler(*this, context == context_t::CLIENT_CONTEXT, requestPeerCert && context == context_t::SERVER_CONTEXT, std::forward(args)...) {} public: @@ -228,9 +280,15 @@ namespace Crypto { inline void Trigger() { _handler.Trigger(); } - inline uint32_t Callback(IValidator* callback) { + inline uint32_t Callback(IValidate* callback) { return (_handler.Callback(callback)); } + inline uint32_t Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key) { + return (_handler.Certificate(certificate, key)); + } + inline uint32_t CustomStore(const CertificateStore& store) { + return (_handler.CustomStore(store)); + } // // Core::IResource interface diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index ddcd4f786..43532c08d 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -312,13 +312,13 @@ namespace Core { /* void* */ memcpy(dataFrame, message.data(), count); -#ifdef _VERBOSE +//#ifdef _VERBOSE std::cout << " |--> dataFrame (" << count << " ) = "; for (int32_t index = 0; index < count; index++) { std::cout << std::hex << static_cast(dataFrame[index]) << " "; } std::cout << "\n"; -#endif +//#endif if (count == message.size()) { /* iterator */ _post.erase(_post.begin()); @@ -342,13 +342,13 @@ namespace Core { if (receivedSize > 0) { _response.emplace_back(std::basic_string{ dataFrame, receivedSize }); -#ifdef _VERBOSE +//#ifdef _VERBOSE std::cout << " |--> dataFrame ( " << receivedSize << " ) = "; for (int32_t index = 0; index < receivedSize; index++) { std::cout << std::hex << static_cast(dataFrame[index]) << " "; } std::cout << "\n"; -#endif +//#endif } return receivedSize; @@ -402,14 +402,14 @@ namespace Core { static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); - // Validat eclient certificate - class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidator { + // Validate client certificate + class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidate { public: Validator() = default; ~Validator() = default; - bool Validate(const Certificate& certificate) const override { + bool Validate(const ::Thunder::Crypto::Certificate& certificate) const override { // Print certificate properties #ifdef _VERBOSE std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; @@ -431,11 +431,18 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostClient.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostClient.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _validator{} { + // Support client certificate request + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostClient.pem").c_str()); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostClient.key").c_str()); + + uint32_t result = Certificate(certificate, privateKey); + + // Validate custom (sefl signed) certificates - uint32_t result = Callback(&_validator); + /* uint32_t */ result = Callback(&_validator); } CustomSecureSocketStream( @@ -445,11 +452,17 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostClient.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostClient.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _validator{} { + // Support client certificate request + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostClient.pem").c_str()); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostClient.key").c_str()); + + uint32_t result = Certificate(certificate, privateKey); + // Validate custom (self signed) client certificates - uint32_t result = Callback(&_validator); + /* uint32_t */ result = Callback(&_validator); } ~CustomSecureSocketStream() @@ -480,8 +493,15 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostServer.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostServer.key"}), ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) - {} + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + { + // Server identification + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem").c_str()); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key").c_str()); + + uint32_t result = Certificate(certificate, privateKey); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + } CustomSecureServerSocketStream( const bool @@ -490,8 +510,15 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostServer.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostServer.key"}), ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) - {} + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + { + // Server identification + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem").c_str()); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key").c_str()); + + uint32_t result = Certificate(certificate, privateKey); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + } ~CustomSecureServerSocketStream() { @@ -509,21 +536,21 @@ namespace Core { static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); // Validat eclient certificate - class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidator { + class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidate { public: Validator() = default; ~Validator() = default; - bool Validate(const Certificate& certificate) const override { + bool Validate(const ::Thunder::Crypto::Certificate& certificate) const override { // Print certificate properties -#ifdef _VERBOSE +//#ifdef _VERBOSE std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; std::cout << " |--> Issuer = " << certificate.Issuer() << "\n"; std::cout << " |--> Subject = " << certificate.Subject() << "\n"; std::cout << " |--> Valid from = " << certificate.ValidFrom().ToRFC1123() << "\n"; std::cout << " |--> Valid until = " << certificate.ValidTill().ToRFC1123() << "\n"; -#endif +//#endif return true; // Always accept } }; @@ -537,11 +564,27 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostServer.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostServer.key"}), true, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, true, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _validator{} { - // Validate custom (sefl signed) certificates - uint32_t result = Callback(&_validator); + // Server identification + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem").c_str()); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key").c_str()); + ::Thunder::Crypto::Certificate certificateCA(std::string(volatilePath).append("rootCA.pem").c_str()); + ::Thunder::Crypto::CertificateStore store{ false }; + + uint32_t result = Certificate(certificate, privateKey); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + + result = store.Add(certificateCA); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + + result = CustomStore(store); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + + // Validate custom (self signed) client certificates + result = Callback(&_validator); + ASSERT(result == ::Thunder::Core::ERROR_NONE); } CustomSecureServerSocketStreamClientValidation( @@ -551,11 +594,27 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, static_cast(std::string{volatilePath} + std::string{"localhostServer.pem"}), static_cast(std::string{volatilePath} + std::string{"localhostServer.key"}), true, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, true, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _validator{} { + // Server identification + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem").c_str()); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key").c_str()); + ::Thunder::Crypto::Certificate certificateCA(std::string(volatilePath).append("rootCA.pem").c_str()); + ::Thunder::Crypto::CertificateStore store{ false }; + + uint32_t result = Certificate(certificate, privateKey); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + + result = store.Add(certificateCA); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + + result = CustomStore(store); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + // Validate custom (self signed) client certificates - uint32_t result = Callback(&_validator); + result = Callback(&_validator); + ASSERT(result == ::Thunder::Core::ERROR_NONE); } ~CustomSecureServerSocketStreamClientValidation() @@ -1475,7 +1534,7 @@ namespace Core { ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); // A small delay so the child can be set up - SleepMs(maxInitTime); + // SleepMs(maxInitTime); EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); From 3aa2d4175851a6a06e51828f6a79947eb5cf6724 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:02:35 +0000 Subject: [PATCH 48/76] [Source/cryptalgo/SecureSocketPort] : suppress narrowing conversion warning on return type --- Source/cryptalgo/SecureSocketPort.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index ceac09e49..597f547b3 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -381,8 +381,11 @@ int PasswdCallback(char* buffer, int size, int rw, void* password) size_t copied = std::min(strlen(static_cast(password)), static_cast(size)); /* void* */ memcpy(buffer, password, copied); +// using common_t = std::common_type::type; +// static_assert(static_cast(std::numeric_limits::max()) <= static_cast(std::numeric_limits::max())); + // 0 - error, 0> - number of characaters in passphrase - return copied; + return static_cast(copied); } } From f4adf70e59430207a52ee4fc67e0ddfdce7afcea Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:32:47 +0000 Subject: [PATCH 49/76] [Tests/unit/core] : only build 'test_websocket' --- Tests/unit/core/CMakeLists.txt | 111 --------------------------------- 1 file changed, 111 deletions(-) diff --git a/Tests/unit/core/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index b63807b14..f03904129 100644 --- a/Tests/unit/core/CMakeLists.txt +++ b/Tests/unit/core/CMakeLists.txt @@ -21,121 +21,10 @@ if(LINUX) # IPTestAdministrator only supported on LINUX platform add_executable(${TEST_RUNNER_NAME} ../IPTestAdministrator.cpp - test_cyclicbuffer.cpp - test_cyclicbuffer_dataexchange.cpp - test_databuffer.cpp - test_dataelement.cpp - test_dataelementfile.cpp - test_doorbell.cpp - test_enumerate.cpp - test_event.cpp - test_filesystem.cpp - test_frametype.cpp - test_hash.cpp - test_hex2strserialization.cpp - #test_ipc.cpp - test_ipcclient.cpp - test_iso639.cpp - test_iterator.cpp - test_jsonparser.cpp - test_keyvalue.cpp - test_library.cpp - test_lockablecontainer.cpp - test_measurementtype.cpp - test_memberavailability.cpp - test_message_dispatcher.cpp - test_messageException.cpp - test_networkinfo.cpp - test_nodeid.cpp - test_numbertype.cpp - test_optional.cpp - test_parser.cpp - test_portability.cpp - test_processinfo.cpp - test_queue.cpp - test_rangetype.cpp - test_readwritelock.cpp - test_rectangle.cpp - test_rpc.cpp - test_semaphore.cpp - test_sharedbuffer.cpp - test_singleton.cpp - test_socketstreamjson.cpp - test_socketstreamtext.cpp - test_statetrigger.cpp - test_stopwatch.cpp - test_synchronize.cpp - test_synchronous.cpp - test_systeminfo.cpp - test_textfragment.cpp - test_textreader.cpp - test_thread.cpp - test_threadpool.cpp - test_time.cpp - test_timer.cpp - test_tristate.cpp - #test_valuerecorder.cpp - test_weblinkjson.cpp - test_weblinktext.cpp test_websocket.cpp - test_websocketjson.cpp - test_websockettext.cpp - test_workerpool.cpp - test_xgetopt.cpp ) -#[[ -target_sources(${TEST_RUNNER_NAME} PRIVATE test_message_unit.cpp) -target_link_libraries(${TEST_RUNNER_NAME} - ThunderMessaging -) -]] else() add_executable(${TEST_RUNNER_NAME} - test_databuffer.cpp - test_dataelement.cpp - test_dataelementfile.cpp - test_enumerate.cpp - test_event.cpp - test_filesystem.cpp - test_frametype.cpp - test_hash.cpp - test_hex2strserialization.cpp - test_iso639.cpp - test_iterator.cpp - test_jsonparser.cpp - test_keyvalue.cpp - test_library.cpp - test_lockablecontainer.cpp - test_measurementtype.cpp - test_memberavailability.cpp - test_messageException.cpp - test_networkinfo.cpp - test_nodeid.cpp - test_numbertype.cpp - test_optional.cpp - test_parser.cpp - test_portability.cpp - test_processinfo.cpp - test_queue.cpp - test_rangetype.cpp - test_readwritelock.cpp - test_rectangle.cpp - test_semaphore.cpp - test_singleton.cpp - test_statetrigger.cpp - test_stopwatch.cpp - test_synchronize.cpp - test_systeminfo.cpp - test_textfragment.cpp - test_textreader.cpp - test_thread.cpp - test_threadpool.cpp - test_time.cpp - test_timer.cpp - test_tristate.cpp - #test_valuerecorder.cpp - test_workerpool.cpp - test_xgetopt.cpp ) endif() From 9bfc4e91ef89bd4e639431b042abf35c14789663 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:29:46 +0000 Subject: [PATCH 50/76] [Tests/unit/core] : Update 'test_websocket' --- Tests/unit/core/test_websocket.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 43532c08d..2532a8e45 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -411,13 +411,13 @@ namespace Core { bool Validate(const ::Thunder::Crypto::Certificate& certificate) const override { // Print certificate properties -#ifdef _VERBOSE +//#ifdef _VERBOSE std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; std::cout << " |--> Issuer = " << certificate.Issuer() << "\n"; std::cout << " |--> Subject = " << certificate.Subject() << "\n"; std::cout << " |--> Valid from = " << certificate.ValidFrom().ToRFC1123() << "\n"; std::cout << " |--> Valid until = " << certificate.ValidTill().ToRFC1123() << "\n"; -#endif +//#endif return true; // Always accept } }; @@ -579,6 +579,9 @@ namespace Core { result = store.Add(certificateCA); ASSERT(result == ::Thunder::Core::ERROR_NONE); + result = store.Add(certificate); + result = store.Remove(certificate); + result = CustomStore(store); ASSERT(result == ::Thunder::Core::ERROR_NONE); @@ -609,6 +612,9 @@ namespace Core { result = store.Add(certificateCA); ASSERT(result == ::Thunder::Core::ERROR_NONE); + result = store.Add(certificate); + result = store.Remove(certificate); + result = CustomStore(store); ASSERT(result == ::Thunder::Core::ERROR_NONE); From 825b27ad3acf934354814f2d2c5eef0a51b5fd67 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:44:53 +0000 Subject: [PATCH 51/76] Revert "[Tests/unit/core] : only build 'test_websocket'" This reverts commit f4adf70e59430207a52ee4fc67e0ddfdce7afcea. --- Tests/unit/core/CMakeLists.txt | 111 +++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/Tests/unit/core/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index f03904129..b63807b14 100644 --- a/Tests/unit/core/CMakeLists.txt +++ b/Tests/unit/core/CMakeLists.txt @@ -21,10 +21,121 @@ if(LINUX) # IPTestAdministrator only supported on LINUX platform add_executable(${TEST_RUNNER_NAME} ../IPTestAdministrator.cpp + test_cyclicbuffer.cpp + test_cyclicbuffer_dataexchange.cpp + test_databuffer.cpp + test_dataelement.cpp + test_dataelementfile.cpp + test_doorbell.cpp + test_enumerate.cpp + test_event.cpp + test_filesystem.cpp + test_frametype.cpp + test_hash.cpp + test_hex2strserialization.cpp + #test_ipc.cpp + test_ipcclient.cpp + test_iso639.cpp + test_iterator.cpp + test_jsonparser.cpp + test_keyvalue.cpp + test_library.cpp + test_lockablecontainer.cpp + test_measurementtype.cpp + test_memberavailability.cpp + test_message_dispatcher.cpp + test_messageException.cpp + test_networkinfo.cpp + test_nodeid.cpp + test_numbertype.cpp + test_optional.cpp + test_parser.cpp + test_portability.cpp + test_processinfo.cpp + test_queue.cpp + test_rangetype.cpp + test_readwritelock.cpp + test_rectangle.cpp + test_rpc.cpp + test_semaphore.cpp + test_sharedbuffer.cpp + test_singleton.cpp + test_socketstreamjson.cpp + test_socketstreamtext.cpp + test_statetrigger.cpp + test_stopwatch.cpp + test_synchronize.cpp + test_synchronous.cpp + test_systeminfo.cpp + test_textfragment.cpp + test_textreader.cpp + test_thread.cpp + test_threadpool.cpp + test_time.cpp + test_timer.cpp + test_tristate.cpp + #test_valuerecorder.cpp + test_weblinkjson.cpp + test_weblinktext.cpp test_websocket.cpp + test_websocketjson.cpp + test_websockettext.cpp + test_workerpool.cpp + test_xgetopt.cpp ) +#[[ +target_sources(${TEST_RUNNER_NAME} PRIVATE test_message_unit.cpp) +target_link_libraries(${TEST_RUNNER_NAME} + ThunderMessaging +) +]] else() add_executable(${TEST_RUNNER_NAME} + test_databuffer.cpp + test_dataelement.cpp + test_dataelementfile.cpp + test_enumerate.cpp + test_event.cpp + test_filesystem.cpp + test_frametype.cpp + test_hash.cpp + test_hex2strserialization.cpp + test_iso639.cpp + test_iterator.cpp + test_jsonparser.cpp + test_keyvalue.cpp + test_library.cpp + test_lockablecontainer.cpp + test_measurementtype.cpp + test_memberavailability.cpp + test_messageException.cpp + test_networkinfo.cpp + test_nodeid.cpp + test_numbertype.cpp + test_optional.cpp + test_parser.cpp + test_portability.cpp + test_processinfo.cpp + test_queue.cpp + test_rangetype.cpp + test_readwritelock.cpp + test_rectangle.cpp + test_semaphore.cpp + test_singleton.cpp + test_statetrigger.cpp + test_stopwatch.cpp + test_synchronize.cpp + test_systeminfo.cpp + test_textfragment.cpp + test_textreader.cpp + test_thread.cpp + test_threadpool.cpp + test_time.cpp + test_timer.cpp + test_tristate.cpp + #test_valuerecorder.cpp + test_workerpool.cpp + test_xgetopt.cpp ) endif() From 70d8237b94c0e9ce8a344e166e140399529619c0 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:41:56 +0000 Subject: [PATCH 52/76] [Source] : Enable SecureSocketPort by default --- Source/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 1ce83c392..21572e50e 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -47,7 +47,8 @@ option(DEADLOCK_DETECTION "Enable deadlock detection tooling." OFF) option(DISABLE_USE_COMPLEMENTARY_CODE_SET "Disable the complementary code set" OFF) - +option(SECURE_SOCKET + "Enable SecureSocketPort" ON) if(HIDE_NON_EXTERNAL_SYMBOLS) set(CMAKE_CXX_VISIBILITY_PRESET hidden) From e5a90d157a3ac8fc28fe41306d3d2c53cbbb60c7 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:00:54 +0000 Subject: [PATCH 53/76] [Source/cryptalgo/SecureSocketPort / Tests/unit/core] : Reduce exposure of implementation details. --- Source/cryptalgo/SecureSocketPort.cpp | 28 +++++++++++++-------------- Source/cryptalgo/SecureSocketPort.h | 10 +++++----- Tests/unit/core/test_websocket.cpp | 28 +++++++++++++-------------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 597f547b3..4c009883e 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -70,7 +70,7 @@ class X509Certificate : public Certificate { operator const X509* () const { - return Certificate::operator const X509* (); + return static_cast(Certificate::operator const void* ()); } }; @@ -216,11 +216,11 @@ static Core::Time ASN1_ToTime(const ASN1_TIME* input) // ----------------------------------------------------------------------------- // class Certificate // ----------------------------------------------------------------------------- -Certificate::Certificate(const X509* certificate) - : _certificate{ const_cast(certificate) } +Certificate::Certificate(const void* certificate) + : _certificate{ const_cast(certificate) } { if (certificate != nullptr) { - VARIABLE_IS_NOT_USED int result = X509_up_ref(_certificate); + VARIABLE_IS_NOT_USED int result = X509_up_ref(static_cast(_certificate)); ASSERT(result == 1); } } @@ -247,7 +247,7 @@ Certificate::Certificate(const Certificate& certificate) : _certificate{ certificate._certificate } { if (_certificate != nullptr) { - VARIABLE_IS_NOT_USED int result = X509_up_ref(_certificate); + VARIABLE_IS_NOT_USED int result = X509_up_ref(static_cast(_certificate)); ASSERT(result == 1); } } @@ -255,7 +255,7 @@ Certificate::Certificate(const Certificate& certificate) Certificate::~Certificate() { if (_certificate != nullptr) { - X509_free(_certificate); + X509_free(static_cast(_certificate)); } } @@ -267,7 +267,7 @@ const std::string Certificate::Issuer() const char* buffer = nullptr; // Do not free - X509_NAME* name = X509_get_issuer_name(_certificate); + X509_NAME* name = X509_get_issuer_name(static_cast(_certificate)); if ((buffer = X509_NAME_oneline(name, nullptr, 0)) != nullptr) { result = buffer; @@ -286,7 +286,7 @@ const std::string Certificate::Subject() const char* buffer = nullptr; // Do not free - X509_NAME* name = X509_get_subject_name(_certificate); + X509_NAME* name = X509_get_subject_name(static_cast(_certificate)); if ((buffer = X509_NAME_oneline(name, nullptr, 0)) != nullptr) { result = buffer; @@ -302,7 +302,7 @@ Core::Time Certificate::ValidFrom() const ASSERT(_certificate != nullptr); // Do not free - return (ASN1_ToTime(X509_get0_notBefore(_certificate))); + return (ASN1_ToTime(X509_get0_notBefore(static_cast(_certificate)))); } Core::Time Certificate::ValidTill() const @@ -310,20 +310,20 @@ Core::Time Certificate::ValidTill() const ASSERT(_certificate != nullptr); // Do not free - return (ASN1_ToTime(X509_get0_notAfter(_certificate))); + return (ASN1_ToTime(X509_get0_notAfter(static_cast(_certificate)))); } bool Certificate::ValidHostname(const std::string& expectedHostname) const { ASSERT(_certificate != nullptr); - return (X509_check_host(_certificate, expectedHostname.c_str(), expectedHostname.size(), 0, nullptr) == 1); + return (X509_check_host(static_cast(_certificate), expectedHostname.c_str(), expectedHostname.size(), 0, nullptr) == 1); } -Certificate::operator const X509* () const +Certificate::operator const void* () const { if (_certificate != nullptr) { - int result = X509_up_ref(_certificate); + int result = X509_up_ref(static_cast(_certificate)); ASSERT(result == 1); } @@ -524,7 +524,7 @@ uint32_t CertificateStore::CreateDefaultStore() while (path.Next()) { const Certificate certificate{ Certificate{ path.Current() } }; - if (X509Certificate { certificate } != nullptr) { + if (X509Certificate { certificate }.operator const X509* () != nullptr) { result = Add(certificate); } diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index d9a8b56f9..5cd426cf2 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -47,11 +47,11 @@ namespace Crypto { bool ValidHostname(const std::string& expectedHostname) const; protected: - Certificate(const X509* certificate); - operator const X509* () const; + Certificate(const void* certificate); + operator const void* () const; private: - mutable X509* _certificate; + mutable void* _certificate; }; class EXTERNAL Key { @@ -138,8 +138,8 @@ namespace Crypto { , _ssl(nullptr) , _callback(nullptr) , _handShaking{isClientSocketType ? CONNECTING : ACCEPTING} - , _certificate{""} - , _privateKey{""} + , _certificate{std::string{""}} + , _privateKey{std::string{""}} , _requestCertificate{requestCert} , _waitTime{0} , _store{ true } diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 2532a8e45..42aa8b13d 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -435,8 +435,8 @@ namespace Core { , _validator{} { // Support client certificate request - ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostClient.pem").c_str()); - ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostClient.key").c_str()); + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostClient.pem")); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostClient.key")); uint32_t result = Certificate(certificate, privateKey); @@ -456,8 +456,8 @@ namespace Core { , _validator{} { // Support client certificate request - ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostClient.pem").c_str()); - ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostClient.key").c_str()); + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostClient.pem")); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostClient.key")); uint32_t result = Certificate(certificate, privateKey); @@ -496,8 +496,8 @@ namespace Core { : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) { // Server identification - ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem").c_str()); - ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key").c_str()); + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem")); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key")); uint32_t result = Certificate(certificate, privateKey); ASSERT(result == ::Thunder::Core::ERROR_NONE); @@ -513,8 +513,8 @@ namespace Core { : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) { // Server identification - ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem").c_str()); - ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key").c_str()); + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem")); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key")); uint32_t result = Certificate(certificate, privateKey); ASSERT(result == ::Thunder::Core::ERROR_NONE); @@ -568,9 +568,9 @@ namespace Core { , _validator{} { // Server identification - ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem").c_str()); - ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key").c_str()); - ::Thunder::Crypto::Certificate certificateCA(std::string(volatilePath).append("rootCA.pem").c_str()); + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem")); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key")); + ::Thunder::Crypto::Certificate certificateCA(std::string(volatilePath).append("rootCA.pem")); ::Thunder::Crypto::CertificateStore store{ false }; uint32_t result = Certificate(certificate, privateKey); @@ -601,9 +601,9 @@ namespace Core { , _validator{} { // Server identification - ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem").c_str()); - ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key").c_str()); - ::Thunder::Crypto::Certificate certificateCA(std::string(volatilePath).append("rootCA.pem").c_str()); + ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem")); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key")); + ::Thunder::Crypto::Certificate certificateCA(std::string(volatilePath).append("rootCA.pem")); ::Thunder::Crypto::CertificateStore store{ false }; uint32_t result = Certificate(certificate, privateKey); From d8807fb79df8e673ecc544f74919a81130c067d2 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:37:08 +0000 Subject: [PATCH 54/76] [Source/cryptalgo/SecureSocketPort]: Reduce compiler warnings --- Source/cryptalgo/SecureSocketPort.cpp | 70 +++++++++++++++++---------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 4c009883e..b109c8347 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -29,6 +29,10 @@ #include +#ifndef _FALLTHROUGH +#define _FALLTHROUGH while(false){}; +#endif + #ifndef __WINDOWS__ namespace { @@ -687,9 +691,14 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) FD_ZERO(&fds); FD_SET(fd, &fds); + using common_time_t = std::common_type::type; + using common_sec_t = std::common_type::type; + static_assert(static_cast(std::numeric_limits::max()) > static_cast(std::numeric_limits::max())); + static_assert(static_cast(std::numeric_limits::max()) > static_cast(std::numeric_limits::max())); + struct timeval tv { - _waitTime / (Core::Time::MilliSecondsPerSecond) - , (_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond) + static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) + , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) }; int result = -1; @@ -704,11 +713,11 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) case SSL_READING : // More data to be read to complete result = (select(fd + 1, nullptr, &fds, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? SSL_read(static_cast(_ssl), buffer, length) : -1; break; - case SSL_X509_LOOKUP : // Callback should be called again, see SSL_CTX_set_client_cert_cb() - case SSL_RETRY_VERIFY : // Callback should be called again, see SSL_set_retry_verify() - case SSL_ASYNC_PAUSED : // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds - case SSL_ASYNC_NO_JOBS : // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() - case SSL_CLIENT_HELLO_CB : // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() + case SSL_X509_LOOKUP : _FALLTHROUGH; // Callback should be called again, see SSL_CTX_set_client_cert_cb() + case SSL_RETRY_VERIFY : _FALLTHROUGH; // Callback should be called again, see SSL_set_retry_verify() + case SSL_ASYNC_PAUSED : _FALLTHROUGH; // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds + case SSL_ASYNC_NO_JOBS : _FALLTHROUGH; // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() + case SSL_CLIENT_HELLO_CB : _FALLTHROUGH; // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() default : // Error not processed result = -1; } @@ -729,9 +738,14 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t FD_ZERO(&fds); FD_SET(fd, &fds); + using common_time_t = std::common_type::type; + using common_sec_t = std::common_type::type; + static_assert(static_cast(std::numeric_limits::max()) > static_cast(std::numeric_limits::max())); + static_assert(static_cast(std::numeric_limits::max()) > static_cast(std::numeric_limits::max())); + struct timeval tv { - _waitTime / (Core::Time::MilliSecondsPerSecond) - , (_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond) + static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) + , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) }; int result = -1; @@ -746,11 +760,11 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t case SSL_READING : // More data to be read to complete result = (select(fd + 1, nullptr, &fds, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? SSL_write(static_cast(_ssl), buffer, length) : -1; break; - case SSL_X509_LOOKUP : // Callback should be called again, see SSL_CTX_set_client_cert_cb() - case SSL_RETRY_VERIFY : // Callback should be called again, see SSL_set_retry_verify() - case SSL_ASYNC_PAUSED : // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds - case SSL_ASYNC_NO_JOBS : // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() - case SSL_CLIENT_HELLO_CB : // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() + case SSL_X509_LOOKUP : _FALLTHROUGH; // Callback should be called again, see SSL_CTX_set_client_cert_cb() + case SSL_RETRY_VERIFY : _FALLTHROUGH; // Callback should be called again, see SSL_set_retry_verify() + case SSL_ASYNC_PAUSED : _FALLTHROUGH; // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds + case SSL_ASYNC_NO_JOBS : _FALLTHROUGH; // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() + case SSL_CLIENT_HELLO_CB : _FALLTHROUGH; // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() default : // Error not processed result = -1; } @@ -886,7 +900,7 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) return result; // 0 - Failure, 1 - OK } -int PeerCertificateCallbackWrapper(X509_STORE_CTX* ctx, void* arg) +int PeerCertificateCallbackWrapper(VARIABLE_IS_NOT_USED X509_STORE_CTX* ctx, VARIABLE_IS_NOT_USED void* arg) { // This is called if the complete certificate validation procedure has its custom implementation // This is typically not what is intended or required, and, a complex process to do correct @@ -930,8 +944,8 @@ void SecureSocketPort::Handler::Update() { /* void */ SSL_CTX_set_cert_store(static_cast(_context), X509CertificateStore{ _store }); } - const char* file = nullptr, *func = nullptr, *data = nullptr; - int line = 0, flag = 0; +// const char* file = nullptr, *func = nullptr, *data = nullptr; +// int line = 0, flag = 0; // ASSERT(ERR_get_error_all(&file, &line, &func, &data, &flag) == 0); @@ -983,9 +997,9 @@ void SecureSocketPort::Handler::Update() { #endif } // Fallthrough for select - case SSL_ERROR_WANT_READ : // Wait until ready to read - case SSL_ERROR_WANT_WRITE : // Wait until ready to write - case SSL_ERROR_WANT_CONNECT : // Operation did not complete. Redo if the connection has been established + case SSL_ERROR_WANT_READ : _FALLTHROUGH; // Wait until ready to read + case SSL_ERROR_WANT_WRITE : _FALLTHROUGH; // Wait until ready to write + case SSL_ERROR_WANT_CONNECT : _FALLTHROUGH; // Operation did not complete. Redo if the connection has been established case SSL_ERROR_WANT_ACCEPT : // Idem switch (select(fd + 1, &fds, &fds, nullptr, &tv)) { default : if (!FD_ISSET(fd, &fds)) { @@ -1002,11 +1016,11 @@ void SecureSocketPort::Handler::Update() { ASSERT(_handShaking != ERROR); } break; - case SSL_ERROR_ZERO_RETURN : - case SSL_ERROR_WANT_ASYNC : - case SSL_ERROR_WANT_ASYNC_JOB : - case SSL_ERROR_WANT_CLIENT_HELLO_CB : - case SSL_ERROR_SSL : // Unrecoverable error + case SSL_ERROR_ZERO_RETURN : _FALLTHROUGH; + case SSL_ERROR_WANT_ASYNC : _FALLTHROUGH; + case SSL_ERROR_WANT_ASYNC_JOB : _FALLTHROUGH; + case SSL_ERROR_WANT_CLIENT_HELLO_CB : _FALLTHROUGH; + case SSL_ERROR_SSL : _FALLTHROUGH; // Unrecoverable error default : // Error _handShaking = ERROR; } @@ -1035,3 +1049,9 @@ void SecureSocketPort::Handler::Update() { } } // namespace Thunder::Crypto + +#ifdef _FALLTHROUGH +#undef _FALLTHROUGH; +#endif + + From 02b81f76ab172418d2d46851381558ccf4ae9740 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 3 Jan 2025 12:21:06 +0000 Subject: [PATCH 55/76] [Source/cryptalgo/SecuresocketPort] : amend 'e5a90' --- Source/cryptalgo/SecureSocketPort.cpp | 174 ++++++++++++++------------ Source/cryptalgo/SecureSocketPort.h | 12 +- Tests/unit/core/test_websocket.cpp | 69 +++++----- 3 files changed, 131 insertions(+), 124 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index b109c8347..9ac6296cb 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -90,7 +90,7 @@ public : operator const EVP_PKEY* () const { - return Key::operator const EVP_PKEY* (); + return static_cast(Key::operator const void* ()); } }; @@ -103,20 +103,57 @@ class X509CertificateStore : public CertificateStore { operator X509_STORE* () const { - VARIABLE_IS_NOT_USED X509_STORE* store = CertificateStore::operator X509_STORE* (); + const std::vector* list { static_cast*>(CertificateStore::operator const void* ()) }; - ASSERT(store != nullptr); + X509_STORE* store{ list != nullptr ? X509_STORE_new() : nullptr }; - return store; + if (store != nullptr) { + for (auto head = list->begin(), index = head, tail = list->end(); index != tail; index++) { + // Do not X509_free + X509* certificate = const_cast(X509Certificate{ *index }.operator const X509*()); + + if ( certificate == nullptr + || X509_STORE_add_cert(store, certificate) != 1 + ) { + X509_STORE_free(store); + + store = nullptr; + + break; + } + } + } + + return (store); } operator STACK_OF(X509_NAME)* () const { - VARIABLE_IS_NOT_USED STACK_OF(X509_NAME)* store = CertificateStore::operator STACK_OF(X509_NAME)* (); + const std::vector* list { static_cast*>(CertificateStore::operator const void* ()) }; + + STACK_OF(X509_NAME)* store{ list != nullptr ? sk_X509_NAME_new_null() : nullptr }; + + if (store != nullptr) { + for (auto head = list->begin(), index = head, tail = list->end(); index != tail; index++) { + // Do not X509_free + X509* certificate = const_cast(X509Certificate{ *index }.operator const X509*()); + + // name must not be freed + X509_NAME* name = certificate != nullptr ? X509_get_subject_name(certificate) : nullptr; + + if ( certificate == nullptr + || sk_X509_NAME_push(store, name) == 0 + ) { + sk_X509_NAME_free(store); + + store = nullptr; - ASSERT(store != nullptr); + break; + } + } + } - return store; + return (store); } }; @@ -327,21 +364,21 @@ bool Certificate::ValidHostname(const std::string& expectedHostname) const Certificate::operator const void* () const { if (_certificate != nullptr) { - int result = X509_up_ref(static_cast(_certificate)); + VARIABLE_IS_NOT_USED int result = X509_up_ref(static_cast(_certificate)); ASSERT(result == 1); } - return _certificate; + return (_certificate); } // ----------------------------------------------------------------------------- // class Key // ----------------------------------------------------------------------------- -Key::Key(const EVP_PKEY* key) - : _key{ const_cast(key) } +Key::Key(const void* key) + : _key{ const_cast(key) } { if (_key != nullptr) { - VARIABLE_IS_NOT_USED int result = EVP_PKEY_up_ref(_key); + VARIABLE_IS_NOT_USED int result = EVP_PKEY_up_ref(static_cast(_key)); ASSERT(result == 1); } } @@ -356,7 +393,8 @@ Key::Key(const Key& key) : _key{ key._key } { if (_key != nullptr) { - EVP_PKEY_up_ref(_key); + VARIABLE_IS_NOT_USED int result = EVP_PKEY_up_ref(static_cast(_key)); + ASSERT(result == 1); } } @@ -374,7 +412,7 @@ Key::Key(const string& fileName) extern "C" { -int PasswdCallback(char* buffer, int size, int rw, void* password) +int PasswdCallback(char* buffer, int size, VARIABLE_IS_NOT_USED int rw, void* password) { // if rw == 0 then request to supply passphrase // if rw == 1, something else, possibly verify the supplied passphrase in a second prompt @@ -408,13 +446,13 @@ Key::Key(const string& fileName, const string& password) Key::~Key() { if (_key != nullptr) { - EVP_PKEY_free(_key); + EVP_PKEY_free(static_cast(_key)); } } -Key::operator const EVP_PKEY* () const +Key::operator const void* () const { - return _key; + return (_key); } // ----------------------------------------------------------------------------- @@ -544,55 +582,9 @@ uint32_t CertificateStore::CreateDefaultStore() return (result); } -CertificateStore::operator X509_STORE* () const +CertificateStore::operator const void* () const { - X509_STORE* store { X509_STORE_new() }; - - if (store != nullptr) { - for (auto head = _list.begin(), index = head, tail = _list.end(); index != tail; index++) { - // Do not X509_free - X509* certificate = const_cast(X509Certificate{ *index }.operator const X509*()); - - if ( certificate == nullptr - || X509_STORE_add_cert(store, certificate) != 1 - ) { - X509_STORE_free(store); - - store = nullptr; - - break; - } - } - } - - return (store); -} - -CertificateStore::operator STACK_OF(X509_NAME)* () const -{ - STACK_OF(X509_NAME)* store = sk_X509_NAME_new_null(); - - if (store != nullptr) { - for (auto head = _list.begin(), index = head, tail = _list.end(); index != tail; index++) { - // Do not X509_free - X509* certificate = const_cast(X509Certificate{ *index }.operator const X509*()); - - // name must not be freed - X509_NAME* name = certificate != nullptr ? X509_get_subject_name(certificate) : nullptr; - - if ( certificate == nullptr - || sk_X509_NAME_push(store, name) == 0 - ) { - sk_X509_NAME_free(store); - - store = nullptr; - - break; - } - } - } - - return (store); + return &_list; } bool CertificateStore::IsDefaultStore() const @@ -693,8 +685,13 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) using common_time_t = std::common_type::type; using common_sec_t = std::common_type::type; - static_assert(static_cast(std::numeric_limits::max()) > static_cast(std::numeric_limits::max())); - static_assert(static_cast(std::numeric_limits::max()) > static_cast(std::numeric_limits::max())); +#ifdef _STATIC_CAST_TIME + static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); + static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); +#else + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); +#endif struct timeval tv { static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) @@ -740,8 +737,13 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t using common_time_t = std::common_type::type; using common_sec_t = std::common_type::type; - static_assert(static_cast(std::numeric_limits::max()) > static_cast(std::numeric_limits::max())); - static_assert(static_cast(std::numeric_limits::max()) > static_cast(std::numeric_limits::max())); +#ifdef _STATIC_CAST_TIME + static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); + static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); +#else + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); +#endif struct timeval tv { static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) @@ -967,13 +969,25 @@ void SecureSocketPort::Handler::Update() { ASSERT(fd >= 0); - fd_set fds; - FD_ZERO(&fds); - FD_SET(fd, &fds); + fd_set rfds, wfds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(fd, &rfds); + FD_SET(fd, &wfds); + + using common_time_t = std::common_type::type; + using common_sec_t = std::common_type::type; +#ifdef _STATIC_CAST_TIME + static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); + static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); +#else + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); +#endif struct timeval tv { - _waitTime / (Core::Time::MilliSecondsPerSecond) - , (_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond) + static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) + , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) }; do { @@ -985,9 +999,9 @@ void SecureSocketPort::Handler::Update() { case SSL_ERROR_SYSCALL : // Some syscall failed if (errno != EAGAIN) { // Last error without removing it from the queue +#ifdef VERBOSE unsigned long result = 0; -#ifdef VERBOSE if ((result = ERR_peek_last_error_all(&file, &line, &func, &data, &flag)) != 0) { int lib = ERR_GET_LIB(result); int reason = ERR_GET_REASON(result); @@ -1001,8 +1015,10 @@ void SecureSocketPort::Handler::Update() { case SSL_ERROR_WANT_WRITE : _FALLTHROUGH; // Wait until ready to write case SSL_ERROR_WANT_CONNECT : _FALLTHROUGH; // Operation did not complete. Redo if the connection has been established case SSL_ERROR_WANT_ACCEPT : // Idem - switch (select(fd + 1, &fds, &fds, nullptr, &tv)) { - default : if (!FD_ISSET(fd, &fds)) { + switch (select(fd + 1, &rfds, &wfds, nullptr, &tv)) { + default : if ( !FD_ISSET(fd, &rfds) + && !FD_ISSET(fd, &wfds) + ) { // Only file descriptors in the set should be set _handShaking = ERROR; ASSERT(_handShaking != ERROR); @@ -1051,7 +1067,5 @@ void SecureSocketPort::Handler::Update() { } // namespace Thunder::Crypto #ifdef _FALLTHROUGH -#undef _FALLTHROUGH; +#undef _FALLTHROUGH #endif - - diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 5cd426cf2..535b1e934 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -21,8 +21,6 @@ #include -#include - #include "Module.h" namespace Thunder { @@ -67,11 +65,11 @@ namespace Crypto { ~Key(); protected: - Key(const EVP_PKEY* key); - operator const EVP_PKEY* () const; + Key(const void* key); + operator const void* () const; private: - mutable EVP_PKEY* _key; + mutable void* _key; }; class EXTERNAL CertificateStore { @@ -92,9 +90,7 @@ namespace Crypto { bool IsDefaultStore() const; protected: - - operator X509_STORE* () const; - operator STACK_OF(X509_NAME)* () const; + operator const void* () const; private: uint32_t CreateDefaultStore(); diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 42aa8b13d..f7fa10a62 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -314,7 +314,7 @@ namespace Core { //#ifdef _VERBOSE std::cout << " |--> dataFrame (" << count << " ) = "; - for (int32_t index = 0; index < count; index++) { + for (size_t index = 0; index < count; index++) { std::cout << std::hex << static_cast(dataFrame[index]) << " "; } std::cout << "\n"; @@ -439,10 +439,11 @@ namespace Core { ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostClient.key")); uint32_t result = Certificate(certificate, privateKey); - + ASSERT(result == ::Thunder::Core::ERROR_NONE); // Validate custom (sefl signed) certificates /* uint32_t */ result = Callback(&_validator); + ASSERT(result == ::Thunder::Core::ERROR_NONE); } CustomSecureSocketStream( @@ -460,9 +461,11 @@ namespace Core { ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostClient.key")); uint32_t result = Certificate(certificate, privateKey); + ASSERT(result == ::Thunder::Core::ERROR_NONE); // Validate custom (self signed) client certificates /* uint32_t */ result = Callback(&_validator); + ASSERT(result == ::Thunder::Core::ERROR_NONE); } ~CustomSecureSocketStream() @@ -579,9 +582,6 @@ namespace Core { result = store.Add(certificateCA); ASSERT(result == ::Thunder::Core::ERROR_NONE); - result = store.Add(certificate); - result = store.Remove(certificate); - result = CustomStore(store); ASSERT(result == ::Thunder::Core::ERROR_NONE); @@ -612,9 +612,6 @@ namespace Core { result = store.Add(certificateCA); ASSERT(result == ::Thunder::Core::ERROR_NONE); - result = store.Add(certificate); - result = store.Remove(certificate); - result = CustomStore(store); ASSERT(result == ::Thunder::Core::ERROR_NONE); @@ -681,8 +678,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking const TCHAR remoteHostName[] {"127.0.0.1"}; @@ -725,7 +722,7 @@ namespace Core { constexpr uint16_t sendBufferSize {1024}; constexpr uint16_t receiveBufferSize {1024}; - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, VARIABLE_IS_NOT_USED maxInitTime = 2000; constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { @@ -733,8 +730,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -799,7 +796,7 @@ namespace Core { constexpr uint16_t sendBufferSize {1024}; constexpr uint16_t receiveBufferSize {1024}; - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, VARIABLE_IS_NOT_USED maxInitTime = 2000; constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { @@ -807,8 +804,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -882,7 +879,7 @@ namespace Core { constexpr uint16_t sendBufferSize {1024}; constexpr uint16_t receiveBufferSize {1024}; - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, VARIABLE_IS_NOT_USED maxInitTime = 2000; constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { @@ -890,8 +887,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -965,7 +962,7 @@ namespace Core { constexpr uint16_t sendBufferSize {1024}; constexpr uint16_t receiveBufferSize {1024}; - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, VARIABLE_IS_NOT_USED maxInitTime = 2000; constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { @@ -973,8 +970,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1042,7 +1039,7 @@ namespace Core { constexpr uint16_t sendBufferSize {1024}; constexpr uint16_t receiveBufferSize {1024}; - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, VARIABLE_IS_NOT_USED maxInitTime = 2000; constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { @@ -1050,8 +1047,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1145,8 +1142,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1294,8 +1291,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking const TCHAR remoteHostName[] {"127.0.0.1"}; @@ -1346,8 +1343,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1459,8 +1456,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking const TCHAR remoteHostName[] {"127.0.0.1"}; @@ -1502,7 +1499,7 @@ namespace Core { constexpr uint16_t sendBufferSize {4096}; constexpr uint16_t receiveBufferSize {4096}; - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, VARIABLE_IS_NOT_USED maxInitTime = 2000; constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { @@ -1510,8 +1507,8 @@ namespace Core { const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) - constexpr bool masking {true}; // Flag set by client to enable masking + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; From cc4c1e894d36254f6d9751bbb4a010c5893d0e59 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:19:22 +0000 Subject: [PATCH 56/76] [Tests] : Reduce compiler warnings --- Tests/cyclic-buffer/main.cpp | 2 +- Tests/cyclic-buffer/process.h | 8 ++++---- Tests/unit/tests/test_iptestmanager.cpp | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/cyclic-buffer/main.cpp b/Tests/cyclic-buffer/main.cpp index 564cccf36..bc6a40cfb 100644 --- a/Tests/cyclic-buffer/main.cpp +++ b/Tests/cyclic-buffer/main.cpp @@ -40,7 +40,7 @@ } \ RESULT = future.get(); -int main(int argc, char* argv[]) +int main(VARIABLE_IS_NOT_USED int argc, VARIABLE_IS_NOT_USED char* argv[]) { using namespace WPEFramework::Core; diff --git a/Tests/cyclic-buffer/process.h b/Tests/cyclic-buffer/process.h index 68dd04bd1..b3e307e01 100644 --- a/Tests/cyclic-buffer/process.h +++ b/Tests/cyclic-buffer/process.h @@ -319,7 +319,7 @@ public : fileName , Core::File::USER_READ // Enable read permissions on the underlying file for other users | Core::File::USER_WRITE // Enable write permission on the underlying file - | (requiredSharedBufferSize ? Core::File::CREATE : 0) // Create a new underlying memory mapped file + | (requiredSharedBufferSize ? static_cast::type>(Core::File::CREATE) : 0) // Create a new underlying memory mapped file | Core::File::SHAREABLE // Allow other processes to access the content of the file , requiredSharedBufferSize // Requested size , false // Overwrite unread data @@ -826,7 +826,7 @@ private : } case 0 : // Child { - const struct timespec timeout = {.tv_sec = _setupTime, .tv_nsec = 0}; + const struct timespec timeout{ _setupTime, 0}; std::array flags = {status::uninitialized, status::initialized}; @@ -924,7 +924,7 @@ private : TRACE_L1(_T("Parent %ld has woken up %ld child(ren)."), gettid(), retval); - const struct timespec timeout = {.tv_sec = _setupTime, .tv_nsec = 0}; + const struct timespec timeout{_setupTime, 0}; flags = {status::initialized, status::ready}; @@ -1015,8 +1015,8 @@ private : std::array _childUsersSet; std::array _parentUsersSet; - uint32_t _runTime; // Milliseconds uint32_t _setupTime; // Seconds + uint32_t _runTime; // Milliseconds uint8_t _numReservedBlocks; }; diff --git a/Tests/unit/tests/test_iptestmanager.cpp b/Tests/unit/tests/test_iptestmanager.cpp index f51cc5929..465dded7a 100644 --- a/Tests/unit/tests/test_iptestmanager.cpp +++ b/Tests/unit/tests/test_iptestmanager.cpp @@ -36,7 +36,7 @@ namespace Core { // Do not change the signal handshake value unless you use a timed loop and repeatedly check. // Wait may return early with an error if it expect a different value. - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 0; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { @@ -98,7 +98,7 @@ namespace Core { // Do not change the signal handshake value unless you use a timed loop and repeatedly check. // Wait may return early with an error if it expect a different value. - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 2; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { @@ -135,7 +135,7 @@ namespace Core { // Do not change the signal handshake value unless you use a timed loop and repeatedly check. // Wait may return early with an error if it expect a different value. - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 15; // Approximately 150% maxWaitTimePeriod IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { From 1ef1e1c8a8b5b6b0a5c9da6d871f41ea1146b307 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:41:27 +0000 Subject: [PATCH 57/76] [Tests/unit/core] : Reduce build warnings in 'Release' --- Source/cryptalgo/SecureSocketPort.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 9ac6296cb..5c15f6e72 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -683,8 +683,8 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) FD_ZERO(&fds); FD_SET(fd, &fds); - using common_time_t = std::common_type::type; - using common_sec_t = std::common_type::type; + using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; + using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; #ifdef _STATIC_CAST_TIME static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); @@ -735,8 +735,8 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t FD_ZERO(&fds); FD_SET(fd, &fds); - using common_time_t = std::common_type::type; - using common_sec_t = std::common_type::type; + using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; + using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; #ifdef _STATIC_CAST_TIME static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); @@ -975,14 +975,14 @@ void SecureSocketPort::Handler::Update() { FD_SET(fd, &rfds); FD_SET(fd, &wfds); - using common_time_t = std::common_type::type; - using common_sec_t = std::common_type::type; + using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; + using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; #ifdef _STATIC_CAST_TIME - static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); - static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); + static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); + static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); #else - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); #endif struct timeval tv { From be02c5ae53e9cca3abe809cac4419dae7fc050ae Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:18:07 +0000 Subject: [PATCH 58/76] [Source/cryptalgo/SecureSocketPort] : remove 'static_assert' --- Source/cryptalgo/SecureSocketPort.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 5c15f6e72..bf20daec0 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -685,13 +685,8 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; -#ifdef _STATIC_CAST_TIME - static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); - static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); -#else ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); -#endif struct timeval tv { static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) @@ -737,13 +732,8 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; -#ifdef _STATIC_CAST_TIME - static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); - static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); -#else ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); -#endif struct timeval tv { static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) @@ -977,13 +967,8 @@ void SecureSocketPort::Handler::Update() { using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; -#ifdef _STATIC_CAST_TIME - static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); - static_assert(static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max()), "Possible narrowing"); -#else ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); -#endif struct timeval tv { static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) From d2471050c0454c1f9e5de5bc2bac3418cf67c283 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:50:29 +0000 Subject: [PATCH 59/76] [Source/cryptalgo/SecureSocketPort] : refactor 'struct timeval ASSERT' --- Source/cryptalgo/SecureSocketPort.cpp | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index bf20daec0..7090d8107 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -683,11 +683,6 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) FD_ZERO(&fds); FD_SET(fd, &fds); - using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; - using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); - struct timeval tv { static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) @@ -730,11 +725,6 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t FD_ZERO(&fds); FD_SET(fd, &fds); - using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; - using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); - struct timeval tv { static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) @@ -765,6 +755,12 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t } uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { + // Users of tsruct timeval should not exhibit overflow + using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; + using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); + ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); + _waitTime = waitTime; return (Core::SocketPort::Open(waitTime)); @@ -965,11 +961,6 @@ void SecureSocketPort::Handler::Update() { FD_SET(fd, &rfds); FD_SET(fd, &wfds); - using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; - using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); - struct timeval tv { static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) From 6478051b26a0212f1bc6e4bbfa4fc9f8af2a379d Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:58:47 +0000 Subject: [PATCH 60/76] [Source/core/Portabaility / Source/cryptalgo/SecureSocketPort] : Use 'DISABLE_WARNING_IMPLICIT_FALLTHROUGH' --- Source/core/Portability.h | 3 ++ Source/cryptalgo/SecureSocketPort.cpp | 54 ++++++++++++++------------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/Source/core/Portability.h b/Source/core/Portability.h index 9708cf97c..f690471cb 100644 --- a/Source/core/Portability.h +++ b/Source/core/Portability.h @@ -195,6 +195,7 @@ #define DISABLE_WARNING_MAYBE_UNINITIALIZED #define DISABLE_WARNING_FREE_NONHEAP_OBJECT #define DISABLE_WARNING_ARRAY_BOUNDS +#define DISABLE_WARNING_IMPLICIT_FALLTHROUGH #else #define DISABLE_WARNING_CONDITIONAL_EXPRESSION_IS_CONSTANT @@ -227,6 +228,8 @@ #define DISABLE_WARNING_MAYBE_UNINITIALIZED PUSH_WARNING_ARG_("-Wmaybe-uninitialized") #define DISABLE_WARNING_FREE_NONHEAP_OBJECT PUSH_WARNING_ARG_("-Wfree-nonheap-object") #define DISABLE_WARNING_ARRAY_BOUNDS PUSH_WARNING_ARG_("-Warray-bounds") +#define DISABLE_WARNING_IMPLICIT_FALLTHROUGH PUSH_WARNING_ARG_("-Wimplicit-fallthrough") + #endif #if !(defined(__clang__)) && (__GNUC__ >= 4) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 7090d8107..42107236f 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -29,10 +29,6 @@ #include -#ifndef _FALLTHROUGH -#define _FALLTHROUGH while(false){}; -#endif - #ifndef __WINDOWS__ namespace { @@ -700,11 +696,13 @@ int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) case SSL_READING : // More data to be read to complete result = (select(fd + 1, nullptr, &fds, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? SSL_read(static_cast(_ssl), buffer, length) : -1; break; - case SSL_X509_LOOKUP : _FALLTHROUGH; // Callback should be called again, see SSL_CTX_set_client_cert_cb() - case SSL_RETRY_VERIFY : _FALLTHROUGH; // Callback should be called again, see SSL_set_retry_verify() - case SSL_ASYNC_PAUSED : _FALLTHROUGH; // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds - case SSL_ASYNC_NO_JOBS : _FALLTHROUGH; // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() - case SSL_CLIENT_HELLO_CB : _FALLTHROUGH; // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() + case SSL_X509_LOOKUP : // Callback should be called again, see SSL_CTX_set_client_cert_cb() +PUSH_WARNING(DISABLE_WARNING_IMPLICIT_FALLTHROUGH) + case SSL_RETRY_VERIFY : // Callback should be called again, see SSL_set_retry_verify() + case SSL_ASYNC_PAUSED : // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds + case SSL_ASYNC_NO_JOBS : // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() + case SSL_CLIENT_HELLO_CB : // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() +POP_WARNING() default : // Error not processed result = -1; } @@ -742,11 +740,13 @@ int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t case SSL_READING : // More data to be read to complete result = (select(fd + 1, nullptr, &fds, nullptr, &tv) > 0) && FD_ISSET(fd, &fds) ? SSL_write(static_cast(_ssl), buffer, length) : -1; break; - case SSL_X509_LOOKUP : _FALLTHROUGH; // Callback should be called again, see SSL_CTX_set_client_cert_cb() - case SSL_RETRY_VERIFY : _FALLTHROUGH; // Callback should be called again, see SSL_set_retry_verify() - case SSL_ASYNC_PAUSED : _FALLTHROUGH; // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds - case SSL_ASYNC_NO_JOBS : _FALLTHROUGH; // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() - case SSL_CLIENT_HELLO_CB : _FALLTHROUGH; // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() + case SSL_X509_LOOKUP : // Callback should be called again, see SSL_CTX_set_client_cert_cb() +PUSH_WARNING(DISABLE_WARNING_IMPLICIT_FALLTHROUGH) + case SSL_RETRY_VERIFY : // Callback should be called again, see SSL_set_retry_verify() + case SSL_ASYNC_PAUSED : // Asynchronous operation partially completed and paused, see SSL_get_all_async_fds + case SSL_ASYNC_NO_JOBS : // Asynchronous jobs could not be started, bone available, see ASYNC_init_thread() + case SSL_CLIENT_HELLO_CB : // Operation did not complete, callback has to be called again. see SSL_CTX_set_client_hello_cb() +POP_WARNING() default : // Error not processed result = -1; } @@ -922,6 +922,7 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() return result; } +//PUSH_WARNING(DISABLE_WARNING_IMPLICIT_FALLTHROUGH) void SecureSocketPort::Handler::Update() { if (IsOpen() == true) { ASSERT(_ssl != nullptr); @@ -973,6 +974,7 @@ void SecureSocketPort::Handler::Update() { case SSL_ERROR_NONE : _handShaking = CONNECTED; break; case SSL_ERROR_SYSCALL : // Some syscall failed +PUSH_WARNING(DISABLE_WARNING_IMPLICIT_FALLTHROUGH) if (errno != EAGAIN) { // Last error without removing it from the queue #ifdef VERBOSE @@ -987,9 +989,10 @@ void SecureSocketPort::Handler::Update() { #endif } // Fallthrough for select - case SSL_ERROR_WANT_READ : _FALLTHROUGH; // Wait until ready to read - case SSL_ERROR_WANT_WRITE : _FALLTHROUGH; // Wait until ready to write - case SSL_ERROR_WANT_CONNECT : _FALLTHROUGH; // Operation did not complete. Redo if the connection has been established + case SSL_ERROR_WANT_READ : // Wait until ready to read + case SSL_ERROR_WANT_WRITE : // Wait until ready to write + case SSL_ERROR_WANT_CONNECT : // Operation did not complete. Redo if the connection has been established +POP_WARNING() case SSL_ERROR_WANT_ACCEPT : // Idem switch (select(fd + 1, &rfds, &wfds, nullptr, &tv)) { default : if ( !FD_ISSET(fd, &rfds) @@ -1008,11 +1011,13 @@ void SecureSocketPort::Handler::Update() { ASSERT(_handShaking != ERROR); } break; - case SSL_ERROR_ZERO_RETURN : _FALLTHROUGH; - case SSL_ERROR_WANT_ASYNC : _FALLTHROUGH; - case SSL_ERROR_WANT_ASYNC_JOB : _FALLTHROUGH; - case SSL_ERROR_WANT_CLIENT_HELLO_CB : _FALLTHROUGH; - case SSL_ERROR_SSL : _FALLTHROUGH; // Unrecoverable error + case SSL_ERROR_ZERO_RETURN : +PUSH_WARNING(DISABLE_WARNING_IMPLICIT_FALLTHROUGH) + case SSL_ERROR_WANT_ASYNC : + case SSL_ERROR_WANT_ASYNC_JOB : + case SSL_ERROR_WANT_CLIENT_HELLO_CB : + case SSL_ERROR_SSL : // Unrecoverable error +POP_WARNING() default : // Error _handShaking = ERROR; } @@ -1037,11 +1042,8 @@ void SecureSocketPort::Handler::Update() { _parent.StateChange(); } } +//POP_WARNING() } } // namespace Thunder::Crypto - -#ifdef _FALLTHROUGH -#undef _FALLTHROUGH -#endif From bbf09bd8f4b6195fb5e04a00451988ef0435b9d2 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:01:15 +0000 Subject: [PATCH 61/76] [Source/cryptalgo/SecureSocketPort] : improve thread-safety 'CertificateStore' --- Source/cryptalgo/SecureSocketPort.cpp | 18 +++++++++++++++++- Source/cryptalgo/SecureSocketPort.h | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 42107236f..d05334af6 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -104,6 +104,8 @@ class X509CertificateStore : public CertificateStore { X509_STORE* store{ list != nullptr ? X509_STORE_new() : nullptr }; if (store != nullptr) { + CertificateStore::_lock.Lock(); + for (auto head = list->begin(), index = head, tail = list->end(); index != tail; index++) { // Do not X509_free X509* certificate = const_cast(X509Certificate{ *index }.operator const X509*()); @@ -118,6 +120,8 @@ class X509CertificateStore : public CertificateStore { break; } } + + CertificateStore::_lock.Unlock(); } return (store); @@ -130,6 +134,8 @@ class X509CertificateStore : public CertificateStore { STACK_OF(X509_NAME)* store{ list != nullptr ? sk_X509_NAME_new_null() : nullptr }; if (store != nullptr) { + CertificateStore::_lock.Lock(); + for (auto head = list->begin(), index = head, tail = list->end(); index != tail; index++) { // Do not X509_free X509* certificate = const_cast(X509Certificate{ *index }.operator const X509*()); @@ -147,6 +153,8 @@ class X509CertificateStore : public CertificateStore { break; } } + + CertificateStore::_lock.Unlock(); } return (store); @@ -478,6 +486,8 @@ uint32_t CertificateStore::Add(const Certificate& certificate) const X509* x509Certificate = X509Certificate{ certificate }; + _lock.Lock(); + if ( x509Certificate != nullptr && std::find_if(_list.begin(), _list.end(), [&x509Certificate](const X509Certificate item){ const X509* x509 = item; @@ -490,7 +500,9 @@ uint32_t CertificateStore::Add(const Certificate& certificate) result = Core::ERROR_NONE; } - return result; + _lock.Unlock(); + + return (result); } uint32_t CertificateStore::Remove(const Certificate& certificate) @@ -501,6 +513,8 @@ uint32_t CertificateStore::Remove(const Certificate& certificate) const X509* x509Certificate = X509Certificate{ certificate }; + _lock.Lock(); + if ((it = std::find_if(_list.begin(), _list.end(), [&x509Certificate](const X509Certificate item){ const X509* x509 = item; return X509_cmp(x509Certificate, x509) == 0; @@ -524,6 +538,8 @@ uint32_t CertificateStore::Remove(const Certificate& certificate) result = Core::ERROR_NONE; } + _lock.Unlock(); + return (result); } diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 535b1e934..6c3ef5dc1 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -92,6 +92,8 @@ namespace Crypto { protected: operator const void* () const; + mutable Core::CriticalSection _lock; + private: uint32_t CreateDefaultStore(); From cf8383a25ca899641fc3c3581c36bad785a203d1 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:40:34 +0100 Subject: [PATCH 62/76] Development/buildwarnings (#1821) * [Tests] : Reduce compiler warnings * [Tests/unit/core] : amend '0badc' --- Tests/unit/core/test_cyclicbuffer.cpp | 58 ++++++++++--------- .../core/test_cyclicbuffer_dataexchange.cpp | 2 +- Tests/unit/core/test_databuffer.cpp | 3 +- Tests/unit/core/test_dataelement.cpp | 2 +- Tests/unit/core/test_jsonparser.cpp | 2 +- Tests/unit/core/test_networkinfo.cpp | 2 +- Tests/unit/core/test_rpc.cpp | 2 +- Tests/unit/core/test_sharedbuffer.cpp | 4 +- Tests/unit/core/test_socketstreamjson.cpp | 2 +- Tests/unit/core/test_socketstreamtext.cpp | 2 +- Tests/unit/core/test_synchronous.cpp | 2 +- Tests/unit/core/test_systeminfo.cpp | 2 +- Tests/unit/core/test_threadpool.cpp | 39 ++++++++++--- Tests/unit/core/test_timer.cpp | 2 +- Tests/unit/core/test_weblinktext.cpp | 2 +- Tests/unit/core/test_websocketjson.cpp | 6 +- Tests/unit/core/test_websockettext.cpp | 6 +- Tests/unit/core/test_workerpool.cpp | 4 +- Tests/unit/core/test_xgetopt.cpp | 2 +- 19 files changed, 87 insertions(+), 57 deletions(-) diff --git a/Tests/unit/core/test_cyclicbuffer.cpp b/Tests/unit/core/test_cyclicbuffer.cpp index 5a31edaa3..fc9c93d97 100644 --- a/Tests/unit/core/test_cyclicbuffer.cpp +++ b/Tests/unit/core/test_cyclicbuffer.cpp @@ -776,16 +776,18 @@ namespace Core { EXPECT_EQ(buffer.Free(), static_cast(cyclicBufferSize - size)); // Try overwrite with size of Free() - uint8_t data3[buffer.Free()]; - memset(&data3, 'C', sizeof(data3)); - size = sizeof(data3) + 2; + uint32_t count = buffer.Free(); + uint8_t* data3 = new uint8_t[count]; + memset(data3, 'C', count); + size = count + 2; reserved = buffer.Reserve(size); if (reserved == size) { buffer.Write(reinterpret_cast(&size), 2); - buffer.Write(reinterpret_cast(&data3), sizeof(data3)); + buffer.Write(data3, count); } EXPECT_EQ(buffer.Used(), size); EXPECT_EQ(buffer.Free(), static_cast(cyclicBufferSize - size)); + delete[] data3; // Flush to start from beginning buffer.Flush(); @@ -832,7 +834,7 @@ namespace Core { static int ClonedProcessFunc(void* arg) { Data* data = static_cast(arg); uint32_t cyclicBufferSize = 10; - uint32_t shareableFlag = (data->shareable == true) ? ::Thunder::Core::File::Mode::SHAREABLE : 0; + uint32_t shareableFlag = (data->shareable == true) ? static_cast::type>(::Thunder::Core::File::Mode::SHAREABLE) : 0; ::Thunder::Core::CyclicBuffer buffer(data->bufferName.c_str(), ::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | ::Thunder::Core::File::Mode::USER_EXECUTE | @@ -874,7 +876,7 @@ namespace Core { SetSharePermissionsFromClonedProcess(bufferName, shareable); uint32_t cyclicBufferSize = 0; - uint32_t shareableFlag = (shareable == true) ? ::Thunder::Core::File::Mode::SHAREABLE : 0; + uint32_t shareableFlag = (shareable == true) ? static_cast::type>(::Thunder::Core::File::Mode::SHAREABLE) : 0; ::Thunder::Core::CyclicBuffer buffer(bufferName.c_str(), ::Thunder::Core::File::Mode::USER_READ | ::Thunder::Core::File::Mode::USER_WRITE | @@ -907,7 +909,7 @@ namespace Core { } void SetSharePermissionsFromForkedProcessAndVerify(bool shareable, bool usingDataElementFile = false, uint32_t offset = 0) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 1; const std::string bufferName {"cyclicbuffer01"}; @@ -920,7 +922,7 @@ namespace Core { | ::Thunder::Core::File::Mode::USER_EXECUTE | ::Thunder::Core::File::Mode::GROUP_READ | ::Thunder::Core::File::Mode::GROUP_WRITE - | (shareable ? ::Thunder::Core::File::Mode::SHAREABLE : 0) + | (shareable ? static_cast::type>(::Thunder::Core::File::Mode::SHAREABLE) : 0) ); const struct Data data{shareable, usingDataElementFile, mode, offset, bufferName.c_str()}; @@ -1095,7 +1097,7 @@ namespace Core { } TEST(Core_CyclicBuffer, WithoutOverwriteUsingForksReversed) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 1; const std::string bufferName {"cyclicbuffer02"}; @@ -1228,7 +1230,7 @@ namespace Core { } TEST(Core_CyclicBuffer, WithOverWriteUsingFork) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 1; const std::string bufferName {"cyclicbuffer03"}; @@ -1343,7 +1345,7 @@ namespace Core { } TEST(Core_CyclicBuffer, WithOverwriteUsingForksReversed) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 1; const std::string bufferName {"cyclicbuffer03"}; @@ -1371,7 +1373,7 @@ namespace Core { uint16_t size; EXPECT_EQ(buffer.Read(reinterpret_cast(&size), 2), 2); - uint8_t loadBuffer[size + 1]; + uint8_t* loadBuffer = new uint8_t[size + 1]; ASSERT_GE(size, 2); @@ -1379,6 +1381,8 @@ namespace Core { loadBuffer[result] = '\0'; EXPECT_EQ(result, size - 2); + delete[] loadBuffer; + ASSERT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); string data = "j"; @@ -1542,12 +1546,12 @@ namespace Core { TEST(Core_CyclicBuffer, DISABLED_LockUnLock_FromParentAndForks) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; - constexpr uint8_t maxRetries = 1; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; + VARIABLE_IS_NOT_USED constexpr uint8_t maxRetries = 1; std::string bufferName {"cyclicbuffer04"}; - IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + IPTestAdministrator::Callback callback_child = [&](VARIABLE_IS_NOT_USED IPTestAdministrator& testAdmin) { uint32_t cyclicBufferSize = 20; const uint32_t mode = @@ -1581,9 +1585,10 @@ namespace Core { uint32_t result = buffer.Write(reinterpret_cast(data.c_str()), data.size()); EXPECT_EQ(result, data.size()); EXPECT_EQ(buffer.Used(), data.size() * 2); - uint8_t loadBuffer[cyclicBufferSize + 1]; + uint8_t* loadBuffer = new uint8_t[cyclicBufferSize + 1]; result = buffer.Peek(loadBuffer, buffer.Used()); loadBuffer[result] = '\0'; + delete[] loadBuffer; // testAdmin.Sync("server wrote and peeked"); // testAdmin.Sync("client unlocked"); @@ -1592,7 +1597,7 @@ namespace Core { // testAdmin.Sync("client read"); }; - IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + IPTestAdministrator::Callback callback_parent = [&](VARIABLE_IS_NOT_USED IPTestAdministrator& testAdmin) { // a small delay so the child can be set up SleepMs(maxInitTime); @@ -1634,10 +1639,11 @@ namespace Core { EXPECT_EQ(buffer.LockPid(), 0u); // testAdmin.Sync("client unlocked"); - uint8_t loadBuffer[cyclicBufferSize + 1]; + uint8_t* loadBuffer = new uint8_t[cyclicBufferSize + 1]; result = buffer.Read(loadBuffer, 4); loadBuffer[result] = '\0'; EXPECT_STREQ((char*)loadBuffer, "jklm"); + delete[] loadBuffer; // testAdmin.Sync("client read"); @@ -1653,12 +1659,12 @@ namespace Core { //TODO: revisit these test cases after fixing the issues with cyclicbuffer lock/unlock sequence TEST(Core_CyclicBuffer, DISABLED_LockUnlock_FromParentAndForks_WithDataPresent) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; - constexpr uint8_t maxRetries = 1; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, VARIABLE_IS_NOT_USED maxInitTime = 2000; + VARIABLE_IS_NOT_USED constexpr uint8_t maxRetries = 1; std::string bufferName {"cyclicbuffer05"}; - IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + IPTestAdministrator::Callback callback_child = [&](VARIABLE_IS_NOT_USED IPTestAdministrator& testAdmin) { uint32_t cyclicBufferSize = 20; const uint32_t mode = @@ -1747,7 +1753,7 @@ namespace Core { EXPECT_EQ(buffer.LockPid(), 0u); }; - IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + IPTestAdministrator::Callback callback_parent = [&](VARIABLE_IS_NOT_USED IPTestAdministrator& testAdmin) { // testAdmin.Sync("setup server"); uint32_t cyclicBufferSize = 0; @@ -1847,12 +1853,12 @@ namespace Core { } TEST(Core_CyclicBuffer, DISABLED_LockUnlock_FromParentAndForks_UsingAlert) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; - constexpr uint8_t maxRetries = 1; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, maxInitTime = 2000; + VARIABLE_IS_NOT_USED constexpr uint8_t maxRetries = 1; std::string bufferName {"cyclicbuffer05"}; - IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + IPTestAdministrator::Callback callback_child = [&](VARIABLE_IS_NOT_USED IPTestAdministrator& testAdmin) { uint32_t cyclicBufferSize = 20; const uint32_t mode = @@ -1893,7 +1899,7 @@ namespace Core { EXPECT_EQ(buffer.LockPid(), 0u); }; - IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + IPTestAdministrator::Callback callback_parent = [&](VARIABLE_IS_NOT_USED IPTestAdministrator& testAdmin) { // a small delay so the child can be set up SleepMs(maxInitTime); diff --git a/Tests/unit/core/test_cyclicbuffer_dataexchange.cpp b/Tests/unit/core/test_cyclicbuffer_dataexchange.cpp index a365455ea..578ab9c29 100644 --- a/Tests/unit/core/test_cyclicbuffer_dataexchange.cpp +++ b/Tests/unit/core/test_cyclicbuffer_dataexchange.cpp @@ -33,7 +33,7 @@ namespace Tests { TEST(Core_CyclicBuffer, DataExchange) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, VARIABLE_IS_NOT_USED maxWaitTimeMs = 4000, VARIABLE_IS_NOT_USED maxInitTime = 2000; constexpr uint8_t maxRetries = 1; const std::string bufferName {"/tmp/SharedCyclicBuffer"}; diff --git a/Tests/unit/core/test_databuffer.cpp b/Tests/unit/core/test_databuffer.cpp index f0ecddf2e..4e36c9215 100644 --- a/Tests/unit/core/test_databuffer.cpp +++ b/Tests/unit/core/test_databuffer.cpp @@ -35,9 +35,10 @@ namespace Core { ::Thunder::Core::ScopedStorage storage; +PUSH_WARNING(DISABLE_WARNING_MAYBE_UNINITIALIZED) EXPECT_EQ(storage.Size(), blocksize); - EXPECT_NE(storage.Buffer(), nullptr); +POP_WARNING() } } // Core diff --git a/Tests/unit/core/test_dataelement.cpp b/Tests/unit/core/test_dataelement.cpp index c9f63f979..451276354 100644 --- a/Tests/unit/core/test_dataelement.cpp +++ b/Tests/unit/core/test_dataelement.cpp @@ -88,7 +88,7 @@ namespace Core { TEST(test_linkeddata, simple_linkeddata) { uint8_t arr[] = {10,20,30,40,50,60,70,80,90,100}; - uint8_t arr1[] ={}; + uint8_t arr1[sizeof(arr)/sizeof(arr[0])]; const uint64_t offset= 0; ::Thunder::Core::DataElement objt1(10,arr); ::Thunder::Core::LinkedDataElement ob1; diff --git a/Tests/unit/core/test_jsonparser.cpp b/Tests/unit/core/test_jsonparser.cpp index 68c72f5f9..6010fa88c 100644 --- a/Tests/unit/core/test_jsonparser.cpp +++ b/Tests/unit/core/test_jsonparser.cpp @@ -93,7 +93,7 @@ namespace Core { private: template - void InitValue(U* value, const std::string& name) + void InitValue(VARIABLE_IS_NOT_USED U* value, VARIABLE_IS_NOT_USED const std::string& name) { } diff --git a/Tests/unit/core/test_networkinfo.cpp b/Tests/unit/core/test_networkinfo.cpp index 128e33791..1d2283758 100644 --- a/Tests/unit/core/test_networkinfo.cpp +++ b/Tests/unit/core/test_networkinfo.cpp @@ -120,7 +120,7 @@ namespace Core { TEST(DISABLED_test_adapterobserver, simple_adapterobserver) { - ::Thunder::Core::AdapterObserver::INotification* callback; + ::Thunder::Core::AdapterObserver::INotification* callback{ nullptr }; ::Thunder::Core::AdapterObserver observer(callback); ::Thunder::Core::Singleton::Dispose(); diff --git a/Tests/unit/core/test_rpc.cpp b/Tests/unit/core/test_rpc.cpp index fdd703057..250e7168f 100644 --- a/Tests/unit/core/test_rpc.cpp +++ b/Tests/unit/core/test_rpc.cpp @@ -235,7 +235,7 @@ namespace Exchange { } private: - virtual void* Acquire(const string& className, const uint32_t interfaceId, const uint32_t versionId) + virtual void* Acquire(VARIABLE_IS_NOT_USED const string& className, const uint32_t interfaceId, VARIABLE_IS_NOT_USED const uint32_t versionId) { void* result = nullptr; diff --git a/Tests/unit/core/test_sharedbuffer.cpp b/Tests/unit/core/test_sharedbuffer.cpp index be461a0d2..cd98be5fd 100644 --- a/Tests/unit/core/test_sharedbuffer.cpp +++ b/Tests/unit/core/test_sharedbuffer.cpp @@ -111,7 +111,7 @@ namespace Core { constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 6, maxWaitTimeMs = 6000, maxInitTime = 2000; constexpr uint8_t maxRetries = 20; - constexpr uint16_t administrationSize = 64; + VARIABLE_IS_NOT_USED constexpr uint16_t administrationSize = 64; constexpr uint32_t bufferSize = 8 * 1024; const std::string bufferName {"testbuffer02"} ; @@ -147,7 +147,7 @@ namespace Core { uint16_t administrationSize = 64; uint32_t bufferSize = 8 * 1024; - uint32_t result; + VARIABLE_IS_NOT_USED uint32_t result; ::Thunder::Core::SharedBuffer buff01(bufferName.c_str(), ::Thunder::Core::File::USER_READ | diff --git a/Tests/unit/core/test_socketstreamjson.cpp b/Tests/unit/core/test_socketstreamjson.cpp index 57762452d..103942be9 100644 --- a/Tests/unit/core/test_socketstreamjson.cpp +++ b/Tests/unit/core/test_socketstreamjson.cpp @@ -167,7 +167,7 @@ namespace Core { } } - virtual void Send(::Thunder::Core::ProxyType<::Thunder::Core::JSON::IElement>& newElement) + virtual void Send(VARIABLE_IS_NOT_USED ::Thunder::Core::ProxyType<::Thunder::Core::JSON::IElement>& newElement) { } diff --git a/Tests/unit/core/test_socketstreamtext.cpp b/Tests/unit/core/test_socketstreamtext.cpp index a91165873..49be407bb 100644 --- a/Tests/unit/core/test_socketstreamtext.cpp +++ b/Tests/unit/core/test_socketstreamtext.cpp @@ -83,7 +83,7 @@ namespace Core { _dataReceived.clear(); } - virtual void Send(const string& text) + virtual void Send(VARIABLE_IS_NOT_USED const string& text) { } diff --git a/Tests/unit/core/test_synchronous.cpp b/Tests/unit/core/test_synchronous.cpp index 0126ebf62..78c423310 100644 --- a/Tests/unit/core/test_synchronous.cpp +++ b/Tests/unit/core/test_synchronous.cpp @@ -156,7 +156,7 @@ namespace Core { EXPECT_EQ(::Thunder::Core::SynchronousChannelType<::Thunder::Core::SocketPort>::Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } - virtual uint16_t Deserialize(const uint8_t* dataFrame, const uint16_t availableData) + virtual uint16_t Deserialize(VARIABLE_IS_NOT_USED const uint8_t* dataFrame, VARIABLE_IS_NOT_USED const uint16_t availableData) { return 1; } diff --git a/Tests/unit/core/test_systeminfo.cpp b/Tests/unit/core/test_systeminfo.cpp index 28a627f68..b5784e30c 100644 --- a/Tests/unit/core/test_systeminfo.cpp +++ b/Tests/unit/core/test_systeminfo.cpp @@ -538,7 +538,7 @@ namespace Core { loadFromSystem[2] = Round(loadFromSystem[2], 2); uint64_t* cpuLoadAvg = ::Thunder::Core::SystemInfo::Instance().GetCpuLoadAvg(); - double loadFromThunder[3]; + VARIABLE_IS_NOT_USED double loadFromThunder[3]; #ifndef __APPLE__ // Fixed point arithmetic EXPECT_DOUBLE_EQ(loadFromSystem[0], cpuLoadAvg[0] / (1 << SI_LOAD_SHIFT)); diff --git a/Tests/unit/core/test_threadpool.cpp b/Tests/unit/core/test_threadpool.cpp index 31d022ca3..723c563ce 100644 --- a/Tests/unit/core/test_threadpool.cpp +++ b/Tests/unit/core/test_threadpool.cpp @@ -741,7 +741,7 @@ namespace Core { uint8_t totalRuns = 0; - ::Thunder::Core::ThreadPool::Metadata info[threadCount]; + ::Thunder::Core::ThreadPool::Metadata* info = new ::Thunder::Core::ThreadPool::Metadata[threadCount]; std::vector jobsStrings; threadPool.Snapshot(threadCount, info, jobsStrings); @@ -749,6 +749,9 @@ namespace Core { for (uint8_t i = 0; i < threadCount; ++i) { totalRuns += info[i].Runs; } + + delete[] info; + EXPECT_EQ(totalRuns, static_cast(queueSize + additionalJobs)); threadPool.Stop(); @@ -824,7 +827,7 @@ namespace Core { uint8_t totalRuns = 0; - ::Thunder::Core::ThreadPool::Metadata info[threadCount]; + ::Thunder::Core::ThreadPool::Metadata* info = new ::Thunder::Core::ThreadPool::Metadata[threadCount]; std::vector jobsStrings; threadPool.Snapshot(threadCount, info, jobsStrings); @@ -832,6 +835,8 @@ namespace Core { for (uint8_t i = 0; i < threadCount; ++i) { totalRuns += info[i].Runs; } + delete[] info; + EXPECT_EQ(totalRuns, queueSize + additionalJobs - 1); threadPool.Stop(); @@ -881,7 +886,7 @@ namespace Core { uint8_t totalRuns = 0; - ::Thunder::Core::ThreadPool::Metadata info[threadCount]; + ::Thunder::Core::ThreadPool::Metadata* info = new ::Thunder::Core::ThreadPool::Metadata[threadCount]; std::vector jobsStrings; threadPool.Snapshot(threadCount, info, jobsStrings); @@ -889,6 +894,9 @@ namespace Core { for (uint8_t i = 0; i < threadCount; ++i) { totalRuns += info[i].Runs; } + + delete[] info; + EXPECT_EQ(totalRuns, queueSize + additionalJobs); threadPool.Stop(); @@ -1033,7 +1041,7 @@ namespace Core { uint8_t totalRuns = 0; - ::Thunder::Core::ThreadPool::Metadata info[threadCount]; + ::Thunder::Core::ThreadPool::Metadata* info = new ::Thunder::Core::ThreadPool::Metadata[threadCount]; std::vector jobsStrings; threadPool.Snapshot(threadCount, info, jobsStrings); @@ -1041,6 +1049,9 @@ namespace Core { for (uint8_t i = 0; i < threadCount; ++i) { totalRuns += info[i].Runs; } + + delete[] info; + EXPECT_EQ(totalRuns, queueSize + additionalJobs - cancelJobsCount); threadPool.Stop(); @@ -1149,7 +1160,7 @@ namespace Core { uint8_t totalRuns = 0; - ::Thunder::Core::ThreadPool::Metadata info[threadCount]; + ::Thunder::Core::ThreadPool::Metadata* info = new ::Thunder::Core::ThreadPool::Metadata[threadCount]; std::vector jobsStrings; threadPool.Snapshot(threadCount, info, jobsStrings); @@ -1157,6 +1168,9 @@ namespace Core { for (uint8_t i = 0; i < threadCount; ++i) { totalRuns += info[i].Runs; } + + delete[] info; + EXPECT_EQ(totalRuns, queueSize + additionalJobs - cancelJobsCount); threadPool.Stop(); @@ -1260,7 +1274,7 @@ namespace Core { uint8_t totalRuns = 0; - ::Thunder::Core::ThreadPool::Metadata info[threadCount]; + ::Thunder::Core::ThreadPool::Metadata* info = new ::Thunder::Core::ThreadPool::Metadata[threadCount]; std::vector jobsStrings; threadPool.Snapshot(threadCount, info, jobsStrings); @@ -1268,6 +1282,9 @@ namespace Core { for (uint8_t i = 0; i < threadCount; ++i) { totalRuns += info[i].Runs; } + + delete[] info; + EXPECT_EQ(totalRuns, (queueSize + additionalJobs - cancelJobsCount) * 2); threadPool.Stop(); @@ -1357,7 +1374,7 @@ namespace Core { uint8_t totalRuns = 0; - ::Thunder::Core::ThreadPool::Metadata info[threadCount]; + ::Thunder::Core::ThreadPool::Metadata* info = new ::Thunder::Core::ThreadPool::Metadata[threadCount]; std::vector jobsStrings; threadPool.Snapshot(threadCount, info, jobsStrings); @@ -1365,6 +1382,9 @@ namespace Core { for (uint8_t i = 0; i < threadCount; ++i) { totalRuns += info[i].Runs; } + + delete[] info; + EXPECT_EQ(totalRuns, (queueSize + additionalJobs) * 2); threadPool.Stop(); @@ -1420,7 +1440,7 @@ namespace Core { uint8_t totalRuns = 0; - ::Thunder::Core::ThreadPool::Metadata info[threadCount]; + ::Thunder::Core::ThreadPool::Metadata* info = new ::Thunder::Core::ThreadPool::Metadata[threadCount]; std::vector jobsStrings; threadPool.Snapshot(threadCount, info, jobsStrings); @@ -1428,6 +1448,9 @@ namespace Core { for (uint8_t i = 0; i < threadCount; ++i) { totalRuns += info[i].Runs; } + + delete[] info; + EXPECT_EQ(totalRuns, (queueSize + additionalJobs) * 2); threadPool.Stop(); diff --git a/Tests/unit/core/test_timer.cpp b/Tests/unit/core/test_timer.cpp index e40e45aa9..98079339c 100644 --- a/Tests/unit/core/test_timer.cpp +++ b/Tests/unit/core/test_timer.cpp @@ -43,7 +43,7 @@ namespace Core { } public: - uint64_t Timed(const uint64_t scheduledTime) + uint64_t Timed(VARIABLE_IS_NOT_USED const uint64_t scheduledTime) { constexpr uint32_t time = 100; // 0.1 second diff --git a/Tests/unit/core/test_weblinktext.cpp b/Tests/unit/core/test_weblinktext.cpp index 090d45f9f..3480c36d7 100644 --- a/Tests/unit/core/test_weblinktext.cpp +++ b/Tests/unit/core/test_weblinktext.cpp @@ -168,7 +168,7 @@ namespace Core { TEST(WebLink, Text) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, VARIABLE_IS_NOT_USED maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 1; const std::string connector {"127.0.0.1"}; diff --git a/Tests/unit/core/test_websocketjson.cpp b/Tests/unit/core/test_websocketjson.cpp index 3b83157ea..b7fa33921 100644 --- a/Tests/unit/core/test_websocketjson.cpp +++ b/Tests/unit/core/test_websocketjson.cpp @@ -124,7 +124,7 @@ namespace Core { this->Submit(jsonObject); } - virtual void Send(::Thunder::Core::ProxyType<::Thunder::Core::JSON::IElement>& jsonObject) + virtual void Send(VARIABLE_IS_NOT_USED ::Thunder::Core::ProxyType<::Thunder::Core::JSON::IElement>& jsonObject) { } @@ -182,7 +182,7 @@ namespace Core { EXPECT_EQ(_dataPending.Unlock(), ::Thunder::Core::ERROR_NONE); } - virtual void Send(::Thunder::Core::ProxyType<::Thunder::Core::JSON::IElement>& jsonObject) + virtual void Send(VARIABLE_IS_NOT_USED ::Thunder::Core::ProxyType<::Thunder::Core::JSON::IElement>& jsonObject) { } @@ -219,7 +219,7 @@ namespace Core { TEST(WebSocket, Json) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, VARIABLE_IS_NOT_USED maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 1; const std::string connector {"/tmp/wpewebsocketjson0"}; diff --git a/Tests/unit/core/test_websockettext.cpp b/Tests/unit/core/test_websockettext.cpp index 583f1b83e..9cd8436f2 100644 --- a/Tests/unit/core/test_websockettext.cpp +++ b/Tests/unit/core/test_websockettext.cpp @@ -68,7 +68,7 @@ namespace Core { Submit(text); } - virtual void Send(const string& text) + virtual void Send(VARIABLE_IS_NOT_USED const string& text) { } @@ -116,7 +116,7 @@ namespace Core { EXPECT_EQ(_dataPending.Unlock(), ::Thunder::Core::ERROR_NONE); } - virtual void Send(const string& text) + virtual void Send(VARIABLE_IS_NOT_USED const string& text) { } @@ -142,7 +142,7 @@ namespace Core { TEST(WebSocket, Text) { - constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; + constexpr uint32_t initHandshakeValue = 0, VARIABLE_IS_NOT_USED maxWaitTime = 4, maxWaitTimeMs = 4000, maxInitTime = 2000; constexpr uint8_t maxRetries = 1; const std::string connector {"/tmp/wpewebsockettext0"}; diff --git a/Tests/unit/core/test_workerpool.cpp b/Tests/unit/core/test_workerpool.cpp index b5e531e4c..0d5e2db19 100644 --- a/Tests/unit/core/test_workerpool.cpp +++ b/Tests/unit/core/test_workerpool.cpp @@ -260,10 +260,10 @@ namespace Core { const uint8_t MaxSize = 15; bool isPoolId = false; char id[MaxSize]; - sprintf(id, "%x", static_cast<::Thunder::Core::thread_id>(pthread_self())); + sprintf(id, "%lx", static_cast<::Thunder::Core::thread_id>(pthread_self())); for (uint8_t index = 0; index < _threadsCount + 2; index++) { char workerId[MaxSize]; - sprintf(workerId, "%x", static_cast<::Thunder::Core::thread_id>(::Thunder::Core::IWorkerPool::Instance().Id(index))); + sprintf(workerId, "%lx", static_cast<::Thunder::Core::thread_id>(::Thunder::Core::IWorkerPool::Instance().Id(index))); if (strcpy(workerId, id)) { isPoolId = true; diff --git a/Tests/unit/core/test_xgetopt.cpp b/Tests/unit/core/test_xgetopt.cpp index 2d702b1b6..1eae36334 100644 --- a/Tests/unit/core/test_xgetopt.cpp +++ b/Tests/unit/core/test_xgetopt.cpp @@ -47,7 +47,7 @@ namespace Core { } private: - virtual void Option(const TCHAR option, const TCHAR* argument) + virtual void Option(const TCHAR option, VARIABLE_IS_NOT_USED const TCHAR* argument) { switch (option) { case 'c': From 25d16c75acc35ba6282349b6254ca54feeec3797 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 7 Jan 2025 08:24:29 +0000 Subject: [PATCH 63/76] [Source/core/SecureSocketPort] : CWE-569 --- Source/cryptalgo/SecureSocketPort.cpp | 50 ++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index d05334af6..a168c3389 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -60,6 +60,10 @@ namespace Crypto { class X509Certificate : public Certificate { public: + X509Certificate() = delete; + X509Certificate& operator=(Certificate&&) = delete; + X509Certificate& operator=(const Certificate&) = delete; + X509Certificate(const Certificate& certificate) : Certificate{ certificate } {} @@ -76,6 +80,10 @@ class X509Certificate : public Certificate { class X509Key : public Key { public : + X509Key() = delete; + X509Key& operator=(Key&&) = delete; + X509Key& operator=(const Key&) = delete; + X509Key(const Key& key) : Key{ key} {} @@ -93,6 +101,10 @@ public : class X509CertificateStore : public CertificateStore { public: + X509CertificateStore() = delete; + X509CertificateStore& operator=(CertificateStore&&) = delete; + X509CertificateStore& operator=(const CertificateStore&) = delete; + X509CertificateStore(const CertificateStore& store) : CertificateStore{ store } {} @@ -770,12 +782,36 @@ POP_WARNING() return (result > 0 ? result : /* error */ -1); } +namespace +{ + template + constexpr typename std::enable_if::value, bool>::type IsNarrowing(V v, W w) + { + using common_t VARIABLE_IS_NOT_USED = typename std::common_type::type; + + return static_cast(v) > static_cast(w); + } + + template + constexpr typename std::enable_if::type IsNarrowing(VARIABLE_IS_NOT_USED T v, VARIABLE_IS_NOT_USED T w) + { + return false; + } +} + uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { - // Users of tsruct timeval should not exhibit overflow + // Users of struct timeval should not exhibit overflow + using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; - using common_sec_t VARIABLE_IS_NOT_USED = std::common_type::type; - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); - ASSERT(static_cast(std::numeric_limits::max()) >= static_cast(_waitTime)); + using common_seconds_t VARIABLE_IS_NOT_USED = std::common_type::type; + + ASSERT( !IsNarrowing(std::numeric_limits::max(), std::numeric_limits::max()) + && (static_cast(std::numeric_limits::max()) >= static_cast(waitTime)) + ); + + ASSERT( !IsNarrowing(std::numeric_limits::max(), std::numeric_limits::max()) + && (static_cast(std::numeric_limits::max()) >= static_cast(waitTime)) + ); _waitTime = waitTime; @@ -800,7 +836,7 @@ uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { } uint32_t SecureSocketPort::Handler::Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key) { - // Load server / client certificate and private key +// Load server / client certificate and private key uint32_t result = Core::ERROR_BAD_REQUEST; @@ -862,7 +898,7 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) { // This is callled for certificates with issues to allow a custom validation int result { verifyStatus }; - switch(verifyStatus) { + switch (verifyStatus) { case 0 : { X509* x509Cert = nullptr; int exDataIndex = -1; @@ -938,7 +974,6 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() return result; } -//PUSH_WARNING(DISABLE_WARNING_IMPLICIT_FALLTHROUGH) void SecureSocketPort::Handler::Update() { if (IsOpen() == true) { ASSERT(_ssl != nullptr); @@ -1058,7 +1093,6 @@ POP_WARNING() _parent.StateChange(); } } -//POP_WARNING() } From 4829f7228b5ad065a90f8ff59ef6e87b56d167f5 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:18:13 +0000 Subject: [PATCH 64/76] [Tests/unit/core] : Uncomment 'VERBOSE' flags --- Tests/unit/core/test_websocket.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index f7fa10a62..573f29240 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -312,13 +312,13 @@ namespace Core { /* void* */ memcpy(dataFrame, message.data(), count); -//#ifdef _VERBOSE +#ifdef _VERBOSE std::cout << " |--> dataFrame (" << count << " ) = "; for (size_t index = 0; index < count; index++) { std::cout << std::hex << static_cast(dataFrame[index]) << " "; } std::cout << "\n"; -//#endif +#endif if (count == message.size()) { /* iterator */ _post.erase(_post.begin()); @@ -342,13 +342,13 @@ namespace Core { if (receivedSize > 0) { _response.emplace_back(std::basic_string{ dataFrame, receivedSize }); -//#ifdef _VERBOSE +#ifdef _VERBOSE std::cout << " |--> dataFrame ( " << receivedSize << " ) = "; for (int32_t index = 0; index < receivedSize; index++) { std::cout << std::hex << static_cast(dataFrame[index]) << " "; } std::cout << "\n"; -//#endif +#endif } return receivedSize; @@ -409,15 +409,15 @@ namespace Core { Validator() = default; ~Validator() = default; - bool Validate(const ::Thunder::Crypto::Certificate& certificate) const override { + bool Validate(VARIABLE_IS_NOT_USED const ::Thunder::Crypto::Certificate& certificate) const override { // Print certificate properties -//#ifdef _VERBOSE +#ifdef _VERBOSE std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; std::cout << " |--> Issuer = " << certificate.Issuer() << "\n"; std::cout << " |--> Subject = " << certificate.Subject() << "\n"; std::cout << " |--> Valid from = " << certificate.ValidFrom().ToRFC1123() << "\n"; std::cout << " |--> Valid until = " << certificate.ValidTill().ToRFC1123() << "\n"; -//#endif +#endif return true; // Always accept } }; @@ -545,15 +545,15 @@ namespace Core { Validator() = default; ~Validator() = default; - bool Validate(const ::Thunder::Crypto::Certificate& certificate) const override { + bool Validate(VARIABLE_IS_NOT_USED const ::Thunder::Crypto::Certificate& certificate) const override { // Print certificate properties -//#ifdef _VERBOSE +#ifdef _VERBOSE std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; std::cout << " |--> Issuer = " << certificate.Issuer() << "\n"; std::cout << " |--> Subject = " << certificate.Subject() << "\n"; std::cout << " |--> Valid from = " << certificate.ValidFrom().ToRFC1123() << "\n"; std::cout << " |--> Valid until = " << certificate.ValidTill().ToRFC1123() << "\n"; -//#endif +#endif return true; // Always accept } }; From 2f2be2d1ecdb1bdb3f7feb681664a7c5b90347a3 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:19:35 +0000 Subject: [PATCH 65/76] [Source/cryptalgo/SecureSocketPort] : CWE-569 --- Source/cryptalgo/SecureSocketPort.cpp | 30 ++------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index a168c3389..cdae121db 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -782,36 +782,10 @@ POP_WARNING() return (result > 0 ? result : /* error */ -1); } -namespace -{ - template - constexpr typename std::enable_if::value, bool>::type IsNarrowing(V v, W w) - { - using common_t VARIABLE_IS_NOT_USED = typename std::common_type::type; - - return static_cast(v) > static_cast(w); - } - - template - constexpr typename std::enable_if::type IsNarrowing(VARIABLE_IS_NOT_USED T v, VARIABLE_IS_NOT_USED T w) - { - return false; - } -} - uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { // Users of struct timeval should not exhibit overflow - - using common_time_t VARIABLE_IS_NOT_USED = std::common_type::type; - using common_seconds_t VARIABLE_IS_NOT_USED = std::common_type::type; - - ASSERT( !IsNarrowing(std::numeric_limits::max(), std::numeric_limits::max()) - && (static_cast(std::numeric_limits::max()) >= static_cast(waitTime)) - ); - - ASSERT( !IsNarrowing(std::numeric_limits::max(), std::numeric_limits::max()) - && (static_cast(std::numeric_limits::max()) >= static_cast(waitTime)) - ); + // Opposed to coverity false positives event tags + ASSERT(waitTime != Core::infinite); _waitTime = waitTime; From da99b1e88e2938fe764ef0ff533f78c2d37adcfd Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:54:17 +0000 Subject: [PATCH 66/76] [Source/cryptalgo/SecureSocketPort / Tests/unit/core] : Move 'IValidate' to 'CertificateStore' --- Source/cryptalgo/SecureSocketPort.cpp | 4 ++-- Source/cryptalgo/SecureSocketPort.h | 19 +++++++++---------- Tests/unit/core/test_websocket.cpp | 4 ++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index cdae121db..f9f8246f3 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -877,7 +877,7 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) X509* x509Cert = nullptr; int exDataIndex = -1; SSL* ssl = nullptr; - SecureSocketPort::IValidate* validator = nullptr; + CertificateStore::IValidate* validator = nullptr; // Retrieve and call the registered callback @@ -885,7 +885,7 @@ int VerifyCallbackWrapper(int verifyStatus, X509_STORE_CTX* ctx) && (exDataIndex = SSL_get_ex_data_X509_STORE_CTX_idx()) != -1 && (ssl = static_cast(X509_STORE_CTX_get_ex_data(ctx, exDataIndex))) != nullptr && (exDataIndex = ApplicationData::Instance().Index(static_cast(ssl))) != -1 - && (validator = static_cast(SSL_get_ex_data(ssl, exDataIndex))) != nullptr + && (validator = static_cast(SSL_get_ex_data(ssl, exDataIndex))) != nullptr && (x509Cert = X509_STORE_CTX_get_current_cert(ctx)) != nullptr ) { X509_up_ref(x509Cert); diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 6c3ef5dc1..e08261e12 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -89,6 +89,12 @@ namespace Crypto { bool IsDefaultStore() const; + struct IValidate { + virtual ~IValidate() = default; + + virtual bool Validate(const Certificate& certificate) const = 0; + }; + protected: operator const void* () const; @@ -104,13 +110,6 @@ namespace Crypto { }; class EXTERNAL SecureSocketPort : public Core::IResource { - public: - struct IValidate { - virtual ~IValidate() = default; - - virtual bool Validate(const Certificate& certificate) const = 0; - }; - private: class EXTERNAL Handler : public Core::SocketPort { private: @@ -166,7 +165,7 @@ namespace Crypto { void StateChange() override { Update(); } - inline uint32_t Callback(IValidate* callback) { + inline uint32_t Callback(CertificateStore::IValidate* callback) { uint32_t result = Core::ERROR_ILLEGAL_STATE; Core::SocketPort::Lock(); @@ -193,7 +192,7 @@ namespace Crypto { SecureSocketPort& _parent; void* _context; void* _ssl; - IValidate* _callback; + CertificateStore::IValidate* _callback; mutable state _handShaking; mutable Crypto::Certificate _certificate; // (PEM formatted ccertificate (chain) mutable Crypto::Key _privateKey; // (PEM formatted) private key @@ -278,7 +277,7 @@ namespace Crypto { inline void Trigger() { _handler.Trigger(); } - inline uint32_t Callback(IValidate* callback) { + inline uint32_t Callback(Crypto::CertificateStore::IValidate* callback) { return (_handler.Callback(callback)); } inline uint32_t Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key) { diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 573f29240..13559849f 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -403,7 +403,7 @@ namespace Core { static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); // Validate client certificate - class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidate { + class Validator : public ::Thunder::Crypto::CertificateStore::IValidate { public: Validator() = default; @@ -539,7 +539,7 @@ namespace Core { static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); // Validat eclient certificate - class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidate { + class Validator : public ::Thunder::Crypto::CertificateStore::IValidate { public: Validator() = default; From 15722865708d6643be3a0f28b4752b02ebd7704b Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 9 Jan 2025 08:12:19 +0000 Subject: [PATCH 67/76] [Source/cryptalgo/SecureSocketPort / tests/unit/core] : Differentiate (compile-time) between 'client' and 'server' type sockets Compile-time differentiation allows for use case available signatures --- Source/core/SocketPort.cpp | 6 +- Source/cryptalgo/SecureSocketPort.cpp | 37 ++++++--- Source/cryptalgo/SecureSocketPort.h | 82 +++++++++++++------- Tests/unit/core/CMakeLists.txt | 105 -------------------------- Tests/unit/core/test_websocket.cpp | 18 ++--- 5 files changed, 92 insertions(+), 156 deletions(-) diff --git a/Source/core/SocketPort.cpp b/Source/core/SocketPort.cpp index c8e953396..4df78ea97 100644 --- a/Source/core/SocketPort.cpp +++ b/Source/core/SocketPort.cpp @@ -1144,11 +1144,11 @@ namespace Thunder { } /* virtual */ int32_t SocketPort::Read(uint8_t buffer[], const uint16_t length) const { - return (::recv(m_Socket, reinterpret_cast(buffer), length, 0)); + return (::recv(m_Socket, reinterpret_cast(buffer), length, MSG_NOSIGNAL)); } /* virtual */ int32_t SocketPort::Write(const uint8_t buffer[], const uint16_t length) { - return (::send(m_Socket, reinterpret_cast(buffer), length, 0)); + return (::send(m_Socket, reinterpret_cast(buffer), length, MSG_NOSIGNAL)); } void SocketPort::Write() @@ -1178,7 +1178,7 @@ namespace Thunder { sendSize = ::sendto(m_Socket, reinterpret_cast(&(m_SendBuffer[m_SendOffset])), - m_SendBytes - m_SendOffset, 0, + m_SendBytes - m_SendOffset, MSG_NOSIGNAL, static_cast(m_RemoteNode), m_RemoteNode.Size()); diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index f9f8246f3..6eff46ce6 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -616,12 +616,14 @@ bool CertificateStore::IsDefaultStore() const return _defaultStore; } -SecureSocketPort::Handler::~Handler() { +template +SecureSocketPortImproved::Handler::~Handler() { ASSERT(IsClosed() == true); Close(0); } -uint32_t SecureSocketPort::Handler::Initialize() { +template +uint32_t SecureSocketPortImproved::Handler::Initialize() { ASSERT(_context == nullptr); uint32_t success = Core::ERROR_NONE; @@ -694,7 +696,8 @@ uint32_t SecureSocketPort::Handler::Initialize() { return success; } -int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { +template +int32_t SecureSocketPortImproved::Handler::Read(uint8_t buffer[], const uint16_t length) const { ASSERT(_handShaking == CONNECTED); ASSERT(_ssl != nullptr); ASSERT(length > 0); @@ -738,7 +741,8 @@ POP_WARNING() return (result > 0 ? result : /* error */ -1); } -int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { +template +int32_t SecureSocketPortImproved::Handler::Write(const uint8_t buffer[], const uint16_t length) { ASSERT(_handShaking == CONNECTED); ASSERT(_ssl != nullptr); ASSERT(length > 0); @@ -782,7 +786,8 @@ POP_WARNING() return (result > 0 ? result : /* error */ -1); } -uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { +template +uint32_t SecureSocketPortImproved::Handler::Open(const uint32_t waitTime) { // Users of struct timeval should not exhibit overflow // Opposed to coverity false positives event tags ASSERT(waitTime != Core::infinite); @@ -792,7 +797,8 @@ uint32_t SecureSocketPort::Handler::Open(const uint32_t waitTime) { return (Core::SocketPort::Open(waitTime)); } -uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { +template +uint32_t SecureSocketPortImproved::Handler::Close(const uint32_t waitTime) { if (_ssl != nullptr) { SSL_shutdown(static_cast(_ssl)); SSL_free(static_cast(_ssl)); @@ -809,7 +815,8 @@ uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { return(Core::SocketPort::Close(waitTime)); } -uint32_t SecureSocketPort::Handler::Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key) { +template +uint32_t SecureSocketPortImproved::Handler::Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key) { // Load server / client certificate and private key uint32_t result = Core::ERROR_BAD_REQUEST; @@ -842,7 +849,8 @@ uint32_t SecureSocketPort::Handler::Certificate(const Crypto::Certificate& certi return (result); } -uint32_t SecureSocketPort::Handler::CustomStore(const CertificateStore& certStore) +template +uint32_t SecureSocketPortImproved::Handler::CustomStore(const CertificateStore& certStore) { static_assert(!std::has_virtual_destructor::value, "new placement without support for virtual base classes"); @@ -852,7 +860,8 @@ uint32_t SecureSocketPort::Handler::CustomStore(const CertificateStore& certStor return (Core::ERROR_NONE); } -void SecureSocketPort::Handler::ValidateHandShake() { +template +void SecureSocketPortImproved::Handler::ValidateHandShake() { ASSERT(_ssl != nullptr && _context != nullptr); // Internal (partial) validation result if no callback is set @@ -923,7 +932,8 @@ int PeerCertificateCallbackWrapper(VARIABLE_IS_NOT_USED X509_STORE_CTX* ctx, VAR return 0; // 0 - Failurre, 1 - OK } -uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() + template +uint32_t SecureSocketPortImproved::Handler::EnableClientCertificateRequest() { uint32_t result{Core::ERROR_NONE}; @@ -948,7 +958,8 @@ uint32_t SecureSocketPort::Handler::EnableClientCertificateRequest() return result; } -void SecureSocketPort::Handler::Update() { +template +void SecureSocketPortImproved::Handler::Update() { if (IsOpen() == true) { ASSERT(_ssl != nullptr); ASSERT(_context != nullptr); @@ -1068,6 +1079,10 @@ POP_WARNING() } } +// Explicit instantiation +template class SecureSocketPortImproved; +template class SecureSocketPortImproved; + } } // namespace Thunder::Crypto diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index e08261e12..4fd6a1ce0 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -109,7 +109,8 @@ namespace Crypto { const bool _defaultStore; }; - class EXTERNAL SecureSocketPort : public Core::IResource { + template + class EXTERNAL SecureSocketPortImproved : public Core::IResource { private: class EXTERNAL Handler : public Core::SocketPort { private: @@ -127,14 +128,14 @@ namespace Crypto { Handler& operator=(const Handler&) = delete; Handler& operator=(Handler&&) = delete; - template - Handler(SecureSocketPort& parent, bool isClientSocketType, bool requestCert, Args&&... args) + template + Handler(SecureSocketPortImproved& parent, bool requestCert, Args&&... args) : Core::SocketPort(std::forward(args)...) , _parent(parent) , _context(nullptr) , _ssl(nullptr) , _callback(nullptr) - , _handShaking{isClientSocketType ? CONNECTING : ACCEPTING} + , _handShaking{HANDLERTYPE::value ? CONNECTING : ACCEPTING} , _certificate{std::string{""}} , _privateKey{std::string{""}} , _requestCertificate{requestCert} @@ -189,7 +190,7 @@ namespace Crypto { uint32_t EnableClientCertificateRequest(); private: - SecureSocketPort& _parent; + SecureSocketPortImproved& _parent; void* _context; void* _ssl; CertificateStore::IValidate* _callback; @@ -202,33 +203,20 @@ namespace Crypto { }; public: - SecureSocketPort(SecureSocketPort&&) = delete; - SecureSocketPort(const SecureSocketPort&) = delete; - SecureSocketPort& operator=(const SecureSocketPort&) = delete; + SecureSocketPortImproved(SecureSocketPortImproved&&) = delete; + SecureSocketPortImproved(const SecureSocketPortImproved&) = delete; + SecureSocketPortImproved& operator=(const SecureSocketPortImproved&) = delete; - protected: - // Operational context - enum class context_t : uint8_t { - CLIENT_CONTEXT - , SERVER_CONTEXT - }; - - template - SecureSocketPort(context_t context, Args&&... args) - : SecureSocketPort(context, false, std::forward(args)...) + template ::type> + SecureSocketPortImproved(bool requestPeerCert, Args&&... args) + : _handler(*this, requestPeerCert, std::forward(args)...) {} - template - SecureSocketPort(context_t context, bool requestPeerCert, Args&&... args) - : _handler(*this, context == context_t::CLIENT_CONTEXT, requestPeerCert && context == context_t::SERVER_CONTEXT, std::forward(args)...) + template ::type> + SecureSocketPortImproved(Args&&... args) + : _handler(*this, false, std::forward(args)...) {} - - public: - template - SecureSocketPort(Args&&... args) - : SecureSocketPort(context_t::CLIENT_CONTEXT, std::forward(args)...) - {} - ~SecureSocketPort() override { + ~SecureSocketPortImproved() override { } public: @@ -313,5 +301,43 @@ namespace Crypto { private: Handler _handler; }; + + using SecureSocketPort = SecureSocketPortImproved<>; + + class SecureSocketPortClientType : public SecureSocketPortImproved + { + public: + SecureSocketPortClientType() = delete; + SecureSocketPortClientType(const SecureSocketPortClientType&) = delete; + SecureSocketPortClientType(SecureSocketPortClientType&&) = delete; + SecureSocketPortClientType& operator=(const SecureSocketPortClientType&) = delete; + SecureSocketPortClientType& operator=(SecureSocketPortClientType&) = delete; + + template + SecureSocketPortClientType(Args&&... args) + : SecureSocketPortImproved(std::forward(args)...) + {} + + ~SecureSocketPortClientType() + {} + }; + + class SecureSocketPortServerType : public SecureSocketPortImproved + { + public: + SecureSocketPortServerType() = delete; + SecureSocketPortServerType(const SecureSocketPortServerType&) = delete; + SecureSocketPortServerType(SecureSocketPortServerType&&) = delete; + SecureSocketPortServerType& operator=(const SecureSocketPortServerType&) = delete; + SecureSocketPortServerType& operator=(SecureSocketPortServerType&) = delete; + + template + SecureSocketPortServerType(bool requestPeerCert, Args&&... args) + : SecureSocketPortImproved(requestPeerCert, std::forward(args)...) + {} + ~SecureSocketPortServerType() + {} + }; + } } diff --git a/Tests/unit/core/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index b63807b14..fb5a22d3c 100644 --- a/Tests/unit/core/CMakeLists.txt +++ b/Tests/unit/core/CMakeLists.txt @@ -21,67 +21,7 @@ if(LINUX) # IPTestAdministrator only supported on LINUX platform add_executable(${TEST_RUNNER_NAME} ../IPTestAdministrator.cpp - test_cyclicbuffer.cpp - test_cyclicbuffer_dataexchange.cpp - test_databuffer.cpp - test_dataelement.cpp - test_dataelementfile.cpp - test_doorbell.cpp - test_enumerate.cpp - test_event.cpp - test_filesystem.cpp - test_frametype.cpp - test_hash.cpp - test_hex2strserialization.cpp - #test_ipc.cpp - test_ipcclient.cpp - test_iso639.cpp - test_iterator.cpp - test_jsonparser.cpp - test_keyvalue.cpp - test_library.cpp - test_lockablecontainer.cpp - test_measurementtype.cpp - test_memberavailability.cpp - test_message_dispatcher.cpp - test_messageException.cpp - test_networkinfo.cpp - test_nodeid.cpp - test_numbertype.cpp - test_optional.cpp - test_parser.cpp - test_portability.cpp - test_processinfo.cpp - test_queue.cpp - test_rangetype.cpp - test_readwritelock.cpp - test_rectangle.cpp - test_rpc.cpp - test_semaphore.cpp - test_sharedbuffer.cpp - test_singleton.cpp - test_socketstreamjson.cpp - test_socketstreamtext.cpp - test_statetrigger.cpp - test_stopwatch.cpp - test_synchronize.cpp - test_synchronous.cpp - test_systeminfo.cpp - test_textfragment.cpp - test_textreader.cpp - test_thread.cpp - test_threadpool.cpp - test_time.cpp - test_timer.cpp - test_tristate.cpp - #test_valuerecorder.cpp - test_weblinkjson.cpp - test_weblinktext.cpp test_websocket.cpp - test_websocketjson.cpp - test_websockettext.cpp - test_workerpool.cpp - test_xgetopt.cpp ) #[[ target_sources(${TEST_RUNNER_NAME} PRIVATE test_message_unit.cpp) @@ -91,51 +31,6 @@ target_link_libraries(${TEST_RUNNER_NAME} ]] else() add_executable(${TEST_RUNNER_NAME} - test_databuffer.cpp - test_dataelement.cpp - test_dataelementfile.cpp - test_enumerate.cpp - test_event.cpp - test_filesystem.cpp - test_frametype.cpp - test_hash.cpp - test_hex2strserialization.cpp - test_iso639.cpp - test_iterator.cpp - test_jsonparser.cpp - test_keyvalue.cpp - test_library.cpp - test_lockablecontainer.cpp - test_measurementtype.cpp - test_memberavailability.cpp - test_messageException.cpp - test_networkinfo.cpp - test_nodeid.cpp - test_numbertype.cpp - test_optional.cpp - test_parser.cpp - test_portability.cpp - test_processinfo.cpp - test_queue.cpp - test_rangetype.cpp - test_readwritelock.cpp - test_rectangle.cpp - test_semaphore.cpp - test_singleton.cpp - test_statetrigger.cpp - test_stopwatch.cpp - test_synchronize.cpp - test_systeminfo.cpp - test_textfragment.cpp - test_textreader.cpp - test_thread.cpp - test_threadpool.cpp - test_time.cpp - test_timer.cpp - test_tristate.cpp - #test_valuerecorder.cpp - test_workerpool.cpp - test_xgetopt.cpp ) endif() diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 13559849f..9d2ed4b62 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -397,7 +397,7 @@ namespace Core { std::vector> _response; // Receive message queue }; - class CustomSecureSocketStream : public ::Thunder::Crypto::SecureSocketPort { + class CustomSecureSocketStream : public ::Thunder::Crypto::SecureSocketPortClientType { private : static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); @@ -431,7 +431,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPortClientType(::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _validator{} { // Support client certificate request @@ -453,7 +453,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::CLIENT_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPortClientType(::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _validator{} { // Support client certificate request @@ -482,7 +482,7 @@ namespace Core { /* static */ constexpr char CustomSecureSocketStream::volatilePath[]; - class CustomSecureServerSocketStream : public ::Thunder::Crypto::SecureSocketPort { + class CustomSecureServerSocketStream : public ::Thunder::Crypto::SecureSocketPortServerType { private : static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); @@ -496,7 +496,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPortServerType(false, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) { // Server identification ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem")); @@ -513,7 +513,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPortServerType(false, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) { // Server identification ::Thunder::Crypto::Certificate certificate(std::string(volatilePath).append("localhostServer.pem")); @@ -533,7 +533,7 @@ namespace Core { /* static */ constexpr char CustomSecureServerSocketStream::volatilePath[]; - class CustomSecureServerSocketStreamClientValidation : public ::Thunder::Crypto::SecureSocketPort { + class CustomSecureServerSocketStreamClientValidation : public ::Thunder::Crypto::SecureSocketPortServerType { private : static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); @@ -567,7 +567,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, true, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPortServerType(true, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) , _validator{} { // Server identification @@ -597,7 +597,7 @@ namespace Core { , const uint16_t sendBufferSize , const uint16_t receiveBufferSize ) - : ::Thunder::Crypto::SecureSocketPort(::Thunder::Crypto::SecureSocketPort::context_t::SERVER_CONTEXT, true, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + : ::Thunder::Crypto::SecureSocketPortServerType(true, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) , _validator{} { // Server identification From 06a2e318b85bfa9680a51683a4639468a44d85f8 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 9 Jan 2025 09:16:07 +0000 Subject: [PATCH 68/76] --amend --- Source/cryptalgo/SecureSocketPort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 6eff46ce6..24be20efc 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -932,7 +932,7 @@ int PeerCertificateCallbackWrapper(VARIABLE_IS_NOT_USED X509_STORE_CTX* ctx, VAR return 0; // 0 - Failurre, 1 - OK } - template +template uint32_t SecureSocketPortImproved::Handler::EnableClientCertificateRequest() { uint32_t result{Core::ERROR_NONE}; From 48b55990845c1628a011ea5399509ff07be36350 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 9 Jan 2025 09:17:54 +0000 Subject: [PATCH 69/76] [Source/cryptalgo/SecureSocketPort] : disable default constructor --- Source/cryptalgo/SecureSocketPort.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 4fd6a1ce0..bbbffc71b 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -123,8 +123,9 @@ namespace Crypto { }; public: - Handler(Handler&&) = delete; + Handler() = delete; Handler(const Handler&) = delete; + Handler(Handler&&) = delete; Handler& operator=(const Handler&) = delete; Handler& operator=(Handler&&) = delete; From 2299da6662330ba86c1b4760de50fc7582a851a4 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 9 Jan 2025 10:05:46 +0000 Subject: [PATCH 70/76] [Source/cryptalgo/SecureSocket / Tests/unit/core] : Improve thread safety --- Source/cryptalgo/SecureSocketPort.h | 2 +- Tests/unit/core/test_websocket.cpp | 63 +++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index bbbffc71b..9f2010182 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -196,7 +196,7 @@ namespace Crypto { void* _ssl; CertificateStore::IValidate* _callback; mutable state _handShaking; - mutable Crypto::Certificate _certificate; // (PEM formatted ccertificate (chain) + mutable Crypto::Certificate _certificate; // (PEM formatted) ccertificate (chain) mutable Crypto::Key _privateKey; // (PEM formatted) private key const bool _requestCertificate; mutable uint32_t _waitTime; // Extracted from Open for use in I/O blocking operations diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 9d2ed4b62..342872570 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -137,13 +137,24 @@ namespace Core { , Args&&... args ) : ::Thunder::Web::WebSocketClientType(path, protocol, query, origin, binary, masking, /* */ std::forward(args)... /**/) + , _post{} + , _guard{} { } ~WebSocketClient() override = default; // Non-idle then data available to send - bool IsIdle() const override { return _post.size() == 0; } + bool IsIdle() const override + { + _guard.Lock(); + + bool result = _post.size() == 0; + + _guard.Unlock(); + + return result; + } // Allow for eventfull state updates in this class void StateChange() override @@ -184,6 +195,8 @@ namespace Core { && ::Thunder::Web::WebSocketClientType::IsWebSocket() // Redundant, covered by IsOpen && ::Thunder::Web::WebSocketClientType::IsCompleted() ) { + _guard.Lock(); + std::basic_string& message = _post.front(); count = std::min(message.size(), static_cast(maxSendSize)); @@ -206,6 +219,8 @@ namespace Core { // Trigger a call to SendData for remaining data ::Thunder::Web::WebSocketClientType::Link().Trigger(); } + + _guard.Unlock(); } return count; @@ -237,18 +252,26 @@ namespace Core { bool Submit(const std::basic_string& message) { + _guard.Lock(); + size_t count = _post.size(); _post.emplace_back(message); + bool result = count < _post.size(); + + _guard.Unlock(); + ::Thunder::Web::WebSocketClientType::Link().Trigger(); - return count < _post.size(); + return result; } private: std::vector> _post; // Send message queue + + mutable ::Thunder::Core::CriticalSection _guard; }; template @@ -259,13 +282,25 @@ namespace Core { WebSocketServer(const SOCKET& socket, const ::Thunder::Core::NodeId remoteNode, ::Thunder::Core::SocketServerType>*) // Initially this should be defined as a regular TCP socket : ::Thunder::Web::WebSocketServerType(false /* binary*/, false /*masking */, socket, remoteNode, SENDBUFFERSIZE /* send buffer size */, RECEIVEBUFFERSIZE /* receive buffer size */) + , _post{} + , _response{} + , _guard{} { } ~WebSocketServer() override = default; // Non-idle then data available to send - bool IsIdle() const override { return _post.size() == 0; } + bool IsIdle() const override + { + _guard.Lock(); + + bool result = _post.size() == 0; + + _guard.Unlock(); + + return result; + } // Allow for eventfull state updates in this class void StateChange() override @@ -306,6 +341,8 @@ namespace Core { && ::Thunder::Web::WebSocketServerType::IsWebSocket() // Redundant, covered by IsOpen && ::Thunder::Web::WebSocketServerType::IsCompleted() ) { + _guard.Lock(); + std::basic_string& message = _post.front(); count = std::min(message.size(), static_cast(maxSendSize)); @@ -328,6 +365,8 @@ namespace Core { // Trigger a call to SendData for remaining data ::Thunder::Web::WebSocketServerType::Link().Trigger(); } + + _guard.Unlock(); } return count; @@ -340,8 +379,12 @@ namespace Core { #endif if (receivedSize > 0) { + _guard.Lock(); + _response.emplace_back(std::basic_string{ dataFrame, receivedSize }); + _guard.Unlock(); + #ifdef _VERBOSE std::cout << " |--> dataFrame ( " << receivedSize << " ) = "; for (int32_t index = 0; index < receivedSize; index++) { @@ -357,14 +400,20 @@ namespace Core { // Put data in the queue to send (to the (connected) client) bool Submit(const std::basic_string& message) { + _guard.Lock(); + size_t count = _post.size(); _post.emplace_back(message); + bool result = count < _post.size(); + + _guard.Unlock(); + // Trigger a call to SendData ::Thunder::Web::WebSocketServerType::Link().Trigger(); - return count < _post.size(); + return result; } std::basic_string Response() @@ -375,6 +424,8 @@ namespace Core { std::basic_string message; + _guard.Lock(); + if (_response.size() > 0) { message = _response.front(); _response.erase(_response.begin()); @@ -388,6 +439,8 @@ namespace Core { #endif } + _guard.Unlock(); + return message; } @@ -395,6 +448,8 @@ namespace Core { std::vector> _post; // Send message queue std::vector> _response; // Receive message queue + + mutable ::Thunder::Core::CriticalSection _guard; }; class CustomSecureSocketStream : public ::Thunder::Crypto::SecureSocketPortClientType { From 23f9df138109b7a55d09b26ebe0a62752db49c0d Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 9 Jan 2025 10:19:58 +0000 Subject: [PATCH 71/76] [Tests/unit/core] : Re-enable disabled tests in 'test_websocket' --- Tests/unit/core/test_websocket.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index 342872570..f8237bde7 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -762,7 +762,7 @@ namespace Core { EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } - TEST(WebSocket, DISABLED_UnsecuredSocketUpgrade) + TEST(WebSocket, UnsecuredSocketUpgrade) { const TCHAR hostName[] {"127.0.0.1"}; @@ -836,7 +836,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, DISABLED_UnsecuredSocketServerPingClientPong) + TEST(WebSocket, UnsecuredSocketServerPingClientPong) { const TCHAR hostName[] {"127.0.0.1"}; @@ -919,7 +919,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, DISABLED_UnsecuredSocketServerUnsollicitedPong) + TEST(WebSocket, UnsecuredSocketServerUnsollicitedPong) { const TCHAR hostName[] {"127.0.0.1"}; @@ -1002,7 +1002,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, DISABLED_UnsecuredSocketClientUnsollicitedPong) + TEST(WebSocket, UnsecuredSocketClientUnsollicitedPong) { const TCHAR hostName[] {"127.0.0.1"}; @@ -1079,7 +1079,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, DISABLED_UnsecuredSocketDataExchange) + TEST(WebSocket, UnsecuredSocketDataExchange) { const TCHAR hostName[] {"127.0.0.1"}; @@ -1172,7 +1172,7 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } - TEST(WebSocket, DISABLED_UnsecuredSocketMultiFrameDataExchange) + TEST(WebSocket, UnsecuredSocketMultiFrameDataExchange) { const TCHAR hostName[] {"127.0.0.1"}; @@ -1375,7 +1375,7 @@ namespace Core { EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } - TEST(WebSocket, DISABLED_SecuredSocketDataExchange) + TEST(WebSocket, SecuredSocketDataExchange) { const TCHAR hostName[] {"127.0.0.1"}; From a29a0b55c3e5f3b2c44dabdf2e2ffdb5f4933d5f Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Thu, 9 Jan 2025 10:47:53 +0000 Subject: [PATCH 72/76] [Tests/unit/core] : Re-enable 'all' tests --- Tests/unit/core/CMakeLists.txt | 105 +++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/Tests/unit/core/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index fb5a22d3c..b63807b14 100644 --- a/Tests/unit/core/CMakeLists.txt +++ b/Tests/unit/core/CMakeLists.txt @@ -21,7 +21,67 @@ if(LINUX) # IPTestAdministrator only supported on LINUX platform add_executable(${TEST_RUNNER_NAME} ../IPTestAdministrator.cpp + test_cyclicbuffer.cpp + test_cyclicbuffer_dataexchange.cpp + test_databuffer.cpp + test_dataelement.cpp + test_dataelementfile.cpp + test_doorbell.cpp + test_enumerate.cpp + test_event.cpp + test_filesystem.cpp + test_frametype.cpp + test_hash.cpp + test_hex2strserialization.cpp + #test_ipc.cpp + test_ipcclient.cpp + test_iso639.cpp + test_iterator.cpp + test_jsonparser.cpp + test_keyvalue.cpp + test_library.cpp + test_lockablecontainer.cpp + test_measurementtype.cpp + test_memberavailability.cpp + test_message_dispatcher.cpp + test_messageException.cpp + test_networkinfo.cpp + test_nodeid.cpp + test_numbertype.cpp + test_optional.cpp + test_parser.cpp + test_portability.cpp + test_processinfo.cpp + test_queue.cpp + test_rangetype.cpp + test_readwritelock.cpp + test_rectangle.cpp + test_rpc.cpp + test_semaphore.cpp + test_sharedbuffer.cpp + test_singleton.cpp + test_socketstreamjson.cpp + test_socketstreamtext.cpp + test_statetrigger.cpp + test_stopwatch.cpp + test_synchronize.cpp + test_synchronous.cpp + test_systeminfo.cpp + test_textfragment.cpp + test_textreader.cpp + test_thread.cpp + test_threadpool.cpp + test_time.cpp + test_timer.cpp + test_tristate.cpp + #test_valuerecorder.cpp + test_weblinkjson.cpp + test_weblinktext.cpp test_websocket.cpp + test_websocketjson.cpp + test_websockettext.cpp + test_workerpool.cpp + test_xgetopt.cpp ) #[[ target_sources(${TEST_RUNNER_NAME} PRIVATE test_message_unit.cpp) @@ -31,6 +91,51 @@ target_link_libraries(${TEST_RUNNER_NAME} ]] else() add_executable(${TEST_RUNNER_NAME} + test_databuffer.cpp + test_dataelement.cpp + test_dataelementfile.cpp + test_enumerate.cpp + test_event.cpp + test_filesystem.cpp + test_frametype.cpp + test_hash.cpp + test_hex2strserialization.cpp + test_iso639.cpp + test_iterator.cpp + test_jsonparser.cpp + test_keyvalue.cpp + test_library.cpp + test_lockablecontainer.cpp + test_measurementtype.cpp + test_memberavailability.cpp + test_messageException.cpp + test_networkinfo.cpp + test_nodeid.cpp + test_numbertype.cpp + test_optional.cpp + test_parser.cpp + test_portability.cpp + test_processinfo.cpp + test_queue.cpp + test_rangetype.cpp + test_readwritelock.cpp + test_rectangle.cpp + test_semaphore.cpp + test_singleton.cpp + test_statetrigger.cpp + test_stopwatch.cpp + test_synchronize.cpp + test_systeminfo.cpp + test_textfragment.cpp + test_textreader.cpp + test_thread.cpp + test_threadpool.cpp + test_time.cpp + test_timer.cpp + test_tristate.cpp + #test_valuerecorder.cpp + test_workerpool.cpp + test_xgetopt.cpp ) endif() From c96074a57617108a1fc770661365300daebbe440 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:46:56 +0000 Subject: [PATCH 73/76] [Tests/unit/core] : add payload tests to 'test_websocket' --- Tests/unit/core/test_websocket.cpp | 336 ++++++++++++++++++++++++++--- 1 file changed, 311 insertions(+), 25 deletions(-) diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp index f8237bde7..ad6338c29 100644 --- a/Tests/unit/core/test_websocket.cpp +++ b/Tests/unit/core/test_websocket.cpp @@ -350,7 +350,7 @@ namespace Core { /* void* */ memcpy(dataFrame, message.data(), count); #ifdef _VERBOSE - std::cout << " |--> dataFrame (" << count << " ) = "; + std::cout << " |--> dataFrame (" << std::dec << count << " ) = "; for (size_t index = 0; index < count; index++) { std::cout << std::hex << static_cast(dataFrame[index]) << " "; } @@ -386,7 +386,7 @@ namespace Core { _guard.Unlock(); #ifdef _VERBOSE - std::cout << " |--> dataFrame ( " << receivedSize << " ) = "; + std::cout << " |--> dataFrame ( " << std::dec << receivedSize << " ) = "; for (int32_t index = 0; index < receivedSize; index++) { std::cout << std::hex << static_cast(dataFrame[index]) << " "; } @@ -400,10 +400,22 @@ namespace Core { // Put data in the queue to send (to the (connected) client) bool Submit(const std::basic_string& message) { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + _guard.Lock(); size_t count = _post.size(); +#ifdef _VERBOSE + std::cout << " |--> message ( " << std::dec << message.size() << " ) = "; + for (size_t index = 0; index < message.size(); index++) { + std::cout << std::hex << static_cast(message[index]) << " "; + } + std::cout << "\n"; +#endif + _post.emplace_back(message); bool result = count < _post.size(); @@ -431,8 +443,8 @@ namespace Core { _response.erase(_response.begin()); #ifdef _VERBOSE - std::cout << " |--> message ( " << message.size() << " ) = "; - for (int32_t index = 0; index < message.size(); index++) { + std::cout << " |--> message ( " << std::dec << message.size() << " ) = "; + for (size_t index = 0; index < message.size(); index++) { std::cout << std::hex << static_cast(message[index]) << " "; } std::cout << "\n"; @@ -729,11 +741,11 @@ namespace Core { TEST(WebSocket, DISABLED_OpeningClientPort) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking const TCHAR remoteHostName[] {"127.0.0.1"}; @@ -781,11 +793,11 @@ namespace Core { constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -855,11 +867,11 @@ namespace Core { constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -938,11 +950,11 @@ namespace Core { constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1021,11 +1033,11 @@ namespace Core { constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1098,11 +1110,11 @@ namespace Core { constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1172,6 +1184,11 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } + // Payload tests + // Payload 0-125 + // Payload 126, next 2 bytes 16 bit (unsigned) length + // Payload 127, next 4 bytes 32 bit (unsigned) length + TEST(WebSocket, UnsecuredSocketMultiFrameDataExchange) { const TCHAR hostName[] {"127.0.0.1"}; @@ -1193,11 +1210,11 @@ namespace Core { constexpr uint16_t nagglesTimeoutMs = 250; // Typical is 200 milliseconds IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1255,10 +1272,9 @@ namespace Core { if (it.Client()->IsOpen()) { // Construct a message larger than the buffer size to force use of continuation frames + // Payload == 90 size_t count = (it.Client()->Link().SendBufferSize() / sizeof(data) + 1 ) * sizeof(data); - ASSERT_LE(count, message.max_size()); - message.resize(count); for (size_t index = 0; index < count; index += sizeof(data) ) { @@ -1301,6 +1317,276 @@ namespace Core { ::Thunder::Core::Singleton::Dispose(); } + TEST(WebSocket, UnsecuredSocketeDataExchangePayload125) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {150}; + constexpr uint16_t receiveBufferSize {150}; + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + constexpr uint16_t nagglesTimeoutMs = 250; // Typical is 200 milliseconds + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + std::cout << " |--> SendBufferSize = " << client.Link().SendBufferSize() << "\n"; + std::cout << " |--> ReceiveBufferSize = " << client.Link().ReceiveBufferSize() << "\n"; + std::cout << " |--> SocketSendBufferSize = " << client.Link().SocketSendBufferSize() << "\n"; + std::cout << " |--> SocketReceiveBufferSize = " << client.Link().SocketReceiveBufferSize() << "\n"; +#endif + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up + SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + // Do not use '\0' as a marker as std::basic_strng<> assumes it is an end of string + + constexpr uint8_t data[] = { 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x3, 0x2, 0x1, 0x10 }; + + std::basic_string message; + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + // Construct a message larger than the buffer size to force use of continuation frames + // Payload len == 125 + constexpr size_t length = 125; + + ASSERT_GT(it.Client()->Link().SendBufferSize(), length); + + const size_t count = (length / sizeof(data) + 1 ) * sizeof(data); + + message.resize(count); + + for (size_t index = 0; index < count; index += sizeof(data) ) { + message.replace(index, sizeof(data), data); + } + + message.resize(length); + + ASSERT_EQ(message.size(), length); + /* bool */ it.Client()->Submit(std::basic_string{ message.data(), length }); + } + } + + // Allow some time to receive the response + SleepMs(maxWaitTimeMs); + + std::reverse(message.begin(), message.end()); + + std::basic_string response; + + response.reserve( message.size() ); + + // A simple poll to keep it simple + for (int8_t retry = 0; retry < maxRetries; ++retry) { + SleepMs(nagglesTimeoutMs); // Naggle's typical delay, perhaps a bit more + + if (it.IsValid()) { + response = it.Client()->Response() + response; + } + } + + EXPECT_TRUE( response.size() == message.size() + && response == message + ); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, UnsecuredSocketMultiFrameDataExchangePayload140) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {150}; + constexpr uint16_t receiveBufferSize {150}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + constexpr uint16_t nagglesTimeoutMs = 250; // Typical is 200 milliseconds + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + std::cout << " |--> SendBufferSize = " << client.Link().SendBufferSize() << "\n"; + std::cout << " |--> ReceiveBufferSize = " << client.Link().ReceiveBufferSize() << "\n"; + std::cout << " |--> SocketSendBufferSize = " << client.Link().SocketSendBufferSize() << "\n"; + std::cout << " |--> SocketReceiveBufferSize = " << client.Link().SocketReceiveBufferSize() << "\n"; +#endif + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up + SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + // Do not use '\0' as a marker as std::basic_strng<> assumes it is an end of string + + constexpr uint8_t data[] = { 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x3, 0x2, 0x1, 0x10 }; + + std::basic_string message; + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + // Construct a message larger than the buffer size to force use of continuation frames + // Payload len == 126 + constexpr size_t length = 140; + + ASSERT_GT(it.Client()->Link().SendBufferSize(), length); + + const size_t count = (length / sizeof(data) + 1 ) * sizeof(data); + + message.resize(count); + + for (size_t index = 0; index < count; index += sizeof(data) ) { + message.replace(index, sizeof(data), data); + } + + message.resize(length); + + ASSERT_EQ(message.size(),length); + + /* bool */ it.Client()->Submit(std::basic_string{ message.data(), length }); + } + } + + // Allow some time to receive the response + SleepMs(maxWaitTimeMs); + + std::reverse(message.begin(), message.end()); + + std::basic_string response; + + response.reserve( message.size() ); + + // A simple poll to keep it simple + for (int8_t retry = 0; retry < maxRetries; ++retry) { + SleepMs(nagglesTimeoutMs); // Naggle's typical delay, perhaps a bit more + + if (it.IsValid()) { + response = it.Client()->Response() + response; + } + } + + EXPECT_TRUE( response.size() == message.size() + && response == message + ); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + TEST(WebSocket, DISABLED_OpeningSecuredServerPort) { const TCHAR localHostName[] {"127.0.0.1"}; @@ -1342,11 +1628,11 @@ namespace Core { TEST(WebSocket, DISABLED_OpeningSecuredClientPort) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking const TCHAR remoteHostName[] {"127.0.0.1"}; @@ -1394,11 +1680,11 @@ namespace Core { constexpr uint8_t maxRetries = 10; IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { - const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol const std::string webSocketURIQuery; // HTTP URI part, absent query allowe const std::string webSocketOrigin; // Optional, set by browser clients - VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + VARIABLE_IS_NOT_USED constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) VARIABLE_IS_NOT_USED constexpr bool masking {true}; // Flag set by client to enable masking constexpr bool rawSocket {false}; @@ -1505,7 +1791,7 @@ namespace Core { EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); } - TEST(WebSocket, DISABLED_OpeningSecuredSocketClientCertificateRequest) + TEST(WebSocket, DISABLED_SecuredSocketClientCertificateRequest) { const std::string webSocketURIPath; // HTTP URI part, empty path allowed const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol From 4008468c056bbe3f535923731f373abf111c0e38 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:03:34 +0000 Subject: [PATCH 74/76] [cryptalgo/SecureSocketPort] : indicate object can be move from --- Source/cryptalgo/SecureSocketPort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 24be20efc..dfe78c9a5 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -480,7 +480,7 @@ CertificateStore::CertificateStore(bool defaultStore) {} CertificateStore::CertificateStore(CertificateStore&& move) noexcept - : _list { move._list } + : _list { std::move(move._list) } , _defaultStore{ move._defaultStore } {} From 928bccdd1262be0a269434c6765ff0481ace5258 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:08:37 +0000 Subject: [PATCH 75/76] [cryptalgo/SecureSocketPort] : improve use of list --- Source/cryptalgo/SecureSocketPort.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index dfe78c9a5..344ccb558 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -501,7 +501,7 @@ uint32_t CertificateStore::Add(const Certificate& certificate) _lock.Lock(); if ( x509Certificate != nullptr - && std::find_if(_list.begin(), _list.end(), [&x509Certificate](const X509Certificate item){ + && std::find_if(_list.begin(), _list.end(), [&x509Certificate](const X509Certificate& item){ const X509* x509 = item; return X509_cmp(x509Certificate, x509) == 0; } @@ -527,11 +527,11 @@ uint32_t CertificateStore::Remove(const Certificate& certificate) _lock.Lock(); - if ((it = std::find_if(_list.begin(), _list.end(), [&x509Certificate](const X509Certificate item){ + if ((it = std::find_if(_list.begin(), _list.end(), [&x509Certificate](const X509Certificate& item){ const X509* x509 = item; return X509_cmp(x509Certificate, x509) == 0; } - )) == _list.end() + )) != _list.end() ) { size_t position = std::distance(_list.begin(), it); From 82bdb79a0b1c0f880ebd86cc4ce731022a028415 Mon Sep 17 00:00:00 2001 From: msieben <4319079+msieben@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:12:36 +0000 Subject: [PATCH 76/76] [cryptalgo/SecureSocketPort] : remove comment --- Source/cryptalgo/SecureSocketPort.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 344ccb558..d1ecc71c0 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -789,7 +789,6 @@ POP_WARNING() template uint32_t SecureSocketPortImproved::Handler::Open(const uint32_t waitTime) { // Users of struct timeval should not exhibit overflow - // Opposed to coverity false positives event tags ASSERT(waitTime != Core::infinite); _waitTime = waitTime;