diff --git a/src/dialogs/exportdialog.cpp b/src/dialogs/exportdialog.cpp index 7b56264af..22782a8e7 100644 --- a/src/dialogs/exportdialog.cpp +++ b/src/dialogs/exportdialog.cpp @@ -1,199 +1,240 @@ /* Copyright (c) 2017 Intevation GmbH Kleopatra 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 of the License, or (at your option) any later version. Kleopatra 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "exportdialog.h" #include "kleopatra_debug.h" #include "view/waitwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include +#include +#if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 +# define GPGME_HAS_EXPORT_FLAGS +#endif + using namespace Kleo; class ExportWidget::Private { public: Private(ExportWidget *qq) : q(qq) {} void setupUi(); GpgME::Key key; + GpgME::Subkey subkey; QTextEdit *textEdit; WaitWidget *waitWidget; + unsigned int flags; private: ExportWidget *const q; }; void ExportWidget::Private::setupUi() { auto vlay = new QVBoxLayout(q); vlay->setContentsMargins(0, 0, 0, 0); textEdit = new QTextEdit; textEdit->setVisible(false); textEdit->setReadOnly(true); auto fixedFont = QFont(QStringLiteral("Monospace")); fixedFont.setStyleHint(QFont::TypeWriter); textEdit->setFont(fixedFont); textEdit->setReadOnly(true); vlay->addWidget(textEdit); waitWidget = new WaitWidget; waitWidget->setText(i18n("Exporting ...")); vlay->addWidget(waitWidget); } ExportWidget::ExportWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { d->setupUi(); } ExportWidget::~ExportWidget() { } static QString injectComments(const GpgME::Key &key, const QByteArray &data) { QString ret = QString::fromUtf8(data); if (key.protocol() != GpgME::OpenPGP) { return ret; } auto overView = Formatting::toolTip(key, Formatting::Fingerprint | Formatting::UserIDs | Formatting::Issuer | Formatting::Subject | Formatting::ExpiryDates | Formatting::CertificateType | Formatting::CertificateUsage); // Fixup the HTML coming from the toolTip for our own format. overView.remove(QLatin1String("")); overView.replace(QLatin1String(""), QLatin1String("\t")); overView.replace(QLatin1String(""), QLatin1String("\n")); overView.remove(QLatin1String("")); overView.remove(QLatin1String("\n
")); overView.replace(QLatin1String("<"), QLatin1String("<")); overView.replace(QLatin1String(">"), QLatin1String(">")); auto overViewLines = overView.split(QLatin1Char('\n')); // Format comments so that they fit for RFC 4880 auto comments = QStringLiteral("Comment: "); comments += overViewLines.join(QLatin1String("\nComment: ")) + QLatin1Char('\n'); ret.insert(37 /* -----BEGIN PGP PUBLIC KEY BLOCK-----\n */, comments); return ret; } void ExportWidget::exportResult(const GpgME::Error &err, const QByteArray &data) { d->waitWidget->setVisible(false); d->textEdit->setVisible(true); if (err) { /* Should not happen. But well,.. */ d->textEdit->setText(i18nc("%1 is error message", "Failed to export: '%1'", QString::fromLatin1(err.asString()))); } - d->textEdit->setText(injectComments(d->key, data)); + if (!d->flags) { + d->textEdit->setText(injectComments(d->key, data)); + } else { + d->textEdit->setText(QString::fromUtf8(data)); + } } -void ExportWidget::setKey(const GpgME::Key &key) +void ExportWidget::setKey(const GpgME::Subkey &key, unsigned int flags) +{ + d->waitWidget->setVisible(true); + d->textEdit->setVisible(false); + d->key = key.parent(); + d->subkey = key; + d->flags = flags; + + auto protocol = d->key.protocol() == GpgME::CMS ? + QGpgME::smime() : QGpgME::openpgp(); + + auto job = protocol->publicKeyExportJob(true); + + connect(job, &QGpgME::ExportJob::result, + this, &ExportWidget::exportResult); + +#ifdef GPGME_HAS_EXPORT_FLAGS + job->setExportFlags(flags); +#endif + job->start(QStringList() << QLatin1String(key.fingerprint()) + QLatin1Char('!')); +} + +void ExportWidget::setKey(const GpgME::Key &key, unsigned int flags) { d->waitWidget->setVisible(true); d->textEdit->setVisible(false); d->key = key; + d->flags = flags; auto protocol = key.protocol() == GpgME::CMS ? QGpgME::smime() : QGpgME::openpgp(); auto job = protocol->publicKeyExportJob(true); - /* New style connect does not work on Windows. */ connect(job, &QGpgME::ExportJob::result, this, &ExportWidget::exportResult); +#ifdef GPGME_HAS_EXPORT_FLAGS + job->setExportFlags(flags); +#endif job->start(QStringList() << QLatin1String(key.primaryFingerprint())); } GpgME::Key ExportWidget::key() const { return d->key; } ExportDialog::ExportDialog(QWidget *parent) : QDialog(parent), mWidget(new ExportWidget(this)) { KConfigGroup dialog(KSharedConfig::openConfig(), "ExportDialog"); const auto size = dialog.readEntry("Size", QSize(600, 800)); if (size.isValid()) { resize(size); } setWindowTitle(i18nc("@title:window", "Export")); auto l = new QVBoxLayout(this); l->addWidget(mWidget); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::pressed, this, &QDialog::accept); l->addWidget(bbox); } ExportDialog::~ExportDialog() { KConfigGroup dialog(KSharedConfig::openConfig(), "ExportDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } -void ExportDialog::setKey(const GpgME::Key &key) +void ExportDialog::setKey(const GpgME::Key &key, unsigned int flags) +{ + mWidget->setKey(key, flags); +} + +void ExportDialog::setKey(const GpgME::Subkey &key, unsigned int flags) { - mWidget->setKey(key); + mWidget->setKey(key, flags); } GpgME::Key ExportDialog::key() const { return mWidget->key(); } diff --git a/src/dialogs/exportdialog.h b/src/dialogs/exportdialog.h index e38668acb..885f7c10f 100644 --- a/src/dialogs/exportdialog.h +++ b/src/dialogs/exportdialog.h @@ -1,67 +1,70 @@ /* Copyright (c) 2017 Intevation GmbH Kleopatra 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 of the License, or (at your option) any later version. Kleopatra 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KLEO_EXPORTDIALOG_H #define KLEO_EXPORTDIALOG_H #include #include namespace GpgME { class Key; +class Subkey; class Error; } namespace Kleo { class ExportWidget : public QWidget { Q_OBJECT public: explicit ExportWidget(QWidget *parent = nullptr); ~ExportWidget(); - void setKey(const GpgME::Key &key); + void setKey(const GpgME::Key &key, unsigned int flags = 0); + void setKey(const GpgME::Subkey &key, unsigned int flags = 0); GpgME::Key key() const; private Q_SLOTS: void exportResult(const GpgME::Error &err, const QByteArray &data); private: class Private; const QScopedPointer d; }; class ExportDialog : public QDialog { Q_OBJECT public: explicit ExportDialog(QWidget *parent = nullptr); ~ExportDialog(); - void setKey(const GpgME::Key &key); + void setKey(const GpgME::Key &key, unsigned int flags = 0); + void setKey(const GpgME::Subkey &key, unsigned int flags = 0); GpgME::Key key() const; private: ExportWidget *mWidget; }; } // namespace Kleo #endif diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp index 6a503c1b7..ae516b849 100644 --- a/src/dialogs/subkeyswidget.cpp +++ b/src/dialogs/subkeyswidget.cpp @@ -1,217 +1,237 @@ /* Copyright (c) 2016 Klarälvdalens Datakonsult AB 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra 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 of the License, or (at your option) any later version. Kleopatra 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "subkeyswidget.h" #include "ui_subkeyswidget.h" #include "smartcard/readerstatus.h" #include "commands/keytocardcommand.h" #include "commands/importpaperkeycommand.h" +#include "exportdialog.h" #include +#include #include #include #include #include #include #include +#include +#if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 +# define GPGME_HAS_EXPORT_FLAGS +#endif + #include Q_DECLARE_METATYPE(GpgME::Subkey) using namespace Kleo; class SubKeysWidget::Private { public: Private(SubKeysWidget *q) : q(q) { ui.setupUi(q); ui.subkeysTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.subkeysTree, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { tableContextMenuRequested(p); }); } GpgME::Key key; Ui::SubKeysWidget ui; void tableContextMenuRequested(const QPoint &p); private: SubKeysWidget *const q; }; void SubKeysWidget::Private::tableContextMenuRequested(const QPoint &p) { auto item = ui.subkeysTree->itemAt(p); if (!item) { return; } const auto subkey = item->data(0, Qt::UserRole).value(); QMenu *menu = new QMenu(q); connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); bool hasActions = false; +#ifdef GPGME_HAS_EXPORT_FLAGS + if (subkey.parent().protocol() && subkey.canAuthenticate()) { + hasActions = true; + menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), + i18n("Export OpenSSH key"), + q, [this, subkey]() { + QScopedPointer dlg(new ExportDialog(q)); + dlg->setKey(subkey, static_cast (GpgME::Context::ExportSSH)); + dlg->exec(); + }); + } +#endif // GPGME_HAS_EXPORT_FLAGS + if (!subkey.isSecret()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-import")), i18n("Restore printed backup"), q, [this, subkey] () { auto cmd = new Kleo::Commands::ImportPaperKeyCommand(subkey.parent()); ui.subkeysTree->setEnabled(false); connect(cmd, &Kleo::Commands::ImportPaperKeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } if (subkey.isSecret() && Kleo::Commands::KeyToCardCommand::supported()) { const auto cards = SmartCard::ReaderStatus::instance()->getCards(); if (cards.size() && cards[0]->appType() == SmartCard::Card::OpenPGPApplication) { const auto card = cards[0]; if (!subkey.cardSerialNumber() || card->serialNumber() != subkey.cardSerialNumber()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("send-to-symbolic")), i18n("Transfer to smartcard"), q, [this, subkey, card]() { auto cmd = new Kleo::Commands::KeyToCardCommand(subkey, card->serialNumber()); ui.subkeysTree->setEnabled(false); connect(cmd, &Kleo::Commands::KeyToCardCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } } } if (hasActions) { menu->popup(ui.subkeysTree->viewport()->mapToGlobal(p)); } else { delete menu; } } SubKeysWidget::SubKeysWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { } SubKeysWidget::~SubKeysWidget() { } void SubKeysWidget::setKey(const GpgME::Key &key) { d->key = key; for (const auto &subkey : key.subkeys()) { auto item = new QTreeWidgetItem(); item->setData(0, Qt::DisplayRole, Formatting::prettyID(subkey.keyID())); item->setData(0, Qt::UserRole, QVariant::fromValue(subkey)); item->setData(1, Qt::DisplayRole, Kleo::Formatting::type(subkey)); item->setData(2, Qt::DisplayRole, Kleo::Formatting::creationDateString(subkey)); item->setData(3, Qt::DisplayRole, Kleo::Formatting::expirationDateString(subkey)); item->setData(4, Qt::DisplayRole, Kleo::Formatting::validityShort(subkey)); switch (subkey.publicKeyAlgorithm()) { case GpgME::Subkey::AlgoECDSA: case GpgME::Subkey::AlgoEDDSA: case GpgME::Subkey::AlgoECDH: item->setData(5, Qt::DisplayRole, QString::fromStdString(subkey.algoName())); break; default: item->setData(5, Qt::DisplayRole, QString::number(subkey.length())); } item->setData(6, Qt::DisplayRole, Kleo::Formatting::usageString(subkey)); item->setData(7, Qt::DisplayRole, subkey.keyID() == key.keyID() ? QStringLiteral("✓") : QString()); d->ui.subkeysTree->addTopLevelItem(item); } const auto subkey = key.subkey(0); if (const char *card = subkey.cardSerialNumber()) { d->ui.stored->setText(i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { d->ui.stored->setText(i18nc("stored...", "on this computer")); } d->ui.subkeysTree->resizeColumnToContents(0); } GpgME::Key SubKeysWidget::key() const { return d->key; } SubKeysDialog::SubKeysDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18nc("@title:window", "Subkeys Details")); auto l = new QVBoxLayout(this); l->addWidget(new SubKeysWidget(this)); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::clicked, this, &QDialog::accept); l->addWidget(bbox); readConfig(); } SubKeysDialog::~SubKeysDialog() { writeConfig(); } void SubKeysDialog::readConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "SubKeysDialog"); const QSize size = dialog.readEntry("Size", QSize(820, 280)); if (size.isValid()) { resize(size); } } void SubKeysDialog::writeConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "SubKeysDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } void SubKeysDialog::setKey(const GpgME::Key &key) { auto w = findChild(); Q_ASSERT(w); w->setKey(key); } GpgME::Key SubKeysDialog::key() const { auto w = findChild(); Q_ASSERT(w); return w->key(); }