diff --git a/src/commands/refreshcertificatecommand.cpp b/src/commands/refreshcertificatecommand.cpp index 19bb4f758..4c8e18399 100644 --- a/src/commands/refreshcertificatecommand.cpp +++ b/src/commands/refreshcertificatecommand.cpp @@ -1,408 +1,414 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/refreshcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "command_p.h" #include "refreshcertificatecommand.h" +#include #include #include #include #include #if QGPGME_SUPPORTS_KEY_REFRESH #include #include #endif #if QGPGME_SUPPORTS_WKD_REFRESH_JOB #include #endif #include #include "kleopatra_debug.h" using namespace Kleo; using namespace GpgME; class RefreshCertificateCommand::Private : public Command::Private { friend class ::RefreshCertificateCommand; RefreshCertificateCommand *q_func() const { return static_cast(q); } public: explicit Private(RefreshCertificateCommand *qq); ~Private() override; void start(); void cancel(); #if QGPGME_SUPPORTS_KEY_REFRESH std::unique_ptr startReceiveKeysJob(); std::unique_ptr startSMIMEJob(); #endif #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr startWKDRefreshJob(); #endif void onReceiveKeysJobResult(const ImportResult &result); void onWKDRefreshJobResult(const ImportResult &result); void onSMIMEJobResult(const Error &err); void showOpenPGPResult(); void showError(const Error &err); private: Key key; #if QGPGME_SUPPORTS_KEY_REFRESH QPointer job; #endif ImportResult receiveKeysResult; ImportResult wkdRefreshResult; }; RefreshCertificateCommand::Private *RefreshCertificateCommand::d_func() { return static_cast(d.get()); } const RefreshCertificateCommand::Private *RefreshCertificateCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() RefreshCertificateCommand::Private::Private(RefreshCertificateCommand *qq) : Command::Private{qq} { } RefreshCertificateCommand::Private::~Private() = default; namespace { Key getKey(const std::vector &keys) { if (keys.size() != 1) { qCWarning(KLEOPATRA_LOG) << "Expected exactly one key, but got" << keys.size(); return {}; } const Key key = keys.front(); if (key.protocol() == GpgME::UnknownProtocol) { qCWarning(KLEOPATRA_LOG) << "Key has unknown protocol"; return {}; } return key; } } void RefreshCertificateCommand::Private::start() { key = getKey(keys()); if (key.isNull()) { finished(); return; } #if QGPGME_SUPPORTS_KEY_REFRESH std::unique_ptr refreshJob; switch (key.protocol()) { case GpgME::OpenPGP: refreshJob = startReceiveKeysJob(); break; case GpgME::CMS: refreshJob = startSMIMEJob(); break; default:; // cannot happen ;-) } if (!refreshJob) { finished(); return; } job = refreshJob.release(); #else error(i18n("The backend does not support updating individual certificates.")); finished(); #endif } void RefreshCertificateCommand::Private::cancel() { #if QGPGME_SUPPORTS_KEY_REFRESH if (job) { job->slotCancel(); } job.clear(); #endif } #if QGPGME_SUPPORTS_KEY_REFRESH std::unique_ptr RefreshCertificateCommand::Private::startReceiveKeysJob() { std::unique_ptr refreshJob{QGpgME::openpgp()->receiveKeysJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::ReceiveKeysJob::result, q, [this](const GpgME::ImportResult &result) { onReceiveKeysJobResult(result); }); #if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); #else connect(refreshJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); }); #endif const GpgME::Error err = refreshJob->start({QString::fromLatin1(key.primaryFingerprint())}); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating key...")); return refreshJob; } std::unique_ptr RefreshCertificateCommand::Private::startSMIMEJob() { std::unique_ptr refreshJob{QGpgME::smime()->refreshKeysJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::RefreshKeysJob::result, q, [this](const GpgME::Error &err) { onSMIMEJobResult(err); }); #if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); #else connect(refreshJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); }); #endif const GpgME::Error err = refreshJob->start({key}); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating certificate...")); return refreshJob; } #endif #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr RefreshCertificateCommand::Private::startWKDRefreshJob() { // check if key is eligible for WKD refresh, i.e. if any user ID has WKD as origin const auto userIds = key.userIDs(); const auto eligibleForWKDRefresh = std::any_of(userIds.begin(), userIds.end(), [](const auto &userId) { return !userId.isRevoked() && !userId.addrSpec().empty() && userId.origin() == Key::OriginWKD; }); if (!eligibleForWKDRefresh) { wkdRefreshResult = ImportResult{Error::fromCode(GPG_ERR_USER_1)}; return {}; } std::unique_ptr refreshJob{QGpgME::openpgp()->wkdRefreshJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::WKDRefreshJob::result, q, [this](const GpgME::ImportResult &result) { onWKDRefreshJobResult(result); }); connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); - const GpgME::Error err = refreshJob->start({key}); + Error err; + if (Settings{}.queryWKDsForAllUserIDs()) { + err = refreshJob->start(key.userIDs()); + } else { + err = refreshJob->start({key}); + } if (err) { wkdRefreshResult = ImportResult{err}; return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating key...")); return refreshJob; } #endif namespace { static auto informationOnChanges(const ImportResult &result) { QString text; // if additional keys have been retrieved via WKD, then most of the below // details are just a guess and may concern the additional keys instead of // the refresh keys; this could only be clarified by a thorough comparison of // unrefreshed and refreshed key if (result.numUnchanged() == result.numConsidered()) { // if numUnchanged < numConsidered, then it is not clear whether the refreshed key // hasn't changed or whether another key retrieved via WKD hasn't changed text = i18n("The key hasn't changed."); } else if (result.newRevocations() > 0) { // it is possible that a revoked key has been newly imported via WKD, // but it is much more likely that the refreshed key was revoked text = i18n("The key has been revoked."); } else { // it doesn't make much sense to list below details if the key has been revoked text = i18n("The key has been updated."); QStringList details; if (result.newUserIDs() > 0) { details.push_back(i18n("New user IDs: %1", result.newUserIDs())); } if (result.newSubkeys() > 0) { details.push_back(i18n("New subkeys: %1", result.newSubkeys())); } if (result.newSignatures() > 0) { details.push_back(i18n("New signatures: %1", result.newSignatures())); } if (!details.empty()) { text += QLatin1String{"

"} + details.join(QLatin1String{"
"}); } } text = QLatin1String{"

"} + text + QLatin1String{"

"}; if (result.numImported() > 0) { text += QLatin1String{"

"} + i18np("Additionally, one new key has been retrieved.", "Additionally, %1 new keys have been retrieved.", result.numImported()) + QLatin1String{"

"}; } return text; } } void RefreshCertificateCommand::Private::onReceiveKeysJobResult(const ImportResult &result) { receiveKeysResult = result; if (result.error().isCanceled()) { finished(); return; } #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr refreshJob = startWKDRefreshJob(); if (!refreshJob) { showOpenPGPResult(); return; } job = refreshJob.release(); #else if (result.error()) { showError(result.error()); } else { information(informationOnChanges(result), i18nc("@title:window", "Key Updated")); } finished(); #endif } void RefreshCertificateCommand::Private::onWKDRefreshJobResult(const ImportResult &result) { wkdRefreshResult = result; showOpenPGPResult(); } void RefreshCertificateCommand::Private::onSMIMEJobResult(const Error &err) { if (err) { showError(err); finished(); return; } if (!err.isCanceled()) { information(i18nc("@info", "The certificate has been updated."), i18nc("@title:window", "Certificate Updated")); } finished(); } void RefreshCertificateCommand::Private::showOpenPGPResult() { if (wkdRefreshResult.error().code() == GPG_ERR_USER_1 || wkdRefreshResult.error().isCanceled()) { if (receiveKeysResult.error()) { showError(receiveKeysResult.error()); } else { information(informationOnChanges(receiveKeysResult), i18nc("@title:window", "Key Updated")); } finished(); return; } if (receiveKeysResult.error() && wkdRefreshResult.error()) { error(xi18nc("@info", "Updating the certificate from a keyserver, an LDAP server, or Active Directory failed:" "%1" "Updating the certificate via Web Key Directory failed:" "%2", Formatting::errorAsString(receiveKeysResult.error()), Formatting::errorAsString(wkdRefreshResult.error())), i18nc("@title:window", "Update Failed")); finished(); return; } QString text; text += QLatin1String{"

"} + i18nc("@info", "Result of update from keyserver, LDAP server, or Active Directory") + QLatin1String{"

"}; if (receiveKeysResult.error()) { text += xi18nc("@info", "The update failed: %1", Formatting::errorAsString(receiveKeysResult.error())); } else { text += informationOnChanges(receiveKeysResult); } text += QLatin1String{"

"} + i18nc("@info", "Result of update via Web Key Directory") + QLatin1String{"

"}; if (wkdRefreshResult.error()) { text += xi18nc("@info", "The update failed: %1", Formatting::errorAsString(wkdRefreshResult.error())); } else { text += informationOnChanges(wkdRefreshResult); } information(text, i18nc("@title:window", "Key Updated")); finished(); } void RefreshCertificateCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred while updating the certificate:" "%1", Formatting::errorAsString(err)), i18nc("@title:window", "Update Failed")); } RefreshCertificateCommand::RefreshCertificateCommand(const GpgME::Key &key) : Command{key, new Private{this}} { } RefreshCertificateCommand::~RefreshCertificateCommand() = default; void RefreshCertificateCommand::doStart() { d->start(); } void RefreshCertificateCommand::doCancel() { d->cancel(); } #undef d #undef q #include "moc_refreshcertificatecommand.cpp" diff --git a/src/conf/dirservconfigpage.cpp b/src/conf/dirservconfigpage.cpp index 8da7c0a79..f95d168d2 100644 --- a/src/conf/dirservconfigpage.cpp +++ b/src/conf/dirservconfigpage.cpp @@ -1,513 +1,526 @@ /* -*- mode: c++; c-basic-offset:4 -*- conf/dirservconfigpage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2004, 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "dirservconfigpage.h" #include "labelledwidget.h" #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace QGpgME; // Option for configuring X.509 servers (available via gpgconf since GnuPG 2.3.5 and 2.2.34) static const char s_x509services_componentName[] = "dirmngr"; static const char s_x509services_entryName[] = "ldapserver"; // Legacy option for configuring X.509 servers (deprecated with GnuPG 2.2.28 and 2.3.2) static const char s_x509services_legacy_componentName[] = "gpgsm"; static const char s_x509services_legacy_entryName[] = "keyserver"; static const char s_pgpservice_componentName[] = "dirmngr"; static const char s_pgpservice_entryName[] = "keyserver"; // legacy config entry used until GnuPG 2.2 static const char s_pgpservice_legacy_componentName[] = "gpg"; static const char s_pgpservice_legacy_entryName[] = "keyserver"; static const char s_timeout_componentName[] = "dirmngr"; static const char s_timeout_entryName[] = "ldaptimeout"; static const char s_maxitems_componentName[] = "dirmngr"; static const char s_maxitems_entryName[] = "max-replies"; class DirectoryServicesConfigurationPage::Private { DirectoryServicesConfigurationPage *q = nullptr; public: Private(DirectoryServicesConfigurationPage *q); void load(); void save(); void defaults(); private: enum EntryMultiplicity { SingleValue, ListValue, }; enum ShowError { DoNotShowError, DoShowError, }; void setX509ServerEntry(const std::vector &servers); void load(const Kleo::Settings &settings); QGpgME::CryptoConfigEntry *configEntry(const char *componentName, const char *entryName, QGpgME::CryptoConfigEntry::ArgType argType, EntryMultiplicity multiplicity, ShowError showError); Kleo::LabelledWidget mOpenPGPKeyserverEdit; Kleo::DirectoryServicesWidget *mDirectoryServices = nullptr; Kleo::LabelledWidget mTimeout; Kleo::LabelledWidget mMaxItems; QCheckBox *mFetchMissingSignerKeysCB = nullptr; + QCheckBox *mQueryWKDsForAllUserIDsCB = nullptr; QGpgME::CryptoConfigEntry *mOpenPGPServiceEntry = nullptr; QGpgME::CryptoConfigEntry *mTimeoutConfigEntry = nullptr; QGpgME::CryptoConfigEntry *mMaxItemsConfigEntry = nullptr; QGpgME::CryptoConfig *mConfig = nullptr; }; DirectoryServicesConfigurationPage::Private::Private(DirectoryServicesConfigurationPage *q) { mConfig = QGpgME::cryptoConfig(); #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) auto glay = new QGridLayout(q); #else auto glay = new QGridLayout(q->widget()); #endif glay->setContentsMargins(0, 0, 0, 0); // OpenPGP keyserver int row = 0; { auto l = new QHBoxLayout{}; l->setContentsMargins(0, 0, 0, 0); #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) mOpenPGPKeyserverEdit.createWidgets(q); #else mOpenPGPKeyserverEdit.createWidgets(q->widget()); #endif mOpenPGPKeyserverEdit.label()->setText(i18n("OpenPGP keyserver:")); l->addWidget(mOpenPGPKeyserverEdit.label()); l->addWidget(mOpenPGPKeyserverEdit.widget()); glay->addLayout(l, row, 0, 1, 3); connect(mOpenPGPKeyserverEdit.widget(), &QLineEdit::textEdited, q, &DirectoryServicesConfigurationPage::markAsChanged); } // X.509 servers if (Settings{}.cmsEnabled()) { ++row; #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) auto groupBox = new QGroupBox{i18n("X.509 Directory Services"), q}; #else auto groupBox = new QGroupBox{i18n("X.509 Directory Services"), q->widget()}; #endif auto groupBoxLayout = new QVBoxLayout{groupBox}; if (gpgme_check_version("1.16.0")) { #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) mDirectoryServices = new Kleo::DirectoryServicesWidget(q); #else mDirectoryServices = new Kleo::DirectoryServicesWidget(q->widget()); #endif if (QLayout *l = mDirectoryServices->layout()) { l->setContentsMargins(0, 0, 0, 0); } groupBoxLayout->addWidget(mDirectoryServices); connect(mDirectoryServices, &DirectoryServicesWidget::changed, q, &DirectoryServicesConfigurationPage::markAsChanged); } else { // QGpgME does not properly support keyserver flags for X.509 keyservers (added in GnuPG 2.2.28); // disable the configuration to prevent the configuration from being corrupted #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) groupBoxLayout->addWidget(new QLabel{i18n("Configuration of directory services is not possible " "because the used gpgme libraries are too old."), q}); #else groupBoxLayout->addWidget(new QLabel{i18n("Configuration of directory services is not possible " "because the used gpgme libraries are too old."), q->widget()}); #endif } glay->addWidget(groupBox, row, 0, 1, 3); } // LDAP timeout ++row; #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) mTimeout.createWidgets(q); #else mTimeout.createWidgets(q->widget()); #endif mTimeout.label()->setText(i18n("LDAP &timeout (minutes:seconds):")); mTimeout.widget()->setDisplayFormat(QStringLiteral("mm:ss")); connect(mTimeout.widget(), &QTimeEdit::timeChanged, q, &DirectoryServicesConfigurationPage::markAsChanged); glay->addWidget(mTimeout.label(), row, 0); glay->addWidget(mTimeout.widget(), row, 1); // Max number of items returned by queries ++row; #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) mMaxItems.createWidgets(q); #else mMaxItems.createWidgets(q->widget()); #endif mMaxItems.label()->setText(i18n("&Maximum number of items returned by query:")); mMaxItems.widget()->setMinimum(0); #if QT_DEPRECATED_SINCE(5, 14) connect(mMaxItems.widget(), qOverload(&QSpinBox::valueChanged), q, &DirectoryServicesConfigurationPage::markAsChanged); #else connect(mMaxItems.widget(), &QSpinBox::valueChanged, q, &DirectoryServicesConfigurationPage::markAsChanged); #endif glay->addWidget(mMaxItems.label(), row, 0); glay->addWidget(mMaxItems.widget(), row, 1); #if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID ++row; mFetchMissingSignerKeysCB = new QCheckBox{q}; mFetchMissingSignerKeysCB->setText(i18nc("@option:check", "Retrieve missing certification keys when importing new keys")); mFetchMissingSignerKeysCB->setToolTip(xi18nc("@info:tooltip", "If enabled, then Kleopatra will automatically try to retrieve the keys " "that were used to certify the user IDs of newly imported OpenPGP keys.")); connect(mFetchMissingSignerKeysCB, &QCheckBox::toggled, q, &DirectoryServicesConfigurationPage::markAsChanged); glay->addWidget(mFetchMissingSignerKeysCB, row, 0, 1, 3); #endif + ++row; + mQueryWKDsForAllUserIDsCB = new QCheckBox{q}; + mQueryWKDsForAllUserIDsCB->setText(i18nc("@option:check", "Query certificate directories of providers for all user IDs")); + mQueryWKDsForAllUserIDsCB->setToolTip(xi18nc("@info:tooltip", + "By default, Kleopatra only queries the certificate directories of providers (WKD) " + "for user IDs that were originally retrieved from a WKD when you update an OpenPGP " + "certificate. If this option is enabled, then Kleopatra will query WKDs for all user IDs.")); + connect(mQueryWKDsForAllUserIDsCB, &QCheckBox::toggled, q, &DirectoryServicesConfigurationPage::markAsChanged); + glay->addWidget(mQueryWKDsForAllUserIDsCB, row, 0, 1, 3); + glay->setRowStretch(++row, 1); glay->setColumnStretch(2, 1); } static auto readKeyserverConfigs(const CryptoConfigEntry *configEntry) { std::vector servers; if (configEntry) { const auto urls = configEntry->urlValueList(); servers.reserve(urls.size()); std::transform(std::begin(urls), std::end(urls), std::back_inserter(servers), &KeyserverConfig::fromUrl); } return servers; } void DirectoryServicesConfigurationPage::Private::load(const Kleo::Settings &settings) { if (mDirectoryServices) { mDirectoryServices->clear(); // gpgsm uses the deprecated keyserver option in gpgsm.conf additionally to the ldapserver option in dirmngr.conf; // we (try to) read servers from both entries, but always write to the newest existing entry const auto *const newEntry = configEntry(s_x509services_componentName, s_x509services_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); const auto *const legacyEntry = configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); auto entry = newEntry ? newEntry : legacyEntry; if (entry) { const auto additionalServers = readKeyserverConfigs(legacyEntry); auto servers = readKeyserverConfigs(newEntry); std::copy(std::begin(additionalServers), std::end(additionalServers), std::back_inserter(servers)); mDirectoryServices->setKeyservers(servers); mDirectoryServices->setReadOnly(entry->isReadOnly()); } else { qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries" << s_x509services_componentName << "/" << s_x509services_entryName << "and" << s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName; mDirectoryServices->setDisabled(true); } } { // gpg prefers the deprecated keyserver option in gpg.conf over the keyserver option in dirmngr.conf; // therefore, we use the deprecated keyserver option if it is set or if the new option doesn't exist (gpg < 2.1.9) auto const newEntry = configEntry(s_pgpservice_componentName, s_pgpservice_entryName, CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError); auto const legacyEntry = configEntry(s_pgpservice_legacy_componentName, s_pgpservice_legacy_entryName, CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError); mOpenPGPServiceEntry = ((legacyEntry && legacyEntry->isSet()) || !newEntry) ? legacyEntry : newEntry; if (!mOpenPGPServiceEntry) { qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries" << s_pgpservice_componentName << "/" << s_pgpservice_entryName << "and" << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName; } else if (mOpenPGPServiceEntry == legacyEntry) { qCDebug(KLEOPATRA_LOG) << "Using config entry" << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName; } else { qCDebug(KLEOPATRA_LOG) << "Using config entry" << s_pgpservice_componentName << "/" << s_pgpservice_entryName; } mOpenPGPKeyserverEdit.widget()->setText(mOpenPGPServiceEntry && mOpenPGPServiceEntry->isSet() ? mOpenPGPServiceEntry->stringValue() : QString()); mOpenPGPKeyserverEdit.setEnabled(mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly()); #if QGPGME_CRYPTOCONFIGENTRY_HAS_DEFAULT_VALUE if (newEntry && !newEntry->defaultValue().isNull()) { mOpenPGPKeyserverEdit.widget()->setPlaceholderText(newEntry->defaultValue().toString()); } else #endif { if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16") { mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkp://keys.gnupg.net")); } else { mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkps://hkps.pool.sks-keyservers.net")); } } } // read LDAP timeout // first try to read the config entry as int (GnuPG 2.3) mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError); if (!mTimeoutConfigEntry) { // if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2) mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError); } if (mTimeoutConfigEntry) { const int ldapTimeout = mTimeoutConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ? mTimeoutConfigEntry->intValue() : static_cast(mTimeoutConfigEntry->uintValue()); const QTime time = QTime(0, 0, 0, 0).addSecs(ldapTimeout); // qCDebug(KLEOPATRA_LOG) <<"timeout:" << mTimeoutConfigEntry->uintValue() <<" ->" << time; mTimeout.widget()->setTime(time); } mTimeout.setEnabled(mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly()); // read max-replies config entry // first try to read the config entry as int (GnuPG 2.3) mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError); if (!mMaxItemsConfigEntry) { // if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2) mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError); } if (mMaxItemsConfigEntry) { const int value = mMaxItemsConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ? mMaxItemsConfigEntry->intValue() : static_cast(mMaxItemsConfigEntry->uintValue()); mMaxItems.widget()->blockSignals(true); // KNumInput emits valueChanged from setValue! mMaxItems.widget()->setValue(value); mMaxItems.widget()->blockSignals(false); } mMaxItems.setEnabled(mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly()); #if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID mFetchMissingSignerKeysCB->setChecked(settings.retrieveSignerKeysAfterImport()); mFetchMissingSignerKeysCB->setEnabled(!settings.isImmutable(QStringLiteral("RetrieveSignerKeysAfterImport"))); -#else - Q_UNUSED(settings) #endif + mQueryWKDsForAllUserIDsCB->setChecked(settings.queryWKDsForAllUserIDs()); + mQueryWKDsForAllUserIDsCB->setEnabled(!settings.isImmutable(QStringLiteral("QueryWKDsForAllUserIDs"))); } void DirectoryServicesConfigurationPage::Private::load() { load(Settings{}); } namespace { void updateIntegerConfigEntry(QGpgME::CryptoConfigEntry *configEntry, int value) { if (!configEntry) { return; } if (configEntry->argType() == CryptoConfigEntry::ArgType_Int) { if (configEntry->intValue() != value) { configEntry->setIntValue(value); } } else { const auto newValue = static_cast(value); if (configEntry->uintValue() != newValue) { configEntry->setUIntValue(newValue); } } } } void DirectoryServicesConfigurationPage::Private::setX509ServerEntry(const std::vector &servers) { const auto newEntry = configEntry(s_x509services_componentName, s_x509services_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); const auto legacyEntry = configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); if ((newEntry && newEntry->isReadOnly()) || (legacyEntry && legacyEntry->isReadOnly())) { // do not change the config entries if either config entry is read-only return; } QList urls; urls.reserve(servers.size()); std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), std::mem_fn(&KeyserverConfig::toUrl)); if (newEntry) { // write all servers to the new config entry newEntry->setURLValueList(urls); // and clear the legacy config entry if (legacyEntry) { legacyEntry->setURLValueList({}); } } else if (legacyEntry) { // write all servers to the legacy config entry if the new entry is not available legacyEntry->setURLValueList(urls); } else { qCWarning(KLEOPATRA_LOG) << "Could not store the X.509 servers. Unknown or wrong typed config entries" << s_x509services_componentName << "/" << s_x509services_entryName << "and" << s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName; } } void DirectoryServicesConfigurationPage::Private::save() { if (mDirectoryServices && mDirectoryServices->isEnabled()) { setX509ServerEntry(mDirectoryServices->keyservers()); } if (mOpenPGPServiceEntry) { const auto keyserver = mOpenPGPKeyserverEdit.widget()->text().trimmed(); if (keyserver.isEmpty()) { mOpenPGPServiceEntry->resetToDefault(); } else { const auto keyserverUrl = keyserver.contains(QLatin1String{"://"}) ? keyserver : (QLatin1String{"hkps://"} + keyserver); mOpenPGPServiceEntry->setStringValue(keyserverUrl); } } const QTime time{mTimeout.widget()->time()}; updateIntegerConfigEntry(mTimeoutConfigEntry, time.minute() * 60 + time.second()); updateIntegerConfigEntry(mMaxItemsConfigEntry, mMaxItems.widget()->value()); mConfig->sync(true); -#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID Settings settings; +#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID settings.setRetrieveSignerKeysAfterImport(mFetchMissingSignerKeysCB->isChecked()); - settings.save(); #endif + settings.setQueryWKDsForAllUserIDs(mQueryWKDsForAllUserIDsCB->isChecked()); + settings.save(); } void DirectoryServicesConfigurationPage::Private::defaults() { // these guys don't have a default, to clear them: if (mDirectoryServices && mDirectoryServices->isEnabled()) { setX509ServerEntry({}); } if (mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly()) { mOpenPGPServiceEntry->setStringValue(QString()); } // these presumably have a default, use that one: if (mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly()) { mTimeoutConfigEntry->resetToDefault(); } if (mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly()) { mMaxItemsConfigEntry->resetToDefault(); } Settings settings; settings.setRetrieveSignerKeysAfterImport(settings.findItem(QStringLiteral("RetrieveSignerKeysAfterImport"))->getDefault().toBool()); + settings.setQueryWKDsForAllUserIDs(settings.findItem(QStringLiteral("QueryWKDsForAllUserIDs"))->getDefault().toBool()); load(settings); } // Find config entry for ldap servers. Implements runtime checks on the configuration option. CryptoConfigEntry *DirectoryServicesConfigurationPage::Private::configEntry(const char *componentName, const char *entryName, CryptoConfigEntry::ArgType argType, EntryMultiplicity multiplicity, ShowError showError) { CryptoConfigEntry *const entry = Kleo::getCryptoConfigEntry(mConfig, componentName, entryName); if (!entry) { if (showError == DoShowError) { #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) KMessageBox::error( q, i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1String(componentName), QLatin1String(entryName))); #else KMessageBox::error( q->widget(), i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1String(componentName), QLatin1String(entryName))); #endif } return nullptr; } if (entry->argType() != argType || entry->isList() != bool(multiplicity)) { if (showError == DoShowError) { #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) KMessageBox::error(q, i18n("Backend error: gpgconf has wrong type for %1/%2: %3 %4", QLatin1String(componentName), QLatin1String(entryName), entry->argType(), entry->isList())); #else KMessageBox::error(q->widget(), i18n("Backend error: gpgconf has wrong type for %1/%2: %3 %4", QLatin1String(componentName), QLatin1String(entryName), entry->argType(), entry->isList())); #endif } return nullptr; } return entry; } #if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0) DirectoryServicesConfigurationPage::DirectoryServicesConfigurationPage(QWidget *parent, const QVariantList &args) : KCModule{parent, args} #else DirectoryServicesConfigurationPage::DirectoryServicesConfigurationPage(QObject *parent, const KPluginMetaData &data, const QVariantList &args) : KCModule(parent, data, args) #endif , d{new Private{this}} { } DirectoryServicesConfigurationPage::~DirectoryServicesConfigurationPage() = default; void DirectoryServicesConfigurationPage::load() { d->load(); } void DirectoryServicesConfigurationPage::save() { d->save(); } void DirectoryServicesConfigurationPage::defaults() { d->defaults(); } diff --git a/src/kcfg/settings.kcfg b/src/kcfg/settings.kcfg index 3192b1a40..ca4712fe3 100644 --- a/src/kcfg/settings.kcfg +++ b/src/kcfg/settings.kcfg @@ -1,198 +1,205 @@ This text will be used as placeholder text for the common name (CN) field of S/MIME certificates. If true, then the common name (CN) field of S/MIME certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true This text will be used as placeholder text for the email address field of OpenPGP and S/MIME certificates. If true, then the email address field of OpenPGP and S/MIME certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true This text will be used as placeholder text for the name field of OpenPGP certificates. If true, then the name field of OpenPGP certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true Specifies the default validity period of new OpenPGP keys in days. This setting specifies how many days a new OpenPGP key is valid by default, or, in other words, after how many days the key will expire. Set this to 0 for unlimited validity. If this setting is not set or if it is set to a negative value, then new OpenPGP keys will be valid for three years (possibly clamped to the allowed minimum or maximum validity period) by default. -1 Specifies the minimum validity period of new OpenPGP keys in days. This setting specifies how many days a new OpenPGP key is valid at least, or, in other words, after how many days the new key will expire at the earliest. 1 Specifies the maximum validity period of new OpenPGP keys in days. This setting specifies how many days a new OpenPGP key is valid at most, or, in other words, after how many days the new key will expire at the latest. If this setting is not set or if it is set to a negative value, then unlimited validity is allowed. -1 If true, hides the advanced settings button in the new certificate wizard. false Specifies the default validity of certifications in days. This setting specifies how many days a certification is valid by default, or, in other words, after how many days a new certification will expire. Set this to 0 for unlimited validity of certifications. 0 sha256sum If true, then the results are shown after successfully signing the clipboard. true If true, then the results are shown after successfully encrypting the clipboard. true Enables support for S/MIME (CMS). If false, then Kleopatra's main UI will not offer any functionality related to S/MIME (CMS). true Allows the creation of S/MIME certificate signing requests. If false, then Kleopatra will not offer the creation of S/MIME certificate signing requests. true Allows signing of text or files with S/MIME certificates. If false, then Kleopatra will not offer functionality for creating signatures with S/MIME certificates. true true true true true true true Specifies the display order of the DN attributes of X.509 certificates. Enable usage of groups of keys. Enable usage of groups of keys to create lists of recipients. true If enabled, then Kleopatra will automatically try to retrieve the keys that were used to certify the user ids of newly imported OpenPGP keys. This is useful in combination with trusted introducers. false + + + By default, Kleopatra only queries the certificate directories of providers (WKD) + for user IDs that were originally retrieved from a WKD when you update an OpenPGP certificate. + If this option is enabled, then Kleopatra will query WKDs for all user IDs. + false + If enabled, then Kleopatra will show notifications in some place when using certificates that are about to expire soon. true This is a list of URL schemes that shall be blocked by the application. This can be used to prevent the application from opening external applications for certain URLs. Searches for the certificates belonging the smartcard keys on the configured keyserver. Searches on keyservers regardless of the protocol for the smartcards key, regardless of the keyserver protocol. Default behavior is to only do this for LDAP keyservers. false Automatically load S/MIME certificates from PKCS#15 (CardOS) smartcards If true, then Kleopatra will call gpgsm --learn if a PKCS#15 Smartcard is inserted with unknown certificates. This can take a while and blocks the smartcard while the command is running. true false