diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp index dddbe6ec7..008c0e5dd 100644 --- a/src/dialogs/subkeyswidget.cpp +++ b/src/dialogs/subkeyswidget.cpp @@ -1,436 +1,426 @@ /* dialogs/subkeyswidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "subkeyswidget.h" #include "commands/addsubkeycommand.h" #include "commands/changeexpirycommand.h" #include "commands/exportsecretsubkeycommand.h" #include "commands/importpaperkeycommand.h" #include "commands/keytocardcommand.h" #include "exportdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(GpgME::Subkey) using namespace Kleo; using namespace Kleo::Commands; class SubKeysWidget::Private { SubKeysWidget *const q; public: Private(SubKeysWidget *qq) : q{qq} , ui{qq} { ui.subkeysTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.subkeysTree, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { tableContextMenuRequested(p); }); connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() { keysMayHaveChanged(); }); } void changeValidity(const GpgME::Subkey &subkey); void exportSSH(const GpgME::Subkey &subkey); void keyToCard(const GpgME::Subkey &subkey); void exportSecret(const GpgME::Subkey &subkey); void importPaperKey(); private: void tableContextMenuRequested(const QPoint &p); void keysMayHaveChanged(); public: GpgME::Key key; public: struct UI { QVBoxLayout *mainLayout; TreeWidget *subkeysTree; QPushButton *addSubkeyButton = nullptr; QPushButton *changeValidityBtn = nullptr; QPushButton *exportOpenSSHBtn = nullptr; QPushButton *restoreBtn = nullptr; QPushButton *transferToSmartcardBtn = nullptr; QPushButton *exportSecretBtn = nullptr; UI(QWidget *widget) { mainLayout = new QVBoxLayout{widget}; mainLayout->setContentsMargins(0, 0, 0, 0); auto subkeysTreeLabel = new QLabel{i18nc("@label", "Subkeys:"), widget}; mainLayout->addWidget(subkeysTreeLabel); subkeysTree = new TreeWidget{widget}; subkeysTreeLabel->setBuddy(subkeysTree); subkeysTree->setAccessibleName(i18nc("@label", "Subkeys")); subkeysTree->setRootIsDecorated(false); subkeysTree->setHeaderLabels({ i18nc("@title:column", "ID"), - i18nc("@title:column", "Type"), + i18nc("@title:column", "Fingerprint"), i18nc("@title:column", "Valid From"), i18nc("@title:column", "Valid Until"), i18nc("@title:column", "Status"), - i18nc("@title:column", "Strength"), + i18nc("@title:column", "Algorithm"), i18nc("@title:column", "Usage"), - i18nc("@title:column", "Primary"), i18nc("@title:column", "Storage"), }); mainLayout->addWidget(subkeysTree); { auto buttonRow = new QHBoxLayout; changeValidityBtn = new QPushButton(i18nc("@action:button", "Change validity"), widget); buttonRow->addWidget(changeValidityBtn); exportOpenSSHBtn = new QPushButton{i18nc("@action:button", "Export OpenSSH key"), widget}; buttonRow->addWidget(exportOpenSSHBtn); restoreBtn = new QPushButton(i18nc("@action:button", "Restore printed backup"), widget); buttonRow->addWidget(restoreBtn); transferToSmartcardBtn = new QPushButton(i18nc("@action:button", "Transfer to smartcard"), widget); buttonRow->addWidget(transferToSmartcardBtn); exportSecretBtn = new QPushButton(i18nc("@action:button", "Export secret subkey"), widget); buttonRow->addWidget(exportSecretBtn); buttonRow->addStretch(1); mainLayout->addLayout(buttonRow); } } } ui; }; void SubKeysWidget::Private::changeValidity(const GpgME::Subkey &subkey) { auto cmd = new ChangeExpiryCommand(subkey.parent()); cmd->setSubkey(subkey); ui.subkeysTree->setEnabled(false); connect(cmd, &ChangeExpiryCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); key.update(); q->setKey(key); }); cmd->setParentWidget(q); cmd->start(); } void SubKeysWidget::Private::exportSSH(const GpgME::Subkey &subkey) { QScopedPointer dlg(new ExportDialog(q)); dlg->setKey(subkey, static_cast(GpgME::Context::ExportSSH)); dlg->exec(); } void SubKeysWidget::Private::importPaperKey() { auto cmd = new ImportPaperKeyCommand(key); ui.subkeysTree->setEnabled(false); connect(cmd, &ImportPaperKeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); } void SubKeysWidget::Private::keyToCard(const GpgME::Subkey &subkey) { auto cmd = new KeyToCardCommand(subkey); ui.subkeysTree->setEnabled(false); connect(cmd, &KeyToCardCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); } void SubKeysWidget::Private::exportSecret(const GpgME::Subkey &subkey) { auto cmd = new ExportSecretSubkeyCommand{{subkey}}; ui.subkeysTree->setEnabled(false); connect(cmd, &ExportSecretSubkeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); } 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(); const bool isOwnKey = subkey.parent().hasSecret(); const bool secretSubkeyStoredInKeyRing = subkey.isSecret() && !subkey.isCardKey(); auto menu = new QMenu(q); connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); if (isOwnKey) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("change-date-symbolic")), i18n("Change validity"), q, [this, subkey]() { changeValidity(subkey); }); action->setEnabled(canBeUsedForSecretKeyOperations(subkey.parent())); } if (subkey.canAuthenticate()) { menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export OpenSSH key"), q, [this, subkey]() { exportSSH(subkey); }); } auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-import")), i18n("Restore printed backup"), q, [this, subkey]() { importPaperKey(); }); action->setEnabled(!secretSubkeyStoredInKeyRing); if (isOwnKey) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("send-to-symbolic")), i18n("Transfer to smartcard"), q, [this, subkey]() { keyToCard(subkey); }); action->setEnabled(secretSubkeyStoredInKeyRing && !KeyToCardCommand::getSuitableCards(subkey).empty()); } const bool isPrimarySubkey = subkey.keyID() == key.keyID(); if (isOwnKey && !isPrimarySubkey) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export secret subkey"), q, [this, subkey]() { exportSecret(subkey); }); action->setEnabled(secretSubkeyStoredInKeyRing); } menu->popup(ui.subkeysTree->viewport()->mapToGlobal(p)); } void SubKeysWidget::Private::keysMayHaveChanged() { qCDebug(KLEOPATRA_LOG) << q << __func__; const auto updatedKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint()); if (!updatedKey.isNull()) { q->setKey(updatedKey); } } SubKeysWidget::SubKeysWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { connect(d->ui.subkeysTree, &TreeWidget::currentItemChanged, this, [this] { const auto currentIndex = d->ui.subkeysTree->currentIndex().row(); const auto &subkey = d->key.subkey(currentIndex); const bool secretSubkeyStoredInKeyRing = subkey.isSecret() && !subkey.isCardKey(); d->ui.exportOpenSSHBtn->setEnabled(subkey.canAuthenticate()); d->ui.changeValidityBtn->setEnabled(d->key.hasSecret() && canBeUsedForSecretKeyOperations(subkey.parent())); d->ui.exportSecretBtn->setEnabled(d->key.hasSecret() && subkey.fingerprint() != d->key.primaryFingerprint() && secretSubkeyStoredInKeyRing); d->ui.restoreBtn->setEnabled(!secretSubkeyStoredInKeyRing); d->ui.transferToSmartcardBtn->setEnabled(secretSubkeyStoredInKeyRing && !KeyToCardCommand::getSuitableCards(subkey).empty()); }); connect(d->ui.changeValidityBtn, &QPushButton::clicked, this, [this] { d->changeValidity(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); }); connect(d->ui.exportOpenSSHBtn, &QPushButton::clicked, this, [this] { d->exportSSH(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); }); connect(d->ui.restoreBtn, &QPushButton::clicked, this, [this] { d->importPaperKey(); }); connect(d->ui.transferToSmartcardBtn, &QPushButton::clicked, this, [this] { d->keyToCard(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); }); connect(d->ui.exportSecretBtn, &QPushButton::clicked, this, [this] { d->exportSecret(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); }); } SubKeysWidget::~SubKeysWidget() = default; void SubKeysWidget::setKey(const GpgME::Key &key) { if (key.protocol() != GpgME::OpenPGP) { return; } 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(); const auto subkeys = key.subkeys(); for (const auto &subkey : subkeys) { auto item = new QTreeWidgetItem; item->setData(0, Qt::DisplayRole, Formatting::prettyID(subkey.keyID())); item->setData(0, Qt::AccessibleTextRole, Formatting::accessibleHexID(subkey.keyID())); item->setData(0, Qt::UserRole, QVariant::fromValue(subkey)); - item->setData(1, Qt::DisplayRole, Kleo::Formatting::type(subkey)); + item->setData(1, Qt::DisplayRole, Formatting::prettyID(subkey.fingerprint())); + item->setData(1, Qt::AccessibleTextRole, Formatting::accessibleHexID(subkey.fingerprint())); item->setData(2, Qt::DisplayRole, Kleo::Formatting::creationDateString(subkey)); item->setData(2, Qt::AccessibleTextRole, Formatting::accessibleCreationDate(subkey)); item->setData(3, Qt::DisplayRole, subkey.neverExpires() ? Kleo::Formatting::expirationDateString(subkey.parent()) : Kleo::Formatting::expirationDateString(subkey)); item->setData(3, Qt::AccessibleTextRole, subkey.neverExpires() ? Kleo::Formatting::accessibleExpirationDate(subkey.parent()) : Kleo::Formatting::accessibleExpirationDate(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(5, Qt::DisplayRole, Kleo::Formatting::prettyAlgorithmName(subkey.algoName())); item->setData(6, Qt::DisplayRole, Kleo::Formatting::usageString(subkey)); const auto isPrimary = subkey.keyID() == key.keyID(); - item->setData(7, Qt::DisplayRole, isPrimary ? QStringLiteral("✓") : QString()); - item->setData(7, Qt::AccessibleTextRole, isPrimary ? i18nc("yes, is primary key", "yes") : i18nc("no, is not primary key", "no")); - if (subkey.isCardKey()) { + if (!key.hasSecret()) { + item->setData(7, Qt::DisplayRole, i18nc("not applicable", "n/a")); + } else if (subkey.isCardKey()) { if (const char *serialNo = subkey.cardSerialNumber()) { - item->setData(8, Qt::DisplayRole, i18nc("smart card ", "smart card %1", QString::fromUtf8(serialNo))); + item->setData(7, Qt::DisplayRole, i18nc("smart card ", "smart card %1", QString::fromUtf8(serialNo))); } else { - item->setData(8, Qt::DisplayRole, i18n("smart card")); + item->setData(7, Qt::DisplayRole, i18n("smart card")); } } else if (isPrimary && key.hasSecret() && !subkey.isSecret()) { - item->setData(8, Qt::DisplayRole, i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline")); + item->setData(7, Qt::DisplayRole, i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline")); } else if (subkey.isSecret()) { - item->setData(8, Qt::DisplayRole, i18n("on this computer")); + item->setData(7, Qt::DisplayRole, i18n("on this computer")); } else { - item->setData(8, Qt::DisplayRole, i18nc("unknown storage location", "unknown")); + item->setData(7, Qt::DisplayRole, i18nc("unknown storage location", "unknown")); } d->ui.subkeysTree->addTopLevelItem(item); if (subkey.fingerprint() == selectedKeyFingerprint) { d->ui.subkeysTree->setCurrentItem(item); } } d->ui.subkeysTree->header()->resizeSections(QHeaderView::ResizeToContents); d->ui.changeValidityBtn->setVisible(key.hasSecret()); d->ui.exportSecretBtn->setVisible(key.hasSecret()); d->ui.transferToSmartcardBtn->setVisible(key.hasSecret()); - d->ui.subkeysTree->restoreColumnLayout(QStringLiteral("SubkeysWidget")); - if (!key.hasSecret()) { - // hide information about storage location for keys of other people - d->ui.subkeysTree->hideColumn(8); + if (!d->ui.subkeysTree->restoreColumnLayout(QStringLiteral("SubkeysWidget"))) { + d->ui.subkeysTree->hideColumn(1); } } 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); const auto subKeysWidget = new SubKeysWidget(this); l->addWidget(subKeysWidget); auto bbox = new QDialogButtonBox(this); auto addSubkeyButton = new QPushButton(this); addSubkeyButton->setText(i18nc("@action:button", "Add Subkey")); addSubkeyButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addSubkeyButton->setObjectName(QLatin1String("AddSubkeyButton")); bbox->addButton(addSubkeyButton, QDialogButtonBox::ActionRole); connect(addSubkeyButton, &QPushButton::clicked, this, [this]() { const auto cmd = new AddSubkeyCommand(key()); connect(cmd, &AddSubkeyCommand::finished, this, [this]() { key().update(); }); cmd->setParentWidget(this); cmd->start(); }); 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::openStateConfig(), QStringLiteral("SubKeysDialog")); const QSize size = dialog.readEntry("Size", QSize(820, 280)); if (size.isValid()) { resize(size); } } void SubKeysDialog::writeConfig() { KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("SubKeysDialog")); dialog.writeEntry("Size", size()); dialog.sync(); } void SubKeysDialog::setKey(const GpgME::Key &key) { auto w = findChild(); Q_ASSERT(w); w->setKey(key); if (!key.hasSecret()) { findChild(QLatin1String("AddSubkeyButton"))->setVisible(false); } } GpgME::Key SubKeysDialog::key() const { auto w = findChild(); Q_ASSERT(w); return w->key(); } #include "moc_subkeyswidget.cpp"