signencryptwidget.cpp 19.4 KB
Newer Older
1
/*  crypto/gui/signencryptwidget.cpp
2
3

    This file is part of Kleopatra, the KDE keymanager
4
5
    SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
    SPDX-FileContributor: Intevation GmbH
6

7
    SPDX-License-Identifier: GPL-2.0-or-later
8
9
10
11
12
13
*/

#include "signencryptwidget.h"

#include "kleopatra_debug.h"

14
#include "certificatelineedit.h"
15
#include "settings.h"
16
#include "unknownrecipientwidget.h"
17

18
19
20
#include "commands/detailscommand.h"

#include "dialogs/certificateselectiondialog.h"
21
#include "dialogs/groupdetailsdialog.h"
22

23
24
25
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
26
27
#include <QCheckBox>
#include <QScrollArea>
28
#include <QScrollBar>
29

30
#include <Libkleo/DefaultKeyFilter>
31
#include <Libkleo/KeyCache>
32
#include <Libkleo/KeyGroup>
33
#include <Libkleo/KeyListModel>
34
#include <Libkleo/KeySelectionCombo>
35
#include <Libkleo/KeyListSortFilterProxyModel>
36

37
#include <Libkleo/GnuPG>
38

39
#include <KLocalizedString>
40
#include <KConfigGroup>
41
#include <KSharedConfig>
42
#include <KMessageBox>
43
44
45
46
47

using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;

48
49
50
51
namespace {
class SignCertificateFilter: public DefaultKeyFilter
{
public:
52
    SignCertificateFilter(GpgME::Protocol proto) : DefaultKeyFilter()
53
54
55
56
57
    {
        setRevoked(DefaultKeyFilter::NotSet);
        setExpired(DefaultKeyFilter::NotSet);
        setHasSecret(DefaultKeyFilter::Set);
        setCanSign(DefaultKeyFilter::Set);
58
59
60
61
62
63

        if (proto == GpgME::OpenPGP) {
            setIsOpenPGP(DefaultKeyFilter::Set);
        } else if (proto == GpgME::CMS) {
            setIsOpenPGP(DefaultKeyFilter::NotSet);
        }
64
65
66
67
68
    }
};
class EncryptCertificateFilter: public DefaultKeyFilter
{
public:
69
    EncryptCertificateFilter(GpgME::Protocol proto): DefaultKeyFilter()
70
71
72
73
    {
        setRevoked(DefaultKeyFilter::NotSet);
        setExpired(DefaultKeyFilter::NotSet);
        setCanEncrypt(DefaultKeyFilter::Set);
74
75
76
77
78
79

        if (proto == GpgME::OpenPGP) {
            setIsOpenPGP(DefaultKeyFilter::Set);
        } else if (proto == GpgME::CMS) {
            setIsOpenPGP(DefaultKeyFilter::NotSet);
        }
80
81
82
83
84
    }
};
class EncryptSelfCertificateFilter: public EncryptCertificateFilter
{
public:
85
    EncryptSelfCertificateFilter(GpgME::Protocol proto): EncryptCertificateFilter(proto)
86
87
88
89
90
91
92
93
94
    {
        setRevoked(DefaultKeyFilter::NotSet);
        setExpired(DefaultKeyFilter::NotSet);
        setCanEncrypt(DefaultKeyFilter::Set);
        setHasSecret(DefaultKeyFilter::Set);
    }
};
}

95
SignEncryptWidget::SignEncryptWidget(QWidget *parent, bool sigEncExclusive)
96
    : QWidget(parent),
97
      mModel(AbstractKeyListModel::createFlatKeyListModel(this)),
98
99
      mRecpRowCount(2),
      mIsExclusive(sigEncExclusive)
100
{
Laurent Montel's avatar
Laurent Montel committed
101
    auto lay = new QVBoxLayout(this);
Laurent Montel's avatar
Laurent Montel committed
102
    lay->setContentsMargins(0, 0, 0, 0);
103

104
    mModel->useKeyCache(true, KeyList::IncludeGroups);
105

106
    /* The signature selection */
Laurent Montel's avatar
Laurent Montel committed
107
108
    auto sigLay = new QHBoxLayout;
    auto sigGrp = new QGroupBox(i18n("Prove authenticity (sign)"));
109
110
    mSigChk = new QCheckBox(i18n("Sign as:"));
    mSigChk->setChecked(true);
111

112
    mSigSelect = new KeySelectionCombo();
113

114
    sigLay->addWidget(mSigChk);
115
    sigLay->addWidget(mSigSelect, 1);
116
117
    sigGrp->setLayout(sigLay);
    lay->addWidget(sigGrp);
118

119
120
    connect(mSigChk, &QCheckBox::toggled, mSigSelect, &QWidget::setEnabled);
    connect(mSigChk, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp);
121
    connect(mSigSelect, &KeySelectionCombo::currentKeyChanged,
122
123
            this, &SignEncryptWidget::updateOp);

124
    // Recipient selection
125
    mRecpLayout = new QGridLayout;
126
    mRecpLayout->setAlignment(Qt::AlignTop);
Laurent Montel's avatar
Laurent Montel committed
127
128
    auto encBoxLay = new QVBoxLayout;
    auto encBox = new QGroupBox(i18nc("@action", "Encrypt"));
129
    encBox->setLayout(encBoxLay);
130
131
132
133
    encBox->setAlignment(Qt::AlignLeft);

    // Own key
    mSelfSelect = new KeySelectionCombo();
134
135
136
    mEncSelfChk = new QCheckBox(i18n("Encrypt for me:"));
    mEncSelfChk->setChecked(true);
    mRecpLayout->addWidget(mEncSelfChk, 0, 0);
137
138
139
    mRecpLayout->addWidget(mSelfSelect, 0, 1);

    // Checkbox for other keys
140
141
142
143
    mEncOtherChk = new QCheckBox(i18n("Encrypt for others:"));
    mRecpLayout->addWidget(mEncOtherChk, 1, 0);
    mEncOtherChk->setChecked(true);
    connect(mEncOtherChk, &QCheckBox::toggled, this,
144
        [this](bool toggled) {
145
            for (CertificateLineEdit *edit : std::as_const(mRecpWidgets)) {
146
147
148
149
150
151
                edit->setEnabled(toggled);
            }
            updateOp();
        });

    // Scroll area for other keys
Laurent Montel's avatar
Laurent Montel committed
152
153
    auto recipientWidget = new QWidget;
    auto recipientScroll = new QScrollArea;
154
155
156
157
    recipientWidget->setLayout(mRecpLayout);
    recipientScroll->setWidget(recipientWidget);
    recipientScroll->setWidgetResizable(true);
    recipientScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
158
    recipientScroll->setFrameStyle(QFrame::NoFrame);
Laurent Montel's avatar
Laurent Montel committed
159
    mRecpLayout->setContentsMargins(0, 0, 0, 0);
160
161
    encBoxLay->addWidget(recipientScroll, 1);

162
163
164
165
166
    auto bar = recipientScroll->verticalScrollBar();
    connect (bar, &QScrollBar::rangeChanged, this, [bar] (int, int max) {
            bar->setValue(max);
        });

167

168
    // Checkbox for password
169
170
    mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data."));
    mSymmetric->setToolTip(i18nc("Tooltip information for symetric encryption",
171
                                 "Additionally to the keys of the recipients you can encrypt your data with a password. "
Yuri Chornoivan's avatar
Yuri Chornoivan committed
172
                                 "Anyone who has the password can read the data without any secret key. "
173
                                 "Using a password is <b>less secure</b> then public key cryptography. Even if you pick a very strong password."));
174
    encBoxLay->addWidget(mSymmetric);
175

176
177
178
    // Connect it
    connect(encBox, &QGroupBox::toggled, recipientWidget, &QWidget::setEnabled);
    connect(encBox, &QGroupBox::toggled, this, &SignEncryptWidget::updateOp);
179
180
    connect(mEncSelfChk, &QCheckBox::toggled, mSelfSelect, &QWidget::setEnabled);
    connect(mEncSelfChk, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp);
181
    connect(mSymmetric, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp);
182
    connect(mSelfSelect, &KeySelectionCombo::currentKeyChanged,
183
184
            this, &SignEncryptWidget::updateOp);

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
    if (mIsExclusive) {
        connect(mEncOtherChk, &QCheckBox::toggled, this, [this](bool value) {
            if (mCurrentProto != GpgME::CMS) {
                return;
            }
            if (value) {
                mSigChk->setChecked(false);
            }
        });
        connect(mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) {
            if (mCurrentProto != GpgME::CMS) {
                return;
            }
            if (value) {
                mSigChk->setChecked(false);
            }
        });
        connect(mSigChk, &QCheckBox::toggled, this, [this](bool value) {
            if (mCurrentProto != GpgME::CMS) {
                return;
            }
            if (value) {
                mEncSelfChk->setChecked(false);
                mEncOtherChk->setChecked(false);
            }
        });
    }

213
214
    // Ensure that the mSigChk is aligned togehter with the encryption check boxes.
    mSigChk->setMinimumWidth(qMax(mEncOtherChk->width(), mEncSelfChk->width()));
215

216
217
    lay->addWidget(encBox);

218
    loadKeys();
219
    setProtocol(GpgME::UnknownProtocol);
220
    addRecipientWidget();
221
222
223
    updateOp();
}

224
CertificateLineEdit *SignEncryptWidget::addRecipientWidget()
225
{
Laurent Montel's avatar
Laurent Montel committed
226
    auto certSel = new CertificateLineEdit(mModel, this,
227
                                                           new EncryptCertificateFilter(mCurrentProto));
228
    mRecpWidgets << certSel;
229

230
    if (!mRecpLayout->itemAtPosition(mRecpRowCount - 1, 1)) {
231
        // First widget. Should align with the row above that
232
        // contains the encrypt for others checkbox.
233
        mRecpLayout->addWidget(certSel, mRecpRowCount - 1, 1);
234
    } else {
235
        mRecpLayout->addWidget(certSel, mRecpRowCount++, 1);
236
237
    }

238
    connect(certSel, &CertificateLineEdit::keyChanged,
239
            this, &SignEncryptWidget::recipientsChanged);
240
241
242
    connect(certSel, &CertificateLineEdit::wantsRemoval,
            this, &SignEncryptWidget::recpRemovalRequested);
    connect(certSel, &CertificateLineEdit::editingStarted,
243
            this, [this] () { addRecipientWidget(); });
244
245
    connect(certSel, &CertificateLineEdit::dialogRequested,
            this, [this, certSel] () { dialogRequested(certSel); });
246

247
248
249
250
251
252
    return certSel;
}

void SignEncryptWidget::addRecipient(const Key &key)
{
    CertificateLineEdit *certSel = addRecipientWidget();
253
254
    if (!key.isNull()) {
        certSel->setKey(key);
255
        mAddedKeys << key;
256
    }
257
258
}

259
260
261
262
263
264
265
266
267
void SignEncryptWidget::addRecipient(const KeyGroup &group)
{
    CertificateLineEdit *certSel = addRecipientWidget();
    if (!group.isNull()) {
        certSel->setGroup(group);
        mAddedGroups << group;
    }
}

268
269
270
271
272
273
274
void SignEncryptWidget::dialogRequested(CertificateLineEdit *certificateLineEdit)
{
    if (!certificateLineEdit->key().isNull()) {
        auto cmd = new Commands::DetailsCommand(certificateLineEdit->key(), nullptr);
        cmd->start();
        return;
    }
275
    if (!certificateLineEdit->group().isNull()) {
Laurent Montel's avatar
Laurent Montel committed
276
        auto dlg = new GroupDetailsDialog;
277
278
279
280
281
        dlg->setAttribute(Qt::WA_DeleteOnClose);
        dlg->setGroup(certificateLineEdit->group());
        dlg->show();
        return;
    }
282

Laurent Montel's avatar
Laurent Montel committed
283
    auto const dlg = new CertificateSelectionDialog(this);
284

285
286
287
288
289
    dlg->setOptions(CertificateSelectionDialog::Options(
        CertificateSelectionDialog::MultiSelection |
        CertificateSelectionDialog::EncryptOnly |
        CertificateSelectionDialog::optionsFromProtocol(mCurrentProto) |
        CertificateSelectionDialog::IncludeGroups));
290
291
292

    if (dlg->exec()) {
        const std::vector<Key> keys = dlg->selectedCertificates();
293
294
        const std::vector<KeyGroup> groups = dlg->selectedGroups();
        if (keys.size() == 0 && groups.size() == 0) {
295
296
            return;
        }
297
298
299
300
301
        bool isFirstItem = true;
        for (const Key &key : keys) {
            if (isFirstItem) {
                certificateLineEdit->setKey(key);
                isFirstItem = false;
302
            } else {
303
304
305
306
307
308
309
310
311
                addRecipient(key);
            }
        }
        for (const KeyGroup &group : groups) {
            if (isFirstItem) {
                certificateLineEdit->setGroup(group);
                isFirstItem = false;
            } else {
                addRecipient(group);
312
313
314
315
316
317
318
            }
        }
    }
    delete dlg;
    recipientsChanged();
}

319
320
void SignEncryptWidget::clearAddedRecipients()
{
321
    for (auto w: std::as_const(mUnknownWidgets)) {
322
323
324
325
        mRecpLayout->removeWidget(w);
        delete w;
    }

326
    for (auto &key: std::as_const(mAddedKeys)) {
327
328
        removeRecipient(key);
    }
329

330
    for (auto &group: std::as_const(mAddedGroups)) {
331
332
        removeRecipient(group);
    }
333
334
335
336
337
338
339
340
341
}

void SignEncryptWidget::addUnknownRecipient(const char *keyID)
{
    auto unknownWidget = new UnknownRecipientWidget(keyID);
    mUnknownWidgets << unknownWidget;

    if (!mRecpLayout->itemAtPosition(mRecpRowCount - 1, 1)) {
        // First widget. Should align with the row above that
342
        // contains the encrypt for others checkbox.
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
        mRecpLayout->addWidget(unknownWidget, mRecpRowCount - 1, 1);
    } else {
        mRecpLayout->addWidget(unknownWidget, mRecpRowCount++, 1);
    }

    connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged,
            this, [this] () {
        // Check if any unknown recipient can now be found.
        for (auto w: mUnknownWidgets) {
            auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData());
            if (key.isNull()) {
                std::vector<std::string> subids;
                subids.push_back(std::string(w->keyID().toLatin1().constData()));
                for (const auto &subkey: KeyCache::instance()->findSubkeysByKeyID(subids)) {
                    key = subkey.parent();
                }
            }
            if (key.isNull()) {
                continue;
            }
            // Key is now available replace by line edit.
            qCDebug(KLEOPATRA_LOG) << "Removing widget for keyid: " << w->keyID();
            mRecpLayout->removeWidget(w);
            mUnknownWidgets.removeAll(w);
            delete w;
            addRecipient(key);
        }
    });
}

373
374
375
void SignEncryptWidget::recipientsChanged()
{
    bool oneEmpty = false;
376
    for (const CertificateLineEdit *w : std::as_const(mRecpWidgets)) {
377
        if (w->key().isNull() && w->group().isNull()) {
378
379
380
381
382
            oneEmpty = true;
            break;
        }
    }
    if (!oneEmpty) {
383
        addRecipientWidget();
384
    }
385
    updateOp();
386
387
388
389
390
}

Key SignEncryptWidget::signKey() const
{
    if (mSigSelect->isEnabled()) {
391
        return mSigSelect->currentKey();
392
393
394
395
396
397
    }
    return Key();
}

Key SignEncryptWidget::selfKey() const
{
398
    if (mSelfSelect->isEnabled()) {
399
        return mSelfSelect->currentKey();
400
    }
401
402
403
    return Key();
}

404
std::vector<Key> SignEncryptWidget::recipients() const
405
{
406
    std::vector<Key> ret;
407
    for (const CertificateLineEdit *w : std::as_const(mRecpWidgets)) {
408
409
410
411
412
        if (!w->isEnabled()) {
            // If one is disabled, all are disabled.
            break;
        }
        const Key k = w->key();
413
        const KeyGroup g = w->group();
414
        if (!k.isNull()) {
415
            ret.push_back(k);
416
417
418
        } else if (!g.isNull()) {
            const auto keys = g.keys();
            std::copy(keys.begin(), keys.end(), std::back_inserter(ret));
419
420
421
422
        }
    }
    const Key k = selfKey();
    if (!k.isNull()) {
423
        ret.push_back(k);
424
425
    }
    return ret;
426
427
}

428
429
430
431
432
433
434
435
bool SignEncryptWidget::isDeVsAndValid() const
{
    if (!signKey().isNull()
        && (!IS_DE_VS(signKey()) || keyValidity(signKey()) < GpgME::UserID::Validity::Full)) {
        return false;
    }

    if (!selfKey().isNull()
Andre Heinecke's avatar
Andre Heinecke committed
436
        && (!IS_DE_VS(selfKey()) || keyValidity(selfKey()) < GpgME::UserID::Validity::Full)) {
437
438
439
440
        return false;
    }

    for (const auto &key: recipients()) {
441
        if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
442
443
444
445
446
447
448
            return false;
        }
    }

    return true;
}

449
450
void SignEncryptWidget::updateOp()
{
451
    const Key sigKey = signKey();
452
    const std::vector<Key> recp = recipients();
453

454
    QString newOp;
455
    if (!sigKey.isNull() && (!recp.empty() || encryptSymmetric())) {
456
        newOp = i18nc("@action", "Sign / Encrypt");
457
    } else if (!recp.empty() || encryptSymmetric()) {
458
459
460
        newOp = i18nc("@action", "Encrypt");
    } else if (!sigKey.isNull()) {
        newOp = i18nc("@action", "Sign");
461
    } else {
462
463
        newOp = QString();
    }
464
465
    mOp = newOp;
    Q_EMIT operationChanged(mOp);
466
    Q_EMIT keysChanged();
467
468
469
470
471
472
}

QString SignEncryptWidget::currentOp() const
{
    return mOp;
}
473
474
475
476
477
478
479

void SignEncryptWidget::recpRemovalRequested(CertificateLineEdit *w)
{
    if (!w) {
        return;
    }
    int emptyEdits = 0;
480
    for (const CertificateLineEdit *edit : std::as_const(mRecpWidgets)) {
481
482
483
484
        if (edit->isEmpty()) {
            emptyEdits++;
        }
        if (emptyEdits > 1) {
485
486
            int row, col, rspan, cspan;
            mRecpLayout->getItemPosition(mRecpLayout->indexOf(w), &row, &col, &rspan, &cspan);
487
488
            mRecpLayout->removeWidget(w);
            mRecpWidgets.removeAll(w);
489
490
491
492
493
494
495
496
497
498
499
500
501
            // The row count of the grid layout does not reflect the actual
            // items so we keep our internal count.
            mRecpRowCount--;
            for (int i = row + 1; i <= mRecpRowCount; i++) {
                // move widgets one up
                auto item = mRecpLayout->itemAtPosition(i, 1);
                if (!item) {
                    break;
                }
                mRecpLayout->removeItem(item);
                mRecpLayout->addItem(item, i - 1, 1);
            }
            w->deleteLater();
502
503
504
505
            return;
        }
    }
}
506

507
508
void SignEncryptWidget::removeRecipient(const GpgME::Key &key)
{
509
    for (CertificateLineEdit *edit: std::as_const(mRecpWidgets)) {
510
511
512
513
514
515
516
517
518
519
520
521
522
523
        const auto editKey = edit->key();
        if (key.isNull() && editKey.isNull()) {
            recpRemovalRequested(edit);
            return;
        }
        if (editKey.primaryFingerprint() &&
            key.primaryFingerprint() &&
            !strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) {
            recpRemovalRequested(edit);
            return;
        }
    }
}

524
525
void SignEncryptWidget::removeRecipient(const KeyGroup &group)
{
526
    for (CertificateLineEdit *edit: std::as_const(mRecpWidgets)) {
527
528
529
530
531
532
533
534
535
536
537
538
        const auto editGroup = edit->group();
        if (group.isNull() && editGroup.isNull()) {
            recpRemovalRequested(edit);
            return;
        }
        if (editGroup.name() == group.name()) {
            recpRemovalRequested(edit);
            return;
        }
    }
}

539
bool SignEncryptWidget::encryptSymmetric() const
540
{
541
    return mSymmetric->isChecked();
542
}
543
544
545
546
547

void SignEncryptWidget::loadKeys()
{
    KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys");
    auto cache = KeyCache::instance();
548
549
    mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString()));
    mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString()));
550
551
552
553
554
555
556
557
558
559
560
561
562
563
}

void SignEncryptWidget::saveOwnKeys() const
{
    KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys");
    auto sigKey = mSigSelect->currentKey();
    auto encKey = mSelfSelect->currentKey();
    if (!sigKey.isNull()) {
        keys.writeEntry("SigningKey", sigKey.primaryFingerprint());
    }
    if (!encKey.isNull()) {
        keys.writeEntry("EncryptKey", encKey.primaryFingerprint());
    }
}
564
565
566
567
568
569

void SignEncryptWidget::setSigningChecked(bool value)
{
    mSigChk->setChecked(value);
}

570
void SignEncryptWidget::setEncryptionChecked(bool checked)
571
{
572
573
574
575
576
577
578
579
580
581
582
583
    if (checked) {
        const bool haveOwnKeys = !KeyCache::instance()->secretKeys().empty();
        const bool haveOtherKeys = !KeyCache::instance()->keys().empty();
        const bool haveKeys = haveOwnKeys && haveOtherKeys;
        mEncSelfChk->setChecked(haveKeys);
        mEncOtherChk->setChecked(haveKeys);
        mSymmetric->setChecked(!haveKeys);
    } else {
        mEncSelfChk->setChecked(false);
        mEncOtherChk->setChecked(false);
        mSymmetric->setChecked(false);
    }
584
}
585
586
587
588
589
590
591
592
593
594

void SignEncryptWidget::setProtocol(GpgME::Protocol proto)
{
    if (mCurrentProto == proto) {
        return;
    }
    mCurrentProto = proto;
    mSigSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new SignCertificateFilter(proto)));
    mSelfSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new EncryptSelfCertificateFilter(proto)));
    const auto encFilter = std::shared_ptr<KeyFilter>(new EncryptCertificateFilter(proto));
595
    for (CertificateLineEdit *edit : std::as_const(mRecpWidgets)) {
596
597
        edit->setKeyFilter(encFilter);
    }
598
599
600
601
602
603
604
605
606
607
608

    if (mIsExclusive) {
        mSymmetric->setDisabled(proto == GpgME::CMS);
        if (mSymmetric->isChecked() && proto == GpgME::CMS) {
            mSymmetric->setChecked(false);
        }
        if (mSigChk->isChecked() && proto == GpgME::CMS &&
                (mEncSelfChk->isChecked() || mEncOtherChk->isChecked())) {
            mSigChk->setChecked(false);
        }
    }
609
}
610
611
612

bool SignEncryptWidget::validate()
{
613
    for (const auto edit: std::as_const(mRecpWidgets)) {
614
        if (!edit->isEmpty() && edit->key().isNull() && edit->group().isNull()) {
615
616
617
618
619
620
621
622
            KMessageBox::error(this, i18nc("%1 is user input that could not be found",
                        "Could not find a key for '%1'", edit->text().toHtmlEscaped()),
                    i18n("Failed to find recipient"), KMessageBox::Notify);
            return false;
        }
    }
    return true;
}