newkeyapprovaldialog.cpp 35.9 KB
Newer Older
1
2
3
4
/*  -*- c++ -*-
    newkeyapprovaldialog.cpp

    This file is part of libkleopatra, the KDE keymanagement library
5
    SPDX-FileCopyrightText: 2018 Intevation GmbH
Ingo Klöcker's avatar
Ingo Klöcker committed
6
7
    SPDX-FileCopyrightText: 2021 g10 Code GmbH
    SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8

9
    SPDX-License-Identifier: GPL-2.0-or-later
10
11
12
13
14
15
16
17
18
19
20
21
*/

#include "newkeyapprovaldialog.h"
#include "kleo/defaultkeyfilter.h"
#include "keyselectioncombo.h"
#include "progressdialog.h"
#include "utils/formatting.h"

#include "libkleo_debug.h"

#include <QApplication>
#include <QButtonGroup>
22
#include <QCheckBox>
23
24
25
26
27
28
29
30
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QMap>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
31
#include <QToolTip>
32
33
34
35
36
37
38
39
40
41
42
43
#include <QVBoxLayout>

#include <QGpgME/DefaultKeyGenerationJob>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/key.h>

#include <KLocalizedString>
#include <KMessageBox>

using namespace Kleo;
44
using namespace GpgME;
45

46
47
48
49
50
51
52
53
54
55
QDebug operator<<(QDebug debug, const GpgME::Key &key)
{
    if (key.isNull()) {
        debug << "Null";
    } else {
        debug << Formatting::summaryLine(key);
    }
    return debug.maybeSpace();
}

56
namespace {
57
58
59
60
61
62
63
64
65
class EncryptFilter: public DefaultKeyFilter
{
public:
    EncryptFilter() : DefaultKeyFilter()
    {
        setCanEncrypt(DefaultKeyFilter::Set);
    }
};
static std::shared_ptr<KeyFilter> s_encryptFilter = std::shared_ptr<KeyFilter>(new EncryptFilter);
66

67
68
69
70
71
72
class OpenPGPFilter: public DefaultKeyFilter
{
public:
    OpenPGPFilter() : DefaultKeyFilter()
    {
        setIsOpenPGP(DefaultKeyFilter::Set);
73
        setCanEncrypt(DefaultKeyFilter::Set);
74
75
    }
};
76
static std::shared_ptr<KeyFilter> s_pgpEncryptFilter = std::shared_ptr<KeyFilter> (new OpenPGPFilter);
77

78
79
80
81
82
83
class OpenPGPSignFilter: public DefaultKeyFilter
{
public:
    OpenPGPSignFilter() : DefaultKeyFilter()
    {
        /* Also list unusable keys to make it transparent why they are unusable */
84
85
86
87
        setDisabled(DefaultKeyFilter::NotSet);
        setRevoked(DefaultKeyFilter::NotSet);
        setExpired(DefaultKeyFilter::NotSet);
        setCanSign(DefaultKeyFilter::Set);
88
89
90
91
92
        setHasSecret(DefaultKeyFilter::Set);
        setIsOpenPGP(DefaultKeyFilter::Set);
    }
};
static std::shared_ptr<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter> (new OpenPGPSignFilter);
93

94
95
96
97
98
99
class SMIMEFilter: public DefaultKeyFilter
{
public:
    SMIMEFilter(): DefaultKeyFilter()
    {
        setIsOpenPGP(DefaultKeyFilter::NotSet);
100
        setCanEncrypt(DefaultKeyFilter::Set);
101
102
    }
};
103
static std::shared_ptr<KeyFilter> s_smimeEncryptFilter = std::shared_ptr<KeyFilter> (new SMIMEFilter);
104

105
106
107
108
109
class SMIMESignFilter: public DefaultKeyFilter
{
public:
    SMIMESignFilter(): DefaultKeyFilter()
    {
110
111
112
113
        setDisabled(DefaultKeyFilter::NotSet);
        setRevoked(DefaultKeyFilter::NotSet);
        setExpired(DefaultKeyFilter::NotSet);
        setCanSign(DefaultKeyFilter::Set);
114
115
116
117
118
119
120
121
122
123
124
125
126
        setIsOpenPGP(DefaultKeyFilter::NotSet);
        setHasSecret(DefaultKeyFilter::Set);
    }
};
static std::shared_ptr<KeyFilter> s_smimeSignFilter = std::shared_ptr<KeyFilter> (new SMIMESignFilter);

/* Some decoration and a button to remove the filter for a keyselectioncombo */
class ComboWidget: public QWidget
{
    Q_OBJECT
public:
    explicit ComboWidget(KeySelectionCombo *combo):
        mCombo(combo),
127
        mFilterBtn(new QPushButton)
128
129
    {
        auto hLay = new QHBoxLayout(this);
130
        auto infoBtn = new QPushButton;
Laurent Montel's avatar
Laurent Montel committed
131
        infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
132
133
134
        infoBtn->setIconSize(QSize(22,22));
        infoBtn->setFlat(true);
        hLay->addWidget(infoBtn);
135
136
137
        hLay->addWidget(combo, 1);
        hLay->addWidget(mFilterBtn, 0);

138
139
140
141
142
        connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn] () {
            QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0),
                    mCombo->currentData(Qt::ToolTipRole).toString(), infoBtn, QRect(), 30000);
        });

143
144
145
146
147
        // FIXME: This is ugly to enforce but otherwise the
        // icon is broken.
        combo->setMinimumHeight(22);
        mFilterBtn->setMinimumHeight(23);

148
149
        updateFilterButton();

150
151
152
        connect(mFilterBtn, &QPushButton::clicked, this, [this] () {
            const QString curFilter = mCombo->idFilter();
            if (curFilter.isEmpty()) {
153
                setIdFilter(mLastIdFilter);
154
155
                mLastIdFilter = QString();
            } else {
156
                setIdFilter(QString());
157
158
159
160
161
                mLastIdFilter = curFilter;
            }
        });
    }

162
163
164
165
166
167
168
169
170
171
    void setIdFilter(const QString &id)
    {
        mCombo->setIdFilter(id);
        updateFilterButton();
    }

    void updateFilterButton()
    {
        if (mCombo->idFilter().isEmpty()) {
            mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters")));
172
            mFilterBtn->setToolTip(i18n("Show keys matching the email address"));
173
174
        } else {
            mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
175
            mFilterBtn->setToolTip(i18n("Show all keys"));
176
177
178
        }
    }

179
180
181
182
183
    KeySelectionCombo *combo()
    {
        return mCombo;
    }

184
185
186
187
188
189
190
191
192
193
    GpgME::Protocol fixedProtocol() const
    {
        return mFixedProtocol;
    }

    void setFixedProtocol(GpgME::Protocol proto)
    {
        mFixedProtocol = proto;
    }

194
195
196
197
private:
    KeySelectionCombo *mCombo;
    QPushButton *mFilterBtn;
    QString mLastIdFilter;
198
    GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol;
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
};

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;
}
214
215
216
217
218
219
220
221
222
223
224

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;
}

225
226
227
228
229
230
231
232
233
234
235
bool anyKeyHasProtocol(const std::vector<Key> &keys, Protocol protocol)
{
    return std::any_of(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; });
}

Key findfirstKeyOfType(const std::vector<Key> &keys, Protocol protocol)
{
    const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol] (const auto &key) { return key.protocol() == protocol; });
    return it != std::end(keys) ? *it : Key();
}

236
237
238
239
240
241
242
243
244
245
246
} // namespace

class NewKeyApprovalDialog::Private
{
private:
    enum Action {
        Unset,
        GenerateKey,
        IgnoreKey,
    };
public:
247
248
249
250
251
    enum {
        OpenPGPButtonId = 1,
        SMIMEButtonId = 2
    };

252
    Private(NewKeyApprovalDialog *qq,
253
254
            bool encrypt,
            bool sign,
255
256
            GpgME::Protocol forcedProtocol,
            GpgME::Protocol presetProtocol,
257
258
            const QString &sender,
            bool allowMixed)
259
260
261
262
263
264
        : mForcedProtocol{forcedProtocol}
        , mSender{sender}
        , mSign{sign}
        , mEncrypt{encrypt}
        , mAllowMixed{allowMixed}
        , q{qq}
265
    {
266
267
        Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol);
        Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol));
268
        Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol));
269

270
271
272
273
274
275
276
        // 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.<br/><br/>"
                                 "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.");
277
278
279
280
        mMainLay = new QVBoxLayout;

        QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
        mOkButton = btnBox->button(QDialogButtonBox::Ok);
281
282
283
#ifndef NDEBUG
        mOkButton->setObjectName(QStringLiteral("ok button"));
#endif
284
285
286
287
288
289
290
291
292
293
294
295
        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);
Laurent Montel's avatar
Laurent Montel committed
296
        mScrollLayout->setContentsMargins(0, 0, 0, 0);
297

298
        q->setWindowTitle(i18nc("@title:window", "Security approval"));
299
300

        auto fmtLayout = new QHBoxLayout;
Laurent Montel's avatar
Laurent Montel committed
301
        mFormatBtns = new QButtonGroup(qq);
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
        QAbstractButton *pgpBtn;
        QAbstractButton *smimeBtn;
        if (mAllowMixed) {
            pgpBtn = new QCheckBox(i18n("OpenPGP"));
            smimeBtn = new QCheckBox(i18n("S/MIME"));
        } else {
            pgpBtn = new QRadioButton(i18n("OpenPGP"));
            smimeBtn = new QRadioButton(i18n("S/MIME"));
        }
#ifndef NDEBUG
        pgpBtn->setObjectName(QStringLiteral("openpgp button"));
        smimeBtn->setObjectName(QStringLiteral("smime button"));
#endif
        mFormatBtns->addButton(pgpBtn, OpenPGPButtonId);
        mFormatBtns->addButton(smimeBtn, SMIMEButtonId);
        mFormatBtns->setExclusive(!mAllowMixed);
318
319
320
321
322
323

        fmtLayout->addStretch(-1);
        fmtLayout->addWidget(pgpBtn);
        fmtLayout->addWidget(smimeBtn);
        mMainLay->addLayout(fmtLayout);

324
        if (mForcedProtocol != GpgME::UnknownProtocol) {
325
326
            pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP);
            smimeBtn->setChecked(mForcedProtocol == GpgME::CMS);
327
            pgpBtn->setVisible(false);
328
329
            smimeBtn->setVisible(false);
        } else {
330
331
            pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol);
            smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol);
332
333
        }

334
335
336
337
338
339
340
341
342
343
        QObject::connect(mFormatBtns, &QButtonGroup::idClicked,
                         q, [this](int buttonId) {
                             // ensure that at least one protocol button is checked
                             if (mAllowMixed
                                    && !mFormatBtns->button(OpenPGPButtonId)->isChecked()
                                    && !mFormatBtns->button(SMIMEButtonId)->isChecked()) {
                                 mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true);
                             }
                             updateWidgets();
                        });
344
345
346
347
348

        mMainLay->addWidget(mScrollArea);

        mComplianceLbl = new QLabel;
        mComplianceLbl->setVisible(false);
349
350
351
#ifndef NDEBUG
        mComplianceLbl->setObjectName(QStringLiteral("compliance label"));
#endif
352
353
354
355
356
357
358
359
360

        auto btnLayout = new QHBoxLayout;
        btnLayout->addWidget(mComplianceLbl);
        btnLayout->addWidget(btnBox);
        mMainLay->addLayout(btnLayout);

        q->setLayout(mMainLay);
    }

Ingo Klöcker's avatar
Ingo Klöcker committed
361
362
    ~Private() = default;

363
364
    Protocol currentProtocol()
    {
365
366
        const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked();
        const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked();
367
        if (mAllowMixed) {
368
369
370
371
372
373
374
            if (openPGPButtonChecked && !smimeButtonChecked) {
                return OpenPGP;
            }
            if (!openPGPButtonChecked && smimeButtonChecked) {
                return CMS;
            }
        } else if (openPGPButtonChecked) {
375
            return OpenPGP;
376
        } else if (smimeButtonChecked) {
377
378
379
380
381
            return CMS;
        }
        return UnknownProtocol;
    }

382
383
384
385
386
387
388
389
390
391
    auto findVisibleKeySelectionComboWithGenerateKey()
    {
        const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos),
                                     [] (auto combo) {
                                         return combo->isVisible()
                                             && combo->currentData(Qt::UserRole).toInt() == GenerateKey;
                                     });
        return it != std::end(mAllCombos) ? *it : nullptr;
    }

392
393
394
395
    void generateKey(KeySelectionCombo *combo)
    {
        const auto &addr = combo->property("address").toString();
        auto job = new QGpgME::DefaultKeyGenerationJob(q);
396
        auto progress = new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") +
Yuri Chornoivan's avatar
Yuri Chornoivan committed
397
                                                 i18n("This can take several minutes."), q);
398
        progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
399
        progress->setWindowTitle(i18nc("@title:window", "Key generation"));
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
        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()) {
418
            combo->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP);
419
420
421
422
423
424
425
426
427
428
429
430
            connect (combo, &KeySelectionCombo::keyListingFinished, q, [this, job] () {
                    mRunningJobs.removeAll(job);
                });
            combo->refreshKeys();
        } else {
            mRunningJobs.removeAll(job);
        }
    }

    void checkAccepted()
    {
        if (mLastError || mLastError.isCanceled()) {
Laurent Montel's avatar
Laurent Montel committed
431
            KMessageBox::error(q, QString::fromLocal8Bit(mLastError.asString()), i18n("Operation Failed"));
432
433
434
            mRunningJobs.clear();
            return;
        }
435
436
437

        if (!mRunningJobs.empty()) {
            return;
438
        }
439

440
441
        /* Save the keys */
        mAcceptedResult.protocol = currentProtocol();
Laurent Montel's avatar
Laurent Montel committed
442
        for (const auto combo: qAsConst(mEncCombos)) {
443
444
445
            const auto addr = combo->property("address").toString();
            const auto key = combo->currentKey();
            if (!combo->isVisible() || key.isNull()) {
446
447
                continue;
            }
448
            mAcceptedResult.encryptionKeys[addr].push_back(key);
449
        }
Laurent Montel's avatar
Laurent Montel committed
450
        for (const auto combo: qAsConst(mSigningCombos)) {
451
            const auto key = combo->currentKey();
452
            if (!combo->isVisible() || key.isNull()) {
453
454
                continue;
            }
455
            mAcceptedResult.signingKeys.push_back(key);
456
457
458
        }

        q->accept();
459
460
461
462
463
464
465
    }

    void accepted()
    {
        // We can assume everything was validly resolved, otherwise
        // the OK button would have been disabled.
        // Handle custom items now.
466
467
468
        if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) {
            generateKey(combo);
            return;
469
470
471
472
        }
        checkAccepted();
    }

473
474
475
476
477
478
479
480
481
482
483
484
485
    auto encryptionKeyFilter(Protocol protocol)
    {
        switch (protocol) {
        case OpenPGP:
            return s_pgpEncryptFilter;
        case CMS:
            return s_smimeEncryptFilter;
        default:
            return s_encryptFilter;
        }
    }

    void updateWidgets()
486
    {
487
        const Protocol protocol = currentProtocol();
488
        const auto encryptionFilter = encryptionKeyFilter(protocol);
489

Laurent Montel's avatar
Laurent Montel committed
490
        for (auto combo: qAsConst(mSigningCombos)) {
491
            auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
492
493
494
495
            if (!widget) {
                qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget";
                continue;
            }
496
            widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol);
497
        }
Laurent Montel's avatar
Laurent Montel committed
498
        for (auto combo: qAsConst(mEncCombos)) {
499
            auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
500
501
502
503
            if (!widget) {
                qCDebug(LIBKLEO_LOG) << "Failed to find combo widget";
                continue;
            }
504
505
506
507
508
509
510
511
512
            widget->setVisible(protocol == UnknownProtocol || widget->fixedProtocol() == UnknownProtocol || widget->fixedProtocol() == protocol);
            if (widget->isVisible() && combo->property("address") != mSender) {
                combo->setKeyFilter(encryptionFilter);
            }
        }
        // hide the labels indicating the protocol of the sender's keys if only a single protocol is active
        const auto protocolLabels = q->findChildren<QLabel *>(QStringLiteral("protocol label"));
        for (auto label: protocolLabels) {
            label->setVisible(protocol == UnknownProtocol);
513
514
515
        }
    }

516
517
518
519
520
521
522
    auto createProtocolLabel(Protocol protocol)
    {
        auto label = new QLabel(Formatting::displayName(protocol));
        label->setObjectName(QStringLiteral("protocol label"));
        return label;
    }

523
    ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, Protocol protocol = UnknownProtocol)
524
    {
525
526
527
        Q_ASSERT(!key.isNull() || protocol != UnknownProtocol);
        protocol = !key.isNull() ? key.protocol() : protocol;

528
        auto combo = new KeySelectionCombo();
529
        auto comboWidget = new ComboWidget(combo);
530
531
532
#ifndef NDEBUG
        combo->setObjectName(QStringLiteral("signing key"));
#endif
533
534
535
536
        if (protocol == GpgME::OpenPGP) {
            combo->setKeyFilter(s_pgpSignFilter);
        } else if (protocol == GpgME::CMS) {
            combo->setKeyFilter(s_smimeSignFilter);
537
        }
538
539
540
541
542
543
        if (key.isNull() || key_has_addr(key, mSender)) {
            comboWidget->setIdFilter(mSender);
        }
        comboWidget->setFixedProtocol(protocol);
        if (!key.isNull()) {
            combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol);
544
        }
545
        if (key.isNull() && protocol == OpenPGP) {
546
            combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")),
547
548
                                    i18n("Generate a new key pair"), GenerateKey,
                                    mGenerateTooltip);
549
550
        }
        combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")),
551
552
553
                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."));
554
555
556
557
558
559
560
561

        mSigningCombos << combo;
        mAllCombos << combo;
        combo->setProperty("address", addr);

        connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () {
            updateOkButton();
        });
562
563
564
        connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), q, [this] () {
            updateOkButton();
        });
565

566
        return comboWidget;
567
568
    }

569
    void setSigningKeys(std::vector<GpgME::Key> preferredKeys, std::vector<GpgME::Key> alternativeKeys)
570
    {
571
572
573
574
575
576
577
578
        auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender));
        group->setAlignment(Qt::AlignLeft);
        auto sigLayout = new QVBoxLayout(group);

        const bool mayNeedOpenPGP = mForcedProtocol != CMS;
        const bool mayNeedCMS = mForcedProtocol != OpenPGP;
        if (mayNeedOpenPGP) {
            if (mAllowMixed) {
579
                sigLayout->addWidget(createProtocolLabel(OpenPGP));
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
            }
            const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP);
            const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP);
            if (!preferredKey.isNull()) {
                qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
                auto comboWidget = createSigningCombo(mSender, preferredKey);
                sigLayout->addWidget(comboWidget);
            } else if (!alternativeKey.isNull()) {
                qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
                auto comboWidget = createSigningCombo(mSender, alternativeKey);
                sigLayout->addWidget(comboWidget);
            } else {
                qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key";
                auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP);
                sigLayout->addWidget(comboWidget);
            }
596
        }
597
598
        if (mayNeedCMS) {
            if (mAllowMixed) {
599
                sigLayout->addWidget(createProtocolLabel(CMS));
600
601
602
603
604
605
606
607
608
609
610
611
612
613
            }
            const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS);
            const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS);
            if (!preferredKey.isNull()) {
                qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
                auto comboWidget = createSigningCombo(mSender, preferredKey);
                sigLayout->addWidget(comboWidget);
            } else if (!alternativeKey.isNull()) {
                qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
                auto comboWidget = createSigningCombo(mSender, alternativeKey);
                sigLayout->addWidget(comboWidget);
            } else {
                qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key";
                auto comboWidget = createSigningCombo(mSender, Key(), CMS);
614
615
616
617
                sigLayout->addWidget(comboWidget);
            }
        }

618
        mScrollLayout->addWidget(group);
619
620
    }

621
    ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, Protocol fixedProtocol)
622
    {
623
        auto combo = new KeySelectionCombo(false);
624
        auto comboWidget = new ComboWidget(combo);
625
#ifndef NDEBUG
626
        combo->setObjectName(QStringLiteral("encryption key"));
627
#endif
628
        if (fixedProtocol == GpgME::OpenPGP) {
629
            combo->setKeyFilter(s_pgpEncryptFilter);
630
        } else if (fixedProtocol == GpgME::CMS) {
631
            combo->setKeyFilter(s_smimeEncryptFilter);
632
        } else {
633
            combo->setKeyFilter(s_encryptFilter);
634
635
636
637
638
        }
        if (key.isNull() || key_has_addr (key, addr)) {
            comboWidget->setIdFilter(addr);
        }
        comboWidget->setFixedProtocol(fixedProtocol);
639
        if (!key.isNull()) {
640
            combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol);
641
        }
642

643
        if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) {
644
645
646
647
            combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")),
                                    i18n("Generate a new key pair"), GenerateKey,
                                    mGenerateTooltip);
        }
648

649
650
651
652
653
654
655
656
657
658
659
660
661
        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.<br/><br/>"
                        "The recipient will receive the encrypted E-Mail, but it can only "
                        "be decrypted with the other keys selected in this dialog."));

        connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () {
            updateOkButton();
        });
        connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), q, [this] () {
            updateOkButton();
        });
662

663
664
665
        mEncCombos << combo;
        mAllCombos << combo;
        combo->setProperty("address", addr);
666
        return comboWidget;
667
    }
668

669
670
671
    void addEncryptionAddr(const QString &addr,
                           Protocol preferredKeysProtocol, const std::vector<GpgME::Key> &preferredKeys,
                           Protocol alternativeKeysProtocol, const std::vector<GpgME::Key> &alternativeKeys,
672
673
                           QGridLayout *encGrid)
    {
674
675
676
677
678
        if (addr == mSender) {
            const bool mayNeedOpenPGP = mForcedProtocol != CMS;
            const bool mayNeedCMS = mForcedProtocol != OpenPGP;
            if (mayNeedOpenPGP) {
                if (mAllowMixed) {
679
                    encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0);
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
                }
                for (const auto &key : preferredKeys) {
                    if (key.protocol() == OpenPGP) {
                        qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
                        auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
                        encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
                    }
                }
                for (const auto &key : alternativeKeys) {
                    if (key.protocol() == OpenPGP) {
                        qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
                        auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
                        encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
                    }
                }
                if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) {
                    qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key";
                    auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP);
                    encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
                }
700
            }
701
702
            if (mayNeedCMS) {
                if (mAllowMixed) {
703
                    encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0);
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
                }
                for (const auto &key : preferredKeys) {
                    if (key.protocol() == CMS) {
                        qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
                        auto comboWidget = createEncryptionCombo(addr, key, CMS);
                        encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
                    }
                }
                for (const auto &key : alternativeKeys) {
                    if (key.protocol() == CMS) {
                        qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
                        auto comboWidget = createEncryptionCombo(addr, key, CMS);
                        encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
                    }
                }
                if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) {
                    qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key";
                    auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS);
                    encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
723
                }
724
725
726
727
728
729
730
731
732
733
734
735
            }
        } else {
            encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0);

            for (const auto &key : preferredKeys) {
                qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
                auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol);
                encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
            }
            for (const auto &key : alternativeKeys) {
                qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
                auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol);
736
737
                encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
            }
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
            if (!mAllowMixed) {
                if (preferredKeys.empty()) {
                    qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol) << "key";
                    auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol);
                    encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
                }
                if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) {
                    qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(alternativeKeysProtocol) << "key";
                    auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol);
                    encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
                }
            } else {
                if (preferredKeys.empty() && alternativeKeys.empty()) {
                    qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key";
                    auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol);
                    encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
                }
            }
756
757
758
        }
    }

759
760
    void setEncryptionKeys(Protocol preferredKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &preferredKeys,
                           Protocol alternativeKeysProtocol, const QMap<QString, std::vector<GpgME::Key>> &alternativeKeys)
761
    {
762
763
764
        {
            auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender));
            group->setAlignment(Qt::AlignLeft);
765
766
767
768
            auto encGrid = new QGridLayout(group);

            addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid);

769
770
771
772
            mScrollLayout->addWidget(group);
        }

        auto group = new QGroupBox(i18n("Encrypt to others:"));
773
774
775
776
777
        group->setAlignment(Qt::AlignLeft);
        auto encGrid = new QGridLayout;
        group->setLayout(encGrid);
        mScrollLayout->addWidget(group);

778
779
780
781
782
        for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) {
            const auto &address = it.key();
            const auto &keys = it.value();
            if (address != mSender) {
                addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid);
783
            }
784
785
786
787
788
789
790
791
792
        }

        encGrid->setColumnStretch(1, -1);
        mScrollLayout->addStretch(-1);
    }

    void updateOkButton()
    {
        static QString origOkText = mOkButton->text();
793
        const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey());
794
795
796
797
798
        const bool allVisibleEncryptionKeysAreIgnored = std::all_of(std::begin(mEncCombos), std::end(mEncCombos),
                                                                    [] (auto combo) {
                                                                        return !combo->isVisible()
                                                                            || combo->currentData(Qt::UserRole).toInt() == IgnoreKey;
                                                                    });
799

800
801
        // 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.
802
        mOkButton->setEnabled(!mEncrypt || !allVisibleEncryptionKeysAreIgnored);
803

804
        mOkButton->setText(isGenerate ? i18n("Generate") : origOkText);
805

806
        if (Formatting::complianceMode() != QLatin1String("de-vs")) {
807
808
809
810
811
            return;
        }

        // Handle compliance
        bool de_vs = true;
812

813
        const Protocol protocol = currentProtocol();
814

815
        for (const auto combo: qAsConst(mAllCombos)) {
816
817
818
            if (!combo->isVisible()) {
                continue;
            }
819
820
821
822
            const auto key = combo->currentKey();
            if (key.isNull()) {
                continue;
            }
823
            if (protocol != UnknownProtocol && key.protocol() != protocol) {
824
825
                continue;
            }
826
827
828
829
830
831
832
            if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
                de_vs = false;
                break;
            }
        }

        mOkButton->setIcon(QIcon::fromTheme(de_vs
Laurent Montel's avatar
Laurent Montel committed
833
834
                    ? QStringLiteral("security-high")
                    : QStringLiteral("security-medium")));
835
836
837
838
        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
839
840
841
842
                ? 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()));
843
844
845
        mComplianceLbl->setVisible(true);
    }

846
    GpgME::Protocol mForcedProtocol;
847
848
849
850
851
852
853
854
855
    QList<KeySelectionCombo *> mSigningCombos;
    QList<KeySelectionCombo *> mEncCombos;
    QList<KeySelectionCombo *> mAllCombos;
    QScrollArea *mScrollArea;
    QVBoxLayout *mScrollLayout;
    QPushButton *mOkButton;
    QVBoxLayout *mMainLay;
    QButtonGroup *mFormatBtns;
    QString mSender;
856
857
    bool mSign;
    bool mEncrypt;
858
859
860
861
862
    bool mAllowMixed;
    NewKeyApprovalDialog *q;
    QList <QGpgME::Job *> mRunningJobs;
    GpgME::Error mLastError;
    QLabel *mComplianceLbl;
863
    KeyResolver::Solution mAcceptedResult;
864
    QString mGenerateTooltip;
865
866
};

867
868
NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt,
                                           bool sign,
869
                                           const QString &sender,
870
871
                                           KeyResolver::Solution preferredSolution,
                                           KeyResolver::Solution alternativeSolution,
872
873
874
                                           bool allowMixed,
                                           GpgME::Protocol forcedProtocol,
                                           QWidget *parent,
875
876
877
                                           Qt::WindowFlags f)
    : QDialog(parent, f)
    , d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)}
878
{
879
880
881
882
    if (sign) {
        d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys));
    }
    if (encrypt) {
883
884
        d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol, std::move(preferredSolution.encryptionKeys),
                             allowMixed ? UnknownProtocol : alternativeSolution.protocol, std::move(alternativeSolution.encryptionKeys));
885
    }
886
    d->updateWidgets();
887
888
889
890
891
892
893
    d->updateOkButton();

    const auto size = sizeHint();
    const auto desk = QApplication::desktop()->screenGeometry(this);
    resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2)));
}

Ingo Klöcker's avatar
Ingo Klöcker committed
894
895
Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default;

896
KeyResolver::Solution NewKeyApprovalDialog::result()
897
{
898
    return d->mAcceptedResult;
899
900
901
}

#include "newkeyapprovaldialog.moc"