File indexing completed on 2025-01-05 04:55:50
0001 /* -*- c++ -*- 0002 newkeyapprovaldialog.cpp 0003 0004 This file is part of libkleopatra, the KDE keymanagement library 0005 SPDX-FileCopyrightText: 2018 Intevation GmbH 0006 SPDX-FileCopyrightText: 2021 g10 Code GmbH 0007 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include <config-libkleo.h> 0013 0014 #include "newkeyapprovaldialog.h" 0015 0016 #include "keyselectioncombo.h" 0017 #include "progressdialog.h" 0018 0019 #include <libkleo/adjustingscrollarea.h> 0020 #include <libkleo/algorithm.h> 0021 #include <libkleo/compliance.h> 0022 #include <libkleo/debug.h> 0023 #include <libkleo/defaultkeyfilter.h> 0024 #include <libkleo/formatting.h> 0025 #include <libkleo/gnupg.h> 0026 #include <libkleo/keyhelpers.h> 0027 #include <libkleo/systeminfo.h> 0028 0029 #include <libkleo_debug.h> 0030 0031 #include <KColorScheme> 0032 #include <KLocalizedString> 0033 #include <KMessageBox> 0034 0035 #include <QGpgME/Protocol> 0036 #include <QGpgME/QuickJob> 0037 0038 #include <QButtonGroup> 0039 #include <QCheckBox> 0040 #include <QDialogButtonBox> 0041 #include <QGroupBox> 0042 #include <QHBoxLayout> 0043 #include <QLabel> 0044 #include <QMap> 0045 #include <QPushButton> 0046 #include <QRadioButton> 0047 #include <QScreen> 0048 #include <QToolTip> 0049 #include <QVBoxLayout> 0050 0051 #include <gpgme++/context.h> 0052 #include <gpgme++/key.h> 0053 #include <gpgme++/keygenerationresult.h> 0054 0055 using namespace Kleo; 0056 using namespace GpgME; 0057 0058 namespace 0059 { 0060 class EncryptFilter : public DefaultKeyFilter 0061 { 0062 public: 0063 EncryptFilter() 0064 : DefaultKeyFilter() 0065 { 0066 setHasEncrypt(DefaultKeyFilter::Set); 0067 } 0068 }; 0069 static std::shared_ptr<KeyFilter> s_encryptFilter = std::shared_ptr<KeyFilter>(new EncryptFilter); 0070 0071 class OpenPGPFilter : public DefaultKeyFilter 0072 { 0073 public: 0074 OpenPGPFilter() 0075 : DefaultKeyFilter() 0076 { 0077 setIsOpenPGP(DefaultKeyFilter::Set); 0078 setHasEncrypt(DefaultKeyFilter::Set); 0079 } 0080 }; 0081 static std::shared_ptr<KeyFilter> s_pgpEncryptFilter = std::shared_ptr<KeyFilter>(new OpenPGPFilter); 0082 0083 class OpenPGPSignFilter : public DefaultKeyFilter 0084 { 0085 public: 0086 OpenPGPSignFilter() 0087 : DefaultKeyFilter() 0088 { 0089 /* Also list unusable keys to make it transparent why they are unusable */ 0090 setDisabled(DefaultKeyFilter::NotSet); 0091 setRevoked(DefaultKeyFilter::NotSet); 0092 setExpired(DefaultKeyFilter::NotSet); 0093 setCanSign(DefaultKeyFilter::Set); 0094 setHasSecret(DefaultKeyFilter::Set); 0095 setIsOpenPGP(DefaultKeyFilter::Set); 0096 } 0097 }; 0098 static std::shared_ptr<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter>(new OpenPGPSignFilter); 0099 0100 class SMIMEFilter : public DefaultKeyFilter 0101 { 0102 public: 0103 SMIMEFilter() 0104 : DefaultKeyFilter() 0105 { 0106 setIsOpenPGP(DefaultKeyFilter::NotSet); 0107 setHasEncrypt(DefaultKeyFilter::Set); 0108 } 0109 }; 0110 static std::shared_ptr<KeyFilter> s_smimeEncryptFilter = std::shared_ptr<KeyFilter>(new SMIMEFilter); 0111 0112 class SMIMESignFilter : public DefaultKeyFilter 0113 { 0114 public: 0115 SMIMESignFilter() 0116 : DefaultKeyFilter() 0117 { 0118 setDisabled(DefaultKeyFilter::NotSet); 0119 setRevoked(DefaultKeyFilter::NotSet); 0120 setExpired(DefaultKeyFilter::NotSet); 0121 setCanSign(DefaultKeyFilter::Set); 0122 setIsOpenPGP(DefaultKeyFilter::NotSet); 0123 setHasSecret(DefaultKeyFilter::Set); 0124 } 0125 }; 0126 static std::shared_ptr<KeyFilter> s_smimeSignFilter = std::shared_ptr<KeyFilter>(new SMIMESignFilter); 0127 0128 /* Some decoration and a button to remove the filter for a keyselectioncombo */ 0129 class ComboWidget : public QWidget 0130 { 0131 Q_OBJECT 0132 public: 0133 explicit ComboWidget(KeySelectionCombo *combo) 0134 : mCombo(combo) 0135 , mFilterBtn(new QPushButton) 0136 { 0137 auto hLay = new QHBoxLayout(this); 0138 auto infoBtn = new QPushButton; 0139 infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); 0140 infoBtn->setIconSize(QSize(22, 22)); 0141 infoBtn->setFlat(true); 0142 infoBtn->setAccessibleName(i18nc("@action:button", "Show Details")); 0143 hLay->addWidget(infoBtn); 0144 hLay->addWidget(combo, 1); 0145 hLay->addWidget(mFilterBtn, 0); 0146 0147 connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn]() { 0148 QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0), 0149 mCombo->currentData(Qt::ToolTipRole).toString(), 0150 infoBtn, 0151 QRect(), 0152 30000); 0153 }); 0154 0155 // FIXME: This is ugly to enforce but otherwise the 0156 // icon is broken. 0157 combo->setMinimumHeight(22); 0158 mFilterBtn->setMinimumHeight(23); 0159 0160 updateFilterButton(); 0161 0162 connect(mFilterBtn, &QPushButton::clicked, this, [this]() { 0163 const QString curFilter = mCombo->idFilter(); 0164 if (curFilter.isEmpty()) { 0165 setIdFilter(mLastIdFilter); 0166 mLastIdFilter = QString(); 0167 } else { 0168 setIdFilter(QString()); 0169 mLastIdFilter = curFilter; 0170 } 0171 }); 0172 } 0173 0174 void setIdFilter(const QString &id) 0175 { 0176 mCombo->setIdFilter(id); 0177 updateFilterButton(); 0178 } 0179 0180 void updateFilterButton() 0181 { 0182 if (mCombo->idFilter().isEmpty()) { 0183 mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters"))); 0184 mFilterBtn->setAccessibleName(i18nc("@action:button", "Show Matching Keys")); 0185 mFilterBtn->setToolTip(i18n("Show keys matching the email address")); 0186 } else { 0187 mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); 0188 mFilterBtn->setAccessibleName(i18nc("@action:button short for 'Show all keys'", "Show All")); 0189 mFilterBtn->setToolTip(i18n("Show all keys")); 0190 } 0191 } 0192 0193 KeySelectionCombo *combo() 0194 { 0195 return mCombo; 0196 } 0197 0198 GpgME::Protocol fixedProtocol() const 0199 { 0200 return mFixedProtocol; 0201 } 0202 0203 void setFixedProtocol(GpgME::Protocol proto) 0204 { 0205 mFixedProtocol = proto; 0206 } 0207 0208 private: 0209 KeySelectionCombo *mCombo; 0210 QPushButton *mFilterBtn; 0211 QString mLastIdFilter; 0212 GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol; 0213 }; 0214 0215 static bool key_has_addr(const GpgME::Key &key, const QString &addr) 0216 { 0217 for (const auto &uid : key.userIDs()) { 0218 if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) { 0219 return true; 0220 } 0221 } 0222 return false; 0223 } 0224 0225 Key findfirstKeyOfType(const std::vector<Key> &keys, GpgME::Protocol protocol) 0226 { 0227 const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) { 0228 return key.protocol() == protocol; 0229 }); 0230 return it != std::end(keys) ? *it : Key(); 0231 } 0232 0233 } // namespace 0234 0235 class NewKeyApprovalDialog::Private 0236 { 0237 private: 0238 enum Action { 0239 Unset, 0240 GenerateKey, 0241 IgnoreKey, 0242 }; 0243 0244 public: 0245 enum { 0246 OpenPGPButtonId = 1, 0247 SMIMEButtonId = 2, 0248 }; 0249 0250 Private(NewKeyApprovalDialog *qq, 0251 bool encrypt, 0252 bool sign, 0253 GpgME::Protocol forcedProtocol, 0254 GpgME::Protocol presetProtocol, 0255 const QString &sender, 0256 bool allowMixed) 0257 : mForcedProtocol{forcedProtocol} 0258 , mSender{sender} 0259 , mSign{sign} 0260 , mEncrypt{encrypt} 0261 , mAllowMixed{allowMixed} 0262 , q{qq} 0263 { 0264 Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol); 0265 Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol)); 0266 Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol)); 0267 0268 // We do the translation here to avoid having the same string multiple times. 0269 mGenerateTooltip = i18nc( 0270 "@info:tooltip for a 'Generate new key pair' action " 0271 "in a combobox when a user does not yet have an OpenPGP or S/MIME key.", 0272 "Generate a new key using your E-Mail address.<br/><br/>" 0273 "The key is necessary to decrypt and sign E-Mails. " 0274 "You will be asked for a passphrase to protect this key and the protected key " 0275 "will be stored in your home directory."); 0276 mMainLay = new QVBoxLayout; 0277 0278 QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0279 mOkButton = btnBox->button(QDialogButtonBox::Ok); 0280 #ifndef NDEBUG 0281 mOkButton->setObjectName(QLatin1StringView("ok button")); 0282 #endif 0283 QObject::connect(btnBox, &QDialogButtonBox::accepted, q, [this]() { 0284 accepted(); 0285 }); 0286 QObject::connect(btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject); 0287 0288 mScrollArea = new AdjustingScrollArea; 0289 mScrollArea->setWidget(new QWidget); 0290 mScrollLayout = new QVBoxLayout; 0291 mScrollArea->widget()->setLayout(mScrollLayout); 0292 mScrollArea->setWidgetResizable(true); 0293 mScrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); 0294 mScrollArea->setFrameStyle(QFrame::NoFrame); 0295 mScrollLayout->setContentsMargins(0, 0, 0, 0); 0296 0297 q->setWindowTitle(i18nc("@title:window", "Security approval")); 0298 0299 auto fmtLayout = new QHBoxLayout; 0300 mFormatBtns = new QButtonGroup(qq); 0301 QAbstractButton *pgpBtn; 0302 QAbstractButton *smimeBtn; 0303 if (mAllowMixed) { 0304 pgpBtn = new QCheckBox(i18n("OpenPGP")); 0305 smimeBtn = new QCheckBox(i18n("S/MIME")); 0306 } else { 0307 pgpBtn = new QRadioButton(i18n("OpenPGP")); 0308 smimeBtn = new QRadioButton(i18n("S/MIME")); 0309 } 0310 #ifndef NDEBUG 0311 pgpBtn->setObjectName(QLatin1StringView("openpgp button")); 0312 smimeBtn->setObjectName(QLatin1StringView("smime button")); 0313 #endif 0314 mFormatBtns->addButton(pgpBtn, OpenPGPButtonId); 0315 mFormatBtns->addButton(smimeBtn, SMIMEButtonId); 0316 mFormatBtns->setExclusive(!mAllowMixed); 0317 0318 connect(mFormatBtns, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), q, [this]() { 0319 updateOkButton(); 0320 }); 0321 0322 fmtLayout->addStretch(-1); 0323 fmtLayout->addWidget(pgpBtn); 0324 fmtLayout->addWidget(smimeBtn); 0325 mMainLay->addLayout(fmtLayout); 0326 0327 if (mForcedProtocol != GpgME::UnknownProtocol) { 0328 pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP); 0329 smimeBtn->setChecked(mForcedProtocol == GpgME::CMS); 0330 pgpBtn->setVisible(false); 0331 smimeBtn->setVisible(false); 0332 } else { 0333 pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol); 0334 smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol); 0335 } 0336 0337 QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) { 0338 // ensure that at least one protocol button is checked 0339 if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) { 0340 mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true); 0341 } 0342 updateWidgets(); 0343 }); 0344 0345 mMainLay->addWidget(mScrollArea); 0346 0347 mComplianceLbl = new QLabel; 0348 mComplianceLbl->setVisible(false); 0349 #ifndef NDEBUG 0350 mComplianceLbl->setObjectName(QLatin1StringView("compliance label")); 0351 #endif 0352 0353 auto btnLayout = new QHBoxLayout; 0354 btnLayout->addWidget(mComplianceLbl); 0355 btnLayout->addWidget(btnBox); 0356 mMainLay->addLayout(btnLayout); 0357 0358 q->setLayout(mMainLay); 0359 } 0360 0361 ~Private() = default; 0362 0363 GpgME::Protocol currentProtocol() 0364 { 0365 const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked(); 0366 const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked(); 0367 if (mAllowMixed) { 0368 if (openPGPButtonChecked && !smimeButtonChecked) { 0369 return GpgME::OpenPGP; 0370 } 0371 if (!openPGPButtonChecked && smimeButtonChecked) { 0372 return GpgME::CMS; 0373 } 0374 } else if (openPGPButtonChecked) { 0375 return GpgME::OpenPGP; 0376 } else if (smimeButtonChecked) { 0377 return GpgME::CMS; 0378 } 0379 return GpgME::UnknownProtocol; 0380 } 0381 0382 auto findVisibleKeySelectionComboWithGenerateKey() 0383 { 0384 const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [](auto combo) { 0385 return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey; 0386 }); 0387 return it != std::end(mAllCombos) ? *it : nullptr; 0388 } 0389 0390 void generateKey(KeySelectionCombo *combo) 0391 { 0392 if (!mRunningJobs.empty()) { 0393 return; 0394 } 0395 const auto &addr = combo->property("address").toString(); 0396 auto job = QGpgME::openpgp()->quickJob(); 0397 auto progress = 0398 new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") + i18n("This can take several minutes."), q); 0399 progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint); 0400 progress->setWindowTitle(i18nc("@title:window", "Key generation")); 0401 progress->setModal(true); 0402 progress->setAutoClose(true); 0403 progress->setMinimumDuration(0); 0404 progress->setValue(0); 0405 0406 mRunningJobs << job; 0407 if (!connect(job, &QGpgME::QuickJob::result, q, [this, job, combo]() { 0408 handleKeyGenResult(QGpgME::Job::context(job)->keyGenerationResult(), job, combo); 0409 })) { 0410 qCWarning(LIBKLEO_LOG) << "new-style connect failed; connecting to QGpgME::QuickJob::result the old way"; 0411 connect(job, SIGNAL(result(const GpgME::Error &)), q, SLOT(handleKeyGenResult())); 0412 } 0413 job->startCreate(addr, nullptr); 0414 return; 0415 } 0416 0417 void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo) 0418 { 0419 mLastError = result.error(); 0420 if (!mLastError) { 0421 connect(combo, &KeySelectionCombo::keyListingFinished, q, [this, job]() { 0422 mRunningJobs.removeAll(job); 0423 }); 0424 // update all combos showing the GenerateKey item 0425 for (auto c : std::as_const(mAllCombos)) { 0426 if (c->currentData(Qt::UserRole).toInt() == GenerateKey) { 0427 c->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP); 0428 c->refreshKeys(); 0429 } 0430 } 0431 } else { 0432 mRunningJobs.removeAll(job); 0433 } 0434 } 0435 0436 void checkAccepted() 0437 { 0438 if (mLastError) { 0439 KMessageBox::error(q, Formatting::errorAsString(mLastError), i18n("Operation Failed")); 0440 mRunningJobs.clear(); 0441 return; 0442 } 0443 0444 if (!mRunningJobs.empty()) { 0445 return; 0446 } 0447 0448 /* Save the keys */ 0449 mAcceptedResult.protocol = currentProtocol(); 0450 for (const auto combo : std::as_const(mEncCombos)) { 0451 const auto addr = combo->property("address").toString(); 0452 const auto key = combo->currentKey(); 0453 if (!combo->isVisible() || key.isNull()) { 0454 continue; 0455 } 0456 mAcceptedResult.encryptionKeys[addr].push_back(key); 0457 } 0458 for (const auto combo : std::as_const(mSigningCombos)) { 0459 const auto key = combo->currentKey(); 0460 if (!combo->isVisible() || key.isNull()) { 0461 continue; 0462 } 0463 mAcceptedResult.signingKeys.push_back(key); 0464 } 0465 0466 q->accept(); 0467 } 0468 0469 void accepted() 0470 { 0471 // We can assume everything was validly resolved, otherwise 0472 // the OK button would have been disabled. 0473 // Handle custom items now. 0474 if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) { 0475 generateKey(combo); 0476 return; 0477 } 0478 checkAccepted(); 0479 } 0480 0481 auto encryptionKeyFilter(GpgME::Protocol protocol) 0482 { 0483 switch (protocol) { 0484 case OpenPGP: 0485 return s_pgpEncryptFilter; 0486 case CMS: 0487 return s_smimeEncryptFilter; 0488 default: 0489 return s_encryptFilter; 0490 } 0491 } 0492 0493 void updateWidgets() 0494 { 0495 const GpgME::Protocol protocol = currentProtocol(); 0496 const auto encryptionFilter = encryptionKeyFilter(protocol); 0497 0498 for (auto combo : std::as_const(mSigningCombos)) { 0499 auto widget = qobject_cast<ComboWidget *>(combo->parentWidget()); 0500 if (!widget) { 0501 qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget"; 0502 continue; 0503 } 0504 widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); 0505 } 0506 for (auto combo : std::as_const(mEncCombos)) { 0507 auto widget = qobject_cast<ComboWidget *>(combo->parentWidget()); 0508 if (!widget) { 0509 qCDebug(LIBKLEO_LOG) << "Failed to find combo widget"; 0510 continue; 0511 } 0512 widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); 0513 if (widget->isVisible() && combo->property("address") != mSender) { 0514 combo->setKeyFilter(encryptionFilter); 0515 } 0516 } 0517 // hide the labels indicating the protocol of the sender's keys if only a single protocol is active 0518 const auto protocolLabels = q->findChildren<QLabel *>(QStringLiteral("protocol label")); 0519 for (auto label : protocolLabels) { 0520 label->setVisible(protocol == GpgME::UnknownProtocol); 0521 } 0522 } 0523 0524 auto createProtocolLabel(GpgME::Protocol protocol) 0525 { 0526 auto label = new QLabel(Formatting::displayName(protocol)); 0527 label->setObjectName(QLatin1StringView("protocol label")); 0528 return label; 0529 } 0530 0531 ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol protocol = GpgME::UnknownProtocol) 0532 { 0533 Q_ASSERT(!key.isNull() || protocol != UnknownProtocol); 0534 protocol = !key.isNull() ? key.protocol() : protocol; 0535 0536 auto combo = new KeySelectionCombo{true, KeyUsage::Sign}; 0537 auto comboWidget = new ComboWidget(combo); 0538 #ifndef NDEBUG 0539 combo->setObjectName(QLatin1StringView("signing key")); 0540 #endif 0541 if (protocol == GpgME::OpenPGP) { 0542 combo->setKeyFilter(s_pgpSignFilter); 0543 } else if (protocol == GpgME::CMS) { 0544 combo->setKeyFilter(s_smimeSignFilter); 0545 } 0546 if (key.isNull() || key_has_addr(key, mSender)) { 0547 comboWidget->setIdFilter(mSender); 0548 } 0549 comboWidget->setFixedProtocol(protocol); 0550 if (!key.isNull()) { 0551 combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol); 0552 } 0553 if (key.isNull() && protocol == OpenPGP) { 0554 combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); 0555 } 0556 combo->appendCustomItem(Formatting::unavailableIcon(), 0557 i18n("Don't confirm identity and integrity"), 0558 IgnoreKey, 0559 i18nc("@info:tooltip for not selecting a key for signing.", "The E-Mail will not be cryptographically signed.")); 0560 0561 mSigningCombos << combo; 0562 mAllCombos << combo; 0563 combo->setProperty("address", addr); 0564 0565 connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() { 0566 updateOkButton(); 0567 }); 0568 connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() { 0569 updateOkButton(); 0570 }); 0571 0572 return comboWidget; 0573 } 0574 0575 void setSigningKeys(const std::vector<GpgME::Key> &preferredKeys, const std::vector<GpgME::Key> &alternativeKeys) 0576 { 0577 auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender)); 0578 group->setAlignment(Qt::AlignLeft); 0579 auto sigLayout = new QVBoxLayout(group); 0580 0581 const bool mayNeedOpenPGP = mForcedProtocol != CMS; 0582 const bool mayNeedCMS = mForcedProtocol != OpenPGP; 0583 if (mayNeedOpenPGP) { 0584 if (mAllowMixed) { 0585 sigLayout->addWidget(createProtocolLabel(OpenPGP)); 0586 } 0587 const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP); 0588 const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP); 0589 if (!preferredKey.isNull()) { 0590 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; 0591 auto comboWidget = createSigningCombo(mSender, preferredKey); 0592 sigLayout->addWidget(comboWidget); 0593 } else if (!alternativeKey.isNull()) { 0594 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; 0595 auto comboWidget = createSigningCombo(mSender, alternativeKey); 0596 sigLayout->addWidget(comboWidget); 0597 } else { 0598 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key"; 0599 auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP); 0600 sigLayout->addWidget(comboWidget); 0601 } 0602 } 0603 if (mayNeedCMS) { 0604 if (mAllowMixed) { 0605 sigLayout->addWidget(createProtocolLabel(CMS)); 0606 } 0607 const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS); 0608 const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS); 0609 if (!preferredKey.isNull()) { 0610 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; 0611 auto comboWidget = createSigningCombo(mSender, preferredKey); 0612 sigLayout->addWidget(comboWidget); 0613 } else if (!alternativeKey.isNull()) { 0614 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; 0615 auto comboWidget = createSigningCombo(mSender, alternativeKey); 0616 sigLayout->addWidget(comboWidget); 0617 } else { 0618 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key"; 0619 auto comboWidget = createSigningCombo(mSender, Key(), CMS); 0620 sigLayout->addWidget(comboWidget); 0621 } 0622 } 0623 0624 mScrollLayout->addWidget(group); 0625 } 0626 0627 ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol fixedProtocol) 0628 { 0629 auto combo = new KeySelectionCombo{false, KeyUsage::Encrypt}; 0630 auto comboWidget = new ComboWidget(combo); 0631 #ifndef NDEBUG 0632 combo->setObjectName(QLatin1StringView("encryption key")); 0633 #endif 0634 if (fixedProtocol == GpgME::OpenPGP) { 0635 combo->setKeyFilter(s_pgpEncryptFilter); 0636 } else if (fixedProtocol == GpgME::CMS) { 0637 combo->setKeyFilter(s_smimeEncryptFilter); 0638 } else { 0639 combo->setKeyFilter(s_encryptFilter); 0640 } 0641 if (key.isNull() || key_has_addr(key, addr)) { 0642 comboWidget->setIdFilter(addr); 0643 } 0644 comboWidget->setFixedProtocol(fixedProtocol); 0645 if (!key.isNull()) { 0646 combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol); 0647 } 0648 0649 if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) { 0650 combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); 0651 } 0652 0653 combo->appendCustomItem(Formatting::unavailableIcon(), 0654 i18n("No key. Recipient will be unable to decrypt."), 0655 IgnoreKey, 0656 i18nc("@info:tooltip for No Key selected for a specific recipient.", 0657 "Do not select a key for this recipient.<br/><br/>" 0658 "The recipient will receive the encrypted E-Mail, but it can only " 0659 "be decrypted with the other keys selected in this dialog.")); 0660 0661 connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() { 0662 updateOkButton(); 0663 }); 0664 connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() { 0665 updateOkButton(); 0666 }); 0667 0668 mEncCombos << combo; 0669 mAllCombos << combo; 0670 combo->setProperty("address", addr); 0671 return comboWidget; 0672 } 0673 0674 void addEncryptionAddr(const QString &addr, 0675 GpgME::Protocol preferredKeysProtocol, 0676 const std::vector<GpgME::Key> &preferredKeys, 0677 GpgME::Protocol alternativeKeysProtocol, 0678 const std::vector<GpgME::Key> &alternativeKeys, 0679 QGridLayout *encGrid) 0680 { 0681 if (addr == mSender) { 0682 const bool mayNeedOpenPGP = mForcedProtocol != CMS; 0683 const bool mayNeedCMS = mForcedProtocol != OpenPGP; 0684 if (mayNeedOpenPGP) { 0685 if (mAllowMixed) { 0686 encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0); 0687 } 0688 for (const auto &key : preferredKeys) { 0689 if (key.protocol() == OpenPGP) { 0690 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; 0691 auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); 0692 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0693 } 0694 } 0695 for (const auto &key : alternativeKeys) { 0696 if (key.protocol() == OpenPGP) { 0697 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; 0698 auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); 0699 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0700 } 0701 } 0702 if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) { 0703 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key"; 0704 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP); 0705 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0706 } 0707 } 0708 if (mayNeedCMS) { 0709 if (mAllowMixed) { 0710 encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0); 0711 } 0712 for (const auto &key : preferredKeys) { 0713 if (key.protocol() == CMS) { 0714 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; 0715 auto comboWidget = createEncryptionCombo(addr, key, CMS); 0716 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0717 } 0718 } 0719 for (const auto &key : alternativeKeys) { 0720 if (key.protocol() == CMS) { 0721 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; 0722 auto comboWidget = createEncryptionCombo(addr, key, CMS); 0723 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0724 } 0725 } 0726 if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) { 0727 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key"; 0728 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS); 0729 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0730 } 0731 } 0732 } else { 0733 encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0); 0734 0735 for (const auto &key : preferredKeys) { 0736 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; 0737 auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol); 0738 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0739 } 0740 for (const auto &key : alternativeKeys) { 0741 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; 0742 auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol); 0743 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0744 } 0745 if (!mAllowMixed) { 0746 if (preferredKeys.empty()) { 0747 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol) 0748 << "key"; 0749 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol); 0750 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0751 } 0752 if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) { 0753 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" 0754 << Formatting::displayName(alternativeKeysProtocol) << "key"; 0755 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol); 0756 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0757 } 0758 } else { 0759 if (preferredKeys.empty() && alternativeKeys.empty()) { 0760 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key"; 0761 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol); 0762 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); 0763 } 0764 } 0765 } 0766 } 0767 0768 void setEncryptionKeys(GpgME::Protocol preferredKeysProtocol, 0769 const QMap<QString, std::vector<GpgME::Key>> &preferredKeys, 0770 GpgME::Protocol alternativeKeysProtocol, 0771 const QMap<QString, std::vector<GpgME::Key>> &alternativeKeys) 0772 { 0773 { 0774 auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender)); 0775 #ifndef NDEBUG 0776 group->setObjectName(QLatin1StringView("encrypt-to-self box")); 0777 #endif 0778 group->setAlignment(Qt::AlignLeft); 0779 auto encGrid = new QGridLayout(group); 0780 0781 addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid); 0782 0783 encGrid->setColumnStretch(1, -1); 0784 mScrollLayout->addWidget(group); 0785 } 0786 0787 const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) { 0788 return recipient != mSender; 0789 }); 0790 if (hasOtherRecipients) { 0791 auto group = new QGroupBox(i18n("Encrypt to others:")); 0792 #ifndef NDEBUG 0793 group->setObjectName(QLatin1StringView("encrypt-to-others box")); 0794 #endif 0795 group->setAlignment(Qt::AlignLeft); 0796 auto encGrid = new QGridLayout{group}; 0797 0798 for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) { 0799 const auto &address = it.key(); 0800 const auto &keys = it.value(); 0801 if (address != mSender) { 0802 addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid); 0803 } 0804 } 0805 0806 encGrid->setColumnStretch(1, -1); 0807 mScrollLayout->addWidget(group); 0808 } 0809 0810 mScrollLayout->addStretch(-1); 0811 } 0812 0813 void updateOkButton() 0814 { 0815 static QString origOkText = mOkButton->text(); 0816 const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey()); 0817 const bool allVisibleEncryptionKeysAreIgnored = Kleo::all_of(mEncCombos, [](auto combo) { 0818 return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey; 0819 }); 0820 const bool allVisibleEncryptionKeysAreUsable = Kleo::all_of(mEncCombos, [](auto combo) { 0821 return !combo->isVisible() || combo->currentKey().isNull() || Kleo::canBeUsedForEncryption(combo->currentKey()); 0822 }); 0823 0824 // If we don't encrypt, then the OK button is always enabled. Likewise, 0825 // if the "generate key" option is selected. Otherwise, 0826 // we only enable it if we encrypt to at least one recipient. 0827 mOkButton->setEnabled(isGenerate || !mEncrypt || (!allVisibleEncryptionKeysAreIgnored && allVisibleEncryptionKeysAreUsable)); 0828 0829 mOkButton->setText(isGenerate ? i18n("Generate") : origOkText); 0830 0831 if (!DeVSCompliance::isActive()) { 0832 return; 0833 } 0834 0835 // Handle compliance 0836 bool de_vs = DeVSCompliance::isCompliant(); 0837 0838 if (de_vs) { 0839 const GpgME::Protocol protocol = currentProtocol(); 0840 0841 for (const auto combo : std::as_const(mAllCombos)) { 0842 if (!combo->isVisible()) { 0843 continue; 0844 } 0845 const auto key = combo->currentKey(); 0846 if (key.isNull()) { 0847 continue; 0848 } 0849 if (protocol != GpgME::UnknownProtocol && key.protocol() != protocol) { 0850 continue; 0851 } 0852 if (!DeVSCompliance::keyIsCompliant(key)) { 0853 de_vs = false; 0854 break; 0855 } 0856 } 0857 } 0858 0859 DeVSCompliance::decorate(mOkButton, de_vs); 0860 mComplianceLbl->setText(DeVSCompliance::name(de_vs)); 0861 mComplianceLbl->setVisible(true); 0862 } 0863 0864 GpgME::Protocol mForcedProtocol; 0865 QList<KeySelectionCombo *> mSigningCombos; 0866 QList<KeySelectionCombo *> mEncCombos; 0867 QList<KeySelectionCombo *> mAllCombos; 0868 QScrollArea *mScrollArea; 0869 QVBoxLayout *mScrollLayout; 0870 QPushButton *mOkButton; 0871 QVBoxLayout *mMainLay; 0872 QButtonGroup *mFormatBtns; 0873 QString mSender; 0874 bool mSign; 0875 bool mEncrypt; 0876 bool mAllowMixed; 0877 NewKeyApprovalDialog *q; 0878 QList<QGpgME::Job *> mRunningJobs; 0879 GpgME::Error mLastError; 0880 QLabel *mComplianceLbl; 0881 KeyResolver::Solution mAcceptedResult; 0882 QString mGenerateTooltip; 0883 }; 0884 0885 NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt, 0886 bool sign, 0887 const QString &sender, 0888 KeyResolver::Solution preferredSolution, 0889 KeyResolver::Solution alternativeSolution, 0890 bool allowMixed, 0891 GpgME::Protocol forcedProtocol, 0892 QWidget *parent, 0893 Qt::WindowFlags f) 0894 : QDialog(parent, f) 0895 , d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)} 0896 { 0897 if (sign) { 0898 d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys)); 0899 } 0900 if (encrypt) { 0901 d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol, 0902 std::move(preferredSolution.encryptionKeys), 0903 allowMixed ? UnknownProtocol : alternativeSolution.protocol, 0904 std::move(alternativeSolution.encryptionKeys)); 0905 } 0906 d->updateWidgets(); 0907 d->updateOkButton(); 0908 0909 const auto size = sizeHint(); 0910 const auto desk = screen()->size(); 0911 resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2))); 0912 } 0913 0914 Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default; 0915 0916 KeyResolver::Solution NewKeyApprovalDialog::result() 0917 { 0918 return d->mAcceptedResult; 0919 } 0920 0921 void NewKeyApprovalDialog::handleKeyGenResult() 0922 { 0923 if (d->mRunningJobs.empty()) { 0924 qCWarning(LIBKLEO_LOG) << __func__ << "No running job"; 0925 } 0926 const auto job = d->mRunningJobs.front(); 0927 const auto result = QGpgME::Job::context(job)->keyGenerationResult(); 0928 const auto combo = d->findVisibleKeySelectionComboWithGenerateKey(); 0929 d->handleKeyGenResult(result, job, combo); 0930 } 0931 0932 #include "newkeyapprovaldialog.moc" 0933 0934 #include "moc_newkeyapprovaldialog.cpp"