Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 035348ca authored by Johan Ouwerkerk's avatar Johan Ouwerkerk

Implement encryption/decryption of token secrets

With this change token secrets are encrypted prior to writing them to
storage, and decrypted as and when needed to generate tokens. Additional
validation is performed to verify that token secrets can be decrypted
successfully when loading accounts from storage.

With this change issue #6 should finally be resolved.
parent a90c16cf
......@@ -13,6 +13,5 @@ Some todo items include,
- QR code scanning
- Backup and Restore of accounts
- Encrypted storage of the secret token
This code is largely based on the [authenticator-ng](https://github.com/dobey/authenticator-ng) application by the Rodney Dawes and Michael Zanetti for the Ubuntu Touch.
......@@ -4,6 +4,7 @@
*/
#include "account/actions_p.h"
#include "../test-utils/secret.h"
#include "../test-utils/spy.h"
#include <QSignalSpy>
......@@ -13,24 +14,32 @@ class ComputeHotpTest: public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase(void);
void testDefaults(void);
void testDefaults_data(void);
private:
accounts::AccountSecret m_secret;
};
/*
* RFC test vector uses the key: 12345678901234567890
* The secret value below is the bas32 encoded version of that
*/
static QLatin1String secret("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ");
// RFC test vector uses the key: 12345678901234567890
static QByteArray rfcSecret("12345678901234567890");
// the RFC test vector consists of 6-character tokens
static int tokenLength = 6;
void ComputeHotpTest::initTestCase(void)
{
QVERIFY2(test::useDummyPassword(&m_secret), "should be able to set up the master key");
}
void ComputeHotpTest::testDefaults(void)
{
QFETCH(quint64, counter);
accounts::ComputeHotp uut(secret, counter, tokenLength);
std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, rfcSecret);
QVERIFY2(tokenSecret, "should be able to encrypt the token secret");
accounts::ComputeHotp uut(&m_secret, *tokenSecret, counter, tokenLength);
QSignalSpy tokenGenerated(&uut, &accounts::ComputeHotp::otp);
QSignalSpy jobFinished(&uut, &accounts::ComputeHotp::finished);
......
......@@ -4,6 +4,7 @@
*/
#include "account/actions_p.h"
#include "../test-utils/secret.h"
#include "../test-utils/spy.h"
#include <QSignalSpy>
......@@ -13,15 +14,15 @@ class ComputeTotpTest: public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase(void);
void testDefaults(void);
void testDefaults_data(void);
private:
accounts::AccountSecret m_secret;
};
/*
* RFC test vector uses the key: 12345678901234567890
* The secret value below is the bas32 encoded version of that
*/
static QLatin1String secret("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ");
// RFC test vector uses the key: 12345678901234567890
static QByteArray rfcSecret("12345678901234567890");
// the RFC test vector consists of 6-character tokens
static int tokenLength = 6;
......@@ -32,6 +33,11 @@ static uint timeStep = 30;
// the default TOTP epoch is the Unix epoch
static QDateTime epoch = QDateTime::fromMSecsSinceEpoch(0);
void ComputeTotpTest::initTestCase(void)
{
QVERIFY2(test::useDummyPassword(&m_secret), "should be able to set up the master key");
}
void ComputeTotpTest::testDefaults(void)
{
QFETCH(qint64, counter);
......@@ -39,7 +45,10 @@ void ComputeTotpTest::testDefaults(void)
return counter * timeStep * 1000;
});
accounts::ComputeTotp uut(secret, epoch, timeStep, tokenLength, accounts::Account::Hash::Default, clock);
std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, rfcSecret);
QVERIFY2(tokenSecret, "should be able to encrypt the token secret");
accounts::ComputeTotp uut(&m_secret, *tokenSecret, epoch, timeStep, tokenLength, accounts::Account::Hash::Default, clock);
QSignalSpy tokenGenerated(&uut, &accounts::ComputeTotp::otp);
QSignalSpy jobFinished(&uut, &accounts::ComputeTotp::finished);
......
......@@ -3,7 +3,7 @@
# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
#
set(Test_DEP_LIBS Qt5::Core Qt5::Test account_lib account_test_lib)
set(Test_DEP_LIBS Qt5::Core Qt5::Test account_lib account_test_lib secrets_test_lib)
qt5_add_resources(RCC_SOURCES resources/resources.qrc)
ecm_add_test(load-accounts.cpp ${RCC_SOURCES} LINK_LIBRARIES ${Test_DEP_LIBS} TEST_NAME load-accounts NAME_PREFIX account-jobs-)
......
......@@ -5,8 +5,11 @@
#include "account/actions_p.h"
#include "../test-utils/output.h"
#include "../test-utils/secret.h"
#include "../test-utils/spy.h"
#include "../../secrets/test-utils/random.h"
#include <QSignalSpy>
#include <QString>
#include <QTest>
......@@ -23,23 +26,33 @@ private Q_SLOTS:
void initTestCase(void);
void emptyAccountsFile(void);
void sampleAccountsFile(void);
private:
accounts::AccountSecret m_secret {&test::fakeRandom};
};
static QByteArray rawSecret = QByteArray::fromBase64(QByteArray("8juE9gJFLp3OgL4CxJ5v5q8sw+h7Vbn06+NY4uc="), QByteArray::Base64Encoding);
static QByteArray rawNonce = QByteArray::fromBase64(QByteArray("QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB"), QByteArray::Base64Encoding);
void LoadAccountsTest::initTestCase(void)
{
QVERIFY2(test::ensureOutputDirectory(), "output directory should be available");
QVERIFY2(test::copyResource(":/load-accounts/empty-accounts.ini", emptyIniResource), "empty INI resource should be available as file");
QVERIFY2(test::copyResource(":/load-accounts/sample-accounts.ini", corpusIniResource), "test corpus INI resource should be available as file");
QVERIFY2(test::useDummyPassword(&m_secret), "should be able to set up the master key");
}
void LoadAccountsTest::emptyAccountsFile(void)
{
accounts::LoadAccounts uut([](const accounts::PersistenceAction &action) -> void
bool actionRun = false;
const accounts::SettingsProvider settings([&actionRun](const accounts::PersistenceAction &action) -> void
{
QSettings data(test::path(emptyIniResource), QSettings::IniFormat);
actionRun = true;
action(data);
});
accounts::LoadAccounts uut(settings, &m_secret);
QSignalSpy hotpFound(&uut, &accounts::LoadAccounts::foundHotp);
QSignalSpy totpFound(&uut, &accounts::LoadAccounts::foundTotp);
QSignalSpy jobFinished(&uut, &accounts::LoadAccounts::finished);
......@@ -47,18 +60,23 @@ void LoadAccountsTest::emptyAccountsFile(void)
uut.run();
QVERIFY2(test::signal_eventually_emitted_once(jobFinished), "job should be finished");
QVERIFY2(actionRun, "accounts action should have run");
QCOMPARE(hotpFound.count(), 0);
QCOMPARE(totpFound.count(), 0);
}
void LoadAccountsTest::sampleAccountsFile(void)
{
accounts::LoadAccounts uut([](const accounts::PersistenceAction &action) -> void
bool actionRun = false;
const accounts::SettingsProvider settings([&actionRun](const accounts::PersistenceAction &action) -> void
{
QSettings data(test::path(corpusIniResource), QSettings::IniFormat);
actionRun = true;
action(data);
});
accounts::LoadAccounts uut(settings, &m_secret);
QSignalSpy hotpFound(&uut, &accounts::LoadAccounts::foundHotp);
QSignalSpy totpFound(&uut, &accounts::LoadAccounts::foundTotp);
QSignalSpy jobFinished(&uut, &accounts::LoadAccounts::finished);
......@@ -66,22 +84,25 @@ void LoadAccountsTest::sampleAccountsFile(void)
uut.run();
QVERIFY2(test::signal_eventually_emitted_once(jobFinished), "job should be finished");
QVERIFY2(actionRun, "accounts action should have run");
QCOMPARE(hotpFound.count(), 1);
QCOMPARE(totpFound.count(), 1);
const auto firstHotp = hotpFound.at(0);
QCOMPARE(firstHotp.at(0).toUuid(), QUuid(QLatin1String("072a645d-6c26-57cc-81eb-d9ef3b9b39e2")));
QCOMPARE(firstHotp.at(1).toString(), QLatin1String("valid-hotp-sample-1"));
QCOMPARE(firstHotp.at(2).toString(), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="));
QCOMPARE(firstHotp.at(3).toULongLong(), 0ULL);
QCOMPARE(firstHotp.at(4).toInt(), 6);
QCOMPARE(firstHotp.at(2).toByteArray(), rawSecret);
QCOMPARE(firstHotp.at(3).toByteArray(), rawNonce);
QCOMPARE(firstHotp.at(4).toULongLong(), 0ULL);
QCOMPARE(firstHotp.at(5).toInt(), 6);
const auto firstTotp = totpFound.at(0);
QCOMPARE(firstTotp.at(0).toUuid(), QUuid(QLatin1String("534cc72e-e9ec-5e39-a1ff-9f017c9be8cc")));
QCOMPARE(firstTotp.at(1).toString(), QLatin1String("valid-totp-sample-1"));
QCOMPARE(firstTotp.at(2).toString(), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="));
QCOMPARE(firstTotp.at(3).toUInt(), 30);
QCOMPARE(firstTotp.at(4).toInt(), 6);
QCOMPARE(firstHotp.at(2).toByteArray(), rawSecret);
QCOMPARE(firstHotp.at(3).toByteArray(), rawNonce);
QCOMPARE(firstTotp.at(4).toUInt(), 30);
QCOMPARE(firstTotp.at(5).toInt(), 6);
}
QTEST_MAIN(LoadAccountsTest)
......
[%7B072a645d-6c26-57cc-81eb-d9ef3b9b39e2%7D]
account=valid-hotp-sample-1
counter=0
nonce=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
pinLength=6
secret="8juE9gJFLp3OgL4CxJ5v5q8sw+h7Vbn06+NY4uc="
type=hotp
secret="NBSWY3DPFQQHO33SNRSCCCQ="
[%7B534cc72e-e9ec-5e39-a1ff-9f017c9be8cc%7D]
account=valid-totp-sample-1
timeStep=30
nonce=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
pinLength=6
secret="8juE9gJFLp3OgL4CxJ5v5q8sw+h7Vbn06+NY4uc="
timeStep=30
type=totp
secret="NBSWY3DPFQQHO33SNRSCCCQ="
[%7B072a645d-6c26-57cc-81eb-d9ef3b9b39e2%7D]
account=valid-hotp-sample-1
counter=0
nonce=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
pinLength=6
secret="NBSWY3DPFQQHO33SNRSCCCQ="
secret="8juE9gJFLp3OgL4CxJ5v5q8sw+h7Vbn06+NY4uc="
type=hotp
[%7B534cc72e-e9ec-5e39-a1ff-9f017c9be8cc%7D]
account=valid-totp-sample-1
nonce=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
pinLength=6
secret="NBSWY3DPFQQHO33SNRSCCCQ="
secret="8juE9gJFLp3OgL4CxJ5v5q8sw+h7Vbn06+NY4uc="
timeStep=30
type=totp
......@@ -5,8 +5,11 @@
#include "account/actions_p.h"
#include "../test-utils/output.h"
#include "../test-utils/secret.h"
#include "../test-utils/spy.h"
#include "../../secrets/test-utils/random.h"
#include <QFile>
#include <QSignalSpy>
#include <QString>
......@@ -23,27 +26,27 @@ private Q_SLOTS:
void validHotp_data(void);
void invalidHotp(void);
void invalidHotp_data(void);
private:
accounts::AccountSecret m_secret {&test::fakeRandom};
};
static void define_test_data(void)
{
QTest::addColumn<QUuid>("id");
QTest::addColumn<QString>("name");
QTest::addColumn<QString>("secret");
QTest::addColumn<quint64>("counter");
QTest::addColumn<int>("tokenLength");
}
static void define_test_case(const char * label, const QUuid &id, const QString &accountName, const QString &secret, quint64 counter, int tokenLength)
static void define_test_case(const char * label, const QUuid &id, const QString &accountName, quint64 counter, int tokenLength)
{
QTest::newRow(label) << id << accountName << secret << counter << tokenLength;
QTest::newRow(label) << id << accountName << counter << tokenLength;
}
void SaveHotpTest::validHotp(void)
{
QFETCH(QUuid, id);
QFETCH(QString, name);
QFETCH(QString, secret);
QFETCH(quint64, counter);
QFETCH(int, tokenLength);
......@@ -58,7 +61,10 @@ void SaveHotpTest::validHotp(void)
action(data);
});
accounts::SaveHotp uut(settings, id, name, secret, counter, tokenLength);
std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, QByteArray("Hello, world!"));
QVERIFY2(tokenSecret, "should be able to encrypt the token secret");
accounts::SaveHotp uut(settings, id, name, *tokenSecret, counter, tokenLength);
QSignalSpy invalidAccount(&uut, &accounts::SaveHotp::invalid);
QSignalSpy savedAccount(&uut, &accounts::SaveHotp::saved);
QSignalSpy jobFinished(&uut, &accounts::SaveHotp::finished);
......@@ -84,7 +90,6 @@ void SaveHotpTest::invalidHotp(void)
{
QFETCH(QUuid, id);
QFETCH(QString, name);
QFETCH(QString, secret);
QFETCH(quint64, counter);
QFETCH(int, tokenLength);
......@@ -99,7 +104,10 @@ void SaveHotpTest::invalidHotp(void)
action(data);
});
accounts::SaveHotp uut(settings, id, name, secret, counter, tokenLength);
std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, QByteArray("Hello, world!"));
QVERIFY2(tokenSecret, "should be able to encrypt the token secret");
accounts::SaveHotp uut(settings, id, name, *tokenSecret, counter, tokenLength);
QSignalSpy invalidAccount(&uut, &accounts::SaveHotp::invalid);
QSignalSpy savedAccount(&uut, &accounts::SaveHotp::saved);
QSignalSpy jobFinished(&uut, &accounts::SaveHotp::finished);
......@@ -121,24 +129,23 @@ void SaveHotpTest::invalidHotp(void)
void SaveHotpTest::validHotp_data(void)
{
define_test_data();
define_test_case("valid-hotp-sample-1", QUuid("072a645d-6c26-57cc-81eb-d9ef3b9b39e2"), QLatin1String("valid-hotp-sample-1"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 0, 6);
define_test_case("valid-hotp-sample-1", QUuid("072a645d-6c26-57cc-81eb-d9ef3b9b39e2"), QLatin1String("valid-hotp-sample-1"), 0, 6);
}
void SaveHotpTest::invalidHotp_data(void)
{
define_test_data();
define_test_case("null UUID", QUuid(), QLatin1String("null UUID"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 0, 6);
define_test_case("null account name", QUuid("00611bbf-5e0b-5c6a-9847-ad865315ce86"), QString(), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 0, 6);
define_test_case("empty account name", QUuid("1e42b907-99d8-5da3-a59b-89b257e49c83"), QLatin1String(""), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 0, 6);
define_test_case("null secret", QUuid("6e5ba95c-984d-538c-844e-f9edc1341bd2"), QLatin1String("null secret"), QString(), 0, 6);
define_test_case("empty secret", QUuid("fe68a65e-287e-5dcd-909b-1837d7ab94ee"), QLatin1String("empty secret"), QLatin1String(""), 0, 6);
define_test_case("tokenLength too small", QUuid("bca12e13-4b5b-5e4e-b162-3b86a6284dea"), QLatin1String("tokenLength too small"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 0, 5);
define_test_case("tokenLength too large", QUuid("5c10d530-fb22-5438-848d-3d4d1f738610"), QLatin1String("tokenLength too large"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 0, 11);
define_test_case("null UUID", QUuid(), QLatin1String("null UUID"), 0, 6);
define_test_case("null account name", QUuid("00611bbf-5e0b-5c6a-9847-ad865315ce86"), QString(), 0, 6);
define_test_case("empty account name", QUuid("1e42b907-99d8-5da3-a59b-89b257e49c83"), QLatin1String(""), 0, 6);
define_test_case("tokenLength too small", QUuid("bca12e13-4b5b-5e4e-b162-3b86a6284dea"), QLatin1String("tokenLength too small"), 0, 5);
define_test_case("tokenLength too large", QUuid("5c10d530-fb22-5438-848d-3d4d1f738610"), QLatin1String("tokenLength too large"), 0, 11);
}
void SaveHotpTest::initTestCase(void)
{
QVERIFY2(test::ensureOutputDirectory(), "output directory should be available");
QVERIFY2(test::useDummyPassword(&m_secret), "should be able to set up the master key");
}
QTEST_MAIN(SaveHotpTest)
......
......@@ -5,8 +5,11 @@
#include "account/actions_p.h"
#include "../test-utils/output.h"
#include "../test-utils/secret.h"
#include "../test-utils/spy.h"
#include "../../secrets/test-utils/random.h"
#include <QFile>
#include <QSignalSpy>
#include <QString>
......@@ -23,27 +26,27 @@ private Q_SLOTS:
void validHotp_data(void);
void invalidHotp(void);
void invalidHotp_data(void);
private:
accounts::AccountSecret m_secret {&test::fakeRandom};
};
static void define_test_data(void)
{
QTest::addColumn<QUuid>("id");
QTest::addColumn<QString>("name");
QTest::addColumn<QString>("secret");
QTest::addColumn<uint>("timeStep");
QTest::addColumn<int>("tokenLength");
}
static void define_test_case(const char * label, const QUuid &id, const QString &accountName, const QString &secret, uint timeStep, int tokenLength)
static void define_test_case(const char * label, const QUuid &id, const QString &accountName, uint timeStep, int tokenLength)
{
QTest::newRow(label) << id << accountName << secret << timeStep << tokenLength;
QTest::newRow(label) << id << accountName << timeStep << tokenLength;
}
void SaveTotpTest::validHotp(void)
{
QFETCH(QUuid, id);
QFETCH(QString, name);
QFETCH(QString, secret);
QFETCH(uint, timeStep);
QFETCH(int, tokenLength);
......@@ -58,7 +61,10 @@ void SaveTotpTest::validHotp(void)
action(data);
});
accounts::SaveTotp uut(settings, id, name, secret, timeStep, tokenLength);
std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, QByteArray("Hello, world!"));
QVERIFY2(tokenSecret, "should be able to encrypt the token secret");
accounts::SaveTotp uut(settings, id, name, *tokenSecret, timeStep, tokenLength);
QSignalSpy invalidAccount(&uut, &accounts::SaveTotp::invalid);
QSignalSpy savedAccount(&uut, &accounts::SaveTotp::saved);
QSignalSpy jobFinished(&uut, &accounts::SaveTotp::finished);
......@@ -84,7 +90,6 @@ void SaveTotpTest::invalidHotp(void)
{
QFETCH(QUuid, id);
QFETCH(QString, name);
QFETCH(QString, secret);
QFETCH(uint, timeStep);
QFETCH(int, tokenLength);
......@@ -99,7 +104,10 @@ void SaveTotpTest::invalidHotp(void)
action(data);
});
accounts::SaveTotp uut(settings, id, name, secret, timeStep, tokenLength);
std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, QByteArray("Hello, world!"));
QVERIFY2(tokenSecret, "should be able to encrypt the token secret");
accounts::SaveTotp uut(settings, id, name, *tokenSecret, timeStep, tokenLength);
QSignalSpy invalidAccount(&uut, &accounts::SaveTotp::invalid);
QSignalSpy savedAccount(&uut, &accounts::SaveTotp::saved);
QSignalSpy jobFinished(&uut, &accounts::SaveTotp::finished);
......@@ -121,25 +129,24 @@ void SaveTotpTest::invalidHotp(void)
void SaveTotpTest::validHotp_data(void)
{
define_test_data();
define_test_case("valid-totp-sample-1", QUuid("534cc72e-e9ec-5e39-a1ff-9f017c9be8cc"), QLatin1String("valid-totp-sample-1"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 30, 6);
define_test_case("valid-totp-sample-1", QUuid("534cc72e-e9ec-5e39-a1ff-9f017c9be8cc"), QLatin1String("valid-totp-sample-1"), 30, 6);
}
void SaveTotpTest::invalidHotp_data(void)
{
define_test_data();
define_test_case("null UUID", QUuid(), QLatin1String("null UUID"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 30, 6);
define_test_case("null account name", QUuid("00611bbf-5e0b-5c6a-9847-ad865315ce86"), QString(), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 30, 6);
define_test_case("empty account name", QUuid("1e42b907-99d8-5da3-a59b-89b257e49c83"), QLatin1String(""), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 30, 6);
define_test_case("null secret", QUuid("6e5ba95c-984d-538c-844e-f9edc1341bd2"), QLatin1String("null secret"), QString(), 30, 6);
define_test_case("empty secret", QUuid("fe68a65e-287e-5dcd-909b-1837d7ab94ee"), QLatin1String("empty secret"), QLatin1String(""), 30, 6);
define_test_case("timeStep too small", QUuid("5ab8749b-f973-5f48-a70e-c261ebd0521a"), QLatin1String("timeStep too small"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 0, 6);
define_test_case("tokenLength too small", QUuid("bca12e13-4b5b-5e4e-b162-3b86a6284dea"), QLatin1String("tokenLength too small"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 30, 5);
define_test_case("tokenLength too large", QUuid("5c10d530-fb22-5438-848d-3d4d1f738610"), QLatin1String("tokenLength too large"), QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 30, 11);
define_test_case("null UUID", QUuid(), QLatin1String("null UUID"), 30, 6);
define_test_case("null account name", QUuid("00611bbf-5e0b-5c6a-9847-ad865315ce86"), QString(), 30, 6);
define_test_case("empty account name", QUuid("1e42b907-99d8-5da3-a59b-89b257e49c83"), QLatin1String(""), 30, 6);
define_test_case("timeStep too small", QUuid("5ab8749b-f973-5f48-a70e-c261ebd0521a"), QLatin1String("timeStep too small"), 0, 6);
define_test_case("tokenLength too small", QUuid("bca12e13-4b5b-5e4e-b162-3b86a6284dea"), QLatin1String("tokenLength too small"), 30, 5);
define_test_case("tokenLength too large", QUuid("5c10d530-fb22-5438-848d-3d4d1f738610"), QLatin1String("tokenLength too large"), 30, 11);
}
void SaveTotpTest::initTestCase(void)
{
QVERIFY2(test::ensureOutputDirectory(), "output directory should be available");
QVERIFY2(test::useDummyPassword(&m_secret), "should be able to set up the master key");
}
QTEST_MAIN(SaveTotpTest)
......
......@@ -3,7 +3,7 @@
# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
#
set(Test_DEP_LIBS Qt5::Core Qt5::Test account_lib account_test_lib)
set(Test_DEP_LIBS Qt5::Core Qt5::Test account_lib account_test_lib secrets_test_lib)
qt5_add_resources(RCC_SOURCES resources/resources.qrc)
ecm_add_test(storage-object-lifecycles.cpp ${RCC_SOURCES} LINK_LIBRARIES ${Test_DEP_LIBS} TEST_NAME storage-object-lifecycles NAME_PREFIX account-)
......
......@@ -7,6 +7,8 @@
#include "../test-utils/output.h"
#include "../test-utils/spy.h"
#include "../../secrets/test-utils/random.h"
#include <QDateTime>
#include <QFile>
#include <QSignalSpy>
......@@ -53,13 +55,7 @@ void HotpCounterUpdateTest::testCounterUpdate(void)
thread->start();
QVERIFY2(test::signal_eventually_emitted_once(threadStarted), "worker thread should be running by now");
accounts::AccountStorage *uut = new accounts::AccountStorage(settings, thread);
QSignalSpy accountAdded(uut, &accounts::AccountStorage::added);
QSignalSpy accountRemoved(uut, &accounts::AccountStorage::removed);
QSignalSpy storageDisposed(uut, &accounts::AccountStorage::disposed);
QSignalSpy storageCleaned(uut, &accounts::AccountStorage::destroyed);
accounts::AccountSecret *secret = uut->secret();
accounts::AccountSecret *secret = new accounts::AccountSecret(&test::fakeRandom);
QSignalSpy existingPasswordNeeded(secret, &accounts::AccountSecret::existingPasswordNeeded);
QSignalSpy newPasswordNeeded(secret, &accounts::AccountSecret::newPasswordNeeded);
QSignalSpy passwordAvailable(secret, &accounts::AccountSecret::passwordAvailable);
......@@ -67,6 +63,12 @@ void HotpCounterUpdateTest::testCounterUpdate(void)
QSignalSpy passwordRequestsCancelled(secret, &accounts::AccountSecret::requestsCancelled);
QSignalSpy secretCleaned(secret, &accounts::AccountSecret::destroyed);
accounts::AccountStorage *uut = new accounts::AccountStorage(settings, thread, secret);
QSignalSpy accountAdded(uut, &accounts::AccountStorage::added);
QSignalSpy accountRemoved(uut, &accounts::AccountStorage::removed);
QSignalSpy storageDisposed(uut, &accounts::AccountStorage::disposed);
QSignalSpy storageCleaned(uut, &accounts::AccountStorage::destroyed);
// first phase: check that account objects can be loaded from storage
// expect that unlocking is scheduled automatically, so advancing the event loop should trigger the signal
......
......@@ -8,6 +8,7 @@ salt="MDEyMzQ1Njc4OUFCQ0RFRg=="
[%7B072a645d-6c26-57cc-81eb-d9ef3b9b39e2%7D]
account=valid-hotp-sample-1
counter=1
nonce=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
pinLength=6
secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
secret=/8zRRlAF80eHEtU/ZJEFbR4nIeuMVs4YBRHvxRSod8iQculp
type=hotp
......@@ -8,6 +8,7 @@ salt="MDEyMzQ1Njc4OUFCQ0RFRg=="
[%7B072a645d-6c26-57cc-81eb-d9ef3b9b39e2%7D]
account=valid-hotp-sample-1
counter=0
nonce=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
pinLength=6
secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
secret=/8zRRlAF80eHEtU/ZJEFbR4nIeuMVs4YBRHvxRSod8iQculp
type=hotp
......@@ -7,7 +7,8 @@ salt="MDEyMzQ1Njc4OUFCQ0RFRg=="
[%7B534cc72e-e9ec-5e39-a1ff-9f017c9be8cc%7D]
account=valid-totp-sample-1
nonce=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
pinLength=8
secret="NBSWY3DPFQQHO33SNRSCCCQ="
secret="LXM8veM3T1qY/gAYsTGZNEdwfrPWTNlXU1OykwY="
timeStep=42
type=totp
......@@ -8,6 +8,7 @@ salt="MDEyMzQ1Njc4OUFCQ0RFRg=="
[%7B072a645d-6c26-57cc-81eb-d9ef3b9b39e2%7D]
account=valid-hotp-sample-1
counter=42
nonce=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
pinLength=7
secret="NBSWY3DPFQQHO33SNRSCCCQ="
secret="LXM8veM3T1qY/gAYsTGZNEdwfrPWTNlXU1OykwY="
type=hotp
......@@ -7,6 +7,8 @@
#include "../test-utils/output.h"
#include "../test-utils/spy.h"
#include "../../secrets/test-utils/random.h"
#include <QDateTime>
#include <QFile>
#include <QSignalSpy>
......@@ -15,6 +17,8 @@
#include <QVector>
#include <QtDebug>
#include <string.h>
static QString testIniResource(QLatin1String("test.ini"));
static QString testIniLockFile(QLatin1String("test.ini.lock"));
......@@ -51,13 +55,7 @@ void StorageLifeCyclesTest::testLifecycle(void)
thread->start();
QVERIFY2(test::signal_eventually_emitted_once(threadStarted), "worker thread should be running by now");
accounts::AccountStorage *uut = new accounts::AccountStorage(settings, thread);
QSignalSpy accountAdded(uut, &accounts::AccountStorage::added);
QSignalSpy accountRemoved(uut, &accounts::AccountStorage::removed);
QSignalSpy storageDisposed(uut, &accounts::AccountStorage::disposed);
QSignalSpy storageCleaned(uut, &accounts::AccountStorage::destroyed);
accounts::AccountSecret *secret = uut->secret();
accounts::AccountSecret *secret = new accounts::AccountSecret(&test::fakeRandom);
QSignalSpy existingPasswordNeeded(secret, &accounts::AccountSecret::existingPasswordNeeded);
QSignalSpy newPasswordNeeded(secret, &accounts::AccountSecret::newPasswordNeeded);
QSignalSpy passwordAvailable(secret, &accounts::AccountSecret::passwordAvailable);
......@@ -65,6 +63,12 @@ void StorageLifeCyclesTest::testLifecycle(void)
QSignalSpy passwordRequestsCancelled(secret, &accounts::AccountSecret::requestsCancelled);
QSignalSpy secretCleaned(secret, &accounts::AccountSecret::destroyed);
accounts::AccountStorage *uut = new accounts::AccountStorage(settings, thread, secret);
QSignalSpy accountAdded(uut, &accounts::AccountStorage::added);
QSignalSpy accountRemoved(uut, &accounts::AccountStorage::removed);
QSignalSpy storageDisposed(uut, &accounts::AccountStorage::disposed);
QSignalSpy storageCleaned(uut, &accounts::AccountStorage::destroyed);
// first phase: check that account objects can be loaded from storage
QCOMPARE(accountAdded.count(), 0);
QVERIFY2(uut->isNameStillAvailable(initialAccountName), "sample account name should still be available");
......@@ -141,7 +145,7 @@ void StorageLifeCyclesTest::testLifecycle(void)
QVERIFY2(test::signal_eventually_emitted_once(initialAccountCleaned), "sample account should be cleaned up by now");
// third phase: check that new account objects can be added to storage
uut->addTotp(addedAccountName, QLatin1String("NBSWY3DPFQQHO33SNRSCCCQ="), 42, 8);
uut->addTotp(addedAccountName, QLatin1String("NBSWY3DPFQQHO33SNRSCC==="), 42, 8);
QVERIFY2(test::signal_eventually_emitted_twice(accountAdded), "new account should be added to storage by now");
QCOMPARE(accountAdded.at(1).at(0), addedAccountName);
......
......@@ -6,8 +6,9 @@
set(account_test_lib_SRCS
job.cpp
output.cpp
secret.cpp
spy.cpp
)
add_library(account_test_lib STATIC ${account_test_lib_SRCS})
target_link_libraries(account_test_lib Qt5::Core Qt5::Test)
target_link_libraries(account_test_lib Qt5::Core Qt5::Test account_lib)
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "secret.h"
#include <QScopedPointer>
#include <QtDebug>
#include <string.h>
namespace test
{
secrets::SecureMasterKey * useDummyPassword(accounts::AccountSecret *secret)
{
QByteArray salt;
salt.resize(crypto_pwhash_SALTBYTES);
salt.fill('\x0', -1);
QString password(QLatin1String("password"));
return useDummyPassword(secret, password, salt);
}
secrets::SecureMasterKey * useDummyPassword(accounts::AccountSecret *secret, QString &password, QByteArray &salt)