Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWeslly <weslly.honorato@gmail.com>2017-04-13 13:05:36 +0300
committerWeslly <weslly.honorato@gmail.com>2017-05-04 02:55:14 +0300
commitbf57a286545a5fce783c463d208c7ffce9c115ef (patch)
tree556908a0e052294273c6ef2d0e778f7dafacacdb /src
parent7040bef27e105774da05ac372e0832d2affe008b (diff)
Add TOTP support
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt6
-rw-r--r--src/autotype/AutoType.cpp8
-rw-r--r--src/core/Entry.cpp77
-rw-r--r--src/core/Entry.h9
-rw-r--r--src/gui/DatabaseWidget.cpp55
-rw-r--r--src/gui/DatabaseWidget.h4
-rw-r--r--src/gui/MainWindow.cpp15
-rw-r--r--src/gui/MainWindow.ui27
-rw-r--r--src/gui/SetupTotpDialog.cpp101
-rw-r--r--src/gui/SetupTotpDialog.h54
-rw-r--r--src/gui/SetupTotpDialog.ui137
-rw-r--r--src/gui/TotpDialog.cpp100
-rw-r--r--src/gui/TotpDialog.h56
-rw-r--r--src/gui/TotpDialog.ui60
-rw-r--r--src/totp/totp.cpp170
-rw-r--r--src/totp/totp.h34
16 files changed, 911 insertions, 2 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a87a21b2c..d50d27b69 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -100,6 +100,8 @@ set(keepassx_SOURCES
gui/SettingsWidget.cpp
gui/SearchWidget.cpp
gui/SortFilterHideProxyModel.cpp
+ gui/SetupTotpDialog.cpp
+ gui/TotpDialog.cpp
gui/UnlockDatabaseWidget.cpp
gui/UnlockDatabaseDialog.cpp
gui/WelcomeWidget.cpp
@@ -129,6 +131,8 @@ set(keepassx_SOURCES
streams/qtiocompressor.cpp
streams/StoreDataStream.cpp
streams/SymmetricCipherStream.cpp
+ totp/totp.h
+ totp/totp.cpp
)
set(keepassx_SOURCES_MAINEXE
@@ -151,6 +155,8 @@ set(keepassx_FORMS
gui/SearchWidget.ui
gui/SettingsWidgetGeneral.ui
gui/SettingsWidgetSecurity.ui
+ gui/SetupTotpDialog.ui
+ gui/TotpDialog.ui
gui/WelcomeWidget.ui
gui/entry/EditEntryWidgetAdvanced.ui
gui/entry/EditEntryWidgetAutoType.ui
diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp
index 1d96377f5..12367fe95 100644
--- a/src/autotype/AutoType.cpp
+++ b/src/autotype/AutoType.cpp
@@ -518,6 +518,14 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
else if (tmplName.compare("clearfield",Qt::CaseInsensitive)==0) {
list.append(new AutoTypeClearField());
}
+ else if (tmplName.compare("totp", Qt::CaseInsensitive) == 0) {
+ QString totp = entry->totp();
+ if (!totp.isEmpty()) {
+ for (const QChar& ch : totp) {
+ list.append(new AutoTypeChar(ch));
+ }
+ }
+ }
if (!list.isEmpty()) {
return list;
diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp
index d1672c5b1..e86a7092f 100644
--- a/src/core/Entry.cpp
+++ b/src/core/Entry.cpp
@@ -14,13 +14,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-
#include "Entry.h"
#include "core/Database.h"
#include "core/DatabaseIcons.h"
#include "core/Group.h"
#include "core/Metadata.h"
+#include "totp/totp.h"
const int Entry::DefaultIconNumber = 0;
@@ -35,6 +35,8 @@ Entry::Entry()
m_data.iconNumber = DefaultIconNumber;
m_data.autoTypeEnabled = true;
m_data.autoTypeObfuscation = 0;
+ m_data.totpStep = QTotp::defaultStep;
+ m_data.totpDigits = QTotp::defaultDigits;
connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged()));
@@ -285,6 +287,77 @@ const EntryAttachments* Entry::attachments() const
return m_attachments;
}
+bool Entry::hasTotp() const
+{
+ return m_attributes->hasKey("TOTP Seed") || m_attributes->hasKey("otp");
+}
+
+QString Entry::totp() const
+{
+ if (hasTotp()) {
+ QString seed = totpSeed();
+ quint64 time = QDateTime::currentDateTime().toTime_t();
+ QString output = QTotp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep);
+
+ return QString(output);
+ } else {
+ return QString("");
+ }
+}
+
+void Entry::setTotp(const QString& seed, quint8& step, quint8& digits)
+{
+ if (step == 0) {
+ step = QTotp::defaultStep;
+ }
+
+ if (digits == 0) {
+ digits = QTotp::defaultDigits;
+ }
+
+ if (m_attributes->hasKey("otp")) {
+ m_attributes->set("otp", QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(digits), true);
+ } else {
+ m_attributes->set("TOTP Seed", seed, true);
+ m_attributes->set("TOTP Settings", QString("%1;%2").arg(step).arg(digits));
+ }
+}
+
+QString Entry::totpSeed() const
+{
+ QString secret = "";
+
+ if (m_attributes->hasKey("otp")) {
+ secret = m_attributes->value("otp");
+ } else {
+ secret = m_attributes->value("TOTP Seed");
+ }
+
+ m_data.totpDigits = QTotp::defaultDigits;
+ m_data.totpStep = QTotp::defaultStep;
+
+ if (m_attributes->hasKey("TOTP Settings")) {
+ QRegExp rx("(\\d+);(\\d)", Qt::CaseInsensitive, QRegExp::RegExp);
+ int pos = rx.indexIn(m_attributes->value("TOTP Settings"));
+ if (pos > -1) {
+ m_data.totpStep = rx.cap(1).toUInt();
+ m_data.totpDigits = rx.cap(2).toUInt();
+ }
+ }
+
+ return QTotp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep);
+}
+
+quint8 Entry::totpStep() const
+{
+ return m_data.totpStep;
+}
+
+quint8 Entry::totpDigits() const
+{
+ return m_data.totpDigits;
+}
+
void Entry::setUuid(const Uuid& uuid)
{
Q_ASSERT(!uuid.isNull());
@@ -679,7 +752,7 @@ QString Entry::resolvePlaceholder(const QString& str) const
k.prepend("{");
}
-
+
k.append("}");
if (result.compare(k,cs)==0) {
result.replace(result,attributes()->value(key));
diff --git a/src/core/Entry.h b/src/core/Entry.h
index 25b9bc386..cdb826eca 100644
--- a/src/core/Entry.h
+++ b/src/core/Entry.h
@@ -47,6 +47,8 @@ struct EntryData
int autoTypeObfuscation;
QString defaultAutoTypeSequence;
TimeInfo timeInfo;
+ mutable quint8 totpDigits;
+ mutable quint8 totpStep;
};
class Entry : public QObject
@@ -78,6 +80,12 @@ public:
QString username() const;
QString password() const;
QString notes() const;
+ QString totp() const;
+ QString totpSeed() const;
+ quint8 totpDigits() const;
+ quint8 totpStep() const;
+
+ bool hasTotp() const;
bool isExpired() const;
bool hasReferences() const;
EntryAttributes* attributes();
@@ -105,6 +113,7 @@ public:
void setNotes(const QString& notes);
void setExpires(const bool& value);
void setExpiryTime(const QDateTime& dateTime);
+ void setTotp(const QString& seed, quint8& step, quint8& digits);
QList<Entry*> historyItems();
const QList<Entry*>& historyItems() const;
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index 115c560f0..946757e40 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -42,6 +42,8 @@
#include "gui/ChangeMasterKeyWidget.h"
#include "gui/Clipboard.h"
#include "gui/CloneDialog.h"
+#include "gui/SetupTotpDialog.h"
+#include "gui/TotpDialog.h"
#include "gui/DatabaseOpenWidget.h"
#include "gui/DatabaseSettingsWidget.h"
#include "gui/KeePass1OpenWidget.h"
@@ -333,6 +335,48 @@ void DatabaseWidget::cloneEntry()
return;
}
+void DatabaseWidget::showTotp()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return;
+ }
+
+ TotpDialog* totpDialog = new TotpDialog(this, currentEntry);
+ totpDialog->open();
+}
+
+void DatabaseWidget::copyTotp()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return;
+ }
+ setClipboardTextAndMinimize(currentEntry->totp());
+}
+
+void DatabaseWidget::setupTotp()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return;
+ }
+
+ SetupTotpDialog* setupTotpDialog = new SetupTotpDialog(this, currentEntry);
+ if (currentEntry->hasTotp()) {
+ setupTotpDialog->setSeed(currentEntry->totpSeed());
+ setupTotpDialog->setStep(currentEntry->totpStep());
+ setupTotpDialog->setDigits(currentEntry->totpDigits());
+ }
+
+ setupTotpDialog->open();
+
+}
+
+
void DatabaseWidget::deleteEntries()
{
const QModelIndexList selected = m_entryView->selectionModel()->selectedRows();
@@ -1225,6 +1269,17 @@ bool DatabaseWidget::currentEntryHasUrl()
return !currentEntry->resolveMultiplePlaceholders(currentEntry->url()).isEmpty();
}
+
+bool DatabaseWidget::currentEntryHasTotp()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return false;
+ }
+ return currentEntry->hasTotp();
+}
+
bool DatabaseWidget::currentEntryHasNotes()
{
Entry* currentEntry = m_entryView->currentEntry();
diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h
index 3add336f0..aa1c83443 100644
--- a/src/gui/DatabaseWidget.h
+++ b/src/gui/DatabaseWidget.h
@@ -96,6 +96,7 @@ public:
bool currentEntryHasPassword();
bool currentEntryHasUrl();
bool currentEntryHasNotes();
+ bool currentEntryHasTotp();
GroupView* groupView();
EntryView* entryView();
void showUnlockDialog();
@@ -133,6 +134,9 @@ public slots:
void copyURL();
void copyNotes();
void copyAttribute(QAction* action);
+ void showTotp();
+ void copyTotp();
+ void setupTotp();
void performAutoType();
void openUrl();
void openUrlForEntry(Entry* entry);
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 6883f8a7e..0415f2b4e 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -167,6 +167,8 @@ MainWindow::MainWindow()
m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E);
m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D);
m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K);
+ m_ui->actionEntryTotp->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_T);
+ m_ui->actionEntryCopyTotp->setShortcut(Qt::CTRL + Qt::Key_T);
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
setShortcut(m_ui->actionEntryAutoType, QKeySequence::Paste, Qt::CTRL + Qt::Key_V);
@@ -275,6 +277,13 @@ MainWindow::MainWindow()
m_actionMultiplexer.connect(m_ui->actionEntryDelete, SIGNAL(triggered()),
SLOT(deleteEntries()));
+ m_actionMultiplexer.connect(m_ui->actionEntryTotp, SIGNAL(triggered()),
+ SLOT(showTotp()));
+ m_actionMultiplexer.connect(m_ui->actionEntrySetupTotp, SIGNAL(triggered()),
+ SLOT(setupTotp()));
+
+ m_actionMultiplexer.connect(m_ui->actionEntryCopyTotp, SIGNAL(triggered()),
+ SLOT(copyTotp()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyTitle, SIGNAL(triggered()),
SLOT(copyTitle()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyUsername, SIGNAL(triggered()),
@@ -428,8 +437,12 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasNotes());
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
+ m_ui->menuEntryTotp->setEnabled(true);
m_ui->actionEntryAutoType->setEnabled(singleEntrySelected);
m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
+ m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
+ m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
+ m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected);
m_ui->actionGroupNew->setEnabled(groupSelected);
m_ui->actionGroupEdit->setEnabled(groupSelected);
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
@@ -463,6 +476,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryCopyURL->setEnabled(false);
m_ui->actionEntryCopyNotes->setEnabled(false);
m_ui->menuEntryCopyAttribute->setEnabled(false);
+ m_ui->menuEntryTotp->setEnabled(false);
m_ui->actionChangeMasterKey->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
@@ -495,6 +509,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryCopyURL->setEnabled(false);
m_ui->actionEntryCopyNotes->setEnabled(false);
m_ui->menuEntryCopyAttribute->setEnabled(false);
+ m_ui->menuEntryTotp->setEnabled(false);
m_ui->actionChangeMasterKey->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui
index 384586e8d..2ed42d0ec 100644
--- a/src/gui/MainWindow.ui
+++ b/src/gui/MainWindow.ui
@@ -220,9 +220,21 @@
<addaction name="actionEntryCopyNotes"/>
<addaction name="separator"/>
</widget>
+ <widget class="QMenu" name="menuEntryTotp">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="title">
+ <string>Timed one-time password</string>
+ </property>
+ <addaction name="actionEntryCopyTotp"/>
+ <addaction name="actionEntryTotp"/>
+ <addaction name="actionEntrySetupTotp"/>
+ </widget>
<addaction name="actionEntryCopyUsername"/>
<addaction name="actionEntryCopyPassword"/>
<addaction name="menuEntryCopyAttribute"/>
+ <addaction name="menuEntryTotp"/>
<addaction name="actionEntryAutoType"/>
<addaction name="actionEntryOpenUrl"/>
<addaction name="actionEntryEdit"/>
@@ -523,6 +535,21 @@
<string>Re&amp;pair database</string>
</property>
</action>
+ <action name="actionEntryTotp">
+ <property name="text">
+ <string>Show TOTP</string>
+ </property>
+ </action>
+ <action name="actionEntrySetupTotp">
+ <property name="text">
+ <string>Setup TOTP</string>
+ </property>
+ </action>
+ <action name="actionEntryCopyTotp">
+ <property name="text">
+ <string>Copy &amp;TOTP</string>
+ </property>
+ </action>
<action name="actionGroupEmptyRecycleBin">
<property name="text">
<string>Empty recycle bin</string>
diff --git a/src/gui/SetupTotpDialog.cpp b/src/gui/SetupTotpDialog.cpp
new file mode 100644
index 000000000..43a042df9
--- /dev/null
+++ b/src/gui/SetupTotpDialog.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "SetupTotpDialog.h"
+#include "ui_SetupTotpDialog.h"
+#include "totp/totp.h"
+
+
+SetupTotpDialog::SetupTotpDialog(DatabaseWidget* parent, Entry* entry)
+ : QDialog(parent)
+ , m_ui(new Ui::SetupTotpDialog())
+{
+ m_entry = entry;
+ m_parent = parent;
+
+ m_ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ this->setFixedSize(this->sizeHint());
+
+ connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
+ connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(setupTotp()));
+ connect(m_ui->customSettingsCheckBox, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool)));
+}
+
+
+void SetupTotpDialog::setupTotp()
+{
+ quint8 digits;
+
+ if (m_ui->radio8Digits->isChecked()) {
+ digits = 8;
+ } else {
+ digits = 6;
+ }
+
+ quint8 step = m_ui->stepSpinBox->value();
+ QString seed = m_ui->seedEdit->text();
+ m_entry->setTotp(seed, step, digits);
+ emit m_parent->entrySelectionChanged();
+ close();
+}
+
+void SetupTotpDialog::toggleCustom(bool status)
+{
+ m_ui->digitsLabel->setEnabled(status);
+ m_ui->radio6Digits->setEnabled(status);
+ m_ui->radio8Digits->setEnabled(status);
+
+ m_ui->stepLabel->setEnabled(status);
+ m_ui->stepSpinBox->setEnabled(status);
+}
+
+
+void SetupTotpDialog::setSeed(QString value)
+{
+ m_ui->seedEdit->setText(value);
+}
+
+void SetupTotpDialog::setStep(quint8 step)
+{
+ m_ui->stepSpinBox->setValue(step);
+
+ if (step != QTotp::defaultStep) {
+ m_ui->customSettingsCheckBox->setChecked(true);
+ }
+}
+
+void SetupTotpDialog::setDigits(quint8 digits)
+{
+ if (digits == 8) {
+ m_ui->radio8Digits->setChecked(true);
+ m_ui->radio6Digits->setChecked(false);
+ } else {
+ m_ui->radio6Digits->setChecked(true);
+ m_ui->radio8Digits->setChecked(false);
+ }
+
+ if (digits != QTotp::defaultDigits) {
+ m_ui->customSettingsCheckBox->setChecked(true);
+ }
+}
+
+
+SetupTotpDialog::~SetupTotpDialog()
+{
+}
diff --git a/src/gui/SetupTotpDialog.h b/src/gui/SetupTotpDialog.h
new file mode 100644
index 000000000..416e19a5c
--- /dev/null
+++ b/src/gui/SetupTotpDialog.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEEPASSX_SETUPTOTPDIALOG_H
+#define KEEPASSX_SETUPTOTPDIALOG_H
+
+#include <QDialog>
+#include <QScopedPointer>
+#include "core/Entry.h"
+#include "core/Database.h"
+#include "gui/DatabaseWidget.h"
+
+namespace Ui {
+ class SetupTotpDialog;
+}
+
+class SetupTotpDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit SetupTotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr);
+ ~SetupTotpDialog();
+ void setSeed(QString value);
+ void setStep(quint8 step);
+ void setDigits(quint8 digits);
+
+private Q_SLOTS:
+ void toggleCustom(bool status);
+ void setupTotp();
+
+private:
+ QScopedPointer<Ui::SetupTotpDialog> m_ui;
+
+protected:
+ Entry* m_entry;
+ DatabaseWidget* m_parent;
+};
+
+#endif // KEEPASSX_SETUPTOTPDIALOG_H
diff --git a/src/gui/SetupTotpDialog.ui b/src/gui/SetupTotpDialog.ui
new file mode 100644
index 000000000..a6d806287
--- /dev/null
+++ b/src/gui/SetupTotpDialog.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SetupTotpDialog</class>
+ <widget class="QDialog" name="SetupTotpDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>282</width>
+ <height>257</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Setup TOTP</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Key:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="seedEdit"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="customSettingsCheckBox">
+ <property name="text">
+ <string>Use custom settings</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Note: Change these settings only if you know what you are doing.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout_3">
+ <property name="fieldGrowthPolicy">
+ <enum>QFormLayout::ExpandingFieldsGrow</enum>
+ </property>
+ <property name="rowWrapPolicy">
+ <enum>QFormLayout::DontWrapRows</enum>
+ </property>
+ <property name="labelAlignment">
+ <set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
+ </property>
+ <item row="1" column="0">
+ <widget class="QLabel" name="stepLabel">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Time step:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QRadioButton" name="radio8Digits">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>8 digits</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QRadioButton" name="radio6Digits">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>6 digits</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="digitsLabel">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Code size:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="stepSpinBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="suffix">
+ <string> sec</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>60</number>
+ </property>
+ <property name="value">
+ <number>30</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/gui/TotpDialog.cpp b/src/gui/TotpDialog.cpp
new file mode 100644
index 000000000..2eb1c9e2c
--- /dev/null
+++ b/src/gui/TotpDialog.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "TotpDialog.h"
+#include "ui_TotpDialog.h"
+
+#include "core/Config.h"
+#include "core/Entry.h"
+#include "gui/DatabaseWidget.h"
+#include "gui/Clipboard.h"
+
+#include <QTimer>
+#include <QDateTime>
+#include <QPushButton>
+
+
+TotpDialog::TotpDialog(DatabaseWidget* parent, Entry* entry)
+ : QDialog(parent)
+ , m_ui(new Ui::TotpDialog())
+{
+ m_entry = entry;
+ m_parent = parent;
+ m_step = m_entry->totpStep();
+
+ m_ui->setupUi(this);
+
+ uCounter = resetCounter();
+ updateProgressBar();
+
+ QTimer *timer = new QTimer(this);
+ connect(timer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
+ connect(timer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
+ timer->start(m_step * 10);
+
+ updateTotp();
+
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Copy"));
+
+ connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
+ connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard()));
+}
+
+void TotpDialog::copyToClipboard()
+{
+ clipboard()->setText(m_entry->totp());
+ if (config()->get("MinimizeOnCopy").toBool()) {
+ m_parent->window()->showMinimized();
+ }
+}
+
+void TotpDialog::updateProgressBar()
+{
+ if (uCounter < 100) {
+ m_ui->progressBar->setValue(100 - uCounter);
+ m_ui->progressBar->update();
+ uCounter++;
+ } else {
+ updateTotp();
+ uCounter = resetCounter();
+ }
+}
+
+
+void TotpDialog::updateSeconds()
+{
+ uint epoch = QDateTime::currentDateTime().toTime_t() - 1;
+ m_ui->timerLabel->setText(tr("Expires in") + " <b>" + QString::number(m_step - (epoch % m_step)) + "</b> " + tr("seconds"));
+}
+
+void TotpDialog::updateTotp()
+{
+ m_ui->totpLabel->setText(m_entry->totp());
+}
+
+double TotpDialog::resetCounter()
+{
+ uint epoch = QDateTime::currentDateTime().toTime_t();
+ double counter = qRound(static_cast<double>(epoch % m_step) / m_step * 100);
+ return counter;
+}
+
+TotpDialog::~TotpDialog()
+{
+}
diff --git a/src/gui/TotpDialog.h b/src/gui/TotpDialog.h
new file mode 100644
index 000000000..66754dd29
--- /dev/null
+++ b/src/gui/TotpDialog.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEEPASSX_TOTPDIALOG_H
+#define KEEPASSX_TOTPDIALOG_H
+
+#include <QDialog>
+#include <QScopedPointer>
+#include "core/Entry.h"
+#include "core/Database.h"
+#include "gui/DatabaseWidget.h"
+
+namespace Ui {
+ class TotpDialog;
+}
+
+class TotpDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr);
+ ~TotpDialog();
+
+private:
+ double uCounter;
+ quint8 m_step;
+ QScopedPointer<Ui::TotpDialog> m_ui;
+
+private Q_SLOTS:
+ void updateTotp();
+ void updateProgressBar();
+ void updateSeconds();
+ void copyToClipboard();
+ double resetCounter();
+
+protected:
+ Entry* m_entry;
+ DatabaseWidget* m_parent;
+};
+
+#endif // KEEPASSX_TOTPDIALOG_H
diff --git a/src/gui/TotpDialog.ui b/src/gui/TotpDialog.ui
new file mode 100644
index 000000000..e11e761e9
--- /dev/null
+++ b/src/gui/TotpDialog.ui
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TotpDialog</class>
+ <widget class="QWidget" name="TotpDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>264</width>
+ <height>194</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Timed Password</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="totpLabel">
+ <property name="font">
+ <font>
+ <pointsize>53</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>000000</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="progressBar">
+ <property name="value">
+ <number>0</number>
+ </property>
+ <property name="textVisible">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="timerLabel">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp
new file mode 100644
index 000000000..45e98d38d
--- /dev/null
+++ b/src/totp/totp.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "totp.h"
+#include <cmath>
+#include <QtEndian>
+#include <QRegExp>
+#include <QDateTime>
+#include <QCryptographicHash>
+#include <QMessageAuthenticationCode>
+#include <QUrl>
+#include <QUrlQuery>
+
+
+const quint8 QTotp::defaultStep = 30;
+const quint8 QTotp::defaultDigits = 6;
+
+QTotp::QTotp()
+{
+}
+
+QString QTotp::parseOtpString(QString key, quint8 &digits, quint8 &step)
+{
+ QUrl url(key);
+
+ QString seed;
+ uint q_digits, q_step;
+
+ // Default OTP url format
+ if (url.isValid() && url.scheme() == "otpauth") {
+ QUrlQuery query(url);
+
+ seed = query.queryItemValue("secret");
+
+ q_digits = query.queryItemValue("digits").toUInt();
+ if (q_digits == 6 || q_digits == 8) {
+ digits = q_digits;
+ }
+
+ q_step = query.queryItemValue("period").toUInt();
+ if (q_step > 0 && q_step <= 60) {
+ step = q_step;
+ }
+
+
+ } else {
+ // Compatibility with "KeeOtp" plugin string format
+ QRegExp rx("key=(.+)", Qt::CaseInsensitive, QRegExp::RegExp);
+
+ if (rx.exactMatch(key)) {
+ QUrlQuery query(key);
+
+ seed = query.queryItemValue("key");
+ q_digits = query.queryItemValue("size").toUInt();
+ if (q_digits == 6 || q_digits == 8) {
+ digits = q_digits;
+ }
+
+ q_step = query.queryItemValue("step").toUInt();
+ if (q_step > 0 && q_step <= 60) {
+ step = q_step;
+ }
+
+ } else {
+ seed = key;
+ }
+ }
+
+ if (digits == 0) {
+ digits = defaultDigits;
+ }
+
+ if (step == 0) {
+ step = defaultStep;
+ }
+
+ return seed;
+}
+
+
+QByteArray QTotp::base32_decode(const QByteArray encoded)
+{
+ // Base32 implementation
+ // Copyright 2010 Google Inc.
+ // Author: Markus Gutschke
+ // Licensed under the Apache License, Version 2.0
+
+ QByteArray result;
+
+ int buffer = 0;
+ int bitsLeft = 0;
+
+ for (char ch : encoded) {
+ if (ch == 0 || ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-' || ch == '=') {
+ continue;
+ }
+
+ buffer <<= 5;
+
+ // Deal with commonly mistyped characters
+ if (ch == '0') {
+ ch = 'O';
+ } else if (ch == '1') {
+ ch = 'L';
+ } else if (ch == '8') {
+ ch = 'B';
+ }
+
+ // Look up one base32 digit
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
+ ch = (ch & 0x1F) - 1;
+ } else if (ch >= '2' && ch <= '7') {
+ ch -= '2' - 26;
+ } else {
+ return QByteArray();
+ }
+
+ buffer |= ch;
+ bitsLeft += 5;
+
+ if (bitsLeft >= 8) {
+ result.append(static_cast<char> (buffer >> (bitsLeft - 8)));
+ bitsLeft -= 8;
+ }
+ }
+
+ return result;
+}
+
+
+QString QTotp::generateTotp(const QByteArray key, quint64 time, const quint8 numDigits = defaultDigits, const quint8 step = defaultStep)
+{
+ quint64 current = qToBigEndian(time / step);
+
+ QByteArray secret = QTotp::base32_decode(key);
+ if (secret.isEmpty()) {
+ return "Invalid TOTP secret key";
+ }
+
+ QMessageAuthenticationCode code(QCryptographicHash::Sha1);
+ code.setKey(secret);
+ code.addData(QByteArray(reinterpret_cast<char*>(&current), sizeof(current)));
+ QByteArray hmac = code.result();
+
+ int offset = (hmac[hmac.length() - 1] & 0xf);
+ int binary =
+ ((hmac[offset] & 0x7f) << 24)
+ | ((hmac[offset + 1] & 0xff) << 16)
+ | ((hmac[offset + 2] & 0xff) << 8)
+ | (hmac[offset + 3] & 0xff);
+
+ quint32 digitsPower = pow(10, numDigits);
+
+ quint64 password = binary % digitsPower;
+ return QString("%1").arg(password, numDigits, 10, QChar('0'));
+}
diff --git a/src/totp/totp.h b/src/totp/totp.h
new file mode 100644
index 000000000..8d7e86744
--- /dev/null
+++ b/src/totp/totp.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QTOTP_H
+#define QTOTP_H
+
+#include <QtCore/qglobal.h>
+
+class QTotp
+{
+public:
+ QTotp();
+ static QString parseOtpString(QString rawSecret, quint8 &digits, quint8 &step);
+ static QByteArray base32_decode(const QByteArray encoded);
+ static QString generateTotp(const QByteArray key, quint64 time, const quint8 numDigits, const quint8 step);
+ static const quint8 defaultStep;
+ static const quint8 defaultDigits;
+};
+
+#endif // QTOTP_H