mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
commit
1870b957f5
@ -1235,6 +1235,22 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
<source>&Clone entry</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Timed one-time password</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Setup TOTP</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy &TOTP</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show TOTP</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Find</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -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,10 @@ set(keepassx_SOURCES
|
||||
streams/qtiocompressor.cpp
|
||||
streams/StoreDataStream.cpp
|
||||
streams/SymmetricCipherStream.cpp
|
||||
totp/base32.h
|
||||
totp/base32.cpp
|
||||
totp/totp.h
|
||||
totp/totp.cpp
|
||||
)
|
||||
|
||||
set(keepassx_SOURCES_MAINEXE
|
||||
@ -151,6 +157,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
|
||||
|
@ -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;
|
||||
|
@ -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 if (m_attributes->hasKey("TOTP Seed")) {
|
||||
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));
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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&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 &TOTP</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGroupEmptyRecycleBin">
|
||||
<property name="text">
|
||||
<string>Empty recycle bin</string>
|
||||
|
101
src/gui/SetupTotpDialog.cpp
Normal file
101
src/gui/SetupTotpDialog.cpp
Normal file
@ -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 = QTotp::parseOtpString(m_ui->seedEdit->text(), digits, step);
|
||||
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()
|
||||
{
|
||||
}
|
54
src/gui/SetupTotpDialog.h
Normal file
54
src/gui/SetupTotpDialog.h
Normal file
@ -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
|
137
src/gui/SetupTotpDialog.ui
Normal file
137
src/gui/SetupTotpDialog.ui
Normal file
@ -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>
|
103
src/gui/TotpDialog.cpp
Normal file
103
src/gui/TotpDialog.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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(static_cast<int>(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()
|
||||
{
|
||||
QString totpCode = m_entry->totp();
|
||||
QString firstHalf = totpCode.left(totpCode.size()/2);
|
||||
QString secondHalf = totpCode.right(totpCode.size()/2);
|
||||
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
56
src/gui/TotpDialog.h
Normal file
56
src/gui/TotpDialog.h
Normal file
@ -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
|
60
src/gui/TotpDialog.ui
Normal file
60
src/gui/TotpDialog.ui
Normal file
@ -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>
|
68
src/totp/base32.cpp
Normal file
68
src/totp/base32.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
// Base32 implementation
|
||||
//
|
||||
// Copyright 2010 Google Inc.
|
||||
// Author: Markus Gutschke
|
||||
// Source: https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c
|
||||
// Modifications copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base32.h"
|
||||
|
||||
Base32::Base32()
|
||||
{
|
||||
}
|
||||
|
||||
QByteArray Base32::base32_decode(const QByteArray encoded)
|
||||
{
|
||||
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;
|
||||
}
|
34
src/totp/base32.h
Normal file
34
src/totp/base32.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Base32 implementation
|
||||
//
|
||||
// Copyright 2010 Google Inc.
|
||||
// Author: Markus Gutschke
|
||||
// Source: https://github.com/google/google-authenticator-libpam/blob/master/src/base32.h
|
||||
// Modifications copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef BASE32_H
|
||||
#define BASE32_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QByteArray>
|
||||
|
||||
class Base32
|
||||
{
|
||||
public:
|
||||
Base32();
|
||||
static QByteArray base32_decode(const QByteArray encoded);
|
||||
};
|
||||
|
||||
|
||||
#endif //BASE32_H
|
121
src/totp/totp.cpp
Normal file
121
src/totp/totp.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 "base32.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;
|
||||
}
|
||||
|
||||
QString QTotp::generateTotp(const QByteArray key, quint64 time,
|
||||
const quint8 numDigits = defaultDigits, const quint8 step = defaultStep)
|
||||
{
|
||||
quint64 current = qToBigEndian(time / step);
|
||||
|
||||
QByteArray secret = Base32::base32_decode(key);
|
||||
if (secret.isEmpty()) {
|
||||
return "Invalid TOTP secret key";
|
||||
}
|
||||
|
||||
QMessageAuthenticationCode code(QCryptographicHash::Sha1);
|
||||
code.setKey(secret);
|
||||
code.addData(QByteArray(reinterpret_cast<char*>(¤t), 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'));
|
||||
}
|
33
src/totp/totp.h
Normal file
33
src/totp/totp.h
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 QString generateTotp(const QByteArray key, quint64 time, const quint8 numDigits, const quint8 step);
|
||||
static const quint8 defaultStep;
|
||||
static const quint8 defaultDigits;
|
||||
};
|
||||
|
||||
#endif // QTOTP_H
|
@ -158,6 +158,9 @@ endif()
|
||||
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testtotp SOURCES TestTotp.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testcsvparser SOURCES TestCsvParser.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
|
103
tests/TestTotp.cpp
Normal file
103
tests/TestTotp.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 "TestTotp.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QtEndian>
|
||||
#include <QTextCodec>
|
||||
|
||||
#include "crypto/Crypto.h"
|
||||
#include "totp/totp.h"
|
||||
#include "totp/base32.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestTotp)
|
||||
|
||||
void TestTotp::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
}
|
||||
|
||||
|
||||
void TestTotp::testParseSecret()
|
||||
{
|
||||
quint8 digits = 0;
|
||||
quint8 step = 0;
|
||||
QString secret = "otpauth://totp/ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30";
|
||||
QCOMPARE(QTotp::parseOtpString(secret, digits, step), QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ"));
|
||||
QCOMPARE(digits, quint8(6));
|
||||
QCOMPARE(step, quint8(30));
|
||||
|
||||
digits = QTotp::defaultDigits;
|
||||
step = QTotp::defaultStep;
|
||||
secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8";
|
||||
QCOMPARE(QTotp::parseOtpString(secret, digits, step), QString("HXDMVJECJJWSRBY="));
|
||||
QCOMPARE(digits, quint8(8));
|
||||
QCOMPARE(step, quint8(25));
|
||||
|
||||
digits = 0;
|
||||
step = 0;
|
||||
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||
QCOMPARE(QTotp::parseOtpString(secret, digits, step), QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq"));
|
||||
QCOMPARE(digits, quint8(6));
|
||||
QCOMPARE(step, quint8(30));
|
||||
}
|
||||
|
||||
void TestTotp::testBase32()
|
||||
{
|
||||
QByteArray key = QString("JBSW Y3DP EB3W 64TM MQXC 4LQA").toLatin1();
|
||||
QByteArray secret = Base32::base32_decode(key);
|
||||
QCOMPARE(QString::fromLatin1(secret), QString("Hello world..."));
|
||||
|
||||
key = QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq").toLatin1();
|
||||
secret = Base32::base32_decode(key);
|
||||
QCOMPARE(QString::fromLatin1(secret), QString("12345678901234567890"));
|
||||
|
||||
key = QString("ORSXG5A=").toLatin1();
|
||||
secret = Base32::base32_decode(key);
|
||||
QCOMPARE(QString::fromLatin1(secret), QString("test"));
|
||||
|
||||
key = QString("MZXW6YTBOI======").toLatin1();
|
||||
secret = Base32::base32_decode(key);
|
||||
QCOMPARE(QString::fromLatin1(secret), QString("foobar"));
|
||||
}
|
||||
|
||||
void TestTotp::testTotpCode()
|
||||
{
|
||||
// Test vectors from RFC 6238
|
||||
// https://tools.ietf.org/html/rfc6238#appendix-B
|
||||
|
||||
QByteArray seed = QString("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ").toLatin1();
|
||||
|
||||
quint64 time = 1234567890;
|
||||
QString output = QTotp::generateTotp(seed, time, 6, 30);
|
||||
QCOMPARE(output, QString("005924"));
|
||||
|
||||
time = 1111111109;
|
||||
output = QTotp::generateTotp(seed, time, 6, 30);
|
||||
QCOMPARE(output, QString("081804"));
|
||||
|
||||
time = 1111111111;
|
||||
output = QTotp::generateTotp(seed, time, 8, 30);
|
||||
QCOMPARE(output, QString("14050471"));
|
||||
|
||||
time = 2000000000;
|
||||
output = QTotp::generateTotp(seed, time, 8, 30);
|
||||
QCOMPARE(output, QString("69279037"));
|
||||
}
|
36
tests/TestTotp.h
Normal file
36
tests/TestTotp.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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_TESTTOTP_H
|
||||
#define KEEPASSX_TESTTOTP_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class Totp;
|
||||
|
||||
class TestTotp : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testParseSecret();
|
||||
void testBase32();
|
||||
void testTotpCode();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTTOTP_H
|
@ -48,6 +48,8 @@
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/CloneDialog.h"
|
||||
#include "gui/TotpDialog.h"
|
||||
#include "gui/SetupTotpDialog.h"
|
||||
#include "gui/FileDialog.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
@ -441,6 +443,55 @@ void TestGui::testDicewareEntryEntropy()
|
||||
QCOMPARE(strengthLabel->text(), QString("Password Quality: Good"));
|
||||
}
|
||||
|
||||
void TestGui::testTotp()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 1);
|
||||
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
QModelIndex item = entryView->model()->index(0, 1);
|
||||
Entry* entry = entryView->entryFromIndex(item);
|
||||
|
||||
clickIndex(item, entryView, Qt::LeftButton);
|
||||
|
||||
triggerAction("actionEntrySetupTotp");
|
||||
|
||||
SetupTotpDialog* setupTotpDialog = m_dbWidget->findChild<SetupTotpDialog*>("SetupTotpDialog");
|
||||
|
||||
Tools::wait(100);
|
||||
|
||||
QLineEdit* seedEdit = setupTotpDialog->findChild<QLineEdit*>("seedEdit");
|
||||
|
||||
QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||
QTest::keyClicks(seedEdit, exampleSeed);
|
||||
|
||||
QDialogButtonBox* setupTotpButtonBox = setupTotpDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(setupTotpButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
||||
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
|
||||
editEntryWidget->setCurrentPage(1);
|
||||
QPlainTextEdit* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
QTest::mouseClick(editEntryWidget->findChild<QAbstractButton*>("revealAttributeButton"), Qt::LeftButton);
|
||||
QCOMPARE(attrTextEdit->toPlainText(), exampleSeed);
|
||||
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
triggerAction("actionEntryTotp");
|
||||
|
||||
TotpDialog* totpDialog = m_dbWidget->findChild<TotpDialog*>("TotpDialog");
|
||||
QLabel* totpLabel = totpDialog->findChild<QLabel*>("totpLabel");
|
||||
|
||||
QCOMPARE(totpLabel->text().replace(" ", ""), entry->totp());
|
||||
}
|
||||
|
||||
void TestGui::testSearch()
|
||||
{
|
||||
// Add canned entries for consistent testing
|
||||
|
@ -46,6 +46,7 @@ private slots:
|
||||
void testAddEntry();
|
||||
void testPasswordEntryEntropy();
|
||||
void testDicewareEntryEntropy();
|
||||
void testTotp();
|
||||
void testSearch();
|
||||
void testDeleteEntry();
|
||||
void testCloneEntry();
|
||||
|
Loading…
Reference in New Issue
Block a user