This commit is contained in:
Max.-F. Helm 2026-03-09 18:59:34 +01:00 committed by GitHub
commit d84b5ad69c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 274 additions and 73 deletions

View file

@ -10422,6 +10422,10 @@ Example: JBSWY3DPEHPK3PXP</source>
<translation>Sie haben einen ungültigen geheimen Schlüssel angegeben. Der Schlüssel muss im Base32-Format sein.
Beispiel: JBSWY3DPEHPK3PXP</translation>
</message>
<message>
<source>You have entered an invalid TOTP URI. The URI must start with otpauth://totp/</source>
<translation>Sie haben eine ungültige Totp Uri eingegeben. Die Uri muss mit otpauth://totp/ beginnen.</translation>
</message>
<message>
<source>Confirm Remove TOTP Settings</source>
<translation>Löschen der TOTP-Einstellungen bestätigen</translation>
@ -10671,4 +10675,4 @@ Beispiel: JBSWY3DPEHPK3PXP</translation>
<translation>Unbekannt</translation>
</message>
</context>
</TS>
</TS>

View file

@ -10440,6 +10440,10 @@ This option is deprecated, use --set-key-file instead.</source>
<source>Secret Key:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>URI:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Secret key must be in Base32 format</source>
<translation type="unfinished"></translation>
@ -10452,6 +10456,10 @@ This option is deprecated, use --set-key-file instead.</source>
<source>Default settings (RFC 6238)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Totp Uri</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steam® settings</source>
<translation type="unfinished"></translation>
@ -10498,6 +10506,10 @@ This option is deprecated, use --set-key-file instead.</source>
Example: JBSWY3DPEHPK3PXP</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>You have entered an invalid TOTP URI. The URI must start with otpauth://totp/</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Confirm Remove TOTP Settings</source>
<translation type="unfinished"></translation>

View file

@ -10363,6 +10363,10 @@ This option is deprecated, use --set-key-file instead.</translation>
<source>Secret Key:</source>
<translation>Secret Key:</translation>
</message>
<message>
<source>URI:</source>
<translation>URI:</translation>
</message>
<message>
<source>Secret key must be in Base32 format</source>
<translation>Secret key must be in Base32 format</translation>
@ -10375,6 +10379,10 @@ This option is deprecated, use --set-key-file instead.</translation>
<source>Default settings (RFC 6238)</source>
<translation>Default settings (RFC 6238)</translation>
</message>
<message>
<source>Totp Uri</source>
<translation>Totp Uri</translation>
</message>
<message>
<source>Steam® settings</source>
<translation>Steam® settings</translation>
@ -10422,6 +10430,10 @@ Example: JBSWY3DPEHPK3PXP</source>
<translation>You have entered an invalid secret key. The key must be in Base32 format.
Example: JBSWY3DPEHPK3PXP</translation>
</message>
<message>
<source>You have entered an invalid TOTP URI. The URI must start with otpauth://totp/</source>
<translation>You have entered an invalid Totp Uri. The Uri must start with otpauth://totp/</translation>
</message>
<message>
<source>Confirm Remove TOTP Settings</source>
<translation>Confirm Remove TOTP Settings</translation>
@ -10671,4 +10683,4 @@ Example: JBSWY3DPEHPK3PXP</translation>
<translation>Unknown</translation>
</message>
</context>
</TS>
</TS>

View file

@ -10363,6 +10363,10 @@ This option is deprecated, use --set-key-file instead.</translation>
<source>Secret Key:</source>
<translation>Secret Key:</translation>
</message>
<message>
<source>URI:</source>
<translation>URI:</translation>
</message>
<message>
<source>Secret key must be in Base32 format</source>
<translation>Secret key must be in Base32 format</translation>
@ -10375,6 +10379,10 @@ This option is deprecated, use --set-key-file instead.</translation>
<source>Default settings (RFC 6238)</source>
<translation>Default settings (RFC 6238)</translation>
</message>
<message>
<source>Totp Uri</source>
<translation>Totp Uri</translation>
</message>
<message>
<source>Steam® settings</source>
<translation>Steam® settings</translation>
@ -10422,6 +10430,10 @@ Example: JBSWY3DPEHPK3PXP</source>
<translation>You have entered an invalid secret key. The key must be in Base32 format.
Example: JBSWY3DPEHPK3PXP</translation>
</message>
<message>
<source>You have entered an invalid TOTP URI. The URI must start with otpauth://totp/</source>
<translation>You have entered an invalid Totp Uri. The Uri must start with otpauth://totp/</translation>
</message>
<message>
<source>Confirm Remove TOTP Settings</source>
<translation>Confirm Remove TOTP Settings</translation>
@ -10671,4 +10683,4 @@ Example: JBSWY3DPEHPK3PXP</translation>
<translation>Unknown</translation>
</message>
</context>
</TS>
</TS>

View file

@ -34,7 +34,7 @@ static QList<Totp::Encoder> 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:

View file

@ -98,6 +98,9 @@ namespace Totp
bool hasCustomSettings(const QSharedPointer<Totp::Settings>& settings);
Totp::Algorithm getHashTypeByName(const QString& name);
QString getNameForHashType(const Totp::Algorithm hashType);
QList<QPair<QString, QString>> supportedEncoders();
QList<QPair<QString, Algorithm>> supportedAlgorithms();

View file

@ -22,6 +22,8 @@
#include "core/Totp.h"
#include "gui/MessageBox.h"
#include <QUrlQuery>
TotpSetupDialog::TotpSetupDialog(QWidget* parent, Entry* entry)
: QDialog(parent)
, m_ui(new Ui::TotpSetupDialog())
@ -35,71 +37,13 @@ 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()
{
// 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;
}
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<Totp::Algorithm>(m_ui->algorithmComboBox->currentData().toInt());
step = m_ui->stepSpinBox->value();
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;
}
}
m_entry->setTotp(Totp::createSettings(key, digits, step, format, encShortName, algorithm));
emit totpUpdated();
close();
}
void TotpSetupDialog::toggleCustom(bool status)
{
m_ui->customSettingsGroup->setEnabled(status);
}
void TotpSetupDialog::init()
{
// Add algorithm choices
@ -133,3 +77,200 @@ void TotpSetupDialog::init()
m_ui->invalidKeyLabel->setVisible(!error.isEmpty());
}
}
void TotpSetupDialog::saveSettings()
{
QSharedPointer<Totp::Settings> 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()) {
newSettings = createFromCustom();
}
if (newSettings.isNull()) {
return;
}
auto settings = m_entry->totpSettings();
if (settings) {
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?"),
MessageBox::Delete | MessageBox::Cancel);
if (answer != MessageBox::Delete) {
return;
}
}
}
m_entry->setTotp(newSettings);
emit totpUpdated();
close();
}
void TotpSetupDialog::toggleCustom(bool status)
{
m_ui->customSettingsGroup->setEnabled(status);
}
void TotpSetupDialog::toggleUri(bool status)
{
if (status) {
m_ui->labelSecretKey->setText(tr("URI:"));
} else {
m_ui->labelSecretKey->setText(tr("Secret Key:"));
}
}
QSharedPointer<Totp::Settings> 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;
auto settings = m_entry->totpSettings();
if (settings) {
format = settings->format;
}
return Totp::createSettings(key, digits, step, format, encShortName, algorithm);
}
QSharedPointer<Totp::Settings> 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<Totp::Settings> 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<Totp::Settings> 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<Totp::Algorithm>(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;
}
}
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;
}

View file

@ -43,11 +43,19 @@ signals:
private slots:
void toggleCustom(bool status);
void toggleUri(bool status);
void saveSettings();
private:
QScopedPointer<Ui::TotpSetupDialog> m_ui;
Entry* m_entry;
QSharedPointer<Totp::Settings> createFromRfc6238();
QSharedPointer<Totp::Settings> createFromUri();
QSharedPointer<Totp::Settings> createFromSteam();
QSharedPointer<Totp::Settings> createFromCustom();
QString sanitizeSecretKey();
QString sanitizeSecretKey(const QString& key);
};
#endif // KEEPASSX_SETUPTOTPDIALOG_H

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>249</width>
<height>278</height>
<height>307</height>
</rect>
</property>
<property name="windowTitle">
@ -18,7 +18,6 @@
<widget class="QLabel" name="invalidKeyLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@ -26,7 +25,7 @@
<string>Error: secret key is invalid</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
@ -39,7 +38,7 @@
<number>5</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<widget class="QLabel" name="labelSecretKey">
<property name="text">
<string>Secret Key:</string>
</property>
@ -97,6 +96,16 @@
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioUri">
<property name="text">
<string>Totp Uri</string>
</property>
<attribute name="buttonGroup">
<string notr="true">settingsButtonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSteam">
<property name="text">
@ -130,13 +139,13 @@
</property>
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
<enum>QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow</enum>
</property>
<property name="rowWrapPolicy">
<enum>QFormLayout::DontWrapRows</enum>
<enum>QFormLayout::RowWrapPolicy::DontWrapRows</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTop|Qt::AlignmentFlag::AlignTrailing</set>
</property>
<property name="horizontalSpacing">
<number>7</number>
@ -215,10 +224,10 @@
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>