File indexing completed on 2024-06-23 05:14:06
0001 /* 0002 dialogs/subkeyswidget.cpp 0003 0004 This file is part of Kleopatra, the KDE keymanager 0005 SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB 0006 SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik 0007 SPDX-FileContributor: Intevation GmbH 0008 SPDX-FileCopyrightText: 2022 g10 Code GmbH 0009 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> 0010 0011 SPDX-License-Identifier: GPL-2.0-or-later 0012 */ 0013 0014 #include <config-kleopatra.h> 0015 0016 #include "subkeyswidget.h" 0017 0018 #include "commands/changeexpirycommand.h" 0019 #include "commands/exportsecretsubkeycommand.h" 0020 #include "commands/importpaperkeycommand.h" 0021 #include "commands/keytocardcommand.h" 0022 #include "exportdialog.h" 0023 0024 #include <kleopatra_debug.h> 0025 0026 #include <Libkleo/Formatting> 0027 #include <Libkleo/KeyCache> 0028 #include <Libkleo/KeyHelpers> 0029 #include <Libkleo/TreeWidget> 0030 0031 #include <KConfigGroup> 0032 #include <KLocalizedString> 0033 #include <KSharedConfig> 0034 0035 #include <QDialogButtonBox> 0036 #include <QHeaderView> 0037 #include <QLabel> 0038 #include <QMenu> 0039 #include <QPushButton> 0040 #include <QTreeWidgetItem> 0041 #include <QVBoxLayout> 0042 0043 #include <gpgme++/context.h> 0044 #include <gpgme++/key.h> 0045 0046 Q_DECLARE_METATYPE(GpgME::Subkey) 0047 0048 using namespace Kleo; 0049 using namespace Kleo::Commands; 0050 0051 class SubKeysWidget::Private 0052 { 0053 SubKeysWidget *const q; 0054 0055 public: 0056 Private(SubKeysWidget *qq) 0057 : q{qq} 0058 , ui{qq} 0059 { 0060 ui.subkeysTree->setContextMenuPolicy(Qt::CustomContextMenu); 0061 connect(ui.subkeysTree, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { 0062 tableContextMenuRequested(p); 0063 }); 0064 connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() { 0065 keysMayHaveChanged(); 0066 }); 0067 } 0068 0069 void changeValidity(const GpgME::Subkey &subkey); 0070 void exportSSH(const GpgME::Subkey &subkey); 0071 void keyToCard(const GpgME::Subkey &subkey); 0072 void exportSecret(const GpgME::Subkey &subkey); 0073 void importPaperKey(); 0074 0075 private: 0076 void tableContextMenuRequested(const QPoint &p); 0077 void keysMayHaveChanged(); 0078 0079 public: 0080 GpgME::Key key; 0081 0082 public: 0083 struct UI { 0084 QVBoxLayout *mainLayout; 0085 TreeWidget *subkeysTree; 0086 0087 QPushButton *changeValidityBtn = nullptr; 0088 QPushButton *exportOpenSSHBtn = nullptr; 0089 QPushButton *restoreBtn = nullptr; 0090 QPushButton *transferToSmartcardBtn = nullptr; 0091 QPushButton *exportSecretBtn = nullptr; 0092 0093 UI(QWidget *widget) 0094 { 0095 mainLayout = new QVBoxLayout{widget}; 0096 mainLayout->setContentsMargins(0, 0, 0, 0); 0097 0098 auto subkeysTreeLabel = new QLabel{i18nc("@label", "Subkeys:"), widget}; 0099 mainLayout->addWidget(subkeysTreeLabel); 0100 0101 subkeysTree = new TreeWidget{widget}; 0102 subkeysTreeLabel->setBuddy(subkeysTree); 0103 subkeysTree->setAccessibleName(i18nc("@label", "Subkeys")); 0104 subkeysTree->setRootIsDecorated(false); 0105 subkeysTree->setHeaderLabels({ 0106 i18nc("@title:column", "ID"), 0107 i18nc("@title:column", "Type"), 0108 i18nc("@title:column", "Valid From"), 0109 i18nc("@title:column", "Valid Until"), 0110 i18nc("@title:column", "Status"), 0111 i18nc("@title:column", "Strength"), 0112 i18nc("@title:column", "Usage"), 0113 i18nc("@title:column", "Primary"), 0114 i18nc("@title:column", "Storage"), 0115 }); 0116 mainLayout->addWidget(subkeysTree); 0117 0118 { 0119 auto buttonRow = new QHBoxLayout; 0120 0121 changeValidityBtn = new QPushButton(i18nc("@action:button", "Change validity"), widget); 0122 buttonRow->addWidget(changeValidityBtn); 0123 0124 exportOpenSSHBtn = new QPushButton{i18nc("@action:button", "Export OpenSSH key"), widget}; 0125 buttonRow->addWidget(exportOpenSSHBtn); 0126 0127 restoreBtn = new QPushButton(i18nc("@action:button", "Restore printed backup"), widget); 0128 buttonRow->addWidget(restoreBtn); 0129 0130 transferToSmartcardBtn = new QPushButton(i18nc("@action:button", "Transfer to smartcard"), widget); 0131 buttonRow->addWidget(transferToSmartcardBtn); 0132 0133 exportSecretBtn = new QPushButton(i18nc("@action:button", "Export secret subkey"), widget); 0134 buttonRow->addWidget(exportSecretBtn); 0135 0136 buttonRow->addStretch(1); 0137 0138 mainLayout->addLayout(buttonRow); 0139 } 0140 } 0141 } ui; 0142 }; 0143 0144 void SubKeysWidget::Private::changeValidity(const GpgME::Subkey &subkey) 0145 { 0146 auto cmd = new ChangeExpiryCommand(subkey.parent()); 0147 cmd->setSubkey(subkey); 0148 ui.subkeysTree->setEnabled(false); 0149 connect(cmd, &ChangeExpiryCommand::finished, q, [this]() { 0150 ui.subkeysTree->setEnabled(true); 0151 key.update(); 0152 q->setKey(key); 0153 }); 0154 cmd->setParentWidget(q); 0155 cmd->start(); 0156 } 0157 0158 void SubKeysWidget::Private::exportSSH(const GpgME::Subkey &subkey) 0159 { 0160 QScopedPointer<ExportDialog> dlg(new ExportDialog(q)); 0161 dlg->setKey(subkey, static_cast<unsigned int>(GpgME::Context::ExportSSH)); 0162 dlg->exec(); 0163 } 0164 0165 void SubKeysWidget::Private::importPaperKey() 0166 { 0167 auto cmd = new ImportPaperKeyCommand(key); 0168 ui.subkeysTree->setEnabled(false); 0169 connect(cmd, &ImportPaperKeyCommand::finished, q, [this]() { 0170 ui.subkeysTree->setEnabled(true); 0171 }); 0172 cmd->setParentWidget(q); 0173 cmd->start(); 0174 } 0175 0176 void SubKeysWidget::Private::keyToCard(const GpgME::Subkey &subkey) 0177 { 0178 auto cmd = new KeyToCardCommand(subkey); 0179 ui.subkeysTree->setEnabled(false); 0180 connect(cmd, &KeyToCardCommand::finished, q, [this]() { 0181 ui.subkeysTree->setEnabled(true); 0182 }); 0183 cmd->setParentWidget(q); 0184 cmd->start(); 0185 } 0186 0187 void SubKeysWidget::Private::exportSecret(const GpgME::Subkey &subkey) 0188 { 0189 auto cmd = new ExportSecretSubkeyCommand{{subkey}}; 0190 ui.subkeysTree->setEnabled(false); 0191 connect(cmd, &ExportSecretSubkeyCommand::finished, q, [this]() { 0192 ui.subkeysTree->setEnabled(true); 0193 }); 0194 cmd->setParentWidget(q); 0195 cmd->start(); 0196 } 0197 0198 void SubKeysWidget::Private::tableContextMenuRequested(const QPoint &p) 0199 { 0200 auto item = ui.subkeysTree->itemAt(p); 0201 if (!item) { 0202 return; 0203 } 0204 const auto subkey = item->data(0, Qt::UserRole).value<GpgME::Subkey>(); 0205 const bool isOwnKey = subkey.parent().hasSecret(); 0206 const bool secretSubkeyStoredInKeyRing = subkey.isSecret() && !subkey.isCardKey(); 0207 0208 auto menu = new QMenu(q); 0209 connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); 0210 0211 if (isOwnKey) { 0212 auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("change-date-symbolic")), i18n("Change validity"), q, [this, subkey]() { 0213 changeValidity(subkey); 0214 }); 0215 action->setEnabled(canBeUsedForSecretKeyOperations(subkey.parent())); 0216 } 0217 0218 if (subkey.canAuthenticate()) { 0219 menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export OpenSSH key"), q, [this, subkey]() { 0220 exportSSH(subkey); 0221 }); 0222 } 0223 0224 auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-import")), i18n("Restore printed backup"), q, [this, subkey]() { 0225 importPaperKey(); 0226 }); 0227 0228 action->setEnabled(!secretSubkeyStoredInKeyRing); 0229 0230 if (isOwnKey) { 0231 auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("send-to-symbolic")), i18n("Transfer to smartcard"), q, [this, subkey]() { 0232 keyToCard(subkey); 0233 }); 0234 action->setEnabled(secretSubkeyStoredInKeyRing && !KeyToCardCommand::getSuitableCards(subkey).empty()); 0235 } 0236 0237 const bool isPrimarySubkey = subkey.keyID() == key.keyID(); 0238 if (isOwnKey && !isPrimarySubkey) { 0239 auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export secret subkey"), q, [this, subkey]() { 0240 exportSecret(subkey); 0241 }); 0242 action->setEnabled(secretSubkeyStoredInKeyRing); 0243 } 0244 0245 menu->popup(ui.subkeysTree->viewport()->mapToGlobal(p)); 0246 } 0247 0248 void SubKeysWidget::Private::keysMayHaveChanged() 0249 { 0250 qCDebug(KLEOPATRA_LOG) << q << __func__; 0251 const auto updatedKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint()); 0252 if (!updatedKey.isNull()) { 0253 q->setKey(updatedKey); 0254 } 0255 } 0256 0257 SubKeysWidget::SubKeysWidget(QWidget *parent) 0258 : QWidget(parent) 0259 , d(new Private(this)) 0260 { 0261 connect(d->ui.subkeysTree, &TreeWidget::currentItemChanged, this, [this] { 0262 const auto currentIndex = d->ui.subkeysTree->currentIndex().row(); 0263 const auto &subkey = d->key.subkey(currentIndex); 0264 const bool secretSubkeyStoredInKeyRing = subkey.isSecret() && !subkey.isCardKey(); 0265 d->ui.exportOpenSSHBtn->setEnabled(subkey.canAuthenticate()); 0266 d->ui.changeValidityBtn->setEnabled(d->key.hasSecret() && canBeUsedForSecretKeyOperations(subkey.parent())); 0267 d->ui.exportSecretBtn->setEnabled(d->key.hasSecret() && subkey.fingerprint() != d->key.primaryFingerprint() && secretSubkeyStoredInKeyRing); 0268 d->ui.restoreBtn->setEnabled(!secretSubkeyStoredInKeyRing); 0269 d->ui.transferToSmartcardBtn->setEnabled(secretSubkeyStoredInKeyRing && !KeyToCardCommand::getSuitableCards(subkey).empty()); 0270 }); 0271 connect(d->ui.changeValidityBtn, &QPushButton::clicked, this, [this] { 0272 d->changeValidity(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); 0273 }); 0274 connect(d->ui.exportOpenSSHBtn, &QPushButton::clicked, this, [this] { 0275 d->exportSSH(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); 0276 }); 0277 connect(d->ui.restoreBtn, &QPushButton::clicked, this, [this] { 0278 d->importPaperKey(); 0279 }); 0280 connect(d->ui.transferToSmartcardBtn, &QPushButton::clicked, this, [this] { 0281 d->keyToCard(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); 0282 }); 0283 connect(d->ui.exportSecretBtn, &QPushButton::clicked, this, [this] { 0284 d->exportSecret(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); 0285 }); 0286 } 0287 0288 SubKeysWidget::~SubKeysWidget() = default; 0289 0290 void SubKeysWidget::setKey(const GpgME::Key &key) 0291 { 0292 if (key.protocol() != GpgME::OpenPGP) { 0293 return; 0294 } 0295 d->key = key; 0296 0297 const auto currentItem = d->ui.subkeysTree->currentItem(); 0298 const QByteArray selectedKeyFingerprint = currentItem ? QByteArray(currentItem->data(0, Qt::UserRole).value<GpgME::Subkey>().fingerprint()) : QByteArray(); 0299 d->ui.subkeysTree->clear(); 0300 0301 const auto subkeys = key.subkeys(); 0302 for (const auto &subkey : subkeys) { 0303 auto item = new QTreeWidgetItem; 0304 item->setData(0, Qt::DisplayRole, Formatting::prettyID(subkey.keyID())); 0305 item->setData(0, Qt::AccessibleTextRole, Formatting::accessibleHexID(subkey.keyID())); 0306 item->setData(0, Qt::UserRole, QVariant::fromValue(subkey)); 0307 item->setData(1, Qt::DisplayRole, Kleo::Formatting::type(subkey)); 0308 item->setData(2, Qt::DisplayRole, Kleo::Formatting::creationDateString(subkey)); 0309 item->setData(2, Qt::AccessibleTextRole, Formatting::accessibleCreationDate(subkey)); 0310 item->setData(3, 0311 Qt::DisplayRole, 0312 subkey.neverExpires() ? Kleo::Formatting::expirationDateString(subkey.parent()) : Kleo::Formatting::expirationDateString(subkey)); 0313 item->setData(3, 0314 Qt::AccessibleTextRole, 0315 subkey.neverExpires() ? Kleo::Formatting::accessibleExpirationDate(subkey.parent()) : Kleo::Formatting::accessibleExpirationDate(subkey)); 0316 item->setData(4, Qt::DisplayRole, Kleo::Formatting::validityShort(subkey)); 0317 switch (subkey.publicKeyAlgorithm()) { 0318 case GpgME::Subkey::AlgoECDSA: 0319 case GpgME::Subkey::AlgoEDDSA: 0320 case GpgME::Subkey::AlgoECDH: 0321 item->setData(5, Qt::DisplayRole, QString::fromStdString(subkey.algoName())); 0322 break; 0323 default: 0324 item->setData(5, Qt::DisplayRole, QString::number(subkey.length())); 0325 } 0326 item->setData(6, Qt::DisplayRole, Kleo::Formatting::usageString(subkey)); 0327 const auto isPrimary = subkey.keyID() == key.keyID(); 0328 item->setData(7, Qt::DisplayRole, isPrimary ? QStringLiteral("✓") : QString()); 0329 item->setData(7, Qt::AccessibleTextRole, isPrimary ? i18nc("yes, is primary key", "yes") : i18nc("no, is not primary key", "no")); 0330 if (subkey.isCardKey()) { 0331 if (const char *serialNo = subkey.cardSerialNumber()) { 0332 item->setData(8, Qt::DisplayRole, i18nc("smart card <serial number>", "smart card %1", QString::fromUtf8(serialNo))); 0333 } else { 0334 item->setData(8, Qt::DisplayRole, i18n("smart card")); 0335 } 0336 } else if (isPrimary && key.hasSecret() && !subkey.isSecret()) { 0337 item->setData(8, Qt::DisplayRole, i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline")); 0338 } else if (subkey.isSecret()) { 0339 item->setData(8, Qt::DisplayRole, i18n("on this computer")); 0340 } else { 0341 item->setData(8, Qt::DisplayRole, i18nc("unknown storage location", "unknown")); 0342 } 0343 d->ui.subkeysTree->addTopLevelItem(item); 0344 if (subkey.fingerprint() == selectedKeyFingerprint) { 0345 d->ui.subkeysTree->setCurrentItem(item); 0346 } 0347 } 0348 d->ui.subkeysTree->header()->resizeSections(QHeaderView::ResizeToContents); 0349 0350 d->ui.changeValidityBtn->setVisible(key.hasSecret()); 0351 d->ui.exportSecretBtn->setVisible(key.hasSecret()); 0352 d->ui.transferToSmartcardBtn->setVisible(key.hasSecret()); 0353 0354 d->ui.subkeysTree->restoreColumnLayout(QStringLiteral("SubkeysWidget")); 0355 if (!key.hasSecret()) { 0356 // hide information about storage location for keys of other people 0357 d->ui.subkeysTree->hideColumn(8); 0358 } 0359 } 0360 0361 GpgME::Key SubKeysWidget::key() const 0362 { 0363 return d->key; 0364 } 0365 0366 SubKeysDialog::SubKeysDialog(QWidget *parent) 0367 : QDialog(parent) 0368 { 0369 setWindowTitle(i18nc("@title:window", "Subkeys Details")); 0370 auto l = new QVBoxLayout(this); 0371 l->addWidget(new SubKeysWidget(this)); 0372 0373 auto bbox = new QDialogButtonBox(this); 0374 auto btn = bbox->addButton(QDialogButtonBox::Close); 0375 connect(btn, &QPushButton::clicked, this, &QDialog::accept); 0376 l->addWidget(bbox); 0377 readConfig(); 0378 } 0379 0380 SubKeysDialog::~SubKeysDialog() 0381 { 0382 writeConfig(); 0383 } 0384 0385 void SubKeysDialog::readConfig() 0386 { 0387 KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("SubKeysDialog")); 0388 const QSize size = dialog.readEntry("Size", QSize(820, 280)); 0389 if (size.isValid()) { 0390 resize(size); 0391 } 0392 } 0393 0394 void SubKeysDialog::writeConfig() 0395 { 0396 KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("SubKeysDialog")); 0397 dialog.writeEntry("Size", size()); 0398 dialog.sync(); 0399 } 0400 0401 void SubKeysDialog::setKey(const GpgME::Key &key) 0402 { 0403 auto w = findChild<SubKeysWidget *>(); 0404 Q_ASSERT(w); 0405 w->setKey(key); 0406 } 0407 0408 GpgME::Key SubKeysDialog::key() const 0409 { 0410 auto w = findChild<SubKeysWidget *>(); 0411 Q_ASSERT(w); 0412 return w->key(); 0413 } 0414 0415 #include "moc_subkeyswidget.cpp"