Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34109802
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
52 KB
Subscribers
None
View Options
diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp
index fa64a2f2e..18f72d9ab 100644
--- a/src/dialogs/certificatedetailswidget.cpp
+++ b/src/dialogs/certificatedetailswidget.cpp
@@ -1,1209 +1,1212 @@
/*
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 "kleopatra_debug.h"
#include "exportdialog.h"
#include "trustchainwidget.h"
#include "subkeyswidget.h"
#include "weboftrustdialog.h"
#include "commands/changepassphrasecommand.h"
#include "commands/changeexpirycommand.h"
#include "commands/certifycertificatecommand.h"
#ifdef MAILAKONADI_ENABLED
#include "commands/exportopenpgpcerttoprovidercommand.h"
#endif // MAILAKONADI_ENABLED
#include "commands/refreshcertificatecommand.h"
#include "commands/revokecertificationcommand.h"
#include "commands/revokeuseridcommand.h"
#include "commands/adduseridcommand.h"
#include "commands/genrevokecommand.h"
#include "commands/detailscommand.h"
#include "commands/dumpcertificatecommand.h"
#include "utils/keys.h"
#include "utils/tags.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/Dn>
#include <Libkleo/KeyCache>
#include <Libkleo/GnuPG>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme++/tofuinfo.h>
#include <QGpgME/Debug>
#include <QGpgME/Protocol>
#include <QGpgME/KeyListJob>
#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>
#define USE_RANGES
#endif
#include <set>
Q_DECLARE_METATYPE(GpgME::UserID)
using namespace Kleo;
static const char *accessibleNameProperty = "_kleo_accessibleName";
class InfoField {
public:
InfoField(const QString &label, QWidget *parent);
void setValue(const QString &value, const QString &accessibleValue = {});
QString value() const;
void setIcon(const QIcon &icon);
void setAction(const QAction *action);
void setToolTip(const QString &toolTip);
void setVisible(bool visible);
QLabel *label() const { return mLabel; }
QLayout *layout() const { return mLayout; }
private:
void onActionChanged();
QLabel *mLabel = nullptr;
QHBoxLayout *mLayout = nullptr;
QLabel *mIcon = nullptr;
QLabel *mValue = nullptr;
QPushButton *mButton = nullptr;
const QAction *mAction = nullptr;
};
InfoField::InfoField(const QString &label, QWidget *parent)
: mLabel{new QLabel{label, parent}}
, mLayout{new QHBoxLayout}
, mIcon{new QLabel{parent}}
, mValue{new QLabel{parent}}
, mButton{new QPushButton{parent}}
{
mLabel->setBuddy(mValue);
mLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
mIcon->setVisible(false);
mLayout->addWidget(mIcon);
mValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
mValue->setFocusPolicy(Qt::TabFocus);
mLayout->addWidget(mValue);
mButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
mButton->setVisible(false);
mLayout->addWidget(mButton);
mLayout->addStretch();
}
void InfoField::setValue(const QString &value, const QString &accessibleValue)
{
mValue->setText(value);
mValue->setAccessibleName(accessibleValue);
}
QString InfoField::value() const
{
return mValue->text();
}
void InfoField::setIcon(const QIcon &icon)
{
if (!icon.isNull()) {
const int iconSize = mIcon->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, mIcon);
mIcon->setPixmap(icon.pixmap(iconSize));
mIcon->setVisible(true);
} else {
mIcon->setVisible(false);
mIcon->clear();
}
}
void InfoField::setAction(const QAction *action)
{
if (action == mAction) {
return;
}
if (mAction) {
QObject::disconnect(mButton, {}, mAction, {});
QObject::disconnect(mAction, {}, mButton, {});
}
mAction = action;
if (mAction) {
QObject::connect(mButton, &QPushButton::clicked, action, &QAction::trigger);
QObject::connect(mAction, &QAction::changed, mButton, [this]() {
onActionChanged();
});
onActionChanged();
mButton->setAccessibleName(mAction->property(accessibleNameProperty).toString());
mButton->setVisible(true);
} else {
mButton->setVisible(false);
mButton->setText({});
mButton->setIcon({});
}
}
void InfoField::setToolTip(const QString &toolTip)
{
mValue->setToolTip(toolTip);
}
void InfoField::setVisible(bool visible)
{
mLabel->setVisible(visible);
mIcon->setVisible(visible && !mIcon->pixmap(Qt::ReturnByValue).isNull());
mValue->setVisible(visible);
mButton->setVisible(visible && mAction);
}
void InfoField::onActionChanged()
{
if (!mAction) {
return;
}
if (mAction->text() != mButton->text()) {
mButton->setText(mAction->text());
}
mButton->setIcon(mAction->icon());
if (mAction->toolTip() != mButton->toolTip()) {
mButton->setToolTip(mAction->toolTip());
}
if (mAction->isEnabled() != mButton->isEnabled()) {
mButton->setEnabled(mAction->isEnabled());
}
}
namespace
{
static bool userHasCertificationKey() {
const auto secretKeys = KeyCache::instance()->secretKeys();
return Kleo::any_of(secretKeys, [](const auto &k) {
return (k.protocol() == GpgME::OpenPGP) && canCreateCertifications(k);
});
}
class UserIDTable : public QTreeWidget
{
Q_OBJECT
public:
using QTreeWidget::QTreeWidget;
std::vector<GpgME::UserID> selectedUserIDs() {
std::vector<GpgME::UserID> userIDs;
const auto selected = selectedItems();
std::transform(selected.begin(), selected.end(), std::back_inserter(userIDs), [](const QTreeWidgetItem *item) {
return item->data(0, Qt::UserRole).value<GpgME::UserID>();
});
return userIDs;
}
protected:
QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override
{
// make keyboard navigation with Left/Right possible by switching the selection behavior to SelectItems
// before calling QTreeWidget::moveCursor, because QTreeWidget::moveCursor ignores MoveLeft/MoveRight
// if the selection behavior is SelectRows
if ((cursorAction == MoveLeft) || (cursorAction == MoveRight)) {
setSelectionBehavior(SelectItems);
}
const auto result = QTreeWidget::moveCursor(cursorAction, modifiers);
if ((cursorAction == MoveLeft) || (cursorAction == MoveRight)) {
setSelectionBehavior(SelectRows);
}
return result;
}
};
}
class CertificateDetailsWidget::Private
{
public:
Private(CertificateDetailsWidget *qq);
void setupCommonProperties();
+ void updateUserIDActions();
void setUpUserIDTable();
void setUpSMIMEAdressList();
void setupPGPProperties();
void setupSMIMEProperties();
void revokeUserID(const GpgME::UserID &uid);
+ void revokeSelectedUserID();
void genRevokeCert();
void refreshCertificate();
void certifyUserIDs();
void webOfTrustClicked();
void exportClicked();
void addUserID();
void changePassphrase();
void changeExpiration();
void keysMayHaveChanged();
void showTrustChainDialog();
void showMoreDetails();
void userIDTableContextMenuRequested(const QPoint &p);
QString tofuTooltipString(const GpgME::UserID &uid) const;
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();
private:
CertificateDetailsWidget *const q;
public:
GpgME::Key key;
bool updateInProgress = false;
private:
InfoField *attributeField(const QString &attributeName)
{
if (ui.smimeAttributeFields.contains(attributeName)) {
return ui.smimeAttributeFields.at(attributeName).get();
}
return nullptr;
}
private:
struct UI {
QWidget *userIDs = nullptr;
QLabel *userIDTableLabel = nullptr;
UserIDTable *userIDTable = nullptr;
QPushButton *addUserIDBtn = nullptr;
QPushButton *certifyBtn = nullptr;
+ QPushButton *revokeUserIDBtn = nullptr;
QPushButton *webOfTrustBtn = 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;
QLabel *smimeRelatedAddresses = nullptr;
QListWidget *smimeAddressList = nullptr;
QPushButton *moreDetailsBtn = nullptr;
QPushButton *trustChainDetailsBtn = nullptr;
QPushButton *refreshBtn = nullptr;
QPushButton *changePassphraseBtn = nullptr;
QPushButton *exportBtn = nullptr;
QPushButton *genRevokeBtn = nullptr;
void setupUi(QWidget *parent)
{
auto mainLayout = new QVBoxLayout{parent};
userIDs = new QWidget{parent};
{
auto userIDsLayout = new QVBoxLayout{userIDs};
userIDsLayout->setContentsMargins({});
userIDTableLabel = new QLabel(i18n("User IDs:"), parent);
userIDsLayout->addWidget(userIDTableLabel);
userIDTable = new UserIDTable{parent};
userIDTableLabel->setBuddy(userIDTable);
userIDTable->setAccessibleName(i18n("User IDs"));
QTreeWidgetItem *__qtreewidgetitem = new QTreeWidgetItem();
__qtreewidgetitem->setText(0, QString::fromUtf8("1"));
userIDTable->setHeaderItem(__qtreewidgetitem);
userIDTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
userIDTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
userIDTable->setRootIsDecorated(false);
userIDTable->setUniformRowHeights(true);
userIDTable->setAllColumnsShowFocus(false);
userIDsLayout->addWidget(userIDTable);
{
auto buttonRow = new QHBoxLayout;
addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent);
buttonRow->addWidget(addUserIDBtn);
certifyBtn = new QPushButton(i18nc("@action:button", "Certify User IDs"), parent);
buttonRow->addWidget(certifyBtn);
webOfTrustBtn = new QPushButton(i18nc("@action:button", "Show Certifications"), parent);
buttonRow->addWidget(webOfTrustBtn);
+ revokeUserIDBtn = new QPushButton(i18nc("@action:button", "Revoke User ID"), parent);
+ buttonRow->addWidget(revokeUserIDBtn);
+
buttonRow->addStretch(1);
userIDsLayout->addLayout(buttonRow);
}
userIDsLayout->addWidget(new KSeparator{Qt::Horizontal, parent});
}
mainLayout->addWidget(userIDs);
{
auto gridLayout = new QGridLayout;
gridLayout->setColumnStretch(1, 1);
int 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("Expires:"), parent);
changeExpirationAction = new QAction{parent};
changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor")));
changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the expiration date"));
changeExpirationAction->setProperty(accessibleNameProperty, i18nc("@action:button", "Change expiration"));
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"));
copyFingerprintAction->setProperty(accessibleNameProperty, 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"));
showIssuerCertificateAction->setProperty(accessibleNameProperty, 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);
mainLayout->addLayout(gridLayout);
}
smimeRelatedAddresses = new QLabel(i18n("Related addresses:"), parent);
mainLayout->addWidget(smimeRelatedAddresses);
smimeAddressList = new QListWidget{parent};
smimeRelatedAddresses->setBuddy(smimeAddressList);
smimeAddressList->setAccessibleName(i18n("Related addresses"));
smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers);
smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection);
mainLayout->addWidget(smimeAddressList);
{
auto buttonRow = new QHBoxLayout;
moreDetailsBtn = new QPushButton(i18nc("@action:button", "More Details..."), parent);
buttonRow->addWidget(moreDetailsBtn);
trustChainDetailsBtn = new QPushButton(i18nc("@action:button", "Trust Chain Details"), parent);
buttonRow->addWidget(trustChainDetailsBtn);
refreshBtn = new QPushButton{i18nc("@action:button", "Update"), parent};
#ifndef QGPGME_SUPPORTS_KEY_REFRESH
refreshBtn->setVisible(false);
#endif
buttonRow->addWidget(refreshBtn);
exportBtn = new QPushButton(i18nc("@action:button", "Export"), parent);
buttonRow->addWidget(exportBtn);
changePassphraseBtn = new QPushButton(i18nc("@action:button", "Change Passphrase"), parent);
buttonRow->addWidget(changePassphraseBtn);
genRevokeBtn = new QPushButton(i18nc("@action:button", "Generate Revocation Certificate"), parent);
genRevokeBtn->setToolTip(u"<html>" %
i18n("A revocation certificate is a file that serves as a \"kill switch\" to publicly "
"declare that a key shall not anymore be used. It is not possible "
"to retract such a revocation certificate once it has been published.") %
u"</html>");
buttonRow->addWidget(genRevokeBtn);
buttonRow->addStretch(1);
mainLayout->addLayout(buttonRow);
}
}
} ui;
};
CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq)
: q{qq}
{
ui.setupUi(q);
ui.userIDTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui.userIDTable, &QAbstractItemView::customContextMenuRequested,
q, [this](const QPoint &p) { userIDTableContextMenuRequested(p); });
+ connect(ui.userIDTable, &QTreeWidget::itemSelectionChanged,
+ q, [this]() { updateUserIDActions(); });
connect(ui.addUserIDBtn, &QPushButton::clicked,
q, [this]() { addUserID(); });
+ connect(ui.revokeUserIDBtn, &QPushButton::clicked,
+ q, [this]() { revokeSelectedUserID(); });
connect(ui.changePassphraseBtn, &QPushButton::clicked,
q, [this]() { changePassphrase(); });
connect(ui.genRevokeBtn, &QPushButton::clicked,
q, [this]() { genRevokeCert(); });
connect(ui.changeExpirationAction, &QAction::triggered,
q, [this]() { changeExpiration(); });
connect(ui.showIssuerCertificateAction, &QAction::triggered,
q, [this]() { showIssuerCertificate(); });
connect(ui.trustChainDetailsBtn, &QPushButton::pressed,
q, [this]() { showTrustChainDialog(); });
connect(ui.moreDetailsBtn, &QPushButton::pressed,
q, [this]() { showMoreDetails(); });
connect(ui.refreshBtn, &QPushButton::clicked,
q, [this]() { refreshCertificate(); });
connect(ui.certifyBtn, &QPushButton::clicked,
q, [this]() { certifyUserIDs(); });
connect(ui.webOfTrustBtn, &QPushButton::clicked,
q, [this]() { webOfTrustClicked(); });
connect(ui.exportBtn, &QPushButton::clicked,
q, [this]() { exportClicked(); });
if (ui.copyFingerprintAction) {
connect(ui.copyFingerprintAction, &QAction::triggered,
q, [this]() { copyFingerprintToClipboard(); });
}
connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged,
q, [this]() { keysMayHaveChanged(); });
}
void CertificateDetailsWidget::Private::setupCommonProperties()
{
const bool isOpenPGP = key.protocol() == GpgME::OpenPGP;
const bool isSMIME = key.protocol() == GpgME::CMS;
const bool isOwnKey = key.hasSecret();
// update visibility of UI elements
ui.userIDs->setVisible(isOpenPGP);
ui.addUserIDBtn->setVisible(isOwnKey);
// ui.certifyBtn->setVisible(true); // always visible (for OpenPGP keys)
+ ui.revokeUserIDBtn->setVisible(isOwnKey);
// ui.webOfTrustBtn->setVisible(true); // always visible (for OpenPGP keys)
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(Kleo::gnupgIsDeVsCompliant());
ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties()
ui.smimeRelatedAddresses->setVisible(isSMIME);
ui.smimeAddressList->setVisible(isSMIME);
// ui.moreDetailsBtn->setVisible(true); // always visible
ui.trustChainDetailsBtn->setVisible(isSMIME);
// ui.refreshBtn->setVisible(true); // always visible
ui.changePassphraseBtn->setVisible(isSecretKeyStoredInKeyRing(key));
// ui.exportBtn->setVisible(true); // always visible
ui.genRevokeBtn->setVisible(isOpenPGP && isOwnKey);
// update availability of buttons
ui.addUserIDBtn->setEnabled(canBeUsedForSecretKeyOperations(key));
ui.certifyBtn->setEnabled(userHasCertificationKey());
+ ui.revokeUserIDBtn->setEnabled(false); // requires a selected user ID
ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key));
ui.changePassphraseBtn->setEnabled(isSecretKeyStoredInKeyRing(key));
ui.genRevokeBtn->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("Expires", "never")),
Formatting::accessibleExpirationDate(key, i18nc("Expires", "never")));
ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()),
Formatting::accessibleHexID(key.primaryFingerprint()));
if (Kleo::gnupgIsDeVsCompliant()) {
ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key));
}
}
+void CertificateDetailsWidget::Private::updateUserIDActions()
+{
+ const auto userIDs = ui.userIDTable->selectedUserIDs();
+ ui.revokeUserIDBtn->setEnabled((userIDs.size() == 1) && canCreateCertifications(key) && canRevokeUserID(userIDs.front()));
+}
+
void CertificateDetailsWidget::Private::setUpUserIDTable()
{
ui.userIDTable->clear();
QStringList headers = { i18n("Email"), i18n("Name"), i18n("Trust Level"), i18n("Tags") };
ui.userIDTable->setColumnCount(headers.count());
ui.userIDTable->setColumnWidth(0, 200);
ui.userIDTable->setColumnWidth(1, 200);
ui.userIDTable->setHeaderLabels(headers);
const auto uids = key.userIDs();
for (unsigned int i = 0; i < uids.size(); ++i) {
const auto &uid = uids[i];
auto item = new QTreeWidgetItem;
const QString toolTip = tofuTooltipString(uid);
item->setData(0, Qt::UserRole, QVariant::fromValue(uid));
auto pMail = Kleo::Formatting::prettyEMail(uid);
auto pName = Kleo::Formatting::prettyName(uid);
item->setData(0, Qt::DisplayRole, pMail);
item->setData(0, Qt::ToolTipRole, toolTip);
item->setData(0, Qt::AccessibleTextRole, pMail.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : pMail);
item->setData(1, Qt::DisplayRole, pName);
item->setData(1, Qt::ToolTipRole, toolTip);
item->setData(2, Qt::DecorationRole, trustLevelIcon(uid));
item->setData(2, Qt::DisplayRole, trustLevelText(uid));
item->setData(2, Qt::ToolTipRole, toolTip);
GpgME::Error err;
QStringList tagList;
for (const auto &tag: uid.remarks(Tags::tagKeys(), err)) {
if (err) {
qCWarning(KLEOPATRA_LOG) << "Getting remarks for user ID" << uid.id() << "failed:" << err;
}
tagList << QString::fromStdString(tag);
}
qCDebug(KLEOPATRA_LOG) << "tagList:" << tagList;
const auto tags = tagList.join(QStringLiteral("; "));
item->setData(3, Qt::DisplayRole, tags);
item->setData(3, Qt::ToolTipRole, toolTip);
ui.userIDTable->addTopLevelItem(item);
}
if (!Tags::tagsEnabled()) {
ui.userIDTable->hideColumn(3);
}
}
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() < 2) {
return;
}
// 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);
}
}
}
void CertificateDetailsWidget::Private::revokeUserID(const GpgME::UserID &userId)
{
const QString message = xi18nc(
"@info",
"<para>Do you really want to revoke the user ID<nl/><emphasis>%1</emphasis> ?</para>",
QString::fromUtf8(userId.id()));
auto confirmButton = KStandardGuiItem::yes();
confirmButton.setText(i18nc("@action:button", "Revoke User ID"));
confirmButton.setToolTip({});
const auto choice = KMessageBox::questionYesNo(
q->window(), message, i18nc("@title:window", "Confirm Revocation"),
confirmButton, KStandardGuiItem::cancel(), {}, KMessageBox::Notify | KMessageBox::WindowModal);
if (choice != KMessageBox::Yes) {
return;
}
auto cmd = new Commands::RevokeUserIDCommand(userId);
cmd->setParentWidget(q);
- ui.userIDTable->setEnabled(false);
- connect(cmd, &Command::finished,
- q, [this]() {
+ connect(cmd, &Command::finished, q, [this]() {
ui.userIDTable->setEnabled(true);
+ // the Revoke User ID button will be updated by the key update
updateKey();
});
+ ui.userIDTable->setEnabled(false);
+ ui.revokeUserIDBtn->setEnabled(false);
cmd->start();
}
+void CertificateDetailsWidget::Private::revokeSelectedUserID()
+{
+ const auto userIDs = ui.userIDTable->selectedUserIDs();
+ if (userIDs.size() != 1) {
+ return;
+ }
+ revokeUserID(userIDs.front());
+}
+
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();
}
void CertificateDetailsWidget::Private::changePassphrase()
{
auto cmd = new Kleo::Commands::ChangePassphraseCommand(key);
QObject::connect(cmd, &Kleo::Commands::ChangePassphraseCommand::finished,
q, [this]() {
ui.changePassphraseBtn->setEnabled(true);
});
ui.changePassphraseBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::genRevokeCert()
{
auto cmd = new Kleo::Commands::GenRevokeCommand(key);
QObject::connect(cmd, &Kleo::Commands::GenRevokeCommand::finished,
q, [this]() {
ui.genRevokeBtn->setEnabled(true);
});
ui.genRevokeBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::refreshCertificate()
{
auto cmd = new Kleo::RefreshCertificateCommand{key};
QObject::connect(cmd, &Kleo::RefreshCertificateCommand::finished,
q, [this]() {
ui.refreshBtn->setEnabled(true);
});
ui.refreshBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::certifyUserIDs()
{
const auto userIDs = ui.userIDTable->selectedUserIDs();
auto cmd = userIDs.empty() ? new Kleo::Commands::CertifyCertificateCommand{key} //
: new Kleo::Commands::CertifyCertificateCommand{userIDs};
QObject::connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished,
q, [this]() {
updateKey();
ui.certifyBtn->setEnabled(true);
});
ui.certifyBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::webOfTrustClicked()
{
QScopedPointer<WebOfTrustDialog> dlg(new WebOfTrustDialog(q));
dlg->setKey(key);
dlg->exec();
}
void CertificateDetailsWidget::Private::exportClicked()
{
QScopedPointer<ExportDialog> dlg(new ExportDialog(q));
dlg->setKey(key);
dlg->exec();
}
void CertificateDetailsWidget::Private::addUserID()
{
auto cmd = new Kleo::Commands::AddUserIDCommand(key);
QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished,
q, [this]() {
ui.addUserIDBtn->setEnabled(true);
updateKey();
});
ui.addUserIDBtn->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);
}
}
void CertificateDetailsWidget::Private::showTrustChainDialog()
{
QScopedPointer<TrustChainDialog> dlg(new TrustChainDialog(q));
dlg->setKey(key);
dlg->exec();
}
-namespace
-{
-bool isLastValidUserID(const GpgME::UserID &userId)
-{
- if (isRevokedOrExpired(userId)) {
- return false;
- }
- const auto userIds = userId.parent().userIDs();
- const int numberOfValidUserIds = std::count_if(std::begin(userIds), std::end(userIds),
- [](const auto &u) {
- return !isRevokedOrExpired(u);
- });
- return numberOfValidUserIds == 1;
-}
-
-bool canRevokeUserID(const GpgME::UserID &userId)
-{
- const auto key = userId.parent();
- return (!userId.isNull() //
- && key.protocol() == GpgME::OpenPGP
- && canCreateCertifications(key)
- && !isLastValidUserID(userId));
-}
-}
-
void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QPoint &p)
{
const auto userIDs = ui.userIDTable->selectedUserIDs();
const auto singleUserID = (userIDs.size() == 1) ? userIDs.front() : GpgME::UserID{};
const bool canSignUserIDs = userHasCertificationKey();
auto menu = new QMenu(q);
{
const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Certify User IDs...")
: i18ncp("@action:inmenu", "Certify User ID...", "Certify User IDs...", userIDs.size());
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")),
actionText,
q, [this]() {
certifyUserIDs();
});
action->setEnabled(canSignUserIDs);
}
{
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")),
i18nc("@action:inmenu", "Revoke User ID"),
q, [this, singleUserID]() {
revokeUserID(singleUserID);
});
- action->setEnabled(canRevokeUserID(singleUserID));
+ action->setEnabled(canCreateCertifications(key) && canRevokeUserID(singleUserID));
}
if (Kleo::Commands::RevokeCertificationCommand::isSupported()) {
const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Revoke Certifications...")
: i18ncp("@action:inmenu", "Revoke Certification...", "Revoke Certifications...", userIDs.size());
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")),
actionText,
q, [this, userIDs]() {
auto cmd = userIDs.empty() ? new Kleo::Commands::RevokeCertificationCommand{key} //
: new Kleo::Commands::RevokeCertificationCommand{userIDs};
ui.userIDTable->setEnabled(false);
connect(cmd, &Kleo::Commands::RevokeCertificationCommand::finished,
q, [this]() {
ui.userIDTable->setEnabled(true);
updateKey();
});
cmd->start();
});
action->setEnabled(canSignUserIDs);
}
#ifdef MAILAKONADI_ENABLED
if (key.hasSecret()) {
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")),
i18nc("@action:inmenu", "Publish at Mail Provider ..."),
q, [this, singleUserID]() {
auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(singleUserID);
ui.userIDTable->setEnabled(false);
connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished,
q, [this]() { ui.userIDTable->setEnabled(true); });
cmd->start();
});
action->setEnabled(!singleUserID.isNull());
}
#endif // MAILAKONADI_ENABLED
connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
menu->popup(ui.userIDTable->viewport()->mapToGlobal(p));
}
void CertificateDetailsWidget::Private::showMoreDetails()
{
ui.moreDetailsBtn->setEnabled(false);
if (key.protocol() == GpgME::CMS) {
auto cmd = new Kleo::Commands::DumpCertificateCommand(key);
connect(cmd, &Kleo::Commands::DumpCertificateCommand::finished,
q, [this]() {
ui.moreDetailsBtn->setEnabled(true);
});
cmd->setUseDialog(true);
cmd->start();
} else {
QScopedPointer<SubKeysDialog> dlg(new SubKeysDialog(q));
dlg->setKey(key);
dlg->exec();
ui.moreDetailsBtn->setEnabled(true);
}
}
QString CertificateDetailsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const
{
const auto tofu = uid.tofuInfo();
if (tofu.isNull()) {
return QString();
}
QString html = QStringLiteral("<table border=\"0\" cell-padding=\"5\">");
const auto appendRow = [&html](const QString &lbl, const QString &val) {
html += QStringLiteral("<tr>"
"<th style=\"text-align: right; padding-right: 5px; white-space: nowrap;\">%1:</th>"
"<td style=\"white-space: nowrap;\">%2</td>"
"</tr>")
.arg(lbl, val);
};
const auto appendHeader = [this, &html](const QString &hdr) {
html += QStringLiteral("<tr><th colspan=\"2\" style=\"background-color: %1; color: %2\">%3</th></tr>")
.arg(q->palette().highlight().color().name(),
q->palette().highlightedText().color().name(),
hdr);
};
const auto dateTime = [](long ts) {
QLocale l;
return ts == 0 ? i18n("never") : l.toString(QDateTime::fromSecsSinceEpoch(ts), QLocale::ShortFormat);
};
appendHeader(i18n("Signing"));
appendRow(i18n("First message"), dateTime(tofu.signFirst()));
appendRow(i18n("Last message"), dateTime(tofu.signLast()));
appendRow(i18n("Message count"), QString::number(tofu.signCount()));
appendHeader(i18n("Encryption"));
appendRow(i18n("First message"), dateTime(tofu.encrFirst()));
appendRow(i18n("Last message"), dateTime(tofu.encrLast()));
appendRow(i18n("Message count"), QString::number(tofu.encrCount()));
html += QStringLiteral("</table>");
// Make sure the tooltip string is different for each UserID, even if the
// data are the same, otherwise the tooltip is not updated and moved when
// user moves mouse from one row to another.
html += QStringLiteral("<!-- %1 //-->").arg(QString::fromUtf8(uid.id()));
return html;
}
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::setupPGPProperties()
{
setUpUserIDTable();
const auto trustDomains = accumulateTrustDomains(key.userIDs());
ui.trustedIntroducerField->setVisible(!trustDomains.empty());
ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", "));
ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the key from external sources."));
}
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()
{
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());
setUpSMIMEAdressList();
ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the CRLs and do a full validation check of the certificate."));
}
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(), nullptr);
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 &)
{
updateInProgress = 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->updateInProgress = 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() << QLatin1String(key.primaryFingerprint()));
}
GpgME::Key CertificateDetailsWidget::key() const
{
return d->key;
}
#include "certificatedetailswidget.moc"
#include "moc_certificatedetailswidget.cpp"
diff --git a/src/utils/keys.cpp b/src/utils/keys.cpp
index 00918da9f..26d89f671 100644
--- a/src/utils/keys.cpp
+++ b/src/utils/keys.cpp
@@ -1,56 +1,79 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/keys.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keys.h"
#include <QByteArray>
// needed for GPGME_VERSION_NUMBER
#include <gpgme.h>
#include <algorithm>
#include <iterator>
+namespace
+{
+bool isLastValidUserID(const GpgME::UserID &userId)
+{
+ if (Kleo::isRevokedOrExpired(userId)) {
+ return false;
+ }
+ const auto userIds = userId.parent().userIDs();
+ const int numberOfValidUserIds = std::count_if(std::begin(userIds), std::end(userIds),
+ [](const auto &u) {
+ return !Kleo::isRevokedOrExpired(u);
+ });
+ return numberOfValidUserIds == 1;
+}
+}
+
bool Kleo::isSelfSignature(const GpgME::UserID::Signature &signature)
{
return !qstrcmp(signature.parent().parent().keyID(), signature.signerKeyID());
}
bool Kleo::isRevokedOrExpired(const GpgME::UserID &userId)
{
const auto sigs = userId.signatures();
std::vector<GpgME::UserID::Signature> selfSigs;
std::copy_if(std::begin(sigs), std::end(sigs), std::back_inserter(selfSigs), &Kleo::isSelfSignature);
std::sort(std::begin(selfSigs), std::end(selfSigs));
// check the most recent signature
const auto sig = !selfSigs.empty() ? selfSigs.back() : GpgME::UserID::Signature{};
return !sig.isNull() && (sig.isRevokation() || sig.isExpired());
}
bool Kleo::canCreateCertifications(const GpgME::Key &key)
{
return key.canCertify() && canBeUsedForSecretKeyOperations(key);
}
bool Kleo::canBeUsedForSecretKeyOperations(const GpgME::Key &key)
{
#if GPGME_VERSION_NUMBER >= 0x011102 // 1.17.2
// we need to check the primary subkey because Key::hasSecret() is also true if just the secret key stub of an offline key is available
return key.subkey(0).isSecret();
#else
// older versions of GpgME did not always set the secret flag for card keys
return key.subkey(0).isSecret() || key.subkey(0).isCardKey();
#endif
}
+bool Kleo::canRevokeUserID(const GpgME::UserID &userId)
+{
+ return (!userId.isNull() //
+ && userId.parent().protocol() == GpgME::OpenPGP
+ && !isLastValidUserID(userId));
+}
+
bool Kleo::isSecretKeyStoredInKeyRing(const GpgME::Key &key)
{
return key.subkey(0).isSecret() && !key.subkey(0).isCardKey();
}
diff --git a/src/utils/keys.h b/src/utils/keys.h
index 928034e7d..f6d1726c4 100644
--- a/src/utils/keys.h
+++ b/src/utils/keys.h
@@ -1,55 +1,61 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/keys.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <gpgme++/key.h>
namespace Kleo
{
struct CertificatePair {
GpgME::Key openpgp;
GpgME::Key cms;
};
/** Returns true if \p signature is a self-signature. */
bool isSelfSignature(const GpgME::UserID::Signature &signature);
/**
* Returns true if the most recent self-signature of \p userId is a revocation
* signature or if it has expired.
*/
bool isRevokedOrExpired(const GpgME::UserID &userId);
/**
* Returns true if \p key can be used to certify user IDs, i.e. if the key
* has the required capability and if the secret key of the (primary)
* certification subkey is available in the keyring or on a smart card.
*/
bool canCreateCertifications(const GpgME::Key &key);
/**
* Returns true if \p key can be used for operations requiring the secret key,
* i.e. if the secret key of the primary key pair is available in the keyring
* or on a smart card.
*
* \note Key::hasSecret() also returns true if a secret key stub, e.g. of an
* offline key, is available in the keyring.
*/
bool canBeUsedForSecretKeyOperations(const GpgME::Key &key);
+/**
+ * Returns true if \p userId can be revoked, i.e. if it isn't the last valid
+ * user ID of an OpenPGP key.
+ */
+bool canRevokeUserID(const GpgME::UserID &userId);
+
/**
* Returns true if the secret key of the primary key pair of \p key is stored
* in the keyring.
*/
bool isSecretKeyStoredInKeyRing(const GpgME::Key &key);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Dec 5, 5:00 AM (1 d, 3 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
5c/b3/73525a5942b8882a6b904bb99b4b
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment