File indexing completed on 2024-04-28 05:50:06
0001 /* 0002 * SPDX-License-Identifier: GPL-3.0-or-later 0003 * SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com> 0004 */ 0005 #include "actions_p.h" 0006 #include "validation.h" 0007 0008 #include "../base32/base32.h" 0009 #include "../logging_p.h" 0010 #include "../oath/oath.h" 0011 0012 #include <QMetaEnum> 0013 #include <QScopedPointer> 0014 #include <QTimer> 0015 0016 #include <limits> 0017 0018 KEYSMITH_LOGGER(logger, ".accounts.actions") 0019 KEYSMITH_LOGGER(dispatcherLogger, ".accounts.dispatcher") 0020 0021 static const quint64 maxCounter = std::numeric_limits<quint64>::max(); 0022 static const int hashTypeId = qRegisterMetaType<accounts::Account::Hash>(); 0023 0024 namespace accounts 0025 { 0026 AccountJob::AccountJob() : 0027 QObject() 0028 { 0029 } 0030 0031 AccountJob::~AccountJob() 0032 { 0033 } 0034 0035 Null::Null() : 0036 AccountJob() 0037 { 0038 } 0039 0040 void Null::run(void) 0041 { 0042 Q_EMIT finished(); 0043 } 0044 0045 void AccountJob::run(void) 0046 { 0047 Q_ASSERT_X(false, Q_FUNC_INFO, "should be overridden in derived classes!"); 0048 } 0049 0050 RequestAccountPassword::RequestAccountPassword(const SettingsProvider &settings, AccountSecret *secret) : 0051 AccountJob(), m_settings(settings), m_secret(secret), m_failed(false), m_succeeded(false) 0052 { 0053 } 0054 0055 LoadAccounts::LoadAccounts(const SettingsProvider &settings, const AccountSecret *secret, 0056 const std::function<qint64(void)> &clock) : 0057 AccountJob(), m_settings(settings), m_secret(secret), m_clock(clock) 0058 { 0059 } 0060 0061 DeleteAccounts::DeleteAccounts(const SettingsProvider &settings, const QSet<QUuid> &ids) : 0062 AccountJob(), m_settings(settings), m_ids(ids) 0063 { 0064 } 0065 0066 SaveHotp::SaveHotp(const SettingsProvider &settings, 0067 const QUuid id, const QString &accountName, const QString &issuer, 0068 const secrets::EncryptedSecret &secret, uint tokenLength, 0069 quint64 counter, const std::optional<uint> offset, bool checksum) : 0070 AccountJob(), m_settings(settings), m_id(id), m_accountName(accountName), m_issuer(issuer), 0071 m_secret(secret), m_tokenLength(tokenLength), m_counter(counter), m_offset(offset), m_checksum(checksum) 0072 { 0073 } 0074 0075 SaveTotp::SaveTotp(const SettingsProvider &settings, 0076 const QUuid id, const QString &accountName, const QString &issuer, 0077 const secrets::EncryptedSecret &secret, uint tokenLength, 0078 uint timeStep, const QDateTime &epoch, Account::Hash hash, 0079 const std::function<qint64(void)> &clock) : 0080 AccountJob(), m_settings(settings), m_id(id), m_accountName(accountName), m_issuer(issuer), 0081 m_secret(secret), m_tokenLength(tokenLength), m_timeStep(timeStep), m_epoch(epoch), m_hash(hash), m_clock(clock) 0082 { 0083 } 0084 0085 void SaveHotp::run(void) 0086 { 0087 if (!checkId(m_id) || !checkName(m_accountName) || !checkIssuer(m_issuer) || 0088 !checkTokenLength(m_tokenLength) || !checkOffset(m_offset, QCryptographicHash::Sha1)) { 0089 qCDebug(logger) 0090 << "Unable to save HOTP account:" << m_id 0091 << "Invalid account details"; 0092 Q_EMIT invalid(); 0093 Q_EMIT finished(); 0094 return; 0095 } 0096 0097 const PersistenceAction act([this](QSettings &settings) -> void 0098 { 0099 if (!settings.isWritable()) { 0100 qCWarning(logger) 0101 << "Unable to save HOTP account:" << m_id 0102 << "Storage not writable"; 0103 Q_EMIT invalid(); 0104 return; 0105 } 0106 0107 qCInfo(logger) << "Saving HOTP account:" << m_id; 0108 0109 const QString group = m_id.toString(); 0110 settings.remove(group); 0111 settings.beginGroup(group); 0112 settings.setValue(QStringLiteral("account"), m_accountName); 0113 if (!m_issuer.isNull()) { 0114 settings.setValue(QStringLiteral("issuer"), m_issuer); 0115 } 0116 settings.setValue(QStringLiteral("type"), QStringLiteral("hotp")); 0117 QString encodedNonce = QString::fromUtf8(m_secret.nonce().toBase64(QByteArray::Base64Encoding)); 0118 QString encodedSecret = QString::fromUtf8(m_secret.cryptText().toBase64(QByteArray::Base64Encoding)); 0119 settings.setValue(QStringLiteral("secret"), encodedSecret); 0120 settings.setValue(QStringLiteral("nonce"), encodedNonce); 0121 settings.setValue(QStringLiteral("counter"), m_counter); 0122 settings.setValue(QStringLiteral("pinLength"), m_tokenLength); 0123 if (m_offset) { 0124 settings.setValue(QStringLiteral("offset"), *m_offset); 0125 } 0126 settings.setValue(QStringLiteral("checksum"), m_checksum); 0127 settings.endGroup(); 0128 0129 // Try to guarantee that data will have been written before claiming the account was actually saved 0130 settings.sync(); 0131 0132 Q_EMIT saved(m_id, m_accountName, m_issuer, m_secret.cryptText(), m_secret.nonce(), m_tokenLength, 0133 m_counter, m_offset.has_value(), m_offset ? *m_offset : 0U, m_checksum); 0134 }); 0135 m_settings(act); 0136 0137 Q_EMIT finished(); 0138 } 0139 0140 void SaveTotp::run(void) 0141 { 0142 if (!checkId(m_id) || !checkName(m_accountName) || !checkIssuer(m_issuer) || 0143 !checkTokenLength(m_tokenLength) || !checkTimeStep(m_timeStep) || !checkEpoch(m_epoch, m_clock)) { 0144 qCDebug(logger) 0145 << "Unable to save TOTP account:" << m_id 0146 << "Invalid account details"; 0147 Q_EMIT invalid(); 0148 Q_EMIT finished(); 0149 return; 0150 } 0151 0152 const PersistenceAction act([this](QSettings &settings) -> void 0153 { 0154 if (!settings.isWritable()) { 0155 qCWarning(logger) 0156 << "Unable to save TOTP account:" << m_id 0157 << "Storage not writable"; 0158 Q_EMIT invalid(); 0159 return; 0160 } 0161 0162 qCInfo(logger) << "Saving TOTP account:" << m_id; 0163 0164 const QString group = m_id.toString(); 0165 settings.remove(group); 0166 settings.beginGroup(group); 0167 settings.setValue(QStringLiteral("account"), m_accountName); 0168 if (!m_issuer.isNull()) { 0169 settings.setValue(QStringLiteral("issuer"), m_issuer); 0170 } 0171 settings.setValue(QStringLiteral("type"), QStringLiteral("totp")); 0172 QString encodedNonce = QString::fromUtf8(m_secret.nonce().toBase64(QByteArray::Base64Encoding)); 0173 QString encodedSecret = QString::fromUtf8(m_secret.cryptText().toBase64(QByteArray::Base64Encoding)); 0174 settings.setValue(QStringLiteral("secret"), encodedSecret); 0175 settings.setValue(QStringLiteral("nonce"), encodedNonce); 0176 settings.setValue(QStringLiteral("timeStep"), m_timeStep); 0177 settings.setValue(QStringLiteral("pinLength"), m_tokenLength); 0178 settings.setValue(QStringLiteral("epoch"), m_epoch.toUTC().toString(Qt::ISODateWithMs)); 0179 settings.setValue(QStringLiteral("hash"), QVariant::fromValue<Account::Hash>(m_hash).toString()); 0180 settings.endGroup(); 0181 0182 // Try to guarantee that data will have been written before claiming the account was actually saved 0183 settings.sync(); 0184 0185 Q_EMIT saved(m_id, m_accountName, m_issuer, m_secret.cryptText(), m_secret.nonce(), m_tokenLength, 0186 m_timeStep, m_epoch, m_hash); 0187 }); 0188 m_settings(act); 0189 0190 Q_EMIT finished(); 0191 } 0192 0193 void DeleteAccounts::run(void) 0194 { 0195 const PersistenceAction act([this](QSettings &settings) -> void 0196 { 0197 if (!settings.isWritable()) { 0198 qCWarning(logger) << "Unable to delete accounts: storage not writable"; 0199 Q_EMIT invalid(); 0200 return; 0201 } 0202 0203 qCInfo(logger) << "Deleting accounts"; 0204 0205 for (const QUuid &id : m_ids) { 0206 settings.remove(id.toString()); 0207 } 0208 }); 0209 m_settings(act); 0210 0211 Q_EMIT finished(); 0212 } 0213 0214 void RequestAccountPassword::fail(void) 0215 { 0216 if (m_failed || m_succeeded) { 0217 qCDebug(logger) << "Suppressing 'failure' in unlocking accounts: already handled"; 0218 return; 0219 } 0220 0221 m_failed = true; 0222 QObject::disconnect(m_secret, &AccountSecret::requestsCancelled, this, &RequestAccountPassword::fail); 0223 QObject::disconnect(m_secret, &AccountSecret::passwordAvailable, this, &RequestAccountPassword::unlock); 0224 QObject::disconnect(m_secret, &AccountSecret::keyAvailable, this, &RequestAccountPassword::finish); 0225 Q_EMIT failed(); 0226 Q_EMIT finished(); 0227 } 0228 0229 void RequestAccountPassword::unlock(void) 0230 { 0231 secrets::SecureMasterKey * derived = m_secret->deriveKey(); 0232 std::optional<secrets::EncryptedSecret> challenge = m_secret->challenge(); 0233 if (derived && challenge) { 0234 qCInfo(logger) << "Successfully derived key for storage"; 0235 return; 0236 } else { 0237 qCInfo(logger) << "Failed to unlock storage:" 0238 << "Unable to derive secret encryption/decryption key or generate its matching challenge"; 0239 } 0240 } 0241 0242 void RequestAccountPassword::finish(void) 0243 { 0244 if (m_succeeded || m_failed) { 0245 qCDebug(logger) << "Suppressing 'success' in unlocking accounts: already handled"; 0246 return; 0247 } 0248 0249 QObject::disconnect(m_secret, &AccountSecret::requestsCancelled, this, &RequestAccountPassword::fail); 0250 QObject::disconnect(m_secret, &AccountSecret::passwordAvailable, this, &RequestAccountPassword::unlock); 0251 QObject::disconnect(m_secret, &AccountSecret::keyAvailable, this, &RequestAccountPassword::finish); 0252 std::optional<secrets::EncryptedSecret> challenge = m_secret->challenge(); 0253 secrets::SecureMasterKey * derived = m_secret->key(); 0254 if (!derived) { 0255 qCInfo(logger) << "Failed to finish unlocking storage: no secret encryption/decryption key"; 0256 m_failed = true; 0257 Q_EMIT failed(); 0258 Q_EMIT finished(); 0259 return; 0260 } 0261 0262 // sanity check: challenge should be available once key derivation has completed successfully 0263 if (!challenge) { 0264 qCInfo(logger) << "Failed to finish unlocking storage: no challenge for encryption/decryption key"; 0265 m_failed = true; 0266 Q_EMIT failed(); 0267 Q_EMIT finished(); 0268 return; 0269 } 0270 0271 bool ok = false; 0272 m_settings([derived, &challenge, &ok](QSettings &settings) -> void 0273 { 0274 if (!settings.isWritable()) { 0275 qCWarning(logger) << "Unable to save account secret key parameters: storage not writable"; 0276 return; 0277 } 0278 0279 const secrets::KeyDerivationParameters params = derived->params(); 0280 0281 QString encodedSalt = QString::fromUtf8(derived->salt().toBase64(QByteArray::Base64Encoding)); 0282 QString encodedChallenge = QString::fromUtf8(challenge->cryptText().toBase64(QByteArray::Base64Encoding)); 0283 QString encodedNonce = QString::fromUtf8(challenge->nonce().toBase64(QByteArray::Base64Encoding)); 0284 settings.beginGroup(QStringLiteral("master-key")); 0285 settings.setValue(QStringLiteral("salt"), encodedSalt); 0286 settings.setValue(QStringLiteral("cpu"), params.cpuCost()); 0287 settings.setValue(QStringLiteral("memory"), (quint64) params.memoryCost()); 0288 settings.setValue(QStringLiteral("algorithm"), params.algorithm()); 0289 settings.setValue(QStringLiteral("length"), params.keyLength()); 0290 settings.setValue(QStringLiteral("nonce"), encodedNonce); 0291 settings.setValue(QStringLiteral("challenge"), encodedChallenge); 0292 settings.endGroup(); 0293 ok = true; 0294 }); 0295 0296 if (ok) { 0297 qCInfo(logger) << "Successfully unlocked storage"; 0298 m_succeeded = true; 0299 Q_EMIT unlocked(); 0300 } else { 0301 qCInfo(logger) << "Failed to finish unlocking storage: unable to store parameters"; 0302 m_failed = true; 0303 Q_EMIT failed(); 0304 } 0305 Q_EMIT finished(); 0306 } 0307 0308 void RequestAccountPassword::run(void) 0309 { 0310 if (!m_secret) { 0311 qCDebug(logger) << "Unable to request accounts password: no account secret object"; 0312 m_failed = true; 0313 Q_EMIT failed(); 0314 Q_EMIT finished(); 0315 return; 0316 } 0317 0318 QObject::connect(m_secret, &AccountSecret::passwordAvailable, this, &RequestAccountPassword::unlock); 0319 QObject::connect(m_secret, &AccountSecret::requestsCancelled, this, &RequestAccountPassword::fail); 0320 QObject::connect(m_secret, &AccountSecret::keyAvailable, this, &RequestAccountPassword::finish); 0321 0322 if (!m_secret->isStillAlive()) { 0323 qCDebug(logger) << "Unable to request accounts password: account secret marked for death"; 0324 fail(); 0325 return; 0326 } 0327 0328 bool ok = false; 0329 m_settings([this, &ok](QSettings &settings) -> void 0330 { 0331 if (!settings.isWritable()) { 0332 qCWarning(logger) << "Unable to request password for accounts: storage not writable"; 0333 return; 0334 } 0335 0336 QStringList groups = settings.childGroups(); 0337 if (!groups.contains(QStringLiteral("master-key"))) { 0338 qCInfo(logger) << "No key derivation parameters found: requesting 'new' password for accounts"; 0339 ok = m_secret->requestNewPassword(); 0340 return; 0341 } 0342 0343 settings.beginGroup(QStringLiteral("master-key")); 0344 QByteArray salt; 0345 QByteArray nonce; 0346 QByteArray challenge; 0347 quint64 cpuCost = 0ULL; 0348 quint64 keyLength = 0ULL; 0349 size_t memoryCost = 0ULL; 0350 // HACK: disables challenge verification, remove at some point! 0351 bool challengeAvailable = settings.contains(QStringLiteral("challenge")); 0352 int algorithm = settings.value(QStringLiteral("algorithm")).toInt(&ok); 0353 if (ok) { 0354 ok = false; 0355 keyLength = settings.value(QStringLiteral("length")).toULongLong(&ok); 0356 } 0357 if (ok) { 0358 ok = false; 0359 cpuCost = settings.value(QStringLiteral("cpu")).toULongLong(&ok); 0360 } 0361 if (ok) { 0362 ok = false; 0363 memoryCost = settings.value(QStringLiteral("memory")).toULongLong(&ok); 0364 } 0365 if (ok) { 0366 QByteArray encodedSalt = settings.value(QStringLiteral("salt")).toString().toUtf8(); 0367 salt = QByteArray::fromBase64(encodedSalt, QByteArray::Base64Encoding); 0368 ok = !salt.isEmpty() && secrets::SecureMasterKey::validate(salt); 0369 } 0370 0371 // HACK: disables challenge verification, remove at some point! 0372 if (challengeAvailable && ok) { 0373 QByteArray encodedChallenge = settings.value(QStringLiteral("challenge")).toString().toUtf8(); 0374 challenge = QByteArray::fromBase64(encodedChallenge, QByteArray::Base64Encoding); 0375 ok = !challenge.isEmpty(); 0376 } 0377 // HACK: disables challenge verification, remove at some point! 0378 if (challengeAvailable && ok) { 0379 QByteArray encodedNonce = settings.value(QStringLiteral("nonce")).toString().toUtf8(); 0380 nonce = QByteArray::fromBase64(encodedNonce, QByteArray::Base64Encoding); 0381 ok = !nonce.isEmpty(); 0382 } 0383 settings.endGroup(); 0384 0385 const auto params = secrets::KeyDerivationParameters::create(keyLength, algorithm, memoryCost, cpuCost); 0386 const auto encryptedChallenge = secrets::EncryptedSecret::from(challenge, nonce); 0387 0388 // HACK: disables challenge verification, remove at some point! 0389 if (!ok || !params || !secrets::SecureMasterKey::validate(*params) || (challengeAvailable && !encryptedChallenge)) { 0390 qCDebug(logger) << "Unable to request 'existing' password: invalid challenge, nonce, salt or key derivation parameters"; 0391 return; 0392 } 0393 0394 qCInfo(logger) << "Requesting 'existing' password for accounts"; 0395 ok = challengeAvailable 0396 ? m_secret->requestExistingPassword(*encryptedChallenge, salt, *params) 0397 : m_secret->requestExistingPassword(salt, *params); // HACK: disables challenge verification, remove at some point! 0398 }); 0399 0400 if (!ok) { 0401 qCInfo(logger) << "Unable to unlock storage: failed to request password for accounts"; 0402 fail(); 0403 } 0404 } 0405 0406 void LoadAccounts::run(void) 0407 { 0408 if (!m_secret || !m_secret->key()) { 0409 qCDebug(logger) << "Unable to load accounts: secret decryption key not available"; 0410 Q_EMIT finished(); 0411 return; 0412 } 0413 0414 bool failed = false; 0415 const PersistenceAction act([this, &failed](QSettings &settings) -> void 0416 { 0417 qCInfo(logger, "Loading accounts from storage"); 0418 const QStringList entries = settings.childGroups(); 0419 for (const QString &group : entries) { 0420 if (group == QLatin1String("master-key")) { 0421 continue; 0422 } 0423 0424 const QUuid id(group); 0425 if (id.isNull()) { 0426 qCDebug(logger) 0427 << "Ignoring:" << group 0428 << "Not an account section"; 0429 failed = true; 0430 continue; 0431 } 0432 0433 settings.beginGroup(group); 0434 0435 const QString accountName = settings.value(QStringLiteral("account")).toString(); 0436 if (!checkName(accountName)) { 0437 qCWarning(logger) 0438 << "Skipping invalid account:" << id 0439 << "Invalid account name"; 0440 settings.endGroup(); 0441 continue; 0442 } 0443 0444 const QString issuer = settings.value(QStringLiteral("issuer"), QString()).toString(); 0445 if (!checkIssuer(issuer)) { 0446 qCWarning(logger) 0447 << "Skipping invalid account:" << id 0448 << "Invalid account issuer"; 0449 settings.endGroup(); 0450 continue; 0451 } 0452 0453 const QString type = settings.value(QStringLiteral("type")).toString(); 0454 if (type != QStringLiteral("hotp") && type != QStringLiteral("totp")) { 0455 qCWarning(logger) 0456 << "Skipping invalid account:" << id 0457 << "Invalid account type"; 0458 settings.endGroup(); 0459 failed = true; 0460 continue; 0461 } 0462 0463 bool ok = false; 0464 const int tokenLength = settings.value(QStringLiteral("pinLength")).toInt(&ok); 0465 if (!ok || !checkTokenLength(tokenLength)) { 0466 qCWarning(logger) 0467 << "Skipping invalid account:" << id 0468 << "Invalid token length"; 0469 settings.endGroup(); 0470 failed = true; 0471 continue; 0472 } 0473 0474 const QByteArray encodedNonce = settings.value(QStringLiteral("nonce")).toString().toUtf8(); 0475 const QByteArray encodedSecret = settings.value(QStringLiteral("secret")).toString().toUtf8(); 0476 const QByteArray nonce = QByteArray::fromBase64(encodedNonce, QByteArray::Base64Encoding); 0477 const QByteArray secret = QByteArray::fromBase64(encodedSecret, QByteArray::Base64Encoding); 0478 0479 const auto encryptedSecret = secrets::EncryptedSecret::from(secret, nonce); 0480 if (!encryptedSecret) { 0481 qCWarning(logger) 0482 << "Skipping invalid account:" << id 0483 << "Invalid token secret"; 0484 settings.endGroup(); 0485 failed = true; 0486 continue; 0487 } 0488 0489 QScopedPointer<secrets::SecureMemory> decrypted(m_secret->decrypt(*encryptedSecret)); 0490 if (!decrypted) { 0491 qCWarning(logger) 0492 << "Skipping invalid account:" << id 0493 << "Unable to decrypt token secret"; 0494 settings.endGroup(); 0495 failed = true; 0496 continue; 0497 } 0498 0499 if (type == QStringLiteral("totp")) { 0500 ok = false; 0501 const uint timeStep = settings.value(QStringLiteral("timeStep")).toUInt(&ok); 0502 if (!ok || !checkTimeStep(timeStep)) { 0503 qCWarning(logger) 0504 << "Skipping invalid account:" << id 0505 << "Invalid time step"; 0506 settings.endGroup(); 0507 failed = true; 0508 continue; 0509 } 0510 0511 const QDateTime epoch = settings.value(QStringLiteral("epoch"), QDateTime::fromMSecsSinceEpoch(0)) 0512 .toDateTime(); 0513 if (!checkEpoch(epoch, m_clock)) { 0514 qCWarning(logger) 0515 << "Skipping invalid account:" << id 0516 << "Invalid epoch"; 0517 settings.endGroup(); 0518 failed = true; 0519 continue; 0520 } 0521 0522 ok = false; 0523 0524 const auto hashEnum = QMetaEnum::fromType<accounts::Account::Hash>(); 0525 const auto hashDefault = QVariant::fromValue<accounts::Account::Hash>(accounts::Account::Sha1); 0526 const QByteArray hashName = settings.value(QStringLiteral("hash"), hashDefault).toByteArray(); 0527 int hash = hashEnum.keyToValue(hashName.constData(), &ok); 0528 if (!ok) { 0529 qCWarning(logger) 0530 << "Skipping invalid account:" << id 0531 << "Invalid hash"; 0532 settings.endGroup(); 0533 failed = true; 0534 continue; 0535 } 0536 0537 qCInfo(logger) << "Found valid TOTP account:" << id; 0538 Q_EMIT foundTotp(id, accountName, issuer, secret, nonce, tokenLength, 0539 timeStep, epoch, (Account::Hash) hash); 0540 } 0541 0542 if (type == QStringLiteral("hotp")) { 0543 ok = false; 0544 const quint64 counter = settings.value(QStringLiteral("counter")).toULongLong(&ok); 0545 if (!ok) { 0546 qCWarning(logger) 0547 << "Skipping invalid account:" << id 0548 << "Invalid counter"; 0549 settings.endGroup(); 0550 failed = true; 0551 continue; 0552 } 0553 0554 const QVariant offsetVariant = settings.value(QStringLiteral("offset")); 0555 ok = offsetVariant.isNull(); 0556 std::optional<uint> offset = ok ? std::nullopt : std::optional<uint>(offsetVariant.toUInt(&ok)); 0557 0558 if (!ok || !checkOffset(offset, QCryptographicHash::Sha1)) { 0559 qCWarning(logger) 0560 << "Skipping invalid account:" << id 0561 << "Invalid offset"; 0562 settings.endGroup(); 0563 failed = true; 0564 continue; 0565 } 0566 0567 const auto checkSumOff = QStringLiteral("false"); 0568 const auto checksum = settings.value(QStringLiteral("checksum"), checkSumOff).toString(); 0569 if (checksum != QStringLiteral("true") && checksum != checkSumOff) { 0570 qCWarning(logger) 0571 << "Skipping invalid account:" << id 0572 << "Invalid checksum"; 0573 settings.endGroup(); 0574 failed = true; 0575 continue; 0576 } 0577 0578 qCInfo(logger) << "Found valid HOTP account:" << id; 0579 Q_EMIT foundHotp(id, accountName, issuer, secret, nonce, tokenLength, 0580 counter, offset.has_value(), offset ? *offset : 0U, 0581 checksum == QStringLiteral("true")); 0582 } 0583 0584 settings.endGroup(); 0585 } 0586 }); 0587 m_settings(act); 0588 0589 if (failed) { 0590 Q_EMIT failedToLoadAllAccounts(); 0591 } 0592 Q_EMIT finished(); 0593 } 0594 0595 static std::optional<QString> computeToken(const AccountSecret *accountSecret, 0596 const secrets::EncryptedSecret &tokenSecret, 0597 const oath::Algorithm &algorithm, 0598 quint64 counter) 0599 { 0600 QScopedPointer<secrets::SecureMemory> secret(accountSecret->decrypt(tokenSecret)); 0601 if (!secret) { 0602 qCDebug(logger) << "Unable to compute token: failed to decrypt account secret"; 0603 return std::nullopt; 0604 } 0605 0606 return algorithm.compute(counter, reinterpret_cast<char*>(secret->data()), secret->size()); 0607 } 0608 0609 0610 ComputeTotp::ComputeTotp(const AccountSecret *secret, 0611 const secrets::EncryptedSecret &tokenSecret, uint tokenLength, 0612 const QDateTime &epoch, uint timeStep, const Account::Hash hash, 0613 const std::function<qint64(void)> &clock) : 0614 AccountJob(), m_secret(secret), m_tokenSecret(tokenSecret), m_tokenLength(tokenLength), 0615 m_epoch(epoch), m_timeStep(timeStep), m_hash(hash), m_clock(clock) 0616 { 0617 } 0618 0619 void ComputeTotp::run(void) 0620 { 0621 if (!m_secret || !m_secret->key()) { 0622 qCDebug(logger) << "Unable to compute TOTP token: secret decryption key not available"; 0623 Q_EMIT finished(); 0624 return; 0625 } 0626 0627 if (!checkTokenLength(m_tokenLength)) { 0628 qCDebug(logger) << "Unable to compute TOTP token: invalid token length:" << m_tokenLength; 0629 Q_EMIT finished(); 0630 return; 0631 } 0632 0633 if (!checkTimeStep(m_timeStep)) { 0634 qCDebug(logger) << "Unable to compute TOTP token: invalid time step:" << m_timeStep; 0635 Q_EMIT finished(); 0636 return; 0637 } 0638 0639 if (!checkEpoch(m_epoch, m_clock)) { 0640 qCDebug(logger) << "Unable to compute TOTP token: invalid epoch:" << m_epoch; 0641 Q_EMIT finished(); 0642 return; 0643 } 0644 0645 QCryptographicHash::Algorithm hash; 0646 switch(m_hash) 0647 { 0648 case Account::Hash::Sha1: 0649 hash = QCryptographicHash::Sha1; 0650 break; 0651 case Account::Hash::Sha256: 0652 hash = QCryptographicHash::Sha256; 0653 break; 0654 case Account::Hash::Sha512: 0655 hash = QCryptographicHash::Sha512; 0656 break; 0657 default: 0658 qCDebug(logger) << "Unable to compute TOTP token: unknown hashing algorithm:" << m_hash; 0659 Q_EMIT finished(); 0660 return; 0661 0662 } 0663 0664 const std::optional<oath::Algorithm> algorithm = oath::Algorithm::totp(hash, m_tokenLength); 0665 if (!algorithm) { 0666 qCDebug(logger) << "Unable to compute TOTP token: failed to construct algorithm"; 0667 Q_EMIT finished(); 0668 return; 0669 } 0670 0671 const std::optional<quint64> counter = oath::count(m_epoch, m_timeStep, m_clock); 0672 if (!counter) { 0673 qCDebug(logger) << "Unable to compute TOTP token: failed to count time steps"; 0674 Q_EMIT finished(); 0675 return; 0676 } 0677 0678 const auto counterValue = *counter; 0679 const auto validFrom = counterValue < maxCounter 0680 ? oath::fromCounter(counterValue + 1ULL, m_epoch, m_timeStep) 0681 : std::nullopt; 0682 const auto validUntil = counterValue < (maxCounter - 1ULL) 0683 ? oath::fromCounter(counterValue + 2ULL, m_epoch, m_timeStep) 0684 : std::nullopt; 0685 if (!validFrom || !validUntil) { 0686 qCDebug(logger) << "Unable to compute TOTP token: failed to determine expiry datetime of tokens"; 0687 Q_EMIT finished(); 0688 return; 0689 } 0690 0691 const auto token = computeToken(m_secret, m_tokenSecret, *algorithm, counterValue); 0692 const auto nextToken = token 0693 ? computeToken(m_secret, m_tokenSecret, *algorithm, counterValue + 1ULL) 0694 : std::nullopt; 0695 if (token && nextToken) { 0696 Q_EMIT otp(*token, *nextToken, *validFrom, *validUntil); 0697 } else { 0698 qCDebug(logger) << "Failed to compute TOTP tokens"; 0699 } 0700 0701 Q_EMIT finished(); 0702 } 0703 0704 ComputeHotp::ComputeHotp(const AccountSecret *secret, 0705 const secrets::EncryptedSecret &tokenSecret, uint tokenLength, 0706 quint64 counter, const std::optional<uint> offset, bool checksum) : 0707 AccountJob(), m_secret(secret), m_tokenSecret(tokenSecret), m_tokenLength(tokenLength), 0708 m_counter(counter), m_offset(offset), m_checksum(checksum) 0709 { 0710 } 0711 0712 void ComputeHotp::run(void) 0713 { 0714 if (!m_secret || !m_secret->key()) { 0715 qCDebug(logger) << "Unable to compute HOTP token: secret decryption key not available"; 0716 Q_EMIT finished(); 0717 return; 0718 } 0719 0720 if (!checkTokenLength(m_tokenLength)) { 0721 qCDebug(logger) << "Unable to compute HOTP token: invalid token length:" << m_tokenLength; 0722 Q_EMIT finished(); 0723 return; 0724 } 0725 0726 if (!checkOffset(m_offset, QCryptographicHash::Sha1)) { 0727 qCDebug(logger) << "Unable to compute HOTP token: invalid offset:" << *m_offset; 0728 Q_EMIT finished(); 0729 return; 0730 } 0731 0732 const std::optional<oath::Algorithm> algorithm = oath::Algorithm::hotp(m_offset, m_tokenLength, m_checksum); 0733 if (!algorithm) { 0734 qCDebug(logger) << "Unable to compute HOTP token: failed to construct algorithm"; 0735 Q_EMIT finished(); 0736 return; 0737 } 0738 0739 if (m_counter == maxCounter) { 0740 qCDebug(logger) << "Unable to compute HOTP token: counter reached its limit"; 0741 Q_EMIT finished(); 0742 return; 0743 } 0744 0745 const auto token = computeToken(m_secret, m_tokenSecret, *algorithm, m_counter); 0746 const auto nextToken = token 0747 ? computeToken(m_secret, m_tokenSecret, *algorithm, m_counter + 1ULL) 0748 : std::nullopt; 0749 if (token && nextToken) { 0750 Q_EMIT otp(*token, *nextToken, m_counter + 1ULL); 0751 } else { 0752 qCDebug(logger) << "Failed to compute HOTP tokens"; 0753 } 0754 0755 Q_EMIT finished(); 0756 } 0757 0758 Dispatcher::Dispatcher(QThread *thread, QObject *parent) : 0759 QObject(parent), m_thread(thread), m_current(nullptr) 0760 { 0761 } 0762 0763 bool Dispatcher::empty(void) const 0764 { 0765 return m_pending.isEmpty(); 0766 } 0767 0768 void Dispatcher::queueAndProceed(AccountJob *job, const std::function<void(void)> &setup_callbacks) 0769 { 0770 if (job) { 0771 qCDebug(dispatcherLogger) << "Queuing job for dispatcher"; 0772 job->moveToThread(m_thread); 0773 setup_callbacks(); 0774 m_pending.append(job); 0775 dispatchNext(); 0776 } 0777 } 0778 0779 void Dispatcher::dispatchNext(void) 0780 { 0781 qCDebug(dispatcherLogger) << "Handling request to dispatch next job"; 0782 0783 if (!empty() && !m_current) { 0784 qCDebug(dispatcherLogger) << "Dispatching next job"; 0785 0786 m_current = m_pending.takeFirst(); 0787 QObject::connect(m_current, &AccountJob::finished, this, &Dispatcher::next); 0788 QObject::connect(this, &Dispatcher::dispatch, m_current, &AccountJob::run); 0789 Q_EMIT dispatch(); 0790 } 0791 } 0792 0793 void Dispatcher::next(void) 0794 { 0795 qCDebug(dispatcherLogger) << "Handling next continuation in dispatcher"; 0796 0797 QObject *from = sender(); 0798 AccountJob *job = from ? qobject_cast<AccountJob*>(from) : nullptr; 0799 if (job) { 0800 Q_ASSERT_X(job == m_current, Q_FUNC_INFO, "sender() should match 'current' job!"); 0801 QObject::disconnect(this, &Dispatcher::dispatch, job, &AccountJob::run); 0802 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks): False positives with QTimer::singleShot 0803 QTimer::singleShot(0, job, &AccountJob::deleteLater); 0804 m_current = nullptr; 0805 dispatchNext(); 0806 } 0807 } 0808 }