/* 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
*
* NOTE:
* As well as calling the public API of PasswordRAMStore this unit testing also accesses
* the private members of PasswordRAMStore and uses knowledge of the implementation,
* making this white box testing. This is so that the hidden behaviour of zeroing
* password storing memory before and after use can be tested.
*
* 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%1000);
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 void erase_all() { PasswordRAMStore::erase_all(); };
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 recording the underlying password store address.
void PasswordRAMStoreTest::SetUpTestCase()
{
protected_mem = PasswordRAMStore::get_protected_mem();
ASSERT_TRUE( protected_mem != NULL ) << __func__ << "(): No locked virtual memory for password RAM store";
}
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::store( "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, IdenticalPassword )
{
// Test a password can be saved twice with the same key and looked up (and the
// single password can be erased and zeroed).
pw = "password";
EXPECT_TRUE( PasswordRAMStore::store( "key-single", pw.c_str() ) );
EXPECT_TRUE( PasswordRAMStore::store( "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, ReplacePassword )
{
// Test a password can be saved and looked up, then saved again with a different
// password using the same key and looked up (and the single replaced password
// is erased and zeroed).
pw = "password";
EXPECT_TRUE( PasswordRAMStore::store( "key-single", pw.c_str() ) );
looked_up_pw = PasswordRAMStore::lookup( "key-single" );
EXPECT_STREQ( pw.c_str(), looked_up_pw );
pw = "password2";
EXPECT_TRUE( PasswordRAMStore::store( "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, 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::store( 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::store( 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::store( "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::store( "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-too-long"));
EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
}
TEST_F( PasswordRAMStoreTest, TotalErasure )
{
// Test all passwords are erased (and zeroed using the same code called during
// password cache destruction).
unsigned int i;
for ( i = 0 ; i < 100 ; i ++ )
{
pw = gen_passwd( i );
EXPECT_TRUE( PasswordRAMStore::store( gen_key(i), pw.c_str() ) );
}
EXPECT_FALSE( mem_is_zero( protected_mem, ProtectedMemSize ) );
PasswordRAMStoreTest::erase_all();
EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
}
} // namespace GParted