diff --git a/share/translations/keepassxc_de.ts b/share/translations/keepassxc_de.ts
index ec732774ca..85494989c4 100644
--- a/share/translations/keepassxc_de.ts
+++ b/share/translations/keepassxc_de.ts
@@ -10422,6 +10422,10 @@ Example: JBSWY3DPEHPK3PXP
Sie haben einen ungültigen geheimen Schlüssel angegeben. Der Schlüssel muss im Base32-Format sein.
Beispiel: JBSWY3DPEHPK3PXP
+
+ You have entered an invalid TOTP URI. The URI must start with otpauth://totp/
+ Sie haben eine ungültige Totp Uri eingegeben. Die Uri muss mit otpauth://totp/ beginnen.
+
Confirm Remove TOTP Settings
Löschen der TOTP-Einstellungen bestätigen
@@ -10671,4 +10675,4 @@ Beispiel: JBSWY3DPEHPK3PXP
Unbekannt
-
\ No newline at end of file
+
diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index f974db170b..4798d94de9 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -10440,6 +10440,10 @@ This option is deprecated, use --set-key-file instead.
Secret Key:
+
+ URI:
+
+
Secret key must be in Base32 format
@@ -10452,6 +10456,10 @@ This option is deprecated, use --set-key-file instead.
Default settings (RFC 6238)
+
+ Totp Uri
+
+
Steam® settings
@@ -10498,6 +10506,10 @@ This option is deprecated, use --set-key-file instead.
Example: JBSWY3DPEHPK3PXP
+
+ You have entered an invalid TOTP URI. The URI must start with otpauth://totp/
+
+
Confirm Remove TOTP Settings
diff --git a/share/translations/keepassxc_en_GB.ts b/share/translations/keepassxc_en_GB.ts
index 8c878dc04f..bfbb31f09b 100644
--- a/share/translations/keepassxc_en_GB.ts
+++ b/share/translations/keepassxc_en_GB.ts
@@ -10363,6 +10363,10 @@ This option is deprecated, use --set-key-file instead.
Secret Key:
Secret Key:
+
+ URI:
+ URI:
+
Secret key must be in Base32 format
Secret key must be in Base32 format
@@ -10375,6 +10379,10 @@ This option is deprecated, use --set-key-file instead.
Default settings (RFC 6238)
Default settings (RFC 6238)
+
+ Totp Uri
+ Totp Uri
+
Steam® settings
Steam® settings
@@ -10422,6 +10430,10 @@ Example: JBSWY3DPEHPK3PXP
You have entered an invalid secret key. The key must be in Base32 format.
Example: JBSWY3DPEHPK3PXP
+
+ You have entered an invalid TOTP URI. The URI must start with otpauth://totp/
+ You have entered an invalid Totp Uri. The Uri must start with otpauth://totp/
+
Confirm Remove TOTP Settings
Confirm Remove TOTP Settings
@@ -10671,4 +10683,4 @@ Example: JBSWY3DPEHPK3PXP
Unknown
-
\ No newline at end of file
+
diff --git a/share/translations/keepassxc_en_US.ts b/share/translations/keepassxc_en_US.ts
index 75fcf9e921..842ab36fcc 100644
--- a/share/translations/keepassxc_en_US.ts
+++ b/share/translations/keepassxc_en_US.ts
@@ -10363,6 +10363,10 @@ This option is deprecated, use --set-key-file instead.
Secret Key:
Secret Key:
+
+ URI:
+ URI:
+
Secret key must be in Base32 format
Secret key must be in Base32 format
@@ -10375,6 +10379,10 @@ This option is deprecated, use --set-key-file instead.
Default settings (RFC 6238)
Default settings (RFC 6238)
+
+ Totp Uri
+ Totp Uri
+
Steam® settings
Steam® settings
@@ -10422,6 +10430,10 @@ Example: JBSWY3DPEHPK3PXP
You have entered an invalid secret key. The key must be in Base32 format.
Example: JBSWY3DPEHPK3PXP
+
+ You have entered an invalid TOTP URI. The URI must start with otpauth://totp/
+ You have entered an invalid Totp Uri. The Uri must start with otpauth://totp/
+
Confirm Remove TOTP Settings
Confirm Remove TOTP Settings
@@ -10671,4 +10683,4 @@ Example: JBSWY3DPEHPK3PXP
Unknown
-
\ No newline at end of file
+
diff --git a/src/core/Totp.cpp b/src/core/Totp.cpp
index ed15a9fb86..7ff88c7bf0 100644
--- a/src/core/Totp.cpp
+++ b/src/core/Totp.cpp
@@ -34,7 +34,7 @@ static QList totpEncoders{
{"steam", Totp::STEAM_SHORTNAME, "23456789BCDFGHJKMNPQRTVWXY", Totp::STEAM_DIGITS, Totp::DEFAULT_STEP, true},
};
-static Totp::Algorithm getHashTypeByName(const QString& name)
+Totp::Algorithm Totp::getHashTypeByName(const QString& name)
{
auto nameUpper = name.toUpper();
if (nameUpper == "SHA512" || nameUpper == "HMAC-SHA-512") {
@@ -46,7 +46,7 @@ static Totp::Algorithm getHashTypeByName(const QString& name)
return Totp::Algorithm::Sha1;
}
-static QString getNameForHashType(const Totp::Algorithm hashType)
+QString Totp::getNameForHashType(const Totp::Algorithm hashType)
{
switch (hashType) {
case Totp::Algorithm::Sha512:
diff --git a/src/core/Totp.h b/src/core/Totp.h
index da857aef2b..d52c8461ad 100644
--- a/src/core/Totp.h
+++ b/src/core/Totp.h
@@ -98,6 +98,9 @@ namespace Totp
bool hasCustomSettings(const QSharedPointer& settings);
+ Totp::Algorithm getHashTypeByName(const QString& name);
+ QString getNameForHashType(const Totp::Algorithm hashType);
+
QList> supportedEncoders();
QList> supportedAlgorithms();
diff --git a/src/gui/TotpSetupDialog.cpp b/src/gui/TotpSetupDialog.cpp
index e7e0bd7498..34869dba9b 100644
--- a/src/gui/TotpSetupDialog.cpp
+++ b/src/gui/TotpSetupDialog.cpp
@@ -22,6 +22,8 @@
#include "core/Totp.h"
#include "gui/MessageBox.h"
+#include
+
TotpSetupDialog::TotpSetupDialog(QWidget* parent, Entry* entry)
: QDialog(parent)
, m_ui(new Ui::TotpSetupDialog())
@@ -35,45 +37,67 @@ TotpSetupDialog::TotpSetupDialog(QWidget* parent, Entry* entry)
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(saveSettings()));
connect(m_ui->radioCustom, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool)));
+ connect(m_ui->radioUri, SIGNAL(toggled(bool)), SLOT(toggleUri(bool)));
init();
}
TotpSetupDialog::~TotpSetupDialog() = default;
-void TotpSetupDialog::saveSettings()
+void TotpSetupDialog::init()
{
- // Secret key sanity check
- // Convert user input to all uppercase and remove '='
- auto key = m_ui->seedEdit->text().toUpper().remove(" ").remove("=").trimmed().toLatin1();
- auto sanitizedKey = Base32::sanitizeInput(key);
- // Use startsWith to ignore added '=' for padding at the end
- if (!sanitizedKey.startsWith(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;
+ // 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);
+ m_ui->invalidKeyLabel->setVisible(false);
- QString encShortName;
- uint digits = Totp::DEFAULT_DIGITS;
- uint step = Totp::DEFAULT_STEP;
- Totp::Algorithm algorithm = Totp::DEFAULT_ALGORITHM;
- Totp::StorageFormat format = Totp::DEFAULT_FORMAT;
+ // Read entry totp settings
+ auto settings = m_entry->totpSettings();
+ if (settings) {
+ auto key = settings->key;
+ m_ui->seedEdit->setText(key.remove("="));
+ m_ui->seedEdit->setCursorPosition(0);
+ m_ui->stepSpinBox->setValue(settings->step);
- if (m_ui->radioSteam->isChecked()) {
- digits = Totp::STEAM_DIGITS;
- encShortName = Totp::STEAM_SHORTNAME;
+ if (settings->encoder.shortName == Totp::STEAM_SHORTNAME) {
+ m_ui->radioSteam->setChecked(true);
+ } else if (Totp::hasCustomSettings(settings)) {
+ m_ui->radioCustom->setChecked(true);
+ m_ui->digitsSpinBox->setValue(settings->digits);
+ int index = m_ui->algorithmComboBox->findData(settings->algorithm);
+ if (index != -1) {
+ m_ui->algorithmComboBox->setCurrentIndex(index);
+ }
+ }
+
+ auto error = Totp::checkValidSettings(settings);
+ m_ui->invalidKeyLabel->setVisible(!error.isEmpty());
+ }
+}
+
+void TotpSetupDialog::saveSettings()
+{
+ QSharedPointer newSettings;
+ if (m_ui->radioDefault->isChecked()) {
+ newSettings = createFromRfc6238();
+ } else if (m_ui->radioUri->isChecked()) {
+ newSettings = createFromUri();
+ } else if (m_ui->radioSteam->isChecked()) {
+ newSettings = createFromSteam();
} else if (m_ui->radioCustom->isChecked()) {
- algorithm = static_cast(m_ui->algorithmComboBox->currentData().toInt());
- step = m_ui->stepSpinBox->value();
- digits = m_ui->digitsSpinBox->value();
+ newSettings = createFromCustom();
+ }
+
+ if (newSettings.isNull()) {
+ return;
}
auto settings = m_entry->totpSettings();
if (settings) {
- if (key.isEmpty()) {
+ if (newSettings->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?"),
@@ -82,15 +106,9 @@ void TotpSetupDialog::saveSettings()
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;
- }
}
- m_entry->setTotp(Totp::createSettings(key, digits, step, format, encShortName, algorithm));
+ m_entry->setTotp(newSettings);
emit totpUpdated();
close();
}
@@ -100,36 +118,159 @@ void TotpSetupDialog::toggleCustom(bool status)
m_ui->customSettingsGroup->setEnabled(status);
}
-void TotpSetupDialog::init()
+void TotpSetupDialog::toggleUri(bool status)
{
- // Add algorithm choices
- auto algorithms = Totp::supportedAlgorithms();
- for (const auto& item : algorithms) {
- m_ui->algorithmComboBox->addItem(item.first, item.second);
+ if (status) {
+ m_ui->labelSecretKey->setText(tr("URI:"));
+ } else {
+ m_ui->labelSecretKey->setText(tr("Secret Key:"));
}
- m_ui->algorithmComboBox->setCurrentIndex(0);
- m_ui->invalidKeyLabel->setVisible(false);
+}
+
+QSharedPointer TotpSetupDialog::createFromRfc6238()
+{
+ QString key = sanitizeSecretKey();
+ if (key == QStringLiteral("err")) {
+ 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 nullptr;
+ }
+
+ QString encShortName;
+ uint digits = Totp::DEFAULT_DIGITS;
+ uint step = Totp::DEFAULT_STEP;
+ Totp::Algorithm algorithm = Totp::DEFAULT_ALGORITHM;
+ Totp::StorageFormat format = Totp::DEFAULT_FORMAT;
- // Read entry totp settings
auto settings = m_entry->totpSettings();
if (settings) {
- auto key = settings->key;
- m_ui->seedEdit->setText(key.remove("="));
- m_ui->seedEdit->setCursorPosition(0);
- m_ui->stepSpinBox->setValue(settings->step);
+ format = settings->format;
+ }
- if (settings->encoder.shortName == Totp::STEAM_SHORTNAME) {
- m_ui->radioSteam->setChecked(true);
- } else if (Totp::hasCustomSettings(settings)) {
- m_ui->radioCustom->setChecked(true);
- m_ui->digitsSpinBox->setValue(settings->digits);
- int index = m_ui->algorithmComboBox->findData(settings->algorithm);
- if (index != -1) {
- m_ui->algorithmComboBox->setCurrentIndex(index);
- }
+ return Totp::createSettings(key, digits, step, format, encShortName, algorithm);
+}
+
+QSharedPointer TotpSetupDialog::createFromUri()
+{
+ auto uri = QUrl(m_ui->seedEdit->text());
+ if (!uri.isValid() || uri.scheme() != "otpauth") {
+ MessageBox::information(this,
+ tr("Invalid TOTP Secret"),
+ tr("You have entered an invalid TOTP URI. The URI must start with otpauth://totp/"));
+ return nullptr;
+ }
+
+ QString encShortName;
+ uint digits = Totp::DEFAULT_DIGITS;
+ uint step = Totp::DEFAULT_STEP;
+ Totp::Algorithm algorithm = Totp::DEFAULT_ALGORITHM;
+ Totp::StorageFormat format = Totp::DEFAULT_FORMAT;
+
+ QUrlQuery query(uri);
+
+ if (!query.hasQueryItem("secret")) {
+ MessageBox::information(this,
+ tr("Invalid TOTP Secret"),
+ tr("You have entered an invalid TOTP URI. The URI must start with otpauth://totp/"));
+ return nullptr;
+ }
+ QString key = sanitizeSecretKey(query.queryItemValue("secret"));
+ if (key == QStringLiteral("err")) {
+ MessageBox::information(this,
+ tr("Invalid TOTP Secret"),
+ tr("You have entered an invalid TOTP URI. The URI must start with otpauth://totp/"));
+ return nullptr;
+ }
+
+ if (query.hasQueryItem("digits")) {
+ digits = query.queryItemValue("digits").toUInt();
+ }
+ if (query.hasQueryItem("period")) {
+ step = query.queryItemValue("period").toUInt();
+ }
+ if (query.hasQueryItem("algorithm")) {
+ algorithm = Totp::getHashTypeByName(query.queryItemValue("algorithm"));
+ }
+
+ auto settings = m_entry->totpSettings();
+ if (settings) {
+ format = settings->format;
+ }
+
+ return Totp::createSettings(key, digits, step, format, encShortName, algorithm);
+}
+
+QSharedPointer TotpSetupDialog::createFromSteam()
+{
+ QString key = sanitizeSecretKey();
+ if (key == QStringLiteral("err")) {
+ 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 nullptr;
+ }
+
+ QString encShortName = Totp::STEAM_SHORTNAME;
+ uint digits = Totp::STEAM_DIGITS;
+ uint step = Totp::DEFAULT_STEP;
+ Totp::Algorithm algorithm = Totp::DEFAULT_ALGORITHM;
+ Totp::StorageFormat format = Totp::DEFAULT_FORMAT;
+
+ auto settings = m_entry->totpSettings();
+ if (settings) {
+ format = settings->format;
+ }
+
+ return Totp::createSettings(key, digits, step, format, encShortName, algorithm);
+}
+
+QSharedPointer TotpSetupDialog::createFromCustom()
+{
+ QString key = sanitizeSecretKey();
+ if (key == QStringLiteral("err")) {
+ 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 nullptr;
+ }
+
+ QString encShortName;
+ uint digits = m_ui->digitsSpinBox->value();
+ uint step = m_ui->stepSpinBox->value();
+ Totp::Algorithm algorithm = static_cast(m_ui->algorithmComboBox->currentData().toInt());
+ Totp::StorageFormat format = Totp::DEFAULT_FORMAT;
+
+ auto settings = m_entry->totpSettings();
+ if (settings) {
+ format = settings->format;
+ if (format == Totp::StorageFormat::LEGACY) {
+ // Implicitly upgrade to the OTPURL format to allow for custom settings
+ format = Totp::DEFAULT_FORMAT;
}
+ }
- auto error = Totp::checkValidSettings(settings);
- m_ui->invalidKeyLabel->setVisible(!error.isEmpty());
+ return Totp::createSettings(key, digits, step, format, encShortName, algorithm);
+}
+
+QString TotpSetupDialog::sanitizeSecretKey()
+{
+ return sanitizeSecretKey(m_ui->seedEdit->text());
+}
+
+QString TotpSetupDialog::sanitizeSecretKey(const QString& key)
+{
+ // Secret key sanity check
+ // Convert user input to all uppercase and remove '='
+ auto keyCleaned = key.toUpper().remove(" ").remove("=").trimmed();
+ auto keyBytes = keyCleaned.toLatin1();
+ auto sanitizedKey = Base32::sanitizeInput(keyBytes);
+ // Use startsWith to ignore added '=' for padding at the end
+ if (!sanitizedKey.startsWith(keyBytes)) {
+ return QStringLiteral("err");
}
+ return sanitizedKey;
}
diff --git a/src/gui/TotpSetupDialog.h b/src/gui/TotpSetupDialog.h
index 8b88cb8e0e..857d10a7ec 100644
--- a/src/gui/TotpSetupDialog.h
+++ b/src/gui/TotpSetupDialog.h
@@ -43,11 +43,19 @@ class TotpSetupDialog : public QDialog
private slots:
void toggleCustom(bool status);
+ void toggleUri(bool status);
void saveSettings();
private:
QScopedPointer m_ui;
Entry* m_entry;
+
+ QSharedPointer createFromRfc6238();
+ QSharedPointer createFromUri();
+ QSharedPointer createFromSteam();
+ QSharedPointer createFromCustom();
+ QString sanitizeSecretKey();
+ QString sanitizeSecretKey(const QString& key);
};
#endif // KEEPASSX_SETUPTOTPDIALOG_H
diff --git a/src/gui/TotpSetupDialog.ui b/src/gui/TotpSetupDialog.ui
index f8c95f4c45..c1336c207b 100644
--- a/src/gui/TotpSetupDialog.ui
+++ b/src/gui/TotpSetupDialog.ui
@@ -7,7 +7,7 @@
0
0
249
- 278
+ 307
@@ -18,7 +18,6 @@
- 75
true
@@ -26,7 +25,7 @@
Error: secret key is invalid
- Qt::AlignCenter
+ Qt::AlignmentFlag::AlignCenter
@@ -39,7 +38,7 @@
5
-
-
+
Secret Key:
@@ -97,6 +96,16 @@
+ -
+
+
+ Totp Uri
+
+
+ settingsButtonGroup
+
+
+
-
@@ -130,13 +139,13 @@
- QFormLayout::ExpandingFieldsGrow
+ QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow
- QFormLayout::DontWrapRows
+ QFormLayout::RowWrapPolicy::DontWrapRows
- Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTop|Qt::AlignmentFlag::AlignTrailing
7
@@ -215,10 +224,10 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+ QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok