Fix TODO items about logging for C++ code

master
Johan Ouwerkerk 2020-04-14 19:45:29 +02:00
parent b4dada08d8
commit f04b15340f
9 changed files with 488 additions and 238 deletions

View File

@ -6,11 +6,14 @@
#include "account_p.h"
#include "actions_p.h"
#include "../logging_p.h"
#include <QTimer>
KEYSMITH_LOGGER(logger, ".accounts.account")
namespace accounts
{
Account::Account(AccountPrivate *d, QObject *parent) : QObject(parent), m_dptr(d)
{
}
@ -189,25 +192,47 @@ namespace accounts
void AccountStorage::handleHotp(const QUuid id, const QString name, const QString secret, quint64 counter, int tokenLength)
{
Q_D(AccountStorage);
if (d->isStillOpen() && isNameStillAvailable(name)) {
Account *accepted = d->acceptHotpAccount(id, name, secret, counter, tokenLength);
QObject::connect(accepted, &Account::removed, this, &AccountStorage::accountRemoved);
Q_EMIT added(name);
if (!d->isStillOpen()) {
qCDebug(logger)
<< "Not handling HOTP account:" << id
<< "Storage no longer open";
return;
}
// TODO: warn if not
if (!isNameStillAvailable(name)) {
qCDebug(logger)
<< "Not handling HOTP account:" << id
<< "Account name not available";
return;
}
Account *accepted = d->acceptHotpAccount(id, name, secret, counter, tokenLength);
QObject::connect(accepted, &Account::removed, this, &AccountStorage::accountRemoved);
Q_EMIT added(name);
}
void AccountStorage::handleTotp(const QUuid id, const QString name, const QString secret, uint timeStep, int tokenLength)
{
Q_D(AccountStorage);
if (d->isStillOpen() && isNameStillAvailable(name)) {
Account *accepted = d->acceptTotpAccount(id, name, secret, timeStep, tokenLength);
QObject::connect(accepted, &Account::removed, this, &AccountStorage::accountRemoved);
Q_EMIT added(name);
if (!d->isStillOpen()) {
qCDebug(logger)
<< "Not handling TOTP account:" << id
<< "Storage no longer open";
return;
}
// TODO: warn if not
if (!isNameStillAvailable(name)) {
qCDebug(logger)
<< "Not handling TOTP account:" << id
<< "Account name not available";
return;
}
Account *accepted = d->acceptTotpAccount(id, name, secret, timeStep, tokenLength);
QObject::connect(accepted, &Account::removed, this, &AccountStorage::accountRemoved);
Q_EMIT added(name);
}
void AccountStorage::dispose(void)

View File

@ -30,9 +30,11 @@ namespace accounts
Totp,
Hotp
};
Q_ENUM(Algorithm)
enum Hash {
Default, Sha256, Sha512
};
Q_ENUM(Hash)
explicit Account(AccountPrivate *d, QObject *parent = nullptr);
QString name(void) const;
QString token(void) const;

View File

@ -5,9 +5,13 @@
#include "account_p.h"
#include "validation.h"
#include "../logging_p.h"
#include <QObject>
#include <QTimer>
KEYSMITH_LOGGER(logger, ".accounts.account_p")
namespace accounts
{
QUuid AccountPrivate::id(void) const
@ -68,71 +72,145 @@ namespace accounts
void AccountPrivate::setCounter(quint64 counter)
{
Q_Q(Account);
if (m_is_still_alive && m_storage->isStillOpen() && m_algorithm == Account::Algorithm::Hotp) {
SaveHotp *job = new SaveHotp(m_storage->settings(),m_id, m_name, m_secret, counter, m_tokenLength);
m_actions->queueAndProceed(job, [counter, job, q, this](void) -> void
{
new HandleCounterUpdate(this, counter, job, q);
});
if (!m_is_still_alive) {
qCDebug(logger)
<< "Will not set counter for account:" << m_id
<< "Object marked for death";
return;
}
// TODO: warn if not
if (!m_storage->isStillOpen()) {
qCDebug(logger)
<< "Will not set counter for account:" << m_id
<< "Storage no longer open";
return;
}
if (m_algorithm != Account::Algorithm::Hotp) {
qCDebug(logger)
<< "Will not set counter for account:" << m_id
<< "Algorithm not applicable:" << m_algorithm;
return;
}
qCDebug(logger) << "Requesting to store updated details for account:" << m_id;
SaveHotp *job = new SaveHotp(m_storage->settings(),m_id, m_name, m_secret, counter, m_tokenLength);
m_actions->queueAndProceed(job, [counter, job, q, this](void) -> void
{
new HandleCounterUpdate(this, counter, job, q);
});
}
void AccountPrivate::acceptCounter(quint64 counter)
{
if (m_is_still_alive && m_storage->isStillOpen() && m_algorithm == Account::Algorithm::Hotp) {
m_counter = counter;
recompute();
if (!m_is_still_alive) {
qCDebug(logger)
<< "Ignoring counter update for account:" << m_id
<< "Object marked for death";
return;
}
// TODO: warn if not
if (!m_storage->isStillOpen()) {
qCDebug(logger)
<< "Ignoring counter update for account:" << m_id
<< "Storage no longer open";
return;
}
if (m_algorithm != Account::Algorithm::Hotp) {
qCDebug(logger)
<< "Ignoring counter update for account:" << m_id
<< "Algorithm not applicable:" << m_algorithm;
return;
}
qCDebug(logger) << "Counter is updated for account:" << m_id;
m_counter = counter;
recompute();
}
void AccountPrivate::acceptToken(QString token)
{
Q_Q(Account);
if (m_is_still_alive && m_storage->isStillOpen()) {
m_token = token;
Q_EMIT q->tokenChanged(m_token);
if (!m_is_still_alive) {
qCDebug(logger)
<< "Ignoring token update for account:" << m_id
<< "Object marked for death";
return;
}
// TODO: warn if not
if (!m_storage->isStillOpen()) {
qCDebug(logger)
<< "Ignoring token update for account:" << m_id
<< "Storage no longer open";
return;
}
qCDebug(logger) << "Token is updated for account:" << m_id;
m_token = token;
Q_EMIT q->tokenChanged(m_token);
}
void AccountPrivate::recompute(void)
{
Q_Q(Account);
if (m_is_still_alive && m_storage->isStillOpen()) {
ComputeHotp *hotpJob = nullptr;
ComputeTotp *totpJob = nullptr;
switch (m_algorithm) {
case Account::Algorithm::Hotp:
hotpJob = new ComputeHotp(m_secret, m_counter, m_tokenLength, m_offset, m_checksum);
m_actions->queueAndProceed(hotpJob, [hotpJob, q, this](void) -> void {
new HandleTokenUpdate(this, hotpJob, q);
});
break;
case Account::Algorithm::Totp:
totpJob = new ComputeTotp(m_secret, m_epoch, m_timeStep, m_tokenLength, m_hash);
m_actions->queueAndProceed(totpJob, [totpJob, q, this](void) -> void
{
new HandleTokenUpdate(this, totpJob, q);
});
break;
default:
break;
}
if (!m_is_still_alive) {
qCDebug(logger)
<< "Will not recompute token for account:" << m_id
<< "Object marked for death";
return;
}
if (!m_storage->isStillOpen()) {
qCDebug(logger)
<< "Will not recompute token for account:" << m_id
<< "Storage no longer open";
return;
}
qCDebug(logger) << "Requesting recomputed token for account:" << m_id;
ComputeHotp *hotpJob = nullptr;
ComputeTotp *totpJob = nullptr;
switch (m_algorithm) {
case Account::Algorithm::Hotp:
hotpJob = new ComputeHotp(m_secret, m_counter, m_tokenLength, m_offset, m_checksum);
m_actions->queueAndProceed(hotpJob, [hotpJob, q, this](void) -> void {
new HandleTokenUpdate(this, hotpJob, q);
});
break;
case Account::Algorithm::Totp:
totpJob = new ComputeTotp(m_secret, m_epoch, m_timeStep, m_tokenLength, m_hash);
m_actions->queueAndProceed(totpJob, [totpJob, q, this](void) -> void
{
new HandleTokenUpdate(this, totpJob, q);
});
break;
default:
Q_ASSERT_X(false, Q_FUNC_INFO, "unknown algorithm value");
break;
}
// TODO: warn if not
}
void AccountPrivate::remove(void)
{
if (m_is_still_alive && m_storage->isStillOpen()) {
QSet<QString> self;
self.insert(m_name);
m_storage->removeAccounts(self);
if (!m_is_still_alive) {
qCDebug(logger)
<< "Will not remove account:" << m_id
<< "Object marked for death";
return;
}
// TODO: warn if not
if (!m_storage->isStillOpen()) {
qCDebug(logger)
<< "Will not remove account:" << m_id
<< "Storage no longer open";
return;
}
QSet<QString> self;
self.insert(m_name);
m_storage->removeAccounts(self);
}
void AccountPrivate::markForRemoval(void)
@ -166,14 +244,20 @@ namespace accounts
QVector<QString> AccountStoragePrivate::activeAccounts(void) const
{
QVector<QString> active;
if (!m_is_still_open) {
qCDebug(logger) << "Not returning accounts: account storage no longer open";
return active;
}
if (m_is_still_open) {
const QList<QString> all = m_names.keys();
for (const QString &account : all) {
if (m_accountsPrivate[m_names[account]]->isStillAlive()) {
active.append(account);
}
// TODO: log if not
const QList<QString> all = m_names.keys();
for (const QString &account : all) {
const QUuid id = m_names[account];
if (m_accountsPrivate[id]->isStillAlive()) {
active.append(account);
} else {
qCDebug(logger)
<< "Not returning account:" << id
<< "Object marked for death";
}
}
@ -187,7 +271,12 @@ namespace accounts
bool AccountStoragePrivate::isNameStillAvailable(const QString &name) const
{
return m_is_still_open && !m_names.contains(name);
if (!m_is_still_open) {
qCDebug(logger) << "Pretending no name is available: account storage no longer open";
return false;
}
return !m_names.contains(name);
}
bool AccountStoragePrivate::contains(const QString &account) const
@ -197,13 +286,28 @@ namespace accounts
* This lets the behaviour of get() and contains() be mutually consistent
* without having to hand out pointers which may be about to go stale in get()
*/
return m_is_still_open && m_names.contains(account) && m_accountsPrivate[m_names[account]]->isStillAlive();
// TODO: warn if not still open, log if account not alive
if (!m_is_still_open) {
qCDebug(logger) << "Pretending no account exists: account storage no longer open";
return false;
}
if (!m_names.contains(account)) {
return false;
}
const QUuid id = m_names[account];
if (!m_accountsPrivate[id]->isStillAlive()) {
qCDebug(logger)
<< "Pretending account does not exist:" << id
<< "Object marked for death";
return false;
}
return true;
}
Account * AccountStoragePrivate::get(const QString &account) const
{
// TODO: warn if not
return contains(account) ? m_accounts[m_names[account]] : nullptr;
}
@ -214,39 +318,89 @@ namespace accounts
void AccountStoragePrivate::removeAccounts(const QSet<QString> &accountNames)
{
if (m_is_still_open) {
QSet<QUuid> ids;
for (const QString &accountName : accountNames) {
if (m_names.contains(accountName)) {
const QUuid id = m_names[accountName];
AccountPrivate *p = m_accountsPrivate[id];
/*
* Avoid doing anything with accounts which are already about to be removed from the maps
*/
if (p->isStillAlive()) {
p->markForRemoval();
ids.insert(id);
}
// TODO: log if not
if (!m_is_still_open) {
qCDebug(logger) << "Not removing accounts: account storage no longer open";
return;
}
QSet<QUuid> ids;
for (const QString &accountName : accountNames) {
if (m_names.contains(accountName)) {
const QUuid id = m_names[accountName];
AccountPrivate *p = m_accountsPrivate[id];
/*
* Avoid doing anything with accounts which are already about to be removed from the maps
*/
if (p->isStillAlive()) {
p->markForRemoval();
ids.insert(id);
} else {
qCDebug(logger)
<< "Not removing account:" << id
<< "Object marked for death";
}
}
DeleteAccounts *job = new DeleteAccounts(m_settings, ids);
m_actions->queueAndProceed(job, [&ids, job, this](void) -> void
{
for (const QUuid &id : ids) {
Account *account = m_accounts[id];
QObject::connect(job, &DeleteAccounts::finished, account, &Account::removed);
}
});
}
// TODO: warn if not
DeleteAccounts *job = new DeleteAccounts(m_settings, ids);
m_actions->queueAndProceed(job, [&ids, job, this](void) -> void
{
for (const QUuid &id : ids) {
Account *account = m_accounts[id];
QObject::connect(job, &DeleteAccounts::finished, account, &Account::removed);
}
});
}
void AccountStoragePrivate::acceptAccountRemoval(const QString &accountName)
{
if (m_names.contains(accountName)) {
if (!m_names.contains(accountName)) {
qCDebug(logger) << "Not accepting account removal: account name unknown";
return;
}
const QUuid id = m_names[accountName];
qCDebug(logger) << "Handling account cleanup for account:" << id;
Account *account = m_accounts[id];
m_accounts.remove(id);
m_accountsPrivate.remove(id);
m_names.remove(accountName);
m_ids.remove(id);
QTimer::singleShot(0, account, &accounts::Account::deleteLater);
}
void AccountStoragePrivate::dispose(const std::function<void(Null*)> &handler)
{
if (!m_is_still_open) {
qCDebug(logger) << "Not disposing of storage: account storage no longer open";
return;
}
qCDebug(logger) << "Disposing of storage";
m_is_still_open = false;
Null *job = new Null();
m_actions->queueAndProceed(job, [job, &handler](void) -> void
{
handler(job);
});
}
void AccountStoragePrivate::acceptDisposal(void)
{
Q_Q(AccountStorage);
if (m_is_still_open) {
qCDebug(logger) << "Ignoring disposal of storage: account storage is still open";
return;
}
qCDebug(logger) << "Handling storage disposal";
for (const QString &accountName : m_names.keys()) {
const QUuid id = m_names[accountName];
qCDebug(logger) << "Handling account cleanup for account:" << id;
Account *account = m_accounts[id];
m_accounts.remove(id);
m_accountsPrivate.remove(id);
@ -254,38 +408,7 @@ namespace accounts
m_ids.remove(id);
QTimer::singleShot(0, account, &accounts::Account::deleteLater);
}
// TODO: warn if not
}
void AccountStoragePrivate::dispose(const std::function<void(Null*)> &handler)
{
if (m_is_still_open) {
m_is_still_open = false;
Null *job = new Null();
m_actions->queueAndProceed(job, [job, &handler](void) -> void
{
handler(job);
});
}
// TODO: warn if not
}
void AccountStoragePrivate::acceptDisposal(void)
{
Q_Q(AccountStorage);
if (!m_is_still_open) {
for (const QString &accountName : m_names.keys()) {
const QUuid id = m_names[accountName];
Account *account = m_accounts[id];
m_accounts.remove(id);
m_accountsPrivate.remove(id);
m_names.remove(accountName);
m_ids.remove(id);
QTimer::singleShot(0, account, &accounts::Account::deleteLater);
}
Q_EMIT q->disposed();
}
// TODO: warn if not
Q_EMIT q->disposed();
}
QUuid AccountStoragePrivate::generateId(const QString &name) const
@ -301,53 +424,78 @@ namespace accounts
{
Q_UNUSED(offset);
Q_UNUSED(addChecksum);
if (m_is_still_open && isNameStillAvailable(name)) {
QUuid id = generateId(name);
if (checkHotpAccount(id, name, secret, tokenLength)) {
m_ids.insert(id);
SaveHotp *job = new SaveHotp(m_settings, id, name, secret, counter, tokenLength);
m_actions->queueAndProceed(job, [job, &handler](void) -> void
{
handler(job);
});
}
if (!m_is_still_open) {
qCDebug(logger) << "Will not add new HOTP account: storage no longer open";
return;
}
// TODO: warn if not
if (!isNameStillAvailable(name)) {
qCDebug(logger) << "Will not add new HOTP account: account name not available";
return;
}
QUuid id = generateId(name);
if (!checkHotpAccount(id, name, secret, tokenLength)) {
qCDebug(logger) << "Will not add new HOTP account: invalid account details";
return;
}
qCDebug(logger) << "Requesting to store details for new HOTP account:" << id;
m_ids.insert(id);
SaveHotp *job = new SaveHotp(m_settings, id, name, secret, counter, tokenLength);
m_actions->queueAndProceed(job, [job, &handler](void) -> void
{
handler(job);
});
}
void AccountStoragePrivate::addTotp(const std::function<void(SaveTotp*)> &handler, const QString &name, const QString &secret, uint timeStep, int tokenLength, const QDateTime &epoch, Account::Hash hash)
{
Q_UNUSED(epoch);
Q_UNUSED(hash);
if (m_is_still_open && isNameStillAvailable(name)) {
QUuid id = generateId(name);
if (checkTotpAccount(id, name, secret, tokenLength, timeStep)) {
m_ids.insert(id);
SaveTotp *job = new SaveTotp(m_settings, id, name, secret, timeStep, tokenLength);
m_actions->queueAndProceed(job, [job, &handler](void) -> void
{
handler(job);
});
}
if (!m_is_still_open) {
qCDebug(logger) << "Will not add new TOTP account: storage no longer open";
return;
}
// TODO: warn if not
if (!isNameStillAvailable(name)) {
qCDebug(logger) << "Will not add new TOTP account: account name not available";
return;
}
QUuid id = generateId(name);
if (!checkTotpAccount(id, name, secret, tokenLength, timeStep)) {
qCDebug(logger) << "Will not add new TOTP account: invalid account details";
return;
}
qCDebug(logger) << "Requesting to store details for new TOTP account:" << id;
m_ids.insert(id);
SaveTotp *job = new SaveTotp(m_settings, id, name, secret, timeStep, tokenLength);
m_actions->queueAndProceed(job, [job, &handler](void) -> void
{
handler(job);
});
}
void AccountStoragePrivate::load(const std::function<void(LoadAccounts*)> &handler)
{
if (m_is_still_open) {
LoadAccounts *job = new LoadAccounts(m_settings);
m_actions->queueAndProceed(job, [job, &handler](void) -> void
{
handler(job);
});
if (!m_is_still_open) {
qCDebug(logger) << "Will not load accounts: storage no longer open";
return;
}
// TODO: warn if not
LoadAccounts *job = new LoadAccounts(m_settings);
m_actions->queueAndProceed(job, [job, &handler](void) -> void
{
handler(job);
});
}
Account * AccountStoragePrivate::acceptHotpAccount(const QUuid &id, const QString &name, const QString &secret, quint64 counter, int tokenLength, int offset, bool addChecksum)
{
Q_Q(AccountStorage);
qCDebug(logger) << "Registering HOTP account:" << id;
const std::function<Account*(AccountPrivate*)> registration([this, q, &id](AccountPrivate *p) -> Account *
{
Account *account = new Account(p, q);
@ -365,6 +513,7 @@ namespace accounts
Account * AccountStoragePrivate::acceptTotpAccount(const QUuid &id, const QString &name, const QString &secret, uint timeStep, int tokenLength, const QDateTime &epoch, Account::Hash hash)
{
Q_Q(AccountStorage);
qCDebug(logger) << "Registering TOTP account:" << id;
const std::function<Account*(AccountPrivate*)> registration([this, q, &id](AccountPrivate *p) -> Account *
{
Account *account = new Account(p, q);
@ -400,8 +549,11 @@ namespace accounts
{
if (m_accept_on_finish) {
m_account->acceptCounter(m_counter);
} else {
qCWarning(logger)
<< "Rejecting counter update for account:" << m_account->id()
<< "Failed to save the updated account details to storage";
}
// TODO: warn if not
deleteLater();
}

View File

@ -6,10 +6,14 @@
#include "validation.h"
#include "../base32/base32.h"
#include "../logging_p.h"
#include "../oath/oath.h"
#include <QTimer>
KEYSMITH_LOGGER(logger, ".accounts.actions")
KEYSMITH_LOGGER(dispatcherLogger, ".accounts.dispatcher")
namespace accounts
{
AccountJob::AccountJob() : QObject()
@ -55,6 +59,9 @@ namespace accounts
void SaveHotp::run(void)
{
if (!checkHotpAccount(m_id, m_accountName, m_secret, m_tokenLength)) {
qCDebug(logger)
<< "Unable to save HOTP account:" << m_id
<< "Invalid account details";
Q_EMIT invalid();
Q_EMIT finished();
return;
@ -63,11 +70,15 @@ namespace accounts
const PersistenceAction act([this](QSettings &settings) -> void
{
if (!settings.isWritable()) {
// TODO: warn if not
qCWarning(logger)
<< "Unable to save HOTP account:" << m_id
<< "Storage not writable";
Q_EMIT invalid();
return;
}
qCInfo(logger) << "Saving HOTP account:" << m_id;
const QString group = m_id.toString();
settings.remove(group);
settings.beginGroup(group);
@ -91,7 +102,9 @@ namespace accounts
void SaveTotp::run(void)
{
if (!checkTotpAccount(m_id, m_accountName, m_secret, m_tokenLength, m_timeStep)) {
// TODO: warn if not
qCDebug(logger)
<< "Unable to save TOTP account:" << m_id
<< "Invalid account details";
Q_EMIT invalid();
Q_EMIT finished();
return;
@ -100,11 +113,15 @@ namespace accounts
const PersistenceAction act([this](QSettings &settings) -> void
{
if (!settings.isWritable()) {
// TODO: warn if not
qCWarning(logger)
<< "Unable to save TOTP account:" << m_id
<< "Storage not writable";
Q_EMIT invalid();
return;
}
qCInfo(logger) << "Saving TOTP account:" << m_id;
const QString group = m_id.toString();
settings.remove(group);
settings.beginGroup(group);
@ -130,11 +147,13 @@ namespace accounts
const PersistenceAction act([this](QSettings &settings) -> void
{
if (!settings.isWritable()) {
// TODO: warn if not
qCWarning(logger) << "Unable to delete accounts: storage not writable";
Q_EMIT invalid();
return;
}
qCInfo(logger) << "Deleting accounts";
for (const QUuid &id : m_ids) {
settings.remove(id.toString());
}
@ -148,11 +167,15 @@ namespace accounts
{
const PersistenceAction act([this](QSettings &settings) -> void
{
qCInfo(logger, "Loading accounts from storage");
const QStringList entries = settings.childGroups();
for (const QString &group : entries) {
const QUuid id(group);
if (id.isNull()) {
qCDebug(logger)
<< "Ignoring:" << group
<< "Not an account section";
continue;
}
@ -165,6 +188,7 @@ namespace accounts
const int tokenLength = settings.value("pinLength").toInt(&ok);
if (!ok || (type != "hotp" && type != "totp")) {
qCWarning(logger) << "Skipping invalid account:" << id;
settings.endGroup();
continue;
}
@ -173,6 +197,7 @@ namespace accounts
ok = false;
const uint timeStep = settings.value("timeStep").toUInt(&ok);
if (ok && checkTotpAccount(id, accountName, secret, tokenLength, timeStep)) {
qCInfo(logger) << "Found valid TOTP account:" << id;
Q_EMIT foundTotp(
id,
accountName,
@ -186,6 +211,7 @@ namespace accounts
ok = false;
const quint64 counter = settings.value("counter").toULongLong(&ok);
if (ok && checkHotpAccount(id, accountName, secret, tokenLength)) {
qCInfo(logger) << "Found valid HOTP account:" << id;
Q_EMIT foundHotp(
id,
accountName,
@ -212,15 +238,15 @@ namespace accounts
void ComputeTotp::run(void)
{
if (!checkTotp(m_secret, m_tokenLength, m_timeStep)) {
qCDebug(logger) << "Unable to compute TOTP token: invalid token details";
Q_EMIT finished();
// TODO warn about this
return;
}
std::optional<QByteArray> secret = base32::decode(m_secret);
if (!secret.has_value()) {
qCDebug(logger) << "Unable to compute TOTP token: unable to decode secret";
Q_EMIT finished();
// TODO warn about this
return;
}
@ -237,31 +263,32 @@ namespace accounts
hash = QCryptographicHash::Sha1;
break;
default:
qCDebug(logger) << "Unable to compute TOTP token: unknown hashing algorithm:" << m_hash;
Q_EMIT finished();
// TODO warn about this
return;
}
const std::optional<oath::Algorithm> algorithm = oath::Algorithm::usingDynamicTruncation(hash, m_tokenLength);
if (!algorithm) {
qCDebug(logger) << "Unable to compute TOTP token: unable to set up truncation for token length:" << m_tokenLength;
Q_EMIT finished();
// TODO warn about this
return;
}
const std::optional<quint64> counter = oath::count(m_epoch, m_timeStep, m_clock);
if (!counter) {
qCDebug(logger) << "Unable to compute TOTP token: unable to count time steps";
Q_EMIT finished();
// TODO warn about this
return;
}
const std::optional<QString> token = algorithm->compute(*counter, secret->data(), secret->size());
if (token) {
Q_EMIT otp(*token);
} else {
qCDebug(logger) << "Failed to compute TOTP token";
}
// TODO warn if not
Q_EMIT finished();
}
@ -274,15 +301,15 @@ namespace accounts
void ComputeHotp::run(void)
{
if (!checkHotp(m_secret, m_tokenLength)) {
qCDebug(logger) << "Unable to compute HOTP token: invalid token details";
Q_EMIT finished();
// TODO warn about this
return;
}
std::optional<QByteArray> secret = base32::decode(m_secret);
if (!secret.has_value()) {
qCDebug(logger) << "Unable to compute HOTP token: unable to decode secret";
Q_EMIT finished();
// TODO warn about this
return;
}
@ -291,16 +318,17 @@ namespace accounts
? oath::Algorithm::usingTruncationOffset(QCryptographicHash::Sha1, (uint) m_offset, encoder)
: oath::Algorithm::usingDynamicTruncation(QCryptographicHash::Sha1, encoder);
if (!algorithm) {
qCDebug(logger) << "Unable to compute HOTP token: unable to set up truncation for token length:" << m_tokenLength;
Q_EMIT finished();
// TODO warn about this
return;
}
const std::optional<QString> token = algorithm->compute(m_counter, secret->data(), secret->size());
if (token) {
Q_EMIT otp(*token);
} else {
qCDebug(logger) << "Failed to compute HOTP token";
}
// TODO warn if not
Q_EMIT finished();
}
@ -317,6 +345,7 @@ namespace accounts
void Dispatcher::queueAndProceed(AccountJob *job, const std::function<void(void)> &setup_callbacks)
{
if (job) {
qCDebug(dispatcherLogger) << "Queuing job for dispatcher";
job->moveToThread(m_thread);
setup_callbacks();
m_pending.append(job);
@ -326,7 +355,11 @@ namespace accounts
void Dispatcher::dispatchNext(void)
{
qCDebug(dispatcherLogger) << "Handling request to dispatch next job";
if (!empty() && !m_current) {
qCDebug(dispatcherLogger) << "Dispatching next job";
m_current = m_pending.takeFirst();
QObject::connect(m_current, &AccountJob::finished, this, &Dispatcher::next);
QObject::connect(this, &Dispatcher::dispatch, m_current, &AccountJob::run);
@ -336,6 +369,8 @@ namespace accounts
void Dispatcher::next(void)
{
qCDebug(dispatcherLogger) << "Handling next continuation in dispatcher";
QObject *from = sender();
AccountJob *job = from ? qobject_cast<AccountJob*>(from) : nullptr;
if (job) {

View File

@ -3,10 +3,13 @@
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "keysmith.h"
#include "../logging_p.h"
#include <QClipboard>
#include <QGuiApplication>
KEYSMITH_LOGGER(logger, ".app")
namespace app
{
Keysmith::Keysmith(QObject *parent): QObject(parent), m_storage(nullptr)
@ -15,7 +18,7 @@ namespace app
Keysmith::~Keysmith()
{
//TODO: log about this
qCDebug(logger) << "Tearing down Keysmith application; requesting disposal of account storage";
if (m_storage) {
m_storage->dispose();
}
@ -24,10 +27,12 @@ namespace app
void Keysmith::copyToClipboard(const QString &text)
{
QClipboard * clipboard = QGuiApplication::clipboard();
if (clipboard) {
clipboard->setText(text);
if (!clipboard) {
qCWarning(logger) << "Unable to copy text: clipboard not available";
return;
}
// TODO warn about this
clipboard->setText(text);
}
model::SimpleAccountListModel * Keysmith::accountListModel(void)

View File

@ -3,6 +3,9 @@
* SPDX-FileCopyrightText: 2019-2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "base32.h"
#include "../logging_p.h"
KEYSMITH_LOGGER(logger, ".base32")
static const QChar alphaMinLowerCase(QLatin1Char('a'));
static const QChar alphaMaxLowerCase(QLatin1Char('z'));
@ -47,11 +50,12 @@ static std::optional<quint64> decode(const QString &encoded, int index)
quint64 result = 0ULL;
for (int i = 0; i < 8; ++i) {
std::optional<int> v = decode(encoded[index + i]);
const QChar inputChar = encoded[index + i];
std::optional<int> v = decode(inputChar);
if (v) {
result = (result << 5) | *v;
} else {
// TODO warn about this
qCDebug(logger) << "Not a valid base32 character:" << inputChar;
return std::nullopt;
}
}
@ -229,19 +233,19 @@ namespace base32
{
int max = until == -1 ? encoded.size() : until;
if (!checkInputRange(encoded, from, max)) {
// TODO warn about this
qCDebug(logger) << "Invalid input range from:" << from << "until:" << until << "implied limit:" << max;
return std::nullopt;
}
std::optional<int> padding = isBase32(encoded, from, max);
if (!padding) {
// TODO warn about this
qCDebug(logger) << "Unable to decode: input range is not valid base32";
return std::nullopt;
}
size_t needed = requiredCapacity(*padding, from, max);
if (outlen < needed) {
// TODO warn about this
qCDebug(logger) << "Unable to decode: required capacity:" << needed << "exceeds allocated output buffer size:" << outlen;
return std::nullopt;
}
@ -265,7 +269,7 @@ namespace base32
std::optional<size_t> capacity = validate(encoded);
if (!capacity) {
// TODO warn about this
qCDebug(logger) << "Unable to decode input: invalid base32";
return std::nullopt;
}
@ -274,8 +278,9 @@ namespace base32
decoded.resize((int) *capacity);
if (decode(encoded, decoded.data(), *capacity)) {
result.emplace(decoded);
} else {
qCDebug(logger) << "Failed to decode base32";
}
// TODO warn if not
return result;
}

View File

@ -3,6 +3,9 @@
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "hmac.h"
#include "../logging_p.h"
KEYSMITH_LOGGER(logger, ".hmac")
static QByteArray hmac_stage(QCryptographicHash::Algorithm algorithm, char * const keyBuf, int ksize, int blockSize, const char fillPad, const char xorKey, const QByteArray &message)
{
@ -98,12 +101,14 @@ namespace hmac
std::optional<int> maybeBlockSize = blockSize(algorithm);
if (!maybeBlockSize) {
// TODO warn
qCDebug(logger) << "Unable to determine block size for hashing algorithm:" << algorithm;
return std::nullopt;
}
if (!validateKeySize(algorithm, ksize, requireSaneKeyLength)) {
// TODO warn
qCDebug(logger)
<< "Invalid HMAC key size:" << ksize << "for hashing algorithm:" << algorithm
<< "Sane key length requirements apply:" << requireSaneKeyLength;
return std::nullopt;
}

View File

@ -4,31 +4,35 @@
*/
#include "accounts.h"
#include "../logging_p.h"
#include <QtDebug>
KEYSMITH_LOGGER(logger, ".model.accounts")
namespace model
{
qint64 millisecondsLeftForToken(const QDateTime &epoch, uint timeStep, const std::function<qint64(void)> &clock)
{
QDateTime now = QDateTime::fromMSecsSinceEpoch(clock());
if (epoch.isValid() && now.isValid() && timeStep > 0) {
/*
* Avoid integer overflow by casting to the wider type first before multiplying.
* Not likely to happen 'in the wild', but good practice nevertheless
*/
qint64 step = ((qint64) timeStep) * 1000LL;
qint64 diff = epoch.msecsTo(now);
/*
* Compensate for the fact that % operator is not the same as mathematical mod in case diff is negative.
* diff is negative when the given epoch is in the 'future' compared to the current clock value.
*/
return diff < 0 ? - (diff % step) : step - (diff % step);
if (!epoch.isValid() || !now.isValid() || timeStep == 0) {
qCDebug(logger) << "Unable to compute milliseconds left: invalid arguments";
return -1;
}
// TODO: warn if not
return -1;
/*
* Avoid integer overflow by casting to the wider type first before multiplying.
* Not likely to happen 'in the wild', but good practice nevertheless
*/
qint64 step = ((qint64) timeStep) * 1000LL;
qint64 diff = epoch.msecsTo(now);
/*
* Compensate for the fact that % operator is not the same as mathematical mod in case diff is negative.
* diff is negative when the given epoch is in the 'future' compared to the current clock value.
*/
return diff < 0 ? - (diff % step) : step - (diff % step);
}
AccountView::AccountView(accounts::Account *model, QObject *parent) : QObject(parent), m_model(model)
@ -72,11 +76,12 @@ namespace model
qint64 AccountView::millisecondsLeftForToken(void) const
{
if (isTotp()) {
return model::millisecondsLeftForToken(m_model->epoch(), m_model->timeStep());
if (!isTotp()) {
qCDebug(logger) << "Unable to compute milliseconds left for token, wrong account type:" << m_model->algorithm();
return -1;
}
// TODO: warn if not
return -1;
return model::millisecondsLeftForToken(m_model->epoch(), m_model->timeStep());
}
SimpleAccountListModel::SimpleAccountListModel(accounts::AccountStorage *storage, QObject *parent) : QAbstractListModel(parent), m_storage(storage), m_index(QVector<QString>())
@ -91,8 +96,9 @@ namespace model
m_index.append(name);
m_accounts[name] = existingAccount;
existingAccount->recompute();
} else {
qCDebug(logger) << "Account storage reported an account (name) but did not yield a valid account object";
}
// TODO: warn if not
}
endResetModel();
}
@ -117,27 +123,29 @@ namespace model
QVariant SimpleAccountListModel::data(const QModelIndex &account, int role) const
{
if (!account.isValid()) {
// TODO warn about this
qCDebug(logger) << "Not returning any data, model index is invalid";
return QVariant();
}
int accountIndex = account.row();
if (accountIndex < 0 || m_index.size() < accountIndex) {
// TODO warn about this
qCDebug(logger) << "Not returning any data, model index is out of bounds:" << accountIndex << "model size is:" << m_index.size();
return QVariant();
}
if (role == NonStandardRoles::AccountRole) {
const QString accountName = m_index.at(accountIndex);
accounts::Account * model = m_accounts.value(accountName, nullptr);
if (model) {
// assume QML ownership: don't worry about object lifecycle
return QVariant::fromValue(new AccountView(model));
}
if (role != NonStandardRoles::AccountRole) {
qCDebug(logger) << "Not returning any data, unknown role:" << role;
}
// TODO warn about this
return QVariant();
const QString accountName = m_index.at(accountIndex);
accounts::Account * model = m_accounts.value(accountName, nullptr);
if (model) {
// assume QML ownership: don't worry about object lifecycle
return QVariant::fromValue(new AccountView(model));
} else {
qCDebug(logger) << "Not returning any data, unable to find associated account model for:" << accountIndex;
return QVariant();
}
}
int SimpleAccountListModel::rowCount(const QModelIndex &parent) const
@ -149,33 +157,40 @@ namespace model
void SimpleAccountListModel::added(const QString &account)
{
accounts::Account * newAccount = m_storage->get(account);
if (newAccount) {
if (m_accounts.contains(account)) {
//TODO warn about this
removed(account);
}
int accountIndex = m_index.size();
beginInsertRows(QModelIndex(), accountIndex, accountIndex);
m_index.append(account);
m_accounts[account] = newAccount;
newAccount->recompute();
endInsertRows();
if (!newAccount) {
qCDebug(logger) << "Unable to handle added account: underlying storage did not return a valid object";
return;
}
// TODO: warn if not
if (m_accounts.contains(account)) {
qCDebug(logger) << "Added account already/still part of the model: requesting removal of the old one from the model first";
removed(account);
}
int accountIndex = m_index.size();
qCDebug(logger) << "Adding (new) account to the model at position:" << accountIndex;
beginInsertRows(QModelIndex(), accountIndex, accountIndex);
m_index.append(account);
m_accounts[account] = newAccount;
newAccount->recompute();
endInsertRows();
}
void SimpleAccountListModel::removed(const QString &account)
{
int accountIndex = m_index.indexOf(account);
if (accountIndex >= 0) {
qCDebug(logger) << "Removing (old) account from the model at position:" << accountIndex;
beginRemoveRows(QModelIndex(), accountIndex, accountIndex);
m_index.remove(accountIndex);
m_accounts.remove(account);
endRemoveRows();
} else {
qCDebug(logger) << "Unable to handle account removal: account not part of the model";
}
// TODO: warn if not
}
bool SimpleAccountListModel::isNameStillAvailable(const QString &account) const
@ -190,7 +205,7 @@ namespace model
QValidator::State AccountNameValidator::validate(QString &input, int &pos) const
{
if (!m_accounts) {
// TODO warn about this
qCDebug(logger) << "Unable to validat account name: missing accounts model object";
return QValidator::Invalid;
}
@ -213,7 +228,8 @@ namespace model
if (accounts) {
m_accounts = accounts;
Q_EMIT accountsChanged();
} else {
qCDebug(logger) << "Ignoring new accounts model: not a valid object";
}
// TODO warn if not
}
}

View File

@ -5,6 +5,9 @@
#include "oath.h"
#include "../hmac/hmac.h"
#include "../logging_p.h"
KEYSMITH_LOGGER(logger, ".oath")
static QString encodeDefaults(quint32 value, uint tokenLength)
{
@ -100,17 +103,17 @@ namespace oath
{
std::optional<uint> digestSize = hmac::outputSize(algorithm);
if (!digestSize) {
// TODO warn about this
qCDebug(logger) << "Unable to determine digest size for algorithm:" << algorithm;
return std::nullopt;
}
if ((*digestSize) < 20) {
// TODO warn about this
qCDebug(logger) << "Digest is too small for dynamic truncation with algorithm:" << algorithm << "digest size:" << *digestSize << "needed:" << 20;
return std::nullopt;
}
if (!validate(encoder)) {
// TODO warn about this
qCDebug(logger) << "Invalid token encoder";
return std::nullopt;
}
@ -122,17 +125,17 @@ namespace oath
{
std::optional<uint> digestSize = hmac::outputSize(algorithm);
if (!digestSize) {
// TODO warn about this
qCDebug(logger) << "Unable to determine digest size for algorithm:" << algorithm;
return std::nullopt;
}
if (offset >= ((*digestSize) - 4)) {
// TODO warn about this
qCDebug(logger) << "Digest is too small for truncation offset:" << offset << "with algorithm:" << algorithm << "digest size:" << *digestSize << "needed:" << offset + 4;
return std::nullopt;
}
if (!validate(encoder)) {
// TODO warn about this
qCDebug(logger) << "Invalid token encoder";
return std::nullopt;
}
@ -155,7 +158,9 @@ namespace oath
}
if (!hmac::validateKeySize(m_algorithm, length, m_enforceKeyLength)) {
// TODO warn about this
qCDebug(logger)
<< "Invalid key size:" << length << "for algorithm:" << m_algorithm
<< "Sane key length requirements apply:" << m_enforceKeyLength;
return std::nullopt;
}
@ -172,8 +177,8 @@ namespace oath
result = Encoder::reduceMod10(result, m_encoder.tokenLength());
return std::optional<QString>(m_encoder.encode(result));
}
// TODO warn if not
qCDebug(logger) << "Failed to compute token";
return std::nullopt;
}
@ -214,12 +219,12 @@ namespace oath
qint64 now = clock();
if (now < epochMillis) {
// TODO warn about this
qCDebug(logger) << "Unable to count time steps: epoch is in the future";
return std::nullopt;
}
if (timeStep == 0UL) {
// TODO warn about this
qCDebug(logger) << "Unable to count time steps: invalid step size:" << timeStep;
return std::nullopt;
}