diff --git a/.gitignore b/.gitignore
index 21aa5e16..77b6050a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,7 @@ tests/*.log
tests/*.trs
tests/test-suite.log
tests/test_BlockSpecial
+tests/test_PasswordRAMStore
tests/test_PipeCapture
tests/test_dummy
xmldocs.make
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1274cab5..0c1cfb61 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -11,8 +11,9 @@ LDADD = \
# Programs to be built by "make check"
check_PROGRAMS = \
- test_dummy \
- test_BlockSpecial \
+ test_dummy \
+ test_BlockSpecial \
+ test_PasswordRAMStore \
test_PipeCapture
# Test cases to be run by "make check"
@@ -25,6 +26,11 @@ test_BlockSpecial_LDADD = \
$(top_builddir)/src/BlockSpecial.$(OBJEXT) \
$(LDADD)
+test_PasswordRAMStore_SOURCES = test_PasswordRAMStore.cc
+test_PasswordRAMStore_LDADD = \
+ $(top_builddir)/src/PasswordRAMStore.$(OBJEXT) \
+ $(LDADD)
+
test_PipeCapture_SOURCES = test_PipeCapture.cc
test_PipeCapture_LDADD = \
$(top_builddir)/src/PipeCapture.$(OBJEXT) \
diff --git a/tests/test_PasswordRAMStore.cc b/tests/test_PasswordRAMStore.cc
new file mode 100644
index 00000000..78a43609
--- /dev/null
+++ b/tests/test_PasswordRAMStore.cc
@@ -0,0 +1,265 @@
+/* Copyright (C) 2017 Mike Fleetwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+/* Test PasswordRAMStore
+ *
+ * WARNING:
+ * This unit testing only calls the public API of the PasswordRAMStore so would normally
+ * be black box testing, however knowledge of the implementation is used to look through
+ * the API to the internals making this white box testing. This is so that the hidden
+ * behaviour of zeroing password storing memory before and after use can be tested.
+ * FIXME: Can't currently test memory is zeroed when the password store is destroyed
+ * because destructor zeros memory AND removes it from the process address space.
+ *
+ * WARNING:
+ * Each test fixture would normally initialise separate resources to make the tests
+ * independent of each other. However the password store is a single long lived shared
+ * resource. Therefore, so that each test fixture is independent of all the others, the
+ * password store must be returned to it's original state of being empty before each
+ * fixture completes.
+ *
+ * Reference:
+ * Google Test, Advanced Guide, Sharing Resources Between Tests in the Same Test Case
+ * https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#sharing-resources-between-tests-in-the-same-test-case
+ */
+
+#include "PasswordRAMStore.h"
+#include "gtest/gtest.h"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace GParted
+{
+
+// Generate repeatable unique keys
+static const char * gen_key( unsigned int i )
+{
+ static char buf[14];
+ snprintf( buf, sizeof( buf ), "key%u", i );
+ return buf;
+}
+
+// Generate repeatable "passwords" exactly 20 characters long
+static const char * gen_passwd( unsigned int i )
+{
+ static char buf[21];
+ snprintf( buf, sizeof( buf ), "password%03u ", i );
+ return buf;
+}
+
+static bool mem_is_zero( const char * mem, size_t len )
+{
+ while ( len-- > 0 )
+ {
+ if ( *mem++ != '\0' )
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Explicit test fixture class for common setup and sharing of the underlying password
+// store address.
+class PasswordRAMStoreTest : public ::testing::Test
+{
+protected:
+ PasswordRAMStoreTest() : looked_up_pw( NULL ) {};
+
+ static void SetUpTestCase();
+
+ static const char * protected_mem;
+
+ std::string pw;
+ const char * looked_up_pw;
+ size_t looked_up_len;
+};
+
+// Initialise test case class static member.
+const char * PasswordRAMStoreTest::protected_mem = NULL;
+
+const size_t ProtectedMemSize = 4096; // [Implementation knowledge: size]
+
+// Common test case initialisation discovering the underlying password store address.
+void PasswordRAMStoreTest::SetUpTestCase()
+{
+ const Glib::ustring key = "key-setup";
+ bool success = PasswordRAMStore::insert( key, "" );
+ ASSERT_TRUE( success ) << __func__ << "(): Insert \"" << key << "\" password failed";
+
+ // First password is stored at the start of the locked memory.
+ // [Implementation knowledge: locked memory layout]
+ protected_mem = PasswordRAMStore::lookup( key );
+ ASSERT_TRUE( protected_mem != NULL ) << __func__
+ << "(): Find \"" << key << "\" password failed";
+
+ success = PasswordRAMStore::erase( key );
+ ASSERT_TRUE( success ) << __func__ << "(): Erase \"" << key << "\" password failed";
+}
+
+TEST_F( PasswordRAMStoreTest, Initialisation )
+{
+ // Test locked memory is initialised with all zeros.
+ EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, UnknownPasswordLookup )
+{
+ // Test lookup of non-existent password fails.
+ looked_up_pw = PasswordRAMStore::lookup( "key-unknown" );
+ EXPECT_TRUE( looked_up_pw == NULL );
+}
+
+TEST_F( PasswordRAMStoreTest, UnknownPasswordErasure )
+{
+ // Test erase non-existent password fails.
+ EXPECT_FALSE( PasswordRAMStore::erase( "key-unknown" ) );
+}
+
+TEST_F( PasswordRAMStoreTest, SinglePassword )
+{
+ // Test a single password can be stored, looked up and erased (and zeroed).
+ pw = "password";
+ EXPECT_TRUE( PasswordRAMStore::insert( "key-single", pw.c_str() ) );
+
+ looked_up_pw = PasswordRAMStore::lookup( "key-single" );
+ EXPECT_STREQ( pw.c_str(), looked_up_pw );
+
+ looked_up_len = strlen( looked_up_pw );
+ EXPECT_TRUE( PasswordRAMStore::erase( "key-single" ) );
+ EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+
+ EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, DuplicatePassword )
+{
+ // Test storing a password with a duplicate key fails (and single password can be
+ // erased and zeroed).
+ pw = "password";
+ EXPECT_TRUE( PasswordRAMStore::insert( "key-single", pw.c_str() ) );
+
+ looked_up_pw = PasswordRAMStore::lookup( "key-single" );
+ EXPECT_STREQ( pw.c_str(), looked_up_pw );
+
+ EXPECT_FALSE( PasswordRAMStore::insert( "key-single", pw.c_str() ) );
+
+ looked_up_len = strlen( looked_up_pw );
+ EXPECT_TRUE( PasswordRAMStore::erase( "key-single" ) );
+ EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+
+ EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, OneHundredPasswordsForwards )
+{
+ // Test 100, 20 character passwords can be stored, looked up and erased (and
+ // zeroed). Passwords are erased forwards (first stored to last stored).
+ unsigned int i;
+ for ( i = 0 ; i < 100 ; i ++ )
+ {
+ pw = gen_passwd( i );
+ EXPECT_TRUE( PasswordRAMStore::insert( gen_key(i), pw.c_str() ) );
+ }
+
+ for ( i = 0 ; i < 100 ; i ++ )
+ {
+ pw = gen_passwd( i );
+ looked_up_pw = PasswordRAMStore::lookup( gen_key(i) );
+ EXPECT_STREQ( pw.c_str(), looked_up_pw );
+ }
+
+ for ( i = 0 ; i < 100 ; i ++ )
+ {
+ pw = gen_passwd( i );
+ looked_up_pw = PasswordRAMStore::lookup( gen_key(i) );
+ looked_up_len = strlen( looked_up_pw );
+ EXPECT_TRUE( PasswordRAMStore::erase( gen_key(i) ) );
+ EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+ }
+
+ EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, OneHundredPasswordsBackwards )
+{
+ // Test 100, 20 character passwords can be stored, looked up and erased (and
+ // zeroed). Passwords are erased backwards (last stored to first stored).
+ unsigned int i;
+ for ( i = 0 ; i < 100 ; i ++ )
+ {
+ pw = gen_passwd( i );
+ EXPECT_TRUE( PasswordRAMStore::insert( gen_key(i), pw.c_str() ) );
+ }
+
+ for ( i = 0 ; i < 100 ; i ++ )
+ {
+ pw = gen_passwd( i );
+ looked_up_pw = PasswordRAMStore::lookup( gen_key(i) );
+ EXPECT_STREQ( pw.c_str(), looked_up_pw );
+ }
+
+ for ( i = 100; i-- > 0 ; )
+ {
+ pw = gen_passwd( i );
+ looked_up_pw = PasswordRAMStore::lookup( gen_key(i) );
+ looked_up_len = strlen( looked_up_pw );
+ EXPECT_TRUE( PasswordRAMStore::erase( gen_key(i) ) );
+ EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+ }
+
+ EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, LongPassword )
+{
+ // Test a 4095 byte password can be stored and looked up (and erased and zeroed).
+ // [Implementation knowledge: size]
+ pw = std::string( ProtectedMemSize-1, 'X' );
+
+ EXPECT_TRUE( PasswordRAMStore::insert( "key-long", pw.c_str() ) );
+
+ looked_up_pw = PasswordRAMStore::lookup( "key-long" );
+ EXPECT_STREQ( pw.c_str(), looked_up_pw );
+
+ looked_up_len = strlen( looked_up_pw );
+ EXPECT_TRUE( PasswordRAMStore::erase( "key-long" ) );
+ EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+
+ EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, TooLongPassword )
+{
+ // Test a 4096 byte password can't be stored nor looked up or erased.
+ // [Implementation knowledge: size]
+ std::string pw = std::string( ProtectedMemSize, 'X' );
+
+ EXPECT_FALSE( PasswordRAMStore::insert( "key-too-long", pw.c_str() ) );
+ EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+
+ looked_up_pw = PasswordRAMStore::lookup( "key-too-long" );
+ EXPECT_TRUE( looked_up_pw == NULL );
+
+ EXPECT_FALSE( PasswordRAMStore::erase( "key-long" ) );
+ EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+} // namespace GParted