Add QR code generator for TOTP export (#1167)

* Resolves #764
* Add libqrencode and qtsvg dependencies 
* Ensure QR code remains square
* Auto-close QR code dialog when database is locked
* Add databaseLocked() Signal to databaseWidget
* Correct otpauth URI output in Totp::writeSettings(...)
This commit is contained in:
Adolfo E. García 2018-10-19 12:42:49 -06:00 committed by Jonathan White
parent 80749958b7
commit bb16dc6d01
21 changed files with 584 additions and 16 deletions

View File

@ -294,16 +294,16 @@ endif(WITH_TESTS)
include(CLangFormat)
if(UNIX AND NOT APPLE)
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools DBus REQUIRED)
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools DBus REQUIRED)
elseif(APPLE)
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools REQUIRED
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools REQUIRED
HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH
)
find_package(Qt5 COMPONENTS MacExtras
HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH
)
else()
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools REQUIRED)
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools REQUIRED)
endif()
if(Qt5Core_VERSION VERSION_LESS "5.2.0")
@ -339,6 +339,8 @@ find_package(Argon2 REQUIRED)
find_package(ZLIB REQUIRED)
find_package(QREncode REQUIRED)
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0")

View File

@ -16,7 +16,7 @@
FROM ubuntu:14.04
ENV REBUILD_COUNTER=8
ENV REBUILD_COUNTER=10
ENV QT5_VERSION=qt510
ENV QT5_PPA_VERSION=qt-5.10.1
@ -50,12 +50,14 @@ RUN set -x \
${QT5_VERSION}x11extras \
${QT5_VERSION}translations \
${QT5_VERSION}imageformats \
${QT5_VERSION}svg \
zlib1g-dev \
libxi-dev \
libxtst-dev \
mesa-common-dev \
libyubikey-dev \
libykpers-1-dev
libykpers-1-dev \
libqrencode-dev
ENV PATH="/opt/${QT5_VERSION}/bin:${PATH}"
ENV CMAKE_PREFIX_PATH="/opt/${QT5_VERSION}/lib/cmake"

View File

@ -18,7 +18,7 @@
FROM ubuntu:14.04
ENV REBUILD_COUNTER=4
ENV REBUILD_COUNTER=5
ENV QT5_VERSION=qt53
ENV QT5_PPA_VERSION=${QT5_VERSION}2
@ -49,11 +49,13 @@ RUN set -x \
${QT5_VERSION}tools \
${QT5_VERSION}x11extras \
${QT5_VERSION}translations \
${QT5_VERSION}svg \
zlib1g-dev \
libyubikey-dev \
libykpers-1-dev \
libxi-dev \
libxtst-dev \
libqrencode-dev \
xvfb
ENV PATH="/opt/${QT5_VERSION}/bin:${PATH}"

22
cmake/FindQREncode.cmake Normal file
View File

@ -0,0 +1,22 @@
# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
#
# 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/>.
find_path(QRENCODE_INCLUDE_DIR qrencode.h)
find_library(QRENCODE_LIBRARY qrencode)
mark_as_advanced(QRENCODE_LIBRARY QRENCODE_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(QREncode DEFAULT_MSG QRENCODE_LIBRARY QRENCODE_INCLUDE_DIR)

View File

@ -39,6 +39,7 @@ parts:
- libgcrypt20-dev
- libqt5x11extras5-dev
- qtbase5-dev
- qtsvg5-dev
- qttools5-dev
- qttools5-dev-tools
- zlib1g-dev
@ -48,6 +49,7 @@ parts:
- libykpers-1-dev
- libsodium-dev
- libargon2-0-dev
- libqrencode-dev
stage-packages:
- dbus
- qttranslations5-l10n # common translations

View File

@ -127,8 +127,10 @@ set(keepassx_SOURCES
gui/ApplicationSettingsWidget.cpp
gui/SearchWidget.cpp
gui/SortFilterHideProxyModel.cpp
gui/SquareSvgWidget.cpp
gui/TotpSetupDialog.cpp
gui/TotpDialog.cpp
gui/TotpExportSettingsDialog.cpp
gui/UnlockDatabaseWidget.cpp
gui/UnlockDatabaseDialog.cpp
gui/WelcomeWidget.cpp
@ -225,6 +227,8 @@ endif()
add_subdirectory(autotype)
add_subdirectory(cli)
add_subdirectory(qrcode)
set(qrcode_LIB qrcode)
add_subdirectory(sshagent)
if(WITH_XC_SSHAGENT)
@ -270,9 +274,10 @@ target_link_libraries(keepassx_core
autotype
${keepassxcbrowser_LIB}
${sshagent_LIB}
${qrcode_LIB}
Qt5::Core
Qt5::Network
Qt5::Concurrent
Qt5::Network
Qt5::Widgets
${CURL_LIBRARIES}
${YUBIKEY_LIBRARIES}
@ -280,7 +285,9 @@ target_link_libraries(keepassx_core
${ARGON2_LIBRARIES}
${GCRYPT_LIBRARIES}
${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES})
${YUBIKEY_LIBRARIES}
${ZLIB_LIBRARIES}
${ZXCVBN_LIBRARIES})
if(APPLE)
target_link_libraries(keepassx_core "-framework Foundation")

View File

@ -369,7 +369,7 @@ void Entry::setTotp(QSharedPointer<Totp::Settings> settings)
beginUpdate();
m_data.totpSettings = settings;
auto text = Totp::writeSettings(m_data.totpSettings);
auto text = Totp::writeSettings(m_data.totpSettings, title(), username());
if (m_attributes->hasKey(Totp::ATTRIBUTE_OTP)) {
m_attributes->set(Totp::ATTRIBUTE_OTP, text, true);
} else {

View File

@ -49,6 +49,7 @@
#include "gui/MessageBox.h"
#include "gui/TotpSetupDialog.h"
#include "gui/TotpDialog.h"
#include "gui/TotpExportSettingsDialog.h"
#include "gui/UnlockDatabaseDialog.h"
#include "gui/UnlockDatabaseWidget.h"
#include "gui/entry/EditEntryWidget.h"
@ -572,6 +573,18 @@ void DatabaseWidget::copyAttribute(QAction* action)
currentEntry->resolveMultiplePlaceholders(currentEntry->attributes()->value(action->data().toString())));
}
void DatabaseWidget::showTotpKeyQrCode()
{
Entry* currentEntry = m_entryView->currentEntry();
Q_ASSERT(currentEntry);
if (!currentEntry) {
return;
}
auto totpDisplayDialog = new TotpExportSettingsDialog(this, currentEntry);
totpDisplayDialog->open();
}
void DatabaseWidget::setClipboardTextAndMinimize(const QString& text)
{
clipboard()->setText(text);
@ -1171,6 +1184,7 @@ void DatabaseWidget::lock()
Database* newDb = new Database();
newDb->metadata()->setName(m_db->metadata()->name());
replaceDatabase(newDb);
emit lockedDatabase();
}
void DatabaseWidget::updateFilePath(const QString& filePath)

View File

@ -125,6 +125,7 @@ signals:
void pressedEntry(Entry* selectedEntry);
void pressedGroup(Group* selectedGroup);
void unlockedDatabase();
void lockedDatabase();
void listModeAboutToActivate();
void listModeActivated();
void searchModeAboutToActivate();
@ -146,6 +147,7 @@ public slots:
void copyNotes();
void copyAttribute(QAction* action);
void showTotp();
void showTotpKeyQrCode();
void copyTotp();
void setupTotp();
void performAutoType();

View File

@ -287,6 +287,7 @@ MainWindow::MainWindow()
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->actionEntryTotpQRCode, SIGNAL(triggered()), SLOT(showTotpKeyQrCode()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyTitle, SIGNAL(triggered()), SLOT(copyTitle()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyUsername, SIGNAL(triggered()), SLOT(copyUsername()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyPassword, SIGNAL(triggered()), SLOT(copyPassword()));
@ -478,6 +479,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected);
m_ui->actionEntryTotpQRCode->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionGroupNew->setEnabled(groupSelected);
m_ui->actionGroupEdit->setEnabled(groupSelected);
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());

View File

@ -251,6 +251,7 @@
</property>
<addaction name="actionEntryCopyTotp"/>
<addaction name="actionEntryTotp"/>
<addaction name="actionEntryTotpQRCode"/>
<addaction name="actionEntrySetupTotp"/>
</widget>
<addaction name="actionEntryCopyUsername"/>
@ -577,7 +578,12 @@
</action>
<action name="actionEntryTotp">
<property name="text">
<string>Show TOTP</string>
<string>Show TOTP...</string>
</property>
</action>
<action name="actionEntryTotpQRCode">
<property name="text">
<string>Show TOTP QR Code...</string>
</property>
</action>
<action name="actionEntrySetupTotp">

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* 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 "SquareSvgWidget.h"
bool SquareSvgWidget::hasHeightForWidth() const
{
return true;
}
int SquareSvgWidget::heightForWidth(int width) const
{
return width;
}

33
src/gui/SquareSvgWidget.h Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* 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_SquareSvgWidget_H
#define KEEPASSX_SquareSvgWidget_H
#include <QtSvg/QSvgWidget>
class SquareSvgWidget : public QSvgWidget
{
public:
SquareSvgWidget() = default;
~SquareSvgWidget() override = default;
bool hasHeightForWidth() const override;
int heightForWidth(int width) const override;
};
#endif // KEEPASSX_SquareSvgWidget_H

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 "TotpExportSettingsDialog.h"
#include "core/Config.h"
#include "core/Entry.h"
#include "gui/Clipboard.h"
#include "gui/DatabaseWidget.h"
#include "gui/SquareSvgWidget.h"
#include "qrcode/QrCode.h"
#include "totp/totp.h"
#include <QBuffer>
#include <QDialogButtonBox>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QSizePolicy>
#include <QVBoxLayout>
TotpExportSettingsDialog::TotpExportSettingsDialog(DatabaseWidget* parent, Entry* entry)
: QDialog(parent)
, m_timer(new QTimer(this))
, m_verticalLayout(new QVBoxLayout())
, m_totpSvgWidget(new SquareSvgWidget())
, m_countDown(new QLabel())
, m_warningLabel(new QLabel())
, m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Close | QDialogButtonBox::Ok))
{
m_verticalLayout->addWidget(m_warningLabel);
m_verticalLayout->addItem(new QSpacerItem(0, 0));
m_verticalLayout->addStretch(0);
m_verticalLayout->addWidget(m_totpSvgWidget);
m_verticalLayout->addStretch(0);
m_verticalLayout->addWidget(m_countDown);
m_verticalLayout->addWidget(m_buttonBox);
setLayout(m_verticalLayout);
setAttribute(Qt::WA_DeleteOnClose);
connect(m_buttonBox, SIGNAL(rejected()), SLOT(close()));
connect(m_buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard()));
connect(m_timer, SIGNAL(timeout()), this, SLOT(autoClose()));
connect(parent, SIGNAL(lockedDatabase()), this, SLOT(close()));
m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Copy"));
m_countDown->setAlignment(Qt::AlignCenter);
m_secTillClose = 45;
autoClose();
m_timer->start(1000);
const auto totpSettings = entry->totpSettings();
if (totpSettings->custom || !totpSettings->encoder.shortName.isEmpty()) {
m_warningLabel->setWordWrap(true);
m_warningLabel->setMargin(5);
m_warningLabel->setText(tr("NOTE: These TOTP settings are custom and may not work with other authenticators.",
"TOTP QR code dialog warning"));
} else {
m_warningLabel->hide();
}
m_totpUri = Totp::writeSettings(entry->totpSettings(), entry->title(), entry->username(), true);
const QrCode qrc(m_totpUri);
if (qrc.isValid()) {
QBuffer buffer;
qrc.writeSvg(&buffer, logicalDpiX());
m_totpSvgWidget->load(buffer.data());
const int minsize = static_cast<int>(logicalDpiX() * 2.5);
m_totpSvgWidget->setMinimumSize(minsize, minsize);
} else {
auto errorBox = new QMessageBox(parent);
errorBox->setAttribute(Qt::WA_DeleteOnClose);
errorBox->setIcon(QMessageBox::Warning);
errorBox->setText(tr("There was an error creating the QR code."));
errorBox->exec();
close();
}
show();
}
void TotpExportSettingsDialog::copyToClipboard()
{
clipboard()->setText(m_totpUri);
if (config()->get("MinimizeOnCopy").toBool()) {
static_cast<DatabaseWidget*>(parent())->window()->showMinimized();
}
}
void TotpExportSettingsDialog::autoClose()
{
if (--m_secTillClose > 0) {
m_countDown->setText(tr("Closing in %1 seconds.").arg(m_secTillClose));
} else {
m_timer->stop();
close();
}
}
TotpExportSettingsDialog::~TotpExportSettingsDialog() = default;

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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_TotpExportSettingsDialog_H
#define KEEPASSX_TotpExportSettingsDialog_H
#include "core/Database.h"
#include "core/Entry.h"
#include "gui/DatabaseWidget.h"
#include <QDialog>
#include <QTimer>
#include <QUrl>
class QVBoxLayout;
class SquareSvgWidget;
class QLabel;
class QDialogButtonBox;
class TotpExportSettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit TotpExportSettingsDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr);
~TotpExportSettingsDialog();
private slots:
void copyToClipboard();
void autoClose();
private:
int m_secTillClose;
QString m_totpUri;
QTimer* m_timer;
QVBoxLayout* m_verticalLayout;
SquareSvgWidget* m_totpSvgWidget;
QLabel* m_countDown;
QLabel* m_warningLabel;
QDialogButtonBox* m_buttonBox;
};
#endif // KEEPASSX_TOTPEXPORTSETTINGSDIALOG_H

21
src/qrcode/CMakeLists.txt Normal file
View File

@ -0,0 +1,21 @@
# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
#
# 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/>.
set(qrcode_SOURCES
QrCode.cpp
)
add_library(qrcode STATIC ${qrcode_SOURCES})
target_link_libraries(qrcode Qt5::Core Qt5::Widgets Qt5::Svg ${QRENCODE_LIBRARY})

132
src/qrcode/QrCode.cpp Normal file
View File

@ -0,0 +1,132 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 "QrCode.h"
#include "QrCode_p.h"
#include <QBrush>
#include <QByteArray>
#include <QIODevice>
#include <QImage>
#include <QPainter>
#include <QPen>
#include <QString>
#include <QSvgGenerator>
#include <QVariant>
QrCodePrivate::QrCodePrivate()
: m_qrcode(nullptr)
{
}
QrCodePrivate::~QrCodePrivate()
{
if (m_qrcode) {
QRcode_free(m_qrcode);
}
}
QrCode::QrCode()
: d_ptr(new QrCodePrivate())
{
}
QrCode::QrCode(const QString& data, const Version version, const ErrorCorrectionLevel ecl, const bool caseSensitive)
: d_ptr(new QrCodePrivate())
{
init(data, version, ecl, caseSensitive);
}
QrCode::QrCode(const QByteArray& data, const Version version, const ErrorCorrectionLevel ecl)
: d_ptr(new QrCodePrivate())
{
init(data, version, ecl);
}
QrCode::~QrCode() = default;
void QrCode::init(const QString& data, const Version version, const ErrorCorrectionLevel ecl, bool caseSensitive)
{
if (data.isEmpty()) {
return;
}
d_ptr->m_qrcode = QRcode_encodeString(data.toLocal8Bit().data(),
static_cast<int>(version),
static_cast<QRecLevel>(ecl),
QR_MODE_8,
caseSensitive ? 1 : 0);
}
void QrCode::init(const QByteArray& data, const Version version, const ErrorCorrectionLevel ecl)
{
if (data.isEmpty()) {
return;
}
d_ptr->m_qrcode = QRcode_encodeData(data.size(),
reinterpret_cast<const unsigned char*>(data.data()),
static_cast<int>(version),
static_cast<QRecLevel>(ecl));
}
bool QrCode::isValid() const
{
return d_ptr->m_qrcode != nullptr;
}
void QrCode::writeSvg(QIODevice* outputDevice, const int dpi, const int margin) const
{
if (margin < 0 || d_ptr->m_qrcode == nullptr || outputDevice == nullptr) {
return;
}
const int width = d_ptr->m_qrcode->width + margin * 2;
QSvgGenerator generator;
generator.setSize(QSize(width, width));
generator.setViewBox(QRect(0, 0, width, width));
generator.setResolution(dpi);
generator.setOutputDevice(outputDevice);
QPainter painter;
painter.begin(&generator);
// Background
painter.setClipRect(QRect(0, 0, width, width));
painter.fillRect(QRect(0, 0, width, width), Qt::white);
// Foreground
// "Dots" are stored in a quint8 x quint8 array using row-major order.
// A dot is black if the LSB of its corresponding quint8 is 1.
const QPen pen(Qt::black, 0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
const QBrush brush(Qt::black);
painter.setPen(pen);
painter.setBrush(brush);
const int rowSize = d_ptr->m_qrcode->width;
unsigned char* dot = d_ptr->m_qrcode->data;
for (int y = 0; y < rowSize; ++y) {
for (int x = 0; x < rowSize; ++x) {
if (quint8(0x01) == (static_cast<quint8>(*dot++) & quint8(0x01))) {
painter.drawRect(margin + x, margin + y, 1, 1);
}
}
}
painter.end();
}

78
src/qrcode/QrCode.h Normal file
View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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_QRCODE_H
#define KEEPASSX_QRCODE_H
#include <QScopedPointer>
#include <QtCore/qglobal.h>
class QImage;
class QIODevice;
class QString;
class QByteArray;
struct QrCodePrivate;
class QrCode
{
public:
enum class ErrorCorrectionLevel : int
{
LOW = 0,
MEDIUM,
QUARTILE,
HIGH
};
// See: http://www.qrcode.com/en/about/version.html
// clang-format off
enum class Version : int
{
AUTO = 0,
V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20,
V21, V22, V23, V24, V25, V26, V27, V28, V29, V30, V31, V32, V33, V34, V35, V36, V37, V38, V39, V40
};
// clang-format on
// Uses QRcode_encodeString (can't contain NUL characters)
explicit QrCode(const QString& data,
const Version version = Version::AUTO,
const ErrorCorrectionLevel ecl = ErrorCorrectionLevel::HIGH,
const bool caseSensitive = true);
// Uses QRcode_encodeData (can contain NUL characters)
explicit QrCode(const QByteArray& data,
const Version version = Version::AUTO,
const ErrorCorrectionLevel ecl = ErrorCorrectionLevel::HIGH);
QrCode();
~QrCode();
bool isValid() const;
void writeSvg(QIODevice* outputDevice, const int dpi, const int margin = 4) const;
private:
void init(const QString& data, const Version version, const ErrorCorrectionLevel ecl, const bool caseSensitive);
void init(const QByteArray& data, const Version version, const ErrorCorrectionLevel ecl);
QScopedPointer<QrCodePrivate> d_ptr;
};
#endif // KEEPASSX_QRCODE_H

33
src/qrcode/QrCode_p.h Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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/>.
*/
/* This class exists to isolate <qrencode.h> from the rest of the code base. */
#ifndef KEEPASSX_QRCODEPRIVATE_H
#define KEEPASSX_QRCODEPRIVATE_H
#include <qrencode.h>
struct QrCodePrivate
{
QRcode* m_qrcode;
QrCodePrivate();
~QrCodePrivate();
};
#endif // KEEPASSX_QRCODEPRIVATE_H

View File

@ -22,7 +22,8 @@
#include <QCryptographicHash>
#include <QMessageAuthenticationCode>
#include <QRegExp>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QUrl>
#include <QUrlQuery>
#include <QVariant>
@ -79,7 +80,7 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
settings->step = qBound(1u, settings->step, 60u);
// Detect custom settings, used by setup GUI
if (settings->encoder.shortName != STEAM_SHORTNAME
if (settings->encoder.shortName.isEmpty()
&& (settings->digits != DEFAULT_DIGITS || settings->step != DEFAULT_STEP)) {
settings->custom = true;
}
@ -96,15 +97,21 @@ QSharedPointer<Totp::Settings> Totp::createSettings(const QString& key, const ui
});
}
QString Totp::writeSettings(const QSharedPointer<Totp::Settings> settings)
QString Totp::writeSettings(const QSharedPointer<Totp::Settings> settings, const QString& title, const QString& username, bool forceOtp)
{
if (settings.isNull()) {
return {};
}
// OTP Url output
if (settings->otpUrl) {
auto urlstring = QString("key=%1&step=%2&size=%3").arg(settings->key).arg(settings->step).arg(settings->digits);
if (settings->otpUrl || forceOtp) {
auto urlstring = QString("otpauth://totp/%1:%2?secret=%3&period=%4&digits=%5&issuer=%1")
.arg(title.isEmpty() ? "KeePassXC" : QString(QUrl::toPercentEncoding(title)))
.arg(username.isEmpty() ? "none" : QString(QUrl::toPercentEncoding(username)))
.arg(QString(Base32::sanitizeInput(settings->key.toLatin1())))
.arg(settings->step)
.arg(settings->digits);
if (!settings->encoder.name.isEmpty()) {
urlstring.append("&encoder=").append(settings->encoder.name);
}

View File

@ -60,7 +60,8 @@ static const QString ATTRIBUTE_SETTINGS = "TOTP Settings";
QSharedPointer<Totp::Settings> parseSettings(const QString& rawSettings, const QString& key = {});
QSharedPointer<Totp::Settings> createSettings(const QString& key, const uint digits, const uint step,
const QString& encoderShortName = {});
QString writeSettings(const QSharedPointer<Totp::Settings> settings);
QString writeSettings(const QSharedPointer<Totp::Settings> settings, const QString& title = {},
const QString& username = {}, bool forceOtp = false);
QString generateTotp(const QSharedPointer<Totp::Settings> settings, const quint64 time = 0ull);