diff --git a/src/crypto/gui/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp index de8a33363..ef7cf29d3 100644 --- a/src/crypto/gui/signencryptwidget.cpp +++ b/src/crypto/gui/signencryptwidget.cpp @@ -1,653 +1,664 @@ /* 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 "commands/detailscommand.h" #include "dialogs/certificateselectiondialog.h" #include "dialogs/groupdetailsdialog.h" #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); 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); 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); } }; } SignEncryptWidget::SignEncryptWidget(QWidget *parent, bool sigEncExclusive) : QWidget(parent), mModel(AbstractKeyListModel::createFlatKeyListModel(this)), mIsExclusive(sigEncExclusive) { auto lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); 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 sigLay = new QHBoxLayout; auto sigGrp = new QGroupBox(i18nc("@title:group", "Prove authenticity (sign)")); mSigChk = new QCheckBox(i18n("Sign as:")); mSigChk->setEnabled(haveSecretKeys); mSigChk->setChecked(haveSecretKeys); mSigSelect = new KeySelectionCombo(); mSigSelect->setEnabled(mSigChk->isChecked()); sigLay->addWidget(mSigChk); sigLay->addWidget(mSigSelect, 1); sigGrp->setLayout(sigLay); lay->addWidget(sigGrp); connect(mSigChk, &QCheckBox::toggled, mSigSelect, &QWidget::setEnabled); connect(mSigChk, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); connect(mSigSelect, &KeySelectionCombo::currentKeyChanged, this, &SignEncryptWidget::updateOp); // Recipient selection auto encBoxLay = new QVBoxLayout; auto encBox = new QGroupBox(i18nc("@title:group", "Encrypt")); encBox->setLayout(encBoxLay); auto recipientGrid = new QGridLayout; // Own key mEncSelfChk = new QCheckBox(i18n("Encrypt for me:")); mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly); mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); mSelfSelect = new KeySelectionCombo(); mSelfSelect->setEnabled(mEncSelfChk->isChecked()); recipientGrid->addWidget(mEncSelfChk, 0, 0); recipientGrid->addWidget(mSelfSelect, 0, 1); // Checkbox for other keys mEncOtherChk = new QCheckBox(i18n("Encrypt for others:")); mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly); mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly); recipientGrid->addWidget(mEncOtherChk, 1, 0, Qt::AlignTop); connect(mEncOtherChk, &QCheckBox::toggled, this, [this](bool toggled) { for (CertificateLineEdit *edit : std::as_const(mRecpWidgets)) { edit->setEnabled(toggled); } updateOp(); }); mRecpLayout = new QVBoxLayout; recipientGrid->addLayout(mRecpLayout, 1, 1); recipientGrid->setRowStretch(2, 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); }); addRecipientWidget(); // Checkbox for password mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data.")); 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.")); mSymmetric->setChecked(symmetricOnly || !havePublicKeys); encBoxLay->addWidget(mSymmetric); // Connect it connect(mEncSelfChk, &QCheckBox::toggled, mSelfSelect, &QWidget::setEnabled); connect(mEncSelfChk, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); connect(mSymmetric, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); connect(mSelfSelect, &KeySelectionCombo::currentKeyChanged, this, &SignEncryptWidget::updateOp); if (mIsExclusive) { connect(mEncOtherChk, &QCheckBox::toggled, this, [this](bool value) { if (mCurrentProto != GpgME::CMS) { return; } if (value) { mSigChk->setChecked(false); } }); connect(mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) { if (mCurrentProto != GpgME::CMS) { return; } if (value) { mSigChk->setChecked(false); } }); connect(mSigChk, &QCheckBox::toggled, this, [this](bool value) { if (mCurrentProto != GpgME::CMS) { return; } if (value) { mEncSelfChk->setChecked(false); mEncOtherChk->setChecked(false); } }); } // Ensure that the mSigChk is aligned togehter with the encryption check boxes. mSigChk->setMinimumWidth(qMax(mEncOtherChk->width(), mEncSelfChk->width())); lay->addWidget(encBox); connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, - this, [this]() { - 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); - }); + this, &SignEncryptWidget::updateCheckBoxes); + connect(KleopatraApplication::instance(), &KleopatraApplication::configurationChanged, + this, &SignEncryptWidget::updateCheckBoxes); loadKeys(); onProtocolChanged(); updateOp(); } void SignEncryptWidget::setSignAsText(const QString &text) { mSigChk->setText(text); } void SignEncryptWidget::setEncryptForMeText(const QString &text) { mEncSelfChk->setText(text); } void SignEncryptWidget::setEncryptForOthersText(const QString &text) { mEncOtherChk->setText(text); } void SignEncryptWidget::setEncryptWithPasswordText(const QString& text) { mSymmetric->setText(text); } CertificateLineEdit *SignEncryptWidget::addRecipientWidget() { auto certSel = new CertificateLineEdit(mModel, this, new EncryptCertificateFilter(mCurrentProto)); certSel->setEnabled(mEncOtherChk->isChecked()); mRecpWidgets << certSel; if (mRecpLayout->count() > 0) { auto lastWidget = mRecpLayout->itemAt(mRecpLayout->count() - 1)->widget(); setTabOrder(lastWidget, certSel); } mRecpLayout->addWidget(certSel); connect(certSel, &CertificateLineEdit::keyChanged, this, &SignEncryptWidget::recipientsChanged); connect(certSel, &CertificateLineEdit::wantsRemoval, this, &SignEncryptWidget::recpRemovalRequested); connect(certSel, &CertificateLineEdit::editingStarted, this, &SignEncryptWidget::recipientsChanged); connect(certSel, &CertificateLineEdit::dialogRequested, this, [this, certSel] () { dialogRequested(certSel); }); return certSel; } void SignEncryptWidget::addRecipient(const Key &key) { CertificateLineEdit *certSel = addRecipientWidget(); if (!key.isNull()) { certSel->setKey(key); mAddedKeys << key; } } void SignEncryptWidget::addRecipient(const KeyGroup &group) { CertificateLineEdit *certSel = addRecipientWidget(); if (!group.isNull()) { certSel->setGroup(group); mAddedGroups << group; } } void SignEncryptWidget::dialogRequested(CertificateLineEdit *certificateLineEdit) { if (!certificateLineEdit->key().isNull()) { auto cmd = new Commands::DetailsCommand(certificateLineEdit->key(), nullptr); cmd->start(); return; } if (!certificateLineEdit->group().isNull()) { auto dlg = new GroupDetailsDialog; dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setGroup(certificateLineEdit->group()); dlg->show(); return; } auto const dlg = new CertificateSelectionDialog(this); dlg->setOptions(CertificateSelectionDialog::Options( CertificateSelectionDialog::MultiSelection | CertificateSelectionDialog::EncryptOnly | CertificateSelectionDialog::optionsFromProtocol(mCurrentProto) | CertificateSelectionDialog::IncludeGroups)); if (dlg->exec()) { const std::vector keys = dlg->selectedCertificates(); const std::vector groups = dlg->selectedGroups(); if (keys.size() == 0 && groups.size() == 0) { return; } bool isFirstItem = true; for (const Key &key : keys) { if (isFirstItem) { certificateLineEdit->setKey(key); isFirstItem = false; } else { addRecipient(key); } } for (const KeyGroup &group : groups) { if (isFirstItem) { certificateLineEdit->setGroup(group); isFirstItem = false; } else { addRecipient(group); } } } delete dlg; recipientsChanged(); } void SignEncryptWidget::clearAddedRecipients() { for (auto w: std::as_const(mUnknownWidgets)) { mRecpLayout->removeWidget(w); delete w; } for (auto &key: std::as_const(mAddedKeys)) { removeRecipient(key); } for (auto &group: std::as_const(mAddedGroups)) { removeRecipient(group); } } void SignEncryptWidget::addUnknownRecipient(const char *keyID) { auto unknownWidget = new UnknownRecipientWidget(keyID); mUnknownWidgets << unknownWidget; if (mRecpLayout->count() > 0) { auto lastWidget = mRecpLayout->itemAt(mRecpLayout->count() - 1)->widget(); setTabOrder(lastWidget, unknownWidget); } mRecpLayout->addWidget(unknownWidget); connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this] () { // Check if any unknown recipient can now be found. for (auto w: mUnknownWidgets) { auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData()); if (key.isNull()) { std::vector subids; subids.push_back(std::string(w->keyID().toLatin1().constData())); for (const auto &subkey: KeyCache::instance()->findSubkeysByKeyID(subids)) { key = subkey.parent(); } } if (key.isNull()) { continue; } // Key is now available replace by line edit. qCDebug(KLEOPATRA_LOG) << "Removing widget for keyid: " << w->keyID(); mRecpLayout->removeWidget(w); mUnknownWidgets.removeAll(w); delete w; addRecipient(key); } }); } void SignEncryptWidget::recipientsChanged() { const bool hasEmptyRecpWidget = std::any_of(std::cbegin(mRecpWidgets), std::cend(mRecpWidgets), [](auto w) { return w->isEmpty(); }); if (!hasEmptyRecpWidget) { addRecipientWidget(); } updateOp(); } Key SignEncryptWidget::signKey() const { if (mSigSelect->isEnabled()) { return mSigSelect->currentKey(); } return Key(); } Key SignEncryptWidget::selfKey() const { if (mSelfSelect->isEnabled()) { return mSelfSelect->currentKey(); } return Key(); } std::vector SignEncryptWidget::recipients() const { std::vector ret; for (const CertificateLineEdit *w : std::as_const(mRecpWidgets)) { if (!w->isEnabled()) { // If one is disabled, all are disabled. break; } const Key k = w->key(); const KeyGroup g = w->group(); 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)); } } const Key k = selfKey(); if (!k.isNull()) { ret.push_back(k); } return ret; } bool SignEncryptWidget::isDeVsAndValid() const { if (!signKey().isNull() && (!IS_DE_VS(signKey()) || keyValidity(signKey()) < GpgME::UserID::Validity::Full)) { return false; } if (!selfKey().isNull() && (!IS_DE_VS(selfKey()) || keyValidity(selfKey()) < GpgME::UserID::Validity::Full)) { return false; } for (const auto &key: recipients()) { if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } return true; } void SignEncryptWidget::updateOp() { const Key sigKey = signKey(); const std::vector recp = recipients(); QString newOp; if (!sigKey.isNull() && (!recp.empty() || encryptSymmetric())) { newOp = i18nc("@action", "Sign / Encrypt"); } else if (!recp.empty() || encryptSymmetric()) { newOp = i18nc("@action", "Encrypt"); } else if (!sigKey.isNull()) { newOp = i18nc("@action", "Sign"); } else { newOp = QString(); } mOp = newOp; Q_EMIT operationChanged(mOp); Q_EMIT keysChanged(); } QString SignEncryptWidget::currentOp() const { return mOp; } void SignEncryptWidget::recpRemovalRequested(CertificateLineEdit *w) { if (!w) { return; } const int emptyEdits = std::count_if(std::cbegin(mRecpWidgets), std::cend(mRecpWidgets), [](auto w) { return w->isEmpty(); }); if (emptyEdits > 1) { if (w->hasFocus()) { const int index = mRecpLayout->indexOf(w); const auto focusWidget = (index < mRecpLayout->count() - 1) ? mRecpLayout->itemAt(index + 1)->widget() : mRecpLayout->itemAt(mRecpLayout->count() - 2)->widget(); focusWidget->setFocus(); } mRecpLayout->removeWidget(w); mRecpWidgets.removeAll(w); w->deleteLater(); } } void SignEncryptWidget::removeRecipient(const GpgME::Key &key) { for (CertificateLineEdit *edit: std::as_const(mRecpWidgets)) { const auto editKey = edit->key(); if (key.isNull() && editKey.isNull()) { recpRemovalRequested(edit); return; } if (editKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) { recpRemovalRequested(edit); return; } } } void SignEncryptWidget::removeRecipient(const KeyGroup &group) { for (CertificateLineEdit *edit: std::as_const(mRecpWidgets)) { const auto editGroup = edit->group(); if (group.isNull() && editGroup.isNull()) { recpRemovalRequested(edit); return; } if (editGroup.name() == group.name()) { recpRemovalRequested(edit); return; } } } bool SignEncryptWidget::encryptSymmetric() const { return mSymmetric->isChecked(); } void SignEncryptWidget::loadKeys() { KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys"); auto cache = KeyCache::instance(); mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString())); mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString())); } void SignEncryptWidget::saveOwnKeys() const { KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys"); auto sigKey = mSigSelect->currentKey(); auto encKey = mSelfSelect->currentKey(); if (!sigKey.isNull()) { keys.writeEntry("SigningKey", sigKey.primaryFingerprint()); } if (!encKey.isNull()) { keys.writeEntry("EncryptKey", encKey.primaryFingerprint()); } } void SignEncryptWidget::setSigningChecked(bool value) { 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(); mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly); mSymmetric->setChecked(symmetricOnly || !havePublicKeys); } else { mEncSelfChk->setChecked(false); mEncOtherChk->setChecked(false); mSymmetric->setChecked(false); } } void SignEncryptWidget::setProtocol(GpgME::Protocol proto) { if (mCurrentProto == proto) { return; } mCurrentProto = proto; onProtocolChanged(); } void Kleo::SignEncryptWidget::onProtocolChanged() { 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 (CertificateLineEdit *edit : std::as_const(mRecpWidgets)) { 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); } } } bool SignEncryptWidget::validate() { QStringList unresolvedRecipients; for (const auto edit: std::as_const(mRecpWidgets)) { if (edit->isEnabled() && !edit->isEmpty() && edit->key().isNull() && edit->group().isNull()) { unresolvedRecipients.push_back(edit->text().toHtmlEscaped()); } } if (!unresolvedRecipients.isEmpty()) { KMessageBox::errorList(this, i18n("Could not find a key for the following recipients:"), unresolvedRecipients, i18n("Failed to find some keys")); } return unresolvedRecipients.isEmpty(); } + +void SignEncryptWidget::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); + } +} diff --git a/src/crypto/gui/signencryptwidget.h b/src/crypto/gui/signencryptwidget.h index 7a0a21e10..f110f82be 100644 --- a/src/crypto/gui/signencryptwidget.h +++ b/src/crypto/gui/signencryptwidget.h @@ -1,138 +1,139 @@ /* 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 class QCheckBox; class QVBoxLayout; namespace Kleo { class CertificateLineEdit; class KeyGroup; class KeySelectionCombo; class AbstractKeyListModel; class UnknownRecipientWidget; class SignEncryptWidget: public QWidget { Q_OBJECT public: /** If cmsSigEncExclusive is true CMS operations can be * done only either as sign or as encrypt */ explicit SignEncryptWidget(QWidget *parent = nullptr, bool cmsSigEncExclusive = false); /** 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 or * a null string if nothing would happen. */ QString 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); /** Validate that each line edit with content has a key. */ bool validate(); protected Q_SLOTS: void updateOp(); void recipientsChanged(); void recpRemovalRequested(CertificateLineEdit *w); void dialogRequested(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(const QString &op); /* Emitted when the certificate selection might be changed. */ void keysChanged(); private: CertificateLineEdit* addRecipientWidget(); void onProtocolChanged(); + void updateCheckBoxes(); private: KeySelectionCombo *mSigSelect = nullptr; KeySelectionCombo *mSelfSelect = nullptr; QVector mRecpWidgets; QVector mUnknownWidgets; QVector mAddedKeys; QVector mAddedGroups; QVBoxLayout *mRecpLayout = nullptr; QString mOp; AbstractKeyListModel *mModel = nullptr; QCheckBox *mSymmetric = nullptr; QCheckBox *mSigChk = nullptr; QCheckBox *mEncOtherChk = nullptr; QCheckBox *mEncSelfChk = nullptr; GpgME::Protocol mCurrentProto = GpgME::UnknownProtocol; const bool mIsExclusive; }; } // namespace Kleo diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp index 395e8cf44..869e16ab3 100644 --- a/src/kleopatraapplication.cpp +++ b/src/kleopatraapplication.cpp @@ -1,660 +1,670 @@ /* kleopatraapplication.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 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 "kleopatraapplication.h" #include "mainwindow.h" #include "kleopatra_options.h" #include "systrayicon.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_USABLE_ASSUAN # include #endif #include "commands/signencryptfilescommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/checksumcreatefilescommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/detailscommand.h" #include "commands/newcertificatecommand.h" #include "dialogs/updatenotification.h" #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif using namespace Kleo; using namespace Kleo::Commands; static void add_resources() { KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra")); KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg")); } static QList default_logging_options() { QList result; result.push_back("io"); return result; } class KleopatraApplication::Private { friend class ::KleopatraApplication; KleopatraApplication *const q; public: explicit Private(KleopatraApplication *qq) : q(qq), ignoreNewInstance(true), firstNewInstance(true), sysTray(nullptr) { } ~Private() { #ifndef QT_NO_SYSTEMTRAYICON delete sysTray; #endif } void init() { KDAB_SET_OBJECT_NAME(readerStatus); #ifndef QT_NO_SYSTEMTRAYICON sysTray = new SysTrayIcon(); sysTray->setFirstCardWithNullPin(readerStatus.firstCardWithNullPin()); sysTray->setAnyCardCanLearnKeys(readerStatus.anyCardCanLearnKeys()); connect(&readerStatus, &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin); connect(&readerStatus, &SmartCard::ReaderStatus::anyCardCanLearnKeysChanged, sysTray, &SysTrayIcon::setAnyCardCanLearnKeys); #endif } private: void connectConfigureDialog() { - if (configureDialog && q->mainWindow()) { - connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); + if (configureDialog) { + if (q->mainWindow()) { + connect(configureDialog, SIGNAL(configCommitted()), + q->mainWindow(), SLOT(slotConfigCommitted())); + } + connect(configureDialog, &ConfigureDialog::configCommitted, + q, &KleopatraApplication::configurationChanged); } } void disconnectConfigureDialog() { - if (configureDialog && q->mainWindow()) { - disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); + if (configureDialog) { + if (q->mainWindow()) { + disconnect(configureDialog, SIGNAL(configCommitted()), + q->mainWindow(), SLOT(slotConfigCommitted())); + } + disconnect(configureDialog, &ConfigureDialog::configCommitted, + q, &KleopatraApplication::configurationChanged); } } public: bool ignoreNewInstance; bool firstNewInstance; QPointer configureDialog; QPointer mainWindow; SmartCard::ReaderStatus readerStatus; #ifndef QT_NO_SYSTEMTRAYICON SysTrayIcon *sysTray; #endif std::shared_ptr keyCache; std::shared_ptr log; std::shared_ptr watcher; public: void setupKeyCache() { keyCache = KeyCache::mutableInstance(); watcher.reset(new FileSystemWatcher); watcher->whitelistFiles(gnupgFileWhitelist()); watcher->addPath(gnupgHomeDirectory()); watcher->setDelay(1000); keyCache->addFileSystemWatcher(watcher); keyCache->setGroupsConfig(QStringLiteral("kleopatragroupsrc")); keyCache->setGroupsEnabled(Settings().groupsEnabled()); } void setupLogging() { log = Log::mutableInstance(); const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS"); const bool logAll = envOptions.trimmed() == "all"; const QList options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(','); const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR"); if (dirNative.isEmpty()) { return; } const QString dir = QFile::decodeName(dirNative); const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid())); std::unique_ptr logFile(new QFile(logFileName)); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) { qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled"; return; } log->setOutputDirectory(dir); if (logAll || options.contains("io")) { log->setIOLoggingEnabled(true); } qInstallMessageHandler(Log::messageHandler); #ifdef HAVE_USABLE_ASSUAN if (logAll || options.contains("pipeio")) { KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug); } UiServer::setLogStream(log->logFile()); #endif } }; KleopatraApplication::KleopatraApplication(int &argc, char *argv[]) : QApplication(argc, argv), d(new Private(this)) { } void KleopatraApplication::init() { #ifdef Q_OS_WIN QWindowsWindowFunctions::setWindowActivationBehavior( QWindowsWindowFunctions::AlwaysActivateWindow); #endif d->init(); add_resources(); d->setupKeyCache(); d->setupLogging(); #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->show(); #endif setQuitOnLastWindowClosed(false); KWindowSystem::allowExternalProcessWindowActivation(); } KleopatraApplication::~KleopatraApplication() { // main window doesn't receive "close" signal and cannot // save settings before app exit delete d->mainWindow; // work around kdelibs bug https://bugs.kde.org/show_bug.cgi?id=162514 KSharedConfig::openConfig()->sync(); } namespace { using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol); } void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory) { QCommandLineParser parser; kleopatra_options(&parser); QString err; if (!arguments.isEmpty() && !parser.parse(arguments)) { err = parser.errorText(); } else if (arguments.isEmpty()) { // KDBusServices omits the application name if no other // arguments are provided. In that case the parser prints // a warning. parser.parse(QStringList() << QCoreApplication::applicationFilePath()); } if (err.isEmpty()) { err = newInstance(parser, workingDirectory); } if (!err.isEmpty()) { KMessageBox::sorry(nullptr, err.toHtmlEscaped(), i18n("Failed to execute command")); Q_EMIT setExitValue(1); return; } Q_EMIT setExitValue(0); } QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory) { if (d->ignoreNewInstance) { qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance"; return QString(); } QStringList files; const QDir cwd = QDir(workingDirectory); bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search")); // Query and Search treat positional arguments differently, see below. if (!queryMode) { const auto positionalArguments = parser.positionalArguments(); for (const QString &file : positionalArguments) { // We do not check that file exists here. Better handle // these errors in the UI. if (QFileInfo(file).isAbsolute()) { files << file; } else { files << cwd.absoluteFilePath(file); } } } GpgME::Protocol protocol = GpgME::UnknownProtocol; if (parser.isSet(QStringLiteral("openpgp"))) { qCDebug(KLEOPATRA_LOG) << "found OpenPGP"; protocol = GpgME::OpenPGP; } if (parser.isSet(QStringLiteral("cms"))) { qCDebug(KLEOPATRA_LOG) << "found CMS"; if (protocol == GpgME::OpenPGP) { return i18n("Ambiguous protocol: --openpgp and --cms"); } protocol = GpgME::CMS; } // Check for Parent Window id WId parentId = 0; if (parser.isSet(QStringLiteral("parent-windowid"))) { #ifdef Q_OS_WIN // WId is not a portable type as it is a pointer type on Windows. // casting it from an integer is ok though as the values are guaranteed to // be compatible in the documentation. parentId = reinterpret_cast(parser.value(QStringLiteral("parent-windowid")).toUInt()); #else parentId = parser.value(QStringLiteral("parent-windowid")).toUInt(); #endif } // Handle openpgp4fpr URI scheme QString needle; if (queryMode) { needle = parser.positionalArguments().join(QLatin1Char(' ')); } if (needle.startsWith(QLatin1String("openpgp4fpr:"))) { needle.remove(0, 12); } // Check for --search command. if (parser.isSet(QStringLiteral("search"))) { // This is an extra command instead of a combination with the // similar query to avoid changing the older query commands behavior // and query's "show details if a certificate exist or search on a // keyserver" logic is hard to explain and use consistently. if (needle.isEmpty()) { return i18n("No search string specified for --search"); } auto const cmd = new LookupCertificatesCommand(needle, nullptr); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --query command if (parser.isSet(QStringLiteral("query"))) { if (needle.isEmpty()) { return i18n("No fingerprint argument specified for --query"); } auto cmd = Command::commandForQuery(needle); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --gen-key command if (parser.isSet(QStringLiteral("gen-key"))) { auto cmd = new NewCertificateCommand(nullptr); cmd->setParentWId(parentId); cmd->setProtocol(protocol); cmd->start(); return QString(); } // Check for --config command if (parser.isSet(QStringLiteral("config"))) { openConfigDialogWithForeignParent(parentId); return QString(); } static const QMap funcMap { { QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile }, { QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles }, { QStringLiteral("sign"), &KleopatraApplication::signFiles }, { QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles }, { QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles }, { QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles }, { QStringLiteral("verify"), &KleopatraApplication::verifyFiles }, { QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles }, { QStringLiteral("checksum"), &KleopatraApplication::checksumFiles }, }; QString found; Q_FOREACH (const QString &opt, funcMap.keys()) { if (parser.isSet(opt) && found.isEmpty()) { found = opt; } else if (parser.isSet(opt)) { return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt); } } QStringList errors; if (!found.isEmpty()) { if (files.empty()) { return i18n("No files specified for \"%1\" command", found); } qCDebug(KLEOPATRA_LOG) << "found" << found; (this->*funcMap.value(found))(files, protocol); } else { if (files.empty()) { if (!(d->firstNewInstance && isSessionRestored())) { qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow"; openOrRaiseMainWindow(); } } else { for (const QString& fileName : std::as_const(files)) { QFileInfo fi(fileName); if (!fi.isReadable()) { errors << i18n("Cannot read \"%1\"", fileName); } } Q_FOREACH (Command *cmd, Command::commandsForFiles(files)) { if (parentId) { cmd->setParentWId(parentId); } else { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } cmd->setParentWidget(mw); } cmd->start(); } } } d->firstNewInstance = false; #ifdef Q_OS_WIN // On Windows we might be started from the // explorer in any working directory. E.g. // a double click on a file. To avoid preventing // the folder from deletion we set the // working directory to the users homedir. QDir::setCurrent(QDir::homePath()); #endif return errors.join(QLatin1Char('\n')); } #ifndef QT_NO_SYSTEMTRAYICON const SysTrayIcon *KleopatraApplication::sysTrayIcon() const { return d->sysTray; } SysTrayIcon *KleopatraApplication::sysTrayIcon() { return d->sysTray; } #endif const MainWindow *KleopatraApplication::mainWindow() const { return d->mainWindow; } MainWindow *KleopatraApplication::mainWindow() { return d->mainWindow; } void KleopatraApplication::setMainWindow(MainWindow *mainWindow) { if (mainWindow == d->mainWindow) { return; } d->disconnectConfigureDialog(); d->mainWindow = mainWindow; #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->setMainWindow(mainWindow); #endif d->connectConfigureDialog(); } static void open_or_raise(QWidget *w) { if (w->isMinimized()) { KWindowSystem::unminimizeWindow(w->winId()); w->raise(); } else if (w->isVisible()) { w->raise(); } else { w->show(); } } void KleopatraApplication::toggleMainWindowVisibility() { if (mainWindow()) { mainWindow()->setVisible(!mainWindow()->isVisible()); } else { openOrRaiseMainWindow(); } } void KleopatraApplication::restoreMainWindow() { qCDebug(KLEOPATRA_LOG) << "restoring main window"; // Sanity checks if (!isSessionRestored()) { qCDebug(KLEOPATRA_LOG) << "Not in session restore"; return; } if (mainWindow()) { qCDebug(KLEOPATRA_LOG) << "Already have main window"; return; } auto mw = new MainWindow; if (KMainWindow::canBeRestored(1)) { // restore to hidden state, Mainwindow::readProperties() will // restore saved visibility. mw->restore(1, false); } mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } void KleopatraApplication::openOrRaiseMainWindow() { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } open_or_raise(mw); UpdateNotification::checkUpdate(mw); } void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId) { if (!d->configureDialog) { d->configureDialog = new ConfigureDialog; d->configureDialog->setAttribute(Qt::WA_DeleteOnClose); d->connectConfigureDialog(); } // This is similar to what the commands do. if (parentWId) { if (QWidget *pw = QWidget::find(parentWId)) { d->configureDialog->setParent(pw, d->configureDialog->windowFlags()); } else { d->configureDialog->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId); } } open_or_raise(d->configureDialog); // If we have a parent we want to raise over it. if (parentWId) { d->configureDialog->raise(); } } void KleopatraApplication::openOrRaiseConfigDialog() { openConfigDialogWithForeignParent(0); } #ifndef QT_NO_SYSTEMTRAYICON void KleopatraApplication::startMonitoringSmartCard() { d->readerStatus.startMonitoring(); } #endif // QT_NO_SYSTEMTRAYICON void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/) { openOrRaiseMainWindow(); if (!files.empty()) { mainWindow()->importCertificatesFromFile(files); } } void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setEncryptionPolicy(Force); cmd->setSigningPolicy(Allow); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setSigningPolicy(Force); cmd->setEncryptionPolicy(Deny); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Decrypt); cmd->start(); } void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Verify); cmd->start(); } void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->start(); } void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/) { QStringList verifyFiles, createFiles; for (const QString &file : files) { if (isChecksumFile(file)) { verifyFiles << file; } else { createFiles << file; } } if (!verifyFiles.isEmpty()) { auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr); cmd->start(); } if (!createFiles.isEmpty()) { auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr); cmd->start(); } } void KleopatraApplication::setIgnoreNewInstance(bool ignore) { d->ignoreNewInstance = ignore; } bool KleopatraApplication::ignoreNewInstance() const { return d->ignoreNewInstance; } diff --git a/src/kleopatraapplication.h b/src/kleopatraapplication.h index 74437fb34..1d0998620 100644 --- a/src/kleopatraapplication.h +++ b/src/kleopatraapplication.h @@ -1,102 +1,104 @@ /* kleopatraapplication.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 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 */ #pragma once #include #include #include #include class MainWindow; class SysTrayIcon; class KleopatraApplication : public QApplication { Q_OBJECT public: /** Create a new Application object. You have to * make sure to call init afterwards to get a valid object. * This is to delay initialisation after the UniqueService * call is done and our init / call might be forwarded to * another instance. */ KleopatraApplication(int &argc, char *argv[]); ~KleopatraApplication(); /** Initialize the application. Without calling init any * other call to KleopatraApplication will result in undefined behavior * and likely crash. */ void init(); static KleopatraApplication *instance() { return qobject_cast(qApp); } /** Starts a new instance or a command from the command line. * * Handles the parser options and starts the according commands. * If ignoreNewInstance is set this function does nothing. * The parser should have been initialized with kleopatra_options and * already processed. * If kleopatra is not session restored * * @param parser: The command line parser to use. * @param workingDirectory: Optional working directory for file arguments. * * @returns an empty QString on success. A localized error message otherwise. * */ QString newInstance(const QCommandLineParser &parser, const QString &workingDirectory = QString()); void setMainWindow(MainWindow *mw); const MainWindow *mainWindow() const; MainWindow *mainWindow(); const SysTrayIcon *sysTrayIcon() const; SysTrayIcon *sysTrayIcon(); void setIgnoreNewInstance(bool on); bool ignoreNewInstance() const; void toggleMainWindowVisibility(); void restoreMainWindow(); void openConfigDialogWithForeignParent(WId parentWId); public Q_SLOTS: void openOrRaiseMainWindow(); void openOrRaiseConfigDialog(); #ifndef QT_NO_SYSTEMTRAYICON void startMonitoringSmartCard(); void importCertificatesFromFile(const QStringList &files, GpgME::Protocol proto); #endif void encryptFiles(const QStringList &files, GpgME::Protocol proto); void signFiles(const QStringList &files, GpgME::Protocol proto); void signEncryptFiles(const QStringList &files, GpgME::Protocol proto); void decryptFiles(const QStringList &files, GpgME::Protocol proto); void verifyFiles(const QStringList &files, GpgME::Protocol proto); void decryptVerifyFiles(const QStringList &files, GpgME::Protocol proto); void checksumFiles(const QStringList &files, GpgME::Protocol /* unused */); void slotActivateRequested(const QStringList &arguments, const QString &workingDirectory); Q_SIGNALS: /* Emitted from slotActivateRequested to enable setting the * correct exitValue */ void setExitValue(int value); + void configurationChanged(); + private: class Private; kdtools::pimpl_ptr d; };