diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp index 9e0ee9b1f..0f3cf02e8 100644 --- a/src/crypto/gui/signencryptfileswizard.cpp +++ b/src/crypto/gui/signencryptfileswizard.cpp @@ -1,697 +1,694 @@ /* crypto/gui/signencryptfileswizard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptfileswizard.h" #include "signencryptwidget.h" #include "newresultpage.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto::Gui; enum Page { SigEncPageId, ResultPageId, NumPages }; class FileNameRequesterWithIcon : public QWidget { Q_OBJECT public: explicit FileNameRequesterWithIcon(QDir::Filters filter, QWidget *parent = nullptr) : QWidget(parent) { auto layout = new QHBoxLayout{this}; layout->setContentsMargins(0, 0, 0, 0); mIconLabel = new QLabel{this}; mRequester = new FileNameRequester{filter, this}; mRequester->setExistingOnly(false); layout->addWidget(mIconLabel); layout->addWidget(mRequester); setFocusPolicy(mRequester->focusPolicy()); setFocusProxy(mRequester); connect(mRequester, &FileNameRequester::fileNameChanged, this, &FileNameRequesterWithIcon::fileNameChanged); } void setIcon(const QIcon &icon) { mIconLabel->setPixmap(icon.pixmap(32, 32)); } void setFileName(const QString &name) { mRequester->setFileName(name); } QString fileName() const { return mRequester->fileName(); } void setNameFilter(const QString &nameFilter) { mRequester->setNameFilter(nameFilter); } QString nameFilter() const { return mRequester->nameFilter(); } FileNameRequester *requester() { return mRequester; } Q_SIGNALS: void fileNameChanged(const QString &filename); protected: bool event(QEvent *e) override { if (e->type() == QEvent::ToolTipChange) { mRequester->setToolTip(toolTip()); } return QWidget::event(e); } private: QLabel *mIconLabel; FileNameRequester *mRequester; }; class SigEncPage : public QWizardPage { Q_OBJECT public: explicit SigEncPage(QWidget *parent = nullptr) : QWizardPage(parent) , mParent((SignEncryptFilesWizard *)parent) , mWidget(new SignEncryptWidget) , mOutLayout(new QVBoxLayout) , mOutputLabel{nullptr} , mArchive(false) , mUseOutputDir(false) , mSingleFile{true} { setTitle(i18nc("@title", "Sign / Encrypt Files")); auto vLay = new QVBoxLayout(this); vLay->setContentsMargins(0, 0, 0, 0); if (!Settings{}.cmsEnabled()) { mWidget->setProtocol(GpgME::OpenPGP); } - mWidget->setSignAsText(i18nc("@option:check on SignEncryptPage", "&Sign as:")); - mWidget->setEncryptForMeText(i18nc("@option:check on SignEncryptPage", "Encrypt for &me:")); - mWidget->setEncryptForOthersText(i18nc("@option:check on SignEncryptPage", "Encrypt for &others:")); mWidget->setEncryptWithPasswordText( i18nc("@option:check on SignEncryptPage", "Encrypt with &password. Anyone you share the password with can read the data.")); vLay->addWidget(mWidget); connect(mWidget, &SignEncryptWidget::operationChanged, this, &SigEncPage::updateCommitButton); connect(mWidget, &SignEncryptWidget::keysChanged, this, &SigEncPage::updateFileWidgets); auto outputGrp = new QGroupBox(i18nc("@title:group", "Output")); outputGrp->setLayout(mOutLayout); mPlaceholderWidget = new QLabel(i18n("Please select an action.")); mOutLayout->addWidget(mPlaceholderWidget); mOutputLabel = new QLabel(i18nc("@label on SignEncryptPage", "Output &files/folder:")); mOutLayout->addWidget(mOutputLabel); createRequesters(mOutLayout); mUseOutputDirChk = new QCheckBox(i18nc("@option:check on SignEncryptPage", "Encrypt / Sign &each file separately.")); mUseOutputDirChk->setToolTip(i18nc("@info:tooltip", "Keep each file separate instead of creating an archive for all.")); mOutLayout->addWidget(mUseOutputDirChk); connect(mUseOutputDirChk, &QCheckBox::toggled, this, [this](bool state) { mUseOutputDir = state; mArchive = !mUseOutputDir && !mSingleFile; updateFileWidgets(); }); vLay->addWidget(outputGrp); auto messageWidget = new KMessageWidget; messageWidget->setMessageType(KMessageWidget::Error); messageWidget->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this)); messageWidget->setText(i18n("Invalid compliance settings for signing and encrypting files.")); messageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "You cannot use Kleopatra for signing or encrypting files " "because the GnuPG system used by Kleopatra is not %1.", DeVSCompliance::name(true))); messageWidget->setCloseButtonVisible(false); messageWidget->setVisible(DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()); vLay->addWidget(messageWidget); setMinimumHeight(300); } void setEncryptionPreset(bool value) { mWidget->setEncryptionChecked(value); } void setSigningPreset(bool value) { mWidget->setSigningChecked(value); } bool isComplete() const override { if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) { return false; } return mWidget->isComplete(); } int nextId() const override { return ResultPageId; } void initializePage() override { setCommitPage(true); updateCommitButton(mWidget->currentOp()); } void setArchiveForced(bool archive) { mArchive = archive; setArchiveMutable(!archive); } void setArchiveMutable(bool archive) { mUseOutputDirChk->setVisible(archive); if (archive) { const KConfigGroup archCfg(KSharedConfig::openConfig(), QStringLiteral("SignEncryptFilesWizard")); mUseOutputDirChk->setChecked(archCfg.readEntry("LastUseOutputDir", false)); } else { mUseOutputDirChk->setChecked(false); } } void setSingleFile(bool singleFile) { mSingleFile = singleFile; mArchive = !mUseOutputDir && !mSingleFile; } bool validatePage() override { if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) { KMessageBox::error(topLevelWidget(), xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "Sorry! You cannot use Kleopatra for signing or encrypting files " "because the GnuPG system used by Kleopatra is not %1.", DeVSCompliance::name(true))); return false; } bool sign = !mWidget->signKey().isNull(); bool encrypt = !mWidget->selfKey().isNull() || !mWidget->recipients().empty(); if (!mWidget->validate()) { return false; } mWidget->saveOwnKeys(); if (mUseOutputDirChk->isVisible()) { KConfigGroup archCfg(KSharedConfig::openConfig(), QStringLiteral("SignEncryptFilesWizard")); archCfg.writeEntry("LastUseOutputDir", mUseOutputDir); } if (sign && !encrypt && mArchive) { return KMessageBox::warningContinueCancel( this, xi18nc("@info", "Archiving in combination with sign-only currently requires what are known as opaque signatures - " "unlike detached ones, these embed the content in the signature." "This format is rather unusual. You might want to archive the files separately, " "and then sign the archive as one file with Kleopatra." "Future versions of Kleopatra are expected to also support detached signatures in this case."), i18nc("@title:window", "Unusual Signature Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("signencryptfileswizard-archive+sign-only-warning")) == KMessageBox::Continue; } else if (sign && !encrypt) { return true; } if (!mWidget->selfKey().isNull() || mWidget->encryptSymmetric()) { return true; } const auto recipientKeys = recipients(); const bool hasSecret = std::any_of(std::begin(recipientKeys), std::end(recipientKeys), [](const auto &k) { return k.hasSecret(); }); if (!hasSecret) { if (KMessageBox::warningContinueCancel(this, xi18nc("@info", "None of the recipients you are encrypting to seems to be your own." "This means that you will not be able to decrypt the data anymore, once encrypted." "Do you want to continue, or cancel to change the recipient selection?"), i18nc("@title:window", "Encrypt-To-Self Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-encrypt-to-non-self"), KMessageBox::Notify | KMessageBox::Dangerous) == KMessageBox::Cancel) { return false; } } return true; } std::vector recipients() const { return mWidget->recipients(); } /* In the future we might find a usecase for multiple * signers */ std::vector signers() const { const Key k = mWidget->signKey(); if (!k.isNull()) { return {k}; } return {}; } private: struct RequesterInfo { SignEncryptFilesWizard::KindNames id; QString icon; QString toolTip; QString accessibleName; QString nameFilterBinary; QString nameFilterAscii; }; void createRequesters(QBoxLayout *lay) { static const std::array requestersInfo = {{ { SignEncryptFilesWizard::SignatureCMS, QStringLiteral("document-sign"), i18nc("@info:tooltip", "This is the filename of the S/MIME signature."), i18nc("Lineedit accessible name", "S/MIME signature file"), i18nc("Name filter binary", "S/MIME Signatures (*.p7s)"), i18nc("Name filter ASCII", "S/MIME Signatures (*.p7s *.pem)"), }, { SignEncryptFilesWizard::SignaturePGP, QStringLiteral("document-sign"), i18nc("@info:tooltip", "This is the filename of the detached OpenPGP signature."), i18nc("Lineedit accessible name", "OpenPGP signature file"), i18nc("Name filter binary", "OpenPGP Signatures (*.sig *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Signatures (*.asc *.sig)"), }, { SignEncryptFilesWizard::CombinedPGP, QStringLiteral("document-edit-sign-encrypt"), i18nc("@info:tooltip", "This is the filename of the OpenPGP-signed and encrypted file."), i18nc("Lineedit accessible name", "OpenPGP signed and encrypted file"), i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"), }, { SignEncryptFilesWizard::EncryptedPGP, QStringLiteral("document-encrypt"), i18nc("@info:tooltip", "This is the filename of the OpenPGP encrypted file."), i18nc("Lineedit accessible name", "OpenPGP encrypted file"), i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"), }, { SignEncryptFilesWizard::EncryptedCMS, QStringLiteral("document-encrypt"), i18nc("@info:tooltip", "This is the filename of the S/MIME encrypted file."), i18nc("Lineedit accessible name", "S/MIME encrypted file"), i18nc("Name filter binary", "S/MIME Files (*.p7m)"), i18nc("Name filter ASCII", "S/MIME Files (*.p7m *.pem)"), }, { SignEncryptFilesWizard::Directory, QStringLiteral("folder"), i18nc("@info:tooltip", "The resulting files are written to this directory."), i18nc("Lineedit accessible name", "Output directory"), {}, {}, }, }}; if (!mRequesters.empty()) { return; } const bool isAscii = FileOperationsPreferences().addASCIIArmor(); for (const auto &requester : requestersInfo) { const auto id = requester.id; auto requesterWithIcon = new FileNameRequesterWithIcon{id == SignEncryptFilesWizard::Directory ? QDir::Dirs : QDir::Files, this}; requesterWithIcon->setIcon(QIcon::fromTheme(requester.icon)); requesterWithIcon->setToolTip(requester.toolTip); requesterWithIcon->requester()->setAccessibleNameOfLineEdit(requester.accessibleName); requesterWithIcon->setNameFilter(isAscii ? requester.nameFilterAscii : requester.nameFilterBinary); lay->addWidget(requesterWithIcon); connect(requesterWithIcon, &FileNameRequesterWithIcon::fileNameChanged, this, [this, id](const QString &newName) { mOutNames[id] = newName; }); mRequesters.insert(id, requesterWithIcon); } } public: void setOutputNames(const QMap &names) { Q_ASSERT(mOutNames.isEmpty()); for (auto it = std::begin(names); it != std::end(names); ++it) { mRequesters.value(it.key())->setFileName(it.value()); } mOutNames = names; updateFileWidgets(); } QMap outputNames() const { if (!mUseOutputDir) { auto ret = mOutNames; ret.remove(SignEncryptFilesWizard::Directory); return ret; } return mOutNames; } bool encryptSymmetric() const { return mWidget->encryptSymmetric(); } private Q_SLOTS: void updateCommitButton(const SignEncryptWidget::Operations op) { if (mParent->currentPage() != this) { return; } QString label; switch (op) { case SignEncryptWidget::Sign: label = i18nc("@action:button", "Sign"); break; case SignEncryptWidget::Encrypt: label = i18nc("@action:button", "Encrypt"); break; case SignEncryptWidget::SignAndEncrypt: label = i18nc("@action:button", "Sign / Encrypt"); break; default:; }; auto btn = qobject_cast(mParent->button(QWizard::CommitButton)); if (!label.isEmpty()) { mParent->setButtonText(QWizard::CommitButton, label); if (DeVSCompliance::isActive()) { const bool de_vs = DeVSCompliance::isCompliant() && mWidget->isDeVsAndValid(); DeVSCompliance::decorate(btn, de_vs); mParent->setLabelText(DeVSCompliance::name(de_vs)); } } else { mParent->setButtonText(QWizard::CommitButton, i18n("Next")); btn->setIcon(QIcon()); btn->setStyleSheet(QString()); } Q_EMIT completeChanged(); } void updateFileWidgets() { if (mRequesters.isEmpty()) { return; } const std::vector recipients = mWidget->recipients(); const Key sigKey = mWidget->signKey(); const bool pgp = mWidget->encryptSymmetric() || std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::OpenPGP; }); const bool cms = std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::CMS; }); mOutLayout->setEnabled(false); if (cms || pgp || !sigKey.isNull()) { mPlaceholderWidget->setVisible(false); mOutputLabel->setVisible(true); mRequesters[SignEncryptFilesWizard::SignatureCMS]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::CMS); mRequesters[SignEncryptFilesWizard::EncryptedCMS]->setVisible(!mUseOutputDir && cms); mRequesters[SignEncryptFilesWizard::CombinedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::EncryptedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() != Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::SignaturePGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && !pgp); mRequesters[SignEncryptFilesWizard::Directory]->setVisible(mUseOutputDir); auto firstNotHidden = std::find_if(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { return !w->isHidden(); }); mOutputLabel->setBuddy(*firstNotHidden); } else { mPlaceholderWidget->setVisible(true); mOutputLabel->setVisible(false); std::for_each(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { w->setVisible(false); }); mOutputLabel->setBuddy(nullptr); } mOutLayout->setEnabled(true); } private: SignEncryptFilesWizard *mParent; SignEncryptWidget *mWidget; QMap mOutNames; QMap mRequesters; QVBoxLayout *mOutLayout; QWidget *mPlaceholderWidget; QCheckBox *mUseOutputDirChk; QLabel *mOutputLabel; bool mArchive; bool mUseOutputDir; bool mSingleFile; }; class ResultPage : public NewResultPage { Q_OBJECT public: explicit ResultPage(QWidget *parent = nullptr) : NewResultPage(parent) , mParent((SignEncryptFilesWizard *)parent) { setTitle(i18nc("@title", "Results")); setSubTitle(i18nc("@title", "Status and progress of the crypto operations is shown here.")); } void initializePage() override { mParent->setLabelText(QString()); } private: SignEncryptFilesWizard *mParent; }; SignEncryptFilesWizard::SignEncryptFilesWizard(QWidget *parent, Qt::WindowFlags f) : QWizard(parent, f) { readConfig(); const bool de_vs = DeVSCompliance::isActive(); #ifdef Q_OS_WIN // Enforce modern style to avoid vista style ugliness. setWizardStyle(QWizard::ModernStyle); #endif mSigEncPage = new SigEncPage(this); mResultPage = new ResultPage(this); connect(this, &QWizard::currentIdChanged, this, &SignEncryptFilesWizard::slotCurrentIdChanged); setPage(SigEncPageId, mSigEncPage); setPage(ResultPageId, mResultPage); setOptions(QWizard::IndependentPages | // (de_vs ? QWizard::HaveCustomButton1 : QWizard::WizardOption(0)) | // QWizard::NoBackButtonOnLastPage | // QWizard::NoBackButtonOnStartPage); if (de_vs) { /* We use a custom button to display a label next to the buttons. */ auto btn = button(QWizard::CustomButton1); /* We style the button so that it looks and acts like a label. */ btn->setStyleSheet(QStringLiteral("border: none")); btn->setFocusPolicy(Qt::NoFocus); } } void SignEncryptFilesWizard::setLabelText(const QString &label) { button(QWizard::CommitButton)->setToolTip(label); setButtonText(QWizard::CustomButton1, label); } void SignEncryptFilesWizard::slotCurrentIdChanged(int id) { if (id == ResultPageId) { Q_EMIT operationPrepared(); } } SignEncryptFilesWizard::~SignEncryptFilesWizard() { qCDebug(KLEOPATRA_LOG) << this << __func__; writeConfig(); } void SignEncryptFilesWizard::setSigningPreset(bool preset) { mSigEncPage->setSigningPreset(preset); } void SignEncryptFilesWizard::setSigningUserMutable(bool mut) { if (mut == mSigningUserMutable) { return; } mSigningUserMutable = mut; } void SignEncryptFilesWizard::setEncryptionPreset(bool preset) { mSigEncPage->setEncryptionPreset(preset); } void SignEncryptFilesWizard::setEncryptionUserMutable(bool mut) { if (mut == mEncryptionUserMutable) { return; } mEncryptionUserMutable = mut; } void SignEncryptFilesWizard::setArchiveForced(bool archive) { mSigEncPage->setArchiveForced(archive); } void SignEncryptFilesWizard::setArchiveMutable(bool archive) { mSigEncPage->setArchiveMutable(archive); } void SignEncryptFilesWizard::setSingleFile(bool singleFile) { mSigEncPage->setSingleFile(singleFile); } std::vector SignEncryptFilesWizard::resolvedRecipients() const { return mSigEncPage->recipients(); } std::vector SignEncryptFilesWizard::resolvedSigners() const { return mSigEncPage->signers(); } void SignEncryptFilesWizard::setTaskCollection(const std::shared_ptr &coll) { mResultPage->setTaskCollection(coll); } void SignEncryptFilesWizard::setOutputNames(const QMap &map) const { mSigEncPage->setOutputNames(map); } QMap SignEncryptFilesWizard::outputNames() const { return mSigEncPage->outputNames(); } bool SignEncryptFilesWizard::encryptSymmetric() const { return mSigEncPage->encryptSymmetric(); } void SignEncryptFilesWizard::readConfig() { winId(); // ensure there's a window created // set default window size windowHandle()->resize(640, 480); // restore size from config file KConfigGroup cfgGroup(KSharedConfig::openConfig(), QStringLiteral("SignEncryptFilesWizard")); KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); } void SignEncryptFilesWizard::writeConfig() { KConfigGroup cfgGroup(KSharedConfig::openConfig(), QStringLiteral("SignEncryptFilesWizard")); KWindowConfig::saveWindowSize(windowHandle(), cfgGroup); cfgGroup.sync(); } #include "signencryptfileswizard.moc" #include "moc_signencryptfileswizard.cpp" diff --git a/src/crypto/gui/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp index ea786032a..04cb31992 100644 --- a/src/crypto/gui/signencryptwidget.cpp +++ b/src/crypto/gui/signencryptwidget.cpp @@ -1,897 +1,908 @@ /* crypto/gui/signencryptwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "signencryptwidget.h" #include "kleopatra_debug.h" #include "certificatelineedit.h" #include "fileoperationspreferences.h" #include "kleopatraapplication.h" #include "settings.h" #include "unknownrecipientwidget.h" #include "dialogs/certificateselectiondialog.h" #include "utils/gui-helper.h" #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; namespace { class SignCertificateFilter : public DefaultKeyFilter { public: SignCertificateFilter(GpgME::Protocol proto) : DefaultKeyFilter() { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); setCanSign(DefaultKeyFilter::Set); setValidIfSMIME(DefaultKeyFilter::Set); if (proto == GpgME::OpenPGP) { setIsOpenPGP(DefaultKeyFilter::Set); } else if (proto == GpgME::CMS) { setIsOpenPGP(DefaultKeyFilter::NotSet); } } }; class EncryptCertificateFilter : public DefaultKeyFilter { public: EncryptCertificateFilter(GpgME::Protocol proto) : DefaultKeyFilter() { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); setValidIfSMIME(DefaultKeyFilter::Set); if (proto == GpgME::OpenPGP) { setIsOpenPGP(DefaultKeyFilter::Set); } else if (proto == GpgME::CMS) { setIsOpenPGP(DefaultKeyFilter::NotSet); } } }; class EncryptSelfCertificateFilter : public EncryptCertificateFilter { public: EncryptSelfCertificateFilter(GpgME::Protocol proto) : EncryptCertificateFilter(proto) { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); setHasSecret(DefaultKeyFilter::Set); setValidIfSMIME(DefaultKeyFilter::Set); } }; } class SignEncryptWidget::Private { SignEncryptWidget *const q; public: struct RecipientWidgets { CertificateLineEdit *edit; KMessageWidget *expiryMessage; }; explicit Private(SignEncryptWidget *qq, bool sigEncExclusive) : q{qq} , mModel{AbstractKeyListModel::createFlatKeyListModel(qq)} , mIsExclusive{sigEncExclusive} { } CertificateLineEdit *addRecipientWidget(); /* Inserts a new recipient widget after widget @p after or at the end * if @p after is null. */ CertificateLineEdit *insertRecipientWidget(CertificateLineEdit *after); void recpRemovalRequested(const RecipientWidgets &recipient); void onProtocolChanged(); void updateCheckBoxes(); ExpiryChecker *expiryChecker(); void updateExpiryMessages(KMessageWidget *w, const GpgME::Key &key, ExpiryChecker::CheckFlags flags); void updateAllExpiryMessages(); public: UserIDSelectionCombo *mSigSelect = nullptr; KMessageWidget *mSignKeyExpiryMessage = nullptr; UserIDSelectionCombo *mSelfSelect = nullptr; KMessageWidget *mEncryptToSelfKeyExpiryMessage = nullptr; std::vector mRecpWidgets; QList mUnknownWidgets; QList mAddedKeys; QList mAddedGroups; QVBoxLayout *mRecpLayout = nullptr; Operations mOp; AbstractKeyListModel *mModel = nullptr; QCheckBox *mSymmetric = nullptr; - QCheckBox *mSigChk = nullptr; - QCheckBox *mEncOtherChk = nullptr; - QCheckBox *mEncSelfChk = nullptr; + QLabel *mSigChk = nullptr; + QLabel *mEncOtherLabel = nullptr; + QLabel *mEncSelfChk = nullptr; GpgME::Protocol mCurrentProto = GpgME::UnknownProtocol; const bool mIsExclusive; std::unique_ptr mExpiryChecker; }; SignEncryptWidget::SignEncryptWidget(QWidget *parent, bool sigEncExclusive) : QWidget{parent} , d{new Private{this, sigEncExclusive}} { auto lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); d->mModel->useKeyCache(true, KeyList::IncludeGroups); const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty(); const bool havePublicKeys = !KeyCache::instance()->keys().empty(); const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly(); /* The signature selection */ { auto sigGrp = new QGroupBox{i18nc("@title:group", "Prove authenticity (sign)"), this}; - d->mSigChk = new QCheckBox{i18n("Sign as:"), this}; + d->mSigChk = new QLabel{i18n("Sign as:"), this}; d->mSigChk->setEnabled(haveSecretKeys); - d->mSigChk->setChecked(haveSecretKeys); + // d->mSigChk->setChecked(haveSecretKeys); d->mSigSelect = new UserIDSelectionCombo{KeyUsage::Sign, this}; - d->mSigSelect->setEnabled(d->mSigChk->isChecked()); + d->mSigSelect->prependCustomItem(QIcon::fromTheme(QStringLiteral("edit-none")), i18n("Do not sign"), {}); + // d->mSigSelect->setEnabled(d->mSigChk->isChecked()); d->mSignKeyExpiryMessage = new KMessageWidget{this}; d->mSignKeyExpiryMessage->setVisible(false); auto groupLayout = new QGridLayout{sigGrp}; groupLayout->setColumnStretch(1, 1); groupLayout->addWidget(d->mSigChk, 0, 0); groupLayout->addWidget(d->mSigSelect, 0, 1); groupLayout->addWidget(d->mSignKeyExpiryMessage, 1, 1); lay->addWidget(sigGrp); - connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool checked) { - d->mSigSelect->setEnabled(checked); - updateOp(); - d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey); - }); + // connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool checked) { + // d->mSigSelect->setEnabled(checked); + // updateOp(); + // d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey); + // }); connect(d->mSigSelect, &UserIDSelectionCombo::currentKeyChanged, this, [this]() { updateOp(); d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey); }); } // Recipient selection { auto encBox = new QGroupBox{i18nc("@title:group", "Encrypt"), this}; auto encBoxLay = new QVBoxLayout{encBox}; auto recipientGrid = new QGridLayout; int row = 0; // Own key - d->mEncSelfChk = new QCheckBox{i18n("Encrypt for me:"), this}; + d->mEncSelfChk = new QLabel{i18n("Encrypt for me:"), this}; d->mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly); - d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); + // d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); d->mSelfSelect = new UserIDSelectionCombo{KeyUsage::Encrypt, this}; - d->mSelfSelect->setEnabled(d->mEncSelfChk->isChecked()); + d->mSelfSelect->prependCustomItem(QIcon::fromTheme(QStringLiteral("edit-none")), i18n("Do not encrypt for me"), {}); + // d->mSelfSelect->setEnabled(d->mEncSelfChk->isChecked()); d->mEncryptToSelfKeyExpiryMessage = new KMessageWidget{this}; d->mEncryptToSelfKeyExpiryMessage->setVisible(false); recipientGrid->addWidget(d->mEncSelfChk, row, 0); recipientGrid->addWidget(d->mSelfSelect, row, 1); row++; recipientGrid->addWidget(d->mEncryptToSelfKeyExpiryMessage, row, 1); // Checkbox for other keys row++; - d->mEncOtherChk = new QCheckBox{i18n("Encrypt for others:"), this}; - d->mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly); - d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly); - recipientGrid->addWidget(d->mEncOtherChk, row, 0, Qt::AlignTop); - connect(d->mEncOtherChk, &QCheckBox::toggled, this, [this](bool checked) { - for (const auto &recipient : std::as_const(d->mRecpWidgets)) { - recipient.edit->setEnabled(checked); - d->updateExpiryMessages(recipient.expiryMessage, checked ? recipient.edit->key() : Key{}, ExpiryChecker::EncryptionKey); - } - updateOp(); - }); + d->mEncOtherLabel = new QLabel{i18n("Encrypt for others:"), this}; + recipientGrid->addWidget(d->mEncOtherLabel, row, 0, Qt::AlignTop | Qt::AlignRight); d->mRecpLayout = new QVBoxLayout; recipientGrid->addLayout(d->mRecpLayout, row, 1); recipientGrid->setRowStretch(row + 1, 1); // Scroll area for other keys auto recipientWidget = new QWidget; auto recipientScroll = new QScrollArea; recipientWidget->setLayout(recipientGrid); recipientScroll->setWidget(recipientWidget); recipientScroll->setWidgetResizable(true); recipientScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); recipientScroll->setFrameStyle(QFrame::NoFrame); recipientScroll->setFocusPolicy(Qt::NoFocus); recipientGrid->setContentsMargins(0, 0, 0, 0); encBoxLay->addWidget(recipientScroll, 1); auto bar = recipientScroll->verticalScrollBar(); connect(bar, &QScrollBar::rangeChanged, this, [bar](int, int max) { bar->setValue(max); }); - d->addRecipientWidget(); + auto certLineEdit = d->addRecipientWidget(); + if (d->mIsExclusive) { + connect(certLineEdit, &CertificateLineEdit::keyChanged, this, [this]() { + if (d->mCurrentProto != GpgME::CMS) { + return; + } + // d->mSigChk->setChecked(false); + }); + } // Checkbox for password d->mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data.")); d->mSymmetric->setToolTip(i18nc("Tooltip information for symmetric encryption", "Additionally to the keys of the recipients you can encrypt your data with a password. " "Anyone who has the password can read the data without any secret key. " "Using a password is less secure then public key cryptography. Even if you pick a very strong password.")); d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys); encBoxLay->addWidget(d->mSymmetric); // Connect it - connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool checked) { - d->mSelfSelect->setEnabled(checked); - updateOp(); - d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfKey(), ExpiryChecker::OwnEncryptionKey); - }); - connect(d->mSelfSelect, &UserIDSelectionCombo::currentKeyChanged, this, [this]() { + connect(d->mSelfSelect, &UserIDSelectionCombo::currentIndexChanged, this, [this]() { updateOp(); d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfKey(), ExpiryChecker::OwnEncryptionKey); }); connect(d->mSymmetric, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); if (d->mIsExclusive) { - connect(d->mEncOtherChk, &QCheckBox::toggled, this, [this](bool value) { + connect(d->mSelfSelect, &UserIDSelectionCombo::currentIndexChanged, this, [this](const auto index) { if (d->mCurrentProto != GpgME::CMS) { return; } - if (value) { - d->mSigChk->setChecked(false); + if (index != 0) { + d->mSigSelect->setCurrentIndex(0); } }); - connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) { + connect(d->mSigSelect, &UserIDSelectionCombo::currentIndexChanged, this, [this](const auto index) { if (d->mCurrentProto != GpgME::CMS) { return; } - if (value) { - d->mSigChk->setChecked(false); - } - }); - connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool value) { - if (d->mCurrentProto != GpgME::CMS) { - return; - } - if (value) { - d->mEncSelfChk->setChecked(false); - d->mEncOtherChk->setChecked(false); + if (index != 0) { + d->mSelfSelect->setCurrentIndex(0); + for (const auto &widget : d->mRecpWidgets) { + removeRecipient(widget.edit->userID()); + } + d->addRecipientWidget(); } }); } // Ensure that the d->mSigChk is aligned together with the encryption check boxes. - d->mSigChk->setMinimumWidth(qMax(d->mEncOtherChk->width(), d->mEncSelfChk->width())); + d->mSigChk->setMinimumWidth(qMax(d->mEncOtherLabel->width(), d->mEncSelfChk->width())); lay->addWidget(encBox); } connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() { d->updateCheckBoxes(); d->updateAllExpiryMessages(); }); connect(KleopatraApplication::instance(), &KleopatraApplication::configurationChanged, this, [this]() { d->updateCheckBoxes(); d->mExpiryChecker.reset(); d->updateAllExpiryMessages(); }); loadKeys(); d->onProtocolChanged(); updateOp(); } SignEncryptWidget::~SignEncryptWidget() = default; -void SignEncryptWidget::setSignAsText(const QString &text) -{ - d->mSigChk->setText(text); -} - -void SignEncryptWidget::setEncryptForMeText(const QString &text) -{ - d->mEncSelfChk->setText(text); -} - -void SignEncryptWidget::setEncryptForOthersText(const QString &text) -{ - d->mEncOtherChk->setText(text); -} - void SignEncryptWidget::setEncryptWithPasswordText(const QString &text) { d->mSymmetric->setText(text); } CertificateLineEdit *SignEncryptWidget::Private::addRecipientWidget() { return insertRecipientWidget(nullptr); } CertificateLineEdit *SignEncryptWidget::Private::insertRecipientWidget(CertificateLineEdit *after) { Q_ASSERT(!after || mRecpLayout->indexOf(after) != -1); const auto index = after ? mRecpLayout->indexOf(after) + 2 : mRecpLayout->count(); const RecipientWidgets recipient{new CertificateLineEdit{mModel, KeyUsage::Encrypt, new EncryptCertificateFilter{mCurrentProto}, q}, new KMessageWidget{q}}; recipient.edit->setAccessibleNameOfLineEdit(i18nc("text for screen readers", "recipient key")); - recipient.edit->setEnabled(mEncOtherChk->isChecked()); recipient.expiryMessage->setVisible(false); if (static_cast(index / 2) < mRecpWidgets.size()) { mRecpWidgets.insert(mRecpWidgets.begin() + index / 2, recipient); } else { mRecpWidgets.push_back(recipient); } if (mRecpLayout->count() > 0) { auto prevWidget = after ? after : mRecpLayout->itemAt(mRecpLayout->count() - 1)->widget(); Kleo::forceSetTabOrder(prevWidget, recipient.edit); Kleo::forceSetTabOrder(recipient.edit, recipient.expiryMessage); } mRecpLayout->insertWidget(index, recipient.edit); mRecpLayout->insertWidget(index + 1, recipient.expiryMessage); connect(recipient.edit, &CertificateLineEdit::keyChanged, q, &SignEncryptWidget::recipientsChanged); connect(recipient.edit, &CertificateLineEdit::editingStarted, q, &SignEncryptWidget::recipientsChanged); connect(recipient.edit, &CertificateLineEdit::cleared, q, &SignEncryptWidget::recipientsChanged); connect(recipient.edit, &CertificateLineEdit::certificateSelectionRequested, q, [this, recipient]() { q->certificateSelectionRequested(recipient.edit); }); + if (mIsExclusive) { + connect(recipient.edit, &CertificateLineEdit::keyChanged, q, [this]() { + mSigSelect->setCurrentIndex(0); + }); + } + return recipient.edit; } void SignEncryptWidget::addRecipient(const Key &key) { CertificateLineEdit *certSel = d->addRecipientWidget(); if (!key.isNull()) { certSel->setKey(key); d->mAddedKeys << key; } } void SignEncryptWidget::addRecipient(const KeyGroup &group) { CertificateLineEdit *certSel = d->addRecipientWidget(); if (!group.isNull()) { certSel->setGroup(group); d->mAddedGroups << group; } } void SignEncryptWidget::certificateSelectionRequested(CertificateLineEdit *certificateLineEdit) { CertificateSelectionDialog dlg{this}; dlg.setOptions(CertificateSelectionDialog::Options( // CertificateSelectionDialog::MultiSelection | // CertificateSelectionDialog::EncryptOnly | // CertificateSelectionDialog::optionsFromProtocol(d->mCurrentProto) | // CertificateSelectionDialog::IncludeGroups)); if (!certificateLineEdit->key().isNull()) { const auto key = certificateLineEdit->key(); const auto name = QString::fromUtf8(key.userID(0).name()); const auto email = QString::fromUtf8(key.userID(0).email()); dlg.setStringFilter(!name.isEmpty() ? name : email); } else if (!certificateLineEdit->group().isNull()) { dlg.setStringFilter(certificateLineEdit->group().name()); } else if (!certificateLineEdit->userID().isNull()) { const auto userID = certificateLineEdit->userID(); const auto name = QString::fromUtf8(userID.name()); const auto email = QString::fromUtf8(userID.email()); dlg.setStringFilter(!name.isEmpty() ? name : email); } else { dlg.setStringFilter(certificateLineEdit->text()); } if (dlg.exec()) { const std::vector userIds = dlg.selectedUserIDs(); const std::vector groups = dlg.selectedGroups(); if (userIds.size() == 0 && groups.size() == 0) { return; } CertificateLineEdit *certWidget = nullptr; for (const auto &userId : userIds) { if (!certWidget) { certWidget = certificateLineEdit; } else { certWidget = d->insertRecipientWidget(certWidget); } certWidget->setUserID(userId); } for (const KeyGroup &group : groups) { if (!certWidget) { certWidget = certificateLineEdit; } else { certWidget = d->insertRecipientWidget(certWidget); } certWidget->setGroup(group); } } recipientsChanged(); } void SignEncryptWidget::clearAddedRecipients() { for (auto w : std::as_const(d->mUnknownWidgets)) { d->mRecpLayout->removeWidget(w); delete w; } for (auto &key : std::as_const(d->mAddedKeys)) { removeRecipient(key); } for (auto &group : std::as_const(d->mAddedGroups)) { removeRecipient(group); } } void SignEncryptWidget::addUnknownRecipient(const char *keyID) { auto unknownWidget = new UnknownRecipientWidget(keyID); d->mUnknownWidgets << unknownWidget; if (d->mRecpLayout->count() > 0) { auto lastWidget = d->mRecpLayout->itemAt(d->mRecpLayout->count() - 1)->widget(); setTabOrder(lastWidget, unknownWidget); } d->mRecpLayout->addWidget(unknownWidget); connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() { // Check if any unknown recipient can now be found. for (auto w : d->mUnknownWidgets) { auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData()); if (key.isNull()) { std::vector 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(); d->mRecpLayout->removeWidget(w); d->mUnknownWidgets.removeAll(w); delete w; addRecipient(key); } }); } void SignEncryptWidget::recipientsChanged() { const bool hasEmptyRecpWidget = std::any_of(std::cbegin(d->mRecpWidgets), std::cend(d->mRecpWidgets), [](auto w) { return w.edit->isEmpty(); }); if (!hasEmptyRecpWidget) { d->addRecipientWidget(); } updateOp(); for (const auto &recipient : std::as_const(d->mRecpWidgets)) { if (!recipient.edit->isEditingInProgress() || recipient.edit->isEmpty()) { - d->updateExpiryMessages(recipient.expiryMessage, d->mEncOtherChk->isChecked() ? recipient.edit->key() : Key{}, ExpiryChecker::EncryptionKey); + d->updateExpiryMessages(recipient.expiryMessage, recipient.edit->userID().parent(), ExpiryChecker::EncryptionKey); } } } Key SignEncryptWidget::signKey() const { if (d->mSigSelect->isEnabled()) { return d->mSigSelect->currentKey(); } return Key(); } Key SignEncryptWidget::selfKey() const { if (d->mSelfSelect->isEnabled()) { return d->mSelfSelect->currentKey(); } return Key(); } std::vector SignEncryptWidget::recipients() const { std::vector ret; for (const auto &recipient : std::as_const(d->mRecpWidgets)) { const auto *const w = recipient.edit; if (!w->isEnabled()) { // If one is disabled, all are disabled. break; } const Key k = w->key(); const KeyGroup g = w->group(); const UserID u = w->userID(); if (!k.isNull()) { ret.push_back(k); } else if (!g.isNull()) { const auto keys = g.keys(); std::copy(keys.begin(), keys.end(), std::back_inserter(ret)); } else if (!u.isNull()) { ret.push_back(u.parent()); } } const Key k = selfKey(); if (!k.isNull()) { ret.push_back(k); } return ret; } bool SignEncryptWidget::isDeVsAndValid() const { if (!signKey().isNull() && !DeVSCompliance::keyIsCompliant(signKey())) { return false; } if (!selfKey().isNull() && !DeVSCompliance::keyIsCompliant(selfKey())) { return false; } for (const auto &key : recipients()) { if (!DeVSCompliance::keyIsCompliant(key)) { return false; } } return true; } static QString expiryMessage(const ExpiryChecker::Result &result) { if (result.expiration.certificate.isNull()) { return {}; } switch (result.expiration.status) { case ExpiryChecker::Expired: return i18nc("@info", "This certificate is expired."); case ExpiryChecker::ExpiresSoon: { if (result.expiration.duration.count() == 0) { return i18nc("@info", "This certificate expires today."); } else { return i18ncp("@info", "This certificate expires tomorrow.", "This certificate expires in %1 days.", result.expiration.duration.count()); } } case ExpiryChecker::NoSuitableSubkey: if (result.checkFlags & ExpiryChecker::EncryptionKey) { return i18nc("@info", "This certificate cannot be used for encryption."); } else { return i18nc("@info", "This certificate cannot be used for signing."); } case ExpiryChecker::InvalidKey: case ExpiryChecker::InvalidCheckFlags: break; // wrong usage of ExpiryChecker; can be ignored case ExpiryChecker::NotNearExpiry:; } return {}; } void SignEncryptWidget::updateOp() { const Key sigKey = signKey(); const std::vector recp = recipients(); Operations op = NoOperation; if (!sigKey.isNull()) { op |= Sign; } if (!recp.empty() || encryptSymmetric()) { op |= Encrypt; } d->mOp = op; Q_EMIT operationChanged(d->mOp); Q_EMIT keysChanged(); } SignEncryptWidget::Operations SignEncryptWidget::currentOp() const { return d->mOp; } namespace { bool recipientWidgetHasFocus(QWidget *w) { // check if w (or its focus proxy) or a child widget of w has focus return w->hasFocus() || w->isAncestorOf(qApp->focusWidget()); } } void SignEncryptWidget::Private::recpRemovalRequested(const RecipientWidgets &recipient) { if (!recipient.edit) { return; } const int emptyEdits = std::count_if(std::cbegin(mRecpWidgets), std::cend(mRecpWidgets), [](const auto &r) { return r.edit->isEmpty(); }); - if (emptyEdits > 1) { - if (recipientWidgetHasFocus(recipient.edit) || recipientWidgetHasFocus(recipient.expiryMessage)) { - const int index = mRecpLayout->indexOf(recipient.edit); - const auto focusWidget = (index < mRecpLayout->count() - 2) ? // - mRecpLayout->itemAt(index + 2)->widget() - : mRecpLayout->itemAt(mRecpLayout->count() - 3)->widget(); - focusWidget->setFocus(); - } + // TODO what does this line do? if (emptyEdits > 1) { + if (recipientWidgetHasFocus(recipient.edit) || recipientWidgetHasFocus(recipient.expiryMessage)) { + const int index = mRecpLayout->indexOf(recipient.edit); + const auto focusWidget = (index < mRecpLayout->count() - 2) ? // + mRecpLayout->itemAt(index + 2)->widget() + : mRecpLayout->itemAt(mRecpLayout->count() - 3)->widget(); + focusWidget->setFocus(); + } mRecpLayout->removeWidget(recipient.expiryMessage); mRecpLayout->removeWidget(recipient.edit); const auto it = std::find_if(std::begin(mRecpWidgets), std::end(mRecpWidgets), [recipient](const auto &r) { return r.edit == recipient.edit; }); mRecpWidgets.erase(it); recipient.expiryMessage->deleteLater(); recipient.edit->deleteLater(); - } + //} } void SignEncryptWidget::removeRecipient(const GpgME::Key &key) { for (const auto &recipient : std::as_const(d->mRecpWidgets)) { const auto editKey = recipient.edit->key(); if (key.isNull() && editKey.isNull()) { d->recpRemovalRequested(recipient); return; } if (editKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) { d->recpRemovalRequested(recipient); return; } } } +void SignEncryptWidget::removeRecipient(const GpgME::UserID &userId) +{ + for (const auto &recipient : std::as_const(d->mRecpWidgets)) { + const auto editUserId = recipient.edit->userID(); + if (userId.isNull() && editUserId.isNull()) { + d->recpRemovalRequested(recipient); + return; + } + if (editUserId.id() && userId.id() && !strcmp(editUserId.id(), userId.id())) { + d->recpRemovalRequested(recipient); + return; + } + } +} + void SignEncryptWidget::removeRecipient(const KeyGroup &group) { for (const auto &recipient : std::as_const(d->mRecpWidgets)) { const auto editGroup = recipient.edit->group(); if (group.isNull() && editGroup.isNull()) { d->recpRemovalRequested(recipient); return; } if (editGroup.name() == group.name()) { d->recpRemovalRequested(recipient); return; } } } bool SignEncryptWidget::encryptSymmetric() const { return d->mSymmetric->isChecked(); } void SignEncryptWidget::loadKeys() { KConfigGroup keys(KSharedConfig::openConfig(), QStringLiteral("SignEncryptKeys")); auto cache = KeyCache::instance(); d->mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString())); d->mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString())); } void SignEncryptWidget::saveOwnKeys() const { KConfigGroup keys(KSharedConfig::openConfig(), QStringLiteral("SignEncryptKeys")); auto sigKey = d->mSigSelect->currentKey(); auto encKey = d->mSelfSelect->currentKey(); if (!sigKey.isNull()) { keys.writeEntry("SigningKey", sigKey.primaryFingerprint()); } if (!encKey.isNull()) { keys.writeEntry("EncryptKey", encKey.primaryFingerprint()); } } void SignEncryptWidget::setSigningChecked(bool value) { - d->mSigChk->setChecked(value && !KeyCache::instance()->secretKeys().empty()); + // d->mSigChk->setChecked(value && !KeyCache::instance()->secretKeys().empty()); } void SignEncryptWidget::setEncryptionChecked(bool checked) { if (checked) { const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty(); const bool havePublicKeys = !KeyCache::instance()->keys().empty(); const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly(); - d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); - d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly); + // d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys); } else { - d->mEncSelfChk->setChecked(false); - d->mEncOtherChk->setChecked(false); + // d->mEncSelfChk->setChecked(false); d->mSymmetric->setChecked(false); } } void SignEncryptWidget::setProtocol(GpgME::Protocol proto) { if (d->mCurrentProto == proto) { return; } d->mCurrentProto = proto; d->onProtocolChanged(); } void Kleo::SignEncryptWidget::Private::onProtocolChanged() { + auto sigIndex = mSigSelect->currentIndex(); + auto selfEncryptIndex = mSelfSelect->currentIndex(); + auto encryptForOthers = q->recipients().size() > 0; mSigSelect->setKeyFilter(std::shared_ptr(new SignCertificateFilter(mCurrentProto))); mSelfSelect->setKeyFilter(std::shared_ptr(new EncryptSelfCertificateFilter(mCurrentProto))); const auto encFilter = std::shared_ptr(new EncryptCertificateFilter(mCurrentProto)); + for (const auto &widget : mRecpWidgets) { + recpRemovalRequested(widget); + } + addRecipientWidget(); for (const auto &recipient : std::as_const(mRecpWidgets)) { recipient.edit->setKeyFilter(encFilter); } - if (mIsExclusive) { mSymmetric->setDisabled(mCurrentProto == GpgME::CMS); - if (mSymmetric->isChecked() && mCurrentProto == GpgME::CMS) { - mSymmetric->setChecked(false); - } - if (mSigChk->isChecked() && mCurrentProto == GpgME::CMS && (mEncSelfChk->isChecked() || mEncOtherChk->isChecked())) { - mSigChk->setChecked(false); + if (mCurrentProto == GpgME::CMS) { + if (mSymmetric->isChecked() && mCurrentProto) { + mSymmetric->setChecked(false); + } + if (sigIndex != 0 && selfEncryptIndex == 0 && !encryptForOthers) { + mSigSelect->setCurrentIndex(1); + } else { + mSigSelect->setCurrentIndex(0); + } } } + + if (mCurrentProto == GpgME::OpenPGP && sigIndex != 0) { + mSigSelect->setCurrentIndex(1); + } + + if (selfEncryptIndex > 0) { + mSelfSelect->setCurrentIndex(1); + } } static bool recipientIsOkay(const CertificateLineEdit *edit) { if (!edit->isEnabled() || edit->isEmpty()) { return true; } if (!edit->hasAcceptableInput()) { return false; } if (const auto userID = edit->userID(); !userID.isNull()) { return Kleo::canBeUsedForEncryption(userID.parent()) && !userID.isBad(); } if (const auto key = edit->key(); !key.isNull()) { return Kleo::canBeUsedForEncryption(key); } if (const auto group = edit->group(); !group.isNull()) { return Kleo::all_of(group.keys(), Kleo::canBeUsedForEncryption); } // we should never reach this point return false; } bool SignEncryptWidget::isComplete() const { if (currentOp() == NoOperation) { return false; } if ((currentOp() & SignEncryptWidget::Sign) && !Kleo::canBeUsedForSigning(signKey())) { return false; } if (currentOp() & SignEncryptWidget::Encrypt) { if (!selfKey().isNull() && !Kleo::canBeUsedForEncryption(selfKey())) { return false; } const bool allOtherRecipientsAreOkay = Kleo::all_of(d->mRecpWidgets, [](const auto &r) { return recipientIsOkay(r.edit); }); if (!allOtherRecipientsAreOkay) { return false; } } return true; } bool SignEncryptWidget::validate() { CertificateLineEdit *firstUnresolvedRecipient = nullptr; QStringList unresolvedRecipients; for (const auto &recipient : std::as_const(d->mRecpWidgets)) { if (recipient.edit->isEnabled() && !recipient.edit->hasAcceptableInput()) { if (!firstUnresolvedRecipient) { firstUnresolvedRecipient = recipient.edit; } unresolvedRecipients.push_back(recipient.edit->text().toHtmlEscaped()); } } if (!unresolvedRecipients.isEmpty()) { KMessageBox::errorList(this, i18n("Could not find a key for the following recipients:"), unresolvedRecipients, i18nc("@title:window", "Failed to find some keys")); } if (firstUnresolvedRecipient) { firstUnresolvedRecipient->setFocus(); } return unresolvedRecipients.isEmpty(); } void SignEncryptWidget::Private::updateCheckBoxes() { const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty(); const bool havePublicKeys = !KeyCache::instance()->keys().empty(); const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly(); mSigChk->setEnabled(haveSecretKeys); mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly); - mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly); if (symmetricOnly) { - mEncSelfChk->setChecked(false); - mEncOtherChk->setChecked(false); + // mEncSelfChk->setChecked(false); mSymmetric->setChecked(true); } } ExpiryChecker *Kleo::SignEncryptWidget::Private::expiryChecker() { if (!mExpiryChecker) { mExpiryChecker.reset(new ExpiryChecker{ExpiryCheckerConfig{}.settings()}); } return mExpiryChecker.get(); } void SignEncryptWidget::Private::updateExpiryMessages(KMessageWidget *messageWidget, const GpgME::Key &key, ExpiryChecker::CheckFlags flags) { messageWidget->setCloseButtonVisible(false); if (!Settings{}.showExpiryNotifications() || key.isNull()) { messageWidget->setVisible(false); } else { const auto result = expiryChecker()->checkKey(key, flags); const auto message = expiryMessage(result); messageWidget->setText(message); messageWidget->setVisible(!message.isEmpty()); } } void SignEncryptWidget::Private::updateAllExpiryMessages() { updateExpiryMessages(mSignKeyExpiryMessage, q->signKey(), ExpiryChecker::OwnSigningKey); updateExpiryMessages(mEncryptToSelfKeyExpiryMessage, q->selfKey(), ExpiryChecker::OwnEncryptionKey); for (const auto &recipient : std::as_const(mRecpWidgets)) { if (recipient.edit->isEnabled()) { updateExpiryMessages(recipient.expiryMessage, recipient.edit->key(), ExpiryChecker::EncryptionKey); } } } #include "moc_signencryptwidget.cpp" diff --git a/src/crypto/gui/signencryptwidget.h b/src/crypto/gui/signencryptwidget.h index 0971aeec1..aa7d8847c 100644 --- a/src/crypto/gui/signencryptwidget.h +++ b/src/crypto/gui/signencryptwidget.h @@ -1,133 +1,134 @@ /* crypto/gui/signencryptwidget.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include namespace GpgME { class Key; +class UserID; } namespace Kleo { class CertificateLineEdit; class KeyGroup; class SignEncryptWidget : public QWidget { Q_OBJECT public: enum Operation { NoOperation = 0x00, Sign = 0x01, Encrypt = 0x02, SignAndEncrypt = Sign | Encrypt }; Q_DECLARE_FLAGS(Operations, Operation) /** If cmsSigEncExclusive is true CMS operations can be * done only either as sign or as encrypt */ explicit SignEncryptWidget(QWidget *parent = nullptr, bool cmsSigEncExclusive = false); ~SignEncryptWidget() override; /** Overwrite default text with custom text, e.g. with a character marked * as shortcut key. */ - void setSignAsText(const QString &text); - void setEncryptForMeText(const QString &text); - void setEncryptForOthersText(const QString &text); void setEncryptWithPasswordText(const QString &text); /** Returns the list of recipients selected in the dialog * or an empty list if encryption is disabled */ std::vector recipients() const; /** Returns the selected signing key or a null key if signing * is disabled. */ GpgME::Key signKey() const; /** Returns the selected encrypt to self key or a null key if * encrypt to self is disabled. */ GpgME::Key selfKey() const; /** Returns the operation based on the current selection. */ Operations currentOp() const; /** Whether or not symmetric encryption should also be used. */ bool encryptSymmetric() const; /** Save the currently selected signing and encrypt to self keys. */ void saveOwnKeys() const; /** Return whether or not all keys involved in the operation are compliant with CO_DE_VS, and all keys are valid (i.e. all userIDs have Validity >= Full). */ bool isDeVsAndValid() const; /** Set whether or not signing group should be checked */ void setSigningChecked(bool value); /** Set whether or not encryption group should be checked */ void setEncryptionChecked(bool value); /** Filter for a specific protocol. Use UnknownProtocol for both * S/MIME and OpenPGP */ void setProtocol(GpgME::Protocol protocol); /** Add a recipient with the key key */ void addRecipient(const GpgME::Key &key); /** Add a group of recipients */ void addRecipient(const Kleo::KeyGroup &group); /** Add a placehoder for an unknown key */ void addUnknownRecipient(const char *keyId); /** Remove all Recipients added by keyId or by key. */ void clearAddedRecipients(); /** Remove a Recipient key */ void removeRecipient(const GpgME::Key &key); /** Remove a recipient group */ void removeRecipient(const Kleo::KeyGroup &group); + /** Remove a recipient user ID */ + void removeRecipient(const GpgME::UserID &userId); + /** Returns true if all required information has been entered. */ bool isComplete() const; /** Returns true if all recipients have been resolved. Otherwise, shows an error message and returns false. */ bool validate(); protected Q_SLOTS: void updateOp(); void recipientsChanged(); void certificateSelectionRequested(CertificateLineEdit *w); protected: void loadKeys(); Q_SIGNALS: /* Emitted when the certificate selection changed the operation * with that selection. e.g. "Sign" or "Sign/Encrypt". * If no crypto operation is selected this returns a null string. */ void operationChanged(Operations op); /* Emitted when the certificate selection might be changed. */ void keysChanged(); private: class Private; const std::unique_ptr d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(SignEncryptWidget::Operations) } // namespace Kleo