From bf57a286545a5fce783c463d208c7ffce9c115ef Mon Sep 17 00:00:00 2001 From: Weslly Date: Thu, 13 Apr 2017 07:05:36 -0300 Subject: Add TOTP support --- src/CMakeLists.txt | 6 ++ src/autotype/AutoType.cpp | 8 +++ src/core/Entry.cpp | 77 +++++++++++++++++++- src/core/Entry.h | 9 +++ src/gui/DatabaseWidget.cpp | 55 ++++++++++++++ src/gui/DatabaseWidget.h | 4 ++ src/gui/MainWindow.cpp | 15 ++++ src/gui/MainWindow.ui | 27 +++++++ src/gui/SetupTotpDialog.cpp | 101 ++++++++++++++++++++++++++ src/gui/SetupTotpDialog.h | 54 ++++++++++++++ src/gui/SetupTotpDialog.ui | 137 +++++++++++++++++++++++++++++++++++ src/gui/TotpDialog.cpp | 100 ++++++++++++++++++++++++++ src/gui/TotpDialog.h | 56 +++++++++++++++ src/gui/TotpDialog.ui | 60 ++++++++++++++++ src/totp/totp.cpp | 170 ++++++++++++++++++++++++++++++++++++++++++++ src/totp/totp.h | 34 +++++++++ 16 files changed, 911 insertions(+), 2 deletions(-) create mode 100644 src/gui/SetupTotpDialog.cpp create mode 100644 src/gui/SetupTotpDialog.h create mode 100644 src/gui/SetupTotpDialog.ui create mode 100644 src/gui/TotpDialog.cpp create mode 100644 src/gui/TotpDialog.h create mode 100644 src/gui/TotpDialog.ui create mode 100644 src/totp/totp.cpp create mode 100644 src/totp/totp.h (limited to 'src') 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 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 . */ - #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 historyItems(); const QList& 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 @@ + + + false + + + Timed one-time password + + + + + + @@ -523,6 +535,21 @@ Re&pair database + + + Show TOTP + + + + + Setup TOTP + + + + + Copy &TOTP + + Empty recycle bin 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 . + */ + +#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 . + */ + +#ifndef KEEPASSX_SETUPTOTPDIALOG_H +#define KEEPASSX_SETUPTOTPDIALOG_H + +#include +#include +#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 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 @@ + + + SetupTotpDialog + + + + 0 + 0 + 282 + 257 + + + + Setup TOTP + + + + + + + + Key: + + + + + + + + + + + + Use custom settings + + + + + + + Note: Change these settings only if you know what you are doing. + + + true + + + + + + + QFormLayout::ExpandingFieldsGrow + + + QFormLayout::DontWrapRows + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + false + + + Time step: + + + + + + + false + + + 8 digits + + + + + + + false + + + 6 digits + + + true + + + + + + + false + + + Code size: + + + + + + + false + + + sec + + + 1 + + + 60 + + + 30 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 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 . + */ + +#include "TotpDialog.h" +#include "ui_TotpDialog.h" + +#include "core/Config.h" +#include "core/Entry.h" +#include "gui/DatabaseWidget.h" +#include "gui/Clipboard.h" + +#include +#include +#include + + +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") + " " + QString::number(m_step - (epoch % m_step)) + " " + 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(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 . + */ + +#ifndef KEEPASSX_TOTPDIALOG_H +#define KEEPASSX_TOTPDIALOG_H + +#include +#include +#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 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 @@ + + + TotpDialog + + + + 0 + 0 + 264 + 194 + + + + Timed Password + + + + + + + 53 + + + + 000000 + + + Qt::AlignCenter + + + + + + + 0 + + + false + + + + + + + + + + + + + + QDialogButtonBox::Close|QDialogButtonBox::Ok + + + + + + + + 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 . + */ + +#include "totp.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +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 (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(¤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')); +} 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 . + */ + +#ifndef QTOTP_H +#define QTOTP_H + +#include + +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 -- cgit v1.2.3