File indexing completed on 2024-06-23 05:13:52

0001 /*  crypto/gui/signencryptwidget.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
0005     SPDX-FileContributor: Intevation GmbH
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "signencryptwidget.h"
0011 
0012 #include "kleopatra_debug.h"
0013 
0014 #include "certificatelineedit.h"
0015 #include "fileoperationspreferences.h"
0016 #include "kleopatraapplication.h"
0017 #include "settings.h"
0018 #include "unknownrecipientwidget.h"
0019 
0020 #include "dialogs/certificateselectiondialog.h"
0021 #include "utils/gui-helper.h"
0022 
0023 #include <QCheckBox>
0024 #include <QGroupBox>
0025 #include <QHBoxLayout>
0026 #include <QScrollArea>
0027 #include <QScrollBar>
0028 #include <QVBoxLayout>
0029 
0030 #include <Libkleo/Algorithm>
0031 #include <Libkleo/Compliance>
0032 #include <Libkleo/DefaultKeyFilter>
0033 #include <Libkleo/ExpiryChecker>
0034 #include <Libkleo/ExpiryCheckerConfig>
0035 #include <Libkleo/ExpiryCheckerSettings>
0036 #include <Libkleo/KeyCache>
0037 #include <Libkleo/KeyHelpers>
0038 #include <Libkleo/KeyListModel>
0039 #include <Libkleo/KeyListSortFilterProxyModel>
0040 #include <Libkleo/KeySelectionCombo>
0041 
0042 #include <Libkleo/GnuPG>
0043 
0044 #include <KConfigGroup>
0045 #include <KLocalizedString>
0046 #include <KMessageBox>
0047 #include <KMessageWidget>
0048 #include <KSharedConfig>
0049 
0050 using namespace Kleo;
0051 using namespace Kleo::Dialogs;
0052 using namespace GpgME;
0053 
0054 namespace
0055 {
0056 class SignCertificateFilter : public DefaultKeyFilter
0057 {
0058 public:
0059     SignCertificateFilter(GpgME::Protocol proto)
0060         : DefaultKeyFilter()
0061     {
0062         setRevoked(DefaultKeyFilter::NotSet);
0063         setExpired(DefaultKeyFilter::NotSet);
0064         setHasSecret(DefaultKeyFilter::Set);
0065         setCanSign(DefaultKeyFilter::Set);
0066         setValidIfSMIME(DefaultKeyFilter::Set);
0067 
0068         if (proto == GpgME::OpenPGP) {
0069             setIsOpenPGP(DefaultKeyFilter::Set);
0070         } else if (proto == GpgME::CMS) {
0071             setIsOpenPGP(DefaultKeyFilter::NotSet);
0072         }
0073     }
0074 };
0075 class EncryptCertificateFilter : public DefaultKeyFilter
0076 {
0077 public:
0078     EncryptCertificateFilter(GpgME::Protocol proto)
0079         : DefaultKeyFilter()
0080     {
0081         setRevoked(DefaultKeyFilter::NotSet);
0082         setExpired(DefaultKeyFilter::NotSet);
0083         setCanEncrypt(DefaultKeyFilter::Set);
0084         setValidIfSMIME(DefaultKeyFilter::Set);
0085 
0086         if (proto == GpgME::OpenPGP) {
0087             setIsOpenPGP(DefaultKeyFilter::Set);
0088         } else if (proto == GpgME::CMS) {
0089             setIsOpenPGP(DefaultKeyFilter::NotSet);
0090         }
0091     }
0092 };
0093 class EncryptSelfCertificateFilter : public EncryptCertificateFilter
0094 {
0095 public:
0096     EncryptSelfCertificateFilter(GpgME::Protocol proto)
0097         : EncryptCertificateFilter(proto)
0098     {
0099         setRevoked(DefaultKeyFilter::NotSet);
0100         setExpired(DefaultKeyFilter::NotSet);
0101         setCanEncrypt(DefaultKeyFilter::Set);
0102         setHasSecret(DefaultKeyFilter::Set);
0103         setValidIfSMIME(DefaultKeyFilter::Set);
0104     }
0105 };
0106 }
0107 
0108 class SignEncryptWidget::Private
0109 {
0110     SignEncryptWidget *const q;
0111 
0112 public:
0113     struct RecipientWidgets {
0114         CertificateLineEdit *edit;
0115         KMessageWidget *expiryMessage;
0116     };
0117 
0118     explicit Private(SignEncryptWidget *qq, bool sigEncExclusive)
0119         : q{qq}
0120         , mModel{AbstractKeyListModel::createFlatKeyListModel(qq)}
0121         , mIsExclusive{sigEncExclusive}
0122     {
0123     }
0124 
0125     CertificateLineEdit *addRecipientWidget();
0126     /* Inserts a new recipient widget after widget @p after or at the end
0127      * if @p after is null. */
0128     CertificateLineEdit *insertRecipientWidget(CertificateLineEdit *after);
0129     void recpRemovalRequested(const RecipientWidgets &recipient);
0130     void onProtocolChanged();
0131     void updateCheckBoxes();
0132     ExpiryChecker *expiryChecker();
0133     void updateExpiryMessages(KMessageWidget *w, const GpgME::Key &key, ExpiryChecker::CheckFlags flags);
0134     void updateAllExpiryMessages();
0135 
0136 public:
0137     KeySelectionCombo *mSigSelect = nullptr;
0138     KMessageWidget *mSignKeyExpiryMessage = nullptr;
0139     KeySelectionCombo *mSelfSelect = nullptr;
0140     KMessageWidget *mEncryptToSelfKeyExpiryMessage = nullptr;
0141     std::vector<RecipientWidgets> mRecpWidgets;
0142     QList<UnknownRecipientWidget *> mUnknownWidgets;
0143     QList<GpgME::Key> mAddedKeys;
0144     QList<KeyGroup> mAddedGroups;
0145     QVBoxLayout *mRecpLayout = nullptr;
0146     Operations mOp;
0147     AbstractKeyListModel *mModel = nullptr;
0148     QCheckBox *mSymmetric = nullptr;
0149     QCheckBox *mSigChk = nullptr;
0150     QCheckBox *mEncOtherChk = nullptr;
0151     QCheckBox *mEncSelfChk = nullptr;
0152     GpgME::Protocol mCurrentProto = GpgME::UnknownProtocol;
0153     const bool mIsExclusive;
0154     std::unique_ptr<ExpiryChecker> mExpiryChecker;
0155 };
0156 
0157 SignEncryptWidget::SignEncryptWidget(QWidget *parent, bool sigEncExclusive)
0158     : QWidget{parent}
0159     , d{new Private{this, sigEncExclusive}}
0160 {
0161     auto lay = new QVBoxLayout(this);
0162     lay->setContentsMargins(0, 0, 0, 0);
0163 
0164     d->mModel->useKeyCache(true, KeyList::IncludeGroups);
0165 
0166     const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
0167     const bool havePublicKeys = !KeyCache::instance()->keys().empty();
0168     const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
0169 
0170     /* The signature selection */
0171     {
0172         auto sigGrp = new QGroupBox{i18nc("@title:group", "Prove authenticity (sign)"), this};
0173         d->mSigChk = new QCheckBox{i18n("Sign as:"), this};
0174         d->mSigChk->setEnabled(haveSecretKeys);
0175         d->mSigChk->setChecked(haveSecretKeys);
0176 
0177         d->mSigSelect = new KeySelectionCombo{KeyUsage::Sign, this};
0178         d->mSigSelect->setEnabled(d->mSigChk->isChecked());
0179 
0180         d->mSignKeyExpiryMessage = new KMessageWidget{this};
0181         d->mSignKeyExpiryMessage->setVisible(false);
0182 
0183         auto groupLayout = new QGridLayout{sigGrp};
0184         groupLayout->setColumnStretch(1, 1);
0185         groupLayout->addWidget(d->mSigChk, 0, 0);
0186         groupLayout->addWidget(d->mSigSelect, 0, 1);
0187         groupLayout->addWidget(d->mSignKeyExpiryMessage, 1, 1);
0188         lay->addWidget(sigGrp);
0189 
0190         connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool checked) {
0191             d->mSigSelect->setEnabled(checked);
0192             updateOp();
0193             d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey);
0194         });
0195         connect(d->mSigSelect, &KeySelectionCombo::currentKeyChanged, this, [this]() {
0196             updateOp();
0197             d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey);
0198         });
0199     }
0200 
0201     // Recipient selection
0202     {
0203         auto encBox = new QGroupBox{i18nc("@title:group", "Encrypt"), this};
0204         auto encBoxLay = new QVBoxLayout{encBox};
0205         auto recipientGrid = new QGridLayout;
0206         int row = 0;
0207 
0208         // Own key
0209         d->mEncSelfChk = new QCheckBox{i18n("Encrypt for me:"), this};
0210         d->mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly);
0211         d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly);
0212         d->mSelfSelect = new KeySelectionCombo{KeyUsage::Encrypt, this};
0213         d->mSelfSelect->setEnabled(d->mEncSelfChk->isChecked());
0214         d->mEncryptToSelfKeyExpiryMessage = new KMessageWidget{this};
0215         d->mEncryptToSelfKeyExpiryMessage->setVisible(false);
0216         recipientGrid->addWidget(d->mEncSelfChk, row, 0);
0217         recipientGrid->addWidget(d->mSelfSelect, row, 1);
0218         row++;
0219         recipientGrid->addWidget(d->mEncryptToSelfKeyExpiryMessage, row, 1);
0220 
0221         // Checkbox for other keys
0222         row++;
0223         d->mEncOtherChk = new QCheckBox{i18n("Encrypt for others:"), this};
0224         d->mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly);
0225         d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly);
0226         recipientGrid->addWidget(d->mEncOtherChk, row, 0, Qt::AlignTop);
0227         connect(d->mEncOtherChk, &QCheckBox::toggled, this, [this](bool checked) {
0228             for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
0229                 recipient.edit->setEnabled(checked);
0230                 d->updateExpiryMessages(recipient.expiryMessage, checked ? recipient.edit->key() : Key{}, ExpiryChecker::EncryptionKey);
0231             }
0232             updateOp();
0233         });
0234         d->mRecpLayout = new QVBoxLayout;
0235         recipientGrid->addLayout(d->mRecpLayout, row, 1);
0236         recipientGrid->setRowStretch(row + 1, 1);
0237 
0238         // Scroll area for other keys
0239         auto recipientWidget = new QWidget;
0240         auto recipientScroll = new QScrollArea;
0241         recipientWidget->setLayout(recipientGrid);
0242         recipientScroll->setWidget(recipientWidget);
0243         recipientScroll->setWidgetResizable(true);
0244         recipientScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
0245         recipientScroll->setFrameStyle(QFrame::NoFrame);
0246         recipientScroll->setFocusPolicy(Qt::NoFocus);
0247         recipientGrid->setContentsMargins(0, 0, 0, 0);
0248         encBoxLay->addWidget(recipientScroll, 1);
0249 
0250         auto bar = recipientScroll->verticalScrollBar();
0251         connect(bar, &QScrollBar::rangeChanged, this, [bar](int, int max) {
0252             bar->setValue(max);
0253         });
0254 
0255         d->addRecipientWidget();
0256 
0257         // Checkbox for password
0258         d->mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data."));
0259         d->mSymmetric->setToolTip(i18nc("Tooltip information for symmetric encryption",
0260                                         "Additionally to the keys of the recipients you can encrypt your data with a password. "
0261                                         "Anyone who has the password can read the data without any secret key. "
0262                                         "Using a password is <b>less secure</b> then public key cryptography. Even if you pick a very strong password."));
0263         d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys);
0264         encBoxLay->addWidget(d->mSymmetric);
0265 
0266         // Connect it
0267         connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool checked) {
0268             d->mSelfSelect->setEnabled(checked);
0269             updateOp();
0270             d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfKey(), ExpiryChecker::OwnEncryptionKey);
0271         });
0272         connect(d->mSelfSelect, &KeySelectionCombo::currentKeyChanged, this, [this]() {
0273             updateOp();
0274             d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfKey(), ExpiryChecker::OwnEncryptionKey);
0275         });
0276         connect(d->mSymmetric, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp);
0277 
0278         if (d->mIsExclusive) {
0279             connect(d->mEncOtherChk, &QCheckBox::toggled, this, [this](bool value) {
0280                 if (d->mCurrentProto != GpgME::CMS) {
0281                     return;
0282                 }
0283                 if (value) {
0284                     d->mSigChk->setChecked(false);
0285                 }
0286             });
0287             connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) {
0288                 if (d->mCurrentProto != GpgME::CMS) {
0289                     return;
0290                 }
0291                 if (value) {
0292                     d->mSigChk->setChecked(false);
0293                 }
0294             });
0295             connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool value) {
0296                 if (d->mCurrentProto != GpgME::CMS) {
0297                     return;
0298                 }
0299                 if (value) {
0300                     d->mEncSelfChk->setChecked(false);
0301                     d->mEncOtherChk->setChecked(false);
0302                 }
0303             });
0304         }
0305 
0306         // Ensure that the d->mSigChk is aligned together with the encryption check boxes.
0307         d->mSigChk->setMinimumWidth(qMax(d->mEncOtherChk->width(), d->mEncSelfChk->width()));
0308 
0309         lay->addWidget(encBox);
0310     }
0311 
0312     connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() {
0313         d->updateCheckBoxes();
0314         d->updateAllExpiryMessages();
0315     });
0316     connect(KleopatraApplication::instance(), &KleopatraApplication::configurationChanged, this, [this]() {
0317         d->updateCheckBoxes();
0318         d->mExpiryChecker.reset();
0319         d->updateAllExpiryMessages();
0320     });
0321 
0322     loadKeys();
0323     d->onProtocolChanged();
0324     updateOp();
0325 }
0326 
0327 SignEncryptWidget::~SignEncryptWidget() = default;
0328 
0329 void SignEncryptWidget::setSignAsText(const QString &text)
0330 {
0331     d->mSigChk->setText(text);
0332 }
0333 
0334 void SignEncryptWidget::setEncryptForMeText(const QString &text)
0335 {
0336     d->mEncSelfChk->setText(text);
0337 }
0338 
0339 void SignEncryptWidget::setEncryptForOthersText(const QString &text)
0340 {
0341     d->mEncOtherChk->setText(text);
0342 }
0343 
0344 void SignEncryptWidget::setEncryptWithPasswordText(const QString &text)
0345 {
0346     d->mSymmetric->setText(text);
0347 }
0348 
0349 CertificateLineEdit *SignEncryptWidget::Private::addRecipientWidget()
0350 {
0351     return insertRecipientWidget(nullptr);
0352 }
0353 
0354 CertificateLineEdit *SignEncryptWidget::Private::insertRecipientWidget(CertificateLineEdit *after)
0355 {
0356     Q_ASSERT(!after || mRecpLayout->indexOf(after) != -1);
0357 
0358     const auto index = after ? mRecpLayout->indexOf(after) + 2 : mRecpLayout->count();
0359 
0360     const RecipientWidgets recipient{new CertificateLineEdit{mModel, KeyUsage::Encrypt, new EncryptCertificateFilter{mCurrentProto}, q}, new KMessageWidget{q}};
0361     recipient.edit->setAccessibleNameOfLineEdit(i18nc("text for screen readers", "recipient key"));
0362     recipient.edit->setEnabled(mEncOtherChk->isChecked());
0363     recipient.expiryMessage->setVisible(false);
0364     if (static_cast<unsigned>(index / 2) < mRecpWidgets.size()) {
0365         mRecpWidgets.insert(mRecpWidgets.begin() + index / 2, recipient);
0366     } else {
0367         mRecpWidgets.push_back(recipient);
0368     }
0369 
0370     if (mRecpLayout->count() > 0) {
0371         auto prevWidget = after ? after : mRecpLayout->itemAt(mRecpLayout->count() - 1)->widget();
0372         Kleo::forceSetTabOrder(prevWidget, recipient.edit);
0373         Kleo::forceSetTabOrder(recipient.edit, recipient.expiryMessage);
0374     }
0375     mRecpLayout->insertWidget(index, recipient.edit);
0376     mRecpLayout->insertWidget(index + 1, recipient.expiryMessage);
0377 
0378     connect(recipient.edit, &CertificateLineEdit::keyChanged, q, &SignEncryptWidget::recipientsChanged);
0379     connect(recipient.edit, &CertificateLineEdit::editingStarted, q, &SignEncryptWidget::recipientsChanged);
0380     connect(recipient.edit, &CertificateLineEdit::cleared, q, &SignEncryptWidget::recipientsChanged);
0381     connect(recipient.edit, &CertificateLineEdit::certificateSelectionRequested, q, [this, recipient]() {
0382         q->certificateSelectionRequested(recipient.edit);
0383     });
0384 
0385     return recipient.edit;
0386 }
0387 
0388 void SignEncryptWidget::addRecipient(const Key &key)
0389 {
0390     CertificateLineEdit *certSel = d->addRecipientWidget();
0391     if (!key.isNull()) {
0392         certSel->setKey(key);
0393         d->mAddedKeys << key;
0394     }
0395 }
0396 
0397 void SignEncryptWidget::addRecipient(const KeyGroup &group)
0398 {
0399     CertificateLineEdit *certSel = d->addRecipientWidget();
0400     if (!group.isNull()) {
0401         certSel->setGroup(group);
0402         d->mAddedGroups << group;
0403     }
0404 }
0405 
0406 void SignEncryptWidget::certificateSelectionRequested(CertificateLineEdit *certificateLineEdit)
0407 {
0408     CertificateSelectionDialog dlg{this};
0409 
0410     dlg.setOptions(CertificateSelectionDialog::Options( //
0411         CertificateSelectionDialog::MultiSelection | //
0412         CertificateSelectionDialog::EncryptOnly | //
0413         CertificateSelectionDialog::optionsFromProtocol(d->mCurrentProto) | //
0414         CertificateSelectionDialog::IncludeGroups));
0415 
0416     if (!certificateLineEdit->key().isNull()) {
0417         const auto key = certificateLineEdit->key();
0418         const auto name = QString::fromUtf8(key.userID(0).name());
0419         const auto email = QString::fromUtf8(key.userID(0).email());
0420         dlg.setStringFilter(!name.isEmpty() ? name : email);
0421     } else if (!certificateLineEdit->group().isNull()) {
0422         dlg.setStringFilter(certificateLineEdit->group().name());
0423     } else {
0424         dlg.setStringFilter(certificateLineEdit->text());
0425     }
0426 
0427     if (dlg.exec()) {
0428         const std::vector<Key> keys = dlg.selectedCertificates();
0429         const std::vector<KeyGroup> groups = dlg.selectedGroups();
0430         if (keys.size() == 0 && groups.size() == 0) {
0431             return;
0432         }
0433         CertificateLineEdit *certWidget = nullptr;
0434         for (const Key &key : keys) {
0435             if (!certWidget) {
0436                 certWidget = certificateLineEdit;
0437             } else {
0438                 certWidget = d->insertRecipientWidget(certWidget);
0439             }
0440             certWidget->setKey(key);
0441         }
0442         for (const KeyGroup &group : groups) {
0443             if (!certWidget) {
0444                 certWidget = certificateLineEdit;
0445             } else {
0446                 certWidget = d->insertRecipientWidget(certWidget);
0447             }
0448             certWidget->setGroup(group);
0449         }
0450     }
0451 
0452     recipientsChanged();
0453 }
0454 
0455 void SignEncryptWidget::clearAddedRecipients()
0456 {
0457     for (auto w : std::as_const(d->mUnknownWidgets)) {
0458         d->mRecpLayout->removeWidget(w);
0459         delete w;
0460     }
0461 
0462     for (auto &key : std::as_const(d->mAddedKeys)) {
0463         removeRecipient(key);
0464     }
0465 
0466     for (auto &group : std::as_const(d->mAddedGroups)) {
0467         removeRecipient(group);
0468     }
0469 }
0470 
0471 void SignEncryptWidget::addUnknownRecipient(const char *keyID)
0472 {
0473     auto unknownWidget = new UnknownRecipientWidget(keyID);
0474     d->mUnknownWidgets << unknownWidget;
0475 
0476     if (d->mRecpLayout->count() > 0) {
0477         auto lastWidget = d->mRecpLayout->itemAt(d->mRecpLayout->count() - 1)->widget();
0478         setTabOrder(lastWidget, unknownWidget);
0479     }
0480     d->mRecpLayout->addWidget(unknownWidget);
0481 
0482     connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() {
0483         // Check if any unknown recipient can now be found.
0484         for (auto w : d->mUnknownWidgets) {
0485             auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData());
0486             if (key.isNull()) {
0487                 std::vector<std::string> subids;
0488                 subids.push_back(std::string(w->keyID().toLatin1().constData()));
0489                 for (const auto &subkey : KeyCache::instance()->findSubkeysByKeyID(subids)) {
0490                     key = subkey.parent();
0491                 }
0492             }
0493             if (key.isNull()) {
0494                 continue;
0495             }
0496             // Key is now available replace by line edit.
0497             qCDebug(KLEOPATRA_LOG) << "Removing widget for keyid: " << w->keyID();
0498             d->mRecpLayout->removeWidget(w);
0499             d->mUnknownWidgets.removeAll(w);
0500             delete w;
0501             addRecipient(key);
0502         }
0503     });
0504 }
0505 
0506 void SignEncryptWidget::recipientsChanged()
0507 {
0508     const bool hasEmptyRecpWidget = std::any_of(std::cbegin(d->mRecpWidgets), std::cend(d->mRecpWidgets), [](auto w) {
0509         return w.edit->isEmpty();
0510     });
0511     if (!hasEmptyRecpWidget) {
0512         d->addRecipientWidget();
0513     }
0514     updateOp();
0515     for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
0516         if (!recipient.edit->isEditingInProgress() || recipient.edit->isEmpty()) {
0517             d->updateExpiryMessages(recipient.expiryMessage, d->mEncOtherChk->isChecked() ? recipient.edit->key() : Key{}, ExpiryChecker::EncryptionKey);
0518         }
0519     }
0520 }
0521 
0522 Key SignEncryptWidget::signKey() const
0523 {
0524     if (d->mSigSelect->isEnabled()) {
0525         return d->mSigSelect->currentKey();
0526     }
0527     return Key();
0528 }
0529 
0530 Key SignEncryptWidget::selfKey() const
0531 {
0532     if (d->mSelfSelect->isEnabled()) {
0533         return d->mSelfSelect->currentKey();
0534     }
0535     return Key();
0536 }
0537 
0538 std::vector<Key> SignEncryptWidget::recipients() const
0539 {
0540     std::vector<Key> ret;
0541     for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
0542         const auto *const w = recipient.edit;
0543         if (!w->isEnabled()) {
0544             // If one is disabled, all are disabled.
0545             break;
0546         }
0547         const Key k = w->key();
0548         const KeyGroup g = w->group();
0549         if (!k.isNull()) {
0550             ret.push_back(k);
0551         } else if (!g.isNull()) {
0552             const auto keys = g.keys();
0553             std::copy(keys.begin(), keys.end(), std::back_inserter(ret));
0554         }
0555     }
0556     const Key k = selfKey();
0557     if (!k.isNull()) {
0558         ret.push_back(k);
0559     }
0560     return ret;
0561 }
0562 
0563 bool SignEncryptWidget::isDeVsAndValid() const
0564 {
0565     if (!signKey().isNull() && !DeVSCompliance::keyIsCompliant(signKey())) {
0566         return false;
0567     }
0568 
0569     if (!selfKey().isNull() && !DeVSCompliance::keyIsCompliant(selfKey())) {
0570         return false;
0571     }
0572 
0573     for (const auto &key : recipients()) {
0574         if (!DeVSCompliance::keyIsCompliant(key)) {
0575             return false;
0576         }
0577     }
0578 
0579     return true;
0580 }
0581 
0582 static QString expiryMessage(const ExpiryChecker::Result &result)
0583 {
0584     if (result.expiration.certificate.isNull()) {
0585         return {};
0586     }
0587     switch (result.expiration.status) {
0588     case ExpiryChecker::Expired:
0589         return i18nc("@info", "This certificate is expired.");
0590     case ExpiryChecker::ExpiresSoon: {
0591         if (result.expiration.duration.count() == 0) {
0592             return i18nc("@info", "This certificate expires today.");
0593         } else {
0594             return i18ncp("@info", "This certificate expires tomorrow.", "This certificate expires in %1 days.", result.expiration.duration.count());
0595         }
0596     }
0597     case ExpiryChecker::NoSuitableSubkey:
0598         if (result.checkFlags & ExpiryChecker::EncryptionKey) {
0599             return i18nc("@info", "This certificate cannot be used for encryption.");
0600         } else {
0601             return i18nc("@info", "This certificate cannot be used for signing.");
0602         }
0603     case ExpiryChecker::InvalidKey:
0604     case ExpiryChecker::InvalidCheckFlags:
0605         break; // wrong usage of ExpiryChecker; can be ignored
0606     case ExpiryChecker::NotNearExpiry:;
0607     }
0608     return {};
0609 }
0610 
0611 void SignEncryptWidget::updateOp()
0612 {
0613     const Key sigKey = signKey();
0614     const std::vector<Key> recp = recipients();
0615 
0616     Operations op = NoOperation;
0617     if (!sigKey.isNull()) {
0618         op |= Sign;
0619     }
0620     if (!recp.empty() || encryptSymmetric()) {
0621         op |= Encrypt;
0622     }
0623     d->mOp = op;
0624     Q_EMIT operationChanged(d->mOp);
0625     Q_EMIT keysChanged();
0626 }
0627 
0628 SignEncryptWidget::Operations SignEncryptWidget::currentOp() const
0629 {
0630     return d->mOp;
0631 }
0632 
0633 namespace
0634 {
0635 bool recipientWidgetHasFocus(QWidget *w)
0636 {
0637     // check if w (or its focus proxy) or a child widget of w has focus
0638     return w->hasFocus() || w->isAncestorOf(qApp->focusWidget());
0639 }
0640 }
0641 
0642 void SignEncryptWidget::Private::recpRemovalRequested(const RecipientWidgets &recipient)
0643 {
0644     if (!recipient.edit) {
0645         return;
0646     }
0647     const int emptyEdits = std::count_if(std::cbegin(mRecpWidgets), std::cend(mRecpWidgets), [](const auto &r) {
0648         return r.edit->isEmpty();
0649     });
0650     if (emptyEdits > 1) {
0651         if (recipientWidgetHasFocus(recipient.edit) || recipientWidgetHasFocus(recipient.expiryMessage)) {
0652             const int index = mRecpLayout->indexOf(recipient.edit);
0653             const auto focusWidget = (index < mRecpLayout->count() - 2) ? //
0654                 mRecpLayout->itemAt(index + 2)->widget()
0655                                                                         : mRecpLayout->itemAt(mRecpLayout->count() - 3)->widget();
0656             focusWidget->setFocus();
0657         }
0658         mRecpLayout->removeWidget(recipient.expiryMessage);
0659         mRecpLayout->removeWidget(recipient.edit);
0660         const auto it = std::find_if(std::begin(mRecpWidgets), std::end(mRecpWidgets), [recipient](const auto &r) {
0661             return r.edit == recipient.edit;
0662         });
0663         mRecpWidgets.erase(it);
0664         recipient.expiryMessage->deleteLater();
0665         recipient.edit->deleteLater();
0666     }
0667 }
0668 
0669 void SignEncryptWidget::removeRecipient(const GpgME::Key &key)
0670 {
0671     for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
0672         const auto editKey = recipient.edit->key();
0673         if (key.isNull() && editKey.isNull()) {
0674             d->recpRemovalRequested(recipient);
0675             return;
0676         }
0677         if (editKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) {
0678             d->recpRemovalRequested(recipient);
0679             return;
0680         }
0681     }
0682 }
0683 
0684 void SignEncryptWidget::removeRecipient(const KeyGroup &group)
0685 {
0686     for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
0687         const auto editGroup = recipient.edit->group();
0688         if (group.isNull() && editGroup.isNull()) {
0689             d->recpRemovalRequested(recipient);
0690             return;
0691         }
0692         if (editGroup.name() == group.name()) {
0693             d->recpRemovalRequested(recipient);
0694             return;
0695         }
0696     }
0697 }
0698 
0699 bool SignEncryptWidget::encryptSymmetric() const
0700 {
0701     return d->mSymmetric->isChecked();
0702 }
0703 
0704 void SignEncryptWidget::loadKeys()
0705 {
0706     KConfigGroup keys(KSharedConfig::openConfig(), QStringLiteral("SignEncryptKeys"));
0707     auto cache = KeyCache::instance();
0708     d->mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString()));
0709     d->mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString()));
0710 }
0711 
0712 void SignEncryptWidget::saveOwnKeys() const
0713 {
0714     KConfigGroup keys(KSharedConfig::openConfig(), QStringLiteral("SignEncryptKeys"));
0715     auto sigKey = d->mSigSelect->currentKey();
0716     auto encKey = d->mSelfSelect->currentKey();
0717     if (!sigKey.isNull()) {
0718         keys.writeEntry("SigningKey", sigKey.primaryFingerprint());
0719     }
0720     if (!encKey.isNull()) {
0721         keys.writeEntry("EncryptKey", encKey.primaryFingerprint());
0722     }
0723 }
0724 
0725 void SignEncryptWidget::setSigningChecked(bool value)
0726 {
0727     d->mSigChk->setChecked(value && !KeyCache::instance()->secretKeys().empty());
0728 }
0729 
0730 void SignEncryptWidget::setEncryptionChecked(bool checked)
0731 {
0732     if (checked) {
0733         const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
0734         const bool havePublicKeys = !KeyCache::instance()->keys().empty();
0735         const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
0736         d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly);
0737         d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly);
0738         d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys);
0739     } else {
0740         d->mEncSelfChk->setChecked(false);
0741         d->mEncOtherChk->setChecked(false);
0742         d->mSymmetric->setChecked(false);
0743     }
0744 }
0745 
0746 void SignEncryptWidget::setProtocol(GpgME::Protocol proto)
0747 {
0748     if (d->mCurrentProto == proto) {
0749         return;
0750     }
0751     d->mCurrentProto = proto;
0752     d->onProtocolChanged();
0753 }
0754 
0755 void Kleo::SignEncryptWidget::Private::onProtocolChanged()
0756 {
0757     mSigSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new SignCertificateFilter(mCurrentProto)));
0758     mSelfSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new EncryptSelfCertificateFilter(mCurrentProto)));
0759     const auto encFilter = std::shared_ptr<KeyFilter>(new EncryptCertificateFilter(mCurrentProto));
0760     for (const auto &recipient : std::as_const(mRecpWidgets)) {
0761         recipient.edit->setKeyFilter(encFilter);
0762     }
0763 
0764     if (mIsExclusive) {
0765         mSymmetric->setDisabled(mCurrentProto == GpgME::CMS);
0766         if (mSymmetric->isChecked() && mCurrentProto == GpgME::CMS) {
0767             mSymmetric->setChecked(false);
0768         }
0769         if (mSigChk->isChecked() && mCurrentProto == GpgME::CMS && (mEncSelfChk->isChecked() || mEncOtherChk->isChecked())) {
0770             mSigChk->setChecked(false);
0771         }
0772     }
0773 }
0774 
0775 static bool recipientIsOkay(const CertificateLineEdit *edit)
0776 {
0777     if (!edit->isEnabled() || edit->isEmpty()) {
0778         return true;
0779     }
0780     if (!edit->hasAcceptableInput()) {
0781         return false;
0782     }
0783     if (const auto key = edit->key(); !key.isNull()) {
0784         return Kleo::canBeUsedForEncryption(key);
0785     }
0786     if (const auto group = edit->group(); !group.isNull()) {
0787         return Kleo::all_of(group.keys(), Kleo::canBeUsedForEncryption);
0788     }
0789     // we should never reach this point
0790     return false;
0791 }
0792 
0793 bool SignEncryptWidget::isComplete() const
0794 {
0795     if (currentOp() == NoOperation) {
0796         return false;
0797     }
0798     if ((currentOp() & SignEncryptWidget::Sign) && !Kleo::canBeUsedForSigning(signKey())) {
0799         return false;
0800     }
0801     if (currentOp() & SignEncryptWidget::Encrypt) {
0802         if (!selfKey().isNull() && !Kleo::canBeUsedForEncryption(selfKey())) {
0803             return false;
0804         }
0805         const bool allOtherRecipientsAreOkay = Kleo::all_of(d->mRecpWidgets, [](const auto &r) {
0806             return recipientIsOkay(r.edit);
0807         });
0808         if (!allOtherRecipientsAreOkay) {
0809             return false;
0810         }
0811     }
0812     return true;
0813 }
0814 
0815 bool SignEncryptWidget::validate()
0816 {
0817     CertificateLineEdit *firstUnresolvedRecipient = nullptr;
0818     QStringList unresolvedRecipients;
0819     for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
0820         if (recipient.edit->isEnabled() && !recipient.edit->hasAcceptableInput()) {
0821             if (!firstUnresolvedRecipient) {
0822                 firstUnresolvedRecipient = recipient.edit;
0823             }
0824             unresolvedRecipients.push_back(recipient.edit->text().toHtmlEscaped());
0825         }
0826     }
0827     if (!unresolvedRecipients.isEmpty()) {
0828         KMessageBox::errorList(this,
0829                                i18n("Could not find a key for the following recipients:"),
0830                                unresolvedRecipients,
0831                                i18nc("@title:window", "Failed to find some keys"));
0832     }
0833     if (firstUnresolvedRecipient) {
0834         firstUnresolvedRecipient->setFocus();
0835     }
0836     return unresolvedRecipients.isEmpty();
0837 }
0838 
0839 void SignEncryptWidget::Private::updateCheckBoxes()
0840 {
0841     const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
0842     const bool havePublicKeys = !KeyCache::instance()->keys().empty();
0843     const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
0844     mSigChk->setEnabled(haveSecretKeys);
0845     mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly);
0846     mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly);
0847     if (symmetricOnly) {
0848         mEncSelfChk->setChecked(false);
0849         mEncOtherChk->setChecked(false);
0850         mSymmetric->setChecked(true);
0851     }
0852 }
0853 
0854 ExpiryChecker *Kleo::SignEncryptWidget::Private::expiryChecker()
0855 {
0856     if (!mExpiryChecker) {
0857         mExpiryChecker.reset(new ExpiryChecker{ExpiryCheckerConfig{}.settings()});
0858     }
0859     return mExpiryChecker.get();
0860 }
0861 
0862 void SignEncryptWidget::Private::updateExpiryMessages(KMessageWidget *messageWidget, const GpgME::Key &key, ExpiryChecker::CheckFlags flags)
0863 {
0864     messageWidget->setCloseButtonVisible(false);
0865     if (!Settings{}.showExpiryNotifications() || key.isNull()) {
0866         messageWidget->setVisible(false);
0867     } else {
0868         const auto result = expiryChecker()->checkKey(key, flags);
0869         const auto message = expiryMessage(result);
0870         messageWidget->setText(message);
0871         messageWidget->setVisible(!message.isEmpty());
0872     }
0873 }
0874 
0875 void SignEncryptWidget::Private::updateAllExpiryMessages()
0876 {
0877     updateExpiryMessages(mSignKeyExpiryMessage, q->signKey(), ExpiryChecker::OwnSigningKey);
0878     updateExpiryMessages(mEncryptToSelfKeyExpiryMessage, q->selfKey(), ExpiryChecker::OwnEncryptionKey);
0879     for (const auto &recipient : std::as_const(mRecpWidgets)) {
0880         if (recipient.edit->isEnabled()) {
0881             updateExpiryMessages(recipient.expiryMessage, recipient.edit->key(), ExpiryChecker::EncryptionKey);
0882         }
0883     }
0884 }
0885 
0886 #include "moc_signencryptwidget.cpp"