diff --git a/autotests/newkeyapprovaldialogtest.cpp b/autotests/newkeyapprovaldialogtest.cpp index d4b06fd68..dc365122f 100644 --- a/autotests/newkeyapprovaldialogtest.cpp +++ b/autotests/newkeyapprovaldialogtest.cpp @@ -1,159 +1,201 @@ /* autotests/newkeyapprovaldialogtest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include +#include using namespace Kleo; namespace { GpgME::Key createTestKey(const char *uid, GpgME::Protocol protocol = GpgME::UnknownProtocol) { static int count = 0; count++; gpgme_key_t key; gpgme_key_from_uid(&key, uid); Q_ASSERT(key); if (protocol != GpgME::UnknownProtocol) { key->protocol = protocol == GpgME::OpenPGP ? GPGME_PROTOCOL_OpenPGP : GPGME_PROTOCOL_CMS; } const QByteArray fingerprint = QByteArray::number(count, 16).rightJustified(40, '0'); key->fpr = strdup(fingerprint.constData()); return GpgME::Key(key, false); } QList visibleWidgets(const QList &widgets) { QList result; std::copy_if(widgets.begin(), widgets.end(), std::back_inserter(result), std::mem_fn(&QWidget::isVisible)); return result; } } class NewKeyApprovalDialogTest: public QObject { Q_OBJECT private Q_SLOTS: void test_all_resolved_exclusive_prefer_OpenPGP() { const QStringList unresolvedSenders; const QStringList unresolvedRecipients; const QString sender = QStringLiteral("sender@example.net"); bool allowMixed = false; GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; GpgME::Protocol presetProtocol = GpgME::OpenPGP; const auto dialog = std::make_unique(resolved_senders_openpgp_and_smime(), resolved_recipients_openpgp_and_smime(), unresolvedSenders, unresolvedRecipients, sender, allowMixed, forcedProtocol, presetProtocol); dialog->show(); const QList signingKeyWidgets = dialog->findChildren(QStringLiteral("signing key")); QCOMPARE(signingKeyWidgets.size(), 2); const auto visibleSigningKeyWidgets = visibleWidgets(signingKeyWidgets); QCOMPARE(visibleSigningKeyWidgets.size(), 1); for (auto widget : visibleSigningKeyWidgets) { KeySelectionCombo *combo = qobject_cast(widget); QVERIFY(combo); QVERIFY2(!combo->defaultKey(GpgME::OpenPGP).isEmpty(), "visible signing key widget should default to OpenPGP key"); } const QList encryptionKeyWidgets = dialog->findChildren(QStringLiteral("encryption key")); QCOMPARE(encryptionKeyWidgets.size(), 4); const auto visibleEncryptionKeyWidgets = visibleWidgets(encryptionKeyWidgets); QCOMPARE(visibleEncryptionKeyWidgets.size(), 3); for (auto widget : visibleEncryptionKeyWidgets) { KeySelectionCombo *combo = qobject_cast(widget); QVERIFY(combo); QVERIFY2(combo->property("address").toString() != sender || !combo->defaultKey(GpgME::OpenPGP).isEmpty(), "encryption key widget for sender's CMS key should be hidden"); } } void test_all_resolved_exclusive_prefer_SMIME() { const QStringList unresolvedSenders; const QStringList unresolvedRecipients; const QString sender = QStringLiteral("sender@example.net"); bool allowMixed = false; GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; GpgME::Protocol presetProtocol = GpgME::CMS; const auto dialog = std::make_unique(resolved_senders_openpgp_and_smime(), resolved_recipients_openpgp_and_smime(), unresolvedSenders, unresolvedRecipients, sender, allowMixed, forcedProtocol, presetProtocol); dialog->show(); const QList signingKeyWidgets = dialog->findChildren(QStringLiteral("signing key")); QCOMPARE(signingKeyWidgets.size(), 2); const auto visibleSigningKeyWidgets = visibleWidgets(signingKeyWidgets); QCOMPARE(visibleSigningKeyWidgets.size(), 1); for (auto widget : visibleSigningKeyWidgets) { KeySelectionCombo *combo = qobject_cast(widget); QVERIFY(combo); QVERIFY2(!combo->defaultKey(GpgME::CMS).isEmpty(), "visible signing key widget should default to S/MIME key"); } const QList encryptionKeyWidgets = dialog->findChildren(QStringLiteral("encryption key")); QCOMPARE(encryptionKeyWidgets.size(), 4); const auto visibleEncryptionKeyWidgets = visibleWidgets(encryptionKeyWidgets); QCOMPARE(visibleEncryptionKeyWidgets.size(), 3); for (auto widget : visibleEncryptionKeyWidgets) { KeySelectionCombo *combo = qobject_cast(widget); QVERIFY(combo); QVERIFY2(combo->property("address").toString() != sender || !combo->defaultKey(GpgME::CMS).isEmpty(), "encryption key widget for sender's OpenPGP key should be hidden"); } } + void test_all_resolved_allow_mixed() + { + const QStringList unresolvedSenders; + const QStringList unresolvedRecipients; + const QString sender = QStringLiteral("sender@example.net"); + bool allowMixed = true; + GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol; + GpgME::Protocol presetProtocol = GpgME::UnknownProtocol; + const auto resolvedSenders = resolved_senders_openpgp_and_smime(); + const auto dialog = std::make_unique(resolvedSenders, + resolved_recipients_openpgp_and_smime(), + unresolvedSenders, + unresolvedRecipients, + sender, + allowMixed, + forcedProtocol, + presetProtocol); + dialog->show(); + const QList signingKeyWidgets = dialog->findChildren(QStringLiteral("signing key")); + QCOMPARE(signingKeyWidgets.size(), 2); + for (auto widget : signingKeyWidgets) { + QVERIFY2(widget->isVisible(), "signing key widget should be visible"); + } + // one signing key widget should default to sender's OpenPGP key, the other to sender's S/MIME key + const std::set signingKeyFingerprints = { + QString::fromLatin1(resolvedSenders["sender@example.net"][0].primaryFingerprint()), + QString::fromLatin1(resolvedSenders["sender@example.net"][1].primaryFingerprint()), + }; + const std::set defaultKeys = { + signingKeyWidgets[0]->defaultKey(), + signingKeyWidgets[1]->defaultKey() + }; + QCOMPARE(defaultKeys, signingKeyFingerprints); + const QList encryptionKeyWidgets = dialog->findChildren(QStringLiteral("encryption key")); + QCOMPARE(encryptionKeyWidgets.size(), 4); + for (auto widget : encryptionKeyWidgets) { + QVERIFY2(widget->isVisible(), + qPrintable(QString("encryption key widget should be visible for address %1").arg(widget->property("address").toString()))); + } + } + private: QMap > resolved_senders_openpgp_and_smime() { return { {QStringLiteral("sender@example.net"), { createTestKey("sender@example.net", GpgME::OpenPGP), createTestKey("sender@example.net", GpgME::CMS) }} }; } QMap > resolved_recipients_openpgp_and_smime() { return { {QStringLiteral("prefer-openpgp@example.net"), {createTestKey("Full Trust ", GpgME::OpenPGP)}}, {QStringLiteral("prefer-smime@example.net"), {createTestKey("Trusted S/MIME ", GpgME::CMS)}}, {QStringLiteral("sender@example.net"), { createTestKey("sender@example.net", GpgME::OpenPGP), createTestKey("sender@example.net", GpgME::CMS) }} }; } }; QTEST_MAIN(NewKeyApprovalDialogTest) #include "newkeyapprovaldialogtest.moc" diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp index 1ef5e2e10..2fa777fa4 100644 --- a/src/ui/newkeyapprovaldialog.cpp +++ b/src/ui/newkeyapprovaldialog.cpp @@ -1,756 +1,766 @@ /* -*- c++ -*- newkeyapprovaldialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "newkeyapprovaldialog.h" #include "kleo/defaultkeyfilter.h" #include "keyselectioncombo.h" #include "progressdialog.h" #include "utils/formatting.h" - #include "libkleo_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { class OpenPGPFilter: public DefaultKeyFilter { public: OpenPGPFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::Set); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpFilter = 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_smimeFilter = 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); static std::shared_ptr s_defaultFilter= std::shared_ptr (new DefaultKeyFilter); class SignFilter: public DefaultKeyFilter { public: SignFilter(): DefaultKeyFilter() { setHasSecret(DefaultKeyFilter::Set); } }; static std::shared_ptr s_signFilter = std::shared_ptr (new SignFilter); /* 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), mFromOverride(GpgME::UnknownProtocol) { 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); }); // Assume that combos start out with a filter mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setToolTip(i18n("Remove Filter")); // FIXME: This is ugly to enforce but otherwise the // icon is broken. combo->setMinimumHeight(22); mFilterBtn->setMinimumHeight(23); connect(mFilterBtn, &QPushButton::clicked, this, [this] () { const QString curFilter = mCombo->idFilter(); if (curFilter.isEmpty()) { mCombo->setIdFilter(mLastIdFilter); mLastIdFilter = QString(); mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setToolTip(i18n("Remove Filter")); } else { mLastIdFilter = curFilter; mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters"))); mFilterBtn->setToolTip(i18n("Add Filter")); mCombo->setIdFilter(QString()); } }); } KeySelectionCombo *combo() { return mCombo; } GpgME::Protocol fromOverride() const { return mFromOverride; } void setFromOverride(GpgME::Protocol proto) { mFromOverride = proto; } private: KeySelectionCombo *mCombo; QPushButton *mFilterBtn; QString mLastIdFilter; GpgME::Protocol mFromOverride; }; 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; } } // namespace class NewKeyApprovalDialog::Private { private: enum Action { Unset, GenerateKey, IgnoreKey, }; public: Private(NewKeyApprovalDialog *pub, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, const QString &sender, bool allowMixed): mProto(forcedProtocol), mSender(sender), mAllowMixed(allowMixed), q(pub) { // 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); 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; auto pgpBtn = new QRadioButton(i18n("OpenPGP")); auto smimeBtn = new QRadioButton(i18n("S/MIME")); mFormatBtns->addButton(pgpBtn, 1); mFormatBtns->addButton(smimeBtn, 2); mFormatBtns->setExclusive(true); fmtLayout->addStretch(-1); fmtLayout->addWidget(pgpBtn); fmtLayout->addWidget(smimeBtn); mMainLay->addLayout(fmtLayout); // Handle force / preset if (forcedProtocol == GpgME::OpenPGP) { pgpBtn->setChecked(true); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else if (forcedProtocol == GpgME::CMS) { smimeBtn->setChecked(true); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else if (presetProtocol == GpgME::CMS) { smimeBtn->setChecked(true); } else if (!mAllowMixed) { pgpBtn->setChecked(true); } else if (mAllowMixed) { smimeBtn->setVisible(false); pgpBtn->setVisible(false); } updateFilter(); QObject::connect (mFormatBtns, static_cast (&QButtonGroup::buttonToggled), q, [this](QAbstractButton *, bool) { updateFilter(); }); mMainLay->addWidget(mScrollArea); mComplianceLbl = new QLabel; mComplianceLbl->setVisible(false); auto btnLayout = new QHBoxLayout; btnLayout->addWidget(mComplianceLbl); btnLayout->addWidget(btnBox); mMainLay->addLayout(btnLayout); q->setLayout(mMainLay); } ~Private() = default; 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 */ bool isPGP = mFormatBtns->checkedId() == 1; bool isSMIME = mFormatBtns->checkedId() == 2; mAcceptedEnc.clear(); mAcceptedSig.clear(); for (const auto combo: qAsConst(mEncCombos)) { const auto &addr = combo->property("address").toString(); const auto &key = combo->currentKey(); if (!combo->isVisible()) { continue; } if (isSMIME && key.protocol() != GpgME::CMS) { continue; } if (isPGP && key.protocol() != GpgME::OpenPGP) { continue; } if (mAcceptedEnc.contains(addr)) { mAcceptedEnc[addr].push_back(key); } else { std::vector vec; vec.push_back(key); mAcceptedEnc.insert(addr, vec); } } for (const auto combo: qAsConst(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible()) { continue; } if (isSMIME && key.protocol() != GpgME::CMS) { continue; } if (isPGP && key.protocol() != GpgME::OpenPGP) { continue; } mAcceptedSig.push_back(combo->currentKey()); } q->accept(); } void accepted() { // We can assume everything was validly resolved, otherwise // the OK button would have been disabled. // Handle custom items now. for (auto combo: qAsConst(mAllCombos)) { auto act = combo->currentData(Qt::UserRole).toInt(); if (act == GenerateKey) { generateKey(combo); // Only generate once return; } } checkAccepted(); } void updateFilter() { - bool isPGP = mFormatBtns->checkedId() == 1; - bool isSMIME = mFormatBtns->checkedId() == 2; + const bool isPGP = !mAllowMixed && mFormatBtns->checkedId() == 1; + const bool isSMIME = !mAllowMixed && mFormatBtns->checkedId() == 2; if (isSMIME) { mCurEncFilter = s_smimeFilter; mCurSigFilter = s_smimeSignFilter; } else if (isPGP) { mCurEncFilter = s_pgpFilter; mCurSigFilter = s_pgpSignFilter; } else { mCurEncFilter = s_defaultFilter; mCurSigFilter = s_signFilter; } for (auto combo: qAsConst(mSigningCombos)) { combo->setKeyFilter(mCurSigFilter); auto widget = qobject_cast (combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget"; continue; } - widget->setVisible(widget->fromOverride() == GpgME::UnknownProtocol || + widget->setVisible(mAllowMixed || + widget->fromOverride() == GpgME::UnknownProtocol || ((isSMIME && widget->fromOverride() == GpgME::CMS) || (isPGP && widget->fromOverride() == GpgME::OpenPGP))); } for (auto combo: qAsConst(mEncCombos)) { combo->setKeyFilter(mCurEncFilter); auto widget = qobject_cast (combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find combo widget"; continue; } - widget->setVisible(widget->fromOverride() == GpgME::UnknownProtocol || + widget->setVisible(mAllowMixed || + widget->fromOverride() == GpgME::UnknownProtocol || ((isSMIME && widget->fromOverride() == GpgME::CMS) || (isPGP && widget->fromOverride() == GpgME::OpenPGP))); } } ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key) { auto combo = new KeySelectionCombo(); #ifndef NDEBUG combo->setObjectName(QStringLiteral("signing key")); #endif combo->setKeyFilter(mCurSigFilter); if (!key.isNull()) { - combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), key.protocol()); + if (mAllowMixed) { + // do not set the key for a specific protocol if mixed protocols are allowed + combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint())); + } else { + combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), key.protocol()); + } } if (key.isNull() && mProto != GpgME::CMS) { 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::of(&QComboBox::currentIndexChanged), q, [this] () { updateOkButton(); }); return new ComboWidget(combo); } void addSigningKeys(const QMap > &resolved, const QStringList &unresolved) { if (resolved.empty() && unresolved.empty()) { return; } for (const QString &addr: resolved.keys()) { auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", addr)); group->setAlignment(Qt::AlignLeft); mScrollLayout->addWidget(group); auto sigLayout = new QVBoxLayout; group->setLayout(sigLayout); for (const auto &key: resolved[addr]) { auto comboWidget = createSigningCombo(addr, key); if (key_has_addr (key, addr)) { comboWidget->combo()->setIdFilter(addr); } if (resolved[addr].size() > 1) { comboWidget->setFromOverride(key.protocol()); } sigLayout->addWidget(comboWidget); } } for (const QString &addr: qAsConst(unresolved)) { auto group = new QGroupBox(i18nc("Caption for signing key selection, no key found", "No key found for the address '%1':", addr)); group->setAlignment(Qt::AlignLeft); mScrollLayout->addWidget(group); auto sigLayout = new QHBoxLayout; group->setLayout(sigLayout); auto comboWidget = createSigningCombo(addr, GpgME::Key()); comboWidget->combo()->setIdFilter(addr); sigLayout->addWidget(comboWidget); } } void addEncryptionAddr(const QString &addr, const std::vector &keys, QGridLayout *encGrid) { encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0); for (const auto &key: keys) { auto combo = new KeySelectionCombo(false); #ifndef NDEBUG combo->setObjectName(QStringLiteral("encryption key")); #endif combo->setKeyFilter(mCurEncFilter); if (!key.isNull()) { - combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), - key.protocol()); + if (mAllowMixed) { + // do not set the key for a specific protocol if mixed protocols are allowed + combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint())); + } else { + combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), key.protocol()); + } } if (mSender == addr && key.isNull()) { 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.")); if (key.isNull() || key_has_addr (key, addr)) { combo->setIdFilter(addr); } connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () { updateOkButton(); }); connect(combo, QOverload::of(&QComboBox::currentIndexChanged), q, [this] () { updateOkButton(); }); mEncCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); auto comboWidget = new ComboWidget(combo); if (keys.size() > 1) { comboWidget->setFromOverride(key.protocol()); } encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } void addEncryptionKeys(const QMap > &resolved, const QStringList &unresolved) { if (resolved.empty() && unresolved.empty()) { return; } auto group = new QGroupBox(i18n("Encrypt to:")); group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout; group->setLayout(encGrid); mScrollLayout->addWidget(group); for (const QString &addr: resolved.keys()) { addEncryptionAddr(addr, resolved[addr], encGrid); } std::vector dummy; dummy.push_back(GpgME::Key()); for (const QString &addr: unresolved) { addEncryptionAddr(addr, dummy, encGrid); } encGrid->setColumnStretch(1, -1); mScrollLayout->addStretch(-1); } void updateOkButton() { static QString origOkText = mOkButton->text(); bool isGenerate = false; bool isAllIgnored = true; // Check if generate is selected. for (auto combo: mAllCombos) { auto act = combo->currentData(Qt::UserRole).toInt(); if (act == GenerateKey) { mOkButton->setText(i18n("Generate")); isGenerate = true; } if (act != IgnoreKey) { isAllIgnored = false; } } // 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. if (!mEncCombos.size()) { mOkButton->setEnabled(true); } else { mOkButton->setEnabled(!isAllIgnored); } if (!isGenerate) { mOkButton->setText(origOkText); } if (Formatting::complianceMode() != QLatin1String("de-vs")) { return; } // Handle compliance bool de_vs = true; bool isPGP = mFormatBtns->checkedId() == 1; bool isSMIME = mFormatBtns->checkedId() == 2; for (const auto combo: qAsConst(mEncCombos)) { const auto &key = combo->currentKey(); if (!combo->isVisible()) { continue; } if (isSMIME && key.protocol() != GpgME::CMS) { continue; } if (isPGP && key.protocol() != GpgME::OpenPGP) { continue; } if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } if (de_vs) { for (const auto combo: qAsConst(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible()) { continue; } if (isSMIME && key.protocol() != GpgME::CMS) { continue; } if (isPGP && key.protocol() != GpgME::OpenPGP) { 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); } void selectionChanged() { bool isPGP = false; bool isCMS = false; for (const auto combo: mEncCombos) { isPGP |= combo->currentKey().protocol() == GpgME::OpenPGP; isCMS |= combo->currentKey().protocol() == GpgME::CMS; if (isPGP && isCMS) { break; } } } GpgME::Protocol mProto; QList mSigningCombos; QList mEncCombos; QList mAllCombos; QScrollArea *mScrollArea; QVBoxLayout *mScrollLayout; QPushButton *mOkButton; QVBoxLayout *mMainLay; QButtonGroup *mFormatBtns; std::shared_ptr mCurSigFilter; std::shared_ptr mCurEncFilter; QString mSender; bool mAllowMixed; NewKeyApprovalDialog *q; QList mRunningJobs; GpgME::Error mLastError; QLabel *mComplianceLbl; QMap > mAcceptedEnc; std::vector mAcceptedSig; QString mGenerateTooltip; }; NewKeyApprovalDialog::NewKeyApprovalDialog(const QMap > &resolvedSigningKeys, const QMap > &resolvedRecp, const QStringList &unresolvedSigKeys, const QStringList &unresolvedRecp, const QString &sender, bool allowMixed, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, QWidget *parent, Qt::WindowFlags f): QDialog(parent, f), d(new Private(this, forcedProtocol, presetProtocol, sender, allowMixed)) { d->addSigningKeys(resolvedSigningKeys, unresolvedSigKeys); d->addEncryptionKeys(resolvedRecp, unresolvedRecp); d->updateFilter(); 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; std::vector NewKeyApprovalDialog::signingKeys() { return d->mAcceptedSig; } QMap > NewKeyApprovalDialog::encryptionKeys() { return d->mAcceptedEnc; } #include "newkeyapprovaldialog.moc"