diff --git a/src/conf/cryptooperationsconfigwidget.cpp b/src/conf/cryptooperationsconfigwidget.cpp index f5f36d7b3..d8505264b 100644 --- a/src/conf/cryptooperationsconfigwidget.cpp +++ b/src/conf/cryptooperationsconfigwidget.cpp @@ -1,225 +1,244 @@ /* cryptooperationsconfigwidget.cpp This file is part of kleopatra, the KDE key manager SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "cryptooperationsconfigwidget.h" #include "kleopatra_debug.h" #include "fileoperationspreferences.h" #include "settings.h" #include <Libkleo/ChecksumDefinition> #include <Libkleo/KeyFilterManager> #include <libkleo/classifyconfig.h> #include <KConfig> #include <KConfigGroup> #include <KLocalizedString> #include <KMessageBox> #include <KSharedConfig> #include <QCheckBox> #include <QComboBox> #include <QDir> #include <QGroupBox> #include <QHBoxLayout> #include <QLabel> #include <QPushButton> #include <QRegularExpression> #include <QVBoxLayout> #include <memory> using namespace Kleo; using namespace Kleo::Config; CryptoOperationsConfigWidget::CryptoOperationsConfigWidget(QWidget *p, Qt::WindowFlags f) : QWidget{p, f} { setupGui(); } void CryptoOperationsConfigWidget::setupGui() { auto baseLay = new QVBoxLayout(this); mPGPFileExtCB = new QCheckBox(i18n(R"(Create OpenPGP encrypted files with ".pgp" file extensions instead of ".gpg")")); mASCIIArmorCB = new QCheckBox(i18n("Create signed or encrypted files as text files.")); mASCIIArmorCB->setToolTip(i18nc("@info", "Set this option to encode encrypted or signed files as base64 encoded text. " "So that they can be opened with an editor or sent in a mail body. " "This will increase file size by one third.")); mTreatP7mEmailCB = new QCheckBox(i18nc("@option:check", "Treat .p7m files without extensions as mails.")); mAutoDecryptVerifyCB = new QCheckBox(i18n("Automatically start operation based on input detection for decrypt/verify.")); mAutoExtractArchivesCB = new QCheckBox(i18n("Automatically extract file archives after decryption")); mTmpDirCB = new QCheckBox(i18n("Create temporary decrypted files in the folder of the encrypted file.")); mTmpDirCB->setToolTip(i18nc("@info", "Set this option to avoid using the users temporary directory.")); mSymmetricOnlyCB = new QCheckBox(i18n("Use symmetric encryption only.")); mSymmetricOnlyCB->setToolTip(i18nc("@info", "Set this option to disable public key encryption.")); + mPublicKeyOnlyCB = new QCheckBox(i18n("Use public-key encryption only.")); + mPublicKeyOnlyCB->setToolTip(i18nc("@info", "Set this option to disable password-based encryption.")); + + connect(mSymmetricOnlyCB, &QCheckBox::toggled, this, [this]() { + if (mSymmetricOnlyCB->isChecked()) { + mPublicKeyOnlyCB->setChecked(false); + } + }); + + connect(mPublicKeyOnlyCB, &QCheckBox::toggled, this, [this]() { + if (mPublicKeyOnlyCB->isChecked()) { + mSymmetricOnlyCB->setChecked(false); + } + }); baseLay->addWidget(mPGPFileExtCB); baseLay->addWidget(mTreatP7mEmailCB); baseLay->addWidget(mAutoDecryptVerifyCB); baseLay->addWidget(mAutoExtractArchivesCB); baseLay->addWidget(mASCIIArmorCB); baseLay->addWidget(mTmpDirCB); baseLay->addWidget(mSymmetricOnlyCB); + baseLay->addWidget(mPublicKeyOnlyCB); auto comboLay = new QGridLayout; mChecksumDefinitionCB.createWidgets(this); mChecksumDefinitionCB.label()->setText(i18n("Checksum program to use when creating checksum files:")); comboLay->addWidget(mChecksumDefinitionCB.label(), 0, 0); comboLay->addWidget(mChecksumDefinitionCB.widget(), 0, 1); mArchiveDefinitionCB.createWidgets(this); mArchiveDefinitionCB.label()->setText(i18n("Archive command to use when archiving files:")); comboLay->addWidget(mArchiveDefinitionCB.label(), 1, 0); comboLay->addWidget(mArchiveDefinitionCB.widget(), 1, 1); baseLay->addLayout(comboLay); baseLay->addStretch(1); for (auto cb : findChildren<QCheckBox *>()) { connect(cb, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); } for (auto combo : findChildren<QComboBox *>()) { connect(combo, &QComboBox::currentIndexChanged, this, &CryptoOperationsConfigWidget::changed); } } CryptoOperationsConfigWidget::~CryptoOperationsConfigWidget() { } void CryptoOperationsConfigWidget::defaults() { FileOperationsPreferences filePrefs; filePrefs.setUsePGPFileExt(filePrefs.findItem(QStringLiteral("UsePGPFileExt"))->getDefault().toBool()); filePrefs.setAutoDecryptVerify(filePrefs.findItem(QStringLiteral("AutoDecryptVerify"))->getDefault().toBool()); filePrefs.setAutoExtractArchives(filePrefs.findItem(QStringLiteral("AutoExtractArchives"))->getDefault().toBool()); filePrefs.setAddASCIIArmor(filePrefs.findItem(QStringLiteral("AddASCIIArmor"))->getDefault().toBool()); filePrefs.setDontUseTmpDir(filePrefs.findItem(QStringLiteral("DontUseTmpDir"))->getDefault().toBool()); filePrefs.setSymmetricEncryptionOnly(filePrefs.findItem(QStringLiteral("SymmetricEncryptionOnly"))->getDefault().toBool()); + filePrefs.setPublicKeyEncryptionOnly(filePrefs.findItem(QStringLiteral("PublicKeyEncryptionOnly"))->getDefault().toBool()); filePrefs.setArchiveCommand(filePrefs.findItem(QStringLiteral("ArchiveCommand"))->getDefault().toString()); ClassifyConfig classifyConfig; classifyConfig.setP7mWithoutExtensionAreEmail(classifyConfig.defaultP7mWithoutExtensionAreEmailValue()); Settings settings; settings.setChecksumDefinitionId(settings.findItem(QStringLiteral("ChecksumDefinitionId"))->getDefault().toString()); load(filePrefs, settings, classifyConfig); } void CryptoOperationsConfigWidget::load(const Kleo::FileOperationsPreferences &filePrefs, const Kleo::Settings &settings, const Kleo::ClassifyConfig &classifyConfig) { mPGPFileExtCB->setChecked(filePrefs.usePGPFileExt()); mPGPFileExtCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("UsePGPFileExt"))); mAutoDecryptVerifyCB->setChecked(filePrefs.autoDecryptVerify()); mAutoDecryptVerifyCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AutoDecryptVerify"))); mAutoExtractArchivesCB->setChecked(filePrefs.autoExtractArchives()); mAutoExtractArchivesCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AutoExtractArchives"))); mASCIIArmorCB->setChecked(filePrefs.addASCIIArmor()); mASCIIArmorCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AddASCIIArmor"))); mTmpDirCB->setChecked(filePrefs.dontUseTmpDir()); mTmpDirCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("DontUseTmpDir"))); mSymmetricOnlyCB->setChecked(filePrefs.symmetricEncryptionOnly()); mSymmetricOnlyCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("SymmetricEncryptionOnly"))); + mPublicKeyOnlyCB->setChecked(filePrefs.publicKeyEncryptionOnly()); + mPublicKeyOnlyCB->setEnabled(!filePrefs.isPublicKeyEncryptionOnlyImmutable()); mTreatP7mEmailCB->setChecked(classifyConfig.p7mWithoutExtensionAreEmail()); mTreatP7mEmailCB->setEnabled(!classifyConfig.isP7mWithoutExtensionAreEmailImmutable()); const auto defaultChecksumDefinitionId = settings.checksumDefinitionId(); { const auto index = mChecksumDefinitionCB.widget()->findData(defaultChecksumDefinitionId); if (index >= 0) { mChecksumDefinitionCB.widget()->setCurrentIndex(index); } else { qCWarning(KLEOPATRA_LOG) << "No checksum definition found with id" << defaultChecksumDefinitionId; } } mChecksumDefinitionCB.setEnabled(!settings.isImmutable(QStringLiteral("ChecksumDefinitionId"))); const auto ad_default_id = filePrefs.archiveCommand(); { const auto index = mArchiveDefinitionCB.widget()->findData(ad_default_id); if (index >= 0) { mArchiveDefinitionCB.widget()->setCurrentIndex(index); } else { qCWarning(KLEOPATRA_LOG) << "No archive definition found with id" << ad_default_id; } } mArchiveDefinitionCB.setEnabled(!filePrefs.isImmutable(QStringLiteral("ArchiveCommand"))); } void CryptoOperationsConfigWidget::load() { mChecksumDefinitionCB.widget()->clear(); const auto cds = ChecksumDefinition::getChecksumDefinitions(); for (const std::shared_ptr<ChecksumDefinition> &cd : cds) { mChecksumDefinitionCB.widget()->addItem(cd->label(), QVariant{cd->id()}); } // This is a weird hack but because we are a KCM we can't link // against ArchiveDefinition which pulls in loads of other classes. // So we do the parsing which archive definitions exist here ourself. mArchiveDefinitionCB.widget()->clear(); if (KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"))) { const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Archive Definition #"))); for (const QString &group : groups) { const KConfigGroup cGroup(config, group); const QString id = cGroup.readEntryUntranslated(QStringLiteral("id")); const QString name = cGroup.readEntry("Name"); mArchiveDefinitionCB.widget()->addItem(name, QVariant(id)); } } load(FileOperationsPreferences{}, Settings{}, ClassifyConfig{}); } void CryptoOperationsConfigWidget::save() { FileOperationsPreferences filePrefs; filePrefs.setUsePGPFileExt(mPGPFileExtCB->isChecked()); filePrefs.setAutoDecryptVerify(mAutoDecryptVerifyCB->isChecked()); filePrefs.setAutoExtractArchives(mAutoExtractArchivesCB->isChecked()); filePrefs.setAddASCIIArmor(mASCIIArmorCB->isChecked()); filePrefs.setDontUseTmpDir(mTmpDirCB->isChecked()); filePrefs.setSymmetricEncryptionOnly(mSymmetricOnlyCB->isChecked()); + filePrefs.setPublicKeyEncryptionOnly(mPublicKeyOnlyCB->isChecked()); Settings settings; const int idx = mChecksumDefinitionCB.widget()->currentIndex(); if (idx >= 0) { const auto id = mChecksumDefinitionCB.widget()->itemData(idx).toString(); settings.setChecksumDefinitionId(id); } settings.save(); const int aidx = mArchiveDefinitionCB.widget()->currentIndex(); if (aidx >= 0) { const QString id = mArchiveDefinitionCB.widget()->itemData(aidx).toString(); filePrefs.setArchiveCommand(id); } filePrefs.save(); ClassifyConfig classifyConfig; classifyConfig.setP7mWithoutExtensionAreEmail(mTreatP7mEmailCB->isChecked()); classifyConfig.save(); } #include "moc_cryptooperationsconfigwidget.cpp" diff --git a/src/conf/cryptooperationsconfigwidget.h b/src/conf/cryptooperationsconfigwidget.h index a40557432..4ad3d7853 100644 --- a/src/conf/cryptooperationsconfigwidget.h +++ b/src/conf/cryptooperationsconfigwidget.h @@ -1,63 +1,64 @@ /* cryptooperationsconfigwidget.h This file is part of kleopatra, the KDE key manager SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "labelledwidget.h" #include <QWidget> class QCheckBox; class QComboBox; class QBoxLayout; class QPushButton; namespace Kleo { class FileOperationsPreferences; class Settings; class ClassifyConfig; namespace Config { class CryptoOperationsConfigWidget : public QWidget { Q_OBJECT public: explicit CryptoOperationsConfigWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~CryptoOperationsConfigWidget() override; void load(); void save(); void defaults(); Q_SIGNALS: void changed(); private: void setupGui(); void load(const Kleo::FileOperationsPreferences &filePrefs, const Kleo::Settings &settings, const Kleo::ClassifyConfig &classifyConfig); private: QCheckBox *mPGPFileExtCB = nullptr; QCheckBox *mTreatP7mEmailCB = nullptr; QCheckBox *mAutoDecryptVerifyCB = nullptr; QCheckBox *mAutoExtractArchivesCB = nullptr; QCheckBox *mASCIIArmorCB = nullptr; QCheckBox *mTmpDirCB = nullptr; QCheckBox *mSymmetricOnlyCB = nullptr; + QCheckBox *mPublicKeyOnlyCB = nullptr; Kleo::LabelledWidget<QComboBox> mChecksumDefinitionCB; Kleo::LabelledWidget<QComboBox> mArchiveDefinitionCB; QPushButton *mApplyBtn = nullptr; }; } } diff --git a/src/crypto/gui/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp index 892eaec5b..f0b828058 100644 --- a/src/crypto/gui/signencryptwidget.cpp +++ b/src/crypto/gui/signencryptwidget.cpp @@ -1,895 +1,898 @@ /* 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 <QCheckBox> #include <QGroupBox> #include <QHBoxLayout> #include <QScrollArea> #include <QScrollBar> #include <QVBoxLayout> #include <Libkleo/Algorithm> #include <Libkleo/Compliance> #include <Libkleo/DefaultKeyFilter> #include <Libkleo/ExpiryChecker> #include <Libkleo/ExpiryCheckerConfig> #include <Libkleo/ExpiryCheckerSettings> #include <Libkleo/KeyCache> #include <Libkleo/KeyHelpers> #include <Libkleo/KeyListModel> #include <Libkleo/KeyListSortFilterProxyModel> #include <Libkleo/UserIDSelectionCombo> #include <Libkleo/GnuPG> #include <KConfigGroup> #include <KLocalizedString> #include <KMessageBox> #include <KMessageWidget> #include <KSharedConfig> using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; namespace { class SignCertificateFilter : public DefaultKeyFilter { public: SignCertificateFilter(GpgME::Protocol proto) : DefaultKeyFilter() { setIsBad(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() { setIsBad(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<RecipientWidgets> mRecpWidgets; QList<UnknownRecipientWidget *> mUnknownWidgets; QList<GpgME::Key> mAddedKeys; QList<KeyGroup> mAddedGroups; QVBoxLayout *mRecpLayout = nullptr; Operations mOp; AbstractKeyListModel *mModel = nullptr; QCheckBox *mSymmetric = nullptr; QCheckBox *mSigChk = nullptr; QCheckBox *mEncOtherChk = nullptr; QCheckBox *mEncSelfChk = nullptr; GpgME::Protocol mCurrentProto = GpgME::UnknownProtocol; const bool mIsExclusive; std::unique_ptr<ExpiryChecker> 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(); + const bool publicKeyOnly = FileOperationsPreferences().publicKeyEncryptionOnly(); /* The signature selection */ { auto sigGrp = new QGroupBox{i18nc("@title:group", "Prove authenticity (sign)"), this}; d->mSigChk = new QCheckBox{i18n("Sign as:"), this}; d->mSigChk->setEnabled(haveSecretKeys); d->mSigChk->setChecked(haveSecretKeys); d->mSigSelect = new UserIDSelectionCombo{KeyUsage::Sign, this}; 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->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->setEnabled(haveSecretKeys && !symmetricOnly); d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); d->mSelfSelect = new UserIDSelectionCombo{KeyUsage::Encrypt, this}; 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->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(); // 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 <b>less secure</b> then public key cryptography. Even if you pick a very strong password.")); - d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys); + d->mSymmetric->setChecked((symmetricOnly || !havePublicKeys) && !publicKeyOnly); + d->mSymmetric->setEnabled(!publicKeyOnly); 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]() { 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) { if (d->mCurrentProto != GpgME::CMS) { return; } if (value) { d->mSigChk->setChecked(false); } }); connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) { 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); } }); } // Ensure that the d->mSigChk is aligned together with the encryption check boxes. d->mSigChk->setMinimumWidth(qMax(d->mEncOtherChk->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<unsigned>(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); }); 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<UserID> userIds = dlg.selectedUserIDs(); const std::vector<KeyGroup> 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<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(); 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); } } } 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<Key> SignEncryptWidget::recipients() const { std::vector<Key> 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<Key> 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(); } 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 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()); } 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(); + const bool publicKeyOnly = FileOperationsPreferences().publicKeyEncryptionOnly(); d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly); - d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys); + d->mSymmetric->setChecked((symmetricOnly || !havePublicKeys) && !publicKeyOnly); } else { d->mEncSelfChk->setChecked(false); d->mEncOtherChk->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() { mSigSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new SignCertificateFilter(mCurrentProto))); mSelfSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new EncryptSelfCertificateFilter(mCurrentProto))); const auto encFilter = std::shared_ptr<KeyFilter>(new EncryptCertificateFilter(mCurrentProto)); 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); } } } 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 std::ranges::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 = std::ranges::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); 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/kcfg/fileoperationspreferences.kcfg b/src/kcfg/fileoperationspreferences.kcfg index b2d4f9672..f0e8c9662 100644 --- a/src/kcfg/fileoperationspreferences.kcfg +++ b/src/kcfg/fileoperationspreferences.kcfg @@ -1,45 +1,50 @@ <?xml version="1.0" encoding="UTF-8"?> <kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > <kcfgfile name="kleopatrarc" /> <group name="FileOperations"> <entry name="UsePGPFileExt" key="file-extension-pgp" type="Bool"> <label>Use pgp as the default extension for generated OpenPGP files</label> <whatsthis>Set this to make kleopatra default to pgp file extensions for OpenPGP files.</whatsthis> <default>false</default> </entry> <entry name="AutoDecryptVerify" key="auto-decrypt-verify" type="Bool"> <label>Automatically start operation based on input detection for decrypt/verify.</label> <whatsthis>With this option set Kleopatra no longer asks you what you want to do with input files but instead automatically starts the operations it detects as applicable to the input.</whatsthis> <default>true</default> </entry> <entry name="AutoExtractArchives" key="auto-extract-archives" type="Bool"> <label>Automatically extract archives after decryption.</label> <whatsthis>If this option is set, then Kleopatra automatically extracts file archives after decryption.</whatsthis> <default>true</default> </entry> <entry name="ArchiveCommand" key="default-archive-cmd" type="String"> <label>Use this command to create file archives.</label> <whatsthis>When encrypting multiple files or a folder Kleopatra creates an encrypted archive with this command.</whatsthis> <default>tar</default> </entry> <entry name="AddASCIIArmor" key="ascii-armor" type="Bool"> <label>Create signed or encrypted files as text files.</label> <whatsthis>Set this option to encode encrypted or signed files as base64 encoded text. So that they can be opened with an editor or sent in a mail body. This will increase file size by one third.</whatsthis> <default>false</default> </entry> <entry name="DontUseTmpDir" key="dont-use-tmp-dir" type="Bool"> <label>Create temporary decrypted files in the folder of the encrypted file.</label> <whatsthis>Set this option to avoid using the users temporary directory.</whatsthis> <default>false</default> </entry> <entry name="SymmetricEncryptionOnly" key="symmetric-encryption-only" type="Bool"> <label>Use symmetric encryption only.</label> <whatsthis>Set this option to disable public key encryption.</whatsthis> <default>false</default> </entry> + <entry name="PublicKeyEncryptionOnly" key="public-key-encryption-only" type="Bool"> + <label>Use public key encryption only.</label> + <whatsthis>Set this option to disable password-based encryption.</whatsthis> + <default>false</default> + </entry> </group> </kcfg>