mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-04 15:39:34 +01:00
Don't add space to invalid TOTP strings
* Fixes #11357 * Introduces validity parameter to TOTP generator function for future use elsewhere in the code base * Fixes this in preview panel and TOTP dialog * Disable actions to copy/show TOTP if the settings are invalid * Show an error message on the TOTP setup dialog if the settings are invalid * Show a TOTP icon with an x if the settings are invalid
This commit is contained in:
1
COPYING
1
COPYING
@@ -223,6 +223,7 @@ Files: share/icons/application/scalable/actions/application-exit.svg
|
|||||||
share/icons/application/scalable/actions/totp-copy.svg
|
share/icons/application/scalable/actions/totp-copy.svg
|
||||||
share/icons/application/scalable/actions/totp-copy-password.svg
|
share/icons/application/scalable/actions/totp-copy-password.svg
|
||||||
share/icons/application/scalable/actions/totp-edit.svg
|
share/icons/application/scalable/actions/totp-edit.svg
|
||||||
|
share/icons/application/scalable/actions/totp-invalid.svg
|
||||||
share/icons/application/scalable/actions/trash.svg
|
share/icons/application/scalable/actions/trash.svg
|
||||||
share/icons/application/scalable/actions/url-copy.svg
|
share/icons/application/scalable/actions/url-copy.svg
|
||||||
share/icons/application/scalable/actions/user-guide.svg
|
share/icons/application/scalable/actions/user-guide.svg
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14.47 15.08L11 13V7H12.5V12.25L15.58 14.08C15.17 14.36 14.79 14.7 14.47 15.08M13.08 19.92C12.72 19.97 12.37 20 12 20C7.58 20 4 16.42 4 12S7.58 4 12 4 20 7.58 20 12C20 12.37 19.97 12.72 19.92 13.08C20.61 13.18 21.25 13.4 21.84 13.72C21.94 13.16 22 12.59 22 12C22 6.5 17.5 2 12 2S2 6.5 2 12C2 17.5 6.47 22 12 22C12.59 22 13.16 21.94 13.72 21.84C13.4 21.25 13.18 20.61 13.08 19.92M21.12 15.46L19 17.59L16.88 15.47L15.47 16.88L17.59 19L15.47 21.12L16.88 22.54L19 20.41L21.12 22.54L22.54 21.12L20.41 19L22.54 16.88L21.12 15.46Z" /></svg>
|
||||||
|
After Width: | Height: | Size: 602 B |
@@ -92,6 +92,7 @@
|
|||||||
<file>application/scalable/actions/totp-copy.svg</file>
|
<file>application/scalable/actions/totp-copy.svg</file>
|
||||||
<file>application/scalable/actions/totp-copy-password.svg</file>
|
<file>application/scalable/actions/totp-copy-password.svg</file>
|
||||||
<file>application/scalable/actions/totp-edit.svg</file>
|
<file>application/scalable/actions/totp-edit.svg</file>
|
||||||
|
<file>application/scalable/actions/totp-invalid.svg</file>
|
||||||
<file>application/scalable/actions/trash.svg</file>
|
<file>application/scalable/actions/trash.svg</file>
|
||||||
<file>application/scalable/actions/url-copy.svg</file>
|
<file>application/scalable/actions/url-copy.svg</file>
|
||||||
<file>application/scalable/actions/user-guide.svg</file>
|
<file>application/scalable/actions/user-guide.svg</file>
|
||||||
|
|||||||
@@ -724,6 +724,10 @@
|
|||||||
<source>Invalid placeholder: %1</source>
|
<source>Invalid placeholder: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Entry has invalid TOTP settings</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>AutoTypeAssociationsModel</name>
|
<name>AutoTypeAssociationsModel</name>
|
||||||
@@ -9247,6 +9251,16 @@ This option is deprecated, use --set-key-file instead.</source>
|
|||||||
<source>Warning: the chosen wordlist is smaller than the minimum recommended size!</source>
|
<source>Warning: the chosen wordlist is smaller than the minimum recommended size!</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Invalid Step</source>
|
||||||
|
<comment>TOTP</comment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Invalid Digits</source>
|
||||||
|
<comment>TOTP</comment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Fit</source>
|
<source>Fit</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@@ -10317,6 +10331,10 @@ Example: JBSWY3DPEHPK3PXP</source>
|
|||||||
<source>Are you sure you want to delete TOTP settings for this entry?</source>
|
<source>Are you sure you want to delete TOTP settings for this entry?</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Error: secret key is invalid</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>URLEdit</name>
|
<name>URLEdit</name>
|
||||||
|
|||||||
@@ -637,11 +637,17 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
|
|||||||
// Platform-specific field clearing
|
// Platform-specific field clearing
|
||||||
actions << QSharedPointer<AutoTypeClearField>::create();
|
actions << QSharedPointer<AutoTypeClearField>::create();
|
||||||
} else if (placeholder == "totp") {
|
} else if (placeholder == "totp") {
|
||||||
|
if (entry->hasValidTotp()) {
|
||||||
// Entry totp (requires special handling)
|
// Entry totp (requires special handling)
|
||||||
QString totp = entry->totp();
|
QString totp = entry->totp();
|
||||||
for (const auto& ch : totp) {
|
for (const auto& ch : totp) {
|
||||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||||
}
|
}
|
||||||
|
} else if (entry->hasTotp()) {
|
||||||
|
// Entry has TOTP configured but invalid settings
|
||||||
|
error = tr("Entry has invalid TOTP settings");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
} else if (placeholder.startsWith("pickchars")) {
|
} else if (placeholder.startsWith("pickchars")) {
|
||||||
// Reset to the original capture to preserve case
|
// Reset to the original capture to preserve case
|
||||||
placeholder = match.captured(3);
|
placeholder = match.captured(3);
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ void AutoTypeSelectDialog::updateActionMenu(const AutoTypeMatch& match)
|
|||||||
|
|
||||||
bool hasUsername = !match.first->username().isEmpty();
|
bool hasUsername = !match.first->username().isEmpty();
|
||||||
bool hasPassword = !match.first->password().isEmpty();
|
bool hasPassword = !match.first->password().isEmpty();
|
||||||
bool hasTotp = match.first->hasTotp();
|
bool hasTotp = match.first->hasValidTotp();
|
||||||
|
|
||||||
for (auto action : m_actionMenu->actions()) {
|
for (auto action : m_actionMenu->actions()) {
|
||||||
auto prop = action->property(MENU_FIELD_PROP_NAME);
|
auto prop = action->property(MENU_FIELD_PROP_NAME);
|
||||||
|
|||||||
@@ -1192,7 +1192,7 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
|||||||
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
|
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
|
||||||
res["group"] = entry->resolveMultiplePlaceholders(entry->group()->name());
|
res["group"] = entry->resolveMultiplePlaceholders(entry->group()->name());
|
||||||
|
|
||||||
if (entry->hasTotp()) {
|
if (entry->hasValidTotp()) {
|
||||||
res["totp"] = entry->totp();
|
res["totp"] = entry->totp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -570,6 +570,12 @@ bool Entry::hasTotp() const
|
|||||||
return !m_data.totpSettings.isNull();
|
return !m_data.totpSettings.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Entry::hasValidTotp() const
|
||||||
|
{
|
||||||
|
auto error = Totp::checkValidSettings(m_data.totpSettings);
|
||||||
|
return error.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
bool Entry::hasPasskey() const
|
bool Entry::hasPasskey() const
|
||||||
{
|
{
|
||||||
return m_attributes->hasPasskey();
|
return m_attributes->hasPasskey();
|
||||||
@@ -581,10 +587,13 @@ void Entry::removePasskey()
|
|||||||
removeTag(tr("Passkey"));
|
removeTag(tr("Passkey"));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Entry::totp() const
|
QString Entry::totp(bool* isValid) const
|
||||||
{
|
{
|
||||||
if (hasTotp()) {
|
if (hasTotp()) {
|
||||||
return Totp::generateTotp(m_data.totpSettings);
|
return Totp::generateTotp(m_data.totpSettings, isValid);
|
||||||
|
}
|
||||||
|
if (isValid) {
|
||||||
|
*isValid = false;
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public:
|
|||||||
QString password() const;
|
QString password() const;
|
||||||
QString notes() const;
|
QString notes() const;
|
||||||
QString attribute(const QString& key) const;
|
QString attribute(const QString& key) const;
|
||||||
QString totp() const;
|
QString totp(bool* isValid = nullptr) const;
|
||||||
QString totpSettingsString() const;
|
QString totpSettingsString() const;
|
||||||
QSharedPointer<Totp::Settings> totpSettings() const;
|
QSharedPointer<Totp::Settings> totpSettings() const;
|
||||||
Group* previousParentGroup();
|
Group* previousParentGroup();
|
||||||
@@ -126,6 +126,7 @@ public:
|
|||||||
void removePasskey();
|
void removePasskey();
|
||||||
|
|
||||||
bool hasTotp() const;
|
bool hasTotp() const;
|
||||||
|
bool hasValidTotp() const;
|
||||||
bool isExpired() const;
|
bool isExpired() const;
|
||||||
bool willExpireInDays(int days) const;
|
bool willExpireInDays(int days) const;
|
||||||
void expireNow();
|
void expireNow();
|
||||||
|
|||||||
@@ -210,12 +210,33 @@ QString Totp::writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const quint64 time)
|
QString Totp::checkValidSettings(const QSharedPointer<Totp::Settings>& settings)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!settings.isNull());
|
|
||||||
if (settings.isNull()) {
|
if (settings.isNull()) {
|
||||||
return QObject::tr("Invalid Settings", "TOTP");
|
return QObject::tr("Invalid Settings", "TOTP");
|
||||||
}
|
}
|
||||||
|
QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1()));
|
||||||
|
if (secret.isNull()) {
|
||||||
|
return QObject::tr("Invalid Key", "TOTP");
|
||||||
|
}
|
||||||
|
if (settings->step == 0) {
|
||||||
|
return QObject::tr("Invalid Step", "TOTP");
|
||||||
|
}
|
||||||
|
if (settings->digits == 0) {
|
||||||
|
return QObject::tr("Invalid Digits", "TOTP");
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, bool* isValid, const quint64 time)
|
||||||
|
{
|
||||||
|
auto error = checkValidSettings(settings);
|
||||||
|
if (!error.isEmpty()) {
|
||||||
|
if (isValid) {
|
||||||
|
*isValid = false;
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
const Encoder& encoder = settings->encoder;
|
const Encoder& encoder = settings->encoder;
|
||||||
uint step = settings->step;
|
uint step = settings->step;
|
||||||
@@ -229,9 +250,6 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1()));
|
QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1()));
|
||||||
if (secret.isNull()) {
|
|
||||||
return QObject::tr("Invalid Key", "TOTP");
|
|
||||||
}
|
|
||||||
|
|
||||||
QCryptographicHash::Algorithm cryptoHash;
|
QCryptographicHash::Algorithm cryptoHash;
|
||||||
switch (settings->algorithm) {
|
switch (settings->algorithm) {
|
||||||
@@ -274,6 +292,9 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const
|
|||||||
retval[pos] = encoder.alphabet[int(password % encoder.alphabet.size())];
|
retval[pos] = encoder.alphabet[int(password % encoder.alphabet.size())];
|
||||||
password /= encoder.alphabet.size();
|
password /= encoder.alphabet.size();
|
||||||
}
|
}
|
||||||
|
if (isValid) {
|
||||||
|
*isValid = true;
|
||||||
|
}
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,8 +91,10 @@ namespace Totp
|
|||||||
const QString& title = {},
|
const QString& title = {},
|
||||||
const QString& username = {},
|
const QString& username = {},
|
||||||
bool forceOtp = false);
|
bool forceOtp = false);
|
||||||
|
// Returns an empty string if settings are valid, otherwise an error message is supplied
|
||||||
QString generateTotp(const QSharedPointer<Totp::Settings>& settings, const quint64 time = 0ull);
|
QString checkValidSettings(const QSharedPointer<Totp::Settings>& settings);
|
||||||
|
QString
|
||||||
|
generateTotp(const QSharedPointer<Totp::Settings>& settings, bool* isValid = nullptr, const quint64 time = 0ull);
|
||||||
|
|
||||||
bool hasCustomSettings(const QSharedPointer<Totp::Settings>& settings);
|
bool hasCustomSettings(const QSharedPointer<Totp::Settings>& settings);
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ namespace FdoSecrets
|
|||||||
// add some informative and readonly attributes
|
// add some informative and readonly attributes
|
||||||
attrs[ItemAttributes::UuidKey] = m_backend->uuidToHex();
|
attrs[ItemAttributes::UuidKey] = m_backend->uuidToHex();
|
||||||
attrs[ItemAttributes::PathKey] = path();
|
attrs[ItemAttributes::PathKey] = path();
|
||||||
if (m_backend->hasTotp()) {
|
if (m_backend->hasValidTotp()) {
|
||||||
attrs[ItemAttributes::TotpKey] = m_backend->totp();
|
attrs[ItemAttributes::TotpKey] = m_backend->totp();
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -1527,7 +1527,7 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EntryModel::Totp:
|
case EntryModel::Totp:
|
||||||
if (entry->hasTotp()) {
|
if (entry->hasValidTotp()) {
|
||||||
setClipboardTextAndMinimize(entry->totp());
|
setClipboardTextAndMinimize(entry->totp());
|
||||||
} else {
|
} else {
|
||||||
setupTotp();
|
setupTotp();
|
||||||
@@ -2386,7 +2386,7 @@ bool DatabaseWidget::currentEntryHasTotp()
|
|||||||
if (!currentEntry) {
|
if (!currentEntry) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return currentEntry->hasTotp();
|
return currentEntry->hasValidTotp();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WITH_XC_SSHAGENT
|
#ifdef WITH_XC_SSHAGENT
|
||||||
|
|||||||
@@ -70,8 +70,7 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent)
|
|||||||
|
|
||||||
m_ui->entryTotpLabel->installEventFilter(this);
|
m_ui->entryTotpLabel->installEventFilter(this);
|
||||||
|
|
||||||
connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpLabel, SLOT(setVisible(bool)));
|
connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotp, SLOT(setVisible(bool)));
|
||||||
connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpProgress, SLOT(setVisible(bool)));
|
|
||||||
connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide()));
|
connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide()));
|
||||||
connect(m_ui->toggleUsernameButton, SIGNAL(clicked(bool)), SLOT(setUsernameVisible(bool)));
|
connect(m_ui->toggleUsernameButton, SIGNAL(clicked(bool)), SLOT(setUsernameVisible(bool)));
|
||||||
connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool)));
|
connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool)));
|
||||||
@@ -260,8 +259,7 @@ void EntryPreviewWidget::updateEntryTotp()
|
|||||||
m_ui->entryTotpProgress->setMaximum(m_currentEntry->totpSettings()->step);
|
m_ui->entryTotpProgress->setMaximum(m_currentEntry->totpSettings()->step);
|
||||||
updateTotpLabel();
|
updateTotpLabel();
|
||||||
} else {
|
} else {
|
||||||
m_ui->entryTotpLabel->hide();
|
m_ui->entryTotp->hide();
|
||||||
m_ui->entryTotpProgress->hide();
|
|
||||||
m_ui->entryTotpButton->setChecked(false);
|
m_ui->entryTotpButton->setChecked(false);
|
||||||
m_ui->entryTotpLabel->clear();
|
m_ui->entryTotpLabel->clear();
|
||||||
m_totpTimer.stop();
|
m_totpTimer.stop();
|
||||||
@@ -546,16 +544,23 @@ void EntryPreviewWidget::updateGroupSharingTab()
|
|||||||
void EntryPreviewWidget::updateTotpLabel()
|
void EntryPreviewWidget::updateTotpLabel()
|
||||||
{
|
{
|
||||||
if (!m_locked && m_currentEntry && m_currentEntry->hasTotp()) {
|
if (!m_locked && m_currentEntry && m_currentEntry->hasTotp()) {
|
||||||
auto totpCode = m_currentEntry->totp();
|
bool isValid = false;
|
||||||
|
auto totpCode = m_currentEntry->totp(&isValid);
|
||||||
|
if (isValid) {
|
||||||
totpCode.insert(totpCode.size() / 2, " ");
|
totpCode.insert(totpCode.size() / 2, " ");
|
||||||
m_ui->entryTotpLabel->setText(totpCode);
|
|
||||||
|
|
||||||
auto step = m_currentEntry->totpSettings()->step;
|
auto step = m_currentEntry->totpSettings()->step;
|
||||||
auto timeleft = step - (Clock::currentSecondsSinceEpoch() % step);
|
auto timeleft = step - (Clock::currentSecondsSinceEpoch() % step);
|
||||||
m_ui->entryTotpProgress->setValue(timeleft);
|
m_ui->entryTotpProgress->setValue(timeleft);
|
||||||
m_ui->entryTotpProgress->update();
|
m_ui->entryTotpProgress->update();
|
||||||
} else {
|
} else {
|
||||||
m_ui->entryTotpLabel->clear();
|
m_totpTimer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ui->entryTotpProgress->setVisible(isValid);
|
||||||
|
m_ui->entryTotpLabel->setText(totpCode);
|
||||||
|
} else {
|
||||||
|
m_ui->entryTotp->setVisible(false);
|
||||||
m_totpTimer.stop();
|
m_totpTimer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,16 +110,34 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<widget class="QWidget" name="entryTotp" native="true">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="entryTotpLabel">
|
<widget class="QLabel" name="entryTotpLabel">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<pointsize>10</pointsize>
|
<pointsize>10</pointsize>
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
<bold>true</bold>
|
||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
@@ -130,7 +148,7 @@
|
|||||||
<string notr="true">1234567</string>
|
<string notr="true">1234567</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="textInteractionFlags">
|
<property name="textInteractionFlags">
|
||||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
<set>Qt::TextInteractionFlag::LinksAccessibleByMouse|Qt::TextInteractionFlag::TextSelectableByKeyboard|Qt::TextInteractionFlag::TextSelectableByMouse</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -151,6 +169,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QToolButton" name="entryTotpButton">
|
<widget class="QToolButton" name="entryTotpButton">
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ TotpDialog::TotpDialog(QWidget* parent, Entry* entry)
|
|||||||
m_step = m_entry->totpSettings()->step;
|
m_step = m_entry->totpSettings()->step;
|
||||||
resetCounter();
|
resetCounter();
|
||||||
updateProgressBar();
|
updateProgressBar();
|
||||||
|
updateSeconds();
|
||||||
|
|
||||||
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
|
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
|
||||||
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
|
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
|
||||||
@@ -88,10 +89,15 @@ void TotpDialog::updateSeconds()
|
|||||||
|
|
||||||
void TotpDialog::updateTotp()
|
void TotpDialog::updateTotp()
|
||||||
{
|
{
|
||||||
QString totpCode = m_entry->totp();
|
bool isValid = false;
|
||||||
QString firstHalf = totpCode.left(totpCode.size() / 2);
|
QString totpCode = m_entry->totp(&isValid);
|
||||||
QString secondHalf = totpCode.mid(totpCode.size() / 2);
|
if (isValid) {
|
||||||
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
|
totpCode.insert(totpCode.size() / 2, " ");
|
||||||
|
}
|
||||||
|
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isValid);
|
||||||
|
m_ui->progressBar->setVisible(isValid);
|
||||||
|
m_ui->timerLabel->setVisible(isValid);
|
||||||
|
m_ui->totpLabel->setText(totpCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TotpDialog::resetCounter()
|
void TotpDialog::resetCounter()
|
||||||
|
|||||||
@@ -127,5 +127,8 @@ void TotpSetupDialog::init()
|
|||||||
m_ui->algorithmComboBox->setCurrentIndex(index);
|
m_ui->algorithmComboBox->setCurrentIndex(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto error = Totp::checkValidSettings(settings);
|
||||||
|
m_ui->invalidKeyLabel->setVisible(!error.isEmpty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,22 @@
|
|||||||
<string>Setup TOTP</string>
|
<string>Setup TOTP</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="invalidKeyLabel">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Error: secret key is invalid</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
@@ -210,6 +226,7 @@
|
|||||||
<zorder>customSettingsGroup</zorder>
|
<zorder>customSettingsGroup</zorder>
|
||||||
<zorder>buttonBox</zorder>
|
<zorder>buttonBox</zorder>
|
||||||
<zorder>groupBox</zorder>
|
<zorder>groupBox</zorder>
|
||||||
|
<zorder>invalidKeyLabel</zorder>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>seedEdit</tabstop>
|
<tabstop>seedEdit</tabstop>
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
|||||||
break;
|
break;
|
||||||
case Totp:
|
case Totp:
|
||||||
if (entry->hasTotp()) {
|
if (entry->hasTotp()) {
|
||||||
return icons()->icon("totp");
|
return entry->hasValidTotp() ? icons()->icon("totp") : icons()->icon("totp-invalid");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PasswordStrength:
|
case PasswordStrength:
|
||||||
|
|||||||
@@ -115,18 +115,18 @@ void TestTotp::testTotpCode()
|
|||||||
|
|
||||||
// Test 6 digit TOTP (default)
|
// Test 6 digit TOTP (default)
|
||||||
quint64 time = 1234567890;
|
quint64 time = 1234567890;
|
||||||
QCOMPARE(Totp::generateTotp(settings, time), QString("005924"));
|
QCOMPARE(Totp::generateTotp(settings, nullptr, time), QString("005924"));
|
||||||
|
|
||||||
time = 1111111109;
|
time = 1111111109;
|
||||||
QCOMPARE(Totp::generateTotp(settings, time), QString("081804"));
|
QCOMPARE(Totp::generateTotp(settings, nullptr, time), QString("081804"));
|
||||||
|
|
||||||
// Test 8 digit TOTP (custom)
|
// Test 8 digit TOTP (custom)
|
||||||
settings->digits = 8;
|
settings->digits = 8;
|
||||||
time = 1111111111;
|
time = 1111111111;
|
||||||
QCOMPARE(Totp::generateTotp(settings, time), QString("14050471"));
|
QCOMPARE(Totp::generateTotp(settings, nullptr, time), QString("14050471"));
|
||||||
|
|
||||||
time = 2000000000;
|
time = 2000000000;
|
||||||
QCOMPARE(Totp::generateTotp(settings, time), QString("69279037"));
|
QCOMPARE(Totp::generateTotp(settings, nullptr, time), QString("69279037"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestTotp::testSteamTotp()
|
void TestTotp::testSteamTotp()
|
||||||
@@ -155,9 +155,9 @@ void TestTotp::testSteamTotp()
|
|||||||
// Steam mobile app with a throw-away steam account. The above secret was extracted
|
// Steam mobile app with a throw-away steam account. The above secret was extracted
|
||||||
// from the Steam app's data for use in testing here.
|
// from the Steam app's data for use in testing here.
|
||||||
quint64 time = 1511200518;
|
quint64 time = 1511200518;
|
||||||
QCOMPARE(Totp::generateTotp(settings, time), QString("FR8RV"));
|
QCOMPARE(Totp::generateTotp(settings, nullptr, time), QString("FR8RV"));
|
||||||
time = 1511200714;
|
time = 1511200714;
|
||||||
QCOMPARE(Totp::generateTotp(settings, time), QString("9P3VP"));
|
QCOMPARE(Totp::generateTotp(settings, nullptr, time), QString("9P3VP"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestTotp::testEntryHistory()
|
void TestTotp::testEntryHistory()
|
||||||
|
|||||||
Reference in New Issue
Block a user