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) 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 c1a57eb56..d1ecc71c0 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -1,4 +1,4 @@ -/* +/* * If not stated otherwise in this file or this component's LICENSE file the * following copyright and licenses apply: * @@ -21,45 +21,36 @@ #include #include -#include +#include +#include #include -#include -#include -#include -#include - -#ifdef __WINDOWS__ -#include -#include -#include -#include - -#pragma comment (lib, "crypt32.lib") -#pragma comment (lib, "cryptui.lib") -#endif + +#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() - { - } - }; +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 @@ -67,6 +58,187 @@ namespace Thunder { namespace Crypto { +class X509Certificate : public Certificate { +public: + X509Certificate() = delete; + X509Certificate& operator=(Certificate&&) = delete; + X509Certificate& operator=(const Certificate&) = delete; + + X509Certificate(const Certificate& certificate) + : Certificate{ certificate } + {} + + X509Certificate(const X509* certificate) + : Certificate { certificate } + {} + + operator const X509* () const + { + return static_cast(Certificate::operator const void* ()); + } +}; + +class X509Key : public Key { +public : + X509Key() = delete; + X509Key& operator=(Key&&) = delete; + X509Key& operator=(const Key&) = delete; + + X509Key(const Key& key) + : Key{ key} + {} + + X509Key(const EVP_PKEY* key) + : Key{ key } + {} + + operator const EVP_PKEY* () const + { + return static_cast(Key::operator const void* ()); + } +}; + + +class X509CertificateStore : public CertificateStore { +public: + X509CertificateStore() = delete; + X509CertificateStore& operator=(CertificateStore&&) = delete; + X509CertificateStore& operator=(const CertificateStore&) = delete; + + X509CertificateStore(const CertificateStore& store) + : CertificateStore{ store } + {} + + operator X509_STORE* () const + { + const std::vector* list { static_cast*>(CertificateStore::operator const void* ()) }; + + 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*()); + + if ( certificate == nullptr + || X509_STORE_add_cert(store, certificate) != 1 + ) { + X509_STORE_free(store); + + store = nullptr; + + break; + } + } + + CertificateStore::_lock.Unlock(); + } + + return (store); + } + + operator STACK_OF(X509_NAME)* () const + { + 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) { + 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*()); + + // 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; + } + } + + CertificateStore::_lock.Unlock(); + } + + return (store); + } +}; + +// 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; + + result = _map.insert({ssl, index}).second ? index : -1; + + return result; + } + + int Index(const SSL* const ssl) const + { + ASSERT(ssl != nullptr); + + int result = -1; + + auto it = _map.find(ssl); + + result = it != _map.end() ? it->second : -1; + + 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; +}; + static Core::Time ASN1_ToTime(const ASN1_TIME* input) { Core::Time result; @@ -101,157 +273,293 @@ static Core::Time ASN1_ToTime(const ASN1_TIME* input) // ----------------------------------------------------------------------------- // class Certificate // ----------------------------------------------------------------------------- -Certificate::Certificate(const x509_st* certificate) - : _certificate(certificate) { +Certificate::Certificate(const void* certificate) + : _certificate{ const_cast(certificate) } +{ if (certificate != nullptr) { - X509_up_ref(const_cast(_certificate)); + VARIABLE_IS_NOT_USED int result = X509_up_ref(static_cast(_certificate)); + ASSERT(result == 1); } } -Certificate::Certificate(const TCHAR fileName[]) { - X509* cert = X509_new(); - BIO* bio_cert = BIO_new_file(fileName, "rb"); - PEM_read_bio_X509(bio_cert, &cert, NULL, NULL); - _certificate = cert; +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._certificate } +{ certificate._certificate = nullptr; } Certificate::Certificate(const Certificate& certificate) - : _certificate(certificate._certificate) { + : _certificate{ certificate._certificate } +{ if (_certificate != nullptr) { - X509_up_ref(const_cast(_certificate)); + VARIABLE_IS_NOT_USED int result = X509_up_ref(static_cast(_certificate)); + ASSERT(result == 1); } } Certificate::~Certificate() { if (_certificate != nullptr) { - X509_free(const_cast(_certificate)); + X509_free(static_cast(_certificate)); } } -string Certificate::Issuer() const { - char buffer[1024]; - buffer[0] = '\0'; - X509_NAME_oneline(X509_get_issuer_name(_certificate), buffer, sizeof(buffer)); +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(static_cast(_certificate)); - return (string(buffer)); + if ((buffer = X509_NAME_oneline(name, nullptr, 0)) != nullptr) { + result = buffer; + + free(buffer); + } + + return (result); } -string Certificate::Subject() const { - char buffer[1024]; - buffer[0] = '\0'; - X509_NAME_oneline(X509_get_subject_name(_certificate), buffer, sizeof(buffer)); +const std::string Certificate::Subject() const +{ + ASSERT(_certificate != nullptr); - return (string(buffer)); + std::string result; + char* buffer = nullptr; + + // Do not free + X509_NAME* name = X509_get_subject_name(static_cast(_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(static_cast(_certificate)))); } -Core::Time Certificate::ValidFrom() const { - return(ASN1_ToTime(X509_get0_notBefore(_certificate))); +Core::Time Certificate::ValidTill() const +{ + ASSERT(_certificate != nullptr); + + // Do not free + return (ASN1_ToTime(X509_get0_notAfter(static_cast(_certificate)))); } -Core::Time Certificate::ValidTill() const { - return(ASN1_ToTime(X509_get0_notAfter(_certificate))); +bool Certificate::ValidHostname(const std::string& expectedHostname) const +{ + ASSERT(_certificate != nullptr); + + return (X509_check_host(static_cast(_certificate), expectedHostname.c_str(), expectedHostname.size(), 0, nullptr) == 1); } -bool Certificate::ValidHostname(const string& expectedHostname) const { - return (X509_check_host(const_cast(_certificate), expectedHostname.data(), expectedHostname.size(), 0, nullptr) == 1); +Certificate::operator const void* () const +{ + if (_certificate != nullptr) { + VARIABLE_IS_NOT_USED int result = X509_up_ref(static_cast(_certificate)); + ASSERT(result == 1); + } + + return (_certificate); } // ----------------------------------------------------------------------------- // class Key // ----------------------------------------------------------------------------- -Key::Key(const evp_pkey_st* key) - : _key(key) { +Key::Key(const void* key) + : _key{ const_cast(key) } +{ if (_key != nullptr) { - EVP_PKEY_up_ref(const_cast(_key)); + VARIABLE_IS_NOT_USED int result = EVP_PKEY_up_ref(static_cast(_key)); + ASSERT(result == 1); } } Key::Key(Key&& key) noexcept - : _key(key._key) { + : _key{ key._key } +{ key._key = nullptr; } Key::Key(const Key& key) - : _key(key._key) { + : _key{ key._key } +{ if (_key != nullptr) { - EVP_PKEY_up_ref(const_cast(_key)); + VARIABLE_IS_NOT_USED int result = EVP_PKEY_up_ref(static_cast(_key)); + ASSERT(result == 1); } } Key::Key(const string& fileName) - : _key(nullptr) { + : _key{ nullptr } +{ + BIO* bioFile{ !fileName.empty() ? BIO_new_file(fileName.c_str(), "r") : nullptr }; - BIO* bio_key = BIO_new_file(fileName.c_str(), "rb"); - - if (bio_key != nullptr) { - _key = PEM_read_bio_PUBKEY(bio_key, NULL, NULL, NULL); + if (bioFile != nullptr) { + _key = PEM_read_bio_PrivateKey(bioFile, nullptr, nullptr, nullptr); - BIO_free(bio_key); + BIO_free(bioFile); } } -static int passwd_callback(char* buffer, int size, int /* flags */, void* password) +extern "C" +{ +int PasswdCallback(char* buffer, int size, VARIABLE_IS_NOT_USED int rw, void* password) { - int copied = std::min(static_cast(strlen(static_cast(password))), size); - memcpy(buffer, password, copied); - return copied; + // 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); + +// 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 static_cast(copied); +} } Key::Key(const string& fileName, const string& password) - : _key(nullptr) { - BIO* bio_key = BIO_new_file(fileName.c_str(), "rb"); + : _key(nullptr) +{ + BIO* bioFile{ !fileName.empty() ? BIO_new_file(fileName.c_str(), "r") : nullptr }; + + if (bioFile != nullptr) { + _key = PEM_read_bio_PrivateKey(bioFile, nullptr, PasswdCallback, const_cast(password.c_str())); - if (bio_key != nullptr) { - _key = PEM_read_bio_PrivateKey(bio_key, NULL, passwd_callback, const_cast(static_cast(password.c_str()))); - BIO_free(bio_key); + BIO_free(bioFile); } } Key::~Key() { if (_key != nullptr) { - EVP_PKEY_free(const_cast(_key)); + EVP_PKEY_free(static_cast(_key)); } } +Key::operator const void* () const +{ + return (_key); +} + // ----------------------------------------------------------------------------- // class CertificateStore // ----------------------------------------------------------------------------- +CertificateStore::CertificateStore(bool defaultStore) + : _list{} + , _defaultStore{ defaultStore && CreateDefaultStore() } +{} + +CertificateStore::CertificateStore(CertificateStore&& move) noexcept + : _list { std::move(move._list) } + , _defaultStore{ move._defaultStore } +{} -#ifdef __WINDOWS__ -static struct x509_store_st* CreateDefaultStore() +CertificateStore::CertificateStore(const CertificateStore& copy) + : _list { copy._list } + , _defaultStore{ copy._defaultStore } +{} + +CertificateStore::~CertificateStore() +{} + +uint32_t CertificateStore::Add(const Certificate& certificate) { - HCERTSTORE hStore; - PCCERT_CONTEXT pContext = nullptr; - X509_STORE* store = X509_STORE_new(); + uint32_t result = Core::ERROR_GENERAL; - hStore = CertOpenSystemStore(NULL, _T("ROOT")); + const X509* x509Certificate = X509Certificate{ certificate }; - if (hStore != nullptr) { - while (pContext = CertEnumCertificatesInStore(hStore, pContext)) { - X509* x509 = d2i_X509(nullptr, (const unsigned char**)&pContext->pbCertEncoded, pContext->cbCertEncoded); + _lock.Lock(); - if (x509 != nullptr) { - X509_STORE_add_cert(store, x509); - X509_free(x509); - } + 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; + } + + _lock.Unlock(); + + return (result); +} + +uint32_t CertificateStore::Remove(const Certificate& certificate) +{ + uint32_t result = Core::ERROR_GENERAL; + + std::vector::iterator it; + + 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; + } + )) != _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()); } - CertFreeCertificateContext(pContext); - CertCloseStore(hStore, 0); + + _list.pop_back(); + + result = Core::ERROR_NONE; } - return (store); + _lock.Unlock(); + + return (result); } -#else -static struct x509_store_st* CreateDefaultStore() + +uint32_t CertificateStore::CreateDefaultStore() { - X509_STORE* store = X509_STORE_new(); + // Intended use only at object construction + + uint32_t result = Core::ERROR_NONE; const char* dir = getenv(X509_get_default_cert_dir_env()); @@ -259,267 +567,521 @@ static struct x509_store_st* CreateDefaultStore() dir = X509_get_default_cert_dir(); } - X509_STORE_load_path(store, dir); + const std::string paths = dir; - return (store); -} -#endif + std::string::size_type head = paths.empty() ? std::string::npos : 0; -/* static */ struct x509_store_st* CertificateStore::_default = CreateDefaultStore(); + // OPENSSL_info requires at least version 3.0 -CertificateStore::CertificateStore() - : _store(X509_STORE_new()) { -} + 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" + ); -CertificateStore::CertificateStore(CertificateStore&& move) noexcept - : _store(move._store) { - move._store = nullptr; -} + const char* separator = OPENSSL_info(OPENSSL_INFO_LIST_SEPARATOR); -CertificateStore::CertificateStore(const CertificateStore& copy) - : _store(copy._store) { - if (_store != nullptr) { - X509_STORE_up_ref(const_cast(_store)); - } -} + 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 }.operator const X509* () != nullptr) { + result = Add(certificate); + } + + if (result != Core::ERROR_NONE) { + tail = std::string::npos; + break; + } + } -CertificateStore::CertificateStore(struct x509_store_st* store) - : _store(store) { - if (_store != nullptr) { - X509_STORE_up_ref(const_cast(_store)); + head = tail == std::string::npos ? tail : tail + 1; } + + return (result); } -CertificateStore::~CertificateStore() { - if (_store != nullptr) { - X509_STORE_free(const_cast(_store)); - } +CertificateStore::operator const void* () const +{ + return &_list; } -void CertificateStore::Add(const Certificate& certificate) { - const struct x509_st* cert = certificate; - X509_STORE_add_cert(_store, const_cast(cert)); +bool CertificateStore::IsDefaultStore() const +{ + return _defaultStore; } -// ----------------------------------------------------------------------------- -// class SecureSocketPort::Handler -// ----------------------------------------------------------------------------- -SecureSocketPort::Handler::Handler(SecureSocketPort& parent, - const enumType socketType, - const Core::NodeId& localNode, - const Core::NodeId& remoteNode, - const uint16_t sendBufferSize, - const uint16_t receiveBufferSize, - const uint32_t socketSendBufferSize, - const uint32_t socketReceiveBufferSize) - : SocketPort(socketType, localNode, remoteNode, sendBufferSize, receiveBufferSize, socketSendBufferSize, socketReceiveBufferSize) - , _parent(parent) - , _callback(nullptr) - , _handShaking(EXCHANGE) { - CreateContext(TLS_method()); -} - -SecureSocketPort::Handler::Handler(SecureSocketPort& parent, - const enumType socketType, - const SOCKET& connector, - const Core::NodeId& remoteNode, - const uint16_t sendBufferSize, - const uint16_t receiveBufferSize, - const uint32_t socketSendBufferSize, - const uint32_t socketReceiveBufferSize) - : SocketPort(socketType, connector, remoteNode, sendBufferSize, receiveBufferSize, socketSendBufferSize, socketReceiveBufferSize) - , _parent(parent) - , _callback(nullptr) - , _handShaking(EXCHANGE) { - CreateContext(TLS_server_method()); -} - -SecureSocketPort::Handler::~Handler() { +template +SecureSocketPortImproved::Handler::~Handler() { ASSERT(IsClosed() == true); Close(0); - - if (_ssl != nullptr) { - SSL_free(_ssl); - _ssl = nullptr; - } - if (_context != nullptr) { - SSL_CTX_free(_context); - _context = nullptr; - } } -void SecureSocketPort::Handler::CreateContext(const struct ssl_method_st* method) { - _context = SSL_CTX_new(method); - if (_context != nullptr) { - _ssl = SSL_new(_context); +template +uint32_t SecureSocketPortImproved::Handler::Initialize() { + ASSERT(_context == nullptr); - if (_ssl == nullptr) { - SSL_CTX_free(_context); - _context = nullptr; - } - else { - constexpr unsigned long options = SSL_OP_ALL | SSL_OP_NO_SSLv2; + uint32_t success = Core::ERROR_NONE; - VARIABLE_IS_NOT_USED unsigned long bitmask = SSL_CTX_set_options(_context, options); + // Client and server use + _context = SSL_CTX_new(TLS_method()); - ASSERT((bitmask & options) == options); - } + ASSERT(_context != nullptr); + + 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); + + 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 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) + && ( + x509Certificate == nullptr + || + ( x509Certificate != nullptr + && (SSL_CTX_use_certificate(static_cast(_context), x509Certificate) == 1) + ) + ) + && ( + x509Key == nullptr + || + ( x509Key != nullptr + && (SSL_CTX_use_PrivateKey(static_cast(_context), x509Key) == 1) + && (SSL_CTX_check_private_key(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) + ) { + // 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; } -} -uint32_t SecureSocketPort::Handler::Initialize() { - bool initialized = false; + ASSERT(success == 0); - ASSERT(_context != nullptr); + return success; +} + +template +int32_t SecureSocketPortImproved::Handler::Read(uint8_t buffer[], const uint16_t length) const { + ASSERT(_handShaking == CONNECTED); ASSERT(_ssl != nullptr); + ASSERT(length > 0); - if (SSL_set_fd(_ssl, static_cast(*this).Descriptor()) == 1) { - SSL_set_tlsext_host_name(_ssl, RemoteNode().HostName().c_str()); - if (IsOpen() == true) { - SSL_set_accept_state(_ssl); - } - else { - SSL_set_connect_state(_ssl); - } + int fd = SSL_get_fd(static_cast(_ssl)); - initialized = Core::SocketPort::Initialize(); - } + ASSERT(fd >= 0); - return (initialized); -} - -int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); - ASSERT(_handShaking != ERROR); + struct timeval tv { + static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) + , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) + }; - if (_handShaking != OPEN) { - const_cast(*this).Update(); + 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() +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; } - - return (SSL_read(_ssl, buffer, length)); + + 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); + + int fd = SSL_get_fd(static_cast(_ssl)); - ASSERT(_handShaking != ERROR); + ASSERT(fd >= 0); - uint32_t result = SSL_write(_ssl, buffer, length); + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); + + struct timeval tv { + static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) + , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) + }; - if (_handShaking != OPEN) { - Update(); + 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() +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; } - return (result); + 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 + ASSERT(waitTime != Core::infinite); + + _waitTime = waitTime; + return (Core::SocketPort::Open(waitTime)); } -uint32_t SecureSocketPort::Handler::Close(const uint32_t waitTime) { - ASSERT(_ssl != nullptr); - SSL_shutdown(_ssl); +template +uint32_t SecureSocketPortImproved::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) { + SSL_CTX_free(static_cast(_context)); + _context = nullptr; + } return(Core::SocketPort::Close(waitTime)); } -uint32_t SecureSocketPort::Handler::Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key) { - // Load server certificate and private key - const struct x509_st* cert = certificate; - const struct evp_pkey_st* base_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; - if (SSL_CTX_use_certificate(_context, const_cast(cert)) == 1) { - result = Core::ERROR_UNKNOWN_KEY; - if (SSL_CTX_use_PrivateKey(_context, const_cast(base_key)) == 1) { - result = Core::ERROR_NONE; - } + // 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::Root(const CertificateStore& certStore) { - const struct x509_store_st* store = 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"); - SSL_CTX_set_cert_store(_context, const_cast(store)); + this->_store.~CertificateStore(); + new (&(this->_store)) CertificateStore(certStore); return (Core::ERROR_NONE); } -void SecureSocketPort::Handler::ValidateHandShake() { - // Step 1: verify a certificate was presented during the negotiation - X509* x509cert = SSL_get_peer_certificate(_ssl); +template +void SecureSocketPortImproved::Handler::ValidateHandShake() { + ASSERT(_ssl != nullptr && _context != nullptr); - if (x509cert == nullptr) { + // Internal (partial) validation result if no callback is set + if (SSL_get_verify_result(static_cast(_ssl)) != X509_V_OK) { _handShaking = ERROR; SetError(); - _parent.StateChange(); } - else { - long error; - string validationError; - Crypto::Certificate certificate(x509cert); - - // Step 2: Validate certificate - use custom IValidator instance if available or if self signed - // certificates are needed :-) - if (_callback != nullptr) { - if (_callback->Validate(certificate) == false) { - _handShaking = ERROR; - SetError(); - _parent.StateChange(); - } - else { - _handShaking = OPEN; - _parent.StateChange(); - } +} + +extern "C" +{ +int VerifyCallbackWrapper(int checkOK, X509_STORE_CTX* ctx); +int PeerCertificateCallbackWrapper(X509_STORE_CTX* ctx, void* arg); +} + +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) { + case 0 : { + X509* x509Cert = nullptr; + int exDataIndex = -1; + SSL* ssl = nullptr; + CertificateStore::IValidate* validator = nullptr; + + // Retrieve 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); + + X509Certificate certificate(x509Cert); + + 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); + } + + break; + } + case 1 : // No error + break; + default : ASSERT(false); // Not within set of defined values + } + + return result; // 0 - Failure, 1 - OK +} + +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 + + ASSERT(false); + + return 0; // 0 - Failurre, 1 - OK +} + +template +uint32_t SecureSocketPortImproved::Handler::EnableClientCertificateRequest() +{ + uint32_t result{Core::ERROR_NONE}; + + if (_requestCertificate) { + STACK_OF(X509_NAME)* nameList = X509CertificateStore{ _store }; + + 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 + 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 { + result = Core::ERROR_GENERAL; } - // SSL handshake does an implicit verification, its result is: - else if ((error = SSL_get_verify_result(_ssl)) != X509_V_OK) { - // string errorMsg = X509_verify_cert_error_string(error); - _handShaking = ERROR; - SetError(); - _parent.StateChange(); + } + + return result; +} + +template +void SecureSocketPortImproved::Handler::Update() { + if (IsOpen() == true) { + 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 }); } - else { - _handShaking = OPEN; - _parent.StateChange(); + +// const char* file = nullptr, *func = nullptr, *data = nullptr; +// int line = 0, flag = 0; + +// ASSERT(ERR_get_error_all(&file, &line, &func, &data, &flag) == 0); + + errno = 0; + + 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); } - X509_free(x509cert); - } -} + const int fd = SSL_get_fd(static_cast(_ssl)); -void SecureSocketPort::Handler::Update() { + ASSERT(fd >= 0); - if (IsOpen() == true) { - int result = 1; + fd_set rfds, wfds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(fd, &rfds); + FD_SET(fd, &wfds); - ASSERT(_ssl != nullptr); + struct timeval tv { + static_cast(_waitTime / (Core::Time::MilliSecondsPerSecond)) + , static_cast((_waitTime % (Core::Time::MilliSecondsPerSecond)) * (Core::Time::MilliSecondsPerSecond / Core::Time::MicroSecondsPerSecond)) + }; - if (_handShaking == EXCHANGE) { - if ((result = SSL_do_handshake(_ssl)) == 1) { - ValidateHandShake(); - } - else { - result = SSL_get_error(_ssl, result); + do { + int result = SSL_do_handshake(static_cast(_ssl)); - if (result == SSL_ERROR_WANT_WRITE) { - Trigger(); - } - else if (result != SSL_ERROR_WANT_READ) { - _handShaking = ERROR; - } + switch ((result = SSL_get_error(static_cast(_ssl), result))) { + 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 + unsigned long result = 0; + + 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); + + 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 + 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) + && !FD_ISSET(fd, &wfds) + ) { + // Only file descriptors in the set should be set + _handShaking = ERROR; + ASSERT(_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; + ASSERT(_handShaking != ERROR); + } + break; + 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; } + + } 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(); } - } - else { - _handShaking = EXCHANGE; + ASSERT(_handShaking != ERROR); + _parent.StateChange(); } } -SecureSocketPort::~SecureSocketPort() { +// Explicit instantiation +template class SecureSocketPortImproved; +template class SecureSocketPortImproved; + } -} } // namespace Thunder::Crypto +} // namespace Thunder::Crypto diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index c1ff30fb8..9f2010182 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -19,14 +19,9 @@ #pragma once -#include "Module.h" +#include -struct ssl_st; -struct ssl_ctx_st; -struct ssl_method_st; -struct x509_st; -struct evp_pkey_st; -struct x509_store_st; +#include "Module.h" namespace Thunder { namespace Crypto { @@ -37,112 +32,117 @@ namespace Crypto { Certificate& operator=(Certificate&&) = delete; Certificate& operator=(const Certificate&) = delete; - Certificate(const x509_st* certificate); - Certificate(const TCHAR fileName[]); + Certificate(const std::string& fileName); Certificate(Certificate&& move) noexcept; Certificate(const Certificate& copy); ~Certificate(); public: - string Issuer() const; - string Subject() const; + const std::string Issuer() const; + const std::string Subject() const; Core::Time ValidFrom() const; Core::Time ValidTill() const; - bool ValidHostname(const string& expectedHostname) const; + bool ValidHostname(const std::string& expectedHostname) const; - inline operator const struct x509_st* () const { - return (_certificate); - } + protected: + Certificate(const void* certificate); + operator const void* () const; private: - const x509_st* _certificate; + mutable void* _certificate; }; + class EXTERNAL Key { public: Key() = delete; Key& operator=(Key&&) = delete; Key& operator=(const Key&) = delete; - Key(const evp_pkey_st* key); Key(const string& fileName); Key(const string& fileName, const string& password); Key(Key&& move) noexcept; Key(const Key& copy); ~Key(); - public: - inline operator const evp_pkey_st* () const { - return (_key); - } + protected: + Key(const void* key); + operator const void* () const; private: - const evp_pkey_st* _key; + mutable void* _key; }; + class EXTERNAL CertificateStore { public: + CertificateStore() = delete; CertificateStore& operator=(CertificateStore&&) = delete; CertificateStore& operator=(const CertificateStore&) = delete; - CertificateStore(); + CertificateStore(bool defaultStore); CertificateStore(CertificateStore&&) noexcept; CertificateStore(const CertificateStore&); - CertificateStore(struct x509_store_st*); ~CertificateStore(); public: - static CertificateStore Default() { - return (CertificateStore(_default)); - } - void Add(const Certificate& cert); - inline operator const x509_store_st* () const { - return (_store); - } + uint32_t Add(const Certificate& certificate); + uint32_t Remove(const Certificate& certificate); + bool IsDefaultStore() const; - private: - struct x509_store_st* _store; - static struct x509_store_st* _default; - }; - class EXTERNAL SecureSocketPort : public Core::IResource { - public: - struct EXTERNAL IValidate { + struct IValidate { virtual ~IValidate() = default; - // Client part, override custom validation - virtual bool Validate(const Certificate&) const = 0; + virtual bool Validate(const Certificate& certificate) const = 0; }; + protected: + operator const void* () const; + + mutable Core::CriticalSection _lock; + + private: + uint32_t CreateDefaultStore(); + + // (Extra) added certificates + std::vector _list; + + const bool _defaultStore; + }; + + template + class EXTERNAL SecureSocketPortImproved : public Core::IResource { private: class EXTERNAL Handler : public Core::SocketPort { private: enum state : uint8_t { + ACCEPTING, + CONNECTING, EXCHANGE, - OPEN, + CONNECTED, ERROR }; public: - Handler(Handler&&) = delete; + Handler() = delete; Handler(const Handler&) = delete; - Handler& operator=(Handler&&) = delete; + Handler(Handler&&) = delete; Handler& operator=(const Handler&) = delete; + Handler& operator=(Handler&&) = delete; - Handler(SecureSocketPort& parent, - const enumType socketType, - const Core::NodeId& localNode, - const Core::NodeId& remoteNode, - const uint16_t sendBufferSize, - const uint16_t receiveBufferSize, - const uint32_t socketSendBufferSize, - const uint32_t socketReceiveBufferSize); - Handler(SecureSocketPort& parent, - const enumType socketType, - const SOCKET& connector, - const Core::NodeId& remoteNode, - const uint16_t sendBufferSize, - const uint16_t receiveBufferSize, - const uint32_t socketSendBufferSize, - const uint32_t socketReceiveBufferSize); + template + Handler(SecureSocketPortImproved& parent, bool requestCert, Args&&... args) + : Core::SocketPort(std::forward(args)...) + , _parent(parent) + , _context(nullptr) + , _ssl(nullptr) + , _callback(nullptr) + , _handShaking{HANDLERTYPE::value ? CONNECTING : ACCEPTING} + , _certificate{std::string{""}} + , _privateKey{std::string{""}} + , _requestCertificate{requestCert} + , _waitTime{0} + , _store{ true } + {} ~Handler(); public: @@ -155,86 +155,70 @@ namespace Crypto { uint32_t Close(const uint32_t waitTime); // Methods to extract and insert data into the socket buffers - inline uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) override { + uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) override { return (_parent.SendData(dataFrame, maxSendSize)); } - inline uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) override { + uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) override { return (_parent.ReceiveData(dataFrame, receivedSize)); } // Signal a state change, Opened, Closed or Accepted - inline void StateChange() override { + void StateChange() override { Update(); - }; - inline void Validate(const IValidate* callback) { + } + inline uint32_t Callback(CertificateStore::IValidate* callback) { + uint32_t result = Core::ERROR_ILLEGAL_STATE; + Core::SocketPort::Lock(); - ASSERT((callback == nullptr) ^ (_callback == nullptr)); + ASSERT((callback == nullptr) || (_callback == nullptr)); - _callback = callback; + if ((callback == nullptr) || (_callback == nullptr)) { + _callback = callback; + result = Core::ERROR_NONE; + } Core::SocketPort::Unlock(); + + return (result); } uint32_t Certificate(const Crypto::Certificate& certificate, const Crypto::Key& key); - uint32_t Root(const CertificateStore& store); + uint32_t CustomStore( const CertificateStore& store); private: void Update(); void ValidateHandShake(); - void CreateContext(const struct ssl_method_st* method); + uint32_t EnableClientCertificateRequest(); private: - SecureSocketPort& _parent; - struct ssl_ctx_st* _context; - struct ssl_st* _ssl; - const IValidate* _callback; + SecureSocketPortImproved& _parent; + void* _context; + void* _ssl; + CertificateStore::IValidate* _callback; mutable state _handShaking; + 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: - SecureSocketPort(SecureSocketPort&&) = delete; - SecureSocketPort(const SecureSocketPort&) = delete; - SecureSocketPort& operator=(SecureSocketPort&&) = delete; - SecureSocketPort& operator=(const SecureSocketPort&) = delete; - - SecureSocketPort( - const Core::SocketPort::enumType socketType, - const Core::NodeId& localNode, - const Core::NodeId& remoteNode, - const uint16_t sendBufferSize, - const uint16_t receiveBufferSize) - : _handler(*this, socketType, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) { - } - SecureSocketPort( - const Core::SocketPort::enumType socketType, - const Core::NodeId& localNode, - const Core::NodeId& remoteNode, - const uint16_t sendBufferSize, - const uint16_t receiveBufferSize, - const uint32_t socketSendBufferSize, - const uint32_t socketReceiveBufferSize) - : _handler(*this, socketType, localNode, remoteNode, sendBufferSize, receiveBufferSize, socketSendBufferSize, socketReceiveBufferSize) { - } - SecureSocketPort( - const Core::SocketPort::enumType socketType, - const SOCKET& connector, - const Core::NodeId& remoteNode, - const uint16_t sendBufferSize, - const uint16_t receiveBufferSize) - : _handler(*this, socketType, connector, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) { + SecureSocketPortImproved(SecureSocketPortImproved&&) = delete; + SecureSocketPortImproved(const SecureSocketPortImproved&) = delete; + SecureSocketPortImproved& operator=(const SecureSocketPortImproved&) = delete; + + template ::type> + SecureSocketPortImproved(bool requestPeerCert, Args&&... args) + : _handler(*this, requestPeerCert, std::forward(args)...) + {} + + template ::type> + SecureSocketPortImproved(Args&&... args) + : _handler(*this, false, std::forward(args)...) + {} + ~SecureSocketPortImproved() override { } - SecureSocketPort( - const Core::SocketPort::enumType socketType, - const SOCKET& connector, - const Core::NodeId& remoteNode, - const uint16_t sendBufferSize, - const uint16_t receiveBufferSize, - const uint32_t socketSendBufferSize, - const uint32_t socketReceiveBufferSize) - : _handler(*this, socketType, connector, remoteNode, sendBufferSize, receiveBufferSize, socketSendBufferSize, socketReceiveBufferSize) { - } - - ~SecureSocketPort() override; public: inline bool IsOpen() const @@ -282,17 +266,16 @@ namespace Crypto { inline void Trigger() { _handler.Trigger(); } - inline void Validate(const IValidate* callback) { - _handler.Validate(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) { return (_handler.Certificate(certificate, key)); } - inline uint32_t Root(const CertificateStore& store) { - return (_handler.Root(store)); + inline uint32_t CustomStore(const CertificateStore& store) { + return (_handler.CustomStore(store)); } - // // Core::IResource interface // ------------------------------------------------------------------------ @@ -320,38 +303,42 @@ namespace Crypto { Handler _handler; }; - template - class SecureSocketServerType : public Core::SocketServerType { - public: - SecureSocketServerType() = delete; - SecureSocketServerType(SecureSocketServerType&&) = delete; - SecureSocketServerType(const SecureSocketServerType&) = delete; - SecureSocketServerType& operator=(SecureSocketServerType&&) = delete; - SecureSocketServerType& operator=(const SecureSocketServerType&) = delete; - - SecureSocketServerType(const Certificate& certificate, const Key& key) - : Core::SocketServerType() - , _certificate(certificate) - , _key(key) { - } - SecureSocketServerType(const Certificate& certificate, const Key& key, const Core::NodeId& serverNode) - : Core::SocketServerType(serverNode) - , _certificate(certificate) - , _key(key) { - } - ~SecureSocketServerType() = default; + using SecureSocketPort = SecureSocketPortImproved<>; + class SecureSocketPortClientType : public SecureSocketPortImproved + { public: - const Crypto::Certificate& Certificate() const { - return (_certificate); - } - const Crypto::Key& Key() const { - return (_key); - } + 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() + {} + }; - private: - Crypto::Certificate _certificate; - Crypto::Key _key; + 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/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/CMakeLists.txt b/Tests/unit/core/CMakeLists.txt index eb8aa8826..b63807b14 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,10 @@ 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}\" + SECURESOCKETS_ENABLED=1 + VOLATILE_PATH=${CMAKE_INSTALL_PREFIX}/${VOLATILE_PATH} ) target_compile_definitions(${TEST_RUNNER_NAME} @@ -164,6 +168,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/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----- diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp new file mode 100644 index 000000000..ad6338c29 --- /dev/null +++ b/Tests/unit/core/test_websocket.cpp @@ -0,0 +1,1928 @@ +/* + * 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 + +#ifndef SECURESOCKETS_ENABLED +#error "This unit test requires SecureSocketPort" +#endif + +#include "../IPTestAdministrator.h" + +#ifdef VOLATILE_PATH +#define XSTR(s) STR(s) +#define STR(s) #s "/" +#else +#define XSTR(s) +#define STR(s) +#endif + +//#define _VERBOSE + +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 + ) + : SocketStream(false, socket, localNode, sendBufferSize, receiveBufferSize) + { + } + + CustomSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + ) + : SocketStream(false, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + { + } + + ~CustomSocketStream() + { +#ifdef _VERBOSE + std::cout.flush(); +#endif + } + + // 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; + } + }; + + 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)... /**/) + , _post{} + , _guard{} + { + } + + ~WebSocketClient() override = default; + + // Non-idle then data available to send + 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 + { +#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() + ) { + _guard.Lock(); + + 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(); + } + + _guard.Unlock(); + } + + 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) + { + _guard.Lock(); + + size_t count = _post.size(); + + _post.emplace_back(message); + + bool result = count < _post.size(); + + _guard.Unlock(); + + ::Thunder::Web::WebSocketClientType::Link().Trigger(); + + return result; + } + + private: + + std::vector> _post; // Send message queue + + mutable ::Thunder::Core::CriticalSection _guard; + }; + + 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 */) + , _post{} + , _response{} + , _guard{} + { + } + + ~WebSocketServer() override = default; + + // Non-idle then data available to send + 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 + { +#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() + ) { + _guard.Lock(); + + 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 (" << std::dec << count << " ) = "; + for (size_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(); + } + + _guard.Unlock(); + } + + 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) { + _guard.Lock(); + + _response.emplace_back(std::basic_string{ dataFrame, receivedSize }); + + _guard.Unlock(); + +#ifdef _VERBOSE + std::cout << " |--> dataFrame ( " << std::dec << 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) + { +#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(); + + _guard.Unlock(); + + // Trigger a call to SendData + ::Thunder::Web::WebSocketServerType::Link().Trigger(); + + return result; + } + + std::basic_string Response() + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + std::basic_string message; + + _guard.Lock(); + + if (_response.size() > 0) { + message = _response.front(); + _response.erase(_response.begin()); + +#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 + } + + _guard.Unlock(); + + return message; + } + + private: + + std::vector> _post; // Send message queue + std::vector> _response; // Receive message queue + + mutable ::Thunder::Core::CriticalSection _guard; + }; + + class CustomSecureSocketStream : public ::Thunder::Crypto::SecureSocketPortClientType { + private : + + static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); + + // Validate client certificate + class Validator : public ::Thunder::Crypto::CertificateStore::IValidate { + public: + + Validator() = default; + ~Validator() = default; + + bool Validate(VARIABLE_IS_NOT_USED const ::Thunder::Crypto::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 + ) + : ::Thunder::Crypto::SecureSocketPortClientType(::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + , _validator{} + { + // Support client certificate request + ::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); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + + // Validate custom (sefl signed) certificates + /* uint32_t */ result = Callback(&_validator); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + } + + CustomSecureSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + ) + : ::Thunder::Crypto::SecureSocketPortClientType(::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")); + ::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() + { + #ifdef _VERBOSE + std::cout.flush(); +#endif + } + + private: + const std::string _prefix; + Validator _validator; + }; + + /* static */ constexpr char CustomSecureSocketStream::volatilePath[]; + + class CustomSecureServerSocketStream : public ::Thunder::Crypto::SecureSocketPortServerType { + private : + + static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); + + 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::SecureSocketPortServerType(false, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + { + // Server identification + ::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); + } + + CustomSecureServerSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t 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")); + ::Thunder::Crypto::Key privateKey(std::string(volatilePath).append("localhostServer.key")); + + uint32_t result = Certificate(certificate, privateKey); + ASSERT(result == ::Thunder::Core::ERROR_NONE); + } + + ~CustomSecureServerSocketStream() + { +#ifdef _VERBOSE + std::cout.flush(); +#endif + } + }; + + /* static */ constexpr char CustomSecureServerSocketStream::volatilePath[]; + + class CustomSecureServerSocketStreamClientValidation : public ::Thunder::Crypto::SecureSocketPortServerType { + private : + + static constexpr char volatilePath[] = XSTR(VOLATILE_PATH); + + // Validat eclient certificate + class Validator : public ::Thunder::Crypto::CertificateStore::IValidate { + public: + + Validator() = default; + ~Validator() = default; + + bool Validate(VARIABLE_IS_NOT_USED const ::Thunder::Crypto::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 + CustomSecureServerSocketStreamClientValidation( + const SOCKET& socket + , const ::Thunder::Core::NodeId& localNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + ) + : ::Thunder::Crypto::SecureSocketPortServerType(true, ::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + , _validator{} + { + // Server identification + ::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); + 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( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + ) + : ::Thunder::Crypto::SecureSocketPortServerType(true, ::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + , _validator{} + { + // Server identification + ::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); + 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() + { +#ifdef _VERBOSE + std::cout.flush(); +#endif + } + + private: + const std::string _prefix; + Validator _validator; + }; + + /* static */ constexpr char CustomSecureServerSocketStreamClientValidation::volatilePath[]; + + 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 + 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"}; + + 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); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, 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, VARIABLE_IS_NOT_USED 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 + 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}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + + 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, 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, VARIABLE_IS_NOT_USED 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 + 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}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + + 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, 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, VARIABLE_IS_NOT_USED 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 + 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}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + + 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, 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, VARIABLE_IS_NOT_USED 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 + 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}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize); + + 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, 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, VARIABLE_IS_NOT_USED 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 + 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); + + // 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(); + } + + // 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"}; + + // 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 + 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 == 90 + size_t count = (it.Client()->Link().SendBufferSize() / sizeof(data) + 1 ) * sizeof(data); + + 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(); + } + + 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"}; + + 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, 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 + 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 + + 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); + + SleepMs(maxWaitTimeMs); + + 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 + 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); + + // 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, DISABLED_SecuredSocketServerCertificateRequest) + { + 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_SecuredSocketClientCertificateRequest) + { + 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 + + 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); + 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, VARIABLE_IS_NOT_USED 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 + 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); + + // 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 + +#ifdef VOLATILE_PATH +#undef STR +#undef XSTR +#endif