Page MenuHome GnuPG

No OneTemporary

diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp
index 60aa5a083..7ebc79cb5 100644
--- a/src/dialogs/certificatedetailswidget.cpp
+++ b/src/dialogs/certificatedetailswidget.cpp
@@ -1,675 +1,674 @@
/*
dialogs/certificatedetailswidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-FileCopyrightText: 2022 Felix Tiede
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certificatedetailswidget.h"
#include "certificatedumpwidget.h"
#include "dialogs/weboftrustwidget.h"
#include "kleopatra_debug.h"
#include "subkeyswidget.h"
#include "trustchainwidget.h"
#include "useridswidget.h"
#include "commands/changeexpirycommand.h"
#include "commands/detailscommand.h"
#include "utils/accessibility.h"
#include "utils/tags.h"
#include "view/infofield.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Compliance>
#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyHelpers>
#include <Libkleo/TreeWidget>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <QGpgME/Debug>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QClipboard>
#include <QDateTime>
#include <QGridLayout>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QLocale>
#include <QMenu>
#include <QPushButton>
#include <QStringBuilder>
#include <QTreeWidget>
#include <QVBoxLayout>
#include <map>
#if __has_include(<ranges>)
#include <ranges>
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
#define USE_RANGES
#endif
#endif
#include <set>
Q_DECLARE_METATYPE(GpgME::UserID)
using namespace Kleo;
class CertificateDetailsWidget::Private
{
public:
Private(CertificateDetailsWidget *qq);
void setupCommonProperties();
void setUpSMIMEAdressList();
void setupPGPProperties();
void setupSMIMEProperties();
void refreshCertificate();
void changeExpiration();
void keysMayHaveChanged();
QIcon trustLevelIcon(const GpgME::UserID &uid) const;
QString trustLevelText(const GpgME::UserID &uid) const;
void showIssuerCertificate();
void updateKey();
void setUpdatedKey(const GpgME::Key &key);
void keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &, const QString &, const GpgME::Error &);
void copyFingerprintToClipboard();
void setUpdateInProgress(bool updateInProgress);
void setTabVisible(QWidget *tab, bool visible);
private:
CertificateDetailsWidget *const q;
public:
GpgME::Key key;
bool updateInProgress = false;
private:
InfoField *attributeField(const QString &attributeName)
{
const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName);
if (keyValuePairIt != ui.smimeAttributeFields.end()) {
return (*keyValuePairIt).second.get();
}
return nullptr;
}
private:
struct UI {
UserIdsWidget *userIDs = nullptr;
std::map<QString, std::unique_ptr<InfoField>> smimeAttributeFields;
std::unique_ptr<InfoField> smimeTrustLevelField;
std::unique_ptr<InfoField> validFromField;
std::unique_ptr<InfoField> expiresField;
QAction *changeExpirationAction = nullptr;
std::unique_ptr<InfoField> fingerprintField;
QAction *copyFingerprintAction = nullptr;
std::unique_ptr<InfoField> smimeIssuerField;
QAction *showIssuerCertificateAction = nullptr;
std::unique_ptr<InfoField> complianceField;
std::unique_ptr<InfoField> trustedIntroducerField;
std::unique_ptr<InfoField> primaryUserIdField;
std::unique_ptr<InfoField> privateKeyInfoField;
QListWidget *smimeAddressList = nullptr;
QTabWidget *tabWidget = nullptr;
SubKeysWidget *subKeysWidget = nullptr;
WebOfTrustWidget *webOfTrustWidget = nullptr;
TrustChainWidget *trustChainWidget = nullptr;
CertificateDumpWidget *certificateDumpWidget = nullptr;
void setupUi(QWidget *parent)
{
auto mainLayout = new QVBoxLayout{parent};
{
auto gridLayout = new QGridLayout;
gridLayout->setColumnStretch(1, 1);
int row = -1;
row++;
primaryUserIdField = std::make_unique<InfoField>(i18n("User ID:"), parent);
gridLayout->addWidget(primaryUserIdField->label(), row, 0);
gridLayout->addLayout(primaryUserIdField->layout(), row, 1);
for (const auto &attribute : DN::attributeOrder()) {
const auto attributeLabel = DN::attributeNameToLabel(attribute);
if (attributeLabel.isEmpty()) {
continue;
}
const auto labelWithColon = i18nc("interpunctation for labels", "%1:", attributeLabel);
const auto &[it, inserted] = smimeAttributeFields.try_emplace(attribute, std::make_unique<InfoField>(labelWithColon, parent));
if (inserted) {
row++;
const auto &field = it->second;
gridLayout->addWidget(field->label(), row, 0);
gridLayout->addLayout(field->layout(), row, 1);
}
}
row++;
smimeTrustLevelField = std::make_unique<InfoField>(i18n("Trust level:"), parent);
gridLayout->addWidget(smimeTrustLevelField->label(), row, 0);
gridLayout->addLayout(smimeTrustLevelField->layout(), row, 1);
row++;
validFromField = std::make_unique<InfoField>(i18n("Valid from:"), parent);
gridLayout->addWidget(validFromField->label(), row, 0);
gridLayout->addLayout(validFromField->layout(), row, 1);
row++;
expiresField = std::make_unique<InfoField>(i18n("Valid until:"), parent);
changeExpirationAction = new QAction{parent};
changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor")));
changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the end of the validity period"));
Kleo::setAccessibleName(changeExpirationAction, i18nc("@action:button", "Change Validity"));
expiresField->setAction(changeExpirationAction);
gridLayout->addWidget(expiresField->label(), row, 0);
gridLayout->addLayout(expiresField->layout(), row, 1);
row++;
fingerprintField = std::make_unique<InfoField>(i18n("Fingerprint:"), parent);
if (QGuiApplication::clipboard()) {
copyFingerprintAction = new QAction{parent};
copyFingerprintAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
copyFingerprintAction->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard"));
Kleo::setAccessibleName(copyFingerprintAction, i18nc("@action:button", "Copy fingerprint"));
fingerprintField->setAction(copyFingerprintAction);
}
gridLayout->addWidget(fingerprintField->label(), row, 0);
gridLayout->addLayout(fingerprintField->layout(), row, 1);
row++;
smimeIssuerField = std::make_unique<InfoField>(i18n("Issuer:"), parent);
showIssuerCertificateAction = new QAction{parent};
showIssuerCertificateAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information")));
showIssuerCertificateAction->setToolTip(i18nc("@info:tooltip", "Show the issuer certificate"));
Kleo::setAccessibleName(showIssuerCertificateAction, i18nc("@action:button", "Show certificate"));
smimeIssuerField->setAction(showIssuerCertificateAction);
gridLayout->addWidget(smimeIssuerField->label(), row, 0);
gridLayout->addLayout(smimeIssuerField->layout(), row, 1);
row++;
complianceField = std::make_unique<InfoField>(i18n("Compliance:"), parent);
gridLayout->addWidget(complianceField->label(), row, 0);
gridLayout->addLayout(complianceField->layout(), row, 1);
row++;
trustedIntroducerField = std::make_unique<InfoField>(i18n("Trusted introducer for:"), parent);
gridLayout->addWidget(trustedIntroducerField->label(), row, 0);
trustedIntroducerField->setToolTip(i18n("See certifications for details."));
gridLayout->addLayout(trustedIntroducerField->layout(), row, 1);
row++;
privateKeyInfoField = std::make_unique<InfoField>(i18n("Private Key:"), parent);
gridLayout->addWidget(privateKeyInfoField->label(), row, 0);
gridLayout->addLayout(privateKeyInfoField->layout(), row, 1);
- privateKeyInfoField->setValue(i18n("Smartcard"));
mainLayout->addLayout(gridLayout);
}
tabWidget = new QTabWidget(parent);
mainLayout->addWidget(tabWidget);
userIDs = new UserIdsWidget(parent);
- tabWidget->addTab(userIDs, i18n("User IDs"));
+ tabWidget->addTab(userIDs, i18nc("@title:tab", "User IDs"));
smimeAddressList = new QListWidget{parent};
smimeAddressList->setAccessibleName(i18n("Related addresses"));
smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers);
smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection);
- tabWidget->addTab(smimeAddressList, i18n("Related addresses"));
+ tabWidget->addTab(smimeAddressList, i18nc("@title:tab", "Related Addresses"));
subKeysWidget = new SubKeysWidget(parent);
- tabWidget->addTab(subKeysWidget, i18n("Subkeys"));
+ tabWidget->addTab(subKeysWidget, i18nc("@title:tab", "Subkeys"));
webOfTrustWidget = new WebOfTrustWidget(parent);
- tabWidget->addTab(webOfTrustWidget, i18n("Certifications"));
+ tabWidget->addTab(webOfTrustWidget, i18nc("@title:tab", "Certifications"));
trustChainWidget = new TrustChainWidget(parent);
- tabWidget->addTab(trustChainWidget, i18n("Trust chain details"));
+ tabWidget->addTab(trustChainWidget, i18nc("@title:tab", "Trust Cchain Details"));
certificateDumpWidget = new CertificateDumpWidget(parent);
- tabWidget->addTab(certificateDumpWidget, i18n("Certificate dump"));
+ tabWidget->addTab(certificateDumpWidget, i18nc("@title:tab", "Certificate Dump"));
}
} ui;
};
CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq)
: q{qq}
{
ui.setupUi(q);
connect(ui.changeExpirationAction, &QAction::triggered, q, [this]() {
changeExpiration();
});
connect(ui.showIssuerCertificateAction, &QAction::triggered, q, [this]() {
showIssuerCertificate();
});
if (ui.copyFingerprintAction) {
connect(ui.copyFingerprintAction, &QAction::triggered, q, [this]() {
copyFingerprintToClipboard();
});
}
connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() {
keysMayHaveChanged();
});
connect(ui.userIDs, &UserIdsWidget::updateKey, q, [this]() {
updateKey();
});
}
void CertificateDetailsWidget::Private::setupCommonProperties()
{
const bool isOpenPGP = key.protocol() == GpgME::OpenPGP;
const bool isSMIME = key.protocol() == GpgME::CMS;
const bool isOwnKey = key.hasSecret();
for (const auto &[_, field] : ui.smimeAttributeFields) {
field->setVisible(isSMIME);
}
ui.smimeTrustLevelField->setVisible(isSMIME);
// ui.validFromField->setVisible(true); // always visible
// ui.expiresField->setVisible(true); // always visible
if (isOpenPGP && isOwnKey) {
ui.expiresField->setAction(ui.changeExpirationAction);
} else {
ui.expiresField->setAction(nullptr);
}
// ui.fingerprintField->setVisible(true); // always visible
ui.smimeIssuerField->setVisible(isSMIME);
ui.complianceField->setVisible(DeVSCompliance::isCompliant());
ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties()
// update availability of buttons
ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key));
// update values of protocol-independent UI elements
ui.validFromField->setValue(Formatting::creationDateString(key), Formatting::accessibleCreationDate(key));
ui.expiresField->setValue(Formatting::expirationDateString(key, i18nc("Valid until:", "unlimited")), Formatting::accessibleExpirationDate(key));
ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()), Formatting::accessibleHexID(key.primaryFingerprint()));
QString storage;
const auto &subkey = key.subkey(0);
if (!key.hasSecret()) {
storage = i18nc("not applicable", "n/a");
} else if (key.subkey(0).isCardKey()) {
if (const char *serialNo = subkey.cardSerialNumber()) {
storage = i18nc("smart card <serial number>", "smart card %1", QString::fromUtf8(serialNo));
} else {
storage = i18n("smart card");
}
} else if (key.hasSecret() && !subkey.isSecret()) {
storage = i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline");
} else if (subkey.isSecret()) {
storage = i18n("on this computer");
} else {
storage = i18nc("unknown storage location", "unknown");
}
if (!key.subkey(0).isSecret()) {
storage = i18n("none");
} else if (key.subkey(0).cardSerialNumber()) {
storage = i18n("smart card");
}
ui.privateKeyInfoField->setValue(storage);
if (DeVSCompliance::isCompliant()) {
ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key));
}
}
void CertificateDetailsWidget::Private::setUpSMIMEAdressList()
{
ui.smimeAddressList->clear();
const auto *const emailField = attributeField(QStringLiteral("EMAIL"));
// add email address from primary user ID if not listed already as attribute field
if (!emailField) {
const auto ownerId = key.userID(0);
const Kleo::DN dn(ownerId.id());
const QString dnEmail = dn[QStringLiteral("EMAIL")];
if (!dnEmail.isEmpty()) {
ui.smimeAddressList->addItem(dnEmail);
}
}
if (key.numUserIDs() > 1) {
// iterate over the secondary user IDs
#ifdef USE_RANGES
for (const auto uids = key.userIDs(); const auto &uid : std::ranges::subrange(std::next(uids.begin()), uids.end())) {
#else
const auto uids = key.userIDs();
for (auto it = std::next(uids.begin()); it != uids.end(); ++it) {
const auto &uid = *it;
#endif
const auto name = Kleo::Formatting::prettyName(uid);
const auto email = Kleo::Formatting::prettyEMail(uid);
QString itemText;
if (name.isEmpty() && !email.isEmpty()) {
// skip email addresses already listed in email attribute field
if (emailField && email == emailField->value()) {
continue;
}
itemText = email;
} else {
// S/MIME certificates sometimes contain urls where both
// name and mail is empty. In that case we print whatever
// the uid is as name.
//
// Can be ugly like (3:uri24:http://ca.intevation.org), but
// this is better then showing an empty entry.
itemText = QString::fromUtf8(uid.id());
}
// avoid duplicate entries in the list
if (ui.smimeAddressList->findItems(itemText, Qt::MatchExactly).empty()) {
ui.smimeAddressList->addItem(itemText);
}
}
}
if (ui.smimeAddressList->count() == 0) {
ui.tabWidget->setTabVisible(1, false);
}
}
void CertificateDetailsWidget::Private::changeExpiration()
{
auto cmd = new Kleo::Commands::ChangeExpiryCommand(key);
QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() {
ui.changeExpirationAction->setEnabled(true);
});
ui.changeExpirationAction->setEnabled(false);
cmd->start();
}
namespace
{
void ensureThatKeyDetailsAreLoaded(GpgME::Key &key)
{
if (key.userID(0).numSignatures() == 0) {
key.update();
}
}
}
void CertificateDetailsWidget::Private::keysMayHaveChanged()
{
auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint());
if (!newKey.isNull()) {
ensureThatKeyDetailsAreLoaded(newKey);
setUpdatedKey(newKey);
}
}
QIcon CertificateDetailsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const
{
if (updateInProgress) {
return QIcon::fromTheme(QStringLiteral("emblem-question"));
}
switch (uid.validity()) {
case GpgME::UserID::Unknown:
case GpgME::UserID::Undefined:
return QIcon::fromTheme(QStringLiteral("emblem-question"));
case GpgME::UserID::Never:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
case GpgME::UserID::Marginal:
return QIcon::fromTheme(QStringLiteral("emblem-warning"));
case GpgME::UserID::Full:
case GpgME::UserID::Ultimate:
return QIcon::fromTheme(QStringLiteral("emblem-success"));
}
return {};
}
QString CertificateDetailsWidget::Private::trustLevelText(const GpgME::UserID &uid) const
{
return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid);
}
namespace
{
auto isGood(const GpgME::UserID::Signature &signature)
{
return signature.status() == GpgME::UserID::Signature::NoError //
&& !signature.isInvalid() //
&& 0x10 <= signature.certClass() && signature.certClass() <= 0x13;
}
auto accumulateTrustDomains(const std::vector<GpgME::UserID::Signature> &signatures)
{
return std::accumulate(std::begin(signatures), std::end(signatures), std::set<QString>(), [](auto domains, const auto &signature) {
if (isGood(signature) && signature.isTrustSignature()) {
domains.insert(Formatting::trustSignatureDomain(signature));
}
return domains;
});
}
auto accumulateTrustDomains(const std::vector<GpgME::UserID> &userIds)
{
return std::accumulate(std::begin(userIds), std::end(userIds), std::set<QString>(), [](auto domains, const auto &userID) {
const auto newDomains = accumulateTrustDomains(userID.signatures());
std::copy(std::begin(newDomains), std::end(newDomains), std::inserter(domains, std::end(domains)));
return domains;
});
}
}
void CertificateDetailsWidget::Private::setTabVisible(QWidget *tab, bool visible)
{
ui.tabWidget->setTabVisible(ui.tabWidget->indexOf(tab), visible);
}
void CertificateDetailsWidget::Private::setupPGPProperties()
{
setTabVisible(ui.userIDs, true);
setTabVisible(ui.smimeAddressList, false);
setTabVisible(ui.subKeysWidget, true);
setTabVisible(ui.webOfTrustWidget, true);
setTabVisible(ui.trustChainWidget, false);
setTabVisible(ui.certificateDumpWidget, false);
ui.userIDs->setKey(key);
ui.subKeysWidget->setKey(key);
ui.webOfTrustWidget->setKey(key);
const auto trustDomains = accumulateTrustDomains(key.userIDs());
ui.trustedIntroducerField->setVisible(!trustDomains.empty());
ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", "));
ui.primaryUserIdField->setValue(Formatting::prettyUserID(key.userID(0)));
ui.primaryUserIdField->setVisible(true);
}
static QString formatDNToolTip(const Kleo::DN &dn)
{
QString html = QStringLiteral("<table border=\"0\" cell-spacing=15>");
const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) {
const QString val = dn[attr];
if (!val.isEmpty()) {
html += QStringLiteral(
"<tr><th style=\"text-align: left; white-space: nowrap\">%1:</th>"
"<td style=\"white-space: nowrap\">%2</td>"
"</tr>")
.arg(lbl, val);
}
};
appendRow(i18n("Common Name"), QStringLiteral("CN"));
appendRow(i18n("Organization"), QStringLiteral("O"));
appendRow(i18n("Street"), QStringLiteral("STREET"));
appendRow(i18n("City"), QStringLiteral("L"));
appendRow(i18n("State"), QStringLiteral("ST"));
appendRow(i18n("Country"), QStringLiteral("C"));
html += QStringLiteral("</table>");
return html;
}
void CertificateDetailsWidget::Private::setupSMIMEProperties()
{
setTabVisible(ui.userIDs, false);
setTabVisible(ui.smimeAddressList, true);
setTabVisible(ui.subKeysWidget, false);
setTabVisible(ui.webOfTrustWidget, false);
setTabVisible(ui.trustChainWidget, true);
setTabVisible(ui.certificateDumpWidget, true);
ui.trustChainWidget->setKey(key);
const auto ownerId = key.userID(0);
const Kleo::DN dn(ownerId.id());
for (const auto &[attributeName, field] : ui.smimeAttributeFields) {
const QString attributeValue = dn[attributeName];
field->setValue(attributeValue);
field->setVisible(!attributeValue.isEmpty());
}
ui.smimeTrustLevelField->setIcon(trustLevelIcon(ownerId));
ui.smimeTrustLevelField->setValue(trustLevelText(ownerId));
const Kleo::DN issuerDN(key.issuerName());
const QString issuerCN = issuerDN[QStringLiteral("CN")];
const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN;
ui.smimeIssuerField->setValue(issuer);
ui.smimeIssuerField->setToolTip(formatDNToolTip(issuerDN));
ui.showIssuerCertificateAction->setEnabled(!key.isRoot());
ui.primaryUserIdField->setVisible(false);
ui.certificateDumpWidget->setKey(key);
setUpSMIMEAdressList();
}
void CertificateDetailsWidget::Private::showIssuerCertificate()
{
// there is either one or no parent key
const auto parentKeys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
if (parentKeys.empty()) {
KMessageBox::error(q, i18n("The issuer certificate could not be found locally."));
return;
}
auto cmd = new Kleo::Commands::DetailsCommand(parentKeys.front());
cmd->setParentWidget(q);
cmd->start();
}
void CertificateDetailsWidget::Private::copyFingerprintToClipboard()
{
if (auto clipboard = QGuiApplication::clipboard()) {
clipboard->setText(QString::fromLatin1(key.primaryFingerprint()));
}
}
CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
CertificateDetailsWidget::~CertificateDetailsWidget() = default;
void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &keys, const QString &, const GpgME::Error &)
{
setUpdateInProgress(false);
if (keys.size() != 1) {
qCWarning(KLEOPATRA_LOG) << "Invalid keylist result in update.";
return;
}
// As we listen for keysmayhavechanged we get the update
// after updating the keycache.
KeyCache::mutableInstance()->insert(keys);
}
void CertificateDetailsWidget::Private::updateKey()
{
key.update();
setUpdatedKey(key);
}
void CertificateDetailsWidget::Private::setUpdatedKey(const GpgME::Key &k)
{
key = k;
setupCommonProperties();
if (key.protocol() == GpgME::OpenPGP) {
setupPGPProperties();
} else {
setupSMIMEProperties();
}
}
void CertificateDetailsWidget::setKey(const GpgME::Key &key)
{
if (key.protocol() == GpgME::CMS) {
// For everything but S/MIME this should be quick
// and we don't need to show another status.
d->setUpdateInProgress(true);
}
d->setUpdatedKey(key);
// Run a keylistjob with full details (TOFU / Validate)
QGpgME::KeyListJob *job =
key.protocol() == GpgME::OpenPGP ? QGpgME::openpgp()->keyListJob(false, true, true) : QGpgME::smime()->keyListJob(false, true, true);
auto ctx = QGpgME::Job::context(job);
ctx->addKeyListMode(GpgME::WithTofu);
ctx->addKeyListMode(GpgME::SignatureNotations);
if (key.hasSecret()) {
ctx->addKeyListMode(GpgME::WithSecret);
}
// Windows QGpgME new style connect problem makes this necessary.
connect(job,
SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>, QString, GpgME::Error)),
this,
SLOT(keyListDone(GpgME::KeyListResult, std::vector<GpgME::Key>, QString, GpgME::Error)));
job->start(QStringList() << QLatin1StringView(key.primaryFingerprint()));
}
GpgME::Key CertificateDetailsWidget::key() const
{
return d->key;
}
void CertificateDetailsWidget::Private::setUpdateInProgress(bool updateInProgress)
{
this->updateInProgress = updateInProgress;
ui.userIDs->setUpdateInProgress(updateInProgress);
}
#include "moc_certificatedetailswidget.cpp"
diff --git a/src/dialogs/certificatedumpwidget.cpp b/src/dialogs/certificatedumpwidget.cpp
index 3d1e010c6..45e91bd79 100644
--- a/src/dialogs/certificatedumpwidget.cpp
+++ b/src/dialogs/certificatedumpwidget.cpp
@@ -1,78 +1,76 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <config-kleopatra.h>
#include "certificatedumpwidget.h"
#include "commands/dumpcertificatecommand.h"
#include <kleopatra_debug.h>
#include <Libkleo/GnuPG>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QTextEdit>
#include <QVBoxLayout>
-Q_DECLARE_METATYPE(GpgME::Subkey)
-
class CertificateDumpWidget::Private
{
CertificateDumpWidget *const q;
public:
Private(CertificateDumpWidget *qq)
: q{qq}
, ui{qq}
{
}
GpgME::Key key;
struct UI {
QVBoxLayout *mainLayout;
QTextEdit *textEdit;
UI(QWidget *widget)
{
mainLayout = new QVBoxLayout{widget};
mainLayout->setContentsMargins({});
mainLayout->setSpacing(0);
textEdit = new QTextEdit(widget);
textEdit->setReadOnly(true);
mainLayout->addWidget(textEdit);
}
} ui;
};
CertificateDumpWidget::CertificateDumpWidget(QWidget *parent)
: QWidget(parent)
, d(new Private(this))
{
}
CertificateDumpWidget::~CertificateDumpWidget() = default;
void CertificateDumpWidget::setKey(const GpgME::Key &key)
{
d->key = key;
auto command = new Kleo::Commands::DumpCertificateCommand(key);
command->setUseDialog(false);
connect(command, &Kleo::Command::finished, this, [command, this]() {
d->ui.textEdit->setText(command->output().join(u'\n'));
});
command->start();
}
GpgME::Key CertificateDumpWidget::key() const
{
return d->key;
}
#include "moc_certificatedumpwidget.cpp"
diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp
index b8c840811..b4d14dafc 100644
--- a/src/dialogs/subkeyswidget.cpp
+++ b/src/dialogs/subkeyswidget.cpp
@@ -1,414 +1,418 @@
/*
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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#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 <kleopatra_debug.h>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyHelpers>
#include <Libkleo/TreeWidget>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSeparator>
#include <KSharedConfig>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QLabel>
#include <QMenu>
#include <QPushButton>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
Q_DECLARE_METATYPE(GpgME::Subkey)
using namespace Kleo;
using namespace Kleo::Commands;
static QPushButton *addActionButton(QLayout *buttonBox, QAction *action, bool bindVisibility = true)
{
if (!action) {
return nullptr;
}
auto button = new QPushButton(buttonBox->parentWidget());
button->setText(action->text());
buttonBox->addWidget(button);
button->setEnabled(action->isEnabled());
QObject::connect(action, &QAction::changed, button, [action, button, bindVisibility]() {
button->setEnabled(action->isEnabled());
if (bindVisibility) {
button->setVisible(action->isVisible());
}
});
QObject::connect(button, &QPushButton::clicked, action, &QAction::trigger);
return button;
}
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();
});
connect(ui.moreButton, &QPushButton::clicked, q, [this]() {
auto menu = new QMenu(q);
menu->addAction(ui.exportOpenSSHAction);
menu->addAction(ui.transferToSmartcardAction);
menu->addAction(ui.exportSecretAction);
+ menu->addAction(ui.restoreAction);
menu->popup(ui.moreButton->mapToGlobal(QPoint()));
});
}
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();
void addSubkey();
private:
void tableContextMenuRequested(const QPoint &p);
void keysMayHaveChanged();
public:
GpgME::Key key;
public:
struct UI {
QVBoxLayout *mainLayout;
TreeWidget *subkeysTree;
QAction *changeValidityAction = nullptr;
- QAction *restoreAction = nullptr;
QAction *transferToSmartcardAction = nullptr;
QAction *exportSecretAction = nullptr;
QAction *addSubkeyAction = nullptr;
+ QAction *restoreAction = nullptr;
+ QPushButton *restoreBtn = nullptr;
QAction *exportOpenSSHAction = nullptr;
QPushButton *exportOpenSSHBtn = nullptr;
QPushButton *moreButton = nullptr;
UI(QWidget *widget)
{
mainLayout = new QVBoxLayout{widget};
mainLayout->setContentsMargins({});
mainLayout->setSpacing(0);
subkeysTree = new TreeWidget{widget};
subkeysTree->setAccessibleName(i18nc("@label", "Subkeys"));
subkeysTree->setRootIsDecorated(false);
subkeysTree->setHeaderLabels({
i18nc("@title:column", "ID"),
i18nc("@title:column", "Fingerprint"),
i18nc("@title:column", "Valid From"),
i18nc("@title:column", "Valid Until"),
i18nc("@title:column", "Status"),
i18nc("@title:column", "Algorithm"),
i18nc("@title:column", "Usage"),
i18nc("@title:column", "Storage"),
});
mainLayout->addWidget(subkeysTree);
auto separator = new KSeparator(widget);
mainLayout->addWidget(separator);
{
auto buttonRow = new QHBoxLayout;
addSubkeyAction = new QAction({}, i18nc("@action:button", "Add subkey"));
changeValidityAction = new QAction({}, i18nc("@action:button", "Change validity"), widget);
exportOpenSSHAction = new QAction({}, i18nc("@action:button", "Export OpenSSH key"), widget);
restoreAction = new QAction({}, i18nc("@action:button", "Restore printed backup"), widget);
transferToSmartcardAction = new QAction({}, i18nc("@action:button", "Transfer to smartcard"), widget);
exportSecretAction = new QAction({}, i18nc("@action:button", "Export secret subkey"), widget);
addActionButton(buttonRow, addSubkeyAction);
addActionButton(buttonRow, changeValidityAction);
exportOpenSSHBtn = addActionButton(buttonRow, exportOpenSSHAction, false);
+ restoreBtn = addActionButton(buttonRow, restoreAction, false);
moreButton = new QPushButton(QIcon::fromTheme(QStringLiteral("application-menu")), {});
moreButton->setToolTip(i18nc("@info:tooltip", "Show more options"));
buttonRow->addWidget(moreButton);
buttonRow->addStretch(1);
mainLayout->addLayout(buttonRow);
}
}
} ui;
};
void SubKeysWidget::Private::changeValidity(const GpgME::Subkey &subkey)
{
ui.changeValidityAction->setEnabled(false);
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);
ui.changeValidityAction->setEnabled(true);
});
cmd->setParentWidget(q);
cmd->start();
}
void SubKeysWidget::Private::exportSSH(const GpgME::Subkey &subkey)
{
QScopedPointer<ExportDialog> dlg(new ExportDialog(q));
dlg->setKey(subkey, static_cast<unsigned int>(GpgME::Context::ExportSSH));
dlg->exec();
}
void SubKeysWidget::Private::importPaperKey()
{
ui.restoreAction->setEnabled(false);
auto cmd = new ImportPaperKeyCommand(key);
ui.subkeysTree->setEnabled(false);
connect(cmd, &ImportPaperKeyCommand::finished, q, [this]() {
ui.subkeysTree->setEnabled(true);
ui.restoreAction->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)
{
ui.exportSecretAction->setEnabled(false);
auto cmd = new ExportSecretSubkeyCommand{{subkey}};
ui.subkeysTree->setEnabled(false);
connect(cmd, &ExportSecretSubkeyCommand::finished, q, [this]() {
ui.subkeysTree->setEnabled(true);
ui.exportSecretAction->setEnabled(true);
});
cmd->setParentWidget(q);
cmd->start();
}
void SubKeysWidget::Private::addSubkey()
{
ui.addSubkeyAction->setEnabled(false);
const auto cmd = new AddSubkeyCommand(q->key());
connect(cmd, &AddSubkeyCommand::finished, q, [this]() {
q->key().update();
ui.addSubkeyAction->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<GpgME::Subkey>();
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);
});
}
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.exportOpenSSHAction->setEnabled(subkey.canAuthenticate());
d->ui.changeValidityAction->setEnabled(d->key.hasSecret() && canBeUsedForSecretKeyOperations(subkey.parent()));
d->ui.exportSecretAction->setEnabled(d->key.hasSecret() && subkey.fingerprint() != d->key.primaryFingerprint() && secretSubkeyStoredInKeyRing);
d->ui.restoreAction->setEnabled(!secretSubkeyStoredInKeyRing);
d->ui.transferToSmartcardAction->setEnabled(secretSubkeyStoredInKeyRing && !KeyToCardCommand::getSuitableCards(subkey).empty());
});
connect(d->ui.changeValidityAction, &QAction::triggered, this, [this] {
d->changeValidity(d->key.subkey(d->ui.subkeysTree->currentIndex().row()));
});
connect(d->ui.exportOpenSSHAction, &QAction::triggered, this, [this] {
d->exportSSH(d->key.subkey(d->ui.subkeysTree->currentIndex().row()));
});
connect(d->ui.restoreAction, &QAction::triggered, this, [this] {
d->importPaperKey();
});
connect(d->ui.transferToSmartcardAction, &QAction::triggered, this, [this] {
d->keyToCard(d->key.subkey(d->ui.subkeysTree->currentIndex().row()));
});
connect(d->ui.exportSecretAction, &QAction::triggered, this, [this] {
d->exportSecret(d->key.subkey(d->ui.subkeysTree->currentIndex().row()));
});
connect(d->ui.addSubkeyAction, &QAction::triggered, this, [this]() {
d->addSubkey();
});
}
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<GpgME::Subkey>().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, 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));
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();
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(7, Qt::DisplayRole, i18nc("smart card <serial number>", "smart card %1", QString::fromUtf8(serialNo)));
} else {
item->setData(7, Qt::DisplayRole, i18n("smart card"));
}
} else if (isPrimary && key.hasSecret() && !subkey.isSecret()) {
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(7, Qt::DisplayRole, i18n("on this computer"));
} else {
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.changeValidityAction->setVisible(key.hasSecret());
d->ui.exportSecretAction->setVisible(key.hasSecret());
d->ui.transferToSmartcardAction->setVisible(key.hasSecret());
d->ui.addSubkeyAction->setVisible(key.hasSecret());
- d->ui.restoreAction->setVisible(key.hasSecret());
+ d->ui.restoreAction->setVisible(true);
d->ui.exportOpenSSHAction->setEnabled(false);
d->ui.exportOpenSSHBtn->setVisible(!key.hasSecret());
d->ui.exportOpenSSHBtn->setEnabled(false);
+ d->ui.restoreBtn->setVisible(!key.hasSecret());
d->ui.moreButton->setVisible(key.hasSecret());
if (!d->ui.subkeysTree->restoreColumnLayout(QStringLiteral("SubkeysWidget"))) {
d->ui.subkeysTree->hideColumn(1);
}
for (int i = 0; i < d->ui.subkeysTree->columnCount(); i++) {
d->ui.subkeysTree->resizeColumnToContents(i);
}
}
GpgME::Key SubKeysWidget::key() const
{
return d->key;
}
#include "moc_subkeyswidget.cpp"

File Metadata

Mime Type
text/x-diff
Expires
Fri, Dec 5, 4:58 AM (1 d, 6 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
5f/6b/2ffaf68681fdcdf759d1294e6f67

Event Timeline