1 /* -*- c++ -*-
2 newkeyapprovaldialog.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2018 Intevation GmbH
6 SPDX-FileCopyrightText: 2021 g10 Code GmbH
7 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12 #include "newkeyapprovaldialog.h"
13
14 #include "keyselectioncombo.h"
15 #include "progressdialog.h"
16 #include "kleo/defaultkeyfilter.h"
17 #include "utils/formatting.h"
18 #include "utils/gnupg.h"
19
20 #include <KLocalizedString>
21 #include <KMessageBox>
22
23 #include <QApplication>
24 #include <QButtonGroup>
25 #include <QCheckBox>
26 #include <QDesktopWidget>
27 #include <QDialogButtonBox>
28 #include <QGroupBox>
29 #include <QHBoxLayout>
30 #include <QLabel>
31 #include <QMap>
32 #include <QPushButton>
33 #include <QRadioButton>
34 #include <QScrollArea>
35 #include <QToolTip>
36 #include <QVBoxLayout>
37
38 #include <QGpgME/DefaultKeyGenerationJob>
39 #include <QGpgME/Job>
40
41 #include <gpgme++/keygenerationresult.h>
42 #include <gpgme++/key.h>
43
44 #include "libkleo_debug.h"
45
46 using namespace Kleo;
47 using namespace GpgME;
48
operator <<(QDebug debug,const GpgME::Key & key)49 QDebug operator<<(QDebug debug, const GpgME::Key &key)
50 {
51 if (key.isNull()) {
52 debug << "Null";
53 } else {
54 debug << Formatting::summaryLine(key);
55 }
56 return debug.maybeSpace();
57 }
58
59 namespace {
60 class EncryptFilter: public DefaultKeyFilter
61 {
62 public:
EncryptFilter()63 EncryptFilter() : DefaultKeyFilter()
64 {
65 setCanEncrypt(DefaultKeyFilter::Set);
66 }
67 };
68 static std::shared_ptr<KeyFilter> s_encryptFilter = std::shared_ptr<KeyFilter>(new EncryptFilter);
69
70 class OpenPGPFilter: public DefaultKeyFilter
71 {
72 public:
OpenPGPFilter()73 OpenPGPFilter() : DefaultKeyFilter()
74 {
75 setIsOpenPGP(DefaultKeyFilter::Set);
76 setCanEncrypt(DefaultKeyFilter::Set);
77 }
78 };
79 static std::shared_ptr<KeyFilter> s_pgpEncryptFilter = std::shared_ptr<KeyFilter> (new OpenPGPFilter);
80
81 class OpenPGPSignFilter: public DefaultKeyFilter
82 {
83 public:
OpenPGPSignFilter()84 OpenPGPSignFilter() : DefaultKeyFilter()
85 {
86 /* Also list unusable keys to make it transparent why they are unusable */
87 setDisabled(DefaultKeyFilter::NotSet);
88 setRevoked(DefaultKeyFilter::NotSet);
89 setExpired(DefaultKeyFilter::NotSet);
90 setCanSign(DefaultKeyFilter::Set);
91 setHasSecret(DefaultKeyFilter::Set);
92 setIsOpenPGP(DefaultKeyFilter::Set);
93 }
94 };
95 static std::shared_ptr<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter> (new OpenPGPSignFilter);
96
97 class SMIMEFilter: public DefaultKeyFilter
98 {
99 public:
SMIMEFilter()100 SMIMEFilter(): DefaultKeyFilter()
101 {
102 setIsOpenPGP(DefaultKeyFilter::NotSet);
103 setCanEncrypt(DefaultKeyFilter::Set);
104 }
105 };
106 static std::shared_ptr<KeyFilter> s_smimeEncryptFilter = std::shared_ptr<KeyFilter> (new SMIMEFilter);
107
108 class SMIMESignFilter: public DefaultKeyFilter
109 {
110 public:
SMIMESignFilter()111 SMIMESignFilter(): DefaultKeyFilter()
112 {
113 setDisabled(DefaultKeyFilter::NotSet);
114 setRevoked(DefaultKeyFilter::NotSet);
115 setExpired(DefaultKeyFilter::NotSet);
116 setCanSign(DefaultKeyFilter::Set);
117 setIsOpenPGP(DefaultKeyFilter::NotSet);
118 setHasSecret(DefaultKeyFilter::Set);
119 }
120 };
121 static std::shared_ptr<KeyFilter> s_smimeSignFilter = std::shared_ptr<KeyFilter> (new SMIMESignFilter);
122
123 /* Some decoration and a button to remove the filter for a keyselectioncombo */
124 class ComboWidget: public QWidget
125 {
126 Q_OBJECT
127 public:
ComboWidget(KeySelectionCombo * combo)128 explicit ComboWidget(KeySelectionCombo *combo):
129 mCombo(combo),
130 mFilterBtn(new QPushButton)
131 {
132 auto hLay = new QHBoxLayout(this);
133 auto infoBtn = new QPushButton;
134 infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
135 infoBtn->setIconSize(QSize(22,22));
136 infoBtn->setFlat(true);
137 hLay->addWidget(infoBtn);
138 hLay->addWidget(combo, 1);
139 hLay->addWidget(mFilterBtn, 0);
140
141 connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn] () {
142 QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0),
143 mCombo->currentData(Qt::ToolTipRole).toString(), infoBtn, QRect(), 30000);
144 });
145
146 // FIXME: This is ugly to enforce but otherwise the
147 // icon is broken.
148 combo->setMinimumHeight(22);
149 mFilterBtn->setMinimumHeight(23);
150
151 updateFilterButton();
152
153 connect(mFilterBtn, &QPushButton::clicked, this, [this] () {
154 const QString curFilter = mCombo->idFilter();
155 if (curFilter.isEmpty()) {
156 setIdFilter(mLastIdFilter);
157 mLastIdFilter = QString();
158 } else {
159 setIdFilter(QString());
160 mLastIdFilter = curFilter;
161 }
162 });
163 }
164
setIdFilter(const QString & id)165 void setIdFilter(const QString &id)
166 {
167 mCombo->setIdFilter(id);
168 updateFilterButton();
169 }
170
updateFilterButton()171 void updateFilterButton()
172 {
173 if (mCombo->idFilter().isEmpty()) {
174 mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters")));
175 mFilterBtn->setToolTip(i18n("Show keys matching the email address"));
176 } else {
177 mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
178 mFilterBtn->setToolTip(i18n("Show all keys"));
179 }
180 }
181
combo()182 KeySelectionCombo *combo()
183 {
184 return mCombo;
185 }
186
fixedProtocol() const187 GpgME::Protocol fixedProtocol() const
188 {
189 return mFixedProtocol;
190 }
191
setFixedProtocol(GpgME::Protocol proto)192 void setFixedProtocol(GpgME::Protocol proto)
193 {
194 mFixedProtocol = proto;
195 }
196
197 private:
198 KeySelectionCombo *mCombo;
199 QPushButton *mFilterBtn;
200 QString mLastIdFilter;
201 GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol;
202 };
203
keyValidity(const GpgME::Key & key)204 static enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key)
205 {
206 enum GpgME::UserID::Validity validity = GpgME::UserID::Validity::Unknown;
207
208 for (const auto &uid: key.userIDs()) {
209 if (validity == GpgME::UserID::Validity::Unknown
210 || validity > uid.validity()) {
211 validity = uid.validity();
212 }
213 }
214
215 return validity;
216 }
217
key_has_addr(const GpgME::Key & key,const QString & addr)218 static bool key_has_addr(const GpgME::Key &key, const QString &addr)
219 {
220 for (const auto &uid: key.userIDs()) {
221 if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) {
222 return true;
223 }
224 }
225 return false;
226 }
227
anyKeyHasProtocol(const std::vector<Key> & keys,Protocol protocol)228 bool anyKeyHasProtocol(const std::vector<Key> &keys, Protocol protocol)
229 {
230 return std::any_of(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; });
231 }
232
findfirstKeyOfType(const std::vector<Key> & keys,Protocol protocol)233 Key findfirstKeyOfType(const std::vector<Key> &keys, Protocol protocol)
234 {
235 const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; });
236 return it != std::end(keys) ? *it : Key();
237 }
238
239 } // namespace
240
241 class NewKeyApprovalDialog::Private
242 {
243 private:
244 enum Action {
245 Unset,
246 GenerateKey,
247 IgnoreKey,
248 };
249 public:
250 enum {
251 OpenPGPButtonId = 1,
252 SMIMEButtonId = 2
253 };
254
Private(NewKeyApprovalDialog * qq,bool encrypt,bool sign,GpgME::Protocol forcedProtocol,GpgME::Protocol presetProtocol,const QString & sender,bool allowMixed)255 Private(NewKeyApprovalDialog *qq,
256 bool encrypt,
257 bool sign,
258 GpgME::Protocol forcedProtocol,
259 GpgME::Protocol presetProtocol,
260 const QString &sender,
261 bool allowMixed)
262 : mForcedProtocol{forcedProtocol}
263 , mSender{sender}
264 , mSign{sign}
265 , mEncrypt{encrypt}
266 , mAllowMixed{allowMixed}
267 , q{qq}
268 {
269 Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol);
270 Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol));
271 Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol));
272
273 // We do the translation here to avoid having the same string multiple times.
274 mGenerateTooltip = i18nc("@info:tooltip for a 'Generate new key pair' action "
275 "in a combobox when a user does not yet have an OpenPGP or S/MIME key.",
276 "Generate a new key using your E-Mail address.<br/><br/>"
277 "The key is necessary to decrypt and sign E-Mails. "
278 "You will be asked for a passphrase to protect this key and the protected key "
279 "will be stored in your home directory.");
280 mMainLay = new QVBoxLayout;
281
282 QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
283 mOkButton = btnBox->button(QDialogButtonBox::Ok);
284 #ifndef NDEBUG
285 mOkButton->setObjectName(QStringLiteral("ok button"));
286 #endif
__anon713252920702() 287 QObject::connect (btnBox, &QDialogButtonBox::accepted, q, [this] () {
288 accepted();
289 });
290 QObject::connect (btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
291
292 mScrollArea = new QScrollArea;
293 mScrollArea->setWidget(new QWidget);
294 mScrollLayout = new QVBoxLayout;
295 mScrollArea->widget()->setLayout(mScrollLayout);
296 mScrollArea->setWidgetResizable(true);
297 mScrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
298 mScrollArea->setFrameStyle(QFrame::NoFrame);
299 mScrollLayout->setContentsMargins(0, 0, 0, 0);
300
301 q->setWindowTitle(i18nc("@title:window", "Security approval"));
302
303 auto fmtLayout = new QHBoxLayout;
304 mFormatBtns = new QButtonGroup(qq);
305 QAbstractButton *pgpBtn;
306 QAbstractButton *smimeBtn;
307 if (mAllowMixed) {
308 pgpBtn = new QCheckBox(i18n("OpenPGP"));
309 smimeBtn = new QCheckBox(i18n("S/MIME"));
310 } else {
311 pgpBtn = new QRadioButton(i18n("OpenPGP"));
312 smimeBtn = new QRadioButton(i18n("S/MIME"));
313 }
314 #ifndef NDEBUG
315 pgpBtn->setObjectName(QStringLiteral("openpgp button"));
316 smimeBtn->setObjectName(QStringLiteral("smime button"));
317 #endif
318 mFormatBtns->addButton(pgpBtn, OpenPGPButtonId);
319 mFormatBtns->addButton(smimeBtn, SMIMEButtonId);
320 mFormatBtns->setExclusive(!mAllowMixed);
321
322 fmtLayout->addStretch(-1);
323 fmtLayout->addWidget(pgpBtn);
324 fmtLayout->addWidget(smimeBtn);
325 mMainLay->addLayout(fmtLayout);
326
327 if (mForcedProtocol != GpgME::UnknownProtocol) {
328 pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP);
329 smimeBtn->setChecked(mForcedProtocol == GpgME::CMS);
330 pgpBtn->setVisible(false);
331 smimeBtn->setVisible(false);
332 } else {
333 pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol);
334 smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol);
335 }
336
337 QObject::connect(mFormatBtns, &QButtonGroup::idClicked,
__anon713252920802(int buttonId) 338 q, [this](int buttonId) {
339 // ensure that at least one protocol button is checked
340 if (mAllowMixed
341 && !mFormatBtns->button(OpenPGPButtonId)->isChecked()
342 && !mFormatBtns->button(SMIMEButtonId)->isChecked()) {
343 mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true);
344 }
345 updateWidgets();
346 });
347
348 mMainLay->addWidget(mScrollArea);
349
350 mComplianceLbl = new QLabel;
351 mComplianceLbl->setVisible(false);
352 #ifndef NDEBUG
353 mComplianceLbl->setObjectName(QStringLiteral("compliance label"));
354 #endif
355
356 auto btnLayout = new QHBoxLayout;
357 btnLayout->addWidget(mComplianceLbl);
358 btnLayout->addWidget(btnBox);
359 mMainLay->addLayout(btnLayout);
360
361 q->setLayout(mMainLay);
362 }
363
364 ~Private() = default;
365
currentProtocol()366 Protocol currentProtocol()
367 {
368 const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked();
369 const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked();
370 if (mAllowMixed) {
371 if (openPGPButtonChecked && !smimeButtonChecked) {
372 return OpenPGP;
373 }
374 if (!openPGPButtonChecked && smimeButtonChecked) {
375 return CMS;
376 }
377 } else if (openPGPButtonChecked) {
378 return OpenPGP;
379 } else if (smimeButtonChecked) {
380 return CMS;
381 }
382 return UnknownProtocol;
383 }
384
findVisibleKeySelectionComboWithGenerateKey()385 auto findVisibleKeySelectionComboWithGenerateKey()
386 {
387 const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos),
388 [] (auto combo) {
389 return combo->isVisible()
390 && combo->currentData(Qt::UserRole).toInt() == GenerateKey;
391 });
392 return it != std::end(mAllCombos) ? *it : nullptr;
393 }
394
generateKey(KeySelectionCombo * combo)395 void generateKey(KeySelectionCombo *combo)
396 {
397 const auto &addr = combo->property("address").toString();
398 auto job = new QGpgME::DefaultKeyGenerationJob(q);
399 auto progress = new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") +
400 i18n("This can take several minutes."), q);
401 progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
402 progress->setWindowTitle(i18nc("@title:window", "Key generation"));
403 progress->setModal(true);
404 progress->setAutoClose(true);
405 progress->setMinimumDuration(0);
406 progress->setValue(0);
407
408 mRunningJobs << job;
409 connect (job, &QGpgME::DefaultKeyGenerationJob::result, q,
410 [this, job, combo] (const GpgME::KeyGenerationResult &result) {
411 handleKeyGenResult(result, job, combo);
412 });
413 job->start(addr, QString());
414 return;
415 }
416
handleKeyGenResult(const GpgME::KeyGenerationResult & result,QGpgME::Job * job,KeySelectionCombo * combo)417 void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo)
418 {
419 mLastError = result.error();
420 if (!mLastError || mLastError.isCanceled()) {
421 combo->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP);
422 connect (combo, &KeySelectionCombo::keyListingFinished, q, [this, job] () {
423 mRunningJobs.removeAll(job);
424 });
425 combo->refreshKeys();
426 } else {
427 mRunningJobs.removeAll(job);
428 }
429 }
430
checkAccepted()431 void checkAccepted()
432 {
433 if (mLastError || mLastError.isCanceled()) {
434 KMessageBox::error(q, QString::fromLocal8Bit(mLastError.asString()), i18n("Operation Failed"));
435 mRunningJobs.clear();
436 return;
437 }
438
439 if (!mRunningJobs.empty()) {
440 return;
441 }
442
443 /* Save the keys */
444 mAcceptedResult.protocol = currentProtocol();
445 for (const auto combo: std::as_const(mEncCombos)) {
446 const auto addr = combo->property("address").toString();
447 const auto key = combo->currentKey();
448 if (!combo->isVisible() || key.isNull()) {
449 continue;
450 }
451 mAcceptedResult.encryptionKeys[addr].push_back(key);
452 }
453 for (const auto combo: std::as_const(mSigningCombos)) {
454 const auto key = combo->currentKey();
455 if (!combo->isVisible() || key.isNull()) {
456 continue;
457 }
458 mAcceptedResult.signingKeys.push_back(key);
459 }
460
461 q->accept();
462 }
463
accepted()464 void accepted()
465 {
466 // We can assume everything was validly resolved, otherwise
467 // the OK button would have been disabled.
468 // Handle custom items now.
469 if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) {
470 generateKey(combo);
471 return;
472 }
473 checkAccepted();
474 }
475
encryptionKeyFilter(Protocol protocol)476 auto encryptionKeyFilter(Protocol protocol)
477 {
478 switch (protocol) {
479 case OpenPGP:
480 return s_pgpEncryptFilter;
481 case CMS:
482 return s_smimeEncryptFilter;
483 default:
484 return s_encryptFilter;
485 }
486 }
487
updateWidgets()488 void updateWidgets()
489 {
490 const Protocol protocol = currentProtocol();
491 const auto encryptionFilter = encryptionKeyFilter(protocol);
492
493 for (auto combo: std::as_const(mSigningCombos)) {
494 auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
495 if (!widget) {
496 qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget";
497 continue;
498 }
499 widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol);
500 }
501 for (auto combo: std::as_const(mEncCombos)) {
502 auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
503 if (!widget) {
504 qCDebug(LIBKLEO_LOG) << "Failed to find combo widget";
505 continue;
506 }
507 widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol);
508 if (widget->isVisible() && combo->property("address") != mSender) {
509 combo->setKeyFilter(encryptionFilter);
510 }
511 }
512 // hide the labels indicating the protocol of the sender's keys if only a single protocol is active
513 const auto protocolLabels = q->findChildren<QLabel *>(QStringLiteral("protocol label"));
514 for (auto label: protocolLabels) {
515 label->setVisible(protocol == UnknownProtocol);
516 }
517 }
518
createProtocolLabel(Protocol protocol)519 auto createProtocolLabel(Protocol protocol)
520 {
521 auto label = new QLabel(Formatting::displayName(protocol));
522 label->setObjectName(QStringLiteral("protocol label"));
523 return label;
524 }
525
createSigningCombo(const QString & addr,const GpgME::Key & key,Protocol protocol=UnknownProtocol)526 ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, Protocol protocol = UnknownProtocol)
527 {
528 Q_ASSERT(!key.isNull() || protocol != UnknownProtocol);
529 protocol = !key.isNull() ? key.protocol() : protocol;
530
531 auto combo = new KeySelectionCombo();
532 auto comboWidget = new ComboWidget(combo);
533 #ifndef NDEBUG
534 combo->setObjectName(QStringLiteral("signing key"));
535 #endif
536 if (protocol == GpgME::OpenPGP) {
537 combo->setKeyFilter(s_pgpSignFilter);
538 } else if (protocol == GpgME::CMS) {
539 combo->setKeyFilter(s_smimeSignFilter);
540 }
541 if (key.isNull() || key_has_addr(key, mSender)) {
542 comboWidget->setIdFilter(mSender);
543 }
544 comboWidget->setFixedProtocol(protocol);
545 if (!key.isNull()) {
546 combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol);
547 }
548 if (key.isNull() && protocol == OpenPGP) {
549 combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")),
550 i18n("Generate a new key pair"), GenerateKey,
551 mGenerateTooltip);
552 }
553 combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")),
554 i18n("Don't confirm identity and integrity"), IgnoreKey,
555 i18nc("@info:tooltip for not selecting a key for signing.",
556 "The E-Mail will not be cryptographically signed."));
557
558 mSigningCombos << combo;
559 mAllCombos << combo;
560 combo->setProperty("address", addr);
561
562 connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () {
563 updateOkButton();
564 });
565 connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this] () {
566 updateOkButton();
567 });
568
569 return comboWidget;
570 }
571
setSigningKeys(const std::vector<GpgME::Key> & preferredKeys,const std::vector<GpgME::Key> & alternativeKeys)572 void setSigningKeys(const std::vector<GpgME::Key> &preferredKeys, const std::vector<GpgME::Key> &alternativeKeys)
573 {
574 auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender));
575 group->setAlignment(Qt::AlignLeft);
576 auto sigLayout = new QVBoxLayout(group);
577
578 const bool mayNeedOpenPGP = mForcedProtocol != CMS;
579 const bool mayNeedCMS = mForcedProtocol != OpenPGP;
580 if (mayNeedOpenPGP) {
581 if (mAllowMixed) {
582 sigLayout->addWidget(createProtocolLabel(OpenPGP));
583 }
584 const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP);
585 const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP);
586 if (!preferredKey.isNull()) {
587 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
588 auto comboWidget = createSigningCombo(mSender, preferredKey);
589 sigLayout->addWidget(comboWidget);
590 } else if (!alternativeKey.isNull()) {
591 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
592 auto comboWidget = createSigningCombo(mSender, alternativeKey);
593 sigLayout->addWidget(comboWidget);
594 } else {
595 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key";
596 auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP);
597 sigLayout->addWidget(comboWidget);
598 }
599 }
600 if (mayNeedCMS) {
601 if (mAllowMixed) {
602 sigLayout->addWidget(createProtocolLabel(CMS));
603 }
604 const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS);
605 const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS);
606 if (!preferredKey.isNull()) {
607 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
608 auto comboWidget = createSigningCombo(mSender, preferredKey);
609 sigLayout->addWidget(comboWidget);
610 } else if (!alternativeKey.isNull()) {
611 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
612 auto comboWidget = createSigningCombo(mSender, alternativeKey);
613 sigLayout->addWidget(comboWidget);
614 } else {
615 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key";
616 auto comboWidget = createSigningCombo(mSender, Key(), CMS);
617 sigLayout->addWidget(comboWidget);
618 }
619 }
620
621 mScrollLayout->addWidget(group);
622 }
623
createEncryptionCombo(const QString & addr,const GpgME::Key & key,Protocol fixedProtocol)624 ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, Protocol fixedProtocol)
625 {
626 auto combo = new KeySelectionCombo(false);
627 auto comboWidget = new ComboWidget(combo);
628 #ifndef NDEBUG
629 combo->setObjectName(QStringLiteral("encryption key"));
630 #endif
631 if (fixedProtocol == GpgME::OpenPGP) {
632 combo->setKeyFilter(s_pgpEncryptFilter);
633 } else if (fixedProtocol == GpgME::CMS) {
634 combo->setKeyFilter(s_smimeEncryptFilter);
635 } else {
636 combo->setKeyFilter(s_encryptFilter);
637 }
638 if (key.isNull() || key_has_addr (key, addr)) {
639 comboWidget->setIdFilter(addr);
640 }
641 comboWidget->setFixedProtocol(fixedProtocol);
642 if (!key.isNull()) {
643 combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol);
644 }
645
646 if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) {
647 combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")),
648 i18n("Generate a new key pair"), GenerateKey,
649 mGenerateTooltip);
650 }
651
652 combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")),
653 i18n("No key. Recipient will be unable to decrypt."), IgnoreKey,
654 i18nc("@info:tooltip for No Key selected for a specific recipient.",
655 "Do not select a key for this recipient.<br/><br/>"
656 "The recipient will receive the encrypted E-Mail, but it can only "
657 "be decrypted with the other keys selected in this dialog."));
658
659 connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () {
660 updateOkButton();
661 });
662 connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this] () {
663 updateOkButton();
664 });
665
666 mEncCombos << combo;
667 mAllCombos << combo;
668 combo->setProperty("address", addr);
669 return comboWidget;
670 }
671
addEncryptionAddr(const QString & addr,Protocol preferredKeysProtocol,const std::vector<GpgME::Key> & preferredKeys,Protocol alternativeKeysProtocol,const std::vector<GpgME::Key> & alternativeKeys,QGridLayout * encGrid)672 void addEncryptionAddr(const QString &addr,
673 Protocol preferredKeysProtocol, const std::vector<GpgME::Key> &preferredKeys,
674 Protocol alternativeKeysProtocol, const std::vector<GpgME::Key> &alternativeKeys,
675 QGridLayout *encGrid)
676 {
677 if (addr == mSender) {
678 const bool mayNeedOpenPGP = mForcedProtocol != CMS;
679 const bool mayNeedCMS = mForcedProtocol != OpenPGP;
680 if (mayNeedOpenPGP) {
681 if (mAllowMixed) {
682 encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0);
683 }
684 for (const auto &key : preferredKeys) {
685 if (key.protocol() == OpenPGP) {
686 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
687 auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
688 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
689 }
690 }
691 for (const auto &key : alternativeKeys) {
692 if (key.protocol() == OpenPGP) {
693 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
694 auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
695 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
696 }
697 }
698 if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) {
699 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key";
700 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP);
701 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
702 }
703 }
704 if (mayNeedCMS) {
705 if (mAllowMixed) {
706 encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0);
707 }
708 for (const auto &key : preferredKeys) {
709 if (key.protocol() == CMS) {
710 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
711 auto comboWidget = createEncryptionCombo(addr, key, CMS);
712 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
713 }
714 }
715 for (const auto &key : alternativeKeys) {
716 if (key.protocol() == CMS) {
717 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
718 auto comboWidget = createEncryptionCombo(addr, key, CMS);
719 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
720 }
721 }
722 if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) {
723 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key";
724 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS);
725 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
726 }
727 }
728 } else {
729 encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0);
730
731 for (const auto &key : preferredKeys) {
732 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
733 auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol);
734 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
735 }
736 for (const auto &key : alternativeKeys) {
737 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
738 auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol);
739 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
740 }
741 if (!mAllowMixed) {
742 if (preferredKeys.empty()) {
743 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol) << "key";
744 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol);
745 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
746 }
747 if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) {
748 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(alternativeKeysProtocol) << "key";
749 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol);
750 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
751 }
752 } else {
753 if (preferredKeys.empty() && alternativeKeys.empty()) {
754 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key";
755 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol);
756 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
757 }
758 }
759 }
760 }
761
setEncryptionKeys(Protocol preferredKeysProtocol,const QMap<QString,std::vector<GpgME::Key>> & preferredKeys,Protocol alternativeKeysProtocol,const QMap<QString,std::vector<GpgME::Key>> & alternativeKeys)762 void setEncryptionKeys(Protocol preferredKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &preferredKeys,
763 Protocol alternativeKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &alternativeKeys)
764 {
765 {
766 auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender));
767 #ifndef NDEBUG
768 group->setObjectName(QStringLiteral("encrypt-to-self box"));
769 #endif
770 group->setAlignment(Qt::AlignLeft);
771 auto encGrid = new QGridLayout(group);
772
773 addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid);
774
775 encGrid->setColumnStretch(1, -1);
776 mScrollLayout->addWidget(group);
777 }
778
779 const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) { return recipient != mSender; });
780 if (hasOtherRecipients) {
781 auto group = new QGroupBox(i18n("Encrypt to others:"));
782 #ifndef NDEBUG
783 group->setObjectName(QStringLiteral("encrypt-to-others box"));
784 #endif
785 group->setAlignment(Qt::AlignLeft);
786 auto encGrid = new QGridLayout{group};
787
788 for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) {
789 const auto &address = it.key();
790 const auto &keys = it.value();
791 if (address != mSender) {
792 addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid);
793 }
794 }
795
796 encGrid->setColumnStretch(1, -1);
797 mScrollLayout->addWidget(group);
798 }
799
800 mScrollLayout->addStretch(-1);
801 }
802
updateOkButton()803 void updateOkButton()
804 {
805 static QString origOkText = mOkButton->text();
806 const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey());
807 const bool allVisibleEncryptionKeysAreIgnored = std::all_of(std::begin(mEncCombos), std::end(mEncCombos),
808 [] (auto combo) {
809 return !combo->isVisible()
810 || combo->currentData(Qt::UserRole).toInt() == IgnoreKey;
811 });
812
813 // If we don't encrypt the ok button is always enabled. But otherwise
814 // we only enable it if we encrypt to at least one recipient.
815 mOkButton->setEnabled(!mEncrypt || !allVisibleEncryptionKeysAreIgnored);
816
817 mOkButton->setText(isGenerate ? i18n("Generate") : origOkText);
818
819 if (!Kleo::gnupgUsesDeVsCompliance()) {
820 return;
821 }
822
823 // Handle compliance
824 bool de_vs = Kleo::gnupgIsDeVsCompliant();
825
826 if (de_vs) {
827 const Protocol protocol = currentProtocol();
828
829 for (const auto combo: std::as_const(mAllCombos)) {
830 if (!combo->isVisible()) {
831 continue;
832 }
833 const auto key = combo->currentKey();
834 if (key.isNull()) {
835 continue;
836 }
837 if (protocol != UnknownProtocol && key.protocol() != protocol) {
838 continue;
839 }
840 if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
841 de_vs = false;
842 break;
843 }
844 }
845 }
846
847 mOkButton->setIcon(QIcon::fromTheme(de_vs
848 ? QStringLiteral("security-high")
849 : QStringLiteral("security-medium")));
850 mOkButton->setStyleSheet(QStringLiteral("background-color: ") + (de_vs
851 ? QStringLiteral("#D5FAE2") // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name()
852 : QStringLiteral("#FAE9EB"))); //KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name()));
853 mComplianceLbl->setText(de_vs
854 ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
855 "%1 communication possible.", Formatting::deVsString())
856 : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
857 "%1 communication not possible.", Formatting::deVsString()));
858 mComplianceLbl->setVisible(true);
859 }
860
861 GpgME::Protocol mForcedProtocol;
862 QList<KeySelectionCombo *> mSigningCombos;
863 QList<KeySelectionCombo *> mEncCombos;
864 QList<KeySelectionCombo *> mAllCombos;
865 QScrollArea *mScrollArea;
866 QVBoxLayout *mScrollLayout;
867 QPushButton *mOkButton;
868 QVBoxLayout *mMainLay;
869 QButtonGroup *mFormatBtns;
870 QString mSender;
871 bool mSign;
872 bool mEncrypt;
873 bool mAllowMixed;
874 NewKeyApprovalDialog *q;
875 QList <QGpgME::Job *> mRunningJobs;
876 GpgME::Error mLastError;
877 QLabel *mComplianceLbl;
878 KeyResolver::Solution mAcceptedResult;
879 QString mGenerateTooltip;
880 };
881
NewKeyApprovalDialog(bool encrypt,bool sign,const QString & sender,KeyResolver::Solution preferredSolution,KeyResolver::Solution alternativeSolution,bool allowMixed,GpgME::Protocol forcedProtocol,QWidget * parent,Qt::WindowFlags f)882 NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt,
883 bool sign,
884 const QString &sender,
885 KeyResolver::Solution preferredSolution,
886 KeyResolver::Solution alternativeSolution,
887 bool allowMixed,
888 GpgME::Protocol forcedProtocol,
889 QWidget *parent,
890 Qt::WindowFlags f)
891 : QDialog(parent, f)
892 , d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)}
893 {
894 if (sign) {
895 d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys));
896 }
897 if (encrypt) {
898 d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol, std::move(preferredSolution.encryptionKeys),
899 allowMixed ? UnknownProtocol : alternativeSolution.protocol, std::move(alternativeSolution.encryptionKeys));
900 }
901 d->updateWidgets();
902 d->updateOkButton();
903
904 const auto size = sizeHint();
905 const auto desk = QApplication::desktop()->screenGeometry(this);
906 resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2)));
907 }
908
909 Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default;
910
result()911 KeyResolver::Solution NewKeyApprovalDialog::result()
912 {
913 return d->mAcceptedResult;
914 }
915
916 #include "newkeyapprovaldialog.moc"
917