diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp index 6ebf631f7..a10286522 100644 --- a/src/ui/newkeyapprovaldialog.cpp +++ b/src/ui/newkeyapprovaldialog.cpp @@ -1,916 +1,916 @@ /* -*- c++ -*- newkeyapprovaldialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "newkeyapprovaldialog.h" #include "keyselectioncombo.h" #include "progressdialog.h" #include "kleo/defaultkeyfilter.h" #include "utils/formatting.h" #include "utils/gnupg.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkleo_debug.h" using namespace Kleo; using namespace GpgME; QDebug operator<<(QDebug debug, const GpgME::Key &key) { if (key.isNull()) { debug << "Null"; } else { debug << Formatting::summaryLine(key); } return debug.maybeSpace(); } namespace { class EncryptFilter: public DefaultKeyFilter { public: EncryptFilter() : DefaultKeyFilter() { setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_encryptFilter = std::shared_ptr(new EncryptFilter); class OpenPGPFilter: public DefaultKeyFilter { public: OpenPGPFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::Set); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpEncryptFilter = std::shared_ptr (new OpenPGPFilter); class OpenPGPSignFilter: public DefaultKeyFilter { public: OpenPGPSignFilter() : DefaultKeyFilter() { /* Also list unusable keys to make it transparent why they are unusable */ setDisabled(DefaultKeyFilter::NotSet); setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanSign(DefaultKeyFilter::Set); setHasSecret(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpSignFilter = std::shared_ptr (new OpenPGPSignFilter); class SMIMEFilter: public DefaultKeyFilter { public: SMIMEFilter(): DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_smimeEncryptFilter = std::shared_ptr (new SMIMEFilter); class SMIMESignFilter: public DefaultKeyFilter { public: SMIMESignFilter(): DefaultKeyFilter() { setDisabled(DefaultKeyFilter::NotSet); setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanSign(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); } }; static std::shared_ptr s_smimeSignFilter = std::shared_ptr (new SMIMESignFilter); /* Some decoration and a button to remove the filter for a keyselectioncombo */ class ComboWidget: public QWidget { Q_OBJECT public: explicit ComboWidget(KeySelectionCombo *combo): mCombo(combo), mFilterBtn(new QPushButton) { auto hLay = new QHBoxLayout(this); auto infoBtn = new QPushButton; infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); infoBtn->setIconSize(QSize(22,22)); infoBtn->setFlat(true); hLay->addWidget(infoBtn); hLay->addWidget(combo, 1); hLay->addWidget(mFilterBtn, 0); connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn] () { QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0), mCombo->currentData(Qt::ToolTipRole).toString(), infoBtn, QRect(), 30000); }); // FIXME: This is ugly to enforce but otherwise the // icon is broken. combo->setMinimumHeight(22); mFilterBtn->setMinimumHeight(23); updateFilterButton(); connect(mFilterBtn, &QPushButton::clicked, this, [this] () { const QString curFilter = mCombo->idFilter(); if (curFilter.isEmpty()) { setIdFilter(mLastIdFilter); mLastIdFilter = QString(); } else { setIdFilter(QString()); mLastIdFilter = curFilter; } }); } void setIdFilter(const QString &id) { mCombo->setIdFilter(id); updateFilterButton(); } void updateFilterButton() { if (mCombo->idFilter().isEmpty()) { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters"))); mFilterBtn->setToolTip(i18n("Show keys matching the email address")); } else { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setToolTip(i18n("Show all keys")); } } KeySelectionCombo *combo() { return mCombo; } GpgME::Protocol fixedProtocol() const { return mFixedProtocol; } void setFixedProtocol(GpgME::Protocol proto) { mFixedProtocol = proto; } private: KeySelectionCombo *mCombo; QPushButton *mFilterBtn; QString mLastIdFilter; GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol; }; static enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key) { enum GpgME::UserID::Validity validity = GpgME::UserID::Validity::Unknown; for (const auto &uid: key.userIDs()) { if (validity == GpgME::UserID::Validity::Unknown || validity > uid.validity()) { validity = uid.validity(); } } return validity; } static bool key_has_addr(const GpgME::Key &key, const QString &addr) { for (const auto &uid: key.userIDs()) { if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) { return true; } } return false; } -bool anyKeyHasProtocol(const std::vector &keys, Protocol protocol) +bool anyKeyHasProtocol(const std::vector &keys, GpgME::Protocol protocol) { return std::any_of(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; }); } -Key findfirstKeyOfType(const std::vector &keys, Protocol protocol) +Key findfirstKeyOfType(const std::vector &keys, GpgME::Protocol protocol) { const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; }); return it != std::end(keys) ? *it : Key(); } } // namespace class NewKeyApprovalDialog::Private { private: enum Action { Unset, GenerateKey, IgnoreKey, }; public: enum { OpenPGPButtonId = 1, SMIMEButtonId = 2 }; Private(NewKeyApprovalDialog *qq, bool encrypt, bool sign, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, const QString &sender, bool allowMixed) : mForcedProtocol{forcedProtocol} , mSender{sender} , mSign{sign} , mEncrypt{encrypt} , mAllowMixed{allowMixed} , q{qq} { Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol); Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol)); Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol)); // We do the translation here to avoid having the same string multiple times. mGenerateTooltip = i18nc("@info:tooltip for a 'Generate new key pair' action " "in a combobox when a user does not yet have an OpenPGP or S/MIME key.", "Generate a new key using your E-Mail address.

" "The key is necessary to decrypt and sign E-Mails. " "You will be asked for a passphrase to protect this key and the protected key " "will be stored in your home directory."); mMainLay = new QVBoxLayout; QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mOkButton = btnBox->button(QDialogButtonBox::Ok); #ifndef NDEBUG mOkButton->setObjectName(QStringLiteral("ok button")); #endif QObject::connect (btnBox, &QDialogButtonBox::accepted, q, [this] () { accepted(); }); QObject::connect (btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject); mScrollArea = new QScrollArea; mScrollArea->setWidget(new QWidget); mScrollLayout = new QVBoxLayout; mScrollArea->widget()->setLayout(mScrollLayout); mScrollArea->setWidgetResizable(true); mScrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); mScrollArea->setFrameStyle(QFrame::NoFrame); mScrollLayout->setContentsMargins(0, 0, 0, 0); q->setWindowTitle(i18nc("@title:window", "Security approval")); auto fmtLayout = new QHBoxLayout; mFormatBtns = new QButtonGroup(qq); QAbstractButton *pgpBtn; QAbstractButton *smimeBtn; if (mAllowMixed) { pgpBtn = new QCheckBox(i18n("OpenPGP")); smimeBtn = new QCheckBox(i18n("S/MIME")); } else { pgpBtn = new QRadioButton(i18n("OpenPGP")); smimeBtn = new QRadioButton(i18n("S/MIME")); } #ifndef NDEBUG pgpBtn->setObjectName(QStringLiteral("openpgp button")); smimeBtn->setObjectName(QStringLiteral("smime button")); #endif mFormatBtns->addButton(pgpBtn, OpenPGPButtonId); mFormatBtns->addButton(smimeBtn, SMIMEButtonId); mFormatBtns->setExclusive(!mAllowMixed); fmtLayout->addStretch(-1); fmtLayout->addWidget(pgpBtn); fmtLayout->addWidget(smimeBtn); mMainLay->addLayout(fmtLayout); if (mForcedProtocol != GpgME::UnknownProtocol) { pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP); smimeBtn->setChecked(mForcedProtocol == GpgME::CMS); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else { pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol); smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol); } QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) { // ensure that at least one protocol button is checked if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) { mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true); } updateWidgets(); }); mMainLay->addWidget(mScrollArea); mComplianceLbl = new QLabel; mComplianceLbl->setVisible(false); #ifndef NDEBUG mComplianceLbl->setObjectName(QStringLiteral("compliance label")); #endif auto btnLayout = new QHBoxLayout; btnLayout->addWidget(mComplianceLbl); btnLayout->addWidget(btnBox); mMainLay->addLayout(btnLayout); q->setLayout(mMainLay); } ~Private() = default; - Protocol currentProtocol() + GpgME::Protocol currentProtocol() { const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked(); const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked(); if (mAllowMixed) { if (openPGPButtonChecked && !smimeButtonChecked) { - return OpenPGP; + return GpgME::OpenPGP; } if (!openPGPButtonChecked && smimeButtonChecked) { - return CMS; + return GpgME::CMS; } } else if (openPGPButtonChecked) { - return OpenPGP; + return GpgME::OpenPGP; } else if (smimeButtonChecked) { - return CMS; + return GpgME::CMS; } - return UnknownProtocol; + return GpgME::UnknownProtocol; } auto findVisibleKeySelectionComboWithGenerateKey() { const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [] (auto combo) { return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey; }); return it != std::end(mAllCombos) ? *it : nullptr; } void generateKey(KeySelectionCombo *combo) { const auto &addr = combo->property("address").toString(); auto job = new QGpgME::DefaultKeyGenerationJob(q); auto progress = new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") + i18n("This can take several minutes."), q); progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint); progress->setWindowTitle(i18nc("@title:window", "Key generation")); progress->setModal(true); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setValue(0); mRunningJobs << job; connect (job, &QGpgME::DefaultKeyGenerationJob::result, q, [this, job, combo] (const GpgME::KeyGenerationResult &result) { handleKeyGenResult(result, job, combo); }); job->start(addr, QString()); return; } void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo) { mLastError = result.error(); if (!mLastError || mLastError.isCanceled()) { combo->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP); connect (combo, &KeySelectionCombo::keyListingFinished, q, [this, job] () { mRunningJobs.removeAll(job); }); combo->refreshKeys(); } else { mRunningJobs.removeAll(job); } } void checkAccepted() { if (mLastError || mLastError.isCanceled()) { KMessageBox::error(q, QString::fromLocal8Bit(mLastError.asString()), i18n("Operation Failed")); mRunningJobs.clear(); return; } if (!mRunningJobs.empty()) { return; } /* Save the keys */ mAcceptedResult.protocol = currentProtocol(); for (const auto combo: std::as_const(mEncCombos)) { const auto addr = combo->property("address").toString(); const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.encryptionKeys[addr].push_back(key); } for (const auto combo: std::as_const(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.signingKeys.push_back(key); } q->accept(); } void accepted() { // We can assume everything was validly resolved, otherwise // the OK button would have been disabled. // Handle custom items now. if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) { generateKey(combo); return; } checkAccepted(); } - auto encryptionKeyFilter(Protocol protocol) + auto encryptionKeyFilter(GpgME::Protocol protocol) { switch (protocol) { case OpenPGP: return s_pgpEncryptFilter; case CMS: return s_smimeEncryptFilter; default: return s_encryptFilter; } } void updateWidgets() { - const Protocol protocol = currentProtocol(); + const GpgME::Protocol protocol = currentProtocol(); const auto encryptionFilter = encryptionKeyFilter(protocol); for (auto combo: std::as_const(mSigningCombos)) { auto widget = qobject_cast(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget"; continue; } - widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol); + widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); } for (auto combo: std::as_const(mEncCombos)) { auto widget = qobject_cast(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find combo widget"; continue; } - widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol); + widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); if (widget->isVisible() && combo->property("address") != mSender) { combo->setKeyFilter(encryptionFilter); } } // hide the labels indicating the protocol of the sender's keys if only a single protocol is active const auto protocolLabels = q->findChildren(QStringLiteral("protocol label")); for (auto label: protocolLabels) { - label->setVisible(protocol == UnknownProtocol); + label->setVisible(protocol == GpgME::UnknownProtocol); } } - auto createProtocolLabel(Protocol protocol) + auto createProtocolLabel(GpgME::Protocol protocol) { auto label = new QLabel(Formatting::displayName(protocol)); label->setObjectName(QStringLiteral("protocol label")); return label; } - ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, Protocol protocol = UnknownProtocol) + ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol protocol = GpgME::UnknownProtocol) { Q_ASSERT(!key.isNull() || protocol != UnknownProtocol); protocol = !key.isNull() ? key.protocol() : protocol; auto combo = new KeySelectionCombo(); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("signing key")); #endif if (protocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpSignFilter); } else if (protocol == GpgME::CMS) { combo->setKeyFilter(s_smimeSignFilter); } if (key.isNull() || key_has_addr(key, mSender)) { comboWidget->setIdFilter(mSender); } comboWidget->setFixedProtocol(protocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol); } if (key.isNull() && protocol == OpenPGP) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); } combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")), i18n("Don't confirm identity and integrity"), IgnoreKey, i18nc("@info:tooltip for not selecting a key for signing.", "The E-Mail will not be cryptographically signed.")); mSigningCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () { updateOkButton(); }); connect(combo, qOverload(&QComboBox::currentIndexChanged), q, [this] () { updateOkButton(); }); return comboWidget; } void setSigningKeys(const std::vector &preferredKeys, const std::vector &alternativeKeys) { auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender)); group->setAlignment(Qt::AlignLeft); auto sigLayout = new QVBoxLayout(group); const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(OpenPGP)); } const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP); const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP); if (!preferredKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; auto comboWidget = createSigningCombo(mSender, preferredKey); sigLayout->addWidget(comboWidget); } else if (!alternativeKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; auto comboWidget = createSigningCombo(mSender, alternativeKey); sigLayout->addWidget(comboWidget); } else { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key"; auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP); sigLayout->addWidget(comboWidget); } } if (mayNeedCMS) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(CMS)); } const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS); const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS); if (!preferredKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; auto comboWidget = createSigningCombo(mSender, preferredKey); sigLayout->addWidget(comboWidget); } else if (!alternativeKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; auto comboWidget = createSigningCombo(mSender, alternativeKey); sigLayout->addWidget(comboWidget); } else { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key"; auto comboWidget = createSigningCombo(mSender, Key(), CMS); sigLayout->addWidget(comboWidget); } } mScrollLayout->addWidget(group); } - ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, Protocol fixedProtocol) + ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol fixedProtocol) { auto combo = new KeySelectionCombo(false); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("encryption key")); #endif if (fixedProtocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpEncryptFilter); } else if (fixedProtocol == GpgME::CMS) { combo->setKeyFilter(s_smimeEncryptFilter); } else { combo->setKeyFilter(s_encryptFilter); } if (key.isNull() || key_has_addr (key, addr)) { comboWidget->setIdFilter(addr); } comboWidget->setFixedProtocol(fixedProtocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol); } if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); } combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")), i18n("No key. Recipient will be unable to decrypt."), IgnoreKey, i18nc("@info:tooltip for No Key selected for a specific recipient.", "Do not select a key for this recipient.

" "The recipient will receive the encrypted E-Mail, but it can only " "be decrypted with the other keys selected in this dialog.")); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () { updateOkButton(); }); connect(combo, qOverload(&QComboBox::currentIndexChanged), q, [this] () { updateOkButton(); }); mEncCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); return comboWidget; } void addEncryptionAddr(const QString &addr, - Protocol preferredKeysProtocol, const std::vector &preferredKeys, - Protocol alternativeKeysProtocol, const std::vector &alternativeKeys, + GpgME::Protocol preferredKeysProtocol, const std::vector &preferredKeys, + GpgME::Protocol alternativeKeysProtocol, const std::vector &alternativeKeys, QGridLayout *encGrid) { if (addr == mSender) { const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0); } for (const auto &key : preferredKeys) { if (key.protocol() == OpenPGP) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } for (const auto &key : alternativeKeys) { if (key.protocol() == OpenPGP) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (mayNeedCMS) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0); } for (const auto &key : preferredKeys) { if (key.protocol() == CMS) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } for (const auto &key : alternativeKeys) { if (key.protocol() == CMS) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } } else { encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0); for (const auto &key : preferredKeys) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } for (const auto &key : alternativeKeys) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } if (!mAllowMixed) { if (preferredKeys.empty()) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol) << "key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(alternativeKeysProtocol) << "key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } else { if (preferredKeys.empty() && alternativeKeys.empty()) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } } } - void setEncryptionKeys(Protocol preferredKeysProtocol, const QMap> &preferredKeys, - Protocol alternativeKeysProtocol, const QMap> &alternativeKeys) + void setEncryptionKeys(GpgME::Protocol preferredKeysProtocol, const QMap> &preferredKeys, + GpgME::Protocol alternativeKeysProtocol, const QMap> &alternativeKeys) { { auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender)); #ifndef NDEBUG group->setObjectName(QStringLiteral("encrypt-to-self box")); #endif group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout(group); addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid); encGrid->setColumnStretch(1, -1); mScrollLayout->addWidget(group); } const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) { return recipient != mSender; }); if (hasOtherRecipients) { auto group = new QGroupBox(i18n("Encrypt to others:")); #ifndef NDEBUG group->setObjectName(QStringLiteral("encrypt-to-others box")); #endif group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout{group}; for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) { const auto &address = it.key(); const auto &keys = it.value(); if (address != mSender) { addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid); } } encGrid->setColumnStretch(1, -1); mScrollLayout->addWidget(group); } mScrollLayout->addStretch(-1); } void updateOkButton() { static QString origOkText = mOkButton->text(); const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey()); const bool allVisibleEncryptionKeysAreIgnored = std::all_of(std::begin(mEncCombos), std::end(mEncCombos), [] (auto combo) { return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey; }); // If we don't encrypt the ok button is always enabled. But otherwise // we only enable it if we encrypt to at least one recipient. mOkButton->setEnabled(!mEncrypt || !allVisibleEncryptionKeysAreIgnored); mOkButton->setText(isGenerate ? i18n("Generate") : origOkText); if (!Kleo::gnupgUsesDeVsCompliance()) { return; } // Handle compliance bool de_vs = Kleo::gnupgIsDeVsCompliant(); if (de_vs) { - const Protocol protocol = currentProtocol(); + const GpgME::Protocol protocol = currentProtocol(); for (const auto combo: std::as_const(mAllCombos)) { if (!combo->isVisible()) { continue; } const auto key = combo->currentKey(); if (key.isNull()) { continue; } - if (protocol != UnknownProtocol && key.protocol() != protocol) { + if (protocol != GpgME::UnknownProtocol && key.protocol() != protocol) { continue; } if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } } mOkButton->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); mOkButton->setStyleSheet(QStringLiteral("background-color: ") + (de_vs ? QStringLiteral("#D5FAE2") // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : QStringLiteral("#FAE9EB"))); //KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); mComplianceLbl->setText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication possible.", Formatting::deVsString()) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication not possible.", Formatting::deVsString())); mComplianceLbl->setVisible(true); } GpgME::Protocol mForcedProtocol; QList mSigningCombos; QList mEncCombos; QList mAllCombos; QScrollArea *mScrollArea; QVBoxLayout *mScrollLayout; QPushButton *mOkButton; QVBoxLayout *mMainLay; QButtonGroup *mFormatBtns; QString mSender; bool mSign; bool mEncrypt; bool mAllowMixed; NewKeyApprovalDialog *q; QList mRunningJobs; GpgME::Error mLastError; QLabel *mComplianceLbl; KeyResolver::Solution mAcceptedResult; QString mGenerateTooltip; }; NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt, bool sign, const QString &sender, KeyResolver::Solution preferredSolution, KeyResolver::Solution alternativeSolution, bool allowMixed, GpgME::Protocol forcedProtocol, QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f) , d{std::make_unique(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)} { if (sign) { d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys)); } if (encrypt) { d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol, std::move(preferredSolution.encryptionKeys), allowMixed ? UnknownProtocol : alternativeSolution.protocol, std::move(alternativeSolution.encryptionKeys)); } d->updateWidgets(); d->updateOkButton(); const auto size = sizeHint(); const auto desk = QApplication::desktop()->screenGeometry(this); resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2))); } Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default; KeyResolver::Solution NewKeyApprovalDialog::result() { return d->mAcceptedResult; } #include "newkeyapprovaldialog.moc" diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp index 2fdaeb3ea..a22082d61 100644 --- a/src/utils/formatting.cpp +++ b/src/utils/formatting.cpp @@ -1,1260 +1,1260 @@ /* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*- utils/formatting.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "formatting.h" #include "kleo/dn.h" #include "kleo/keyfiltermanager.h" #include "kleo/keygroup.h" #include "utils/cryptoconfig.h" #include "utils/gnupg.h" #include #include #include #include #include #include #include #include #include // for Qt::escape #include #include #include #include "models/keycache.h" using namespace GpgME; using namespace Kleo; // // Name // QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_) { - if (proto == OpenPGP) { + if (proto == GpgME::OpenPGP) { const QString name = QString::fromUtf8(name_); if (name.isEmpty()) { return QString(); } const QString comment = QString::fromUtf8(comment_); if (comment.isEmpty()) { return name; } return QStringLiteral("%1 (%2)").arg(name, comment); } - if (proto == CMS) { + if (proto == GpgME::CMS) { const DN subject(id); const QString cn = subject[QStringLiteral("CN")].trimmed(); if (cn.isEmpty()) { return subject.prettyDN(); } return cn; } return QString(); } QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_) { return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_)); } QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment) { - if (proto == OpenPGP) { + if (proto == GpgME::OpenPGP) { if (name.isEmpty()) { if (email.isEmpty()) { return QString(); } else if (comment.isEmpty()) { return QStringLiteral("<%1>").arg(email); } else { return QStringLiteral("(%2) <%1>").arg(email, comment); } } if (email.isEmpty()) { if (comment.isEmpty()) { return name; } else { return QStringLiteral("%1 (%2)").arg(name, comment); } } if (comment.isEmpty()) { return QStringLiteral("%1 <%2>").arg(name, email); } else { return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment); } } - if (proto == CMS) { + if (proto == GpgME::CMS) { const DN subject(id); const QString cn = subject[QStringLiteral("CN")].trimmed(); if (cn.isEmpty()) { return subject.prettyDN(); } return cn; } return QString(); } QString Formatting::prettyUserID(const UserID &uid) { - if (uid.parent().protocol() == OpenPGP) { + if (uid.parent().protocol() == GpgME::OpenPGP) { return prettyNameAndEMail(uid); } const QByteArray id = QByteArray(uid.id()).trimmed(); if (id.startsWith('<')) { return prettyEMail(uid.email(), uid.id()); } if (id.startsWith('(')) // ### parse uri/dns: { return QString::fromUtf8(uid.id()); } else { return DN(uid.id()).prettyDN(); } } QString Formatting::prettyKeyID(const char *id) { if (!id) { return QString(); } return QLatin1String("0x") + QString::fromLatin1(id).toUpper(); } QString Formatting::prettyNameAndEMail(const UserID &uid) { return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment()); } QString Formatting::prettyNameAndEMail(const Key &key) { return prettyNameAndEMail(key.userID(0)); } QString Formatting::prettyName(const Key &key) { return prettyName(key.userID(0)); } QString Formatting::prettyName(const UserID &uid) { return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment()); } QString Formatting::prettyName(const UserID::Signature &sig) { - return prettyName(OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment()); + return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment()); } // // EMail // QString Formatting::prettyEMail(const Key &key) { for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) { const QString email = prettyEMail(key.userID(i)); if (!email.isEmpty()) { return email; } } return QString(); } QString Formatting::prettyEMail(const UserID &uid) { return prettyEMail(uid.email(), uid.id()); } QString Formatting::prettyEMail(const UserID::Signature &sig) { return prettyEMail(sig.signerEmail(), sig.signerUserID()); } QString Formatting::prettyEMail(const char *email_, const char *id) { QString email; QString name; QString comment; if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) { return email; } else { return DN(id)[QStringLiteral("EMAIL")].trimmed(); } } // // Tooltip // namespace { static QString protect_whitespace(QString s) { static const QLatin1Char SP(' '); static const QLatin1Char NBSP('\xA0'); return s.replace(SP, NBSP); } template QString format_row(const QString &field, const T_arg &arg) { return QStringLiteral("%1:%2").arg(protect_whitespace(field), arg); } QString format_row(const QString &field, const QString &arg) { return QStringLiteral("%1:%2").arg(protect_whitespace(field), arg.toHtmlEscaped()); } QString format_row(const QString &field, const char *arg) { return format_row(field, QString::fromUtf8(arg)); } QString format_keytype(const Key &key) { const Subkey subkey = key.subkey(0); if (key.hasSecret()) { return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } else { return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } } QString format_subkeytype(const Subkey &subkey) { const auto algo = subkey.publicKeyAlgorithm(); if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) { return QString::fromStdString(subkey.algoName()); } return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } QString format_keyusage(const Key &key) { QStringList capabilities; if (key.canReallySign()) { if (key.isQualified()) { capabilities.push_back(i18n("Signing (Qualified)")); } else { capabilities.push_back(i18n("Signing")); } } if (key.canEncrypt()) { capabilities.push_back(i18n("Encryption")); } if (key.canCertify()) { capabilities.push_back(i18n("Certifying User-IDs")); } if (key.canAuthenticate()) { capabilities.push_back(i18n("SSH Authentication")); } return capabilities.join(QLatin1String(", ")); } QString format_subkeyusage(const Subkey &subkey) { QStringList capabilities; if (subkey.canSign()) { if (subkey.isQualified()) { capabilities.push_back(i18n("Signing (Qualified)")); } else { capabilities.push_back(i18n("Signing")); } } if (subkey.canEncrypt()) { capabilities.push_back(i18n("Encryption")); } if (subkey.canCertify()) { capabilities.push_back(i18n("Certifying User-IDs")); } if (subkey.canAuthenticate()) { capabilities.push_back(i18n("SSH Authentication")); } return capabilities.join(QLatin1String(", ")); } static QString time_t2string(time_t t) { QDateTime dt; dt.setTime_t(t); return QLocale().toString(dt, QLocale::ShortFormat); } static QString make_red(const QString &txt) { return QLatin1String("") + txt.toHtmlEscaped() + QLatin1String(""); } } QString Formatting::toolTip(const Key &key, int flags) { - if (flags == 0 || (key.protocol() != CMS && key.protocol() != OpenPGP)) { + if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) { return QString(); } const Subkey subkey = key.subkey(0); QString result; if (flags & Validity) { - if (key.protocol() == OpenPGP || (key.keyListMode() & Validate)) { + if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) { if (key.isRevoked()) { result = make_red(i18n("Revoked")); } else if (key.isExpired()) { result = make_red(i18n("Expired")); } else if (key.isDisabled()) { result = i18n("Disabled"); } else if (key.keyListMode() & GpgME::Validate) { unsigned int fullyTrusted = 0; for (const auto &uid: key.userIDs()) { if (uid.validity() >= UserID::Validity::Full) { fullyTrusted++; } } if (fullyTrusted == key.numUserIDs()) { result = i18n("All User-IDs are certified."); const auto compliance = complianceStringForKey(key); if (!compliance.isEmpty()) { result += QStringLiteral("
") + compliance; } } else { result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted); } } else { result = i18n("The validity cannot be checked at the moment."); } } else { result = i18n("The validity cannot be checked at the moment."); } } if (flags == Validity) { return result; } result += QLatin1String(""); - if (key.protocol() == CMS) { + if (key.protocol() == GpgME::CMS) { if (flags & SerialNumber) { result += format_row(i18n("Serial number"), key.issuerSerial()); } if (flags & Issuer) { result += format_row(i18n("Issuer"), key.issuerName()); } } if (flags & UserIDs) { const std::vector uids = key.userIDs(); if (!uids.empty()) { - result += format_row(key.protocol() == CMS + result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), prettyUserID(uids.front())); } if (uids.size() > 1) { for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) { if (!it->isRevoked() && !it->isInvalid()) { result += format_row(i18n("a.k.a."), prettyUserID(*it)); } } } } if (flags & ExpiryDates) { result += format_row(i18n("Created"), time_t2string(subkey.creationTime())); if (key.isExpired()) { result += format_row(i18n("Expired"), time_t2string(subkey.expirationTime())); } else if (!subkey.neverExpires()) { result += format_row(i18n("Expires"), time_t2string(subkey.expirationTime())); } } if (flags & CertificateType) { result += format_row(i18n("Type"), format_keytype(key)); } if (flags & CertificateUsage) { result += format_row(i18n("Usage"), format_keyusage(key)); } if (flags & KeyID) { result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID())); } if (flags & Fingerprint) { result += format_row(i18n("Fingerprint"), key.primaryFingerprint()); } if (flags & OwnerTrust) { - if (key.protocol() == OpenPGP) { + if (key.protocol() == GpgME::OpenPGP) { result += format_row(i18n("Certification trust"), ownerTrustShort(key)); } else if (key.isRoot()) { result += format_row(i18n("Trusted issuer?"), key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") : /* else */ i18n("No")); } } if (flags & StorageLocation) { if (const char *card = subkey.cardSerialNumber()) { result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); } } if (flags & Subkeys) { for (const auto &sub: key.subkeys()) { result += QLatin1String("
"); result += format_row(i18n("Subkey"), sub.fingerprint()); if (sub.isRevoked()) { result += format_row(i18n("Status"), i18n("Revoked")); } else if (sub.isExpired()) { result += format_row(i18n("Status"), i18n("Expired")); } if (flags & ExpiryDates) { result += format_row(i18n("Created"), time_t2string(sub.creationTime())); if (key.isExpired()) { result += format_row(i18n("Expired"), time_t2string(sub.expirationTime())); } else if (!subkey.neverExpires()) { result += format_row(i18n("Expires"), time_t2string(sub.expirationTime())); } } if (flags & CertificateType) { result += format_row(i18n("Type"), format_subkeytype(sub)); } if (flags & CertificateUsage) { result += format_row(i18n("Usage"), format_subkeyusage(sub)); } if (flags & StorageLocation) { if (const char *card = sub.cardSerialNumber()) { result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); } } } } result += QLatin1String("
"); return result; } namespace { template QString getValidityStatement(const Container &keys) { - const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [] (const Key &key) { return key.protocol() == OpenPGP; }); + const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [] (const Key &key) { return key.protocol() == GpgME::OpenPGP; }); const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [] (const Key &key) { return key.keyListMode() & Validate; }); if (allKeysAreOpenPGP || allKeysAreValidated) { const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad)); if (someKeysAreBad) { return i18n("Some keys are revoked, expired, disabled, or invalid."); } else { const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Formatting::uidsHaveFullValidity); if (allKeysAreFullyValid) { return i18n("All keys are certified."); } else { return i18n("Some keys are not certified."); } } } return i18n("The validity of the keys cannot be checked at the moment."); } } QString Formatting::toolTip(const KeyGroup &group, int flags) { static const unsigned int maxNumKeysForTooltip = 20; if (group.isNull()) { return QString(); } const KeyGroup::Keys &keys = group.keys(); if (keys.size() == 0) { return i18nc("@info:tooltip", "This group does not contain any keys."); } const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString(); if (flags == Validity) { return validity; } // list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys" const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size(); QStringList result; result.reserve(3 + 2 + numKeysForTooltip + 2); if (!validity.isEmpty()) { result.push_back(QStringLiteral("

")); result.push_back(validity.toHtmlEscaped()); result.push_back(QStringLiteral("

")); } result.push_back(QStringLiteral("

")); result.push_back(i18n("Keys:")); { auto it = keys.cbegin(); for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) { result.push_back(QLatin1String("
") + Formatting::summaryLine(*it).toHtmlEscaped()); } } if (keys.size() > numKeysForTooltip) { result.push_back(QLatin1String("
") + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip)); } result.push_back(QStringLiteral("

")); return result.join(QLatin1Char('\n')); } // // Creation and Expiration // namespace { static QDate time_t2date(time_t t) { if (!t) { return {}; } QDateTime dt; dt.setTime_t(t); return dt.date(); } static QString date2string(const QDate &date) { return QLocale().toString(date, QLocale::ShortFormat); } template QString expiration_date_string(const T &tee) { return tee.neverExpires() ? QString() : date2string(time_t2date(tee.expirationTime())); } template QDate creation_date(const T &tee) { return time_t2date(tee.creationTime()); } template QDate expiration_date(const T &tee) { return time_t2date(tee.expirationTime()); } } QString Formatting::dateString(time_t t) { return date2string(time_t2date(t)); } QString Formatting::expirationDateString(const Key &key) { return expiration_date_string(key.subkey(0)); } QString Formatting::expirationDateString(const Subkey &subkey) { return expiration_date_string(subkey); } QString Formatting::expirationDateString(const UserID::Signature &sig) { return expiration_date_string(sig); } QDate Formatting::expirationDate(const Key &key) { return expiration_date(key.subkey(0)); } QDate Formatting::expirationDate(const Subkey &subkey) { return expiration_date(subkey); } QDate Formatting::expirationDate(const UserID::Signature &sig) { return expiration_date(sig); } QString Formatting::creationDateString(const Key &key) { return date2string(creation_date(key.subkey(0))); } QString Formatting::creationDateString(const Subkey &subkey) { return date2string(creation_date(subkey)); } QString Formatting::creationDateString(const UserID::Signature &sig) { return date2string(creation_date(sig)); } QDate Formatting::creationDate(const Key &key) { return creation_date(key.subkey(0)); } QDate Formatting::creationDate(const Subkey &subkey) { return creation_date(subkey); } QDate Formatting::creationDate(const UserID::Signature &sig) { return creation_date(sig); } // // Types // -QString Formatting::displayName(Protocol p) +QString Formatting::displayName(GpgME::Protocol p) { - if (p == CMS) { + if (p == GpgME::CMS) { return i18nc("X.509/CMS encryption standard", "S/MIME"); } - if (p == OpenPGP) { + if (p == GpgME::OpenPGP) { return i18n("OpenPGP"); } return i18nc("Unknown encryption protocol", "Unknown"); } QString Formatting::type(const Key &key) { return displayName(key.protocol()); } QString Formatting::type(const Subkey &subkey) { return QString::fromUtf8(subkey.publicKeyAlgorithmAsString()); } QString Formatting::type(const KeyGroup &group) { Q_UNUSED(group) return i18nc("a group of keys/certificates", "Group"); } // // Status / Validity // QString Formatting::ownerTrustShort(const Key &key) { return ownerTrustShort(key.ownerTrust()); } QString Formatting::ownerTrustShort(Key::OwnerTrust trust) { switch (trust) { case Key::Unknown: return i18nc("unknown trust level", "unknown"); case Key::Never: return i18n("untrusted"); case Key::Marginal: return i18nc("marginal trust", "marginal"); case Key::Full: return i18nc("full trust", "full"); case Key::Ultimate: return i18nc("ultimate trust", "ultimate"); case Key::Undefined: return i18nc("undefined trust", "undefined"); default: Q_ASSERT(!"unexpected owner trust value"); break; } return QString(); } QString Formatting::validityShort(const Subkey &subkey) { if (subkey.isRevoked()) { return i18n("revoked"); } if (subkey.isExpired()) { return i18n("expired"); } if (subkey.isDisabled()) { return i18n("disabled"); } if (subkey.isInvalid()) { return i18n("invalid"); } return i18nc("as in good/valid signature", "good"); } QString Formatting::validityShort(const UserID &uid) { if (uid.isRevoked()) { return i18n("revoked"); } if (uid.isInvalid()) { return i18n("invalid"); } switch (uid.validity()) { case UserID::Unknown: return i18nc("unknown trust level", "unknown"); case UserID::Undefined: return i18nc("undefined trust", "undefined"); case UserID::Never: return i18n("untrusted"); case UserID::Marginal: return i18nc("marginal trust", "marginal"); case UserID::Full: return i18nc("full trust", "full"); case UserID::Ultimate: return i18nc("ultimate trust", "ultimate"); } return QString(); } QString Formatting::validityShort(const UserID::Signature &sig) { switch (sig.status()) { case UserID::Signature::NoError: if (!sig.isInvalid()) { /* See RFC 4880 Section 5.2.1 */ switch (sig.certClass()) { case 0x10: /* Generic */ case 0x11: /* Persona */ case 0x12: /* Casual */ case 0x13: /* Positive */ return i18n("valid"); case 0x30: return i18n("revoked"); default: return i18n("class %1", sig.certClass()); } } Q_FALLTHROUGH(); // fall through: case UserID::Signature::GeneralError: return i18n("invalid"); case UserID::Signature::SigExpired: return i18n("expired"); case UserID::Signature::KeyExpired: return i18n("certificate expired"); case UserID::Signature::BadSignature: return i18nc("fake/invalid signature", "bad"); case UserID::Signature::NoPublicKey: { /* GnuPG returns the same error for no public key as for expired * or revoked certificates. */ const auto key = KeyCache::instance()->findByKeyIDOrFingerprint (sig.signerKeyID()); if (key.isNull()) { return i18n("no public key"); } else if (key.isExpired()) { return i18n("key expired"); } else if (key.isRevoked()) { return i18n("key revoked"); } else if (key.isDisabled()) { return i18n("key disabled"); } /* can't happen */ return QStringLiteral("unknown"); } } return QString(); } QIcon Formatting::validityIcon(const UserID::Signature &sig) { switch (sig.status()) { case UserID::Signature::NoError: if (!sig.isInvalid()) { /* See RFC 4880 Section 5.2.1 */ switch (sig.certClass()) { case 0x10: /* Generic */ case 0x11: /* Persona */ case 0x12: /* Casual */ case 0x13: /* Positive */ return QIcon::fromTheme(QStringLiteral("emblem-success")); case 0x30: return QIcon::fromTheme(QStringLiteral("emblem-error")); default: return QIcon(); } } Q_FALLTHROUGH(); // fall through: case UserID::Signature::BadSignature: case UserID::Signature::GeneralError: return QIcon::fromTheme(QStringLiteral("emblem-error")); case UserID::Signature::SigExpired: case UserID::Signature::KeyExpired: return QIcon::fromTheme(QStringLiteral("emblem-information")); case UserID::Signature::NoPublicKey: return QIcon::fromTheme(QStringLiteral("emblem-question")); } return QIcon(); } QString Formatting::formatKeyLink(const Key &key) { if (key.isNull()) { return QString(); } return QStringLiteral("%2").arg(QLatin1String(key.primaryFingerprint()), Formatting::prettyName(key)); } QString Formatting::formatForComboBox(const GpgME::Key &key) { const QString name = prettyName(key); QString mail = prettyEMail(key); if (!mail.isEmpty()) { mail = QLatin1Char('<') + mail + QLatin1Char('>'); } return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1String(key.shortKeyID())).simplified(); } namespace { static QString keyToString(const Key &key) { Q_ASSERT(!key.isNull()); const QString email = Formatting::prettyEMail(key); const QString name = Formatting::prettyName(key); if (name.isEmpty()) { return email; } else if (email.isEmpty()) { return name; } else { return QStringLiteral("%1 <%2>").arg(name, email); } } } const char *Formatting::summaryToString(const Signature::Summary summary) { if (summary & Signature::Red) { return "RED"; } if (summary & Signature::Green) { return "GREEN"; } return "YELLOW"; } QString Formatting::signatureToString(const Signature &sig, const Key &key) { if (sig.isNull()) { return QString(); } const bool red = (sig.summary() & Signature::Red); const bool valid = (sig.summary() & Signature::Valid); if (red) { if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString())); } else { return i18n("Bad signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString())); } } else { return i18n("Bad signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString())); } } else if (valid) { if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr)); } else { return i18n("Good signature by an unknown certificate."); } } else { return i18n("Good signature by %1.", keyToString(key)); } } else if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString())); } else { return i18n("Invalid signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString())); } } else { return i18n("Invalid signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString())); } } // // ImportResult // QString Formatting::importMetaData(const Import &import, const QStringList &ids) { const QString result = importMetaData(import); if (result.isEmpty()) { return QString(); } else { return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n')); } } QString Formatting::importMetaData(const Import &import) { if (import.isNull()) { return QString(); } if (import.error().isCanceled()) { return i18n("The import of this certificate was canceled."); } if (import.error()) { return i18n("An error occurred importing this certificate: %1", QString::fromLocal8Bit(import.error().asString())); } const unsigned int status = import.status(); if (status & Import::NewKey) { return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.") : i18n("This certificate is new to your keystore."); } QStringList results; if (status & Import::NewUserIDs) { results.push_back(i18n("New user-ids were added to this certificate by the import.")); } if (status & Import::NewSignatures) { results.push_back(i18n("New signatures were added to this certificate by the import.")); } if (status & Import::NewSubkeys) { results.push_back(i18n("New subkeys were added to this certificate by the import.")); } return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n')); } // // Overview in CertificateDetailsDialog // QString Formatting::formatOverview(const Key &key) { return toolTip(key, AllOptions); } QString Formatting::usageString(const Subkey &sub) { QStringList usageStrings; if (sub.canCertify()) { usageStrings << i18n("Certify"); } if (sub.canSign()) { usageStrings << i18n("Sign"); } if (sub.canEncrypt()) { usageStrings << i18n("Encrypt"); } if (sub.canAuthenticate()) { usageStrings << i18n("Authenticate"); } return usageStrings.join(QLatin1String(", ")); } QString Formatting::summaryLine(const Key &key) { return keyToString(key) + QLatin1Char(' ') + i18nc("(validity, protocol, creation date)", "(%1, %2, created: %3)", Formatting::complianceStringShort(key), displayName(key.protocol()), Formatting::creationDateString(key)); } QString Formatting::summaryLine(const KeyGroup &group) { switch (group.source()) { case KeyGroup::ApplicationConfig: case KeyGroup::GnuPGConfig: return i18ncp("name of group of keys (n key(s), validity)", "%2 (1 key, %3)", "%2 (%1 keys, %3)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); case KeyGroup::Tags: return i18ncp("name of group of keys (n key(s), validity, tag)", "%2 (1 key, %3, tag)", "%2 (%1 keys, %3, tag)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); default: return i18ncp("name of group of keys (n key(s), validity, group ...)", "%2 (1 key, %3, unknown origin)", "%2 (%1 keys, %3, unknown origin)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); } } namespace { QIcon iconForValidity(UserID::Validity validity) { switch (validity) { case UserID::Ultimate: case UserID::Full: case UserID::Marginal: return QIcon::fromTheme(QStringLiteral("emblem-success")); case UserID::Never: return QIcon::fromTheme(QStringLiteral("emblem-error")); case UserID::Undefined: case UserID::Unknown: default: return QIcon::fromTheme(QStringLiteral("emblem-information")); } } } // Icon for certificate selection indication QIcon Formatting::iconForUid(const UserID &uid) { return iconForValidity(uid.validity()); } QString Formatting::validity(const UserID &uid) { switch (uid.validity()) { case UserID::Ultimate: return i18n("The certificate is marked as your own."); case UserID::Full: return i18n("The certificate belongs to this recipient."); case UserID::Marginal: return i18n("The trust model indicates marginally that the certificate belongs to this recipient."); case UserID::Never: return i18n("This certificate should not be used."); case UserID::Undefined: case UserID::Unknown: default: return i18n("There is no indication that this certificate belongs to this recipient."); } } QString Formatting::validity(const KeyGroup &group) { if (group.isNull()) { return QString(); } const KeyGroup::Keys &keys = group.keys(); if (keys.size() == 0) { return i18n("This group does not contain any keys."); } return getValidityStatement(keys); } namespace { UserID::Validity minimalValidityOfNotRevokedUserIDs(const Key &key) { std::vector userIDs = key.userIDs(); const auto endOfNotRevokedUserIDs = std::remove_if(userIDs.begin(), userIDs.end(), std::mem_fn(&UserID::isRevoked)); const int minValidity = std::accumulate(userIDs.begin(), endOfNotRevokedUserIDs, UserID::Ultimate + 1, [] (int validity, const UserID &userID) { return std::min(validity, static_cast(userID.validity())); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } template UserID::Validity minimalValidity(const Container& keys) { const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [] (int validity, const Key &key) { return std::min(validity, minimalValidityOfNotRevokedUserIDs(key)); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } } QIcon Formatting::validityIcon(const KeyGroup &group) { return iconForValidity(minimalValidity(group.keys())); } bool Formatting::uidsHaveFullValidity(const Key &key) { return minimalValidityOfNotRevokedUserIDs(key) >= UserID::Full; } QString Formatting::complianceMode() { const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance"); return complianceValue == QLatin1String("gnupg") ? QString() : complianceValue; } bool Formatting::isKeyDeVs(const GpgME::Key &key) { for (const auto &sub: key.subkeys()) { if (sub.isExpired() || sub.isRevoked()) { // Ignore old subkeys continue; } if (!sub.isDeVs()) { return false; } } return true; } QString Formatting::complianceStringForKey(const GpgME::Key &key) { // There will likely be more in the future for other institutions // for now we only have DE-VS if (Kleo::gnupgIsDeVsCompliant()) { if (uidsHaveFullValidity(key) && isKeyDeVs(key)) { return i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "May be used for %1 communication.", deVsString()); } else { return i18nc("VS-NfD-conforming is a German standard for restricted documents. For which special restrictions about algorithms apply. The string describes if a key is compliant to that..", "May not be used for %1 communication.", deVsString()); } } return QString(); } QString Formatting::complianceStringShort(const GpgME::Key &key) { const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate); if (keyValidityChecked && Formatting::uidsHaveFullValidity(key)) { if (Kleo::gnupgIsDeVsCompliant() && Formatting::isKeyDeVs(key)) { return QStringLiteral("★ ") + deVsString(true); } return i18nc("As in all user IDs are valid.", "certified"); } if (key.isExpired()) { return i18n("expired"); } if (key.isRevoked()) { return i18n("revoked"); } if (key.isDisabled()) { return i18n("disabled"); } if (key.isInvalid()) { return i18n("invalid"); } if (keyValidityChecked) { return i18nc("As in not all user IDs are valid.", "not certified"); } return i18nc("The validity of the user IDs has not been/could not be checked", "not checked"); } QString Formatting::complianceStringShort(const KeyGroup &group) { const KeyGroup::Keys &keys = group.keys(); const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Formatting::uidsHaveFullValidity); if (allKeysFullyValid) { return i18nc("As in all keys are valid.", "all certified"); } return i18nc("As in not all keys are valid.", "not all certified"); } QString Formatting::prettyID(const char *id) { if (!id) { return QString(); } QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed(); // For the standard 10 group fingerprint let us use a double space in the // middle to increase readability if (ret.size() == 49) { ret.insert(24, QLatin1Char(' ')); } return ret; } QString Formatting::origin(int o) { switch (o) { case Key::OriginKS: return i18n("Keyserver"); case Key::OriginDane: return QStringLiteral("DANE"); case Key::OriginWKD: return QStringLiteral("WKD"); case Key::OriginURL: return QStringLiteral("URL"); case Key::OriginFile: return i18n("File import"); case Key::OriginSelf: return i18n("Generated"); case Key::OriginOther: case Key::OriginUnknown: default: return i18n("Unknown"); } } QString Formatting::deVsString(bool compliant) { const auto filter = KeyFilterManager::instance()->keyFilterByID(compliant ? QStringLiteral("de-vs-filter") : QStringLiteral("not-de-vs-filter")); if (!filter) { return compliant ? i18n("VS-NfD compliant") : i18n("Not VS-NfD compliant"); } return filter->name(); } namespace { QString formatTrustScope(const char *trustScope) { static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\([^0-9A-Za-z]))")}; const auto scopeRegExp = QString::fromUtf8(trustScope); if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) { // looks like a trust scope regular expression created by gpg auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2); domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)")); return domain; } return scopeRegExp; } } QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig) { #ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES return formatTrustScope(sig.trustScope()); #else return {}; #endif } QString Formatting::trustSignature(const GpgME::UserID::Signature &sig) { #ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES switch (sig.trustValue()) { case TrustSignatureTrust::Partial: return i18nc("Certifies this key as partially trusted introducer for 'domain name'.", "Certifies this key as partially trusted introducer for '%1'.", trustSignatureDomain(sig)); case TrustSignatureTrust::Complete: return i18nc("Certifies this key as fully trusted introducer for 'domain name'.", "Certifies this key as fully trusted introducer for '%1'.", trustSignatureDomain(sig)); default: return {}; } #else return {}; #endif }