diff --git a/src/include/baselib/core/SecureStringWrapper.h b/src/include/baselib/core/SecureStringWrapper.h index 36dc205..7ff9a6d 100644 --- a/src/include/baselib/core/SecureStringWrapper.h +++ b/src/include/baselib/core/SecureStringWrapper.h @@ -45,26 +45,57 @@ namespace bl INITIAL_CAPACITY = 16u }; - SecureStringWrapperT( SAA_inout std::string* externalString = nullptr ) - : m_implPtr( externalString ? externalString : &m_impl ) + SecureStringWrapperT( + SAA_inout_opt std::string* externalString = nullptr, + SAA_in_opt const std::size_t initialCapacity = 0U + ) + : m_implPtr( externalString ? externalString : &m_impl ), + m_dataPtr( m_implPtr -> data() ) { + /* + * This class can be used in three ways: + * 1. as a standalone object, which uses internal string as a storage + * 2. as a wrapper of an external string, which will be managed only by object of this class + * 3. as a wrapper of an external string, which will be modified outside of this class + * In order to assure a secure memory cleanup in the last case, user has to specify + * an initial capacity up to which the string is expected to grow. The string has to + * be empty, otherwise the allocation hint will be ignored. If the string is reallocated + * due to external changes, the wrapper is no longer able to wipe it securely.In such case, + * the application will be terminated. Due to this, the only safe operations are probably + * 'push_back', 'append' and some forms of 'assign' with the first being recommended way of + * updating the string. + */ + + if( initialCapacity > 0 ) + { + BL_CHK( + false, + externalString && externalString -> empty(), + "Initial capacity can only be specified for empty external string " + ); + + reserve( initialCapacity ); + } } SecureStringWrapperT( SAA_in const SecureStringWrapperT& other ) - : m_implPtr( &m_impl ) + : m_implPtr( &m_impl ), + m_dataPtr( m_implPtr -> data() ) { - append( *other.m_implPtr ); + append( other ); } SecureStringWrapperT( SAA_in SecureStringWrapperT&& other ) - : m_implPtr( &m_impl ) + : m_implPtr( &m_impl ), + m_dataPtr( m_implPtr -> data() ) { - append( *other.m_implPtr ); + append( other ); other.clear(); } explicit SecureStringWrapperT( SAA_in std::string&& other ) - : m_implPtr( &m_impl ) + : m_implPtr( &m_impl ), + m_dataPtr( m_implPtr -> data() ) { append( other ); secureClear( other ); @@ -78,7 +109,7 @@ namespace bl } clear(); - append( *other.m_implPtr ); + append( other ); return *this; } @@ -90,7 +121,11 @@ namespace bl return *this; } - return *this = std::move( *other.m_implPtr ); + clear(); + append( other ); + other.clear(); + + return *this; } SecureStringWrapperT& operator=( SAA_in std::string&& other ) @@ -124,6 +159,8 @@ namespace bl { m_implPtr -> push_back( ch ); } + + m_size = size(); } void append( SAA_in const char ch ) @@ -131,6 +168,8 @@ namespace bl grow( size() + 1 ); m_implPtr -> push_back( ch ); + + m_size = size(); } const std::string& getAsNonSecureString() const NOEXCEPT @@ -150,11 +189,38 @@ namespace bl void clear() NOEXCEPT { + checkWrappedStringIntegrity(); + secureClear( *m_implPtr ); + + m_size = size(); } private: + void reserve( SAA_in const std::size_t newCapacity ) + { + m_implPtr -> reserve( newCapacity ); + m_dataPtr = m_implPtr -> data(); + } + + void checkWrappedStringIntegrity() + { + BL_RT_ASSERT( + m_dataPtr == m_implPtr -> data(), + "Unable to ensure integrity of the wrapped string, as it was reallocated by external operation" + ); + + /* + * The wrapped external string can grow as a result of external operation but cannot shrink. + */ + + BL_RT_ASSERT( + m_size <= size(), + "Unable to ensure integrity of the wrapped string, as it was shrank by external operation" + ); + } + static void secureClear( SAA_inout std::string& other ) NOEXCEPT { BL_NOEXCEPT_BEGIN() @@ -186,16 +252,19 @@ namespace bl { if( m_implPtr -> empty() ) { - m_implPtr -> reserve( calculateNewCapacity( newSize ) ); + reserve( calculateNewCapacity( newSize ) ); } else if( newSize > m_implPtr -> capacity() ) { SecureStringWrapperT temp( *this ); clear(); + reserve( calculateNewCapacity( newSize ) ); - m_implPtr -> reserve( calculateNewCapacity( newSize ) ); - m_implPtr -> assign( *temp.m_implPtr ); + for( const auto ch : *temp.m_implPtr ) + { + m_implPtr -> push_back( ch ); + } } } @@ -203,6 +272,9 @@ namespace bl std::string m_impl; std::string* m_implPtr; + + const char* m_dataPtr; + cpp::ScalarTypeIniter< std::size_t > m_size; }; using SecureStringWrapper = SecureStringWrapperT<>; diff --git a/src/include/baselib/http/SimpleSecureHttpSslTask.h b/src/include/baselib/http/SimpleSecureHttpSslTask.h index 1e2c360..f8eb03d 100644 --- a/src/include/baselib/http/SimpleSecureHttpSslTask.h +++ b/src/include/baselib/http/SimpleSecureHttpSslTask.h @@ -61,8 +61,7 @@ namespace bl str::empty() /* content */, BL_PARAM_FWD( requestHeaders ) ), - m_secureContentIn( &this -> m_contentIn ), - m_secureContentOut( &this -> m_contentOut ) + m_secureContentIn( &this -> m_contentIn ) { m_secureContentIn = content; @@ -82,7 +81,6 @@ namespace bl protected: str::SecureStringWrapper m_secureContentIn; - str::SecureStringWrapper m_secureContentOut; }; diff --git a/src/utests/include/utests/baselib/HttpServerHelpers.h b/src/utests/include/utests/baselib/HttpServerHelpers.h index 2eda60a..ec7e977 100644 --- a/src/utests/include/utests/baselib/HttpServerHelpers.h +++ b/src/utests/include/utests/baselib/HttpServerHelpers.h @@ -29,6 +29,7 @@ #include #include +#include #include #include diff --git a/src/utests/utf_baselib/TestBaselibDefault.h b/src/utests/utf_baselib/TestBaselibDefault.h index 316ca45..6e53ea4 100644 --- a/src/utests/utf_baselib/TestBaselibDefault.h +++ b/src/utests/utf_baselib/TestBaselibDefault.h @@ -1,12 +1,12 @@ /* * This file is part of the swblocks-baselib library. - * + * * 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. @@ -5160,83 +5160,59 @@ UTF_AUTO_TEST_CASE( BaseLib_StringUtilsSecureStringWrapper ) UTF_CHECK_EQUAL( sec1.size(), 0U ); UTF_CHECK( sec1.empty() ); - const char* dataPtr = nullptr; - - const auto chkDataPtrIsNot = []( - SAA_in const char* dataPtr, - SAA_in const char* cmpString - ) - { - while( *cmpString ) - { - UTF_CHECK( *dataPtr != *cmpString ); - ++cmpString; - ++dataPtr; - } - }; - { - std::string s( "test" ); - - dataPtr = s.c_str(); - - bl::str::SecureStringWrapper sec2( std::move( s ) ); + /* + * Testing wipe functionality + */ - chkDataPtrIsNot( dataPtr, "test" ); + std::string s( "test" ); - UTF_CHECK( s.empty() ); - UTF_CHECK( ! sec2.empty() ); - UTF_CHECK_EQUAL( sec2.getAsNonSecureString(), "test" ); + { + bl::str::SecureStringWrapper sec2( std::move( s ) ); - dataPtr = sec2.getAsNonSecureString().c_str(); + UTF_CHECK( s.empty() ); + UTF_CHECK( ! sec2.empty() ); + UTF_CHECK_EQUAL( sec2.getAsNonSecureString(), "test" ); + } } - chkDataPtrIsNot( dataPtr, "test" ); - { - std::string s( "test" ); - dataPtr = s.c_str(); + /* + * Testing move assignment operator from string and move ctor from SecureStringWrapper + */ + std::string s( "test" ); bl::str::SecureStringWrapper sec2; - sec2 = std::move( s ); - chkDataPtrIsNot( dataPtr, "test" ); + sec2 = std::move( s ); UTF_CHECK( s.empty() ); UTF_CHECK( ! sec2.empty() ); UTF_CHECK_EQUAL( sec2.getAsNonSecureString(), "test" ); - dataPtr = sec2.getAsNonSecureString().c_str(); - bl::str::SecureStringWrapper sec3( std::move( sec2 ) ); - chkDataPtrIsNot( dataPtr, "test" ); - UTF_CHECK( sec2.empty() ); UTF_CHECK( ! sec3.empty() ); UTF_CHECK_EQUAL( sec3.getAsNonSecureString(), "test" ); - dataPtr = sec3.getAsNonSecureString().c_str(); - sec3.clear(); UTF_CHECK( sec3.empty() ); - - chkDataPtrIsNot( dataPtr, "test" ); } { + /* + * Testing move ctor from string + reallocation + */ + std::string s( "test" ); bl::str::SecureStringWrapper sec2( std::move( s ) ); bl::str::SecureStringWrapper sec3; - dataPtr = sec2.getAsNonSecureString().c_str(); - sec3 = std::move( sec2 ); - chkDataPtrIsNot( dataPtr, "test" ); - UTF_CHECK( sec2.empty() ); UTF_CHECK( ! sec3.empty() ); UTF_CHECK_EQUAL( sec3.getAsNonSecureString(), "test" ); @@ -5249,8 +5225,6 @@ UTF_AUTO_TEST_CASE( BaseLib_StringUtilsSecureStringWrapper ) UTF_CHECK_EQUAL( sec3.getAsNonSecureString(), "testtest1" ); - dataPtr = sec3.getAsNonSecureString().c_str(); - bl::str::SecureStringWrapper sec4( "long string, long string, long string, long string, " ); sec4.append( "long string, long string, long string, long string, " ); sec4.append( "long string, long string, long string, long string, " ); @@ -5263,9 +5237,6 @@ UTF_AUTO_TEST_CASE( BaseLib_StringUtilsSecureStringWrapper ) sec3 = std::move( sec4 ); - chkDataPtrIsNot( dataPtr, "test" ); - chkDataPtrIsNot( dataPtr4, "long" ); - UTF_CHECK( sec4.empty() ); UTF_CHECK( ! sec3.empty() ); UTF_CHECK_EQUAL( sec3.size(), size4 ); @@ -5273,11 +5244,13 @@ UTF_AUTO_TEST_CASE( BaseLib_StringUtilsSecureStringWrapper ) } { + /* + * Testing SecureStringWrapper management of external string + */ + std::string s( "test" ); { - dataPtr = s.c_str(); - bl::str::SecureStringWrapper sec2( &s ); UTF_CHECK( ! s.empty() ); @@ -5287,17 +5260,34 @@ UTF_AUTO_TEST_CASE( BaseLib_StringUtilsSecureStringWrapper ) sec2.append( "long string, long string, long string, long string" ); - chkDataPtrIsNot( dataPtr, "test" ); - - UTF_CHECK( dataPtr != s.c_str() ); UTF_CHECK_EQUAL( s.c_str(), sec2.getAsNonSecureString().c_str() ); - - dataPtr = s.c_str(); } UTF_CHECK( s.empty() ); + } - chkDataPtrIsNot( dataPtr, "test" ); + { + /* + * Initial capacity can only be specified for external and empty string + */ + + UTF_CHECK_THROW( + bl::str::SecureStringWrapper sec1( nullptr, 1024 ), + bl::UnexpectedException + ); + + std::string s( "test" ); + + UTF_CHECK_THROW( + bl::str::SecureStringWrapper sec2( &s, 1024 ), + bl::UnexpectedException + ); + + std::string s2; + + UTF_CHECK_NO_THROW( + bl::str::SecureStringWrapper sec3( &s2, 1024 ) + ); } } diff --git a/src/utests/utf_baselib_http/TestClientHttpTasks.h b/src/utests/utf_baselib_http/TestClientHttpTasks.h index cb953c1..47a0fa8 100644 --- a/src/utests/utf_baselib_http/TestClientHttpTasks.h +++ b/src/utests/utf_baselib_http/TestClientHttpTasks.h @@ -320,3 +320,82 @@ UTF_AUTO_TEST_CASE( Client_SimpleHttpPerfTests ) ); } +UTF_AUTO_TEST_CASE( Client_SimpleSecureHttpSslGetTests ) +{ + using namespace bl; + using namespace bl::data; + using namespace bl::tasks; + using namespace bl::transfer; + + utest::http::HttpServerHelpers::startHttpServerAndExecuteCallback< bl::httpserver::HttpSslServer >( + []() -> void + { + BL_LOG_MULTILINE( + Logging::debug(), + BL_MSG() + << "\n******************************** Starting test: Client_SimpleSecureHttpSslGetTests ********************************\n" + ); + + scheduleAndExecuteInParallel( + []( SAA_in const om::ObjPtr< ExecutionQueue >& eq ) -> void + { + eq -> setOptions( ExecutionQueue::OptionKeepAll ); + + { + http::HeadersMap headers; + + headers[ "MyHeader" ] = "MyValue"; + + bl::str::SecureStringWrapper content( "Hidden content" ); + + const auto stask = SimpleSecureHttpSslGetTaskImpl::createInstance( + cpp::copy( test::UtfArgsParser::host() ), + cpp::copy( test::UtfArgsParser::port() ), + utest::http::g_requestUri, + content, + std::move( headers ) + ); + + UTF_REQUIRE_EQUAL( stask -> isSecureMode(), true ); + + const auto task = om::qi< Task >( stask ); + UTF_REQUIRE_EQUAL( Task::Created, task -> getState() ); + + eq -> push_back( task ); + const auto executedTask = eq -> pop( true ); + + BL_LOG_MULTILINE( Logging::debug(), BL_MSG() << "\n******* HTTP task executed ******* \n" ); + + UTF_REQUIRE( executedTask ); + UTF_REQUIRE( om::areEqual( task, executedTask ) ); + UTF_REQUIRE_EQUAL( Task::Completed, task -> getState() ); + UTF_REQUIRE( eq -> isEmpty() ); + + if( stask -> isFailed() ) + { + UTF_REQUIRE( nullptr != stask -> exception() ); + cpp::safeRethrowException( stask -> exception() ); + } + + // Check the response code is 200, and content was received + UTF_REQUIRE( nullptr == stask -> exception() ); + UTF_REQUIRE_EQUAL( 200U, stask -> getHttpStatus() ); + + const auto contentType = stask -> tryGetResponseHeader( http::Parameters::HttpHeader::g_contentType ); + + UTF_REQUIRE( contentType ); + UTF_REQUIRE( str::istarts_with( *contentType, "application/json;" ) ); + + const auto& response = stask -> getResponse(); + + UTF_REQUIRE( response.size() ); + BL_LOG_MULTILINE( Logging::debug(), BL_MSG() << "\n******* begin HTTP response ******* \n" ); + BL_LOG_MULTILINE( Logging::debug(), BL_MSG() << response ); + BL_LOG_MULTILINE( Logging::debug(), BL_MSG() << "\n******* end HTTP response ******* \n" ); + } + + }); + } + ); +} +