From 6baa775efd59af9a8ec0fda46ae4b8a2feb9b064 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Mon, 14 Oct 2019 09:25:45 -0400 Subject: [PATCH] Correct issues with TOTP Setup * Fix #3142 - Warn user when entering invalid TOTP secret key. * Fix #773 - The TOTP dialog now listens for the copy shortcut without having to press the Copy button. * Add ability to choose hash algorithm from the TOTP setup dialog * Add upgrade to "otp" attribute when custom attributes are chosen to prevent data loss Ran make format --- src/core/Entry.cpp | 10 +-- src/gui/TotpDialog.cpp | 10 ++- src/gui/TotpExportSettingsDialog.cpp | 16 +++-- src/gui/TotpSetupDialog.cpp | 65 +++++++++++++---- src/gui/TotpSetupDialog.ui | 79 ++++++++++++-------- src/gui/entry/EditEntryWidget.cpp | 5 +- src/totp/totp.cpp | 103 +++++++++++++++------------ src/totp/totp.h | 25 ++++--- tests/TestTotp.cpp | 18 +++-- 9 files changed, 210 insertions(+), 121 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 677c7b3872..1b05b9e6e5 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -442,16 +442,16 @@ QString Entry::totp() const void Entry::setTotp(QSharedPointer settings) { beginUpdate(); + m_attributes->remove(Totp::ATTRIBUTE_OTP); + m_attributes->remove(Totp::ATTRIBUTE_SEED); + m_attributes->remove(Totp::ATTRIBUTE_SETTINGS); + if (settings->key.isEmpty()) { m_data.totpSettings.reset(); - m_attributes->remove(Totp::ATTRIBUTE_OTP); - m_attributes->remove(Totp::ATTRIBUTE_SEED); - m_attributes->remove(Totp::ATTRIBUTE_SETTINGS); } else { m_data.totpSettings = std::move(settings); - auto text = Totp::writeSettings(m_data.totpSettings, title(), username()); - if (m_attributes->hasKey(Totp::ATTRIBUTE_OTP)) { + if (m_data.totpSettings->format != Totp::StorageFormat::LEGACY) { m_attributes->set(Totp::ATTRIBUTE_OTP, text, true); } else { m_attributes->set(Totp::ATTRIBUTE_SEED, m_data.totpSettings->key, true); diff --git a/src/gui/TotpDialog.cpp b/src/gui/TotpDialog.cpp index 8cc84eaa8c..3f438e9ae3 100644 --- a/src/gui/TotpDialog.cpp +++ b/src/gui/TotpDialog.cpp @@ -22,6 +22,9 @@ #include "core/Clock.h" #include "core/Config.h" #include "gui/Clipboard.h" +#include "gui/MainWindow.h" + +#include TotpDialog::TotpDialog(QWidget* parent, Entry* entry) : QDialog(parent) @@ -39,6 +42,7 @@ TotpDialog::TotpDialog(QWidget* parent, Entry* entry) resetCounter(); updateProgressBar(); + connect(parent, SIGNAL(lockedDatabase()), SLOT(close())); connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar())); connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds())); m_totpUpdateTimer.start(m_step * 10); @@ -46,6 +50,8 @@ TotpDialog::TotpDialog(QWidget* parent, Entry* entry) setAttribute(Qt::WA_DeleteOnClose); + new QShortcut(QKeySequence(QKeySequence::Copy), this, SLOT(copyToClipboard())); + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Copy")); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close())); @@ -61,9 +67,9 @@ void TotpDialog::copyToClipboard() clipboard()->setText(m_entry->totp()); if (config()->get("HideWindowOnCopy").toBool()) { if (config()->get("MinimizeOnCopy").toBool()) { - qobject_cast(parent())->window()->showMinimized(); + getMainWindow()->showMinimized(); } else if (config()->get("DropToBackgroundOnCopy").toBool()) { - qobject_cast(parent())->window()->lower(); + getMainWindow()->lower(); window()->lower(); } } diff --git a/src/gui/TotpExportSettingsDialog.cpp b/src/gui/TotpExportSettingsDialog.cpp index 956cca5324..178cd6d968 100644 --- a/src/gui/TotpExportSettingsDialog.cpp +++ b/src/gui/TotpExportSettingsDialog.cpp @@ -16,10 +16,12 @@ */ #include "TotpExportSettingsDialog.h" + #include "core/Config.h" #include "core/Entry.h" #include "gui/Clipboard.h" #include "gui/DatabaseWidget.h" +#include "gui/MainWindow.h" #include "gui/SquareSvgWidget.h" #include "qrcode/QrCode.h" #include "totp/totp.h" @@ -29,6 +31,7 @@ #include #include #include +#include #include #include @@ -55,10 +58,13 @@ TotpExportSettingsDialog::TotpExportSettingsDialog(DatabaseWidget* parent, Entry connect(m_buttonBox, SIGNAL(rejected()), SLOT(close())); connect(m_buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard())); - connect(m_timer, SIGNAL(timeout()), this, SLOT(autoClose())); - connect(parent, SIGNAL(lockedDatabase()), this, SLOT(close())); + connect(m_timer, SIGNAL(timeout()), SLOT(autoClose())); + connect(parent, SIGNAL(lockedDatabase()), SLOT(close())); + + new QShortcut(QKeySequence(QKeySequence::Copy), this, SLOT(copyToClipboard())); m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Copy")); + m_buttonBox->setFocus(); m_countDown->setAlignment(Qt::AlignCenter); m_secTillClose = 45; @@ -92,8 +98,6 @@ TotpExportSettingsDialog::TotpExportSettingsDialog(DatabaseWidget* parent, Entry errorBox->exec(); close(); } - - show(); } void TotpExportSettingsDialog::copyToClipboard() @@ -101,9 +105,9 @@ void TotpExportSettingsDialog::copyToClipboard() clipboard()->setText(m_totpUri); if (config()->get("HideWindowOnCopy").toBool()) { if (config()->get("MinimizeOnCopy").toBool()) { - static_cast(parent())->window()->showMinimized(); + getMainWindow()->showMinimized(); } else if (config()->get("DropToBackgroundOnCopy").toBool()) { - static_cast(parent())->window()->lower(); + getMainWindow()->lower(); window()->lower(); } } diff --git a/src/gui/TotpSetupDialog.cpp b/src/gui/TotpSetupDialog.cpp index 4710824e08..8acf7d1154 100644 --- a/src/gui/TotpSetupDialog.cpp +++ b/src/gui/TotpSetupDialog.cpp @@ -19,6 +19,8 @@ #include "TotpSetupDialog.h" #include "ui_TotpSetupDialog.h" +#include "core/Base32.h" +#include "gui/MessageBox.h" #include "totp/totp.h" TotpSetupDialog::TotpSetupDialog(QWidget* parent, Entry* entry) @@ -43,38 +45,73 @@ TotpSetupDialog::~TotpSetupDialog() void TotpSetupDialog::saveSettings() { + // Secret key sanity check + auto key = m_ui->seedEdit->text().toLatin1(); + auto sanitizedKey = Base32::sanitizeInput(key); + if (sanitizedKey != key) { + MessageBox::information(this, + tr("Invalid TOTP Secret"), + tr("You have entered an invalid secret key. The key must be in Base32 format.\n" + "Example: JBSWY3DPEHPK3PXP")); + return; + } + QString encShortName; uint digits = Totp::DEFAULT_DIGITS; uint step = Totp::DEFAULT_STEP; + Totp::Algorithm algorithm = Totp::DEFAULT_ALGORITHM; + Totp::StorageFormat format = Totp::DEFAULT_FORMAT; if (m_ui->radioSteam->isChecked()) { digits = Totp::STEAM_DIGITS; encShortName = Totp::STEAM_SHORTNAME; } else if (m_ui->radioCustom->isChecked()) { + algorithm = static_cast(m_ui->algorithmComboBox->currentData().toInt()); step = m_ui->stepSpinBox->value(); - if (m_ui->radio8Digits->isChecked()) { - digits = 8; - } else if (m_ui->radio7Digits->isChecked()) { - digits = 7; + digits = m_ui->digitsSpinBox->value(); + } + + auto settings = m_entry->totpSettings(); + if (settings) { + if (key.isEmpty()) { + auto answer = MessageBox::question(this, + tr("Confirm Remove TOTP Settings"), + tr("Are you sure you want to delete TOTP settings for this entry?"), + MessageBox::Delete | MessageBox::Cancel); + if (answer != MessageBox::Delete) { + return; + } + } + + format = settings->format; + if (format == Totp::StorageFormat::LEGACY && m_ui->radioCustom->isChecked()) { + // Implicitly upgrade to the OTPURL format to allow for custom settings + format = Totp::DEFAULT_FORMAT; } } - auto settings = Totp::createSettings( - m_ui->seedEdit->text(), digits, step, encShortName, Totp::HashType::Sha1, m_entry->totpSettings()); - m_entry->setTotp(settings); + m_entry->setTotp(Totp::createSettings(key, digits, step, format, encShortName, algorithm)); emit totpUpdated(); close(); } void TotpSetupDialog::toggleCustom(bool status) { - m_ui->customGroup->setEnabled(status); + m_ui->customSettingsGroup->setEnabled(status); } void TotpSetupDialog::init() { + // Add algorithm choices + auto algorithms = Totp::supportedAlgorithms(); + for (const auto& item : algorithms) { + m_ui->algorithmComboBox->addItem(item.first, item.second); + } + m_ui->algorithmComboBox->setCurrentIndex(0); + + // Read entry totp settings auto settings = m_entry->totpSettings(); - if (!settings.isNull()) { + if (settings) { m_ui->seedEdit->setText(settings->key); m_ui->stepSpinBox->setValue(settings->step); @@ -82,12 +119,10 @@ void TotpSetupDialog::init() m_ui->radioSteam->setChecked(true); } else if (settings->custom) { m_ui->radioCustom->setChecked(true); - if (settings->digits == 8) { - m_ui->radio8Digits->setChecked(true); - } else if (settings->digits == 7) { - m_ui->radio7Digits->setChecked(true); - } else { - m_ui->radio6Digits->setChecked(true); + m_ui->digitsSpinBox->setValue(settings->digits); + int index = m_ui->algorithmComboBox->findData(settings->algorithm); + if (index != -1) { + m_ui->algorithmComboBox->setCurrentIndex(index); } } } diff --git a/src/gui/TotpSetupDialog.ui b/src/gui/TotpSetupDialog.ui index a12da0fa49..84ba4cb545 100644 --- a/src/gui/TotpSetupDialog.ui +++ b/src/gui/TotpSetupDialog.ui @@ -25,14 +25,26 @@ - Key: + Secret Key: + + + 0 + 0 + + + + + 150 + 0 + + - Secret key in Base32 format + Secret key must be in Base32 format Secret key field @@ -93,13 +105,10 @@ - + false - - - Custom Settings @@ -113,20 +122,36 @@ Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + 7 + + + 7 + - 5 + 20 - 5 + 20 + + + Algorithm: + + + + + + + Time step: - + Time step field @@ -145,34 +170,26 @@ - + Code size: - - - - 6 digits + + + + digits - - true + + 6 - - - - - - 7 digits + + 10 - - - - - - 8 digits + + 1 @@ -190,6 +207,9 @@ + customSettingsGroup + buttonBox + groupBox seedEdit @@ -197,9 +217,6 @@ radioSteam radioCustom stepSpinBox - radio6Digits - radio7Digits - radio8Digits diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 3345848afe..8567255173 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -962,8 +962,9 @@ void EditEntryWidget::setForms(Entry* entry, bool restore) #ifdef WITH_XC_BROWSER if (m_customData->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) { - m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) - == "true"); + // clang-format off + m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == "true"); + // clang-format on } else { m_browserUi->skipAutoSubmitCheckbox->setChecked(false); } diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp index 5b37af36e7..c6bad01563 100644 --- a/src/totp/totp.cpp +++ b/src/totp/totp.cpp @@ -23,35 +23,34 @@ #include #include #include -#include #include #include #include #include #include -static QList encoders{ +static QList totpEncoders{ {"", "", "0123456789", Totp::DEFAULT_DIGITS, Totp::DEFAULT_STEP, false}, {"steam", Totp::STEAM_SHORTNAME, "23456789BCDFGHJKMNPQRTVWXY", Totp::STEAM_DIGITS, Totp::DEFAULT_STEP, true}, }; -static Totp::HashType getHashTypeByName(const QString& name) +static Totp::Algorithm getHashTypeByName(const QString& name) { if (name.compare(QString("SHA512"), Qt::CaseInsensitive) == 0) { - return Totp::HashType::Sha512; + return Totp::Algorithm::Sha512; } if (name.compare(QString("SHA256"), Qt::CaseInsensitive) == 0) { - return Totp::HashType::Sha256; + return Totp::Algorithm::Sha256; } - return Totp::HashType::Sha1; + return Totp::Algorithm::Sha1; } -static QString getNameForHashType(const Totp::HashType hashType) +static QString getNameForHashType(const Totp::Algorithm hashType) { switch (hashType) { - case Totp::HashType::Sha512: + case Totp::Algorithm::Sha512: return QString("SHA512"); - case Totp::HashType::Sha256: + case Totp::Algorithm::Sha256: return QString("SHA256"); default: return QString("SHA1"); @@ -67,24 +66,26 @@ QSharedPointer Totp::parseSettings(const QString& rawSettings, c if (url.isValid() && url.scheme() == "otpauth") { // Default OTP url format QUrlQuery query(url); - settings->otpUrl = true; + settings->format = StorageFormat::OTPURL; settings->key = query.queryItemValue("secret"); - settings->digits = query.queryItemValue("digits").toUInt(); - settings->step = query.queryItemValue("period").toUInt(); + if (query.hasQueryItem("digits")) { + settings->digits = query.queryItemValue("digits").toUInt(); + } + if (query.hasQueryItem("period")) { + settings->step = query.queryItemValue("period").toUInt(); + } if (query.hasQueryItem("encoder")) { settings->encoder = getEncoderByName(query.queryItemValue("encoder")); } if (query.hasQueryItem("algorithm")) { - settings->hashType = getHashTypeByName(query.queryItemValue("algorithm")); + settings->algorithm = getHashTypeByName(query.queryItemValue("algorithm")); } } else { QUrlQuery query(rawSettings); if (query.hasQueryItem("key")) { // Compatibility with "KeeOtp" plugin - settings->keeOtp = true; + settings->format = StorageFormat::KEEOTP; settings->key = query.queryItemValue("key"); - settings->digits = DEFAULT_DIGITS; - settings->step = DEFAULT_STEP; if (query.hasQueryItem("size")) { settings->digits = query.queryItemValue("size").toUInt(); } @@ -92,10 +93,11 @@ QSharedPointer Totp::parseSettings(const QString& rawSettings, c settings->step = query.queryItemValue("step").toUInt(); } if (query.hasQueryItem("otpHashMode")) { - settings->hashType = getHashTypeByName(query.queryItemValue("otpHashMode")); + settings->algorithm = getHashTypeByName(query.queryItemValue("otpHashMode")); } } else { // Parse semi-colon separated values ([step];[digits|S]) + settings->format = StorageFormat::LEGACY; auto vars = rawSettings.split(";"); if (vars.size() >= 2) { if (vars[1] == STEAM_SHORTNAME) { @@ -116,7 +118,8 @@ QSharedPointer Totp::parseSettings(const QString& rawSettings, c // Detect custom settings, used by setup GUI if (settings->encoder.shortName.isEmpty() - && (settings->digits != DEFAULT_DIGITS || settings->step != DEFAULT_STEP)) { + && (settings->digits != DEFAULT_DIGITS || settings->step != DEFAULT_STEP + || settings->algorithm != DEFAULT_ALGORITHM)) { settings->custom = true; } @@ -126,23 +129,13 @@ QSharedPointer Totp::parseSettings(const QString& rawSettings, c QSharedPointer Totp::createSettings(const QString& key, const uint digits, const uint step, + const Totp::StorageFormat format, const QString& encoderShortName, - const Totp::HashType hashType, - QSharedPointer prevSettings) + const Totp::Algorithm algorithm) { - bool isCustom = digits != DEFAULT_DIGITS || step != DEFAULT_STEP; - if (prevSettings) { - prevSettings->key = key; - prevSettings->hashType = hashType; - prevSettings->digits = digits; - prevSettings->step = step; - prevSettings->encoder = Totp::getEncoderByShortName(encoderShortName); - prevSettings->custom = isCustom; - return prevSettings; - } else { - return QSharedPointer(new Totp::Settings{ - getEncoderByShortName(encoderShortName), hashType, key, false, false, isCustom, digits, step}); - } + bool isCustom = digits != DEFAULT_DIGITS || step != DEFAULT_STEP || algorithm != DEFAULT_ALGORITHM; + return QSharedPointer( + new Totp::Settings{format, getEncoderByShortName(encoderShortName), algorithm, key, isCustom, digits, step}); } QString Totp::writeSettings(const QSharedPointer& settings, @@ -155,7 +148,7 @@ QString Totp::writeSettings(const QSharedPointer& settings, } // OTP Url output - if (settings->otpUrl || forceOtp) { + if (settings->format == StorageFormat::OTPURL || forceOtp) { auto urlstring = QString("otpauth://totp/%1:%2?secret=%3&period=%4&digits=%5&issuer=%1") .arg(title.isEmpty() ? "KeePassXC" : QString(QUrl::toPercentEncoding(title)), username.isEmpty() ? "none" : QString(QUrl::toPercentEncoding(username)), @@ -166,18 +159,18 @@ QString Totp::writeSettings(const QSharedPointer& settings, if (!settings->encoder.name.isEmpty()) { urlstring.append("&encoder=").append(settings->encoder.name); } - if (settings->hashType != Totp::DEFAULT_HASHTYPE) { - urlstring.append("&algorithm=").append(getNameForHashType(settings->hashType)); + if (settings->algorithm != Totp::DEFAULT_ALGORITHM) { + urlstring.append("&algorithm=").append(getNameForHashType(settings->algorithm)); } return urlstring; - } else if (settings->keeOtp) { + } else if (settings->format == StorageFormat::KEEOTP) { // KeeOtp output auto keyString = QString("key=%1&size=%2&step=%3") .arg(QString(Base32::sanitizeInput(settings->key.toLatin1()))) .arg(settings->digits) .arg(settings->step); - if (settings->hashType != Totp::DEFAULT_HASHTYPE) { - keyString.append("&otpHashMode=").append(getNameForHashType(settings->hashType)); + if (settings->algorithm != Totp::DEFAULT_ALGORITHM) { + keyString.append("&otpHashMode=").append(getNameForHashType(settings->algorithm)); } return keyString; } else if (!settings->encoder.shortName.isEmpty()) { @@ -213,11 +206,11 @@ QString Totp::generateTotp(const QSharedPointer& settings, const } QCryptographicHash::Algorithm cryptoHash; - switch (settings->hashType) { - case Totp::HashType::Sha512: + switch (settings->algorithm) { + case Totp::Algorithm::Sha512: cryptoHash = QCryptographicHash::Sha512; break; - case Totp::HashType::Sha256: + case Totp::Algorithm::Sha256: cryptoHash = QCryptographicHash::Sha256; break; default: @@ -256,11 +249,29 @@ QString Totp::generateTotp(const QSharedPointer& settings, const return retval; } +QList> Totp::supportedEncoders() +{ + QList> encoders; + for (auto& encoder : totpEncoders) { + encoders << QPair(encoder.name, encoder.shortName); + } + return encoders; +} + +QList> Totp::supportedAlgorithms() +{ + QList> algorithms; + algorithms << QPair(QStringLiteral("SHA-1"), Algorithm::Sha1); + algorithms << QPair(QStringLiteral("SHA-256"), Algorithm::Sha256); + algorithms << QPair(QStringLiteral("SHA-512"), Algorithm::Sha512); + return algorithms; +} + Totp::Encoder& Totp::defaultEncoder() { // The first encoder is always the default - Q_ASSERT(!encoders.empty()); - return encoders[0]; + Q_ASSERT(!totpEncoders.empty()); + return totpEncoders[0]; } Totp::Encoder& Totp::steamEncoder() @@ -270,7 +281,7 @@ Totp::Encoder& Totp::steamEncoder() Totp::Encoder& Totp::getEncoderByShortName(const QString& shortName) { - for (auto& encoder : encoders) { + for (auto& encoder : totpEncoders) { if (encoder.shortName == shortName) { return encoder; } @@ -280,7 +291,7 @@ Totp::Encoder& Totp::getEncoderByShortName(const QString& shortName) Totp::Encoder& Totp::getEncoderByName(const QString& name) { - for (auto& encoder : encoders) { + for (auto& encoder : totpEncoders) { if (encoder.name == name) { return encoder; } diff --git a/src/totp/totp.h b/src/totp/totp.h index ccece84fe4..e21f555711 100644 --- a/src/totp/totp.h +++ b/src/totp/totp.h @@ -28,7 +28,6 @@ class QUrl; namespace Totp { - struct Encoder { QString name; @@ -39,20 +38,26 @@ namespace Totp bool reverse; }; - enum HashType + enum Algorithm { Sha1, Sha256, Sha512, }; + enum StorageFormat + { + OTPURL, + KEEOTP, + LEGACY, + }; + struct Settings { + Totp::StorageFormat format; Totp::Encoder encoder; - Totp::HashType hashType; + Totp::Algorithm algorithm; QString key; - bool otpUrl; - bool keeOtp; bool custom; uint digits; uint step; @@ -61,7 +66,8 @@ namespace Totp constexpr uint DEFAULT_STEP = 30u; constexpr uint DEFAULT_DIGITS = 6u; constexpr uint STEAM_DIGITS = 5u; - constexpr Totp::HashType DEFAULT_HASHTYPE = Sha1; + constexpr Totp::Algorithm DEFAULT_ALGORITHM = Sha1; + constexpr Totp::StorageFormat DEFAULT_FORMAT = OTPURL; static const QString STEAM_SHORTNAME = "S"; static const QString ATTRIBUTE_OTP = "otp"; @@ -72,9 +78,9 @@ namespace Totp QSharedPointer createSettings(const QString& key, const uint digits, const uint step, + const Totp::StorageFormat format = DEFAULT_FORMAT, const QString& encoderShortName = {}, - const Totp::HashType hashType = DEFAULT_HASHTYPE, - QSharedPointer prevSettings = {}); + const Totp::Algorithm algorithm = DEFAULT_ALGORITHM); QString writeSettings(const QSharedPointer& settings, const QString& title = {}, const QString& username = {}, @@ -82,6 +88,9 @@ namespace Totp QString generateTotp(const QSharedPointer& settings, const quint64 time = 0ull); + QList> supportedEncoders(); + QList> supportedAlgorithms(); + Encoder& defaultEncoder(); Encoder& steamEncoder(); Encoder& getEncoderByShortName(const QString& shortName); diff --git a/tests/TestTotp.cpp b/tests/TestTotp.cpp index c44cd4e8cf..5eb9f6e536 100644 --- a/tests/TestTotp.cpp +++ b/tests/TestTotp.cpp @@ -39,9 +39,10 @@ void TestTotp::testParseSecret() QVERIFY(!settings.isNull()); QCOMPARE(settings->key, QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ")); QCOMPARE(settings->custom, false); + QCOMPARE(settings->format, Totp::StorageFormat::OTPURL); QCOMPARE(settings->digits, 6u); QCOMPARE(settings->step, 30u); - QCOMPARE(settings->hashType, Totp::HashType::Sha1); + QCOMPARE(settings->algorithm, Totp::Algorithm::Sha1); // OTP URL with non-default hash type secret = "otpauth://totp/" @@ -50,10 +51,11 @@ void TestTotp::testParseSecret() settings = Totp::parseSettings(secret); QVERIFY(!settings.isNull()); QCOMPARE(settings->key, QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ")); - QCOMPARE(settings->custom, false); + QCOMPARE(settings->custom, true); + QCOMPARE(settings->format, Totp::StorageFormat::OTPURL); QCOMPARE(settings->digits, 6u); QCOMPARE(settings->step, 30u); - QCOMPARE(settings->hashType, Totp::HashType::Sha512); + QCOMPARE(settings->algorithm, Totp::Algorithm::Sha512); // KeeOTP Parsing secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8&otpHashMode=Sha256"; @@ -61,9 +63,10 @@ void TestTotp::testParseSecret() QVERIFY(!settings.isNull()); QCOMPARE(settings->key, QString("HXDMVJECJJWSRBY=")); QCOMPARE(settings->custom, true); + QCOMPARE(settings->format, Totp::StorageFormat::KEEOTP); QCOMPARE(settings->digits, 8u); QCOMPARE(settings->step, 25u); - QCOMPARE(settings->hashType, Totp::HashType::Sha256); + QCOMPARE(settings->algorithm, Totp::Algorithm::Sha256); // Semi-colon delineated "TOTP Settings" secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; @@ -71,9 +74,10 @@ void TestTotp::testParseSecret() QVERIFY(!settings.isNull()); QCOMPARE(settings->key, QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq")); QCOMPARE(settings->custom, true); + QCOMPARE(settings->format, Totp::StorageFormat::LEGACY); QCOMPARE(settings->digits, 8u); QCOMPARE(settings->step, 30u); - QCOMPARE(settings->hashType, Totp::HashType::Sha1); + QCOMPARE(settings->algorithm, Totp::Algorithm::Sha1); // Bare secret (no "TOTP Settings" attribute) secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; @@ -81,9 +85,10 @@ void TestTotp::testParseSecret() QVERIFY(!settings.isNull()); QCOMPARE(settings->key, QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq")); QCOMPARE(settings->custom, false); + QCOMPARE(settings->format, Totp::StorageFormat::LEGACY); QCOMPARE(settings->digits, 6u); QCOMPARE(settings->step, 30u); - QCOMPARE(settings->hashType, Totp::HashType::Sha1); + QCOMPARE(settings->algorithm, Totp::Algorithm::Sha1); } void TestTotp::testTotpCode() @@ -119,6 +124,7 @@ void TestTotp::testSteamTotp() QCOMPARE(settings->key, QString("63BEDWCQZKTQWPESARIERL5DTTQFCJTK")); QCOMPARE(settings->encoder.shortName, Totp::STEAM_SHORTNAME); + QCOMPARE(settings->format, Totp::StorageFormat::OTPURL); QCOMPARE(settings->digits, Totp::STEAM_DIGITS); QCOMPARE(settings->step, 30u);