Merge branch 'release/2.3.0' into develop

This commit is contained in:
Janek Bevendorff 2018-02-04 23:06:23 +01:00
commit a5745eee5d
23 changed files with 1463 additions and 819 deletions

View File

@ -132,6 +132,7 @@ set(keepassx_SOURCES
gui/UnlockDatabaseWidget.cpp
gui/UnlockDatabaseDialog.cpp
gui/WelcomeWidget.cpp
gui/widgets/ElidedLabel.cpp
gui/csvImport/CsvImportWidget.cpp
gui/csvImport/CsvImportWizard.cpp
gui/csvImport/CsvParserModel.cpp

View File

@ -188,10 +188,10 @@ QString BrowserService::storeKey(const QString& key)
"If you would like to allow it access to your KeePassXC database,\n"
"give it a unique name to identify and accept it."));
keyDialog.setOkButtonText(tr("Save and allow access"));
keyDialog.setWindowFlags(keyDialog.windowFlags() | Qt::WindowStaysOnTopHint);
keyDialog.show();
keyDialog.activateWindow();
keyDialog.raise();
keyDialog.setWindowFlags(keyDialog.windowFlags() | Qt::WindowStaysOnTopHint);
auto ok = keyDialog.exec();
id = keyDialog.textValue();

View File

@ -22,6 +22,7 @@
#include <QDir>
#include <QSettings>
#include <QTemporaryFile>
#include <QStandardPaths>
Config* Config::m_instance(nullptr);

View File

@ -48,7 +48,7 @@ bool EntryAttributes::hasKey(const QString& key) const
return m_attributes.contains(key);
}
QList<QString> EntryAttributes::customKeys()
QList<QString> EntryAttributes::customKeys() const
{
QList<QString> customKeys;
const QList<QString> keyList = keys();

View File

@ -33,7 +33,7 @@ public:
explicit EntryAttributes(QObject* parent = nullptr);
QList<QString> keys() const;
bool hasKey(const QString& key) const;
QList<QString> customKeys();
QList<QString> customKeys() const;
QString value(const QString& key) const;
bool contains(const QString& key) const;
bool containsValue(const QString& value) const;

View File

@ -439,11 +439,11 @@ void Group::setParent(Database* db)
QObject::setParent(db);
}
QStringList Group::hierarchy()
QStringList Group::hierarchy() const
{
QStringList hierarchy;
Group* group = this;
Group* parent = m_parent;
const Group* group = this;
const Group* parent = m_parent;
hierarchy.prepend(group->name());
while (parent) {

View File

@ -117,7 +117,7 @@ public:
Group* parentGroup();
const Group* parentGroup() const;
void setParent(Group* parent, int index = -1);
QStringList hierarchy();
QStringList hierarchy() const;
Database* database();
const Database* database() const;

View File

@ -111,9 +111,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
"border-radius: 5px;");
m_detailsView = new DetailsWidget(this);
connect(m_detailsView, &DetailsWidget::errorOccurred, this, [this](const QString& error) {
showMessage(error, MessageWidget::MessageType::Error);
});
m_detailsView->hide();
connect(this, SIGNAL(pressedEntry(Entry*)), m_detailsView, SLOT(setEntry(Entry*)));
connect(this, SIGNAL(pressedGroup(Group*)), m_detailsView, SLOT(setGroup(Group*)));
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)),
m_detailsView, SLOT(setDatabaseMode(DatabaseWidget::Mode)));
connect(m_detailsView, SIGNAL(errorOccurred(QString)), this, SLOT(showErrorMessage(QString)));
QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget);
vLayout->setMargin(0);
@ -1488,6 +1492,11 @@ void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType
m_messageWidget->showMessage(text, type, autoHideTimeout);
}
void DatabaseWidget::showErrorMessage(const QString& errorMessage)
{
showMessage(errorMessage, MessageWidget::MessageType::Error);
}
void DatabaseWidget::hideMessage()
{
if (m_messageWidget->isVisible()) {

View File

@ -178,6 +178,7 @@ public slots:
void showMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton = true,
int autoHideTimeout = MessageWidget::DefaultAutoHideTimeout);
void showErrorMessage(const QString& errorMessage);
void hideMessage();
private slots:

View File

@ -28,153 +28,189 @@
#include "gui/Clipboard.h"
#include "entry/EntryAttachmentsModel.h"
namespace {
constexpr int GeneralTabIndex = 0;
}
DetailsWidget::DetailsWidget(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::DetailsWidget())
, m_locked(false)
, m_currentEntry(nullptr)
, m_currentGroup(nullptr)
, m_timer(nullptr)
, m_attributesTabWidget(nullptr)
, m_attachmentsTabWidget(nullptr)
, m_autotypeTabWidget(nullptr)
, m_step(0)
, m_totpTimer(nullptr)
, m_selectedTabEntry(0)
, m_selectedTabGroup(0)
{
m_ui->setupUi(this);
connect(parent, SIGNAL(pressedEntry(Entry*)), SLOT(getSelectedEntry(Entry*)));
connect(parent, SIGNAL(pressedGroup(Group*)), SLOT(getSelectedGroup(Group*)));
connect(parent, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), SLOT(setDatabaseMode(DatabaseWidget::Mode)));
// Entry
m_ui->entryTotpButton->setIcon(filePath()->icon("actions", "chronometer"));
m_ui->entryCloseButton->setIcon(filePath()->icon("actions", "dialog-close"));
m_ui->totpButton->setIcon(filePath()->icon("actions", "chronometer"));
m_ui->closeButton->setIcon(filePath()->icon("actions", "dialog-close"));
m_ui->entryAttachmentsWidget->setReadOnly(true);
m_ui->entryAttachmentsWidget->setButtonsVisible(false);
connect(m_ui->totpButton, SIGNAL(toggled(bool)), SLOT(showTotp(bool)));
connect(m_ui->closeButton, SIGNAL(toggled(bool)), SLOT(hideDetails()));
connect(m_ui->tabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndex(int)));
connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool)));
connect(m_ui->entryCloseButton, SIGNAL(toggled(bool)), SLOT(hide()));
connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection);
m_ui->attachmentsWidget->setReadOnly(true);
m_ui->attachmentsWidget->setButtonsVisible(false);
m_attributesTabWidget = m_ui->tabWidget->widget(AttributesTab);
m_attachmentsTabWidget = m_ui->tabWidget->widget(AttachmentsTab);
m_autotypeTabWidget = m_ui->tabWidget->widget(AutotypeTab);
this->hide();
// Group
m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close"));
connect(m_ui->groupCloseButton, SIGNAL(toggled(bool)), SLOT(hide()));
connect(m_ui->groupTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection);
}
DetailsWidget::~DetailsWidget()
{
if (m_timer) {
delete m_timer;
}
deleteTotpTimer();
}
void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
void DetailsWidget::setEntry(Entry* selectedEntry)
{
if (!selectedEntry) {
hideDetails();
hide();
return;
}
m_currentEntry = selectedEntry;
if (!config()->get("GUI/HideDetailsView").toBool()) {
this->show();
updateEntryHeaderLine();
updateEntryTotp();
updateEntryGeneralTab();
updateEntryNotesTab();
updateEntryAttributesTab();
updateEntryAttachmentsTab();
updateEntryAutotypeTab();
setVisible(!config()->get("GUI/HideDetailsView").toBool());
m_ui->stackedWidget->setCurrentWidget(m_ui->pageEntry);
const int tabIndex = m_ui->entryTabWidget->isTabEnabled(m_selectedTabEntry) ? m_selectedTabEntry
: GeneralTabIndex;
Q_ASSERT(m_ui->entryTabWidget->isTabEnabled(GeneralTabIndex));
m_ui->entryTabWidget->setCurrentIndex(tabIndex);
}
void DetailsWidget::setGroup(Group* selectedGroup)
{
if (!selectedGroup) {
hide();
return;
}
m_ui->stackedWidget->setCurrentIndex(EntryPreview);
m_currentGroup = selectedGroup;
updateGroupHeaderLine();
updateGroupGeneralTab();
updateGroupNotesTab();
if (m_ui->tabWidget->count() < 5) {
m_ui->tabWidget->insertTab(static_cast<int>(AttributesTab), m_attributesTabWidget, tr("Attributes"));
m_ui->tabWidget->insertTab(static_cast<int>(AttachmentsTab), m_attachmentsTabWidget, tr("Attachments"));
m_ui->tabWidget->insertTab(static_cast<int>(AutotypeTab), m_autotypeTabWidget, tr("Autotype"));
setVisible(!config()->get("GUI/HideDetailsView").toBool());
m_ui->stackedWidget->setCurrentWidget(m_ui->pageGroup);
const int tabIndex = m_ui->groupTabWidget->isTabEnabled(m_selectedTabGroup) ? m_selectedTabGroup
: GeneralTabIndex;
Q_ASSERT(m_ui->groupTabWidget->isTabEnabled(GeneralTabIndex));
m_ui->groupTabWidget->setCurrentIndex(tabIndex);
}
void DetailsWidget::setDatabaseMode(DatabaseWidget::Mode mode)
{
m_locked = mode == DatabaseWidget::LockedMode;
if (m_locked) {
return;
}
m_ui->tabWidget->setTabEnabled(AttributesTab, false);
m_ui->tabWidget->setTabEnabled(NotesTab, false);
m_ui->tabWidget->setTabEnabled(AutotypeTab, false);
m_ui->totpButton->hide();
m_ui->totpWidget->hide();
m_ui->totpButton->setChecked(false);
auto icon = m_currentEntry->iconPixmap();
if (icon.width() > 16 || icon.height() > 16) {
icon = icon.scaled(16, 16);
}
m_ui->entryIcon->setPixmap(icon);
QString title = QString(" / ");
Group* entry_group = m_currentEntry->group();
if (entry_group) {
QStringList hierarchy = entry_group->hierarchy();
hierarchy.removeFirst();
title += hierarchy.join(" / ");
if (hierarchy.size() > 0) {
title += " / ";
if (mode == DatabaseWidget::ViewMode) {
if (m_ui->stackedWidget->currentWidget() == m_ui->pageGroup) {
setGroup(m_currentGroup);
} else {
setEntry(m_currentEntry);
}
}
title.append(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->title()));
m_ui->titleLabel->setText(title);
}
m_ui->usernameLabel->setText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->username()));
void DetailsWidget::updateEntryHeaderLine()
{
Q_ASSERT(m_currentEntry);
const QString title = m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->title());
m_ui->entryTitleLabel->setText(hierarchy(m_currentEntry->group(), title));
m_ui->entryIcon->setPixmap(preparePixmap(m_currentEntry->iconPixmap(), 16));
}
void DetailsWidget::updateEntryTotp()
{
Q_ASSERT(m_currentEntry);
const bool hasTotp = m_currentEntry->hasTotp();
m_ui->entryTotpButton->setVisible(hasTotp);
m_ui->entryTotpWidget->hide();
m_ui->entryTotpButton->setChecked(false);
if (hasTotp) {
deleteTotpTimer();
m_totpTimer = new QTimer(m_currentEntry);
connect(m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel()));
m_totpTimer->start(1000);
m_step = m_currentEntry->totpStep();
updateTotpLabel();
} else {
m_ui->entryTotpLabel->clear();
stopTotpTimer();
}
}
void DetailsWidget::updateEntryGeneralTab()
{
Q_ASSERT(m_currentEntry);
m_ui->entryUsernameLabel->setText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->username()));
if (!config()->get("security/hidepassworddetails").toBool()) {
m_ui->passwordLabel->setText(
shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())));
m_ui->passwordLabel->setToolTip(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()));
const QString password = m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password());
m_ui->entryPasswordLabel->setRawText(password);
m_ui->entryPasswordLabel->setToolTip(password);
} else {
m_ui->passwordLabel->setText(QString("\u25cf").repeated(6));
m_ui->entryPasswordLabel->setRawText(QString("\u25cf").repeated(6));
m_ui->entryPasswordLabel->setToolTip({});
}
QString url = m_currentEntry->webUrl();
const QString url = m_currentEntry->webUrl();
if (!url.isEmpty()) {
// URL is well formed and can be opened in a browser
// create a new display url that masks password placeholders
// the actual link will use the password
url = QString("<a href=\"%1\">%2</a>").arg(url).arg(shortUrl(m_currentEntry->displayUrl()));
m_ui->urlLabel->setOpenExternalLinks(true);
m_ui->entryUrlLabel->setRawText(m_currentEntry->displayUrl());
m_ui->entryUrlLabel->setUrl(url);
} else {
// Fallback to the raw url string
url = shortUrl(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->url()));
m_ui->urlLabel->setOpenExternalLinks(false);
}
m_ui->urlLabel->setText(url);
TimeInfo entryTime = m_currentEntry->timeInfo();
if (entryTime.expires()) {
m_ui->expirationLabel->setText(entryTime.expiryTime().toString(Qt::DefaultLocaleShortDate));
} else {
m_ui->expirationLabel->setText(tr("Never"));
m_ui->entryUrlLabel->setRawText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->url()));
m_ui->entryUrlLabel->setUrl({});
}
if (m_currentEntry->hasTotp()) {
m_step = m_currentEntry->totpStep();
const TimeInfo entryTime = m_currentEntry->timeInfo();
const QString expires = entryTime.expires() ? entryTime.expiryTime().toString(Qt::DefaultLocaleShortDate)
: tr("Never");
m_ui->entryExpirationLabel->setText(expires);
}
if (m_timer) {
delete m_timer;
}
m_timer = new QTimer(selectedEntry);
connect(m_timer, SIGNAL(timeout()), this, SLOT(updateTotp()));
updateTotp();
m_timer->start(m_step * 1000);
m_ui->totpButton->show();
}
void DetailsWidget::updateEntryNotesTab()
{
Q_ASSERT(m_currentEntry);
const QString notes = m_currentEntry->notes();
setTabEnabled(m_ui->entryTabWidget, m_ui->entryNotesTab, !notes.isEmpty());
m_ui->entryNotesEdit->setText(m_currentEntry->resolveMultiplePlaceholders(notes));
}
QString notes = m_currentEntry->notes();
if (!notes.isEmpty()) {
m_ui->tabWidget->setTabEnabled(NotesTab, true);
m_ui->notesEdit->setText(m_currentEntry->resolveMultiplePlaceholders(notes));
}
QStringList customAttributes = m_currentEntry->attributes()->customKeys();
if (customAttributes.size() > 0) {
m_ui->tabWidget->setTabEnabled(AttributesTab, true);
m_ui->attributesEdit->clear();
QString attributesText = QString();
void DetailsWidget::updateEntryAttributesTab()
{
Q_ASSERT(m_currentEntry);
m_ui->entryAttributesEdit->clear();
const EntryAttributes* attributes = m_currentEntry->attributes();
const QStringList customAttributes = attributes->customKeys();
const bool haveAttributes = customAttributes.size() > 0;
setTabEnabled(m_ui->entryTabWidget, m_ui->entryAttributesTab, haveAttributes);
if (haveAttributes) {
QString attributesText;
for (const QString& key : customAttributes) {
QString value = m_currentEntry->attributes()->value(key);
if (m_currentEntry->attributes()->isProtected(key)) {
@ -182,174 +218,118 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
}
attributesText.append(QString("<b>%1</b>: %2<br/>").arg(key, value));
}
m_ui->attributesEdit->setText(attributesText);
m_ui->entryAttributesEdit->setText(attributesText);
}
}
void DetailsWidget::updateEntryAttachmentsTab()
{
Q_ASSERT(m_currentEntry);
const bool hasAttachments = !m_currentEntry->attachments()->isEmpty();
m_ui->tabWidget->setTabEnabled(AttachmentsTab, hasAttachments);
m_ui->attachmentsWidget->setEntryAttachments(m_currentEntry->attachments());
setTabEnabled(m_ui->entryTabWidget, m_ui->entryAttachmentsTab, hasAttachments);
m_ui->entryAttachmentsWidget->setEntryAttachments(m_currentEntry->attachments());
}
m_ui->autotypeTree->clear();
AutoTypeAssociations* autotypeAssociations = m_currentEntry->autoTypeAssociations();
void DetailsWidget::updateEntryAutotypeTab()
{
Q_ASSERT(m_currentEntry);
m_ui->entryAutotypeTree->clear();
QList<QTreeWidgetItem*> items;
for (auto assoc : autotypeAssociations->getAll()) {
QStringList association = QStringList() << assoc.window << assoc.sequence;
if (association.at(1).isEmpty()) {
association.replace(1, m_currentEntry->effectiveAutoTypeSequence());
}
items.append(new QTreeWidgetItem(m_ui->autotypeTree, association));
}
if (items.count() > 0) {
m_ui->autotypeTree->addTopLevelItems(items);
m_ui->tabWidget->setTabEnabled(AutotypeTab, true);
const AutoTypeAssociations* autotypeAssociations = m_currentEntry->autoTypeAssociations();
const auto associations = autotypeAssociations->getAll();
for (const auto& assoc : associations) {
const QString sequence = assoc.sequence.isEmpty() ? m_currentEntry->effectiveAutoTypeSequence()
: assoc.sequence;
items.append(new QTreeWidgetItem(m_ui->entryAutotypeTree, {assoc.window, sequence}));
}
if (m_ui->tabWidget->isTabEnabled(m_selectedTabEntry)) {
m_ui->tabWidget->setCurrentIndex(m_selectedTabEntry);
m_ui->entryAutotypeTree->addTopLevelItems(items);
setTabEnabled(m_ui->entryTabWidget, m_ui->entryAutotypeTab, !items.isEmpty());
}
void DetailsWidget::updateGroupHeaderLine()
{
Q_ASSERT(m_currentGroup);
m_ui->groupTitleLabel->setText(hierarchy(m_currentGroup, {}));
m_ui->groupIcon->setPixmap(preparePixmap(m_currentGroup->iconPixmap(), 32));
}
void DetailsWidget::updateGroupGeneralTab()
{
Q_ASSERT(m_currentGroup);
const QString searchingText = m_currentGroup->resolveSearchingEnabled() ? tr("Enabled") : tr("Disabled");
m_ui->groupSearchingLabel->setText(searchingText);
const QString autotypeText = m_currentGroup->resolveAutoTypeEnabled() ? tr("Enabled") : tr("Disabled");
m_ui->groupAutotypeLabel->setText(autotypeText);
const TimeInfo groupTime = m_currentGroup->timeInfo();
const QString expiresText = groupTime.expires() ? groupTime.expiryTime().toString(Qt::DefaultLocaleShortDate)
: tr("Never");
m_ui->groupExpirationLabel->setText(expiresText);
}
void DetailsWidget::updateGroupNotesTab()
{
Q_ASSERT(m_currentGroup);
const QString notes = m_currentGroup->notes();
setTabEnabled(m_ui->groupTabWidget, m_ui->groupNotesTab, !notes.isEmpty());
m_ui->groupNotesEdit->setText(notes);
}
void DetailsWidget::stopTotpTimer()
{
if (m_totpTimer) {
m_totpTimer->stop();
}
}
void DetailsWidget::getSelectedGroup(Group* selectedGroup)
void DetailsWidget::deleteTotpTimer()
{
if (!selectedGroup) {
hideDetails();
return;
if (m_totpTimer) {
delete m_totpTimer;
}
}
m_currentGroup = selectedGroup;
if (!config()->get("GUI/HideDetailsView").toBool()) {
this->show();
void DetailsWidget::updateTotpLabel()
{
if (!m_locked && m_currentEntry) {
const QString totpCode = m_currentEntry->totp();
const QString firstHalf = totpCode.left(totpCode.size() / 2);
const QString secondHalf = totpCode.mid(totpCode.size() / 2);
m_ui->entryTotpLabel->setText(firstHalf + " " + secondHalf);
} else {
m_ui->entryTotpLabel->clear();
stopTotpTimer();
}
}
m_ui->stackedWidget->setCurrentIndex(GroupPreview);
void DetailsWidget::updateTabIndexes()
{
m_selectedTabEntry = m_ui->entryTabWidget->currentIndex();
m_selectedTabGroup = m_ui->groupTabWidget->currentIndex();
}
if (m_ui->tabWidget->count() > 2) {
m_ui->tabWidget->removeTab(AutotypeTab);
m_ui->tabWidget->removeTab(AttachmentsTab);
m_ui->tabWidget->removeTab(AttributesTab);
void DetailsWidget::setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled)
{
const int tabIndex = tabWidget->indexOf(widget);
Q_ASSERT(tabIndex != -1);
tabWidget->setTabEnabled(tabIndex, enabled);
}
QPixmap DetailsWidget::preparePixmap(const QPixmap& pixmap, int size)
{
if (pixmap.width() > size || pixmap.height() > size) {
return pixmap.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
return pixmap;
}
m_ui->tabWidget->setTabEnabled(GroupNotesTab, false);
m_ui->totpButton->hide();
m_ui->totpWidget->hide();
auto icon = m_currentGroup->iconPixmap();
if (icon.width() > 32 || icon.height() > 32) {
icon = icon.scaled(32, 32);
}
m_ui->entryIcon->setPixmap(icon);
QString title = " / ";
QStringList hierarchy = m_currentGroup->hierarchy();
QString DetailsWidget::hierarchy(const Group* group, const QString& title)
{
const QString separator(" / ");
QStringList hierarchy = group->hierarchy();
hierarchy.removeFirst();
title += hierarchy.join(" / ");
if (hierarchy.size() > 0) {
title += " / ";
}
m_ui->titleLabel->setText(title);
QString notes = m_currentGroup->notes();
if (!notes.isEmpty()) {
m_ui->tabWidget->setTabEnabled(GroupNotesTab, true);
m_ui->notesEdit->setText(notes);
}
QString searching = tr("Disabled");
if (m_currentGroup->resolveSearchingEnabled()) {
searching = tr("Enabled");
}
m_ui->searchingLabel->setText(searching);
QString autotype = tr("Disabled");
if (m_currentGroup->resolveAutoTypeEnabled()) {
autotype = tr("Enabled");
}
m_ui->autotypeLabel->setText(autotype);
TimeInfo groupTime = m_currentGroup->timeInfo();
if (groupTime.expires()) {
m_ui->groupExpirationLabel->setText(groupTime.expiryTime().toString(Qt::DefaultLocaleShortDate));
} else {
m_ui->groupExpirationLabel->setText(tr("Never"));
}
if (m_ui->tabWidget->isTabEnabled(m_selectedTabGroup)) {
m_ui->tabWidget->setCurrentIndex(m_selectedTabGroup);
}
}
void DetailsWidget::updateTotp()
{
if (!m_locked) {
QString totpCode = m_currentEntry->totp();
QString firstHalf = totpCode.left(totpCode.size() / 2);
QString secondHalf = totpCode.mid(totpCode.size() / 2);
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
} else if (m_timer) {
m_timer->stop();
}
}
void DetailsWidget::showTotp(bool visible)
{
if (visible) {
m_ui->totpWidget->show();
} else {
m_ui->totpWidget->hide();
}
}
QString DetailsWidget::shortUrl(QString url)
{
QString newurl = "";
if (url.length() > 60) {
newurl.append(url.left(20));
newurl.append("");
newurl.append(url.right(20));
return newurl;
}
return url;
}
QString DetailsWidget::shortPassword(QString password)
{
QString newpassword = "";
if (password.length() > 60) {
newpassword.append(password.left(50));
newpassword.append("");
return newpassword;
}
return password;
}
void DetailsWidget::hideDetails()
{
this->hide();
}
void DetailsWidget::setDatabaseMode(DatabaseWidget::Mode mode)
{
m_locked = false;
if (mode == DatabaseWidget::LockedMode) {
m_locked = true;
return;
}
if (mode == DatabaseWidget::ViewMode) {
if (m_ui->stackedWidget->currentIndex() == GroupPreview) {
getSelectedGroup(m_currentGroup);
} else {
getSelectedEntry(m_currentEntry);
}
}
}
void DetailsWidget::updateTabIndex(int index)
{
if (m_ui->stackedWidget->currentIndex() == GroupPreview) {
m_selectedTabGroup = index;
} else {
m_selectedTabEntry = index;
}
hierarchy.append(title);
return QString("%1%2").arg(separator, hierarchy.join(separator));
}

View File

@ -19,8 +19,8 @@
#define KEEPASSX_DETAILSWIDGET_H
#include "gui/DatabaseWidget.h"
#include <QWidget>
#include <QTimer>
namespace Ui {
class DetailsWidget;
@ -34,48 +34,46 @@ public:
explicit DetailsWidget(QWidget* parent = nullptr);
~DetailsWidget();
enum StackedWidgetIndex
{
EntryPreview = 0,
GroupPreview = 1,
};
enum TabWidgetIndex
{
GeneralTab = 0,
AttributesTab = 1,
GroupNotesTab = 1,
AttachmentsTab = 2,
NotesTab = 3,
AutotypeTab = 4,
};
public slots:
void setEntry(Entry* selectedEntry);
void setGroup(Group* selectedGroup);
void setDatabaseMode(DatabaseWidget::Mode mode);
signals:
void errorOccurred(const QString& error);
private slots:
void getSelectedEntry(Entry* selectedEntry);
void getSelectedGroup(Group* selectedGroup);
void showTotp(bool visible);
void updateTotp();
void hideDetails();
void setDatabaseMode(DatabaseWidget::Mode mode);
void updateTabIndex(int index);
void updateEntryHeaderLine();
void updateEntryTotp();
void updateEntryGeneralTab();
void updateEntryNotesTab();
void updateEntryAttributesTab();
void updateEntryAttachmentsTab();
void updateEntryAutotypeTab();
void updateGroupHeaderLine();
void updateGroupGeneralTab();
void updateGroupNotesTab();
void stopTotpTimer();
void deleteTotpTimer();
void updateTotpLabel();
void updateTabIndexes();
private:
void setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled);
static QPixmap preparePixmap(const QPixmap& pixmap, int size);
static QString hierarchy(const Group* group, const QString& title);
const QScopedPointer<Ui::DetailsWidget> m_ui;
bool m_locked;
Entry* m_currentEntry;
Group* m_currentGroup;
quint8 m_step;
QPointer<QTimer> m_timer = nullptr;
QWidget* m_attributesTabWidget;
QWidget* m_attachmentsTabWidget;
QWidget* m_autotypeTabWidget;
QPointer<QTimer> m_totpTimer;
quint8 m_selectedTabEntry;
quint8 m_selectedTabGroup;
QString shortUrl(QString url);
QString shortPassword(QString password);
};
#endif // KEEPASSX_DETAILSWIDGET_H

File diff suppressed because it is too large Load Diff

View File

@ -444,6 +444,10 @@ bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key)
return false;
}
if (key.comment().isEmpty()) {
key.setComment(m_entry->username());
}
return true;
}

View File

@ -0,0 +1,116 @@
/*
* 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 "ElidedLabel.h"
#include <QResizeEvent>
#include <QDebug>
namespace {
const QString htmlLinkTemplate("<a href=\"%1\">%2</a>");
}
ElidedLabel::ElidedLabel(QWidget* parent, Qt::WindowFlags f)
: QLabel(parent, f)
, m_elideMode(Qt::ElideMiddle)
{
connect(this, SIGNAL(elideModeChanged(Qt::TextElideMode)), this, SLOT(updateElidedText()));
connect(this, SIGNAL(rawTextChanged(QString)), this, SLOT(updateElidedText()));
connect(this, SIGNAL(urlChanged(QString)), this, SLOT(updateElidedText()));
}
ElidedLabel::ElidedLabel(const QString& text, QWidget* parent, Qt::WindowFlags f)
: ElidedLabel(parent, f)
{
setText(text);
}
Qt::TextElideMode ElidedLabel::elideMode() const
{
return m_elideMode;
}
QString ElidedLabel::rawText() const
{
return m_rawText;
}
QString ElidedLabel::url() const
{
return m_url;
}
void ElidedLabel::setElideMode(Qt::TextElideMode elideMode)
{
if (m_elideMode == elideMode)
return;
if (m_elideMode != Qt::ElideNone) {
setWordWrap(false);
}
m_elideMode = elideMode;
emit elideModeChanged(m_elideMode);
}
void ElidedLabel::setRawText(const QString& elidedText)
{
if (m_rawText == elidedText)
return;
m_rawText = elidedText;
emit rawTextChanged(m_rawText);
}
void ElidedLabel::setUrl(const QString& url)
{
if (m_url == url)
return;
m_url = url;
emit urlChanged(m_url);
}
void ElidedLabel::clear()
{
setRawText(QString());
setElideMode(Qt::ElideMiddle);
setUrl(QString());
QLabel::clear();
}
void ElidedLabel::updateElidedText()
{
if (m_rawText.isEmpty()) {
QLabel::clear();
return;
}
QString displayText = m_rawText;
if (m_elideMode != Qt::ElideNone) {
const QFontMetrics metrix(font());
displayText = metrix.elidedText(m_rawText, m_elideMode, width() - 2);
}
setText(m_url.isEmpty() ? displayText : htmlLinkTemplate.arg(m_url, displayText));
setOpenExternalLinks(!m_url.isEmpty());
}
void ElidedLabel::resizeEvent(QResizeEvent* event)
{
updateElidedText();
QLabel::resizeEvent(event);
}

View File

@ -0,0 +1,61 @@
/*
* 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_ELIDEDLABEL_H
#define KEEPASSX_ELIDEDLABEL_H
#include <QLabel>
class QResizeEvent;
class ElidedLabel : public QLabel
{
Q_OBJECT
Q_PROPERTY(Qt::TextElideMode elideMode READ elideMode WRITE setElideMode NOTIFY elideModeChanged)
Q_PROPERTY(QString rawText READ rawText WRITE setRawText NOTIFY rawTextChanged)
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
public:
explicit ElidedLabel(QWidget *parent=nullptr, Qt::WindowFlags f=Qt::WindowFlags());
explicit ElidedLabel(const QString &text, QWidget *parent=nullptr, Qt::WindowFlags f=Qt::WindowFlags());
Qt::TextElideMode elideMode() const;
QString rawText() const;
QString url() const;
public slots:
void setElideMode(Qt::TextElideMode elideMode);
void setRawText(const QString& rawText);
void setUrl(const QString& url);
void clear();
signals:
void elideModeChanged(Qt::TextElideMode elideMode);
void rawTextChanged(QString rawText);
void urlChanged(QString url);
private slots:
void updateElidedText();
private:
void resizeEvent(QResizeEvent* event);
Qt::TextElideMode m_elideMode;
QString m_rawText;
QString m_url;
};
#endif // KEEPASSX_ELIDEDLABEL_H

186
src/sshagent/ASN1Key.cpp Normal file
View File

@ -0,0 +1,186 @@
/*
* Copyright (C) 2018 Toni Spets <toni.spets@iki.fi>
* 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 "ASN1Key.h"
#include <gcrypt.h>
namespace {
constexpr quint8 TAG_INT = 0x02;
constexpr quint8 TAG_SEQUENCE = 0x30;
constexpr quint8 KEY_ZERO = 0x0;
bool nextTag(BinaryStream& stream, quint8& tag, quint32& len)
{
stream.read(tag);
quint8 lenByte;
stream.read(lenByte);
if (lenByte & 0x80) {
quint32 bytes = lenByte & ~0x80;
if (bytes == 1) {
stream.read(lenByte);
len = lenByte;
} else if (bytes == 2) {
quint16 lenShort;
stream.read(lenShort);
len = lenShort;
} else if (bytes == 4) {
stream.read(len);
} else {
return false;
}
} else {
len = lenByte;
}
return true;
}
bool parseHeader(BinaryStream& stream, quint8 wantedType)
{
quint8 tag;
quint32 len;
nextTag(stream, tag, len);
if (tag != TAG_SEQUENCE) {
return false;
}
nextTag(stream, tag, len);
if (tag != TAG_INT || len != 1) {
return false;
}
quint8 keyType;
stream.read(keyType);
return (keyType == wantedType);
}
bool readInt(BinaryStream& stream, QByteArray& target)
{
quint8 tag;
quint32 len;
nextTag(stream, tag, len);
if (tag != TAG_INT) {
return false;
}
target.resize(len);
stream.read(target);
return true;
}
QByteArray calculateIqmp(QByteArray& bap, QByteArray& baq)
{
gcry_mpi_t u, p, q;
QByteArray iqmp_hex;
u = gcry_mpi_snew(bap.length() * 8);
gcry_mpi_scan(&p, GCRYMPI_FMT_HEX, bap.toHex().data(), 0, nullptr);
gcry_mpi_scan(&q, GCRYMPI_FMT_HEX, baq.toHex().data(), 0, nullptr);
mpi_invm(u, q, p);
iqmp_hex.resize((bap.length() + 1) * 2);
gcry_mpi_print(GCRYMPI_FMT_HEX, reinterpret_cast<unsigned char*>(iqmp_hex.data()), iqmp_hex.length(), nullptr, u);
gcry_mpi_release(u);
gcry_mpi_release(p);
gcry_mpi_release(q);
return QByteArray::fromHex(iqmp_hex);
}
}
bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key)
{
BinaryStream stream(&ba);
if (!parseHeader(stream, KEY_ZERO)) {
return false;
}
QByteArray p,q,g,y,x;
readInt(stream, p);
readInt(stream, q);
readInt(stream, g);
readInt(stream, y);
readInt(stream, x);
QList<QByteArray> publicData;
publicData.append(p);
publicData.append(q);
publicData.append(g);
publicData.append(y);
QList<QByteArray> privateData;
privateData.append(p);
privateData.append(q);
privateData.append(g);
privateData.append(y);
privateData.append(x);
key.setType("ssh-dss");
key.setPublicData(publicData);
key.setPrivateData(privateData);
key.setComment("");
return true;
}
bool ASN1Key::parseRSA(QByteArray& ba, OpenSSHKey& key)
{
BinaryStream stream(&ba);
if (!parseHeader(stream, KEY_ZERO)) {
return false;
}
QByteArray n,e,d,p,q,dp,dq,qinv;
readInt(stream, n);
readInt(stream, e);
readInt(stream, d);
readInt(stream, p);
readInt(stream, q);
readInt(stream, dp);
readInt(stream, dq);
readInt(stream, qinv);
QList<QByteArray> publicData;
publicData.append(e);
publicData.append(n);
QList<QByteArray> privateData;
privateData.append(n);
privateData.append(e);
privateData.append(d);
privateData.append(calculateIqmp(p, q));
privateData.append(p);
privateData.append(q);
key.setType("ssh-rsa");
key.setPublicData(publicData);
key.setPrivateData(privateData);
key.setComment("");
return true;
}

31
src/sshagent/ASN1Key.h Normal file
View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2018 Toni Spets <toni.spets@iki.fi>
* 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 ASN1KEY_H
#define ASN1KEY_H
#include "OpenSSHKey.h"
#include <QtCore>
namespace ASN1Key
{
bool parseDSA(QByteArray& ba, OpenSSHKey& key);
bool parseRSA(QByteArray& ba, OpenSSHKey& key);
}
#endif // ASN1KEY_H

View File

@ -9,6 +9,7 @@ if(WITH_XC_SSHAGENT)
BinaryStream.cpp
KeeAgentSettings.cpp
OpenSSHKey.cpp
ASN1Key.cpp
SSHAgent.cpp
)

View File

@ -17,11 +17,16 @@
*/
#include "OpenSSHKey.h"
#include "ASN1Key.h"
#include <QRegularExpression>
#include <QStringList>
#include <QCryptographicHash>
#include "crypto/SymmetricCipher.h"
const QString OpenSSHKey::TYPE_DSA = "DSA PRIVATE KEY";
const QString OpenSSHKey::TYPE_RSA = "RSA PRIVATE KEY";
const QString OpenSSHKey::TYPE_OPENSSH = "OPENSSH PRIVATE KEY";
// bcrypt_pbkdf.cpp
int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds);
@ -34,6 +39,7 @@ OpenSSHKey::OpenSSHKey(QObject *parent)
, m_rawPrivateData(QByteArray())
, m_publicData(QList<QByteArray>())
, m_privateData(QList<QByteArray>())
, m_privateType(QString())
, m_comment(QString())
, m_error(QString())
{
@ -178,14 +184,31 @@ bool OpenSSHKey::parsePEM(const QByteArray& in, QByteArray& out)
return false;
}
if (beginMatch.captured(1) != "OPENSSH PRIVATE KEY") {
m_error = tr("This is not an OpenSSH key, only modern keys are supported");
return false;
}
m_privateType = beginMatch.captured(1);
rows.removeFirst();
rows.removeLast();
QRegularExpression keyValueExpr = QRegularExpression("^([A-Za-z0-9-]+): (.+)$");
QMap<QString, QString> pemOptions;
do {
QRegularExpressionMatch keyValueMatch = keyValueExpr.match(rows.first());
if (!keyValueMatch.hasMatch()) {
break;
}
pemOptions.insert(keyValueMatch.captured(1), keyValueMatch.captured(2));
rows.removeFirst();
} while (!rows.isEmpty());
if (pemOptions.contains("Proc-Type")) {
m_error = tr("Encrypted keys are not yet supported");
return false;
}
out = QByteArray::fromBase64(rows.join("").toLatin1());
if (out.isEmpty()) {
@ -204,51 +227,58 @@ bool OpenSSHKey::parse(const QByteArray& in)
return false;
}
BinaryStream stream(&data);
if (m_privateType == TYPE_DSA || m_privateType == TYPE_RSA) {
m_rawPrivateData = data;
} else if (m_privateType == TYPE_OPENSSH) {
BinaryStream stream(&data);
QByteArray magic;
magic.resize(15);
QByteArray magic;
magic.resize(15);
if (!stream.read(magic)) {
m_error = tr("Key file way too small.");
return false;
}
if (QString::fromLatin1(magic) != "openssh-key-v1") {
m_error = tr("Key file magic header id invalid");
return false;
}
stream.readString(m_cipherName);
stream.readString(m_kdfName);
stream.readString(m_kdfOptions);
quint32 numberOfKeys;
stream.read(numberOfKeys);
if (numberOfKeys == 0) {
m_error = tr("Found zero keys");
return false;
}
for (quint32 i = 0; i < numberOfKeys; ++i) {
QByteArray publicKey;
if (!stream.readString(publicKey)) {
m_error = tr("Failed to read public key.");
if (!stream.read(magic)) {
m_error = tr("Key file way too small.");
return false;
}
if (i == 0) {
BinaryStream publicStream(&publicKey);
if (!readPublic(publicStream)) {
if (QString::fromLatin1(magic) != "openssh-key-v1") {
m_error = tr("Key file magic header id invalid");
return false;
}
stream.readString(m_cipherName);
stream.readString(m_kdfName);
stream.readString(m_kdfOptions);
quint32 numberOfKeys;
stream.read(numberOfKeys);
if (numberOfKeys == 0) {
m_error = tr("Found zero keys");
return false;
}
for (quint32 i = 0; i < numberOfKeys; ++i) {
QByteArray publicKey;
if (!stream.readString(publicKey)) {
m_error = tr("Failed to read public key.");
return false;
}
}
}
// padded list of keys
if (!stream.readString(m_rawPrivateData)) {
m_error = tr("Corrupted key file, reading private key failed");
if (i == 0) {
BinaryStream publicStream(&publicKey);
if (!readPublic(publicStream)) {
return false;
}
}
}
// padded list of keys
if (!stream.readString(m_rawPrivateData)) {
m_error = tr("Corrupted key file, reading private key failed");
return false;
}
} else {
m_error = tr("Unsupported key type: %s").arg(m_privateType);
return false;
}
@ -283,7 +313,7 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
} else if (m_cipherName == "aes256-ctr") {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt));
} else if (m_cipherName != "none") {
m_error = tr("Unknown cipher: ") + m_cipherName;
m_error = tr("Unknown cipher: %s").arg(m_cipherName);
return false;
}
@ -320,8 +350,13 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
ivData.setRawData(decryptKey.data() + cipher->keySize(), cipher->blockSize());
cipher->init(keyData, ivData);
if (!cipher->init(keyData, ivData)) {
m_error = cipher->errorString();
return false;
}
} else if (m_kdfName != "none") {
m_error = tr("Unknown KDF: ") + m_kdfName;
m_error = tr("Unknown KDF: %s").arg(m_kdfName);
return false;
}
@ -336,20 +371,39 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
}
}
BinaryStream keyStream(&rawPrivateData);
if (m_privateType == TYPE_DSA) {
if (!ASN1Key::parseDSA(rawPrivateData, *this)) {
m_error = tr("Reading DSA private key failed, only unencrypted keys are supported at this time");
return false;
}
quint32 checkInt1;
quint32 checkInt2;
return true;
} else if (m_privateType == TYPE_RSA) {
if (!ASN1Key::parseRSA(rawPrivateData, *this)) {
m_error = tr("Reading RSA private key failed, only unencrypted keys are supported at this time");
return false;
}
keyStream.read(checkInt1);
keyStream.read(checkInt2);
return true;
} else if (m_privateType == TYPE_OPENSSH) {
BinaryStream keyStream(&rawPrivateData);
if (checkInt1 != checkInt2) {
m_error = tr("Decryption failed, wrong passphrase?");
return false;
quint32 checkInt1;
quint32 checkInt2;
keyStream.read(checkInt1);
keyStream.read(checkInt2);
if (checkInt1 != checkInt2) {
m_error = tr("Decryption failed, wrong passphrase?");
return false;
}
return readPrivate(keyStream);
}
return readPrivate(keyStream);
m_error = tr("Unsupported key type: %s").arg(m_privateType);
return false;
}
bool OpenSSHKey::readPublic(BinaryStream& stream)
@ -371,7 +425,7 @@ bool OpenSSHKey::readPublic(BinaryStream& stream)
} else if (m_type == "ssh-ed25519") {
keyParts = 1;
} else {
m_error = tr("Unknown key type: ") + m_type;
m_error = tr("Unknown key type: %s").arg(m_type);
return false;
}
@ -408,7 +462,7 @@ bool OpenSSHKey::readPrivate(BinaryStream& stream)
} else if (m_type == "ssh-ed25519") {
keyParts = 2;
} else {
m_error = tr("Unknown key type: ") + m_type;
m_error = tr("Unknown key type: %s").arg(m_type);
return false;
}

View File

@ -55,6 +55,10 @@ public:
bool writePrivate(BinaryStream& stream);
private:
static const QString TYPE_DSA;
static const QString TYPE_RSA;
static const QString TYPE_OPENSSH;
bool parsePEM(const QByteArray& in, QByteArray& out);
QString m_type;
@ -64,6 +68,7 @@ private:
QByteArray m_rawPrivateData;
QList<QByteArray> m_publicData;
QList<QByteArray> m_privateData;
QString m_privateType;
QString m_comment;
QString m_error;
};

View File

@ -260,6 +260,10 @@ void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode)
continue;
}
if (key.comment().isEmpty()) {
key.setComment(e->username());
}
if (settings.removeAtDatabaseClose()) {
removeIdentityAtLock(key, uuid);
}

View File

@ -50,6 +50,7 @@ void TestOpenSSHKey::testParse()
QCOMPARE(key.cipherName(), QString("none"));
QCOMPARE(key.type(), QString("ssh-ed25519"));
QCOMPARE(key.comment(), QString("opensshkey-test-parse@keepassxc"));
QCOMPARE(key.fingerprint(), QString("SHA256:D1fVmA15YXzaJ5sdO9dXxo5coHL/pnNaIfCvokHzTA4"));
QByteArray publicKey, privateKey;
BinaryStream publicStream(&publicKey), privateStream(&privateKey);
@ -61,6 +62,77 @@ void TestOpenSSHKey::testParse()
QVERIFY(privateKey.length() == 154);
}
void TestOpenSSHKey::testParseDSA()
{
const QString keyString = QString(
"-----BEGIN DSA PRIVATE KEY-----\n"
"MIIBuwIBAAKBgQCudjbvSh8JxQOr2laCqZM1t4kNWBETVOXz5vgk9iw6Z5opB9/k\n"
"g4nFc1PVq7fdAIc8W/5WCAjugKcxPb9PIHfcwY2fimmiPWFK68/eHKLoCuIn2wxB\n"
"63ig2hAhx5U5aYG9QHkNCaT6VX7rc19nToSeZXlpja4x54/DaQaqOEWYsQIVAOer\n"
"UQWfccz7KXUu6+x7heGob6I3AoGAVDRFJIlL0DI/4nePIcgwgwbfgs2ojSu21g4w\n"
"dQoXvqU34XydPgPQ985XIIuiDkaomRw4yYd/Sh4ZapFcrP++iJ1V+WS6kLcWPHMq\n"
"poYwk8mq6GLbPFLEjr+n6HgX5ln15n3i4WAopNH7mEl0glY9L0rxmcN0XOpqw6Ux\n"
"ETGEfAwCgYAiOeYwblMkkTIGtVx5NvNsOlfrBYL4GqUP9oQMO5I+xLZLWQIf+7Jp\n"
"8t6mwxSBz0RHjNVQ11vZowNjq3587aLy57bVwf2lIm9KSvS6z9HoNbHgQimcBorR\n"
"J9l9RUrj7TnsZgiVw66j2r34nHRHRtggiO+qrMtw7MJc0Q7jiuTmzgIVAMXbk0T9\n"
"nBfSLWQz/L8RexU2GR4e\n"
"-----END DSA PRIVATE KEY-----\n"
);
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
QVERIFY(key.parse(keyData));
QVERIFY(!key.encrypted());
QCOMPARE(key.cipherName(), QString("none"));
QCOMPARE(key.type(), QString("ssh-dss"));
QCOMPARE(key.comment(), QString(""));
QCOMPARE(key.fingerprint(), QString("SHA256:tbbNuLN1hja8JNASDTlLOZQsbTlJDzJlz/oAGK3sX18"));
}
void TestOpenSSHKey::testParseRSA()
{
const QString keyString = QString(
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpAIBAAKCAQEAsCHtJicDPWnvHSIKbnTZaJkIB9vgE0pmLdK580JUqBuonVbB\n"
"y1QTy0ZQ7/TtqvLPgwPK88TR46OLO/QGCzo2+XxgJ85uy0xfuyUYRmSuw0drsErN\n"
"mH8vU91lSBxsGDp9LtBbgHKoR23vMWZ34IxFRc55XphrIH48ijsMaL6bXBwF/3tD\n"
"9T3lm2MpP1huyVNnIY9+GRRWCy4f9LMj/UGu/n4RtwwfpOZBBRwYkq5QkzA9lPm/\n"
"VzF3MP1rKTMkvAw+Nfb383mkmc6MRnsa6uh6iDa9aVB7naegM13UJQX/PY1Ks6pO\n"
"XDpy/MQ7iCh+HmYNq5dRmARyaNl9xIXJNhz1cQIDAQABAoIBAQCnEUc1LUQxeM5K\n"
"wANNCqE+SgoIClPdeHC7fmrLh1ttqe6ib6ybBUFRS31yXs0hnfefunVEDKlaV8K2\n"
"N52UAMAsngFHQNRvGh6kEWeZPd9Xc+N98TZbNCjcT+DGKc+Om8wqH5DrodZlCq4c\n"
"GaoT4HnE4TjWtZTH2XXrWF9I66PKFWf070R44nvyVcvaZi4pC2YmURRPuGF6K1iK\n"
"dH8zM6HHG1UGu2W6hLNn+K01IulG0Lb8eWNaNYMmtQWaxyp7I2IWkkecUs3nCuiR\n"
"byFOoomCjdh8r9yZFvwxjGUhgtkALN9GCU0Mwve+s11IB2gevruN+q9/Qejbyfdm\n"
"IlgLAeTRAoGBANRcVzW9CYeobCf+U9hKJFEOur8XO+J2mTMaELA0EjWpTJFAeIT7\n"
"KeRpCRG4/vOSklxxRF6vP1EACA4Z+5BlN+FTipHHs+bSEgqkPZiiANDH7Zot5Iqv\n"
"1q0fRyldNRZNZK7DWp08BPNVWGA/EnEuKJiURxnxBaxNXbUyMCdjxvMvAoGBANRT\n"
"utbrqS/bAa/DcHKn3V6DRqBl3TDOfvCNjiKC84a67F2uXgzLIdMktr4d1NyCZVJd\n"
"7/zVgWORLIdg1eAi6rYGoOvNV39wwga7CF+m9sBY0wAaKYCELe6L26r4aQHVCX6n\n"
"rnIgUv+4o4itmU2iP0r3wlmDC9pDRQP82vfvQPlfAoGASwhleANW/quvq2HdViq8\n"
"Mje2HBalfhrRfpDTHK8JUBSFjTzuWG42GxJRtgVbb8x2ElujAKGDCaetMO5VSGu7\n"
"Fs5hw6iAFCpdXY0yhl+XUi2R8kwM2EPQ4lKO3jqkq0ClNmqn9a5jQWcCVt9yMLNS\n"
"fLbHeI8EpiCf34ngIcrLXNkCgYEAzlcEZuKkC46xB+dNew8pMTUwSKZVm53BfPKD\n"
"44QRN6imFbBjU9mAaJnwQbfp6dWKs834cGPolyM4++MeVfB42iZ88ksesgmZdUMD\n"
"szkl6O0pOJs0I+HQZVdjRbadDZvD22MHQ3+oST1dJ3FVXz3Cdo9qPuT8esMO6f4r\n"
"qfDH2s8CgYAXC/lWWHQ//PGP0pH4oiEXisx1K0X1u0xMGgrChxBRGRiKZUwNMIvJ\n"
"TqUu7IKizK19cLHF/NBvxHYHFw+m7puNjn6T1RtRCUjRZT7Dx1VHfVosL9ih5DA8\n"
"tpbZA5KGKcvHtB5DDgT0MHwzBZnb4Q//Rhovzn+HXZPsJTTgHHy3NQ==\n"
"-----END RSA PRIVATE KEY-----\n"
);
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
QVERIFY(key.parse(keyData));
QVERIFY(!key.encrypted());
QCOMPARE(key.cipherName(), QString("none"));
QCOMPARE(key.type(), QString("ssh-rsa"));
QCOMPARE(key.comment(), QString(""));
QCOMPARE(key.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk"));
}
void TestOpenSSHKey::testDecryptAES256CBC()
{
const QString keyString = QString(

View File

@ -29,6 +29,8 @@ class TestOpenSSHKey : public QObject
private slots:
void initTestCase();
void testParse();
void testParseDSA();
void testParseRSA();
void testDecryptAES256CBC();
void testDecryptAES256CTR();
};