diff --git a/src/commands/changeexpirycommand.cpp b/src/commands/changeexpirycommand.cpp index a741d1b1d..b76e61126 100644 --- a/src/commands/changeexpirycommand.cpp +++ b/src/commands/changeexpirycommand.cpp @@ -1,265 +1,295 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/changeexpirycommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "changeexpirycommand.h" #include "command_p.h" #include #include #include #include #include #include #include "kleopatra_debug.h" #include +#include + +#include +#if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 +# define CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS +#endif using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace GpgME; using namespace QGpgME; class ChangeExpiryCommand::Private : public Command::Private { friend class ::Kleo::Commands::ChangeExpiryCommand; ChangeExpiryCommand *q_func() const { return static_cast(q); } public: explicit Private(ChangeExpiryCommand *qq, KeyListController *c); ~Private(); void init(); private: void slotDialogAccepted(); void slotDialogRejected(); void slotResult(const Error &err); private: void ensureDialogCreated(); void createJob(); void showErrorDialog(const Error &error); void showSuccessDialog(); private: GpgME::Key key; + GpgME::Subkey subkey; QPointer dialog; QPointer job; }; ChangeExpiryCommand::Private *ChangeExpiryCommand::d_func() { return static_cast(d.get()); } const ChangeExpiryCommand::Private *ChangeExpiryCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() ChangeExpiryCommand::Private::Private(ChangeExpiryCommand *qq, KeyListController *c) : Command::Private(qq, c), key(), dialog(), job() { } ChangeExpiryCommand::Private::~Private() { qCDebug(KLEOPATRA_LOG); } ChangeExpiryCommand::ChangeExpiryCommand(KeyListController *c) : Command(new Private(this, c)) { d->init(); } ChangeExpiryCommand::ChangeExpiryCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { d->init(); } ChangeExpiryCommand::ChangeExpiryCommand(const GpgME::Key &key) : Command(key, new Private(this, nullptr)) { d->init(); } void ChangeExpiryCommand::Private::init() { } ChangeExpiryCommand::~ChangeExpiryCommand() { qCDebug(KLEOPATRA_LOG); } -void ChangeExpiryCommand::doStart() +void ChangeExpiryCommand::setSubkey(const GpgME::Subkey &subkey) { + d->subkey = subkey; +} +void ChangeExpiryCommand::doStart() +{ const std::vector keys = d->keys(); if (keys.size() != 1 || keys.front().protocol() != GpgME::OpenPGP || !keys.front().hasSecret() || keys.front().subkey(0).isNull()) { d->finished(); return; } d->key = keys.front(); + if (!d->subkey.isNull() && + d->subkey.parent().primaryFingerprint() != d->key.primaryFingerprint()) { + qDebug() << "Invalid subkey" << d->subkey.fingerprint() + << ": Not a subkey of key" << d->key.primaryFingerprint(); + d->finished(); + return; + } + + const Subkey subkey = !d->subkey.isNull() ? d->subkey : d->key.subkey(0); + d->ensureDialogCreated(); Q_ASSERT(d->dialog); - const Subkey subkey = d->key.subkey(0); - d->dialog->setDateOfExpiry(subkey.neverExpires() ? QDate() : QDateTime::fromSecsSinceEpoch(d->key.subkey(0).expirationTime()).date()); + d->dialog->setDateOfExpiry(subkey.neverExpires() ? QDate() : + QDateTime::fromSecsSinceEpoch(subkey.expirationTime()).date()); d->dialog->show(); } void ChangeExpiryCommand::Private::slotDialogAccepted() { Q_ASSERT(dialog); - static const QTime END_OF_DAY(23, 59, 59); // not used, so as good as any + static const QTime END_OF_DAY(23, 59, 59); const QDateTime expiry(dialog->dateOfExpiry(), END_OF_DAY); qCDebug(KLEOPATRA_LOG) << "expiry" << expiry; createJob(); Q_ASSERT(job); + std::vector subkeys; + if (!subkey.isNull()) { + subkeys.push_back(subkey); + } + +#ifdef CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS + if (const Error err = job->start(key, expiry, subkeys)) { +#else if (const Error err = job->start(key, expiry)) { +#endif showErrorDialog(err); finished(); } } void ChangeExpiryCommand::Private::slotDialogRejected() { Q_EMIT q->canceled(); finished(); } void ChangeExpiryCommand::Private::slotResult(const Error &err) { if (err.isCanceled()) ; else if (err) { showErrorDialog(err); } else { showSuccessDialog(); } finished(); } void ChangeExpiryCommand::doCancel() { qCDebug(KLEOPATRA_LOG); if (d->job) { d->job->slotCancel(); } } void ChangeExpiryCommand::Private::ensureDialogCreated() { if (dialog) { return; } dialog = new ExpiryDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, SIGNAL(accepted()), q, SLOT(slotDialogAccepted())); connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected())); } void ChangeExpiryCommand::Private::createJob() { Q_ASSERT(!job); const auto backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); if (!backend) { return; } ChangeExpiryJob *const j = backend->changeExpiryJob(); if (!j) { return; } connect(j, &Job::progress, q, &Command::progress); connect(j, SIGNAL(result(GpgME::Error)), q, SLOT(slotResult(GpgME::Error))); job = j; } void ChangeExpiryCommand::Private::showErrorDialog(const Error &err) { error(i18n("

An error occurred while trying to change " "the expiry date for %1:

%2

", Formatting::formatForComboBox(key), QString::fromLocal8Bit(err.asString())), i18n("Expiry Date Change Error")); } void ChangeExpiryCommand::Private::showSuccessDialog() { information(i18n("Expiry date changed successfully."), i18n("Expiry Date Change Succeeded")); } #undef d #undef q #include "moc_changeexpirycommand.cpp" diff --git a/src/commands/changeexpirycommand.h b/src/commands/changeexpirycommand.h index da90a5a65..6da88cd48 100644 --- a/src/commands/changeexpirycommand.h +++ b/src/commands/changeexpirycommand.h @@ -1,73 +1,80 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/changeexpirycommand.h This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef __KLEOPATRA_COMMMANDS_CHANGEEXPIRYCOMMAND_H__ #define __KLEOPATRA_COMMMANDS_CHANGEEXPIRYCOMMAND_H__ #include +namespace GpgME +{ +class Subkey; +} + namespace Kleo { namespace Commands { class ChangeExpiryCommand : public Command { Q_OBJECT public: explicit ChangeExpiryCommand(QAbstractItemView *view, KeyListController *parent); explicit ChangeExpiryCommand(KeyListController *parent); explicit ChangeExpiryCommand(const GpgME::Key &key); ~ChangeExpiryCommand() override; /* reimp */ static Restrictions restrictions() { return OnlyOneKey | MustBeOpenPGP | NeedSecretKey; } + void setSubkey(const GpgME::Subkey &subkey); + private: void doStart() override; void doCancel() override; private: class Private; inline Private *d_func(); inline const Private *d_func() const; Q_PRIVATE_SLOT(d_func(), void slotResult(GpgME::Error)) Q_PRIVATE_SLOT(d_func(), void slotDialogAccepted()) Q_PRIVATE_SLOT(d_func(), void slotDialogRejected()) }; } } #endif // __KLEOPATRA_COMMMANDS_CHANGEEXPIRYCOMMAND_H__ diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp index 9fd29e16a..740802ea4 100644 --- a/src/dialogs/subkeyswidget.cpp +++ b/src/dialogs/subkeyswidget.cpp @@ -1,237 +1,273 @@ /* 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/changeexpirycommand.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 +#if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 +# define CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS +#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 CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS + if (subkey.parent().protocol() == GpgME::OpenPGP && subkey.parent().hasSecret()) { + hasActions = true; + menu->addAction(i18n("Change Expiry Date..."), q, + [this, subkey]() { + auto cmd = new Kleo::Commands::ChangeExpiryCommand(subkey.parent()); + if (subkey.keyID() != key.keyID()) { + // do not set the primary key as subkey + cmd->setSubkey(subkey); + } + ui.subkeysTree->setEnabled(false); + connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, + q, [this]() { + ui.subkeysTree->setEnabled(true); + key.update(); + q->setKey(key); + }); + cmd->setParentWidget(q); + cmd->start(); + } + ); + } +#endif // CHANGEEXPIRYJOB_SUPPORTS_SUBKEYS + #ifdef GPGME_HAS_EXPORT_FLAGS if (subkey.parent().protocol() == GpgME::OpenPGP && 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; + const auto currentItem = d->ui.subkeysTree->currentItem(); + const QByteArray selectedKeyFingerprint = currentItem ? + QByteArray(currentItem->data(0, Qt::UserRole).value().fingerprint()) : QByteArray(); + d->ui.subkeysTree->clear(); + 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); + if (subkey.fingerprint() == selectedKeyFingerprint) { + d->ui.subkeysTree->setCurrentItem(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(); }