Improve reading/writing overall file

* Allow to hash password N times using SHA-256
* Use flags instead of bool parameter
* Expose extended header
* Fix bugs when reading/writing extended headers
* Store password as std::string

Reading files written with previous versions is still
possible. If new features are not used it is also possible
to read new files with previous versions.
This commit is contained in:
Martchus 2018-12-18 23:17:19 +01:00
parent d9edcc4083
commit 448c5b1a37
9 changed files with 404 additions and 76 deletions

View File

@ -29,6 +29,7 @@ set(TEST_SRC_FILES
tests/entrytests.cpp
tests/fieldtests.cpp
tests/opensslrandomdevice.cpp
tests/opensslutils.cpp
)
set(DOC_FILES
@ -42,9 +43,9 @@ set(META_APP_NAME "Passwordfile library")
set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
set(META_APP_DESCRIPTION "C++ library to read/write passwords from/to encrypted files")
set(META_VERSION_MAJOR 3)
set(META_VERSION_MINOR 2)
set(META_VERSION_PATCH 1)
set(META_VERSION_MAJOR 4)
set(META_VERSION_MINOR 0)
set(META_VERSION_PATCH 0)
set(META_PUBLIC_SHARED_LIB_DEPENDS c++utilities)
set(META_PUBLIC_STATIC_LIB_DEPENDS c++utilities_static)

View File

@ -3,6 +3,9 @@
#include "./entry.h"
#include "./parsingexception.h"
#include "../util/openssl.h"
#include "../util/opensslrandomdevice.h"
#include <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/catchiofailure.h>
@ -63,6 +66,7 @@ PasswordFile::PasswordFile(const string &path, const string &password)
*/
PasswordFile::PasswordFile(const PasswordFile &other)
: m_path(other.m_path)
, m_password(other.m_password)
, m_rootEntry(other.m_rootEntry ? make_unique<NodeEntry>(*other.m_rootEntry) : nullptr)
, m_extendedHeader(other.m_extendedHeader)
, m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
@ -70,7 +74,6 @@ PasswordFile::PasswordFile(const PasswordFile &other)
, m_fwriter(BinaryWriter(&m_file))
{
m_file.exceptions(ios_base::failbit | ios_base::badbit);
memcpy(m_password, other.m_password, 32);
}
/*!
@ -78,6 +81,7 @@ PasswordFile::PasswordFile(const PasswordFile &other)
*/
PasswordFile::PasswordFile(PasswordFile &&other)
: m_path(move(other.m_path))
, m_password(move(other.m_password))
, m_rootEntry(move(other.m_rootEntry))
, m_extendedHeader(move(other.m_extendedHeader))
, m_encryptedExtendedHeader(move(other.m_encryptedExtendedHeader))
@ -85,7 +89,6 @@ PasswordFile::PasswordFile(PasswordFile &&other)
, m_freader(BinaryReader(&m_file))
, m_fwriter(BinaryWriter(&m_file))
{
memcpy(m_password, other.m_password, 32);
}
/*!
@ -99,13 +102,14 @@ PasswordFile::~PasswordFile()
* \brief Opens the file. Does not load the contents (see load()).
* \throws Throws ios_base::failure when an IO error occurs.
*/
void PasswordFile::open(bool readOnly)
void PasswordFile::open(PasswordFileOpenFlags options)
{
close();
if (m_path.empty()) {
throwIoFailure("Unable to open file because path is emtpy.");
}
m_file.open(m_path, readOnly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
m_file.open(
m_path, options & PasswordFileOpenFlags::ReadOnly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
opened();
}
@ -169,11 +173,11 @@ void PasswordFile::load()
// check version and flags (used in version 0x3 only)
const auto version = m_freader.readUInt32LE();
if (version != 0x0U && version != 0x1U && version != 0x2U && version != 0x3U && version != 0x4U && version != 0x5U) {
throw ParsingException("Version is unknown.");
if (version > 0x6U) {
throw ParsingException(argsToString("Version \"", version, "\" is unknown. Only versions 0 to 6 are supported."));
}
bool decrypterUsed, ivUsed, compressionUsed;
if (version == 0x3U) {
if (version >= 0x3U) {
const auto flags = m_freader.readByte();
decrypterUsed = flags & 0x80;
ivUsed = flags & 0x40;
@ -190,6 +194,8 @@ void PasswordFile::load()
if (version >= 0x4U) {
uint16 extendedHeaderSize = m_freader.readUInt16BE();
m_extendedHeader = m_freader.readString(extendedHeaderSize);
} else {
m_extendedHeader.clear();
}
// get length
@ -198,7 +204,17 @@ void PasswordFile::load()
auto remainingSize = static_cast<size_t>(m_file.tellg()) - headerSize;
m_file.seekg(static_cast<streamoff>(headerSize), ios_base::beg);
// read file
// read hash count
uint32_t hashCount = 0U;
if (version >= 0x6U && decrypterUsed) {
if (remainingSize < 4) {
throw ParsingException("Hash count truncated.");
}
hashCount = m_freader.readUInt32BE();
remainingSize -= 4;
}
// read IV
unsigned char iv[aes256cbcIvSize] = { 0 };
if (decrypterUsed && ivUsed) {
if (remainingSize < aes256cbcIvSize) {
@ -220,12 +236,23 @@ void PasswordFile::load()
throw CryptoException("Size exceeds limit.");
}
// prepare password
Util::OpenSsl::Sha256Sum password;
if (hashCount) {
// hash the password as often as it has been hashed when writing the file
password = Util::OpenSsl::computeSha256Sum(reinterpret_cast<unsigned const char *>(m_password.data()), m_password.size());
for (uint32_t i = 1; i < hashCount; ++i) {
password = Util::OpenSsl::computeSha256Sum(password.data, Util::OpenSsl::Sha256Sum::size);
}
} else {
m_password.copy(reinterpret_cast<char *>(password.data), Util::OpenSsl::Sha256Sum::size);
}
// initiate ctx, decrypt data
EVP_CIPHER_CTX *ctx = nullptr;
decryptedData.resize(remainingSize + 32);
int outlen1, outlen2;
if ((ctx = EVP_CIPHER_CTX_new()) == nullptr
|| EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
if ((ctx = EVP_CIPHER_CTX_new()) == nullptr || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, password.data, iv) != 1
|| EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()), &outlen1,
reinterpret_cast<unsigned char *>(rawData.data()), static_cast<int>(remainingSize))
!= 1
@ -297,8 +324,11 @@ void PasswordFile::load()
decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast<streamsize>(remainingSize));
#endif
if (version >= 0x5u) {
const auto extendedHeaderSize = m_freader.readUInt16BE();
m_encryptedExtendedHeader = m_freader.readString(extendedHeaderSize);
BinaryReader reader(&decryptedStream);
const auto extendedHeaderSize = reader.readUInt16BE();
m_encryptedExtendedHeader = reader.readString(extendedHeaderSize);
} else {
m_encryptedExtendedHeader.clear();
}
m_rootEntry.reset(new NodeEntry(decryptedStream));
} catch (...) {
@ -310,15 +340,29 @@ void PasswordFile::load()
}
}
/*!
* \brief Returns the minimum file version required to write the current instance with the specified \a options.
*/
uint32 PasswordFile::mininumVersion(PasswordFileSaveFlags options) const
{
if (options & PasswordFileSaveFlags::PasswordHashing) {
return 0x6U; // password hashing requires at least version 6
} else if (!m_encryptedExtendedHeader.empty()) {
return 0x5U; // encrypted extended header requires at least version 5
} else if (!m_extendedHeader.empty()) {
return 0x4U; // regular extended header requires at least version 4
}
return 0x3U; // lowest supported version by the serializer
}
/*!
* \brief Writes the current root entry to the file under path() replacing its previous contents.
* \param useEncryption Specifies whether encryption should be used.
* \param useCompression Specifies whether compression should be used.
* \param options Specify the features (like encryption and compression) to be used.
* \throws Throws ios_base::failure when an IO error occurs.
* \throws Throws runtime_error when no root entry is present or a compression error occurs.
* \throws Throws Io::CryptoException when an encryption error occurs.
*/
void PasswordFile::save(bool useEncryption, bool useCompression)
void PasswordFile::save(PasswordFileSaveFlags options)
{
if (!m_rootEntry) {
throw runtime_error("Root entry has not been created.");
@ -333,19 +377,18 @@ void PasswordFile::save(bool useEncryption, bool useCompression)
m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
}
write(useEncryption, useCompression);
write(options);
m_file.flush();
}
/*!
* \brief Writes the current root entry to the file which is assumed to be opened and writeable.
* \param useEncryption Specifies whether encryption should be used.
* \param useCompression Specifies whether compression should be used.
* \param options Specify the features (like encryption and compression) to be used.
* \throws Throws ios_base::failure when an IO error occurs.
* \throws Throws runtime_error when no root entry is present or a compression error occurs.
* \throws Throws Io::CryptoException when an encryption error occurs.
*/
void PasswordFile::write(bool useEncryption, bool useCompression)
void PasswordFile::write(PasswordFileSaveFlags options)
{
if (!m_rootEntry) {
throw runtime_error("Root entry has not been created.");
@ -354,19 +397,25 @@ void PasswordFile::write(bool useEncryption, bool useCompression)
// write magic number
m_fwriter.writeUInt32LE(0x7770616DU);
// write version, extended header requires version 4, encrypted extended header required version 5
m_fwriter.writeUInt32LE(m_extendedHeader.empty() && m_encryptedExtendedHeader.empty() ? 0x3U : (m_encryptedExtendedHeader.empty() ? 0x4U : 0x5U));
// write version
const auto version = mininumVersion(options);
m_fwriter.writeUInt32LE(version);
// write flags
byte flags = 0x00;
if (useEncryption) {
if (options & PasswordFileSaveFlags::Encryption) {
flags |= 0x80 | 0x40;
}
if (useCompression) {
if (options & PasswordFileSaveFlags::Compression) {
flags |= 0x20;
}
m_fwriter.writeByte(flags);
// write extened header
if (!m_extendedHeader.empty()) {
if (version >= 0x4U) {
if (m_extendedHeader.size() > numeric_limits<uint16>::max()) {
throw runtime_error("Extended header exceeds maximum size.");
}
m_fwriter.writeUInt16BE(static_cast<uint16>(m_extendedHeader.size()));
m_fwriter.writeString(m_extendedHeader);
}
@ -376,9 +425,13 @@ void PasswordFile::write(bool useEncryption, bool useCompression)
buffstr.exceptions(ios_base::failbit | ios_base::badbit);
// write encrypted extened header
if (!m_encryptedExtendedHeader.empty()) {
m_fwriter.writeUInt16BE(static_cast<uint16>(m_encryptedExtendedHeader.size()));
m_fwriter.writeString(m_encryptedExtendedHeader);
if (version >= 0x5U) {
if (m_encryptedExtendedHeader.size() > numeric_limits<uint16>::max()) {
throw runtime_error("Encrypted extended header exceeds maximum size.");
}
BinaryWriter buffstrWriter(&buffstr);
buffstrWriter.writeUInt16BE(static_cast<uint16>(m_encryptedExtendedHeader.size()));
buffstrWriter.writeString(m_encryptedExtendedHeader);
}
m_rootEntry->make(buffstr);
buffstr.seekp(0, ios_base::end);
@ -391,7 +444,7 @@ void PasswordFile::write(bool useEncryption, bool useCompression)
vector<char> encryptedData;
// compress data
if (useCompression) {
if (options & PasswordFileSaveFlags::Compression) {
uLongf compressedSize = compressBound(size);
encryptedData.resize(8 + compressedSize);
ConversionUtilities::LE::getBytes(static_cast<uint64>(size), encryptedData.data());
@ -412,19 +465,32 @@ void PasswordFile::write(bool useEncryption, bool useCompression)
}
// write data without encryption
if (!useEncryption) {
if (!(options & PasswordFileSaveFlags::Encryption)) {
// write data to file
m_file.write(decryptedData.data(), static_cast<streamsize>(size));
return;
}
// prepare password
Util::OpenSsl::Sha256Sum password;
const uint32_t hashCount = (options & PasswordFileSaveFlags::PasswordHashing) ? Util::OpenSsl::generateRandomNumber(1, 100) : 0u;
if (hashCount) {
// hash password a few times
password = Util::OpenSsl::computeSha256Sum(reinterpret_cast<unsigned const char *>(m_password.data()), m_password.size());
for (uint32_t i = 1; i < hashCount; ++i) {
password = Util::OpenSsl::computeSha256Sum(password.data, Util::OpenSsl::Sha256Sum::size);
}
} else {
m_password.copy(reinterpret_cast<char *>(password.data), Util::OpenSsl::Sha256Sum::size);
}
// initiate ctx, encrypt data
EVP_CIPHER_CTX *ctx = nullptr;
unsigned char iv[aes256cbcIvSize];
int outlen1, outlen2;
encryptedData.resize(size + 32);
if (RAND_bytes(iv, aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) == nullptr
|| EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
|| EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, password.data, iv) != 1
|| EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()), &outlen1,
reinterpret_cast<unsigned char *>(decryptedData.data()), static_cast<int>(size))
!= 1
@ -450,6 +516,9 @@ void PasswordFile::write(bool useEncryption, bool useCompression)
}
// write encrypted data to file
if (version >= 0x6U) {
m_fwriter.writeUInt32BE(hashCount);
}
m_file.write(reinterpret_cast<char *>(iv), aes256cbcIvSize);
m_file.write(encryptedData.data(), static_cast<streamsize>(outlen1 + outlen2));
}
@ -610,18 +679,25 @@ void PasswordFile::clearPath()
/*!
* \brief Returns the current password. It will be used when loading or saving using encryption.
*/
const char *PasswordFile::password() const
const std::string &PasswordFile::password() const
{
return m_password;
}
/*!
* \brief Sets the current password. It will be used when loading or saving using encryption.
* \brief Sets the current password. It will be used when loading an encrypted file or when saving using encryption.
*/
void PasswordFile::setPassword(const string &value)
void PasswordFile::setPassword(const string &password)
{
clearPassword();
value.copy(m_password, 32, 0);
m_password = password;
}
/*!
* \brief Sets the current password. It will be used when loading an encrypted file or when saving using encryption.
*/
void PasswordFile::setPassword(const char *password, const size_t passwordSize)
{
m_password.assign(password, passwordSize);
}
/*!
@ -629,7 +705,7 @@ void PasswordFile::setPassword(const string &value)
*/
void PasswordFile::clearPassword()
{
memset(m_password, 0, 32);
m_password.clear();
}
/*!
@ -642,16 +718,16 @@ bool PasswordFile::isEncryptionUsed()
}
m_file.seekg(0);
//check magic number
// check magic number
if (m_freader.readUInt32LE() != 0x7770616DU) {
return false;
}
//check version
// check version
const auto version = m_freader.readUInt32LE();
if (version == 0x1U || version == 0x2U) {
return true;
} else if (version == 0x3U) {
} else if (version >= 0x3U) {
return m_freader.readByte() & 0x80;
} else {
return false;
@ -666,6 +742,38 @@ bool PasswordFile::isOpen() const
return m_file.is_open();
}
/*!
* \brief Returns the extended header.
*/
string &PasswordFile::extendedHeader()
{
return m_extendedHeader;
}
/*!
* \brief Returns the extended header.
*/
const string &PasswordFile::extendedHeader() const
{
return m_extendedHeader;
}
/*!
* \brief Returns the encrypted extended header.
*/
string &PasswordFile::encryptedExtendedHeader()
{
return m_encryptedExtendedHeader;
}
/*!
* \brief Returns the encrypted extended header.
*/
const string &PasswordFile::encryptedExtendedHeader() const
{
return m_encryptedExtendedHeader;
}
/*!
* \brief Returns the size of the file if the file is open; otherwise returns zero.
*/

View File

@ -16,6 +16,56 @@ namespace Io {
class NodeEntry;
enum class PasswordFileOpenFlags : uint64 {
None = 0,
ReadOnly = 1,
Default = None,
};
constexpr PasswordFileOpenFlags operator|(PasswordFileOpenFlags lhs, PasswordFileOpenFlags rhs)
{
return static_cast<PasswordFileOpenFlags>(
static_cast<std::underlying_type<PasswordFileOpenFlags>::type>(lhs) | static_cast<std::underlying_type<PasswordFileOpenFlags>::type>(rhs));
}
constexpr PasswordFileOpenFlags operator|=(PasswordFileOpenFlags lhs, PasswordFileOpenFlags rhs)
{
return static_cast<PasswordFileOpenFlags>(
static_cast<std::underlying_type<PasswordFileOpenFlags>::type>(lhs) | static_cast<std::underlying_type<PasswordFileOpenFlags>::type>(rhs));
}
constexpr bool operator&(PasswordFileOpenFlags lhs, PasswordFileOpenFlags rhs)
{
return static_cast<bool>(
static_cast<std::underlying_type<PasswordFileOpenFlags>::type>(lhs) & static_cast<std::underlying_type<PasswordFileOpenFlags>::type>(rhs));
}
enum class PasswordFileSaveFlags : uint64 {
None = 0,
Encryption = 1,
Compression = 2,
PasswordHashing = 4,
Default = Encryption | Compression | PasswordHashing,
};
constexpr PasswordFileSaveFlags operator|(PasswordFileSaveFlags lhs, PasswordFileSaveFlags rhs)
{
return static_cast<PasswordFileSaveFlags>(
static_cast<std::underlying_type<PasswordFileSaveFlags>::type>(lhs) | static_cast<std::underlying_type<PasswordFileSaveFlags>::type>(rhs));
}
constexpr PasswordFileSaveFlags operator|=(PasswordFileSaveFlags lhs, PasswordFileSaveFlags rhs)
{
return static_cast<PasswordFileSaveFlags>(
static_cast<std::underlying_type<PasswordFileSaveFlags>::type>(lhs) | static_cast<std::underlying_type<PasswordFileSaveFlags>::type>(rhs));
}
constexpr bool operator&(PasswordFileSaveFlags lhs, PasswordFileSaveFlags rhs)
{
return static_cast<bool>(
static_cast<std::underlying_type<PasswordFileSaveFlags>::type>(lhs) & static_cast<std::underlying_type<PasswordFileSaveFlags>::type>(rhs));
}
class PASSWORD_FILE_EXPORT PasswordFile {
public:
explicit PasswordFile();
@ -24,15 +74,15 @@ public:
PasswordFile(PasswordFile &&other);
~PasswordFile();
IoUtilities::NativeFileStream &fileStream();
void open(bool readOnly = false);
void open(PasswordFileOpenFlags options = PasswordFileOpenFlags::Default);
void opened();
void generateRootEntry();
void create();
void close();
void load();
// FIXME: use flags in v4
void save(bool useEncryption = true, bool useCompression = true);
void write(bool useEncryption = true, bool useCompression = true);
uint32 mininumVersion(PasswordFileSaveFlags options) const;
void save(PasswordFileSaveFlags options = PasswordFileSaveFlags::Default);
void write(PasswordFileSaveFlags options = PasswordFileSaveFlags::Default);
void clearEntries();
void clear();
void exportToTextfile(const std::string &targetPath) const;
@ -41,18 +91,23 @@ public:
const NodeEntry *rootEntry() const;
NodeEntry *rootEntry();
const std::string &path() const;
const char *password() const;
const std::string &password() const;
void setPath(const std::string &value);
void clearPath();
void setPassword(const std::string &value);
void setPassword(const std::string &password);
void setPassword(const char *password, const std::size_t passwordSize);
void clearPassword();
bool isEncryptionUsed();
bool isOpen() const;
std::string &extendedHeader();
const std::string &extendedHeader() const;
std::string &encryptedExtendedHeader();
const std::string &encryptedExtendedHeader() const;
std::size_t size();
private:
std::string m_path;
char m_password[32];
std::string m_password;
std::unique_ptr<NodeEntry> m_rootEntry;
std::string m_extendedHeader;
std::string m_encryptedExtendedHeader;

64
tests/opensslutils.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "../util/openssl.h"
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/tests/testutils.h>
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <random>
using namespace std;
using namespace Util::OpenSsl;
using namespace ConversionUtilities;
using namespace TestUtilities::Literals;
using namespace CPPUNIT_NS;
/*!
* \brief The OpenSslUtilsTests class tests the functions in the Util::OpenSsl namespace.
*/
class OpenSslUtilsTests : public TestFixture {
CPPUNIT_TEST_SUITE(OpenSslUtilsTests);
CPPUNIT_TEST(testComputeSha256Sum);
CPPUNIT_TEST(testGenerateRandomNumber);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testComputeSha256Sum();
void testGenerateRandomNumber();
};
CPPUNIT_TEST_SUITE_REGISTRATION(OpenSslUtilsTests);
void OpenSslUtilsTests::setUp()
{
}
void OpenSslUtilsTests::tearDown()
{
}
void OpenSslUtilsTests::testComputeSha256Sum()
{
const char someString[] = "hello world";
Sha256Sum sum = computeSha256Sum(reinterpret_cast<unsigned const char *>(someString), sizeof(someString));
string sumAsHex;
sumAsHex.reserve(64);
for (unsigned char hashNumber : sum.data) {
const string digits = numberToString(hashNumber, 16);
sumAsHex.push_back(digits.size() < 2 ? '0' : digits.front());
sumAsHex.push_back(digits.back());
}
CPPUNIT_ASSERT_EQUAL("430646847E70344C09F58739E99D5BC96EAC8D5FE7295CF196B986279876BF9B"s, sumAsHex);
// note that the termination char is hashed as well
}
void OpenSslUtilsTests::testGenerateRandomNumber()
{
CPPUNIT_ASSERT_EQUAL(static_cast<uint32_t>(0u), generateRandomNumber(0u, 0u));
CPPUNIT_ASSERT_EQUAL(static_cast<uint32_t>(1u), generateRandomNumber(1u, 1u));
}

View File

@ -19,9 +19,8 @@ using namespace CPPUNIT_NS;
class PasswordFileTests : public TestFixture {
CPPUNIT_TEST_SUITE(PasswordFileTests);
CPPUNIT_TEST(testReading);
#ifdef PLATFORM_UNIX
CPPUNIT_TEST(testWriting);
#endif
CPPUNIT_TEST(testBasicWriting);
CPPUNIT_TEST(testExtendedWriting);
CPPUNIT_TEST_SUITE_END();
public:
@ -29,11 +28,10 @@ public:
void tearDown();
void testReading();
void testReading(
const string &testfile1path, const string &testfile1password, const string &testfile2, const string &testfile2password, bool testfile2Mod);
#ifdef PLATFORM_UNIX
void testWriting();
#endif
void testReading(const string &context, const string &testfile1path, const string &testfile1password, const string &testfile2,
const string &testfile2password, bool testfile2Mod, bool extendedHeaderMod);
void testBasicWriting();
void testExtendedWriting();
};
CPPUNIT_TEST_SUITE_REGISTRATION(PasswordFileTests);
@ -51,19 +49,20 @@ void PasswordFileTests::tearDown()
*/
void PasswordFileTests::testReading()
{
testReading(TestUtilities::testFilePath("testfile1.pwmgr"), "123456", TestUtilities::testFilePath("testfile2.pwmgr"), string(), false);
testReading(
"read", TestUtilities::testFilePath("testfile1.pwmgr"), "123456", TestUtilities::testFilePath("testfile2.pwmgr"), string(), false, false);
}
void PasswordFileTests::testReading(
const string &testfile1path, const string &testfile1password, const string &testfile2, const string &testfile2password, bool testfile2Mod)
void PasswordFileTests::testReading(const string &context, const string &testfile1path, const string &testfile1password, const string &testfile2,
const string &testfile2password, bool testfile2Mod, bool extendedHeaderMod)
{
PasswordFile file;
// open testfile 1 ...
file.setPath(testfile1path);
file.open(true);
file.open(PasswordFileOpenFlags::ReadOnly);
CPPUNIT_ASSERT_EQUAL(!testfile1password.empty(), file.isEncryptionUsed());
CPPUNIT_ASSERT_EQUAL_MESSAGE(context, !testfile1password.empty(), file.isEncryptionUsed());
// attempt to decrypt using a wrong password
file.setPassword(testfile1password + "asdf");
if (!testfile1password.empty()) {
@ -104,9 +103,16 @@ void PasswordFileTests::testReading(
// test testaccount3
CPPUNIT_ASSERT_EQUAL("testaccount3"s, rootEntry->children()[3]->label());
if (extendedHeaderMod) {
CPPUNIT_ASSERT_EQUAL("foo"s, file.extendedHeader());
} else {
CPPUNIT_ASSERT_EQUAL(""s, file.extendedHeader());
}
CPPUNIT_ASSERT_EQUAL(""s, file.encryptedExtendedHeader());
// open testfile 2
file.setPath(testfile2);
file.open(true);
file.open(PasswordFileOpenFlags::ReadOnly);
CPPUNIT_ASSERT_EQUAL(!testfile2password.empty(), file.isEncryptionUsed());
file.setPassword(testfile2password);
@ -120,13 +126,19 @@ void PasswordFileTests::testReading(
CPPUNIT_ASSERT_EQUAL("testfile2"s, rootEntry2->label());
CPPUNIT_ASSERT_EQUAL(1_st, rootEntry2->children().size());
}
if (extendedHeaderMod) {
CPPUNIT_ASSERT_EQUAL("foo"s, file.extendedHeader());
CPPUNIT_ASSERT_EQUAL("bar"s, file.encryptedExtendedHeader());
} else {
CPPUNIT_ASSERT_EQUAL(""s, file.extendedHeader());
CPPUNIT_ASSERT_EQUAL(""s, file.encryptedExtendedHeader());
}
}
#ifdef PLATFORM_UNIX
/*!
* \brief Tests writing and reading.
* \brief Tests writing (and reading again) using basic features.
*/
void PasswordFileTests::testWriting()
void PasswordFileTests::testBasicWriting()
{
const string testfile1 = TestUtilities::workingCopyPath("testfile1.pwmgr");
const string testfile2 = TestUtilities::workingCopyPath("testfile2.pwmgr");
@ -134,26 +146,66 @@ void PasswordFileTests::testWriting()
// resave testfile 1
file.setPath(testfile1);
file.open(false);
file.open();
file.setPassword("123456");
file.load();
file.doBackup();
file.save(false, true);
file.save(PasswordFileSaveFlags::Compression);
// resave testfile 2
file.setPath(testfile2);
file.open(false);
file.open();
file.load();
file.rootEntry()->setLabel("testfile2 - modified");
new AccountEntry("newAccount", file.rootEntry());
file.setPassword("654321");
file.doBackup();
file.save(true, false);
file.save(PasswordFileSaveFlags::Encryption);
// check results using the reading test
testReading(testfile1, string(), testfile2, "654321", true);
testReading("basic writing", testfile1, string(), testfile2, "654321", true, false);
// check backup files
testReading(testfile1 + ".backup", "123456", testfile2 + ".backup", string(), false);
testReading("basic writing", testfile1 + ".backup", "123456", testfile2 + ".backup", string(), false, false);
}
/*!
* \brief Tests writing (and reading again) using extended features.
*/
void PasswordFileTests::testExtendedWriting()
{
const string testfile1 = TestUtilities::workingCopyPath("testfile1.pwmgr");
const string testfile2 = TestUtilities::workingCopyPath("testfile2.pwmgr");
PasswordFile file;
// resave testfile 1
file.setPath(testfile1);
file.open();
file.setPassword("123456");
file.load();
CPPUNIT_ASSERT_EQUAL(""s, file.extendedHeader());
CPPUNIT_ASSERT_EQUAL(""s, file.encryptedExtendedHeader());
file.doBackup();
file.extendedHeader() = "foo";
file.save(PasswordFileSaveFlags::Encryption | PasswordFileSaveFlags::PasswordHashing);
// resave testfile 2
file.setPath(testfile2);
file.open();
file.load();
CPPUNIT_ASSERT_EQUAL(""s, file.extendedHeader());
CPPUNIT_ASSERT_EQUAL(""s, file.encryptedExtendedHeader());
file.rootEntry()->setLabel("testfile2 - modified");
new AccountEntry("newAccount", file.rootEntry());
file.setPassword("654321");
file.extendedHeader() = "foo";
file.encryptedExtendedHeader() = "bar";
file.doBackup();
file.save(PasswordFileSaveFlags::Encryption | PasswordFileSaveFlags::PasswordHashing);
// check results using the reading test
testReading("extended writing", testfile1, "123456", testfile2, "654321", true, true);
// check backup files
testReading("extended writing", testfile1 + ".backup", "123456", testfile2 + ".backup", string(), false, false);
}
#endif

View File

@ -1,8 +1,14 @@
#include "./openssl.h"
#include "./opensslrandomdevice.h"
#include <c++utilities/conversion/binaryconversion.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <random>
/*!
* \brief Contains utility classes and functions.
@ -14,6 +20,8 @@ namespace Util {
*/
namespace OpenSsl {
static_assert(Sha256Sum::size == SHA256_DIGEST_LENGTH, "SHA-256 sum fits into Sha256Sum struct");
/*!
* \brief Initializes OpenSSL.
*/
@ -35,5 +43,35 @@ void clean()
// remove error strings
ERR_free_strings();
}
/*!
* \brief Computes a SHA-256 sum using OpenSSL.
*/
Sha256Sum computeSha256Sum(const unsigned char *buffer, std::size_t size)
{
// init sha256 hashing
SHA256_CTX sha256;
SHA256_Init(&sha256);
// do the actual hashing
SHA256_Update(&sha256, buffer, size);
// finalize the hashing
Sha256Sum hash;
SHA256_Final(hash.data, &sha256);
return hash;
}
/*!
* \brief Generates a random number using OpenSSL.
*/
uint32_t generateRandomNumber(uint32_t min, uint32_t max)
{
OpenSslRandomDevice dev;
std::default_random_engine rng(dev());
std::uniform_int_distribution<uint32_t> dist(min, max);
return dist(rng);
}
} // namespace OpenSsl
} // namespace Util

View File

@ -1,14 +1,24 @@
#ifndef PASSWORD_FILE_UTIL_OPENSSL_H
#define OPENSSL_H
#define PASSWORD_FILE_UTIL_OPENSSL_H
#include "../global.h"
#include <cstdint>
namespace Util {
namespace OpenSsl {
struct Sha256Sum {
static constexpr std::size_t size = 32;
unsigned char data[size] = { 0 };
};
void PASSWORD_FILE_EXPORT init();
void PASSWORD_FILE_EXPORT clean();
Sha256Sum PASSWORD_FILE_EXPORT computeSha256Sum(const unsigned char *buffer, std::size_t size);
uint32_t PASSWORD_FILE_EXPORT generateRandomNumber(uint32_t min, uint32_t max);
} // namespace OpenSsl
} // namespace Util

View File

@ -28,7 +28,7 @@ OpenSslRandomDevice::OpenSslRandomDevice()
/*!
* \brief Generates a new random number.
*/
uint32 OpenSslRandomDevice::operator()() const
OpenSslRandomDevice::result_type OpenSslRandomDevice::operator()() const
{
unsigned char buf[4];
if (RAND_bytes(buf, sizeof(buf))) {

View File

@ -14,7 +14,7 @@ public:
using result_type = uint32;
OpenSslRandomDevice();
uint32 operator()() const;
result_type operator()() const;
bool status() const;
static constexpr result_type min();
static constexpr result_type max();