diff --git a/CMakeLists.txt b/CMakeLists.txt index 02c8f34cc..fecdd3400 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,235 +1,235 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16 FATAL_ERROR) set(RELEASE_SERVICE_VERSION_MAJOR "22") set(RELEASE_SERVICE_VERSION_MINOR "11") set(RELEASE_SERVICE_VERSION_MICRO "70") # The RELEASE_SERVICE_VERSION is used by Gpg4win to add the Gpg4win version if (NOT RELEASE_SERVICE_VERSION) set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") endif() if(RELEASE_SERVICE_VERSION_MICRO LESS 10) set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}0${RELEASE_SERVICE_VERSION_MICRO}") else() set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}${RELEASE_SERVICE_VERSION_MICRO}") endif() set(KLEOPATRA_VERSION_MAJOR "3") set(KLEOPATRA_VERSION_MINOR "1") set(KLEOPATRA_VERSION_MICRO "22") set(kleopatra_version "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}.${KDE_APPLICATIONS_COMPACT_VERSION}") # The following is for Windows set(kleopatra_version_win "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}") set(kleopatra_fileversion_win "${KLEOPATRA_VERSION_MAJOR},${KLEOPATRA_VERSION_MINOR},${KLEOPATRA_VERSION_MICRO},0") project(kleopatra VERSION ${kleopatra_version}) option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF) # Standalone build. Find / include everything necessary. set(KF5_MIN_VERSION "5.96.0") set(KIDENTITYMANAGEMENT_VERSION "5.20.40") set(KMAILTRANSPORT_VERSION "5.20.40") set(KMIME_VERSION "5.20.40") -set(LIBKLEO_VERSION "5.20.47") +set(LIBKLEO_VERSION "5.21.41") set(QT_REQUIRED_VERSION "5.15.2") set(GPGME_REQUIRED_VERSION "1.16.0") if (WIN32) set(KF5_WANT_VERSION "5.70.0") set(KMIME_WANT_VERSION "5.12.0") else () set(KF5_WANT_VERSION ${KF5_MIN_VERSION}) set(KMIME_WANT_VERSION ${KMIME_VERSION}) endif () set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(ECM ${KF5_WANT_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMAddTests) include(GenerateExportHeader) include(ECMGenerateHeaders) include(FeatureSummary) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMAddAppIcon) include(ECMQtDeclareLoggingCategory) include(ECMDeprecationSettings) include(KDEClangFormat) # Find KF5 packages find_package(KF5WidgetsAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5DocTools ${KF5_WANT_VERSION} CONFIG) find_package(KF5Crash ${KF5_WANT_VERSION} REQUIRED) set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Documentation tools" PURPOSE "Required to generate Kleopatra documentation." TYPE OPTIONAL) # Optional packages if (WIN32) # Only a replacement available for Windows so this # is required on other platforms. find_package(KF5DBusAddons ${KF5_WANT_VERSION} CONFIG) set_package_properties(KF5DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus" PURPOSE "DBus session integration" URL "https://inqlude.org/libraries/kdbusaddons.html" TYPE OPTIONAL) else() find_package(KF5DBusAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) set(_kleopatra_dbusaddons_libs KF5::DBusAddons) endif() set(HAVE_QDBUS ${Qt${QT_MAJOR_VERSION}DBus_FOUND}) find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) if (QT_MAJOR_VERSION STREQUAL "6") find_package(QGpgmeQt6 ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) else() find_package(QGpgme ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) endif() if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.17.0") set(QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY 1) set(QGPGME_CRYPTOCONFIGENTRY_HAS_DEFAULT_VALUE 1) set(QGPGME_SUPPORTS_WKDLOOKUP 1) set(QGPGME_SUPPORTS_IMPORT_WITH_FILTER 1) set(QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN 1) set(QGPGME_SUPPORTS_SECRET_KEY_EXPORT 1) set(QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 1) set(QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID 1) endif() if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.17.2") set(QGPGME_SUPPORTS_KEY_REVOCATION 1) set(QGPGME_SUPPORTS_KEY_REFRESH 1) set(QGPGME_SUPPORTS_SET_FILENAME 1) endif() # Kdepimlibs packages find_package(KF5Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED) find_package(KF5IdentityManagement ${KIDENTITYMANAGEMENT_VERSION} CONFIG) find_package(KF5MailTransport ${KMAILTRANSPORT_VERSION} CONFIG) find_package(KF5MailTransportAkonadi ${KMAILTRANSPORT_VERSION} CONFIG) find_package(Qt${QT_MAJOR_VERSION} ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport) find_package(Assuan2 REQUIRED) set(kleopatra_release FALSE) if(NOT kleopatra_release) find_package(Git) if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} RESULT_VARIABLE rc ERROR_QUIET) if(rc EQUAL 0) execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%h ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE Kleopatra_WC_REVISION) string(REGEX REPLACE "\n" "" Kleopatra_WC_REVISION "${Kleopatra_WC_REVISION}") execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%cI ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE Kleopatra_WC_LAST_CHANGED_DATE) string(REGEX REPLACE "^([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+):([0-9]+).*$" "\\1\\2\\3T\\4\\5\\6" Kleopatra_WC_LAST_CHANGED_DATE "${Kleopatra_WC_LAST_CHANGED_DATE}") set(kleopatra_version "${kleopatra_version}+git${Kleopatra_WC_LAST_CHANGED_DATE}~${Kleopatra_WC_REVISION}") endif() endif() endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version-kleopatra.h) include (ConfigureChecks.cmake) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kleopatra.h) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${ASSUAN2_INCLUDES} ) add_definitions(-D_ASSUAN_ONLY_GPG_ERRORS) if (WIN32) # On Windows, we need to use stuff deprecated since Qt 5.11, e.g. from QDesktopWidget ecm_set_disabled_deprecation_versions(QT 5.10.0 KF 5.96.0) else () ecm_set_disabled_deprecation_versions(QT 5.14.0 KF 5.96.0) endif () if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces -Wno-parentheses -Wno-ignored-qualifiers") endif() add_definitions(-DQT_NO_EMIT) remove_definitions(-DQT_NO_FOREACH) # Disable the use of QStringBuilder for operator+ to prevent crashes when # returning the result of concatenating string temporaries in lambdas. We do # this for example in some std::transform expressions. # This is a known issue: https://bugreports.qt.io/browse/QTBUG-47066 # Alternatively, one would always have to remember to force the lambdas to # return a QString instead of QStringBuilder, but that's just too easy to # forget and, unfortunately, the compiler doesn't issue a warning if one forgets # this. So, it's just too dangerous. # One can still use QStringBuilder explicitly with the operator% if necessary. remove_definitions(-DQT_USE_FAST_OPERATOR_PLUS) remove_definitions(-DQT_USE_QSTRINGBUILDER) kde_enable_exceptions() option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF) set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF) if (USE_UNITY_CMAKE_SUPPORT) set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON) endif() add_subdirectory(pics) add_subdirectory(src) if(BUILD_TESTING) add_subdirectory(tests) add_subdirectory(autotests) endif() ecm_qt_install_logging_categories( EXPORT KLEOPATRA FILE kleopatra.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) ki18n_install(po) if(KF5DocTools_FOUND) kdoctools_install(po) add_subdirectory(doc) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) diff --git a/src/crypto/decryptverifytask.cpp b/src/crypto/decryptverifytask.cpp index 9fb2a8720..4f2c4856a 100644 --- a/src/crypto/decryptverifytask.cpp +++ b/src/crypto/decryptverifytask.cpp @@ -1,1621 +1,1622 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifytask.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "decryptverifytask.h" #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include // Qt::escape #include #include using namespace Kleo::Crypto; using namespace Kleo; using namespace GpgME; using namespace KMime::Types; namespace { static AuditLog auditLogFromSender(QObject *sender) { return AuditLog::fromJob(qobject_cast(sender)); } static bool addrspec_equal(const AddrSpec &lhs, const AddrSpec &rhs, Qt::CaseSensitivity cs) { return lhs.localPart.compare(rhs.localPart, cs) == 0 && lhs.domain.compare(rhs.domain, Qt::CaseInsensitive) == 0; } static bool mailbox_equal(const Mailbox &lhs, const Mailbox &rhs, Qt::CaseSensitivity cs) { return addrspec_equal(lhs.addrSpec(), rhs.addrSpec(), cs); } static std::string stripAngleBrackets(const std::string &str) { if (str.empty()) { return str; } if (str[0] == '<' && str[str.size() - 1] == '>') { return str.substr(1, str.size() - 2); } return str; } static std::string email(const UserID &uid) { if (uid.parent().protocol() == OpenPGP) { if (const char *const email = uid.email()) { return stripAngleBrackets(email); } else { return std::string(); } } Q_ASSERT(uid.parent().protocol() == CMS); if (const char *const id = uid.id()) if (*id == '<') { return stripAngleBrackets(id); } else { return DN(id)[QStringLiteral("EMAIL")].trimmed().toUtf8().constData(); } else { return std::string(); } } static Mailbox mailbox(const UserID &uid) { const std::string e = email(uid); Mailbox mbox; if (!e.empty()) { mbox.setAddress(e.c_str()); } return mbox; } static std::vector extractMailboxes(const Key &key) { std::vector res; const auto userIDs{key.userIDs()}; for (const UserID &id : userIDs) { const Mailbox mbox = mailbox(id); if (!mbox.addrSpec().isEmpty()) { res.push_back(mbox); } } return res; } static std::vector extractMailboxes(const std::vector &signers) { std::vector res; for (const Key &i : signers) { const std::vector bxs = extractMailboxes(i); res.insert(res.end(), bxs.begin(), bxs.end()); } return res; } static bool keyContainsMailbox(const Key &key, const Mailbox &mbox) { const std::vector mbxs = extractMailboxes(key); return std::find_if(mbxs.cbegin(), mbxs.cend(), [mbox](const Mailbox &m) { return mailbox_equal(mbox, m, Qt::CaseInsensitive); }) != mbxs.cend(); } static bool keysContainMailbox(const std::vector &keys, const Mailbox &mbox) { return std::find_if(keys.cbegin(), keys.cend(), [mbox](const Key &key) { return keyContainsMailbox(key, mbox); }) != keys.cend(); } static bool relevantInDecryptVerifyContext(const VerificationResult &r) { // for D/V operations, we ignore verification results which are not errors and contain // no signatures (which means that the data was just not signed) return (r.error() && r.error().code() != GPG_ERR_DECRYPT_FAILED) || r.numSignatures() > 0; } static QString signatureSummaryToString(int summary) { if (summary & Signature::None) { return i18n("Error: Signature not verified"); } else if (summary & Signature::Valid || summary & Signature::Green) { return i18n("Good signature"); } else if (summary & Signature::KeyRevoked) { return i18n("Signing certificate was revoked"); } else if (summary & Signature::KeyExpired) { return i18n("Signing certificate is expired"); } else if (summary & Signature::KeyMissing) { return i18n("Certificate is not available"); } else if (summary & Signature::SigExpired) { return i18n("Signature expired"); } else if (summary & Signature::CrlMissing) { return i18n("CRL missing"); } else if (summary & Signature::CrlTooOld) { return i18n("CRL too old"); } else if (summary & Signature::BadPolicy) { return i18n("Bad policy"); } else if (summary & Signature::SysError) { return i18n("System error"); //### retrieve system error details? } else if (summary & Signature::Red) { return i18n("Bad signature"); }return QString(); } static QString formatValidSignatureWithTrustLevel(const UserID &id) { if (id.isNull()) { return QString(); } switch (id.validity()) { case UserID::Marginal: return i18n("The signature is valid but the trust in the certificate's validity is only marginal."); case UserID::Full: return i18n("The signature is valid and the certificate's validity is fully trusted."); case UserID::Ultimate: return i18n("The signature is valid and the certificate's validity is ultimately trusted."); case UserID::Never: return i18n("The signature is valid but the certificate's validity is not trusted."); case UserID::Unknown: return i18n("The signature is valid but the certificate's validity is unknown."); case UserID::Undefined: default: return i18n("The signature is valid but the certificate's validity is undefined."); } } static QString renderKeyLink(const QString &fpr, const QString &text) { return QStringLiteral("%2").arg(fpr, text); } static QString renderKey(const Key &key) { if (key.isNull()) { return i18n("Unknown certificate"); } if (key.primaryFingerprint() && strlen(key.primaryFingerprint()) > 16 && key.numUserIDs()) { const QString text = QStringLiteral("%1 (%2)").arg(Formatting::prettyNameAndEMail(key).toHtmlEscaped()).arg( Formatting::prettyID(QString::fromLocal8Bit(key.primaryFingerprint()).right(16).toLatin1().constData())); return renderKeyLink(QLatin1String(key.primaryFingerprint()), text); } return renderKeyLink(QLatin1String(key.primaryFingerprint()), Formatting::prettyID(key.primaryFingerprint())); } static QString renderKeyEMailOnlyNameAsFallback(const Key &key) { if (key.isNull()) { return i18n("Unknown certificate"); } const QString email = Formatting::prettyEMail(key); const QString user = !email.isEmpty() ? email : Formatting::prettyName(key); return renderKeyLink(QLatin1String(key.primaryFingerprint()), user); } static QString formatDate(const QDateTime &dt) { return QLocale().toString(dt); } static QString formatSigningInformation(const Signature &sig) { if (sig.isNull()) { return QString(); } const QDateTime dt = sig.creationTime() != 0 ? QDateTime::fromSecsSinceEpoch(sig.creationTime()) : QDateTime(); QString text; Key key = sig.key(); if (dt.isValid()) { text = i18nc("1 is a date", "Signature created on %1", formatDate(dt)) + QStringLiteral("
"); } if (key.isNull()) { return text += i18n("With unavailable certificate:") + QStringLiteral("
ID: 0x%1").arg(QString::fromLatin1(sig.fingerprint()).toUpper()); } text += i18n("With certificate:") + QStringLiteral("
") + renderKey(key); - if (Kleo::gnupgIsDeVsCompliant()) { + if (DeVSCompliance::isCompliant()) { text += (QStringLiteral("
") + (sig.isDeVs() ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "The signature is %1", Formatting::deVsString()) + "The signature is %1", DeVSCompliance::name(true)) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "The signature is not %1.", Formatting::deVsString()))); + "The signature is not %1.", DeVSCompliance::name(true)))); } return text; } static QString strikeOut(const QString &str, bool strike) { return QString(strike ? QStringLiteral("%1") : QStringLiteral("%1")).arg(str.toHtmlEscaped()); } static QString formatInputOutputLabel(const QString &input, const QString &output, bool inputDeleted, bool outputDeleted) { if (output.isEmpty()) { return strikeOut(input, inputDeleted); } return i18nc("Input file --> Output file (rarr is arrow", "%1 → %2", strikeOut(input, inputDeleted), strikeOut(output, outputDeleted)); } static bool IsErrorOrCanceled(const GpgME::Error &err) { return err || err.isCanceled(); } static bool IsErrorOrCanceled(const Result &res) { return IsErrorOrCanceled(res.error()); } static bool IsBad(const Signature &sig) { return sig.summary() & Signature::Red; } static bool IsGoodOrValid(const Signature &sig) { return (sig.summary() & Signature::Valid) || (sig.summary() & Signature::Green); } static UserID findUserIDByMailbox(const Key &key, const Mailbox &mbox) { const auto userIDs{key.userIDs()}; for (const UserID &id : userIDs) if (mailbox_equal(mailbox(id), mbox, Qt::CaseInsensitive)) { return id; } return UserID(); } static void updateKeys(const VerificationResult &result) { // This little hack works around the problem that GnuPG / GpgME does not // provide Key information in a verification result. The Key object is // a dummy just holding the KeyID. This hack ensures that all available // keys are fetched from the backend and are populated for (const auto &sig: result.signatures()) { // Update key information sig.key(true, true); } } } class DecryptVerifyResult::SenderInfo { public: explicit SenderInfo(const Mailbox &infSender, const std::vector &signers_) : informativeSender(infSender), signers(signers_) {} const Mailbox informativeSender; const std::vector signers; bool hasInformativeSender() const { return !informativeSender.addrSpec().isEmpty(); } bool conflicts() const { return hasInformativeSender() && hasKeys() && !keysContainMailbox(signers, informativeSender); } bool hasKeys() const { return std::any_of(signers.cbegin(), signers.cend(), [](const Key &key) { return !key.isNull(); }); } std::vector signerMailboxes() const { return extractMailboxes(signers); } }; namespace { static Task::Result::VisualCode codeForVerificationResult(const VerificationResult &res) { if (res.isNull()) { return Task::Result::NeutralSuccess; } const std::vector sigs = res.signatures(); if (sigs.empty()) { return Task::Result::Warning; } if (std::find_if(sigs.begin(), sigs.end(), IsBad) != sigs.end()) { return Task::Result::Danger; } if ((size_t)std::count_if(sigs.begin(), sigs.end(), IsGoodOrValid) == sigs.size()) { return Task::Result::AllGood; } return Task::Result::Warning; } static QString formatVerificationResultOverview(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info) { if (res.isNull()) { return QString(); } const Error err = res.error(); if (err.isCanceled()) { return i18n("Verification canceled."); } else if (err) { return i18n("Verification failed: %1.", QString::fromLocal8Bit(err.asString()).toHtmlEscaped()); } const std::vector sigs = res.signatures(); if (sigs.empty()) { return i18n("No signatures found."); } const uint bad = std::count_if(sigs.cbegin(), sigs.cend(), IsBad); if (bad > 0) { return i18np("Invalid signature.", "%1 invalid signatures.", bad); } const uint warn = std::count_if(sigs.cbegin(), sigs.cend(), [](const Signature &sig) { return !IsGoodOrValid(sig); }); if (warn == sigs.size()) { return i18np("The data could not be verified.", "%1 signatures could not be verified.", warn); } //Good signature: QString text; if (sigs.size() == 1) { text = i18n("Valid signature by %1", renderKeyEMailOnlyNameAsFallback(sigs[0].key())); if (info.conflicts()) text += i18n("
Warning: The sender's mail address is not stored in the %1 used for signing.", renderKeyLink(QLatin1String(sigs[0].key().primaryFingerprint()), i18n("certificate"))); } else { text = i18np("Valid signature.", "%1 valid signatures.", sigs.size()); if (info.conflicts()) { text += i18n("
Warning: The sender's mail address is not stored in the certificates used for signing."); } } return text; } static QString formatDecryptionResultOverview(const DecryptionResult &result, const QString &errorString = QString()) { const Error err = result.error(); if (err.isCanceled()) { return i18n("Decryption canceled."); } else if (result.isLegacyCipherNoMDC()) { return i18n("Decryption failed: %1.", i18n("No integrity protection (MDC).")); } else if (!errorString.isEmpty()) { return i18n("Decryption failed: %1.", errorString.toHtmlEscaped()); } else if (err) { return i18n("Decryption failed: %1.", QString::fromLocal8Bit(err.asString()).toHtmlEscaped()); } return i18n("Decryption succeeded."); } static QString formatSignature(const Signature &sig, const DecryptVerifyResult::SenderInfo &info) { if (sig.isNull()) { return QString(); } const QString text = formatSigningInformation(sig) + QLatin1String("
"); const Key key = sig.key(); // Green if (sig.summary() & Signature::Valid) { const UserID id = findUserIDByMailbox(key, info.informativeSender); return text + formatValidSignatureWithTrustLevel(!id.isNull() ? id : key.userID(0)); } // Red if ((sig.summary() & Signature::Red)) { const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary())); if (sig.summary() & Signature::SysError) { return ret + QStringLiteral(" (%1)").arg(QString::fromLocal8Bit(sig.status().asString())); } return ret; } // Key missing if ((sig.summary() & Signature::KeyMissing)) { return text + i18n("You can search the certificate on a keyserver or import it from a file."); } // Yellow if ((sig.validity() & Signature::Validity::Undefined) || (sig.validity() & Signature::Validity::Unknown) || (sig.summary() == Signature::Summary::None)) { return text + (key.protocol() == OpenPGP ? i18n("The used key is not certified by you or any trusted person.") : i18n("The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown.")); } // Catch all fall through const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary())); if (sig.summary() & Signature::SysError) { return ret + QStringLiteral(" (%1)").arg(QString::fromLocal8Bit(sig.status().asString())); } return ret; } static QStringList format(const std::vector &mbxs) { QStringList res; std::transform(mbxs.cbegin(), mbxs.cend(), std::back_inserter(res), [](const Mailbox &mbox) { return mbox.prettyAddress(); }); return res; } static QString formatVerificationResultDetails(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info, const QString &errorString) { if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) { return i18n("Input error: %1", errorString); } const std::vector sigs = res.signatures(); QString details; for (const Signature &sig : sigs) { details += formatSignature(sig, info) + QLatin1Char('\n'); } details = details.trimmed(); details.replace(QLatin1Char('\n'), QStringLiteral("

")); if (info.conflicts()) { details += i18n("

The sender's address %1 is not stored in the certificate. Stored: %2

", info.informativeSender.prettyAddress(), format(info.signerMailboxes()).join(i18nc("separator for a list of e-mail addresses", ", "))); } return details; } static QString formatRecipientsDetails(const std::vector &knownRecipients, unsigned int numRecipients) { if (numRecipients == 0) { return {}; } if (knownRecipients.empty()) { return QLatin1String("") + i18np("One unknown recipient.", "%1 unknown recipients.", numRecipients) + QLatin1String(""); } QString details = i18np("Recipient:", "Recipients:", numRecipients); if (numRecipients == 1) { details += QLatin1Char(' ') + renderKey(knownRecipients.front()); } else { details += QLatin1String("
    "); for (const Key &key : knownRecipients) { details += QLatin1String("
  • ") + renderKey(key) + QLatin1String("
  • "); } if (knownRecipients.size() < numRecipients) { details += QLatin1String("
  • ") + i18np("One unknown recipient", "%1 unknown recipients", numRecipients - knownRecipients.size()) + QLatin1String("
  • "); } details += QLatin1String("
"); } return details; } static QString formatDecryptionResultDetails(const DecryptionResult &res, const std::vector &recipients, const QString &errorString, bool isSigned, const QPointer &task) { if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) { return i18n("Input error: %1", errorString); } if (res.isNull() || res.error() || res.error().isCanceled()) { return formatRecipientsDetails(recipients, res.numRecipients()); } QString details; - if (Kleo::gnupgIsDeVsCompliant()) { + if (DeVSCompliance::isCompliant()) { details += ((res.isDeVs() ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "The decryption is %1.", Formatting::deVsString()) + "The decryption is %1.", DeVSCompliance::name(true)) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "The decryption is not %1.", Formatting::deVsString())) + "The decryption is not %1.", DeVSCompliance::name(true))) + QStringLiteral("
")); } if (res.fileName()) { const auto decVerifyTask = qobject_cast (task.data()); if (decVerifyTask) { const auto embedFileName = QString::fromUtf8(res.fileName()).toHtmlEscaped(); if (embedFileName != decVerifyTask->outputLabel()) { details += i18n("Embedded file name: '%1'", embedFileName); details += QStringLiteral("
"); } } } if (!isSigned) { details += i18n("Note: You cannot be sure who encrypted this message as it is not signed.") + QLatin1String("
"); } if (res.isLegacyCipherNoMDC()) { details += i18nc("Integrity protection was missing because an old cipher was used.", "Hint: If this file was encrypted before the year 2003 it is " "likely that the file is legitimate. This is because back " "then integrity protection was not widely used.") + QStringLiteral("

") + i18nc("The user is offered to force decrypt a non integrity protected message. With the strong advice to re-encrypt it.", "If you are confident that the file was not manipulated you should re-encrypt it after you have forced the decryption.") + QStringLiteral("

"); } details += formatRecipientsDetails(recipients, res.numRecipients()); return details; } static QString formatDecryptVerifyResultOverview(const DecryptionResult &dr, const VerificationResult &vr, const DecryptVerifyResult::SenderInfo &info) { if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) { return formatDecryptionResultOverview(dr); } return formatVerificationResultOverview(vr, info); } static QString formatDecryptVerifyResultDetails(const DecryptionResult &dr, const VerificationResult &vr, const std::vector &recipients, const DecryptVerifyResult::SenderInfo &info, const QString &errorString, const QPointer &task) { const QString drDetails = formatDecryptionResultDetails(dr, recipients, errorString, relevantInDecryptVerifyContext(vr), task); if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) { return drDetails; } return drDetails + (drDetails.isEmpty() ? QString() : QStringLiteral("
")) + formatVerificationResultDetails(vr, info, errorString); } } // anon namespace class DecryptVerifyResult::Private { DecryptVerifyResult *const q; public: Private(DecryptVerifyOperation type, const VerificationResult &vr, const DecryptionResult &dr, const QByteArray &stuff, const QString &fileName, int errCode, const QString &errString, const QString &input, const QString &output, const AuditLog &auditLog, Task *parentTask, const Mailbox &informativeSender, DecryptVerifyResult *qq) : q(qq), m_type(type), m_verificationResult(vr), m_decryptionResult(dr), m_stuff(stuff), m_fileName(fileName), m_error(errCode), m_errorString(errString), m_inputLabel(input), m_outputLabel(output), m_auditLog(auditLog), m_parentTask(QPointer(parentTask)), m_informativeSender(informativeSender) { } QString label() const { return formatInputOutputLabel(m_inputLabel, m_outputLabel, false, q->hasError()); } DecryptVerifyResult::SenderInfo makeSenderInfo() const; bool isDecryptOnly() const { return m_type == Decrypt; } bool isVerifyOnly() const { return m_type == Verify; } bool isDecryptVerify() const { return m_type == DecryptVerify; } DecryptVerifyOperation m_type; VerificationResult m_verificationResult; DecryptionResult m_decryptionResult; QByteArray m_stuff; QString m_fileName; int m_error; QString m_errorString; QString m_inputLabel; QString m_outputLabel; const AuditLog m_auditLog; QPointer m_parentTask; const Mailbox m_informativeSender; }; DecryptVerifyResult::SenderInfo DecryptVerifyResult::Private::makeSenderInfo() const { return SenderInfo(m_informativeSender, KeyCache::instance()->findSigners(m_verificationResult)); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptResult(const DecryptionResult &dr, const QByteArray &plaintext, const AuditLog &auditLog) { return std::shared_ptr(new DecryptVerifyResult( Decrypt, VerificationResult(), dr, plaintext, {}, 0, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptResult(const GpgME::Error &err, const QString &what, const AuditLog &auditLog) { return std::shared_ptr(new DecryptVerifyResult( Decrypt, VerificationResult(), DecryptionResult(err), QByteArray(), {}, err.code(), what, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptVerifyResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plaintext, const QString &fileName, const AuditLog &auditLog) { int err = dr.error() ? dr.error().code() : vr.error().code(); return std::shared_ptr(new DecryptVerifyResult( DecryptVerify, vr, dr, plaintext, fileName, err, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptVerifyResult(const GpgME::Error &err, const QString &details, const AuditLog &auditLog) { return std::shared_ptr(new DecryptVerifyResult( DecryptVerify, VerificationResult(), DecryptionResult(err), QByteArray(), {}, err.code(), details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const VerificationResult &vr, const QByteArray &plaintext, const AuditLog &auditLog) { return std::shared_ptr(new DecryptVerifyResult( Verify, vr, DecryptionResult(), plaintext, {}, 0, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const GpgME::Error &err, const QString &details, const AuditLog &auditLog) { return std::shared_ptr(new DecryptVerifyResult( Verify, VerificationResult(err), DecryptionResult(), QByteArray(), {}, err.code(), details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyDetachedResult(const VerificationResult &vr, const AuditLog &auditLog) { return std::shared_ptr(new DecryptVerifyResult( Verify, vr, DecryptionResult(), QByteArray(), {}, 0, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyDetachedResult(const GpgME::Error &err, const QString &details, const AuditLog &auditLog) { return std::shared_ptr(new DecryptVerifyResult( Verify, VerificationResult(err), DecryptionResult(), QByteArray(), {}, err.code(), details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } DecryptVerifyResult::DecryptVerifyResult(DecryptVerifyOperation type, const VerificationResult &vr, const DecryptionResult &dr, const QByteArray &stuff, const QString &fileName, int errCode, const QString &errString, const QString &inputLabel, const QString &outputLabel, const AuditLog &auditLog, Task *parentTask, const Mailbox &informativeSender) : Task::Result(), d(new Private(type, vr, dr, stuff, fileName, errCode, errString, inputLabel, outputLabel, auditLog, parentTask, informativeSender, this)) { } QString DecryptVerifyResult::overview() const { QString ov; if (d->isDecryptOnly()) { ov += formatDecryptionResultOverview(d->m_decryptionResult); } else if (d->isVerifyOnly()) { ov += formatVerificationResultOverview(d->m_verificationResult, d->makeSenderInfo()); } else { ov += formatDecryptVerifyResultOverview(d->m_decryptionResult, d->m_verificationResult, d->makeSenderInfo()); } if (ov.size() + d->label().size() > 120) { // Avoid ugly breaks ov = QStringLiteral("
") + ov; } return i18nc("label: result example: foo.sig: Verification failed. ", "%1: %2", d->label(), ov); } QString DecryptVerifyResult::details() const { if (d->isDecryptOnly()) { return formatDecryptionResultDetails(d->m_decryptionResult, KeyCache::instance()->findRecipients(d->m_decryptionResult), errorString(), false, d->m_parentTask); } if (d->isVerifyOnly()) { return formatVerificationResultDetails(d->m_verificationResult, d->makeSenderInfo(), errorString()); } return formatDecryptVerifyResultDetails(d->m_decryptionResult, d->m_verificationResult, KeyCache::instance()->findRecipients( d->m_decryptionResult), d->makeSenderInfo(), errorString(), d->m_parentTask); } bool DecryptVerifyResult::hasError() const { return d->m_error != 0; } int DecryptVerifyResult::errorCode() const { return d->m_error; } QString DecryptVerifyResult::errorString() const { return d->m_errorString; } AuditLog DecryptVerifyResult::auditLog() const { return d->m_auditLog; } QPointer DecryptVerifyResult::parentTask() const { return d->m_parentTask; } Task::Result::VisualCode DecryptVerifyResult::code() const { if ((d->m_type == DecryptVerify || d->m_type == Verify) && relevantInDecryptVerifyContext(verificationResult())) { return codeForVerificationResult(verificationResult()); } return hasError() ? NeutralError : NeutralSuccess; } GpgME::VerificationResult DecryptVerifyResult::verificationResult() const { return d->m_verificationResult; } GpgME::DecryptionResult DecryptVerifyResult::decryptionResult() const { return d->m_decryptionResult; } QString DecryptVerifyResult::fileName() const { return d->m_fileName; } class AbstractDecryptVerifyTask::Private { public: Mailbox informativeSender; }; AbstractDecryptVerifyTask::AbstractDecryptVerifyTask(QObject *parent) : Task(parent), d(new Private) {} AbstractDecryptVerifyTask::~AbstractDecryptVerifyTask() {} Mailbox AbstractDecryptVerifyTask::informativeSender() const { return d->informativeSender; } void AbstractDecryptVerifyTask::setInformativeSender(const Mailbox &sender) { d->informativeSender = sender; } class DecryptVerifyTask::Private { DecryptVerifyTask *const q; public: explicit Private(DecryptVerifyTask *qq) : q(qq), m_backend(nullptr), m_protocol(UnknownProtocol), m_ignoreMDCError(false) {} void slotResult(const DecryptionResult &, const VerificationResult &, const QByteArray &); void registerJob(QGpgME::DecryptVerifyJob *job) { q->connect(job, SIGNAL(result(GpgME::DecryptionResult,GpgME::VerificationResult,QByteArray)), q, SLOT(slotResult(GpgME::DecryptionResult,GpgME::VerificationResult,QByteArray))); q->connect(job, SIGNAL(progress(QString,int,int)), q, SLOT(setProgress(QString,int,int))); } void emitResult(const std::shared_ptr &result); std::shared_ptr m_input; std::shared_ptr m_output; const QGpgME::Protocol *m_backend; Protocol m_protocol; bool m_ignoreMDCError; }; void DecryptVerifyTask::Private::emitResult(const std::shared_ptr &result) { q->emitResult(result); Q_EMIT q->decryptVerifyResult(result); } void DecryptVerifyTask::Private::slotResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plainText) { updateKeys(vr); { std::stringstream ss; ss << dr << '\n' << vr; qCDebug(KLEOPATRA_LOG) << ss.str().c_str(); } const AuditLog auditLog = auditLogFromSender(q->sender()); if (dr.error().code() || vr.error().code()) { m_output->cancel(); } else { try { kleo_assert(!dr.isNull() || !vr.isNull()); m_output->finalize(); } catch (const GpgME::Exception &e) { emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } catch (const std::exception &e) { emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog)); return; } catch (...) { emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog)); return; } } const int drErr = dr.error().code(); const QString errorString = m_output->errorString(); if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || m_output->failed()) { emitResult(q->fromDecryptResult(drErr ? dr.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog)); return; } emitResult(q->fromDecryptVerifyResult(dr, vr, plainText, m_output->fileName(), auditLog)); } DecryptVerifyTask::DecryptVerifyTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this)) { } DecryptVerifyTask::~DecryptVerifyTask() { } void DecryptVerifyTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void DecryptVerifyTask::setOutput(const std::shared_ptr &output) { d->m_output = output; kleo_assert(d->m_output && d->m_output->ioDevice()); } void DecryptVerifyTask::setProtocol(Protocol prot) { kleo_assert(prot != UnknownProtocol); d->m_protocol = prot; d->m_backend = prot == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(d->m_backend); } void DecryptVerifyTask::autodetectProtocolFromInput() { if (!d->m_input) { return; } const Protocol p = findProtocol(d->m_input->classification()); if (p == UnknownProtocol) { throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature/ciphertext - maybe it is neither ciphertext nor a signature?"), Exception::MessageOnly); } setProtocol(p); } QString DecryptVerifyTask::label() const { return i18n("Decrypting: %1...", d->m_input->label()); } unsigned long long DecryptVerifyTask::inputSize() const { return d->m_input ? d->m_input->size() : 0; } QString DecryptVerifyTask::inputLabel() const { return d->m_input ? d->m_input->label() : QString(); } QString DecryptVerifyTask::outputLabel() const { return d->m_output ? d->m_output->label() : QString(); } Protocol DecryptVerifyTask::protocol() const { return d->m_protocol; } void DecryptVerifyTask::cancel() { } static void ensureIOOpen(QIODevice *input, QIODevice *output) { if (input && !input->isOpen()) { input->open(QIODevice::ReadOnly); } if (output && !output->isOpen()) { output->open(QIODevice::WriteOnly); } } void DecryptVerifyTask::setIgnoreMDCError(bool value) { d->m_ignoreMDCError = value; } void DecryptVerifyTask::doStart() { kleo_assert(d->m_backend); try { QGpgME::DecryptVerifyJob *const job = d->m_backend->decryptVerifyJob(); if (d->m_ignoreMDCError) { qCDebug(KLEOPATRA_LOG) << "Modifying job to ignore MDC errors."; auto ctx = QGpgME::Job::context(job); if (!ctx) { qCWarning(KLEOPATRA_LOG) << "Failed to get context for job"; } else { const auto err = ctx->setFlag("ignore-mdc-error", "1"); if (err) { qCWarning(KLEOPATRA_LOG) << "Failed to set ignore mdc errors" << err.asString(); } } } kleo_assert(job); d->registerJob(job); ensureIOOpen(d->m_input->ioDevice().get(), d->m_output->ioDevice().get()); job->start(d->m_input->ioDevice(), d->m_output->ioDevice()); } catch (const GpgME::Exception &e) { d->emitResult(fromDecryptVerifyResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLog())); } catch (const std::exception &e) { d->emitResult(fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLog())); } catch (...) { d->emitResult(fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLog())); } } class DecryptTask::Private { DecryptTask *const q; public: explicit Private(DecryptTask *qq) : q(qq), m_backend(nullptr), m_protocol(UnknownProtocol) {} void slotResult(const DecryptionResult &, const QByteArray &); void registerJob(QGpgME::DecryptJob *job) { q->connect(job, SIGNAL(result(GpgME::DecryptionResult,QByteArray)), q, SLOT(slotResult(GpgME::DecryptionResult,QByteArray))); q->connect(job, SIGNAL(progress(QString,int,int)), q, SLOT(setProgress(QString,int,int))); } void emitResult(const std::shared_ptr &result); std::shared_ptr m_input; std::shared_ptr m_output; const QGpgME::Protocol *m_backend; Protocol m_protocol; }; void DecryptTask::Private::emitResult(const std::shared_ptr &result) { q->emitResult(result); Q_EMIT q->decryptVerifyResult(result); } void DecryptTask::Private::slotResult(const DecryptionResult &result, const QByteArray &plainText) { { std::stringstream ss; ss << result; qCDebug(KLEOPATRA_LOG) << ss.str().c_str(); } const AuditLog auditLog = auditLogFromSender(q->sender()); if (result.error().code()) { m_output->cancel(); } else { try { kleo_assert(!result.isNull()); m_output->finalize(); } catch (const GpgME::Exception &e) { emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } catch (const std::exception &e) { emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog)); return; } catch (...) { emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog)); return; } } const int drErr = result.error().code(); const QString errorString = m_output->errorString(); if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || m_output->failed()) { emitResult(q->fromDecryptResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog)); return; } emitResult(q->fromDecryptResult(result, plainText, auditLog)); } DecryptTask::DecryptTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this)) { } DecryptTask::~DecryptTask() { } void DecryptTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void DecryptTask::setOutput(const std::shared_ptr &output) { d->m_output = output; kleo_assert(d->m_output && d->m_output->ioDevice()); } void DecryptTask::setProtocol(Protocol prot) { kleo_assert(prot != UnknownProtocol); d->m_protocol = prot; d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(d->m_backend); } void DecryptTask::autodetectProtocolFromInput() { if (!d->m_input) { return; } const Protocol p = findProtocol(d->m_input->classification()); if (p == UnknownProtocol) { throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this was S/MIME- or OpenPGP-encrypted - maybe it is not ciphertext at all?"), Exception::MessageOnly); } setProtocol(p); } QString DecryptTask::label() const { return i18n("Decrypting: %1...", d->m_input->label()); } unsigned long long DecryptTask::inputSize() const { return d->m_input ? d->m_input->size() : 0; } QString DecryptTask::inputLabel() const { return d->m_input ? d->m_input->label() : QString(); } QString DecryptTask::outputLabel() const { return d->m_output ? d->m_output->label() : QString(); } Protocol DecryptTask::protocol() const { return d->m_protocol; } void DecryptTask::cancel() { } void DecryptTask::doStart() { kleo_assert(d->m_backend); try { QGpgME::DecryptJob *const job = d->m_backend->decryptJob(); kleo_assert(job); d->registerJob(job); ensureIOOpen(d->m_input->ioDevice().get(), d->m_output->ioDevice().get()); job->start(d->m_input->ioDevice(), d->m_output->ioDevice()); } catch (const GpgME::Exception &e) { d->emitResult(fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLog())); } catch (const std::exception &e) { d->emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLog())); } catch (...) { d->emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLog())); } } class VerifyOpaqueTask::Private { VerifyOpaqueTask *const q; public: explicit Private(VerifyOpaqueTask *qq) : q(qq), m_backend(nullptr), m_protocol(UnknownProtocol) {} void slotResult(const VerificationResult &, const QByteArray &); void registerJob(QGpgME::VerifyOpaqueJob *job) { q->connect(job, SIGNAL(result(GpgME::VerificationResult,QByteArray)), q, SLOT(slotResult(GpgME::VerificationResult,QByteArray))); q->connect(job, SIGNAL(progress(QString,int,int)), q, SLOT(setProgress(QString,int,int))); } void emitResult(const std::shared_ptr &result); std::shared_ptr m_input; std::shared_ptr m_output; const QGpgME::Protocol *m_backend; Protocol m_protocol; }; void VerifyOpaqueTask::Private::emitResult(const std::shared_ptr &result) { q->emitResult(result); Q_EMIT q->decryptVerifyResult(result); } void VerifyOpaqueTask::Private::slotResult(const VerificationResult &result, const QByteArray &plainText) { updateKeys(result); { std::stringstream ss; ss << result; qCDebug(KLEOPATRA_LOG) << ss.str().c_str(); } const AuditLog auditLog = auditLogFromSender(q->sender()); if (result.error().code()) { m_output->cancel(); } else { try { kleo_assert(!result.isNull()); m_output->finalize(); } catch (const GpgME::Exception &e) { emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } catch (const std::exception &e) { emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog)); return; } catch (...) { emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog)); return; } } const int drErr = result.error().code(); const QString errorString = m_output->errorString(); if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || m_output->failed()) { emitResult(q->fromDecryptResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog)); return; } emitResult(q->fromVerifyOpaqueResult(result, plainText, auditLog)); } VerifyOpaqueTask::VerifyOpaqueTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this)) { } VerifyOpaqueTask::~VerifyOpaqueTask() { } void VerifyOpaqueTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void VerifyOpaqueTask::setOutput(const std::shared_ptr &output) { d->m_output = output; kleo_assert(d->m_output && d->m_output->ioDevice()); } void VerifyOpaqueTask::setProtocol(Protocol prot) { kleo_assert(prot != UnknownProtocol); d->m_protocol = prot; d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(d->m_backend); } void VerifyOpaqueTask::autodetectProtocolFromInput() { if (!d->m_input) { return; } const Protocol p = findProtocol(d->m_input->classification()); if (p == UnknownProtocol) { throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"), Exception::MessageOnly); } setProtocol(p); } QString VerifyOpaqueTask::label() const { return i18n("Verifying: %1...", d->m_input->label()); } unsigned long long VerifyOpaqueTask::inputSize() const { return d->m_input ? d->m_input->size() : 0; } QString VerifyOpaqueTask::inputLabel() const { return d->m_input ? d->m_input->label() : QString(); } QString VerifyOpaqueTask::outputLabel() const { return d->m_output ? d->m_output->label() : QString(); } Protocol VerifyOpaqueTask::protocol() const { return d->m_protocol; } void VerifyOpaqueTask::cancel() { } void VerifyOpaqueTask::doStart() { kleo_assert(d->m_backend); try { QGpgME::VerifyOpaqueJob *const job = d->m_backend->verifyOpaqueJob(); kleo_assert(job); d->registerJob(job); ensureIOOpen(d->m_input->ioDevice().get(), d->m_output ? d->m_output->ioDevice().get() : nullptr); job->start(d->m_input->ioDevice(), d->m_output ? d->m_output->ioDevice() : std::shared_ptr()); } catch (const GpgME::Exception &e) { d->emitResult(fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLog())); } catch (const std::exception &e) { d->emitResult(fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLog())); } catch (...) { d->emitResult(fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLog())); } } class VerifyDetachedTask::Private { VerifyDetachedTask *const q; public: explicit Private(VerifyDetachedTask *qq) : q(qq), m_backend(nullptr), m_protocol(UnknownProtocol) {} void slotResult(const VerificationResult &); void registerJob(QGpgME::VerifyDetachedJob *job) { q->connect(job, SIGNAL(result(GpgME::VerificationResult)), q, SLOT(slotResult(GpgME::VerificationResult))); q->connect(job, SIGNAL(progress(QString,int,int)), q, SLOT(setProgress(QString,int,int))); } void emitResult(const std::shared_ptr &result); std::shared_ptr m_input, m_signedData; const QGpgME::Protocol *m_backend; Protocol m_protocol; }; void VerifyDetachedTask::Private::emitResult(const std::shared_ptr &result) { q->emitResult(result); Q_EMIT q->decryptVerifyResult(result); } void VerifyDetachedTask::Private::slotResult(const VerificationResult &result) { updateKeys(result); { std::stringstream ss; ss << result; qCDebug(KLEOPATRA_LOG) << ss.str().c_str(); } const AuditLog auditLog = auditLogFromSender(q->sender()); try { kleo_assert(!result.isNull()); emitResult(q->fromVerifyDetachedResult(result, auditLog)); } catch (const GpgME::Exception &e) { emitResult(q->fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); } catch (const std::exception &e) { emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog)); } catch (...) { emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog)); } } VerifyDetachedTask::VerifyDetachedTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this)) { } VerifyDetachedTask::~VerifyDetachedTask() { } void VerifyDetachedTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void VerifyDetachedTask::setSignedData(const std::shared_ptr &signedData) { d->m_signedData = signedData; kleo_assert(d->m_signedData && d->m_signedData->ioDevice()); } void VerifyDetachedTask::setProtocol(Protocol prot) { kleo_assert(prot != UnknownProtocol); d->m_protocol = prot; d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(d->m_backend); } void VerifyDetachedTask::autodetectProtocolFromInput() { if (!d->m_input) { return; } const Protocol p = findProtocol(d->m_input->classification()); if (p == UnknownProtocol) { throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"), Exception::MessageOnly); } setProtocol(p); } unsigned long long VerifyDetachedTask::inputSize() const { return d->m_signedData ? d->m_signedData->size() : 0; } QString VerifyDetachedTask::label() const { if (d->m_signedData) { return xi18nc("Verification of a detached signature in progress. The first file contains the data." "The second file is the signature file.", "Verifying: %1 with %2...", d->m_signedData->label(), d->m_input->label()); } return i18n("Verifying signature: %1...", d->m_input->label()); } QString VerifyDetachedTask::inputLabel() const { if (d->m_signedData && d->m_input) { return xi18nc("Verification of a detached signature summary. The first file contains the data." "The second file is signature.", "Verified %1 with %2", d->m_signedData->label(), d->m_input->label()); } return d->m_input ? d->m_input->label() : QString(); } QString VerifyDetachedTask::outputLabel() const { return QString(); } Protocol VerifyDetachedTask::protocol() const { return d->m_protocol; } void VerifyDetachedTask::cancel() { } void VerifyDetachedTask::doStart() { kleo_assert(d->m_backend); try { QGpgME::VerifyDetachedJob *const job = d->m_backend->verifyDetachedJob(); kleo_assert(job); d->registerJob(job); ensureIOOpen(d->m_input->ioDevice().get(), nullptr); ensureIOOpen(d->m_signedData->ioDevice().get(), nullptr); job->start(d->m_input->ioDevice(), d->m_signedData->ioDevice()); } catch (const GpgME::Exception &e) { d->emitResult(fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLog())); } catch (const std::exception &e) { d->emitResult(fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLog())); } catch (...) { d->emitResult(fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLog())); } } #include "moc_decryptverifytask.cpp" diff --git a/src/crypto/gui/signencryptemailconflictdialog.cpp b/src/crypto/gui/signencryptemailconflictdialog.cpp index 016bd8afd..a91a27e14 100644 --- a/src/crypto/gui/signencryptemailconflictdialog.cpp +++ b/src/crypto/gui/signencryptemailconflictdialog.cpp @@ -1,648 +1,649 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signencryptemailconflictdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptemailconflictdialog.h" #include #include #include "dialogs/certificateselectiondialog.h" #include "certificateselectionline.h" #include #include "utils/gui-helper.h" #include "utils/kleo_assert.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace Kleo::Dialogs; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) Q_DECLARE_METATYPE(GpgME::UserID) static CertificateSelectionDialog * create_certificate_selection_dialog(QWidget *parent, Protocol proto) { auto const dlg = new CertificateSelectionDialog(parent); dlg->setOptions(proto == OpenPGP ? CertificateSelectionDialog::OpenPGPFormat : proto == CMS ? CertificateSelectionDialog::CMSFormat : CertificateSelectionDialog::AnyFormat); return dlg; } static CertificateSelectionDialog * create_encryption_certificate_selection_dialog(QWidget *parent, Protocol proto, const QString &mailbox) { CertificateSelectionDialog *const dlg = create_certificate_selection_dialog(parent, proto); dlg->setCustomLabelText(i18n("Please select an encryption certificate for recipient \"%1\"", mailbox)); dlg->setOptions(CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::EncryptOnly | dlg->options()); return dlg; } static CertificateSelectionDialog * create_signing_certificate_selection_dialog(QWidget *parent, Protocol proto, const QString &mailbox) { CertificateSelectionDialog *const dlg = create_certificate_selection_dialog(parent, proto); dlg->setCustomLabelText(i18n("Please select a signing certificate for sender \"%1\"", mailbox)); dlg->setOptions(CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::SignOnly | CertificateSelectionDialog::SecretKeys | dlg->options()); return dlg; } static QString make_top_label_conflict_text(bool sign, bool enc) { return sign && enc ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for all recipients/senders of the message.\n" "Please select the correct certificates for each recipient:") : sign ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for the sender of the message.\n" "Please select the correct certificates for the sender:") : enc ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for all recipients of the message.\n" "Please select the correct certificates for each recipient:") : /* else */ (kleo_assert_fail(sign || enc), QString()); } static QString make_top_label_quickmode_text(bool sign, bool enc) { return enc ? i18n("Please verify that correct certificates have been selected for each recipient:") : sign ? i18n("Please verify that the correct certificate has been selected for the sender:") : /*else*/ (kleo_assert_fail(sign || enc), QString()); } class SignEncryptEMailConflictDialog::Private { friend class ::Kleo::Crypto::Gui::SignEncryptEMailConflictDialog; SignEncryptEMailConflictDialog *const q; public: explicit Private(SignEncryptEMailConflictDialog *qq) : q(qq), senders(), recipients(), sign(true), encrypt(true), presetProtocol(UnknownProtocol), ui(q) { } private: void updateTopLabelText() { ui.conflictTopLB.setText(make_top_label_conflict_text(sign, encrypt)); ui.quickModeTopLB.setText(make_top_label_quickmode_text(sign, encrypt)); } void showHideWidgets() { const Protocol proto = q->selectedProtocol(); const bool quickMode = q->isQuickMode(); const bool needProtocolSelection = presetProtocol == UnknownProtocol; const bool needShowAllRecipientsCB = quickMode ? false : needProtocolSelection ? needShowAllRecipients(OpenPGP) || needShowAllRecipients(CMS) : /* else */ needShowAllRecipients(proto) ; ui.showAllRecipientsCB.setVisible(needShowAllRecipientsCB); ui.pgpRB.setVisible(needProtocolSelection); ui.cmsRB.setVisible(needProtocolSelection); const bool showAll = !needShowAllRecipientsCB || ui.showAllRecipientsCB.isChecked(); bool first; first = true; for (const CertificateSelectionLine &line : std::as_const(ui.signers)) { line.showHide(proto, first, showAll, sign); } ui.selectSigningCertificatesGB.setVisible(sign && (showAll || !first)); first = true; for (const CertificateSelectionLine &line : std::as_const(ui.recipients)) { line.showHide(proto, first, showAll, encrypt); } ui.selectEncryptionCertificatesGB.setVisible(encrypt && (showAll || !first)); } bool needShowAllRecipients(Protocol proto) const { if (sign) { if (const unsigned int num = std::count_if(ui.signers.cbegin(), ui.signers.cend(), [proto](const CertificateSelectionLine &l) { return l.wasInitiallyAmbiguous(proto); })) { if (num != ui.signers.size()) { return true; } } } if (encrypt) { if (const unsigned int num = std::count_if(ui.recipients.cbegin(), ui.recipients.cend(), [proto](const CertificateSelectionLine &l) { return l.wasInitiallyAmbiguous(proto); })) { if (num != ui.recipients.size()) { return true; } } } return false; } void createSendersAndRecipients() { ui.clearSendersAndRecipients(); ui.addSelectSigningCertificatesGB(); for (const Sender &s : std::as_const(senders)) { addSigner(s); } ui.addSelectEncryptionCertificatesGB(); for (const Sender &s : std::as_const(senders)) { addRecipient(s); } for (const Recipient &r : std::as_const(recipients)) { addRecipient(r); } } void addSigner(const Sender &s) { ui.addSigner(s.mailbox().prettyAddress(), s.signingCertificateCandidates(OpenPGP), s.isSigningAmbiguous(OpenPGP), s.signingCertificateCandidates(CMS), s.isSigningAmbiguous(CMS), q); } void addRecipient(const Sender &s) { ui.addRecipient(s.mailbox().prettyAddress(), s.encryptToSelfCertificateCandidates(OpenPGP), s.isEncryptionAmbiguous(OpenPGP), s.encryptToSelfCertificateCandidates(CMS), s.isEncryptionAmbiguous(CMS), q); } void addRecipient(const Recipient &r) { ui.addRecipient(r.mailbox().prettyAddress(), r.encryptionCertificateCandidates(OpenPGP), r.isEncryptionAmbiguous(OpenPGP), r.encryptionCertificateCandidates(CMS), r.isEncryptionAmbiguous(CMS), q); } bool isComplete(Protocol proto) const; private: void updateComplianceStatus() { - if (!Kleo::gnupgIsDeVsCompliant()) { + if (!DeVSCompliance::isCompliant()) { return; } if (q->selectedProtocol() == UnknownProtocol || (q->resolvedSigningKeys().empty() && q->resolvedEncryptionKeys().empty())) { return; } // Handle compliance bool de_vs = true; for (const auto &key: q->resolvedSigningKeys()) { if (!key.isDeVs() || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } if (de_vs) { for (const auto &key: q->resolvedEncryptionKeys()) { if (!key.isDeVs() || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } } auto btn = ui.buttonBox.button(QDialogButtonBox::Ok); btn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); if (!SystemInfo::isHighContrastModeActive()) { btn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); } ui.complianceLB.setText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "%1 communication possible.", Formatting::deVsString()) + "%1 communication possible.", DeVSCompliance::name(true)) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "%1 communication not possible.", Formatting::deVsString())); + "%1 communication not possible.", DeVSCompliance::name(true))); ui.complianceLB.setVisible(true); } void updateDialogStatus() { ui.setOkButtonEnabled(q->isComplete()); updateComplianceStatus(); } void slotCompleteChanged() { updateDialogStatus(); } void slotShowAllRecipientsToggled(bool) { showHideWidgets(); } void slotProtocolChanged() { showHideWidgets(); updateDialogStatus(); } void slotCertificateSelectionDialogRequested() { const QObject *const s = q->sender(); const Protocol proto = q->selectedProtocol(); QPointer dlg; for (const CertificateSelectionLine &l : std::as_const(ui.signers)) if (s == l.toolButton()) { dlg = create_signing_certificate_selection_dialog(q, proto, l.mailboxText()); if (dlg->exec()) { l.addAndSelectCertificate(dlg->selectedCertificate()); } // ### switch to key.protocol(), in case proto == UnknownProtocol break; } for (const CertificateSelectionLine &l : std::as_const(ui.recipients)) if (s == l.toolButton()) { dlg = create_encryption_certificate_selection_dialog(q, proto, l.mailboxText()); if (dlg->exec()) { l.addAndSelectCertificate(dlg->selectedCertificate()); } // ### switch to key.protocol(), in case proto == UnknownProtocol break; } #ifndef Q_OS_WIN // This leads to a crash on Windows. We don't really // leak memory here anyway because the destruction of the // dialog happens when the parent (q) is destroyed anyway. delete dlg; #endif } private: std::vector senders; std::vector recipients; bool sign : 1; bool encrypt : 1; Protocol presetProtocol; private: struct Ui { QLabel conflictTopLB, quickModeTopLB; QCheckBox showAllRecipientsCB; QRadioButton pgpRB, cmsRB; QGroupBox selectSigningCertificatesGB; QGroupBox selectEncryptionCertificatesGB; QCheckBox quickModeCB; QDialogButtonBox buttonBox; QVBoxLayout vlay; QHBoxLayout hlay; QHBoxLayout hlay2; QGridLayout glay; std::vector signers, recipients; QLabel complianceLB; void setOkButtonEnabled(bool enable) { return buttonBox.button(QDialogButtonBox::Ok)->setEnabled(enable); } explicit Ui(SignEncryptEMailConflictDialog *q) : conflictTopLB(make_top_label_conflict_text(true, true), q), quickModeTopLB(make_top_label_quickmode_text(true, true), q), showAllRecipientsCB(i18n("Show all recipients"), q), pgpRB(i18n("OpenPGP"), q), cmsRB(i18n("S/MIME"), q), selectSigningCertificatesGB(i18n("Select Signing Certificate"), q), selectEncryptionCertificatesGB(i18n("Select Encryption Certificate"), q), quickModeCB(i18n("Only show this dialog in case of conflicts (experimental)"), q), buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, q), vlay(q), hlay(), glay(), signers(), recipients() { KDAB_SET_OBJECT_NAME(conflictTopLB); KDAB_SET_OBJECT_NAME(quickModeTopLB); KDAB_SET_OBJECT_NAME(showAllRecipientsCB); KDAB_SET_OBJECT_NAME(pgpRB); KDAB_SET_OBJECT_NAME(cmsRB); KDAB_SET_OBJECT_NAME(selectSigningCertificatesGB); KDAB_SET_OBJECT_NAME(selectEncryptionCertificatesGB); KDAB_SET_OBJECT_NAME(quickModeCB); KDAB_SET_OBJECT_NAME(buttonBox); KDAB_SET_OBJECT_NAME(hlay); KDAB_SET_OBJECT_NAME(glay); KDAB_SET_OBJECT_NAME(vlay); q->setWindowTitle(i18nc("@title:window", "Select Certificates for Message")); conflictTopLB.hide(); selectSigningCertificatesGB.setFlat(true); selectEncryptionCertificatesGB.setFlat(true); selectSigningCertificatesGB.setAlignment(Qt::AlignCenter); selectEncryptionCertificatesGB.setAlignment(Qt::AlignCenter); glay.setColumnStretch(2, 1); glay.setColumnStretch(3, 1); vlay.setSizeConstraint(QLayout::SetMinimumSize); vlay.addWidget(&conflictTopLB); vlay.addWidget(&quickModeTopLB); hlay.addWidget(&showAllRecipientsCB); hlay.addStretch(1); hlay.addWidget(&pgpRB); hlay.addWidget(&cmsRB); vlay.addLayout(&hlay); addSelectSigningCertificatesGB(); addSelectEncryptionCertificatesGB(); vlay.addLayout(&glay); vlay.addStretch(1); complianceLB.setVisible(false); hlay2.addStretch(1); hlay2.addWidget(&complianceLB, 0, Qt::AlignRight); hlay2.addWidget(&buttonBox, 0, Qt::AlignRight); vlay.addWidget(&quickModeCB, 0, Qt::AlignRight); vlay.addLayout(&hlay2); connect(&buttonBox, &QDialogButtonBox::accepted, q, &SignEncryptEMailConflictDialog::accept); connect(&buttonBox, &QDialogButtonBox::rejected, q, &SignEncryptEMailConflictDialog::reject); connect(&showAllRecipientsCB, SIGNAL(toggled(bool)), q, SLOT(slotShowAllRecipientsToggled(bool))); connect(&pgpRB, SIGNAL(toggled(bool)), q, SLOT(slotProtocolChanged())); connect(&cmsRB, SIGNAL(toggled(bool)), q, SLOT(slotProtocolChanged())); } void clearSendersAndRecipients() { std::vector sig, enc; sig.swap(signers); enc.swap(recipients); std::for_each(sig.begin(), sig.end(), std::mem_fn(&CertificateSelectionLine::kill)); std::for_each(enc.begin(), enc.end(), std::mem_fn(&CertificateSelectionLine::kill)); glay.removeWidget(&selectSigningCertificatesGB); glay.removeWidget(&selectEncryptionCertificatesGB); } void addSelectSigningCertificatesGB() { glay.addWidget(&selectSigningCertificatesGB, glay.rowCount(), 0, 1, CertificateSelectionLine::NumColumns); } void addSelectEncryptionCertificatesGB() { glay.addWidget(&selectEncryptionCertificatesGB, glay.rowCount(), 0, 1, CertificateSelectionLine::NumColumns); } void addSigner(const QString &mailbox, const std::vector &pgp, bool pgpAmbiguous, const std::vector &cms, bool cmsAmbiguous, QWidget *q) { CertificateSelectionLine line(i18n("From:"), mailbox, pgp, pgpAmbiguous, cms, cmsAmbiguous, q, glay); signers.push_back(line); } void addRecipient(const QString &mailbox, const std::vector &pgp, bool pgpAmbiguous, const std::vector &cms, bool cmsAmbiguous, QWidget *q) { CertificateSelectionLine line(i18n("To:"), mailbox, pgp, pgpAmbiguous, cms, cmsAmbiguous, q, glay); recipients.push_back(line); } } ui; }; SignEncryptEMailConflictDialog::SignEncryptEMailConflictDialog(QWidget *parent) : QDialog(parent), d(new Private(this)) { } SignEncryptEMailConflictDialog::~SignEncryptEMailConflictDialog() {} void SignEncryptEMailConflictDialog::setPresetProtocol(Protocol p) { if (p == d->presetProtocol) { return; } const QSignalBlocker pgpBlocker(d->ui.pgpRB); const QSignalBlocker cmsBlocker(d->ui.cmsRB); really_check(d->ui.pgpRB, p == OpenPGP); really_check(d->ui.cmsRB, p == CMS); d->presetProtocol = p; d->showHideWidgets(); d->updateDialogStatus(); } Protocol SignEncryptEMailConflictDialog::selectedProtocol() const { if (d->presetProtocol != UnknownProtocol) { return d->presetProtocol; } if (d->ui.pgpRB.isChecked()) { return OpenPGP; } if (d->ui.cmsRB.isChecked()) { return CMS; } return UnknownProtocol; } void SignEncryptEMailConflictDialog::setSubject(const QString &subject) { setWindowTitle(i18nc("@title:window", "Select Certificates for Message \"%1\"", subject)); } void SignEncryptEMailConflictDialog::setSign(bool sign) { if (sign == d->sign) { return; } d->sign = sign; d->updateTopLabelText(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setEncrypt(bool encrypt) { if (encrypt == d->encrypt) { return; } d->encrypt = encrypt; d->updateTopLabelText(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setSenders(const std::vector &senders) { if (senders == d->senders) { return; } d->senders = senders; d->createSendersAndRecipients(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setRecipients(const std::vector &recipients) { if (d->recipients == recipients) { return; } d->recipients = recipients; d->createSendersAndRecipients(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::pickProtocol() { if (selectedProtocol() != UnknownProtocol) { return; // already picked } const bool pgp = d->isComplete(OpenPGP); const bool cms = d->isComplete(CMS); if (pgp && !cms) { d->ui.pgpRB.setChecked(true); } else if (cms && !pgp) { d->ui.cmsRB.setChecked(true); } } bool SignEncryptEMailConflictDialog::isComplete() const { const Protocol proto = selectedProtocol(); return proto != UnknownProtocol && d->isComplete(proto); } bool SignEncryptEMailConflictDialog::Private::isComplete(Protocol proto) const { return (!sign || std::none_of(ui.signers.cbegin(), ui.signers.cend(), [proto](const CertificateSelectionLine &l) { return l.isStillAmbiguous(proto); })) && (!encrypt || std::none_of(ui.recipients.cbegin(), ui.recipients.cend(), [proto](const CertificateSelectionLine &l) { return l.isStillAmbiguous(proto); })); } static std::vector get_keys(const std::vector &lines, Protocol proto) { if (proto == UnknownProtocol) { return std::vector(); } Q_ASSERT(proto == OpenPGP || proto == CMS); std::vector keys; keys.reserve(lines.size()); std::transform(lines.cbegin(), lines.cend(), std::back_inserter(keys), [proto](const CertificateSelectionLine &l) { return l.key(proto); }); return keys; } std::vector SignEncryptEMailConflictDialog::resolvedSigningKeys() const { return d->sign ? get_keys(d->ui.signers, selectedProtocol()) : std::vector(); } std::vector SignEncryptEMailConflictDialog::resolvedEncryptionKeys() const { return d->encrypt ? get_keys(d->ui.recipients, selectedProtocol()) : std::vector(); } void SignEncryptEMailConflictDialog::setQuickMode(bool on) { d->ui.quickModeCB.setChecked(on); } bool SignEncryptEMailConflictDialog::isQuickMode() const { return d->ui.quickModeCB.isChecked(); } void SignEncryptEMailConflictDialog::setConflict(bool conflict) { d->ui.conflictTopLB.setVisible(conflict); d->ui.quickModeTopLB.setVisible(!conflict); } #include "moc_signencryptemailconflictdialog.cpp" diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp index 01d52aabe..a503604f6 100644 --- a/src/crypto/gui/signencryptfileswizard.cpp +++ b/src/crypto/gui/signencryptfileswizard.cpp @@ -1,692 +1,693 @@ /* crypto/gui/signencryptfileswizard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptfileswizard.h" #include "signencryptwidget.h" #include "newresultpage.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto::Gui; enum Page { SigEncPageId, ResultPageId, NumPages }; class FileNameRequesterWithIcon : public QWidget { Q_OBJECT public: explicit FileNameRequesterWithIcon(QDir::Filters filter, QWidget *parent = nullptr) : QWidget(parent) { auto layout = new QHBoxLayout{this}; layout->setContentsMargins(0, 0, 0, 0); mIconLabel = new QLabel{this}; mRequester = new FileNameRequester{filter, this}; mRequester->setExistingOnly(false); layout->addWidget(mIconLabel); layout->addWidget(mRequester); setFocusPolicy(mRequester->focusPolicy()); setFocusProxy(mRequester); connect(mRequester, &FileNameRequester::fileNameChanged, this, &FileNameRequesterWithIcon::fileNameChanged); } void setIcon(const QIcon &icon) { mIconLabel->setPixmap(icon.pixmap(32, 32)); } void setFileName(const QString &name) { mRequester->setFileName(name); } QString fileName() const { return mRequester->fileName(); } void setNameFilter(const QString &nameFilter) { mRequester->setNameFilter(nameFilter); } QString nameFilter() const { return mRequester->nameFilter(); } FileNameRequester *requester() { return mRequester; } Q_SIGNALS: void fileNameChanged(const QString &filename); protected: bool event(QEvent *e) override { if (e->type() == QEvent::ToolTipChange) { mRequester->setToolTip(toolTip()); } return QWidget::event(e); } private: QLabel *mIconLabel; FileNameRequester *mRequester; }; class SigEncPage: public QWizardPage { Q_OBJECT public: explicit SigEncPage(QWidget *parent = nullptr) : QWizardPage(parent), mParent((SignEncryptFilesWizard *) parent), mWidget(new SignEncryptWidget), mOutLayout(new QVBoxLayout), mOutputLabel{nullptr}, mArchive(false), mUseOutputDir(false), mSingleFile{true} { setTitle(i18nc("@title", "Sign / Encrypt Files")); auto vLay = new QVBoxLayout(this); vLay->setContentsMargins(0, 0, 0, 0); if (!Settings{}.cmsEnabled()) { mWidget->setProtocol(GpgME::OpenPGP); } mWidget->setSignAsText(i18nc("@option:check on SignEncryptPage", "&Sign as:")); mWidget->setEncryptForMeText(i18nc("@option:check on SignEncryptPage", "Encrypt for &me:")); mWidget->setEncryptForOthersText(i18nc("@option:check on SignEncryptPage", "Encrypt for &others:")); mWidget->setEncryptWithPasswordText(i18nc("@option:check on SignEncryptPage", "Encrypt with &password. Anyone you share the password with can read the data.")); vLay->addWidget(mWidget); connect(mWidget, &SignEncryptWidget::operationChanged, this, &SigEncPage::updateCommitButton); connect(mWidget, &SignEncryptWidget::keysChanged, this, &SigEncPage::updateFileWidgets); auto outputGrp = new QGroupBox(i18nc("@title:group", "Output")); outputGrp->setLayout(mOutLayout); mPlaceholderWidget = new QLabel(i18n("Please select an action.")); mOutLayout->addWidget(mPlaceholderWidget); mOutputLabel = new QLabel(i18nc("@label on SignEncryptPage", "Output &files/folder:")); mOutLayout->addWidget(mOutputLabel); createRequesters(mOutLayout); mUseOutputDirChk = new QCheckBox(i18nc("@option:check on SignEncryptPage", "Encrypt / Sign &each file separately.")); mUseOutputDirChk->setToolTip(i18nc("@info:tooltip", "Keep each file separate instead of creating an archive for all.")); mOutLayout->addWidget(mUseOutputDirChk); connect (mUseOutputDirChk, &QCheckBox::toggled, this, [this] (bool state) { mUseOutputDir = state; mArchive = !mUseOutputDir && !mSingleFile; updateFileWidgets(); }); vLay->addWidget(outputGrp); auto messageWidget = new KMessageWidget; messageWidget->setMessageType(KMessageWidget::Error); messageWidget->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this)); messageWidget->setText(i18n("Signing and encrypting files is not possible.")); messageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "You cannot use Kleopatra for signing or encrypting files " "because the GnuPG system used by Kleopatra is not %1.", - Formatting::deVsString())); + DeVSCompliance::name(true))); messageWidget->setCloseButtonVisible(false); - messageWidget->setVisible(Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()); + messageWidget->setVisible(DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()); vLay->addWidget(messageWidget); setMinimumHeight(300); } void setEncryptionPreset(bool value) { mWidget->setEncryptionChecked(value); } void setSigningPreset(bool value) { mWidget->setSigningChecked(value); } bool isComplete() const override { - if (Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()) { + if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) { return false; } return mWidget->isComplete(); } int nextId() const override { return ResultPageId; } void initializePage() override { setCommitPage(true); updateCommitButton(mWidget->currentOp()); } void setArchiveForced(bool archive) { mArchive = archive; setArchiveMutable(!archive); } void setArchiveMutable(bool archive) { mUseOutputDirChk->setVisible(archive); if (archive) { const KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); mUseOutputDirChk->setChecked(archCfg.readEntry("LastUseOutputDir", false)); } else { mUseOutputDirChk->setChecked(false); } } void setSingleFile(bool singleFile) { mSingleFile = singleFile; mArchive = !mUseOutputDir && !mSingleFile; } bool validatePage() override { - if (Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()) { + if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) { KMessageBox::sorry(topLevelWidget(), xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "Sorry! You cannot use Kleopatra for signing or encrypting files " "because the GnuPG system used by Kleopatra is not %1.", - Formatting::deVsString())); + DeVSCompliance::name(true))); return false; } bool sign = !mWidget->signKey().isNull(); bool encrypt = !mWidget->selfKey().isNull() || !mWidget->recipients().empty(); if (!mWidget->validate()) { return false; } mWidget->saveOwnKeys(); if (mUseOutputDirChk->isVisible()) { KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); archCfg.writeEntry("LastUseOutputDir", mUseOutputDir); } if (sign && !encrypt && mArchive) { return KMessageBox::warningContinueCancel(this, xi18nc("@info", "Archiving in combination with sign-only currently requires what are known as opaque signatures - " "unlike detached ones, these embed the content in the signature." "This format is rather unusual. You might want to archive the files separately, " "and then sign the archive as one file with Kleopatra." "Future versions of Kleopatra are expected to also support detached signatures in this case."), i18nc("@title:window", "Unusual Signature Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("signencryptfileswizard-archive+sign-only-warning")) == KMessageBox::Continue; } else if (sign && !encrypt) { return true; } if (!mWidget->selfKey().isNull() || mWidget->encryptSymmetric()) { return true; } const auto recipientKeys = recipients(); const bool hasSecret = std::any_of(std::begin(recipientKeys), std::end(recipientKeys), [](const auto &k) { return k.hasSecret(); }); if (!hasSecret) { if (KMessageBox::warningContinueCancel(this, xi18nc("@info", "None of the recipients you are encrypting to seems to be your own." "This means that you will not be able to decrypt the data anymore, once encrypted." "Do you want to continue, or cancel to change the recipient selection?"), i18nc("@title:window", "Encrypt-To-Self Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-encrypt-to-non-self"), KMessageBox::Notify | KMessageBox::Dangerous) == KMessageBox::Cancel) { return false; } } return true; } std::vector recipients() const { return mWidget->recipients(); } /* In the future we might find a usecase for multiple * signers */ std::vector signers() const { const Key k = mWidget->signKey(); if (!k.isNull()) { return {k}; } return {}; } private: struct RequesterInfo { SignEncryptFilesWizard::KindNames id; QString icon; QString toolTip; QString accessibleName; QString nameFilterBinary; QString nameFilterAscii; }; void createRequesters(QBoxLayout *lay) { static const std::array requestersInfo = {{ { SignEncryptFilesWizard::SignatureCMS, QStringLiteral("document-sign"), i18nc("@info:tooltip", "This is the filename of the S/MIME signature."), i18nc("Lineedit accessible name", "S/MIME signature file"), i18nc("Name filter binary", "S/MIME Signatures (*.p7s)"), i18nc("Name filter ASCII", "S/MIME Signatures (*.p7s *.pem)"), }, { SignEncryptFilesWizard::SignaturePGP, QStringLiteral("document-sign"), i18nc("@info:tooltip", "This is the filename of the detached OpenPGP signature."), i18nc("Lineedit accessible name", "OpenPGP signature file"), i18nc("Name filter binary", "OpenPGP Signatures (*.sig *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Signatures (*.asc *.sig)"), }, { SignEncryptFilesWizard::CombinedPGP, QStringLiteral("document-edit-sign-encrypt"), i18nc("@info:tooltip", "This is the filename of the OpenPGP-signed and encrypted file."), i18nc("Lineedit accessible name", "OpenPGP signed and encrypted file"), i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"), }, { SignEncryptFilesWizard::EncryptedPGP, QStringLiteral("document-encrypt"), i18nc("@info:tooltip", "This is the filename of the OpenPGP encrypted file."), i18nc("Lineedit accessible name", "OpenPGP encrypted file"), i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"), }, { SignEncryptFilesWizard::EncryptedCMS, QStringLiteral("document-encrypt"), i18nc("@info:tooltip", "This is the filename of the S/MIME encrypted file."), i18nc("Lineedit accessible name", "S/MIME encrypted file"), i18nc("Name filter binary", "S/MIME Files (*.p7m)"), i18nc("Name filter ASCII", "S/MIME Files (*.p7m *.pem)"), }, { SignEncryptFilesWizard::Directory, QStringLiteral("folder"), i18nc("@info:tooltip", "The resulting files are written to this directory."), i18nc("Lineedit accessible name", "Output directory"), {}, {}, }, }}; if (!mRequesters.empty()) { return; } const bool isAscii = FileOperationsPreferences().addASCIIArmor(); for (const auto &requester : requestersInfo) { const auto id = requester.id; auto requesterWithIcon = new FileNameRequesterWithIcon{id == SignEncryptFilesWizard::Directory ? QDir::Dirs : QDir::Files, this}; requesterWithIcon->setIcon(QIcon::fromTheme(requester.icon)); requesterWithIcon->setToolTip(requester.toolTip); requesterWithIcon->requester()->setAccessibleNameOfLineEdit(requester.accessibleName); requesterWithIcon->setNameFilter(isAscii ? requester.nameFilterAscii : requester.nameFilterBinary); lay->addWidget(requesterWithIcon); connect(requesterWithIcon, &FileNameRequesterWithIcon::fileNameChanged, this, [this, id](const QString &newName) { mOutNames[id] = newName; }); mRequesters.insert(id, requesterWithIcon); } } public: void setOutputNames(const QMap &names) { Q_ASSERT(mOutNames.isEmpty()); for (auto it = std::begin(names); it != std::end(names); ++it) { mRequesters.value(it.key())->setFileName(it.value()); } mOutNames = names; updateFileWidgets(); } QMap outputNames() const { if (!mUseOutputDir) { auto ret = mOutNames; ret.remove(SignEncryptFilesWizard::Directory); return ret; } return mOutNames; } bool encryptSymmetric() const { return mWidget->encryptSymmetric(); } private Q_SLOTS: void updateCommitButton(const QString &label) { if (mParent->currentPage() != this) { return; } auto btn = mParent->button(QWizard::CommitButton); if (!label.isEmpty()) { mParent->setButtonText(QWizard::CommitButton, label); - if (Kleo::gnupgUsesDeVsCompliance()) { - const bool de_vs = Kleo::gnupgIsDeVsCompliant() && mWidget->isDeVsAndValid(); + if (DeVSCompliance::isActive()) { + const bool de_vs = DeVSCompliance::isCompliant() && mWidget->isDeVsAndValid(); btn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); if (!SystemInfo::isHighContrastModeActive()) { btn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); } mParent->setLabelText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "%1 communication possible.", Formatting::deVsString()) + "%1 communication possible.", DeVSCompliance::name(true)) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "%1 communication not possible.", Formatting::deVsString())); + "%1 communication not possible.", DeVSCompliance::name(true))); } } else { mParent->setButtonText(QWizard::CommitButton, i18n("Next")); btn->setIcon(QIcon()); btn->setStyleSheet(QString()); } Q_EMIT completeChanged(); } void updateFileWidgets() { if (mRequesters.isEmpty()) { return; } const std::vector recipients = mWidget->recipients(); const Key sigKey = mWidget->signKey(); const bool pgp = mWidget->encryptSymmetric() || std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::OpenPGP; }); const bool cms = std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::CMS; }); mOutLayout->setEnabled(false); if (cms || pgp || !sigKey.isNull()) { mPlaceholderWidget->setVisible(false); mOutputLabel->setVisible(true); mRequesters[SignEncryptFilesWizard::SignatureCMS]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::CMS); mRequesters[SignEncryptFilesWizard::EncryptedCMS]->setVisible(!mUseOutputDir && cms); mRequesters[SignEncryptFilesWizard::CombinedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::EncryptedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() != Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::SignaturePGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && !pgp); mRequesters[SignEncryptFilesWizard::Directory]->setVisible(mUseOutputDir); auto firstNotHidden = std::find_if(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { return !w->isHidden(); }); mOutputLabel->setBuddy(*firstNotHidden); } else { mPlaceholderWidget->setVisible(true); mOutputLabel->setVisible(false); std::for_each(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { w->setVisible(false); }); mOutputLabel->setBuddy(nullptr); } mOutLayout->setEnabled(true); } private: SignEncryptFilesWizard *mParent; SignEncryptWidget *mWidget; QMap mOutNames; QMap mRequesters; QVBoxLayout *mOutLayout; QWidget *mPlaceholderWidget; QCheckBox *mUseOutputDirChk; QLabel *mOutputLabel; bool mArchive; bool mUseOutputDir; bool mSingleFile; }; class ResultPage : public NewResultPage { Q_OBJECT public: explicit ResultPage(QWidget *parent = nullptr) : NewResultPage(parent), mParent((SignEncryptFilesWizard *) parent) { setTitle(i18nc("@title", "Results")); setSubTitle(i18nc("@title", "Status and progress of the crypto operations is shown here.")); } void initializePage() override { mParent->setLabelText(QString()); } private: SignEncryptFilesWizard *mParent; }; SignEncryptFilesWizard::SignEncryptFilesWizard(QWidget *parent, Qt::WindowFlags f) : QWizard(parent, f) { readConfig(); - const bool de_vs = Kleo::gnupgUsesDeVsCompliance(); + const bool de_vs = DeVSCompliance::isActive(); #ifdef Q_OS_WIN // Enforce modern style to avoid vista style ugliness. setWizardStyle(QWizard::ModernStyle); #endif mSigEncPage = new SigEncPage(this); mResultPage = new ResultPage(this); connect(this, &QWizard::currentIdChanged, this, &SignEncryptFilesWizard::slotCurrentIdChanged); setPage(SigEncPageId, mSigEncPage); setPage(ResultPageId, mResultPage); setOptions(QWizard::IndependentPages | (de_vs ? QWizard::HaveCustomButton1 : QWizard::WizardOption(0)) | QWizard::NoBackButtonOnLastPage | QWizard::NoBackButtonOnStartPage); if (de_vs) { /* We use a custom button to display a label next to the buttons. */ auto btn = button(QWizard::CustomButton1); /* We style the button so that it looks and acts like a label. */ btn->setStyleSheet(QStringLiteral("border: none")); btn->setFocusPolicy(Qt::NoFocus); } } void SignEncryptFilesWizard::setLabelText(const QString &label) { button(QWizard::CommitButton)->setToolTip(label); setButtonText(QWizard::CustomButton1, label); } void SignEncryptFilesWizard::slotCurrentIdChanged(int id) { if (id == ResultPageId) { Q_EMIT operationPrepared(); } } SignEncryptFilesWizard::~SignEncryptFilesWizard() { qCDebug(KLEOPATRA_LOG); writeConfig(); } void SignEncryptFilesWizard::setSigningPreset(bool preset) { mSigEncPage->setSigningPreset(preset); } void SignEncryptFilesWizard::setSigningUserMutable(bool mut) { if (mut == mSigningUserMutable) { return; } mSigningUserMutable = mut; } void SignEncryptFilesWizard::setEncryptionPreset(bool preset) { mSigEncPage->setEncryptionPreset(preset); } void SignEncryptFilesWizard::setEncryptionUserMutable(bool mut) { if (mut == mEncryptionUserMutable) { return; } mEncryptionUserMutable = mut; } void SignEncryptFilesWizard::setArchiveForced(bool archive) { mSigEncPage->setArchiveForced(archive); } void SignEncryptFilesWizard::setArchiveMutable(bool archive) { mSigEncPage->setArchiveMutable(archive); } void SignEncryptFilesWizard::setSingleFile(bool singleFile) { mSigEncPage->setSingleFile(singleFile); } std::vector SignEncryptFilesWizard::resolvedRecipients() const { return mSigEncPage->recipients(); } std::vector SignEncryptFilesWizard::resolvedSigners() const { return mSigEncPage->signers(); } void SignEncryptFilesWizard::setTaskCollection(const std::shared_ptr &coll) { mResultPage->setTaskCollection(coll); } void SignEncryptFilesWizard::setOutputNames(const QMap &map) const { mSigEncPage->setOutputNames(map); } QMap SignEncryptFilesWizard::outputNames() const { return mSigEncPage->outputNames(); } bool SignEncryptFilesWizard::encryptSymmetric() const { return mSigEncPage->encryptSymmetric(); } void SignEncryptFilesWizard::readConfig() { winId(); // ensure there's a window created // set default window size windowHandle()->resize(640, 480); // restore size from config file KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); } void SignEncryptFilesWizard::writeConfig() { KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::saveWindowSize(windowHandle(), cfgGroup); cfgGroup.sync(); } #include "signencryptfileswizard.moc" diff --git a/src/crypto/newsignencryptemailcontroller.cpp b/src/crypto/newsignencryptemailcontroller.cpp index 712e85f1a..a20f13d05 100644 --- a/src/crypto/newsignencryptemailcontroller.cpp +++ b/src/crypto/newsignencryptemailcontroller.cpp @@ -1,633 +1,634 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/newsignencryptemailcontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009, 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "newsignencryptemailcontroller.h" #include "kleopatra_debug.h" #include "encryptemailtask.h" #include "signemailtask.h" #include "taskcollection.h" #include "sender.h" #include "recipient.h" #include "emailoperationspreferences.h" #include #include "utils/input.h" #include "utils/output.h" #include #include "utils/kleo_assert.h" +#include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace GpgME; using namespace KMime::Types; // // BEGIN Conflict Detection // /* This code implements the following conflict detection algorithm: 1. There is no conflict if and only if we have a Perfect Match. 2. A Perfect Match is defined as: a. either a Perfect OpenPGP-Match and not even a Partial S/MIME Match b. or a Perfect S/MIME-Match and not even a Partial OpenPGP-Match c. or a Perfect OpenPGP-Match and preselected protocol=OpenPGP d. or a Perfect S/MIME-Match and preselected protocol=S/MIME 3. For Protocol \in {OpenPGP,S/MIME}, a Perfect Protocol-Match is defined as: a. If signing, \foreach Sender, there is exactly one Matching Protocol-Certificate with i. can-sign=true ii. has-secret=true b. and, if encrypting, \foreach Recipient, there is exactly one Matching Protocol-Certificate with i. can-encrypt=true ii. (validity is not considered, cf. msg 24059) 4. For Protocol \in {OpenPGP,S/MIME}, a Partial Protocol-Match is defined as: a. If signing, \foreach Sender, there is at least one Matching Protocol-Certificate with i. can-sign=true ii. has-secret=true b. and, if encrypting, \foreach Recipient, there is at least one Matching Protocol-Certificate with i. can-encrypt=true ii. (validity is not considered, cf. msg 24059) 5. For Protocol \in {OpenPGP,S/MIME}, a Matching Protocol-Certificate is defined as matching by email-address. A revoked, disabled, or expired certificate is not considered a match. 6. Sender is defined as those mailboxes that have been set with the SENDER command. 7. Recipient is defined as those mailboxes that have been set with either the SENDER or the RECIPIENT commands. */ namespace { static size_t count_signing_certificates(Protocol proto, const Sender &sender) { const size_t result = sender.signingCertificateCandidates(proto).size(); qDebug("count_signing_certificates( %9s %20s ) == %2lu", proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : ",", qPrintable(sender.mailbox().prettyAddress()), result); return result; } static size_t count_encrypt_certificates(Protocol proto, const Sender &sender) { const size_t result = sender.encryptToSelfCertificateCandidates(proto).size(); qDebug("count_encrypt_certificates( %9s %20s ) == %2lu", proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : ",", qPrintable(sender.mailbox().prettyAddress()), result); return result; } static size_t count_encrypt_certificates(Protocol proto, const Recipient &recipient) { const size_t result = recipient.encryptionCertificateCandidates(proto).size(); qDebug("count_encrypt_certificates( %9s %20s ) == %2lu", proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : ",", qPrintable(recipient.mailbox().prettyAddress()), result); return result; } } static bool has_perfect_match(bool sign, bool encrypt, Protocol proto, const std::vector &senders, const std::vector &recipients) { if (sign) if (!std::all_of(senders.cbegin(), senders.cend(), [proto](const Sender &sender) { return count_signing_certificates(proto, sender) == 1; })) { return false; } if (encrypt) if (!std::all_of(senders.cbegin(), senders.cend(), [proto](const Sender &sender) { return count_encrypt_certificates(proto, sender) == 1; }) || !std::all_of(recipients.cbegin(), recipients.cend(), [proto](const Recipient &rec) { return count_encrypt_certificates(proto, rec) == 1; })) { return false; } return true; } static bool has_partial_match(bool sign, bool encrypt, Protocol proto, const std::vector &senders, const std::vector &recipients) { if (sign) if (std::all_of(senders.cbegin(), senders.cend(), [proto](const Sender &sender) { return count_signing_certificates(proto, sender) >= 1; })) { return false; } if (encrypt) if (!std::all_of(senders.cbegin(), senders.cend(), [proto](const Sender &sender) { return count_encrypt_certificates(proto, sender) >= 1; }) || !std::all_of(recipients.cbegin(), recipients.cend(), [proto](const Recipient &rec) { return count_encrypt_certificates(proto, rec) >= 1; })) { return false; } return true; } static bool has_perfect_overall_match(bool sign, bool encrypt, const std::vector &senders, const std::vector &recipients, Protocol presetProtocol) { return (presetProtocol == OpenPGP && has_perfect_match(sign, encrypt, OpenPGP, senders, recipients)) || (presetProtocol == CMS && has_perfect_match(sign, encrypt, CMS, senders, recipients)) || (has_perfect_match(sign, encrypt, OpenPGP, senders, recipients) && !has_partial_match(sign, encrypt, CMS, senders, recipients)) || (has_perfect_match(sign, encrypt, CMS, senders, recipients) && !has_partial_match(sign, encrypt, OpenPGP, senders, recipients)); } static bool has_conflict(bool sign, bool encrypt, const std::vector &senders, const std::vector &recipients, Protocol presetProtocol) { return !has_perfect_overall_match(sign, encrypt, senders, recipients, presetProtocol); } static bool is_de_vs_compliant(bool sign, bool encrypt, const std::vector &senders, const std::vector &recipients, Protocol presetProtocol) { if (presetProtocol == Protocol::UnknownProtocol) { return false; } if (sign) { for (const auto &sender: senders) { const auto &key = sender.resolvedSigningKey(presetProtocol); if (!key.isDeVs() || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } } if (encrypt) { for (const auto &sender: senders) { const auto &key = sender.resolvedSigningKey(presetProtocol); if (!key.isDeVs() || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } for (const auto &recipient: recipients) { const auto &key = recipient.resolvedEncryptionKey(presetProtocol); if (!key.isDeVs() || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } } return true; } // // END Conflict Detection // static std::vector mailbox2sender(const std::vector &mbs) { std::vector senders; senders.reserve(mbs.size()); for (const Mailbox &mb : mbs) { senders.push_back(Sender(mb)); } return senders; } static std::vector mailbox2recipient(const std::vector &mbs) { std::vector recipients; recipients.reserve(mbs.size()); for (const Mailbox &mb : mbs) { recipients.push_back(Recipient(mb)); } return recipients; } class NewSignEncryptEMailController::Private { friend class ::Kleo::Crypto::NewSignEncryptEMailController; NewSignEncryptEMailController *const q; public: explicit Private(NewSignEncryptEMailController *qq); ~Private(); private: void slotDialogAccepted(); void slotDialogRejected(); private: void ensureDialogVisible(); void cancelAllTasks(); void startSigning(); void startEncryption(); void schedule(); std::shared_ptr takeRunnable(GpgME::Protocol proto); private: bool sign : 1; bool encrypt : 1; bool resolvingInProgress : 1; bool certificatesResolved : 1; bool detached : 1; Protocol presetProtocol; std::vector signers, recipients; std::vector< std::shared_ptr > runnable, completed; std::shared_ptr cms, openpgp; QPointer dialog; }; NewSignEncryptEMailController::Private::Private(NewSignEncryptEMailController *qq) : q(qq), sign(false), encrypt(false), resolvingInProgress(false), certificatesResolved(false), detached(false), presetProtocol(UnknownProtocol), signers(), recipients(), runnable(), cms(), openpgp(), dialog(new SignEncryptEMailConflictDialog) { connect(dialog, SIGNAL(accepted()), q, SLOT(slotDialogAccepted())); connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected())); } NewSignEncryptEMailController::Private::~Private() { delete dialog; } NewSignEncryptEMailController::NewSignEncryptEMailController(const std::shared_ptr &xc, QObject *p) : Controller(xc, p), d(new Private(this)) { } NewSignEncryptEMailController::NewSignEncryptEMailController(QObject *p) : Controller(p), d(new Private(this)) { } NewSignEncryptEMailController::~NewSignEncryptEMailController() { qCDebug(KLEOPATRA_LOG); } void NewSignEncryptEMailController::setSubject(const QString &subject) { d->dialog->setSubject(subject); } void NewSignEncryptEMailController::setProtocol(Protocol proto) { d->presetProtocol = proto; d->dialog->setPresetProtocol(proto); } Protocol NewSignEncryptEMailController::protocol() const { return d->dialog->selectedProtocol(); } const char *NewSignEncryptEMailController::protocolAsString() const { switch (protocol()) { case OpenPGP: return "OpenPGP"; case CMS: return "CMS"; default: throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL), i18n("Call to NewSignEncryptEMailController::protocolAsString() is ambiguous.")); } } void NewSignEncryptEMailController::setSigning(bool sign) { d->sign = sign; d->dialog->setSign(sign); } bool NewSignEncryptEMailController::isSigning() const { return d->sign; } void NewSignEncryptEMailController::setEncrypting(bool encrypt) { d->encrypt = encrypt; d->dialog->setEncrypt(encrypt); } bool NewSignEncryptEMailController::isEncrypting() const { return d->encrypt; } void NewSignEncryptEMailController::setDetachedSignature(bool detached) { d->detached = detached; } bool NewSignEncryptEMailController::isResolvingInProgress() const { return d->resolvingInProgress; } bool NewSignEncryptEMailController::areCertificatesResolved() const { return d->certificatesResolved; } static bool is_dialog_quick_mode(bool sign, bool encrypt) { const EMailOperationsPreferences prefs; return (!sign || prefs.quickSignEMail()) && (!encrypt || prefs.quickEncryptEMail()) ; } static void save_dialog_quick_mode(bool on) { EMailOperationsPreferences prefs; prefs.setQuickSignEMail(on); prefs.setQuickEncryptEMail(on); prefs.save(); } void NewSignEncryptEMailController::startResolveCertificates(const std::vector &r, const std::vector &s) { d->certificatesResolved = false; d->resolvingInProgress = true; const std::vector senders = mailbox2sender(s); const std::vector recipients = mailbox2recipient(r); const bool quickMode = is_dialog_quick_mode(d->sign, d->encrypt); const bool conflict = quickMode && has_conflict(d->sign, d->encrypt, senders, recipients, d->presetProtocol); d->dialog->setQuickMode(quickMode); d->dialog->setSenders(senders); d->dialog->setRecipients(recipients); d->dialog->pickProtocol(); d->dialog->setConflict(conflict); - const bool compliant = !Kleo::gnupgUsesDeVsCompliance() || - (Kleo::gnupgIsDeVsCompliant() && is_de_vs_compliant(d->sign, - d->encrypt, - senders, - recipients, - d->presetProtocol)); + const bool compliant = !DeVSCompliance::isActive() || + (DeVSCompliance::isCompliant() && is_de_vs_compliant(d->sign, + d->encrypt, + senders, + recipients, + d->presetProtocol)); if (quickMode && !conflict && compliant) { QMetaObject::invokeMethod(this, "slotDialogAccepted", Qt::QueuedConnection); } else { d->ensureDialogVisible(); } } void NewSignEncryptEMailController::Private::slotDialogAccepted() { if (dialog->isQuickMode() != is_dialog_quick_mode(sign, encrypt)) { save_dialog_quick_mode(dialog->isQuickMode()); } resolvingInProgress = false; certificatesResolved = true; signers = dialog->resolvedSigningKeys(); recipients = dialog->resolvedEncryptionKeys(); QMetaObject::invokeMethod(q, "certificatesResolved", Qt::QueuedConnection); } void NewSignEncryptEMailController::Private::slotDialogRejected() { resolvingInProgress = false; certificatesResolved = false; QMetaObject::invokeMethod(q, "error", Qt::QueuedConnection, Q_ARG(int, gpg_error(GPG_ERR_CANCELED)), Q_ARG(QString, i18n("User cancel"))); } void NewSignEncryptEMailController::startEncryption(const std::vector< std::shared_ptr > &inputs, const std::vector< std::shared_ptr > &outputs) { kleo_assert(d->encrypt); kleo_assert(!d->resolvingInProgress); kleo_assert(!inputs.empty()); kleo_assert(outputs.size() == inputs.size()); std::vector< std::shared_ptr > tasks; tasks.reserve(inputs.size()); kleo_assert(!d->recipients.empty()); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr task(new EncryptEMailTask); task->setInput(inputs[i]); task->setOutput(outputs[i]); task->setRecipients(d->recipients); tasks.push_back(task); } // append to runnable stack d->runnable.insert(d->runnable.end(), tasks.begin(), tasks.end()); d->startEncryption(); } void NewSignEncryptEMailController::Private::startEncryption() { std::shared_ptr coll(new TaskCollection); std::vector > tmp; tmp.reserve(runnable.size()); std::copy(runnable.cbegin(), runnable.cend(), std::back_inserter(tmp)); coll->setTasks(tmp); #if 0 #warning use a new result dialog // ### use a new result dialog dialog->setTaskCollection(coll); #endif for (const std::shared_ptr &t : std::as_const(tmp)) { q->connectTask(t); } schedule(); } void NewSignEncryptEMailController::startSigning(const std::vector< std::shared_ptr > &inputs, const std::vector< std::shared_ptr > &outputs) { kleo_assert(d->sign); kleo_assert(!d->resolvingInProgress); kleo_assert(!inputs.empty()); kleo_assert(!outputs.empty()); std::vector< std::shared_ptr > tasks; tasks.reserve(inputs.size()); kleo_assert(!d->signers.empty()); kleo_assert(std::none_of(d->signers.cbegin(), d->signers.cend(), std::mem_fn(&Key::isNull))); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr task(new SignEMailTask); task->setInput(inputs[i]); task->setOutput(outputs[i]); task->setSigners(d->signers); task->setDetachedSignature(d->detached); tasks.push_back(task); } // append to runnable stack d->runnable.insert(d->runnable.end(), tasks.begin(), tasks.end()); d->startSigning(); } void NewSignEncryptEMailController::Private::startSigning() { std::shared_ptr coll(new TaskCollection); std::vector > tmp; tmp.reserve(runnable.size()); std::copy(runnable.cbegin(), runnable.cend(), std::back_inserter(tmp)); coll->setTasks(tmp); #if 0 #warning use a new result dialog // ### use a new result dialog dialog->setTaskCollection(coll); #endif for (const std::shared_ptr &t : std::as_const(tmp)) { q->connectTask(t); } schedule(); } void NewSignEncryptEMailController::Private::schedule() { if (!cms) if (const std::shared_ptr t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (cms || openpgp) { return; } kleo_assert(runnable.empty()); q->emitDoneOrError(); } std::shared_ptr NewSignEncryptEMailController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr(); } const std::shared_ptr result = *it; runnable.erase(it); return result; } void NewSignEncryptEMailController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_ASSERT(task); if (result && result->hasError()) { QPointer that = this; if (result->details().isEmpty()) KMessageBox:: sorry(nullptr, result->overview(), i18nc("@title:window", "Error")); else KMessageBox::detailedSorry(nullptr, result->overview(), result->details(), i18nc("@title:window", "Error")); if (!that) { return; } } // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container if (task == d->cms.get()) { d->completed.push_back(d->cms); d->cms.reset(); } else if (task == d->openpgp.get()) { d->completed.push_back(d->openpgp); d->openpgp.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } void NewSignEncryptEMailController::cancel() { try { d->dialog->close(); d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void NewSignEncryptEMailController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. runnable.clear(); // a cancel() will result in a call to if (cms) { cms->cancel(); } if (openpgp) { openpgp->cancel(); } } void NewSignEncryptEMailController::Private::ensureDialogVisible() { q->bringToForeground(dialog, true); } #include "moc_newsignencryptemailcontroller.cpp" diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp index 33604eac2..e08dc2d77 100644 --- a/src/dialogs/certificatedetailswidget.cpp +++ b/src/dialogs/certificatedetailswidget.cpp @@ -1,1229 +1,1230 @@ /* dialogs/certificatedetailswidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-FileCopyrightText: 2022 Felix Tiede SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "certificatedetailswidget.h" #include "kleopatra_debug.h" #include "exportdialog.h" #include "trustchainwidget.h" #include "subkeyswidget.h" #include "weboftrustdialog.h" #include "commands/changepassphrasecommand.h" #include "commands/changeexpirycommand.h" #include "commands/certifycertificatecommand.h" #ifdef MAILAKONADI_ENABLED #include "commands/exportopenpgpcerttoprovidercommand.h" #endif // MAILAKONADI_ENABLED #include "commands/refreshcertificatecommand.h" #include "commands/revokecertificationcommand.h" #include "commands/revokeuseridcommand.h" #include "commands/adduseridcommand.h" #include "commands/genrevokecommand.h" #include "commands/detailscommand.h" #include "commands/dumpcertificatecommand.h" #include "utils/keys.h" #include "utils/tags.h" #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() #include #define USE_RANGES #endif #include Q_DECLARE_METATYPE(GpgME::UserID) using namespace Kleo; static const char *accessibleNameProperty = "_kleo_accessibleName"; class InfoField { public: InfoField(const QString &label, QWidget *parent); void setValue(const QString &value, const QString &accessibleValue = {}); QString value() const; void setIcon(const QIcon &icon); void setAction(const QAction *action); void setToolTip(const QString &toolTip); void setVisible(bool visible); QLabel *label() const { return mLabel; } QLayout *layout() const { return mLayout; } private: void onActionChanged(); QLabel *mLabel = nullptr; QHBoxLayout *mLayout = nullptr; QLabel *mIcon = nullptr; QLabel *mValue = nullptr; QPushButton *mButton = nullptr; const QAction *mAction = nullptr; }; InfoField::InfoField(const QString &label, QWidget *parent) : mLabel{new QLabel{label, parent}} , mLayout{new QHBoxLayout} , mIcon{new QLabel{parent}} , mValue{new QLabel{parent}} , mButton{new QPushButton{parent}} { mLabel->setBuddy(mValue); mLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); mIcon->setVisible(false); mLayout->addWidget(mIcon); mValue->setTextInteractionFlags(Qt::TextSelectableByMouse); mValue->setFocusPolicy(Qt::TabFocus); mLayout->addWidget(mValue); mButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mButton->setVisible(false); mLayout->addWidget(mButton); mLayout->addStretch(); } void InfoField::setValue(const QString &value, const QString &accessibleValue) { mValue->setText(value); mValue->setAccessibleName(accessibleValue); } QString InfoField::value() const { return mValue->text(); } void InfoField::setIcon(const QIcon &icon) { if (!icon.isNull()) { const int iconSize = mIcon->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, mIcon); mIcon->setPixmap(icon.pixmap(iconSize)); mIcon->setVisible(true); } else { mIcon->setVisible(false); mIcon->clear(); } } void InfoField::setAction(const QAction *action) { if (action == mAction) { return; } if (mAction) { QObject::disconnect(mButton, {}, mAction, {}); QObject::disconnect(mAction, {}, mButton, {}); } mAction = action; if (mAction) { QObject::connect(mButton, &QPushButton::clicked, action, &QAction::trigger); QObject::connect(mAction, &QAction::changed, mButton, [this]() { onActionChanged(); }); onActionChanged(); mButton->setAccessibleName(mAction->property(accessibleNameProperty).toString()); mButton->setVisible(true); } else { mButton->setVisible(false); mButton->setText({}); mButton->setIcon({}); } } void InfoField::setToolTip(const QString &toolTip) { mValue->setToolTip(toolTip); } void InfoField::setVisible(bool visible) { mLabel->setVisible(visible); mIcon->setVisible(visible && !mIcon->pixmap(Qt::ReturnByValue).isNull()); mValue->setVisible(visible); mButton->setVisible(visible && mAction); } void InfoField::onActionChanged() { if (!mAction) { return; } if (mAction->text() != mButton->text()) { mButton->setText(mAction->text()); } mButton->setIcon(mAction->icon()); if (mAction->toolTip() != mButton->toolTip()) { mButton->setToolTip(mAction->toolTip()); } if (mAction->isEnabled() != mButton->isEnabled()) { mButton->setEnabled(mAction->isEnabled()); } } namespace { static bool userHasCertificationKey() { const auto secretKeys = KeyCache::instance()->secretKeys(); return Kleo::any_of(secretKeys, [](const auto &k) { return (k.protocol() == GpgME::OpenPGP) && canCreateCertifications(k); }); } class UserIDTable : public QTreeWidget { Q_OBJECT public: using QTreeWidget::QTreeWidget; std::vector selectedUserIDs() { std::vector userIDs; const auto selected = selectedItems(); std::transform(selected.begin(), selected.end(), std::back_inserter(userIDs), [](const QTreeWidgetItem *item) { return item->data(0, Qt::UserRole).value(); }); return userIDs; } protected: QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override { // make keyboard navigation with Left/Right possible by switching the selection behavior to SelectItems // before calling QTreeWidget::moveCursor, because QTreeWidget::moveCursor ignores MoveLeft/MoveRight // if the selection behavior is SelectRows if ((cursorAction == MoveLeft) || (cursorAction == MoveRight)) { setSelectionBehavior(SelectItems); } const auto result = QTreeWidget::moveCursor(cursorAction, modifiers); if ((cursorAction == MoveLeft) || (cursorAction == MoveRight)) { setSelectionBehavior(SelectRows); } return result; } }; } class CertificateDetailsWidget::Private { public: Private(CertificateDetailsWidget *qq); void setupCommonProperties(); void updateUserIDActions(); void setUpUserIDTable(); void setUpSMIMEAdressList(); void setupPGPProperties(); void setupSMIMEProperties(); void revokeUserID(const GpgME::UserID &uid); void revokeSelectedUserID(); void genRevokeCert(); void refreshCertificate(); void certifyUserIDs(); void revokeCertifications(); void webOfTrustClicked(); void exportClicked(); void addUserID(); void changePassphrase(); void changeExpiration(); void keysMayHaveChanged(); void showTrustChainDialog(); void showMoreDetails(); void userIDTableContextMenuRequested(const QPoint &p); QString tofuTooltipString(const GpgME::UserID &uid) const; QIcon trustLevelIcon(const GpgME::UserID &uid) const; QString trustLevelText(const GpgME::UserID &uid) const; void showIssuerCertificate(); void updateKey(); void setUpdatedKey(const GpgME::Key &key); void keyListDone(const GpgME::KeyListResult &, const std::vector &, const QString &, const GpgME::Error &); void copyFingerprintToClipboard(); private: CertificateDetailsWidget *const q; public: GpgME::Key key; bool updateInProgress = false; private: InfoField *attributeField(const QString &attributeName) { const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName); if (keyValuePairIt != ui.smimeAttributeFields.end()) { return (*keyValuePairIt).second.get(); } return nullptr; } private: struct UI { QWidget *userIDs = nullptr; QLabel *userIDTableLabel = nullptr; UserIDTable *userIDTable = nullptr; QPushButton *addUserIDBtn = nullptr; QPushButton *certifyBtn = nullptr; QPushButton *revokeCertificationsBtn = nullptr; QPushButton *revokeUserIDBtn = nullptr; QPushButton *webOfTrustBtn = nullptr; std::map> smimeAttributeFields; std::unique_ptr smimeTrustLevelField; std::unique_ptr validFromField; std::unique_ptr expiresField; QAction *changeExpirationAction = nullptr; std::unique_ptr fingerprintField; QAction *copyFingerprintAction = nullptr; std::unique_ptr smimeIssuerField; QAction *showIssuerCertificateAction = nullptr; std::unique_ptr complianceField; std::unique_ptr trustedIntroducerField; QLabel *smimeRelatedAddresses = nullptr; QListWidget *smimeAddressList = nullptr; QPushButton *moreDetailsBtn = nullptr; QPushButton *trustChainDetailsBtn = nullptr; QPushButton *refreshBtn = nullptr; QPushButton *changePassphraseBtn = nullptr; QPushButton *exportBtn = nullptr; QPushButton *genRevokeBtn = nullptr; void setupUi(QWidget *parent) { auto mainLayout = new QVBoxLayout{parent}; userIDs = new QWidget{parent}; { auto userIDsLayout = new QVBoxLayout{userIDs}; userIDsLayout->setContentsMargins({}); userIDTableLabel = new QLabel(i18n("User IDs:"), parent); userIDsLayout->addWidget(userIDTableLabel); userIDTable = new UserIDTable{parent}; userIDTableLabel->setBuddy(userIDTable); userIDTable->setAccessibleName(i18n("User IDs")); QTreeWidgetItem *__qtreewidgetitem = new QTreeWidgetItem(); __qtreewidgetitem->setText(0, QString::fromUtf8("1")); userIDTable->setHeaderItem(__qtreewidgetitem); userIDTable->setEditTriggers(QAbstractItemView::NoEditTriggers); userIDTable->setSelectionMode(QAbstractItemView::ExtendedSelection); userIDTable->setRootIsDecorated(false); userIDTable->setUniformRowHeights(true); userIDTable->setAllColumnsShowFocus(false); userIDsLayout->addWidget(userIDTable); { auto buttonRow = new QHBoxLayout; addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent); buttonRow->addWidget(addUserIDBtn); certifyBtn = new QPushButton(i18nc("@action:button", "Certify User IDs"), parent); buttonRow->addWidget(certifyBtn); webOfTrustBtn = new QPushButton(i18nc("@action:button", "Show Certifications"), parent); buttonRow->addWidget(webOfTrustBtn); revokeCertificationsBtn = new QPushButton(i18nc("@action:button", "Revoke Certifications"), parent); buttonRow->addWidget(revokeCertificationsBtn); revokeUserIDBtn = new QPushButton(i18nc("@action:button", "Revoke User ID"), parent); buttonRow->addWidget(revokeUserIDBtn); buttonRow->addStretch(1); userIDsLayout->addLayout(buttonRow); } userIDsLayout->addWidget(new KSeparator{Qt::Horizontal, parent}); } mainLayout->addWidget(userIDs); { auto gridLayout = new QGridLayout; gridLayout->setColumnStretch(1, 1); int row = -1; for (const auto &attribute : DN::attributeOrder()) { const auto attributeLabel = DN::attributeNameToLabel(attribute); if (attributeLabel.isEmpty()) { continue; } const auto labelWithColon = i18nc("interpunctation for labels", "%1:", attributeLabel); const auto & [it, inserted] = smimeAttributeFields.try_emplace(attribute, std::make_unique(labelWithColon, parent)); if (inserted) { row++; const auto &field = it->second; gridLayout->addWidget(field->label(), row, 0); gridLayout->addLayout(field->layout(), row, 1); } } row++; smimeTrustLevelField = std::make_unique(i18n("Trust level:"), parent); gridLayout->addWidget(smimeTrustLevelField->label(), row, 0); gridLayout->addLayout(smimeTrustLevelField->layout(), row, 1); row++; validFromField = std::make_unique(i18n("Valid from:"), parent); gridLayout->addWidget(validFromField->label(), row, 0); gridLayout->addLayout(validFromField->layout(), row, 1); row++; expiresField = std::make_unique(i18n("Expires:"), parent); changeExpirationAction = new QAction{parent}; changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor"))); changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the expiration date")); changeExpirationAction->setProperty(accessibleNameProperty, i18nc("@action:button", "Change expiration")); expiresField->setAction(changeExpirationAction); gridLayout->addWidget(expiresField->label(), row, 0); gridLayout->addLayout(expiresField->layout(), row, 1); row++; fingerprintField = std::make_unique(i18n("Fingerprint:"), parent); if (QGuiApplication::clipboard()) { copyFingerprintAction = new QAction{parent}; copyFingerprintAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); copyFingerprintAction->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard")); copyFingerprintAction->setProperty(accessibleNameProperty, i18nc("@action:button", "Copy fingerprint")); fingerprintField->setAction(copyFingerprintAction); } gridLayout->addWidget(fingerprintField->label(), row, 0); gridLayout->addLayout(fingerprintField->layout(), row, 1); row++; smimeIssuerField = std::make_unique(i18n("Issuer:"), parent); showIssuerCertificateAction = new QAction{parent}; showIssuerCertificateAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); showIssuerCertificateAction->setToolTip(i18nc("@info:tooltip", "Show the issuer certificate")); showIssuerCertificateAction->setProperty(accessibleNameProperty, i18nc("@action:button", "Show certificate")); smimeIssuerField->setAction(showIssuerCertificateAction); gridLayout->addWidget(smimeIssuerField->label(), row, 0); gridLayout->addLayout(smimeIssuerField->layout(), row, 1); row++; complianceField = std::make_unique(i18n("Compliance:"), parent); gridLayout->addWidget(complianceField->label(), row, 0); gridLayout->addLayout(complianceField->layout(), row, 1); row++; trustedIntroducerField = std::make_unique(i18n("Trusted introducer for:"), parent); gridLayout->addWidget(trustedIntroducerField->label(), row, 0); trustedIntroducerField->setToolTip(i18n("See certifications for details.")); gridLayout->addLayout(trustedIntroducerField->layout(), row, 1); mainLayout->addLayout(gridLayout); } smimeRelatedAddresses = new QLabel(i18n("Related addresses:"), parent); mainLayout->addWidget(smimeRelatedAddresses); smimeAddressList = new QListWidget{parent}; smimeRelatedAddresses->setBuddy(smimeAddressList); smimeAddressList->setAccessibleName(i18n("Related addresses")); smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers); smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection); mainLayout->addWidget(smimeAddressList); { auto buttonRow = new QHBoxLayout; moreDetailsBtn = new QPushButton(i18nc("@action:button", "More Details..."), parent); buttonRow->addWidget(moreDetailsBtn); trustChainDetailsBtn = new QPushButton(i18nc("@action:button", "Trust Chain Details"), parent); buttonRow->addWidget(trustChainDetailsBtn); refreshBtn = new QPushButton{i18nc("@action:button", "Update"), parent}; #ifndef QGPGME_SUPPORTS_KEY_REFRESH refreshBtn->setVisible(false); #endif buttonRow->addWidget(refreshBtn); exportBtn = new QPushButton(i18nc("@action:button", "Export"), parent); buttonRow->addWidget(exportBtn); changePassphraseBtn = new QPushButton(i18nc("@action:button", "Change Passphrase"), parent); buttonRow->addWidget(changePassphraseBtn); genRevokeBtn = new QPushButton(i18nc("@action:button", "Generate Revocation Certificate"), parent); genRevokeBtn->setToolTip(u"" % i18n("A revocation certificate is a file that serves as a \"kill switch\" to publicly " "declare that a key shall not anymore be used. It is not possible " "to retract such a revocation certificate once it has been published.") % u""); buttonRow->addWidget(genRevokeBtn); buttonRow->addStretch(1); mainLayout->addLayout(buttonRow); } } } ui; }; CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq) : q{qq} { ui.setupUi(q); ui.userIDTable->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.userIDTable, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { userIDTableContextMenuRequested(p); }); connect(ui.userIDTable, &QTreeWidget::itemSelectionChanged, q, [this]() { updateUserIDActions(); }); connect(ui.addUserIDBtn, &QPushButton::clicked, q, [this]() { addUserID(); }); connect(ui.revokeUserIDBtn, &QPushButton::clicked, q, [this]() { revokeSelectedUserID(); }); connect(ui.changePassphraseBtn, &QPushButton::clicked, q, [this]() { changePassphrase(); }); connect(ui.genRevokeBtn, &QPushButton::clicked, q, [this]() { genRevokeCert(); }); connect(ui.changeExpirationAction, &QAction::triggered, q, [this]() { changeExpiration(); }); connect(ui.showIssuerCertificateAction, &QAction::triggered, q, [this]() { showIssuerCertificate(); }); connect(ui.trustChainDetailsBtn, &QPushButton::pressed, q, [this]() { showTrustChainDialog(); }); connect(ui.moreDetailsBtn, &QPushButton::pressed, q, [this]() { showMoreDetails(); }); connect(ui.refreshBtn, &QPushButton::clicked, q, [this]() { refreshCertificate(); }); connect(ui.certifyBtn, &QPushButton::clicked, q, [this]() { certifyUserIDs(); }); connect(ui.revokeCertificationsBtn, &QPushButton::clicked, q, [this]() { revokeCertifications(); }); connect(ui.webOfTrustBtn, &QPushButton::clicked, q, [this]() { webOfTrustClicked(); }); connect(ui.exportBtn, &QPushButton::clicked, q, [this]() { exportClicked(); }); if (ui.copyFingerprintAction) { connect(ui.copyFingerprintAction, &QAction::triggered, q, [this]() { copyFingerprintToClipboard(); }); } connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() { keysMayHaveChanged(); }); } void CertificateDetailsWidget::Private::setupCommonProperties() { const bool isOpenPGP = key.protocol() == GpgME::OpenPGP; const bool isSMIME = key.protocol() == GpgME::CMS; const bool isOwnKey = key.hasSecret(); // update visibility of UI elements ui.userIDs->setVisible(isOpenPGP); ui.addUserIDBtn->setVisible(isOwnKey); // ui.certifyBtn->setVisible(true); // always visible (for OpenPGP keys) // ui.webOfTrustBtn->setVisible(true); // always visible (for OpenPGP keys) ui.revokeCertificationsBtn->setVisible(Kleo::Commands::RevokeCertificationCommand::isSupported()); ui.revokeUserIDBtn->setVisible(isOwnKey); for (const auto &[_, field] : ui.smimeAttributeFields) { field->setVisible(isSMIME); } ui.smimeTrustLevelField->setVisible(isSMIME); // ui.validFromField->setVisible(true); // always visible // ui.expiresField->setVisible(true); // always visible if (isOpenPGP && isOwnKey) { ui.expiresField->setAction(ui.changeExpirationAction); } else { ui.expiresField->setAction(nullptr); } // ui.fingerprintField->setVisible(true); // always visible ui.smimeIssuerField->setVisible(isSMIME); - ui.complianceField->setVisible(Kleo::gnupgIsDeVsCompliant()); + ui.complianceField->setVisible(DeVSCompliance::isCompliant()); ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties() ui.smimeRelatedAddresses->setVisible(isSMIME); ui.smimeAddressList->setVisible(isSMIME); // ui.moreDetailsBtn->setVisible(true); // always visible ui.trustChainDetailsBtn->setVisible(isSMIME); // ui.refreshBtn->setVisible(true); // always visible ui.changePassphraseBtn->setVisible(isSecretKeyStoredInKeyRing(key)); // ui.exportBtn->setVisible(true); // always visible ui.genRevokeBtn->setVisible(isOpenPGP && isOwnKey); // update availability of buttons const auto userCanSignUserIDs = userHasCertificationKey(); ui.addUserIDBtn->setEnabled(canBeUsedForSecretKeyOperations(key)); ui.certifyBtn->setEnabled(userCanSignUserIDs); ui.revokeCertificationsBtn->setEnabled(userCanSignUserIDs); ui.revokeUserIDBtn->setEnabled(false); // requires a selected user ID ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key)); ui.changePassphraseBtn->setEnabled(isSecretKeyStoredInKeyRing(key)); ui.genRevokeBtn->setEnabled(canBeUsedForSecretKeyOperations(key)); // update values of protocol-independent UI elements ui.validFromField->setValue(Formatting::creationDateString(key), Formatting::accessibleCreationDate(key)); ui.expiresField->setValue(Formatting::expirationDateString(key, i18nc("Expires", "never")), Formatting::accessibleExpirationDate(key, i18nc("Expires", "never"))); ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()), Formatting::accessibleHexID(key.primaryFingerprint())); - if (Kleo::gnupgIsDeVsCompliant()) { + if (DeVSCompliance::isCompliant()) { ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key)); } } void CertificateDetailsWidget::Private::updateUserIDActions() { const auto userIDs = ui.userIDTable->selectedUserIDs(); ui.revokeUserIDBtn->setEnabled((userIDs.size() == 1) && canCreateCertifications(key) && canRevokeUserID(userIDs.front())); } void CertificateDetailsWidget::Private::setUpUserIDTable() { ui.userIDTable->clear(); QStringList headers = { i18n("Email"), i18n("Name"), i18n("Trust Level"), i18n("Tags") }; ui.userIDTable->setColumnCount(headers.count()); ui.userIDTable->setColumnWidth(0, 200); ui.userIDTable->setColumnWidth(1, 200); ui.userIDTable->setHeaderLabels(headers); const auto uids = key.userIDs(); for (unsigned int i = 0; i < uids.size(); ++i) { const auto &uid = uids[i]; auto item = new QTreeWidgetItem; const QString toolTip = tofuTooltipString(uid); item->setData(0, Qt::UserRole, QVariant::fromValue(uid)); auto pMail = Kleo::Formatting::prettyEMail(uid); auto pName = Kleo::Formatting::prettyName(uid); item->setData(0, Qt::DisplayRole, pMail); item->setData(0, Qt::ToolTipRole, toolTip); item->setData(0, Qt::AccessibleTextRole, pMail.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : pMail); item->setData(1, Qt::DisplayRole, pName); item->setData(1, Qt::ToolTipRole, toolTip); item->setData(2, Qt::DecorationRole, trustLevelIcon(uid)); item->setData(2, Qt::DisplayRole, trustLevelText(uid)); item->setData(2, Qt::ToolTipRole, toolTip); GpgME::Error err; QStringList tagList; for (const auto &tag: uid.remarks(Tags::tagKeys(), err)) { if (err) { qCWarning(KLEOPATRA_LOG) << "Getting remarks for user ID" << uid.id() << "failed:" << err; } tagList << QString::fromStdString(tag); } qCDebug(KLEOPATRA_LOG) << "tagList:" << tagList; const auto tags = tagList.join(QStringLiteral("; ")); item->setData(3, Qt::DisplayRole, tags); item->setData(3, Qt::ToolTipRole, toolTip); ui.userIDTable->addTopLevelItem(item); } if (!Tags::tagsEnabled()) { ui.userIDTable->hideColumn(3); } } void CertificateDetailsWidget::Private::setUpSMIMEAdressList() { ui.smimeAddressList->clear(); const auto *const emailField = attributeField(QStringLiteral("EMAIL")); // add email address from primary user ID if not listed already as attribute field if (!emailField) { const auto ownerId = key.userID(0); const Kleo::DN dn(ownerId.id()); const QString dnEmail = dn[QStringLiteral("EMAIL")]; if (!dnEmail.isEmpty()) { ui.smimeAddressList->addItem(dnEmail); } } if (key.numUserIDs() < 2) { return; } // iterate over the secondary user IDs #ifdef USE_RANGES for (const auto uids = key.userIDs(); const auto &uid : std::ranges::subrange(std::next(uids.begin()), uids.end())) { #else const auto uids = key.userIDs(); for (auto it = std::next(uids.begin()); it != uids.end(); ++it) { const auto &uid = *it; #endif const auto name = Kleo::Formatting::prettyName(uid); const auto email = Kleo::Formatting::prettyEMail(uid); QString itemText; if (name.isEmpty() && !email.isEmpty()) { // skip email addresses already listed in email attribute field if (emailField && email == emailField->value()) { continue; } itemText = email; } else { // S/MIME certificates sometimes contain urls where both // name and mail is empty. In that case we print whatever // the uid is as name. // // Can be ugly like (3:uri24:http://ca.intevation.org), but // this is better then showing an empty entry. itemText = QString::fromUtf8(uid.id()); } // avoid duplicate entries in the list if (ui.smimeAddressList->findItems(itemText, Qt::MatchExactly).empty()) { ui.smimeAddressList->addItem(itemText); } } } void CertificateDetailsWidget::Private::revokeUserID(const GpgME::UserID &userId) { const QString message = xi18nc( "@info", "Do you really want to revoke the user ID%1 ?", QString::fromUtf8(userId.id())); auto confirmButton = KStandardGuiItem::yes(); confirmButton.setText(i18nc("@action:button", "Revoke User ID")); confirmButton.setToolTip({}); const auto choice = KMessageBox::questionYesNo( q->window(), message, i18nc("@title:window", "Confirm Revocation"), confirmButton, KStandardGuiItem::cancel(), {}, KMessageBox::Notify | KMessageBox::WindowModal); if (choice != KMessageBox::Yes) { return; } auto cmd = new Commands::RevokeUserIDCommand(userId); cmd->setParentWidget(q); connect(cmd, &Command::finished, q, [this]() { ui.userIDTable->setEnabled(true); // the Revoke User ID button will be updated by the key update updateKey(); }); ui.userIDTable->setEnabled(false); ui.revokeUserIDBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::revokeSelectedUserID() { const auto userIDs = ui.userIDTable->selectedUserIDs(); if (userIDs.size() != 1) { return; } revokeUserID(userIDs.front()); } void CertificateDetailsWidget::Private::changeExpiration() { auto cmd = new Kleo::Commands::ChangeExpiryCommand(key); QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() { ui.changeExpirationAction->setEnabled(true); }); ui.changeExpirationAction->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::changePassphrase() { auto cmd = new Kleo::Commands::ChangePassphraseCommand(key); QObject::connect(cmd, &Kleo::Commands::ChangePassphraseCommand::finished, q, [this]() { ui.changePassphraseBtn->setEnabled(true); }); ui.changePassphraseBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::genRevokeCert() { auto cmd = new Kleo::Commands::GenRevokeCommand(key); QObject::connect(cmd, &Kleo::Commands::GenRevokeCommand::finished, q, [this]() { ui.genRevokeBtn->setEnabled(true); }); ui.genRevokeBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::refreshCertificate() { auto cmd = new Kleo::RefreshCertificateCommand{key}; QObject::connect(cmd, &Kleo::RefreshCertificateCommand::finished, q, [this]() { ui.refreshBtn->setEnabled(true); }); ui.refreshBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::certifyUserIDs() { const auto userIDs = ui.userIDTable->selectedUserIDs(); auto cmd = userIDs.empty() ? new Kleo::Commands::CertifyCertificateCommand{key} // : new Kleo::Commands::CertifyCertificateCommand{userIDs}; QObject::connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() { updateKey(); ui.certifyBtn->setEnabled(true); }); ui.certifyBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::revokeCertifications() { const auto userIDs = ui.userIDTable->selectedUserIDs(); auto cmd = userIDs.empty() ? new Kleo::Commands::RevokeCertificationCommand{key} // : new Kleo::Commands::RevokeCertificationCommand{userIDs}; QObject::connect(cmd, &Kleo::Command::finished, q, [this]() { updateKey(); ui.revokeCertificationsBtn->setEnabled(true); }); ui.revokeCertificationsBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::webOfTrustClicked() { QScopedPointer dlg(new WebOfTrustDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::exportClicked() { QScopedPointer dlg(new ExportDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::addUserID() { auto cmd = new Kleo::Commands::AddUserIDCommand(key); QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished, q, [this]() { ui.addUserIDBtn->setEnabled(true); updateKey(); }); ui.addUserIDBtn->setEnabled(false); cmd->start(); } namespace { void ensureThatKeyDetailsAreLoaded(GpgME::Key &key) { if (key.userID(0).numSignatures() == 0) { key.update(); } } } void CertificateDetailsWidget::Private::keysMayHaveChanged() { auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint()); if (!newKey.isNull()) { ensureThatKeyDetailsAreLoaded(newKey); setUpdatedKey(newKey); } } void CertificateDetailsWidget::Private::showTrustChainDialog() { QScopedPointer dlg(new TrustChainDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QPoint &p) { const auto userIDs = ui.userIDTable->selectedUserIDs(); const auto singleUserID = (userIDs.size() == 1) ? userIDs.front() : GpgME::UserID{}; const bool canSignUserIDs = userHasCertificationKey(); auto menu = new QMenu(q); { const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Certify User IDs...") : i18ncp("@action:inmenu", "Certify User ID...", "Certify User IDs...", userIDs.size()); auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")), actionText, q, [this]() { certifyUserIDs(); }); action->setEnabled(canSignUserIDs); } if (Kleo::Commands::RevokeCertificationCommand::isSupported()) { const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Revoke Certifications...") : i18ncp("@action:inmenu", "Revoke Certification...", "Revoke Certifications...", userIDs.size()); auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), actionText, q, [this]() { revokeCertifications(); }); action->setEnabled(canSignUserIDs); } #ifdef MAILAKONADI_ENABLED if (key.hasSecret()) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18nc("@action:inmenu", "Publish at Mail Provider ..."), q, [this, singleUserID]() { auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(singleUserID); ui.userIDTable->setEnabled(false); connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished, q, [this]() { ui.userIDTable->setEnabled(true); }); cmd->start(); }); action->setEnabled(!singleUserID.isNull()); } #endif // MAILAKONADI_ENABLED { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18nc("@action:inmenu", "Revoke User ID"), q, [this, singleUserID]() { revokeUserID(singleUserID); }); action->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID)); } connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); menu->popup(ui.userIDTable->viewport()->mapToGlobal(p)); } void CertificateDetailsWidget::Private::showMoreDetails() { ui.moreDetailsBtn->setEnabled(false); if (key.protocol() == GpgME::CMS) { auto cmd = new Kleo::Commands::DumpCertificateCommand(key); connect(cmd, &Kleo::Commands::DumpCertificateCommand::finished, q, [this]() { ui.moreDetailsBtn->setEnabled(true); }); cmd->setUseDialog(true); cmd->start(); } else { QScopedPointer dlg(new SubKeysDialog(q)); dlg->setKey(key); dlg->exec(); ui.moreDetailsBtn->setEnabled(true); } } QString CertificateDetailsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const { const auto tofu = uid.tofuInfo(); if (tofu.isNull()) { return QString(); } QString html = QStringLiteral(""); const auto appendRow = [&html](const QString &lbl, const QString &val) { html += QStringLiteral("" "" "" "") .arg(lbl, val); }; const auto appendHeader = [this, &html](const QString &hdr) { html += QStringLiteral("") .arg(q->palette().highlight().color().name(), q->palette().highlightedText().color().name(), hdr); }; const auto dateTime = [](long ts) { QLocale l; return ts == 0 ? i18n("never") : l.toString(QDateTime::fromSecsSinceEpoch(ts), QLocale::ShortFormat); }; appendHeader(i18n("Signing")); appendRow(i18n("First message"), dateTime(tofu.signFirst())); appendRow(i18n("Last message"), dateTime(tofu.signLast())); appendRow(i18n("Message count"), QString::number(tofu.signCount())); appendHeader(i18n("Encryption")); appendRow(i18n("First message"), dateTime(tofu.encrFirst())); appendRow(i18n("Last message"), dateTime(tofu.encrLast())); appendRow(i18n("Message count"), QString::number(tofu.encrCount())); html += QStringLiteral("
%1:%2
%3
"); // Make sure the tooltip string is different for each UserID, even if the // data are the same, otherwise the tooltip is not updated and moved when // user moves mouse from one row to another. html += QStringLiteral("").arg(QString::fromUtf8(uid.id())); return html; } QIcon CertificateDetailsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const { if (updateInProgress) { return QIcon::fromTheme(QStringLiteral("emblem-question")); } switch (uid.validity()) { case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: return QIcon::fromTheme(QStringLiteral("emblem-question")); case GpgME::UserID::Never: return QIcon::fromTheme(QStringLiteral("emblem-error")); case GpgME::UserID::Marginal: return QIcon::fromTheme(QStringLiteral("emblem-warning")); case GpgME::UserID::Full: case GpgME::UserID::Ultimate: return QIcon::fromTheme(QStringLiteral("emblem-success")); } return {}; } QString CertificateDetailsWidget::Private::trustLevelText(const GpgME::UserID &uid) const { return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid); } namespace { auto isGood(const GpgME::UserID::Signature &signature) { return signature.status() == GpgME::UserID::Signature::NoError && !signature.isInvalid() && 0x10 <= signature.certClass() && signature.certClass() <= 0x13; } auto accumulateTrustDomains(const std::vector &signatures) { return std::accumulate( std::begin(signatures), std::end(signatures), std::set(), [] (auto domains, const auto &signature) { if (isGood(signature) && signature.isTrustSignature()) { domains.insert(Formatting::trustSignatureDomain(signature)); } return domains; } ); } auto accumulateTrustDomains(const std::vector &userIds) { return std::accumulate( std::begin(userIds), std::end(userIds), std::set(), [] (auto domains, const auto &userID) { const auto newDomains = accumulateTrustDomains(userID.signatures()); std::copy(std::begin(newDomains), std::end(newDomains), std::inserter(domains, std::end(domains))); return domains; } ); } } void CertificateDetailsWidget::Private::setupPGPProperties() { setUpUserIDTable(); const auto trustDomains = accumulateTrustDomains(key.userIDs()); ui.trustedIntroducerField->setVisible(!trustDomains.empty()); ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", ")); ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the key from external sources.")); } static QString formatDNToolTip(const Kleo::DN &dn) { QString html = QStringLiteral(""); const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) { const QString val = dn[attr]; if (!val.isEmpty()) { html += QStringLiteral( "" "" "").arg(lbl, val); } }; appendRow(i18n("Common Name"), QStringLiteral("CN")); appendRow(i18n("Organization"), QStringLiteral("O")); appendRow(i18n("Street"), QStringLiteral("STREET")); appendRow(i18n("City"), QStringLiteral("L")); appendRow(i18n("State"), QStringLiteral("ST")); appendRow(i18n("Country"), QStringLiteral("C")); html += QStringLiteral("
%1:%2
"); return html; } void CertificateDetailsWidget::Private::setupSMIMEProperties() { const auto ownerId = key.userID(0); const Kleo::DN dn(ownerId.id()); for (const auto &[attributeName, field] : ui.smimeAttributeFields) { const QString attributeValue = dn[attributeName]; field->setValue(attributeValue); field->setVisible(!attributeValue.isEmpty()); } ui.smimeTrustLevelField->setIcon(trustLevelIcon(ownerId)); ui.smimeTrustLevelField->setValue(trustLevelText(ownerId)); const Kleo::DN issuerDN(key.issuerName()); const QString issuerCN = issuerDN[QStringLiteral("CN")]; const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN; ui.smimeIssuerField->setValue(issuer); ui.smimeIssuerField->setToolTip(formatDNToolTip(issuerDN)); ui.showIssuerCertificateAction->setEnabled(!key.isRoot()); setUpSMIMEAdressList(); ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the CRLs and do a full validation check of the certificate.")); } void CertificateDetailsWidget::Private::showIssuerCertificate() { // there is either one or no parent key const auto parentKeys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption); if (parentKeys.empty()) { KMessageBox::error(q, i18n("The issuer certificate could not be found locally.")); return; } auto cmd = new Kleo::Commands::DetailsCommand(parentKeys.front(), nullptr); cmd->setParentWidget(q); cmd->start(); } void CertificateDetailsWidget::Private::copyFingerprintToClipboard() { if (auto clipboard = QGuiApplication::clipboard()) { clipboard->setText(QString::fromLatin1(key.primaryFingerprint())); } } CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { } CertificateDetailsWidget::~CertificateDetailsWidget() = default; void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector &keys, const QString &, const GpgME::Error &) { updateInProgress = false; if (keys.size() != 1) { qCWarning(KLEOPATRA_LOG) << "Invalid keylist result in update."; return; } // As we listen for keysmayhavechanged we get the update // after updating the keycache. KeyCache::mutableInstance()->insert(keys); } void CertificateDetailsWidget::Private::updateKey() { key.update(); setUpdatedKey(key); } void CertificateDetailsWidget::Private::setUpdatedKey(const GpgME::Key &k) { key = k; setupCommonProperties(); if (key.protocol() == GpgME::OpenPGP) { setupPGPProperties(); } else { setupSMIMEProperties(); } } void CertificateDetailsWidget::setKey(const GpgME::Key &key) { if (key.protocol() == GpgME::CMS) { // For everything but S/MIME this should be quick // and we don't need to show another status. d->updateInProgress = true; } d->setUpdatedKey(key); // Run a keylistjob with full details (TOFU / Validate) QGpgME::KeyListJob *job = key.protocol() == GpgME::OpenPGP ? QGpgME::openpgp()->keyListJob(false, true, true) : QGpgME::smime()->keyListJob(false, true, true); auto ctx = QGpgME::Job::context(job); ctx->addKeyListMode(GpgME::WithTofu); ctx->addKeyListMode(GpgME::SignatureNotations); if (key.hasSecret()) { ctx->addKeyListMode(GpgME::WithSecret); } // Windows QGpgME new style connect problem makes this necessary. connect(job, SIGNAL(result(GpgME::KeyListResult,std::vector,QString,GpgME::Error)), this, SLOT(keyListDone(GpgME::KeyListResult,std::vector,QString,GpgME::Error))); job->start(QStringList() << QLatin1String(key.primaryFingerprint())); } GpgME::Key CertificateDetailsWidget::key() const { return d->key; } #include "certificatedetailswidget.moc" #include "moc_certificatedetailswidget.cpp" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4bea556e9..b33567a27 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,782 +1,783 @@ /* -*- mode: c++; c-basic-offset:4 -*- mainwindow.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "mainwindow.h" #include "aboutdata.h" #include "settings.h" #include #include "view/padwidget.h" #include "view/searchbar.h" #include "view/tabwidget.h" #include "view/keylistcontroller.h" #include "view/keycacheoverlay.h" #include "view/smartcardwidget.h" #include "view/welcomewidget.h" #include "commands/selftestcommand.h" #include "commands/importcrlcommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/signencryptfilescommand.h" #include "conf/groupsconfigdialog.h" #include "utils/detail_p.h" #include #include "utils/action_data.h" #include "utils/filedialog.h" #include "utils/clipboardmenu.h" #include "utils/gui-helper.h" #include "utils/qt-cxx20-compat.h" #include "dialogs/updatenotification.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; static KGuiItem KStandardGuiItem_quit() { static const QString app = KAboutData::applicationData().displayName(); KGuiItem item = KStandardGuiItem::quit(); item.setText(xi18nc("@action:button", "&Quit %1", app)); return item; } static KGuiItem KStandardGuiItem_close() { KGuiItem item = KStandardGuiItem::close(); item.setText(i18nc("@action:button", "Only &Close Window")); return item; } static bool isQuitting = false; namespace { static const std::vector mainViewActionNames = { QStringLiteral("view_certificate_overview"), QStringLiteral("manage_smartcard"), QStringLiteral("pad_view") }; class CertificateView : public QWidget, public FocusFirstChild { Q_OBJECT public: CertificateView(QWidget* parent = nullptr) : QWidget{parent} , ui{this} { } SearchBar *searchBar() const { return ui.searchBar; } TabWidget *tabWidget() const { return ui.tabWidget; } void focusFirstChild(Qt::FocusReason reason) override { ui.searchBar->lineEdit()->setFocus(reason); } private: struct UI { TabWidget *tabWidget = nullptr; SearchBar *searchBar = nullptr; explicit UI(CertificateView *q) { auto vbox = new QVBoxLayout{q}; vbox->setSpacing(0); searchBar = new SearchBar{q}; vbox->addWidget(searchBar); tabWidget = new TabWidget{q}; vbox->addWidget(tabWidget); tabWidget->connectSearchBar(searchBar); } } ui; }; } class MainWindow::Private { friend class ::MainWindow; MainWindow *const q; public: explicit Private(MainWindow *qq); ~Private(); template void createAndStart() { (new T(this->currentView(), &this->controller))->start(); } template void createAndStart(QAbstractItemView *view) { (new T(view, &this->controller))->start(); } template void createAndStart(const QStringList &a) { (new T(a, this->currentView(), &this->controller))->start(); } template void createAndStart(const QStringList &a, QAbstractItemView *view) { (new T(a, view, &this->controller))->start(); } void closeAndQuit() { const QString app = KAboutData::applicationData().displayName(); const int rc = KMessageBox::questionYesNoCancel(q, xi18n("%1 may be used by other applications as a service." "You may instead want to close this window without exiting %1.", app), i18nc("@title:window", "Really Quit?"), KStandardGuiItem_close(), KStandardGuiItem_quit(), KStandardGuiItem::cancel(), QLatin1String("really-quit-") + app.toLower()); if (rc == KMessageBox::Cancel) { return; } isQuitting = true; if (!q->close()) { return; } // WARNING: 'this' might be deleted at this point! if (rc == KMessageBox::No) { qApp->quit(); } } void configureToolbars() { KEditToolBar dlg(q->factory()); dlg.exec(); } void editKeybindings() { KShortcutsDialog::showDialog(q->actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, q); updateSearchBarClickMessage(); } void updateSearchBarClickMessage() { const QString shortcutStr = focusToClickSearchAction->shortcut().toString(); ui.searchTab->searchBar()->updateClickMessage(shortcutStr); } void updateStatusBar() { - if (Kleo::gnupgUsesDeVsCompliance()) { + if (DeVSCompliance::isActive()) { auto statusBar = std::make_unique(); - auto statusLbl = std::make_unique(Formatting::deVsString(Kleo::gnupgIsDeVsCompliant())); + auto statusLbl = std::make_unique(DeVSCompliance::name()); if (!SystemInfo::isHighContrastModeActive()) { const auto color = KColorScheme(QPalette::Active, KColorScheme::View).foreground( - Kleo::gnupgIsDeVsCompliant() ? KColorScheme::NormalText: KColorScheme::NegativeText + DeVSCompliance::isCompliant() ? KColorScheme::NormalText: KColorScheme::NegativeText ).color(); const auto background = KColorScheme(QPalette::Active, KColorScheme::View).background( - Kleo::gnupgIsDeVsCompliant() ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground + DeVSCompliance::isCompliant() ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground ).color(); statusLbl->setStyleSheet(QStringLiteral("QLabel { color: %1; background-color: %2; }"). arg(color.name()).arg(background.name())); } statusBar->insertPermanentWidget(0, statusLbl.release()); q->setStatusBar(statusBar.release()); // QMainWindow takes ownership } else { q->setStatusBar(nullptr); } } void selfTest() { createAndStart(); } void configureGroups() { if (KConfigDialog::showDialog(GroupsConfigDialog::dialogName())) { return; } KConfigDialog *dialog = new GroupsConfigDialog(q); dialog->show(); } void showHandbook(); void gnupgLogViewer() { const QString exec = QStandardPaths::findExecutable(QStringLiteral("kwatchgnupg")); if (exec.isEmpty()) { KMessageBox::error( q, i18n("Could not start the GnuPG Log Viewer (kwatchgnupg). " "Please check your installation."), i18n("Error Starting KWatchGnuPG")); } else { QProcess::startDetached(QStringLiteral("kwatchgnupg"), QStringList()); } } void forceUpdateCheck() { UpdateNotification::forceUpdateCheck(q); } void slotConfigCommitted(); void slotContextMenuRequested(QAbstractItemView *, const QPoint &p) { if (auto const menu = qobject_cast(q->factory()->container(QStringLiteral("listview_popup"), q))) { menu->exec(p); } else { qCDebug(KLEOPATRA_LOG) << "no \"listview_popup\" in kleopatra's ui.rc file"; } } void slotFocusQuickSearch() { ui.searchTab->searchBar()->lineEdit()->setFocus(); } void showView(const QString &actionName, QWidget *widget) { const auto coll = q->actionCollection(); if (coll) { for ( const QString &name : mainViewActionNames ) { if (auto action = coll->action(name)) { action->setChecked(name == actionName); } } } ui.stackWidget->setCurrentWidget(widget); if (auto ffci = dynamic_cast(widget)) { ffci->focusFirstChild(Qt::TabFocusReason); } } void showCertificateView() { if (KeyCache::instance()->keys().empty()) { showView(QStringLiteral("view_certificate_overview"), ui.welcomeWidget); } else { showView(QStringLiteral("view_certificate_overview"), ui.searchTab); } } void showSmartcardView() { showView(QStringLiteral("manage_smartcard"), ui.scWidget); } void showPadView() { if (!ui.padWidget) { ui.padWidget = new PadWidget; ui.stackWidget->addWidget(ui.padWidget); } showView(QStringLiteral("pad_view"), ui.padWidget); ui.stackWidget->resize(ui.padWidget->sizeHint()); } void restartDaemons() { Kleo::killDaemons(); } private: void setupActions(); QAbstractItemView *currentView() const { return ui.searchTab->tabWidget()->currentView(); } void keyListingDone() { const auto curWidget = ui.stackWidget->currentWidget(); if (curWidget == ui.scWidget || curWidget == ui.padWidget) { return; } showCertificateView(); } private: Kleo::KeyListController controller; bool firstShow : 1; struct UI { CertificateView *searchTab = nullptr; PadWidget *padWidget = nullptr; SmartCardWidget *scWidget = nullptr; WelcomeWidget *welcomeWidget = nullptr; QStackedWidget *stackWidget = nullptr; explicit UI(MainWindow *q); } ui; QAction *focusToClickSearchAction = nullptr; ClipboardMenu *clipboadMenu = nullptr; }; MainWindow::Private::UI::UI(MainWindow *q) : padWidget(nullptr) { auto mainWidget = new QWidget{q}; auto mainLayout = new QVBoxLayout(mainWidget); stackWidget = new QStackedWidget{q}; searchTab = new CertificateView{q}; stackWidget->addWidget(searchTab); new KeyCacheOverlay(mainWidget, q); scWidget = new SmartCardWidget{q}; stackWidget->addWidget(scWidget); welcomeWidget = new WelcomeWidget{q}; stackWidget->addWidget(welcomeWidget); mainLayout->addWidget(stackWidget); q->setCentralWidget(mainWidget); } MainWindow::Private::Private(MainWindow *qq) : q(qq), controller(q), firstShow(true), ui(q) { KDAB_SET_OBJECT_NAME(controller); AbstractKeyListModel *flatModel = AbstractKeyListModel::createFlatKeyListModel(q); AbstractKeyListModel *hierarchicalModel = AbstractKeyListModel::createHierarchicalKeyListModel(q); KDAB_SET_OBJECT_NAME(flatModel); KDAB_SET_OBJECT_NAME(hierarchicalModel); controller.setFlatModel(flatModel); controller.setHierarchicalModel(hierarchicalModel); controller.setTabWidget(ui.searchTab->tabWidget()); ui.searchTab->tabWidget()->setFlatModel(flatModel); ui.searchTab->tabWidget()->setHierarchicalModel(hierarchicalModel); setupActions(); ui.stackWidget->setCurrentWidget(ui.searchTab); if (auto action = q->actionCollection()->action(QStringLiteral("view_certificate_overview"))) { action->setChecked(true); } connect(&controller, SIGNAL(contextMenuRequested(QAbstractItemView*,QPoint)), q, SLOT(slotContextMenuRequested(QAbstractItemView*,QPoint))); connect(KeyCache::instance().get(), &KeyCache::keyListingDone, q, [this] () {keyListingDone();}); q->createGUI(QStringLiteral("kleopatra.rc")); // make toolbar buttons accessible by keyboard if (auto toolbar = q->findChild()) { auto toolbarButtons = toolbar->findChildren(); for (auto b : toolbarButtons) { b->setFocusPolicy(Qt::TabFocus); } // move toolbar and its child widgets before the central widget in the tab order; // this is necessary to make Shift+Tab work as expected forceSetTabOrder(q, toolbar); auto toolbarChildren = toolbar->findChildren(); std::for_each(std::rbegin(toolbarChildren), std::rend(toolbarChildren), [toolbar](auto w) { forceSetTabOrder(toolbar, w); }); } q->setAcceptDrops(true); // set default window size q->resize(QSize(1024, 500)); q->setAutoSaveSettings(); updateSearchBarClickMessage(); updateStatusBar(); if (KeyCache::instance()->initialized()) { keyListingDone(); } } MainWindow::Private::~Private() {} MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : KXmlGuiWindow(parent, flags), d(new Private(this)) {} MainWindow::~MainWindow() {} void MainWindow::Private::setupActions() { KActionCollection *const coll = q->actionCollection(); const std::vector action_data = { // see keylistcontroller.cpp for more actions // Tools menu #ifndef Q_OS_WIN { "tools_start_kwatchgnupg", i18n("GnuPG Log Viewer"), QString(), "kwatchgnupg", q, [this](bool) { gnupgLogViewer(); }, QString() }, #endif { "tools_restart_backend", i18nc("@action:inmenu", "Restart Background Processes"), i18nc("@info:tooltip", "Restart the background processes, e.g. after making changes to the configuration."), "view-refresh", q, [this](bool) { restartDaemons(); }, {} }, // Help menu #ifdef Q_OS_WIN { "help_check_updates", i18n("Check for updates"), QString(), "gpg4win-compact", q, [this](bool) { forceUpdateCheck(); }, QString() }, #endif // View menu { "view_certificate_overview", i18nc("@action show certificate overview", "Certificates"), i18n("Show certificate overview"), "view-certificate", q, [this](bool) { showCertificateView(); }, QString() }, { "pad_view", i18nc("@action show input / output area for encrypting/signing resp. decrypting/verifying text", "Notepad"), i18n("Show pad for encrypting/decrypting and signing/verifying text"), "note", q, [this](bool) { showPadView(); }, QString() }, { "manage_smartcard", i18nc("@action show smartcard management view", "Smartcards"), i18n("Show smartcard management"), "auth-sim-locked", q, [this](bool) { showSmartcardView(); }, QString() }, // Settings menu { "settings_self_test", i18n("Perform Self-Test"), QString(), nullptr, q, [this](bool) { selfTest(); }, QString() }, { "configure_groups", i18n("Configure Groups..."), QString(), "group", q, [this](bool) { configureGroups(); }, QString() } }; make_actions_from_data(action_data, coll); if (!Settings().groupsEnabled()) { if (auto action = coll->action(QStringLiteral("configure_groups"))) { delete action; } } for ( const QString &name : mainViewActionNames ) { if (auto action = coll->action(name)) { action->setCheckable(true); } } KStandardAction::close(q, SLOT(close()), coll); KStandardAction::quit(q, SLOT(closeAndQuit()), coll); KStandardAction::configureToolbars(q, SLOT(configureToolbars()), coll); KStandardAction::keyBindings(q, SLOT(editKeybindings()), coll); KStandardAction::preferences(qApp, SLOT(openOrRaiseConfigDialog()), coll); focusToClickSearchAction = new QAction(i18n("Set Focus to Quick Search"), q); coll->addAction(QStringLiteral("focus_to_quickseach"), focusToClickSearchAction); coll->setDefaultShortcut(focusToClickSearchAction, QKeySequence(Qt::ALT | Qt::Key_Q)); connect(focusToClickSearchAction, SIGNAL(triggered(bool)), q, SLOT(slotFocusQuickSearch())); clipboadMenu = new ClipboardMenu(q); clipboadMenu->setMainWindow(q); clipboadMenu->clipboardMenu()->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"))); clipboadMenu->clipboardMenu()->setPopupMode(QToolButton::InstantPopup); coll->addAction(QStringLiteral("clipboard_menu"), clipboadMenu->clipboardMenu()); /* Add additional help actions for documentation */ const auto compendium = new DocAction(QIcon::fromTheme(QStringLiteral("gpg4win-compact")), i18n("Gpg4win Compendium"), i18nc("The Gpg4win compendium is only available" "at this point (24.7.2017) in german and english." "Please check with Gpg4win before translating this filename.", "gpg4win-compendium-en.pdf"), QStringLiteral("../share/gpg4win")); coll->addAction(QStringLiteral("help_doc_compendium"), compendium); /* Documentation centered around the german approved VS-NfD mode for official * RESTRICTED communication. This is only available in some distributions with * the focus on official communications. */ const auto symguide = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("Password-based encryption"), i18nc("Only available in German and English. Leave to English for other languages.", "handout_symmetric_encryption_gnupg_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd")); coll->addAction(QStringLiteral("help_doc_symenc"), symguide); const auto quickguide = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("Quickguide"), i18nc("Only available in German and English. Leave to English for other languages.", "handout_sign_encrypt_gnupg_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd")); coll->addAction(QStringLiteral("help_doc_quickguide"), quickguide); const auto vsa10573 = new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")), i18n("SecOps VSA-10573"), i18nc("Only available in German and English. Leave to English for other languages.", "BSI-VSA-10573-ENG_secops-20220207.pdf"), QStringLiteral("../share/doc/gnupg-vsd")); coll->addAction(QStringLiteral("help_doc_vsa10573"), vsa10573); const auto vsa10584 = new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")), i18n("SecOps VSA-10584"), i18nc("Only available in German and English. Leave to English for other languages.", "BSI-VSA-10584-ENG_secops-20220207.pdf"), QStringLiteral("../share/doc/gnupg-vsd")); coll->addAction(QStringLiteral("help_doc_vsa10584"), vsa10584); const auto man_gpg = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("GnuPG Manual"), QStringLiteral("gnupg.pdf"), QStringLiteral("../share/doc/gnupg")); coll->addAction(QStringLiteral("help_doc_gnupg"), man_gpg); q->setStandardToolBarMenuEnabled(true); controller.createActions(coll); ui.searchTab->tabWidget()->createActions(coll); } void MainWindow::Private::slotConfigCommitted() { controller.updateConfig(); updateStatusBar(); } void MainWindow::closeEvent(QCloseEvent *e) { // KMainWindow::closeEvent() insists on quitting the application, // so do not let it touch the event... qCDebug(KLEOPATRA_LOG); if (d->controller.hasRunningCommands()) { if (d->controller.shutdownWarningRequired()) { const int ret = KMessageBox::warningContinueCancel(this, i18n("There are still some background operations ongoing. " "These will be terminated when closing the window. " "Proceed?"), i18n("Ongoing Background Tasks")); if (ret != KMessageBox::Continue) { e->ignore(); return; } } d->controller.cancelCommands(); if (d->controller.hasRunningCommands()) { // wait for them to be finished: setEnabled(false); QEventLoop ev; QTimer::singleShot(100ms, &ev, &QEventLoop::quit); connect(&d->controller, &KeyListController::commandsExecuting, &ev, &QEventLoop::quit); ev.exec(); if (d->controller.hasRunningCommands()) qCWarning(KLEOPATRA_LOG) << "controller still has commands running, this may crash now..."; setEnabled(true); } } if (isQuitting || qApp->isSavingSession()) { d->ui.searchTab->tabWidget()->saveViews(KSharedConfig::openConfig().data()); KConfigGroup grp(KConfigGroup(KSharedConfig::openConfig(), autoSaveGroup())); saveMainWindowSettings(grp); e->accept(); } else { e->ignore(); hide(); } } void MainWindow::showEvent(QShowEvent *e) { KXmlGuiWindow::showEvent(e); if (d->firstShow) { d->ui.searchTab->tabWidget()->loadViews(KSharedConfig::openConfig().data()); d->firstShow = false; } if (!savedGeometry.isEmpty()) { restoreGeometry(savedGeometry); } } void MainWindow::hideEvent(QHideEvent *e) { savedGeometry = saveGeometry(); KXmlGuiWindow::hideEvent(e); } void MainWindow::importCertificatesFromFile(const QStringList &files) { if (!files.empty()) { d->createAndStart(files); } } static QStringList extract_local_files(const QMimeData *data) { const QList urls = data->urls(); // begin workaround KDE/Qt misinterpretation of text/uri-list QList::const_iterator end = urls.end(); if (urls.size() > 1 && !urls.back().isValid()) { --end; } // end workaround QStringList result; std::transform(urls.begin(), end, std::back_inserter(result), std::mem_fn(&QUrl::toLocalFile)); result.erase(std::remove_if(result.begin(), result.end(), std::mem_fn(&QString::isEmpty)), result.end()); return result; } static bool can_decode_local_files(const QMimeData *data) { if (!data) { return false; } return !extract_local_files(data).empty(); } void MainWindow::dragEnterEvent(QDragEnterEvent *e) { qCDebug(KLEOPATRA_LOG); if (can_decode_local_files(e->mimeData())) { e->acceptProposedAction(); } } void MainWindow::dropEvent(QDropEvent *e) { qCDebug(KLEOPATRA_LOG); if (!can_decode_local_files(e->mimeData())) { return; } e->setDropAction(Qt::CopyAction); const QStringList files = extract_local_files(e->mimeData()); const unsigned int classification = classify(files); QMenu menu; QAction *const signEncrypt = menu.addAction(i18n("Sign/Encrypt...")); QAction *const decryptVerify = mayBeAnyMessageType(classification) ? menu.addAction(i18n("Decrypt/Verify...")) : nullptr; if (signEncrypt || decryptVerify) { menu.addSeparator(); } QAction *const importCerts = mayBeAnyCertStoreType(classification) ? menu.addAction(i18n("Import Certificates")) : nullptr; QAction *const importCRLs = mayBeCertificateRevocationList(classification) ? menu.addAction(i18n("Import CRLs")) : nullptr; if (importCerts || importCRLs) { menu.addSeparator(); } if (!signEncrypt && !decryptVerify && !importCerts && !importCRLs) { return; } menu.addAction(i18n("Cancel")); const QAction *const chosen = menu.exec(mapToGlobal(e->pos())); if (!chosen) { return; } if (chosen == signEncrypt) { d->createAndStart(files); } else if (chosen == decryptVerify) { d->createAndStart(files); } else if (chosen == importCerts) { d->createAndStart(files); } else if (chosen == importCRLs) { d->createAndStart(files); } e->accept(); } void MainWindow::readProperties(const KConfigGroup &cg) { qCDebug(KLEOPATRA_LOG); KXmlGuiWindow::readProperties(cg); setHidden(cg.readEntry("hidden", false)); } void MainWindow::saveProperties(KConfigGroup &cg) { qCDebug(KLEOPATRA_LOG); KXmlGuiWindow::saveProperties(cg); cg.writeEntry("hidden", isHidden()); } #include "mainwindow.moc" #include "moc_mainwindow.cpp" diff --git a/src/newcertificatewizard/advancedsettingsdialog.cpp b/src/newcertificatewizard/advancedsettingsdialog.cpp index 5c900d8e5..13e72b59b 100644 --- a/src/newcertificatewizard/advancedsettingsdialog.cpp +++ b/src/newcertificatewizard/advancedsettingsdialog.cpp @@ -1,1113 +1,1114 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/advancedsettingsdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "advancedsettingsdialog_p.h" #include "keyalgo_p.h" #include "listwidget.h" #include "utils/scrollarea.h" #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace GpgME; static const char RSA_KEYSIZES_ENTRY[] = "RSAKeySizes"; static const char DSA_KEYSIZES_ENTRY[] = "DSAKeySizes"; static const char ELG_KEYSIZES_ENTRY[] = "ELGKeySizes"; static const char RSA_KEYSIZE_LABELS_ENTRY[] = "RSAKeySizeLabels"; static const char DSA_KEYSIZE_LABELS_ENTRY[] = "DSAKeySizeLabels"; static const char ELG_KEYSIZE_LABELS_ENTRY[] = "ELGKeySizeLabels"; static const char PGP_KEY_TYPE_ENTRY[] = "PGPKeyType"; static const char CMS_KEY_TYPE_ENTRY[] = "CMSKeyType"; // This should come from gpgme in the future // For now we only support the basic 2.1 curves and check // for GnuPG 2.1. The whole subkey / usage generation needs // new api and a reworked dialog. (ah 10.3.16) // EDDSA should be supported, too. static const QStringList curveNames { { QStringLiteral("brainpoolP256r1") }, { QStringLiteral("brainpoolP384r1") }, { QStringLiteral("brainpoolP512r1") }, { QStringLiteral("NIST P-256") }, { QStringLiteral("NIST P-384") }, { QStringLiteral("NIST P-521") }, }; namespace { static void set_keysize(QComboBox *cb, unsigned int strength) { if (!cb) { return; } const int idx = cb->findData(static_cast(strength)); if (idx >= 0) { cb->setCurrentIndex(idx); } } static unsigned int get_keysize(const QComboBox *cb) { if (!cb) { return 0; } const int idx = cb->currentIndex(); if (idx < 0) { return 0; } return cb->itemData(idx).toInt(); } static void set_curve(QComboBox *cb, const QString &curve) { if (!cb) { return; } const int idx = cb->findText(curve, Qt::MatchFixedString); if (idx >= 0) { cb->setCurrentIndex(idx); } } static QString get_curve(const QComboBox *cb) { if (!cb) { return QString(); } return cb->currentText(); } // Extract the algo information from default_pubkey_algo format // // and put it into the return values size, algo and curve. // // Values look like: // RSA-2048 // rsa2048/cert,sign+rsa2048/enc // brainpoolP256r1+brainpoolP256r1 static void parseAlgoString(const QString &algoString, int *size, Subkey::PubkeyAlgo *algo, QString &curve) { const auto split = algoString.split(QLatin1Char('/')); bool isEncrypt = split.size() == 2 && split[1].contains(QLatin1String("enc")); // Normalize const auto lowered = split[0].toLower().remove(QLatin1Char('-')); if (!algo || !size) { return; } *algo = Subkey::AlgoUnknown; if (lowered.startsWith(QLatin1String("rsa"))) { *algo = Subkey::AlgoRSA; } else if (lowered.startsWith(QLatin1String("dsa"))) { *algo = Subkey::AlgoDSA; } else if (lowered.startsWith(QLatin1String("elg"))) { *algo = Subkey::AlgoELG; } if (*algo != Subkey::AlgoUnknown) { bool ok; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) *size = lowered.rightRef(lowered.size() - 3).toInt(&ok); #else *size = QStringView(lowered).right(lowered.size() - 3).toInt(&ok); #endif if (!ok) { qCWarning(KLEOPATRA_LOG) << "Could not extract size from: " << lowered; *size = 3072; } return; } // Now the ECC Algorithms if (lowered.startsWith(QLatin1String("ed25519"))) { // Special handling for this as technically // this is a cv25519 curve used for EDDSA if (isEncrypt) { curve = QLatin1String("cv25519"); *algo = Subkey::AlgoECDH; } else { curve = split[0]; *algo = Subkey::AlgoEDDSA; } return; } if (lowered.startsWith(QLatin1String("cv25519")) || lowered.startsWith(QLatin1String("nist")) || lowered.startsWith(QLatin1String("brainpool")) || lowered.startsWith(QLatin1String("secp"))) { curve = split[0]; *algo = isEncrypt ? Subkey::AlgoECDH : Subkey::AlgoECDSA; return; } qCWarning(KLEOPATRA_LOG) << "Failed to parse default_pubkey_algo:" << algoString; } enum class OnUnlimitedValidity { ReturnInvalidDate, ReturnInternalDefault }; QDate defaultExpirationDate(OnUnlimitedValidity onUnlimitedValidity) { QDate expirationDate{}; const auto settings = Kleo::Settings{}; const auto defaultExpirationInDays = settings.validityPeriodInDays(); if (defaultExpirationInDays > 0) { expirationDate = QDate::currentDate().addDays(defaultExpirationInDays); } else if (defaultExpirationInDays < 0 || onUnlimitedValidity == OnUnlimitedValidity::ReturnInternalDefault) { expirationDate = QDate::currentDate().addYears(2); } return expirationDate; } } struct AdvancedSettingsDialog::UI { QTabWidget *tabWidget = nullptr; QRadioButton *rsaRB = nullptr; QComboBox *rsaKeyStrengthCB = nullptr; QCheckBox *rsaSubCB = nullptr; QComboBox *rsaKeyStrengthSubCB = nullptr; QRadioButton *dsaRB = nullptr; QComboBox *dsaKeyStrengthCB = nullptr; QCheckBox *elgCB = nullptr; QComboBox *elgKeyStrengthCB = nullptr; QRadioButton *ecdsaRB = nullptr; QComboBox *ecdsaKeyCurvesCB = nullptr; QCheckBox *ecdhCB = nullptr; QComboBox *ecdhKeyCurvesCB = nullptr; QCheckBox *certificationCB = nullptr; QCheckBox *signingCB = nullptr; QCheckBox *encryptionCB = nullptr; QCheckBox *authenticationCB = nullptr; QCheckBox *expiryCB = nullptr; KDateComboBox *expiryDE = nullptr; ScrollArea *personalTab = nullptr; QGroupBox *uidGB = nullptr; Kleo::NewCertificateUi::ListWidget *uidLW = nullptr; QGroupBox *emailGB = nullptr; Kleo::NewCertificateUi::ListWidget *emailLW = nullptr; QGroupBox *dnsGB = nullptr; Kleo::NewCertificateUi::ListWidget *dnsLW = nullptr; QGroupBox *uriGB = nullptr; Kleo::NewCertificateUi::ListWidget *uriLW = nullptr; QDialogButtonBox *buttonBox = nullptr; UI(QDialog *parent) { parent->setWindowTitle(i18nc("@title:window", "Advanced Settings")); auto mainLayout = new QVBoxLayout{parent}; tabWidget = new QTabWidget{parent}; { auto technicalTab = new ScrollArea{tabWidget}; technicalTab->setFocusPolicy(Qt::NoFocus); technicalTab->setFrameStyle(QFrame::NoFrame); technicalTab->setBackgroundRole(parent->backgroundRole()); technicalTab->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); technicalTab->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto tabLayout = qobject_cast(technicalTab->widget()->layout()); { auto groupBox = new QGroupBox{i18nc("@title:group", "Key Material"), technicalTab}; auto groupBoxGrid = new QGridLayout{groupBox}; int row = 0; rsaRB = new QRadioButton{i18nc("@option:radio", "RSA"), groupBox}; rsaRB->setChecked(false); groupBoxGrid->addWidget(rsaRB, row, 0, 1, 2); rsaKeyStrengthCB = new QComboBox{groupBox}; rsaKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(rsaKeyStrengthCB, row, 2, 1, 1); row++; auto subKeyIndentation = new QSpacerItem(13, 13, QSizePolicy::Fixed, QSizePolicy::Minimum); groupBoxGrid->addItem(subKeyIndentation, row, 0, 1, 1); rsaSubCB = new QCheckBox{i18nc("@option:check", "+ RSA"), groupBox}; rsaSubCB->setEnabled(true); groupBoxGrid->addWidget(rsaSubCB, row, 1, 1, 1); rsaKeyStrengthSubCB = new QComboBox{groupBox}; rsaKeyStrengthSubCB->setEnabled(false); groupBoxGrid->addWidget(rsaKeyStrengthSubCB, row, 2, 1, 1); row++; dsaRB = new QRadioButton{i18nc("@option:radio", "DSA"), groupBox}; groupBoxGrid->addWidget(dsaRB, row, 0, 1, 2); dsaKeyStrengthCB = new QComboBox{groupBox}; dsaKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(dsaKeyStrengthCB, row, 2, 1, 1); row++; elgCB = new QCheckBox{i18nc("@option:check", "+ Elgamal"), groupBox}; elgCB->setToolTip(i18nc("@info:tooltip", "This subkey is required for encryption.")); elgCB->setEnabled(true); groupBoxGrid->addWidget(elgCB, row, 1, 1, 1); elgKeyStrengthCB = new QComboBox{groupBox}; elgKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(elgKeyStrengthCB, row, 2, 1, 1); row++; ecdsaRB = new QRadioButton{i18nc("@option:radio", "ECDSA"), groupBox}; groupBoxGrid->addWidget(ecdsaRB, row, 0, 1, 2); ecdsaKeyCurvesCB = new QComboBox{groupBox}; ecdsaKeyCurvesCB->setEnabled(false); groupBoxGrid->addWidget(ecdsaKeyCurvesCB, row, 2, 1, 1); row++; ecdhCB = new QCheckBox{i18nc("@option:check", "+ ECDH"), groupBox}; ecdhCB->setToolTip(i18nc("@info:tooltip", "This subkey is required for encryption.")); ecdhCB->setEnabled(true); groupBoxGrid->addWidget(ecdhCB, row, 1, 1, 1); ecdhKeyCurvesCB = new QComboBox{groupBox}; ecdhKeyCurvesCB->setEnabled(false); groupBoxGrid->addWidget(ecdhKeyCurvesCB, row, 2, 1, 1); groupBoxGrid->setColumnStretch(3, 1); tabLayout->addWidget(groupBox); } { auto groupBox = new QGroupBox{i18nc("@title:group", "Certificate Usage"), technicalTab}; auto groupBoxGrid = new QGridLayout{groupBox}; int row = 0; signingCB = new QCheckBox{i18nc("@option:check", "Signing"), groupBox}; signingCB->setChecked(true); groupBoxGrid->addWidget(signingCB, row, 0, 1, 1); certificationCB = new QCheckBox{i18nc("@option:check", "Certification"), groupBox}; groupBoxGrid->addWidget(certificationCB, row, 1, 1, 1); row++; encryptionCB = new QCheckBox{i18nc("@option:check", "Encryption"), groupBox}; encryptionCB->setChecked(true); groupBoxGrid->addWidget(encryptionCB, row, 0, 1, 1); authenticationCB = new QCheckBox{i18nc("@option:check", "Authentication"), groupBox}; groupBoxGrid->addWidget(authenticationCB, row, 1, 1, 1); row++; { auto hbox = new QHBoxLayout; expiryCB = new QCheckBox{i18nc("@option:check", "Valid until:"), groupBox}; hbox->addWidget(expiryCB); expiryDE = new KDateComboBox(groupBox); hbox->addWidget(expiryDE, 1); groupBoxGrid->addLayout(hbox, row, 0, 1, 2); } tabLayout->addWidget(groupBox); } tabLayout->addStretch(1); tabWidget->addTab(technicalTab, i18nc("@title:tab", "Technical Details")); } { personalTab = new ScrollArea{tabWidget}; personalTab->setFocusPolicy(Qt::NoFocus); personalTab->setFrameStyle(QFrame::NoFrame); personalTab->setBackgroundRole(parent->backgroundRole()); personalTab->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); personalTab->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(personalTab->widget()->layout()); auto tabGrid = new QGridLayout; uidGB = new QGroupBox{i18nc("@title:group", "Additional User IDs"), personalTab}; { auto layout = new QVBoxLayout{uidGB}; uidLW = new Kleo::NewCertificateUi::ListWidget{uidGB}; layout->addWidget(uidLW); } tabGrid->addWidget(uidGB, 0, 0, 1, 2); emailGB = new QGroupBox{i18nc("@title:group", "EMail Addresses"), personalTab}; { auto layout = new QVBoxLayout{emailGB}; emailLW = new Kleo::NewCertificateUi::ListWidget{emailGB}; layout->addWidget(emailLW); } tabGrid->addWidget(emailGB, 2, 0, 2, 1); dnsGB = new QGroupBox{i18nc("@title:group", "DNS Names"), personalTab}; { auto layout = new QVBoxLayout{dnsGB}; dnsLW = new Kleo::NewCertificateUi::ListWidget{dnsGB}; layout->addWidget(dnsLW); } tabGrid->addWidget(dnsGB, 2, 1, 1, 1); uriGB = new QGroupBox{i18nc("@title:group", "URIs"), personalTab}; { auto layout = new QVBoxLayout{uriGB}; uriLW = new Kleo::NewCertificateUi::ListWidget{uriGB}; layout->addWidget(uriLW); } tabGrid->addWidget(uriGB, 3, 1, 1, 1); scrollAreaLayout->addLayout(tabGrid); tabWidget->addTab(personalTab, i18nc("@title:tab", "Personal Details")); } mainLayout->addWidget(tabWidget); buttonBox = new QDialogButtonBox{parent}; buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); mainLayout->addWidget(buttonBox); } }; AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent) : QDialog{parent} , ui{new UI{this}} , mECCSupported{engineIsVersion(2, 1, 0)} , mEdDSASupported{engineIsVersion(2, 1, 15)} { qRegisterMetaType("Subkey::PubkeyAlgo"); const auto settings = Kleo::Settings{}; { const auto minimumExpiry = std::max(0, settings.validityPeriodInDaysMin()); ui->expiryDE->setMinimumDate(QDate::currentDate().addDays(minimumExpiry)); } { const auto maximumExpiry = settings.validityPeriodInDaysMax(); if (maximumExpiry >= 0) { ui->expiryDE->setMaximumDate(std::max(ui->expiryDE->minimumDate(), QDate::currentDate().addDays(maximumExpiry))); } } if (unlimitedValidityIsAllowed()) { ui->expiryDE->setEnabled(ui->expiryCB->isChecked()); } else { ui->expiryCB->setEnabled(false); ui->expiryCB->setChecked(true); if (ui->expiryDE->maximumDate() == ui->expiryDE->minimumDate()) { // validity period is a fixed number of days ui->expiryDE->setEnabled(false); } } ui->expiryDE->setToolTip(validityPeriodHint(ui->expiryDE->minimumDate(), ui->expiryDE->maximumDate())); ui->emailLW->setDefaultValue(i18n("new email")); ui->dnsLW->setDefaultValue(i18n("new dns name")); ui->uriLW->setDefaultValue(i18n("new uri")); fillKeySizeComboBoxen(); connect(ui->rsaRB, &QAbstractButton::toggled, ui->rsaKeyStrengthCB, &QWidget::setEnabled); connect(ui->rsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->rsaSubCB, &QAbstractButton::toggled, ui->rsaKeyStrengthSubCB, &QWidget::setEnabled); connect(ui->rsaSubCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->dsaRB, &QAbstractButton::toggled, ui->dsaKeyStrengthCB, &QWidget::setEnabled); connect(ui->dsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->elgCB, &QAbstractButton::toggled, ui->elgKeyStrengthCB, &QWidget::setEnabled); connect(ui->elgCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->ecdsaRB, &QAbstractButton::toggled, ui->ecdsaKeyCurvesCB, &QWidget::setEnabled); connect(ui->ecdsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->ecdhCB, &QAbstractButton::toggled, ui->ecdhKeyCurvesCB, &QWidget::setEnabled); connect(ui->ecdhCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->signingCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotSigningAllowedToggled); connect(ui->encryptionCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotEncryptionAllowedToggled); connect(ui->expiryCB, &QAbstractButton::toggled, this, [this](bool checked) { ui->expiryDE->setEnabled(checked); if (checked && !ui->expiryDE->isValid()) { setExpiryDate(defaultExpirationDate(OnUnlimitedValidity::ReturnInternalDefault)); } }); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } AdvancedSettingsDialog::~AdvancedSettingsDialog() = default; QString AdvancedSettingsDialog::dateToString(const QDate &date) const { // workaround for QLocale using "yy" way too often for years // stolen from KDateComboBox const auto dateFormat = (locale().dateFormat(QLocale::ShortFormat) // .replace(QLatin1String{"yy"}, QLatin1String{"yyyy"}) .replace(QLatin1String{"yyyyyyyy"}, QLatin1String{"yyyy"})); return locale().toString(date, dateFormat); } QString AdvancedSettingsDialog::validityPeriodHint(const QDate &minDate, const QDate &maxDate) const { // Note: minDate is always valid const auto today = QDate::currentDate(); if (maxDate.isValid()) { if (maxDate == minDate) { return i18n("The validity period cannot be changed."); } else if (minDate == today) { return i18nc("... between today and .", "The validity period must end between today and %1.", dateToString(maxDate)); } else { return i18nc("... between and .", "The validity period must end between %1 and %2.", dateToString(minDate), dateToString(maxDate)); } } else { if (minDate == today) { return i18n("The validity period must end after today."); } else { return i18nc("... after .", "The validity period must end after %1.", dateToString(minDate)); } } } bool AdvancedSettingsDialog::unlimitedValidityIsAllowed() const { return !ui->expiryDE->maximumDate().isValid(); } void AdvancedSettingsDialog::setProtocol(GpgME::Protocol proto) { if (protocol == proto) { return; } protocol = proto; loadDefaults(); } void AdvancedSettingsDialog::setAdditionalUserIDs(const QStringList &items) { ui->uidLW->setItems(items); } QStringList AdvancedSettingsDialog::additionalUserIDs() const { return ui->uidLW->items(); } void AdvancedSettingsDialog::setAdditionalEMailAddresses(const QStringList &items) { ui->emailLW->setItems(items); } QStringList AdvancedSettingsDialog::additionalEMailAddresses() const { return ui->emailLW->items(); } void AdvancedSettingsDialog::setDnsNames(const QStringList &items) { ui->dnsLW->setItems(items); } QStringList AdvancedSettingsDialog::dnsNames() const { return ui->dnsLW->items(); } void AdvancedSettingsDialog::setUris(const QStringList &items) { ui->uriLW->setItems(items); } QStringList AdvancedSettingsDialog::uris() const { return ui->uriLW->items(); } void AdvancedSettingsDialog::setKeyStrength(unsigned int strength) { set_keysize(ui->rsaKeyStrengthCB, strength); set_keysize(ui->dsaKeyStrengthCB, strength); } unsigned int AdvancedSettingsDialog::keyStrength() const { return ui->dsaRB->isChecked() ? get_keysize(ui->dsaKeyStrengthCB) : ui->rsaRB->isChecked() ? get_keysize(ui->rsaKeyStrengthCB) : 0; } void AdvancedSettingsDialog::setKeyType(Subkey::PubkeyAlgo algo) { QRadioButton *const rb = is_rsa(algo) ? ui->rsaRB : is_dsa(algo) ? ui->dsaRB : is_ecdsa(algo) || is_eddsa(algo) ? ui->ecdsaRB : nullptr; if (rb) { rb->setChecked(true); } } Subkey::PubkeyAlgo AdvancedSettingsDialog::keyType() const { return ui->dsaRB->isChecked() ? Subkey::AlgoDSA : ui->rsaRB->isChecked() ? Subkey::AlgoRSA : ui->ecdsaRB->isChecked() ? ui->ecdsaKeyCurvesCB->currentText() == QLatin1String("ed25519") ? Subkey::AlgoEDDSA : Subkey::AlgoECDSA : Subkey::AlgoUnknown; } void AdvancedSettingsDialog::setKeyCurve(const QString &curve) { set_curve(ui->ecdsaKeyCurvesCB, curve); } QString AdvancedSettingsDialog::keyCurve() const { return get_curve(ui->ecdsaKeyCurvesCB); } void AdvancedSettingsDialog::setSubkeyType(Subkey::PubkeyAlgo algo) { ui->elgCB->setChecked(is_elg(algo)); ui->rsaSubCB->setChecked(is_rsa(algo)); ui->ecdhCB->setChecked(is_ecdh(algo)); } Subkey::PubkeyAlgo AdvancedSettingsDialog::subkeyType() const { if (ui->elgCB->isChecked()) { return Subkey::AlgoELG_E; } else if (ui->rsaSubCB->isChecked()) { return Subkey::AlgoRSA; } else if (ui->ecdhCB->isChecked()) { return Subkey::AlgoECDH; } return Subkey::AlgoUnknown; } void AdvancedSettingsDialog::setSubkeyCurve(const QString &curve) { set_curve(ui->ecdhKeyCurvesCB, curve); } QString AdvancedSettingsDialog::subkeyCurve() const { return get_curve(ui->ecdhKeyCurvesCB); } void AdvancedSettingsDialog::setSubkeyStrength(unsigned int strength) { if (subkeyType() == Subkey::AlgoRSA) { set_keysize(ui->rsaKeyStrengthSubCB, strength); } else { set_keysize(ui->elgKeyStrengthCB, strength); } } unsigned int AdvancedSettingsDialog::subkeyStrength() const { if (subkeyType() == Subkey::AlgoRSA) { return get_keysize(ui->rsaKeyStrengthSubCB); } return get_keysize(ui->elgKeyStrengthCB); } void AdvancedSettingsDialog::setSigningAllowed(bool on) { ui->signingCB->setChecked(on); } bool AdvancedSettingsDialog::signingAllowed() const { return ui->signingCB->isChecked(); } void AdvancedSettingsDialog::setEncryptionAllowed(bool on) { ui->encryptionCB->setChecked(on); } bool AdvancedSettingsDialog::encryptionAllowed() const { return ui->encryptionCB->isChecked(); } void AdvancedSettingsDialog::setCertificationAllowed(bool on) { ui->certificationCB->setChecked(on); } bool AdvancedSettingsDialog::certificationAllowed() const { return ui->certificationCB->isChecked(); } void AdvancedSettingsDialog::setAuthenticationAllowed(bool on) { ui->authenticationCB->setChecked(on); } bool AdvancedSettingsDialog::authenticationAllowed() const { return ui->authenticationCB->isChecked(); } QDate AdvancedSettingsDialog::forceDateIntoAllowedRange(QDate date) const { const auto minDate = ui->expiryDE->minimumDate(); if (minDate.isValid() && date < minDate) { date = minDate; } const auto maxDate = ui->expiryDE->maximumDate(); if (maxDate.isValid() && date > maxDate) { date = maxDate; } return date; } void AdvancedSettingsDialog::setExpiryDate(QDate date) { if (date.isValid()) { ui->expiryDE->setDate(forceDateIntoAllowedRange(date)); } else { // check if unlimited validity is allowed if (unlimitedValidityIsAllowed()) { ui->expiryDE->setDate(date); } } if (ui->expiryCB->isEnabled()) { ui->expiryCB->setChecked(ui->expiryDE->isValid()); } } QDate AdvancedSettingsDialog::expiryDate() const { return ui->expiryCB->isChecked() ? forceDateIntoAllowedRange(ui->expiryDE->date()) : QDate(); } void AdvancedSettingsDialog::slotKeyMaterialSelectionChanged() { const unsigned int algo = keyType(); const unsigned int sk_algo = subkeyType(); if (protocol == OpenPGP) { // first update the enabled state, but only if key type is not forced if (!keyTypeImmutable) { ui->elgCB->setEnabled(is_dsa(algo)); ui->rsaSubCB->setEnabled(is_rsa(algo)); ui->ecdhCB->setEnabled(is_ecdsa(algo) || is_eddsa(algo)); if (is_rsa(algo)) { ui->encryptionCB->setEnabled(true); ui->signingCB->setEnabled(true); ui->authenticationCB->setEnabled(true); if (is_rsa(sk_algo)) { ui->encryptionCB->setEnabled(false); } else { ui->encryptionCB->setEnabled(true); } } else if (is_dsa(algo)) { ui->encryptionCB->setEnabled(false); } else if (is_ecdsa(algo) || is_eddsa(algo)) { ui->signingCB->setEnabled(true); ui->authenticationCB->setEnabled(true); ui->encryptionCB->setEnabled(false); } } // then update the checked state if (sender() == ui->dsaRB || sender() == ui->rsaRB || sender() == ui->ecdsaRB) { ui->elgCB->setChecked(is_dsa(algo)); ui->ecdhCB->setChecked(is_ecdsa(algo) || is_eddsa(algo)); ui->rsaSubCB->setChecked(is_rsa(algo)); } if (is_rsa(algo)) { ui->encryptionCB->setChecked(true); ui->signingCB->setChecked(true); if (is_rsa(sk_algo)) { ui->encryptionCB->setChecked(true); } } else if (is_dsa(algo)) { if (is_elg(sk_algo)) { ui->encryptionCB->setChecked(true); } else { ui->encryptionCB->setChecked(false); } } else if (is_ecdsa(algo) || is_eddsa(algo)) { ui->signingCB->setChecked(true); ui->encryptionCB->setChecked(is_ecdh(sk_algo)); } } else { //assert( is_rsa( keyType() ) ); // it can happen through misconfiguration by the admin that no key type is selectable at all } } void AdvancedSettingsDialog::slotSigningAllowedToggled(bool on) { if (!on && protocol == CMS && !encryptionAllowed()) { setEncryptionAllowed(true); } } void AdvancedSettingsDialog::slotEncryptionAllowedToggled(bool on) { if (!on && protocol == CMS && !signingAllowed()) { setSigningAllowed(true); } } static void fill_combobox(QComboBox &cb, const QList &sizes, const QStringList &labels) { cb.clear(); for (int i = 0, end = sizes.size(); i != end; ++i) { const int size = std::abs(sizes[i]); /* As we respect the defaults configurable in GnuPG, and we also have configurable * defaults in Kleopatra its difficult to print out "default" here. To avoid confusion * about that its better not to show any default indication. */ cb.addItem(i < labels.size() && !labels[i].trimmed().isEmpty() ? i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit)", "%2 (%1 bits)", size, labels[i].trimmed()) : i18ncp("%1: key size in bits", "1 bit", "%1 bits", size), size); if (sizes[i] < 0) { cb.setCurrentIndex(cb.count() - 1); } } } void AdvancedSettingsDialog::fillKeySizeComboBoxen() { const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); QList rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << 2048 << -3072 << 4096); - if (Kleo::gnupgUsesDeVsCompliance()) { + if (DeVSCompliance::isActive()) { rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << -3072 << 4096); } const QList dsaKeySizes = config.readEntry(DSA_KEYSIZES_ENTRY, QList() << -2048); const QList elgKeySizes = config.readEntry(ELG_KEYSIZES_ENTRY, QList() << -2048 << 3072 << 4096); const QStringList rsaKeySizeLabels = config.readEntry(RSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList dsaKeySizeLabels = config.readEntry(DSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList elgKeySizeLabels = config.readEntry(ELG_KEYSIZE_LABELS_ENTRY, QStringList()); fill_combobox(*ui->rsaKeyStrengthCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui->rsaKeyStrengthSubCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui->dsaKeyStrengthCB, dsaKeySizes, dsaKeySizeLabels); fill_combobox(*ui->elgKeyStrengthCB, elgKeySizes, elgKeySizeLabels); if (mEdDSASupported) { // If supported we recommend cv25519 ui->ecdsaKeyCurvesCB->addItem(QStringLiteral("ed25519")); ui->ecdhKeyCurvesCB->addItem(QStringLiteral("cv25519")); } ui->ecdhKeyCurvesCB->addItems(curveNames); ui->ecdsaKeyCurvesCB->addItems(curveNames); } // Try to load the default key type from GnuPG void AdvancedSettingsDialog::loadDefaultGnuPGKeyType() { const auto conf = QGpgME::cryptoConfig(); if (!conf) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig."; return; } const auto entry = getCryptoConfigEntry(conf, protocol == CMS ? "gpgsm" : "gpg", "default_pubkey_algo"); if (!entry) { qCDebug(KLEOPATRA_LOG) << "GnuPG does not have default key type. Fallback to RSA"; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); return; } qCDebug(KLEOPATRA_LOG) << "Have default key type: " << entry->stringValue(); // Format is [/usage]+[/usage] const auto split = entry->stringValue().split(QLatin1Char('+')); int size = 0; Subkey::PubkeyAlgo algo = Subkey::AlgoUnknown; QString curve; parseAlgoString(split[0], &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setKeyType(algo); if (is_rsa(algo) || is_elg(algo) || is_dsa(algo)) { setKeyStrength(size); } else { setKeyCurve(curve); } { auto algoString = (split.size() == 2) ? split[1] : split[0]; // If it has no usage we assume encrypt subkey if (!algoString.contains(QLatin1Char('/'))) { algoString += QStringLiteral("/enc"); } parseAlgoString(algoString, &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setSubkeyType(algo); if (is_rsa(algo) || is_elg(algo)) { setSubkeyStrength(size); } else { setSubkeyCurve(curve); } } } void AdvancedSettingsDialog::loadDefaultKeyType() { if (protocol != CMS && protocol != OpenPGP) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); const QString entry = protocol == CMS ? QLatin1String(CMS_KEY_TYPE_ENTRY) : QLatin1String(PGP_KEY_TYPE_ENTRY); const QString keyType = config.readEntry(entry).trimmed().toUpper(); if (protocol == OpenPGP && keyType == QLatin1String("DSA")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoUnknown); } else if (protocol == OpenPGP && keyType == QLatin1String("DSA+ELG")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoELG_E); } else if (keyType.isEmpty() && engineIsVersion(2, 1, 17)) { loadDefaultGnuPGKeyType(); } else { if (!keyType.isEmpty() && keyType != QLatin1String("RSA")) qCWarning(KLEOPATRA_LOG) << "invalid value \"" << qPrintable(keyType) << "\" for entry \"[CertificateCreationWizard]" << qPrintable(entry) << "\""; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); } keyTypeImmutable = config.isEntryImmutable(entry); } void AdvancedSettingsDialog::loadDefaultExpiration() { if (protocol != OpenPGP) { return; } if (unlimitedValidityIsAllowed()) { setExpiryDate(defaultExpirationDate(OnUnlimitedValidity::ReturnInvalidDate)); } else { setExpiryDate(defaultExpirationDate(OnUnlimitedValidity::ReturnInternalDefault)); } } void AdvancedSettingsDialog::loadDefaults() { loadDefaultKeyType(); loadDefaultExpiration(); updateWidgetVisibility(); } void AdvancedSettingsDialog::updateWidgetVisibility() { // Personal Details Page if (protocol == OpenPGP) { // ### hide until multi-uid is implemented if (ui->tabWidget->indexOf(ui->personalTab) != -1) { ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->personalTab)); } } else { if (ui->tabWidget->indexOf(ui->personalTab) == -1) { ui->tabWidget->addTab(ui->personalTab, i18nc("@title:tab", "Personal Details")); } } ui->uidGB->setVisible(protocol == OpenPGP); ui->uidGB->setEnabled(false); ui->uidGB->setToolTip(i18nc("@info:tooltip", "Adding more than one user ID is not yet implemented.")); ui->emailGB->setVisible(protocol == CMS); ui->dnsGB->setVisible(protocol == CMS); ui->uriGB->setVisible(protocol == CMS); // Technical Details Page ui->ecdhCB->setVisible(mECCSupported); ui->ecdhKeyCurvesCB->setVisible(mECCSupported); ui->ecdsaKeyCurvesCB->setVisible(mECCSupported); ui->ecdsaRB->setVisible(mECCSupported); if (mEdDSASupported) { // We use the same radio button for EdDSA as we use for // ECDSA GnuPG does the same and this is really super technical // land. ui->ecdsaRB->setText(QStringLiteral("ECDSA/EdDSA")); } - const bool deVsHack = Kleo::gnupgUsesDeVsCompliance(); + const bool deVsHack = DeVSCompliance::isActive(); if (deVsHack) { // GnuPG Provides no API to query which keys are compliant for // a mode. If we request a different one it will error out so // we have to remove the options. // // Does anyone want to use NIST anyway? int i; while ((i = ui->ecdsaKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui->ecdsaKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui->ecdsaKeyCurvesCB->removeItem(i); } while ((i = ui->ecdhKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui->ecdhKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui->ecdhKeyCurvesCB->removeItem(i); } } ui->certificationCB->setVisible(protocol == OpenPGP); // gpgsm limitation? ui->authenticationCB->setVisible(protocol == OpenPGP); if (keyTypeImmutable) { ui->rsaRB->setEnabled(false); ui->rsaSubCB->setEnabled(false); ui->dsaRB->setEnabled(false); ui->elgCB->setEnabled(false); ui->ecdsaRB->setEnabled(false); ui->ecdhCB->setEnabled(false); // force usage if key type is forced ui->certificationCB->setEnabled(false); ui->signingCB->setEnabled(false); ui->encryptionCB->setEnabled(false); ui->authenticationCB->setEnabled(false); } else { ui->rsaRB->setEnabled(true); ui->rsaSubCB->setEnabled(protocol == OpenPGP); ui->dsaRB->setEnabled(protocol == OpenPGP && !deVsHack); ui->elgCB->setEnabled(protocol == OpenPGP && !deVsHack); ui->ecdsaRB->setEnabled(protocol == OpenPGP); ui->ecdhCB->setEnabled(protocol == OpenPGP); if (protocol == OpenPGP) { // OpenPGP keys must have certify capability ui->certificationCB->setEnabled(false); } if (protocol == CMS) { ui->encryptionCB->setEnabled(true); ui->rsaKeyStrengthSubCB->setEnabled(false); } } if (protocol == OpenPGP) { // OpenPGP keys must have certify capability ui->certificationCB->setChecked(true); } if (protocol == CMS) { ui->rsaSubCB->setChecked(false); } ui->expiryDE->setVisible(protocol == OpenPGP); ui->expiryCB->setVisible(protocol == OpenPGP); slotKeyMaterialSelectionChanged(); } template bool focusFirstButtonIf(const std::vector &buttons, UnaryPredicate p) { auto it = std::find_if(std::begin(buttons), std::end(buttons), p); if (it != std::end(buttons)) { (*it)->setFocus(); return true; } return false; } static bool focusFirstCheckedButton(const std::vector &buttons) { return focusFirstButtonIf(buttons, [](auto btn) { return btn && btn->isEnabled() && btn->isChecked(); }); } static bool focusFirstEnabledButton(const std::vector &buttons) { return focusFirstButtonIf(buttons, [](auto btn) { return btn && btn->isEnabled(); }); } void AdvancedSettingsDialog::setInitialFocus() { // first try the key type radio buttons if (focusFirstCheckedButton({ui->rsaRB, ui->dsaRB, ui->ecdsaRB})) { return; } // then try the usage check boxes and the expiration check box if (focusFirstEnabledButton({ui->signingCB, ui->certificationCB, ui->encryptionCB, ui->authenticationCB, ui->expiryCB})) { return; } // finally, focus the OK button ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); } void AdvancedSettingsDialog::showEvent(QShowEvent *event) { if (isFirstShowEvent) { setInitialFocus(); isFirstShowEvent = false; } QDialog::showEvent(event); } diff --git a/src/selftest/compliancecheck.cpp b/src/selftest/compliancecheck.cpp index c0fb10518..1818ce828 100644 --- a/src/selftest/compliancecheck.cpp +++ b/src/selftest/compliancecheck.cpp @@ -1,68 +1,69 @@ /* -*- mode: c++; c-basic-offset:4 -*- selftest/compliancecheck.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "compliancecheck.h" #include "implementation_p.h" +#include #include #include #include using namespace Kleo; using namespace Kleo::_detail; namespace { class DeVsComplianceCheck : public SelfTestImplementation { public: explicit DeVsComplianceCheck() : SelfTestImplementation(i18nc("@title %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "%1?", Formatting::deVsString())) + "%1?", DeVSCompliance::name(true))) { runTest(); } void runTest() { - m_skipped = !gnupgUsesDeVsCompliance(); + m_skipped = !DeVSCompliance::isActive(); if (m_skipped) { m_explanation = xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "GnuPG is not configured for %1 communication.", Formatting::deVsString()); + "GnuPG is not configured for %1 communication.", DeVSCompliance::name(true)); return; } - m_passed = gnupgIsDeVsCompliant(); + m_passed = DeVSCompliance::isCompliant(); if (m_passed) { return; } - m_error = Formatting::deVsString(m_passed); + m_error = DeVSCompliance::name(m_passed); m_explanation = xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "The GnuPG system used by Kleopatra is not %1.", Formatting::deVsString()); + "The GnuPG system used by Kleopatra is not %1.", DeVSCompliance::name(true)); m_proposedFix = xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "Install a version of GnuPG that is %1.", Formatting::deVsString()); + "Install a version of GnuPG that is %1.", DeVSCompliance::name(true)); } }; } std::shared_ptr Kleo::makeDeVSComplianceCheckSelfTest() { return std::make_shared(); } diff --git a/src/view/padwidget.cpp b/src/view/padwidget.cpp index dbae08d6e..0cebade54 100644 --- a/src/view/padwidget.cpp +++ b/src/view/padwidget.cpp @@ -1,546 +1,547 @@ /* -*- mode: c++; c-basic-offset:4 -*- padwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "padwidget.h" #include "kleopatra_debug.h" #include #include #include +#include #include #include #include #include "crypto/gui/signencryptwidget.h" #include "crypto/gui/resultitemwidget.h" #include "crypto/signencrypttask.h" #include "crypto/decryptverifytask.h" #include #include "utils/input.h" #include "utils/output.h" #include "commands/importcertificatefromdatacommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; static GpgME::Protocol getProtocol(const std::shared_ptr &result) { const auto dvResult = dynamic_cast(result.get()); if (dvResult) { for (const auto &key: KeyCache::instance()->findRecipients(dvResult->decryptionResult())) { return key.protocol(); } for (const auto &key: KeyCache::instance()->findSigners(dvResult->verificationResult())) { return key.protocol(); } } return GpgME::UnknownProtocol; } class PadWidget::Private { friend class ::Kleo::PadWidget; public: Private(PadWidget *qq): q(qq), mEdit(new QTextEdit), mCryptBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit-sign-encrypt")), i18n("Sign / Encrypt Notepad"))), mDecryptBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit-decrypt-verify")), i18n("Decrypt / Verify Notepad"))), mRevertBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Revert"))), mMessageWidget{new KMessageWidget}, mAdditionalInfoLabel(new QLabel), mSigEncWidget(new SignEncryptWidget(nullptr, true)), mProgressBar(new QProgressBar), mProgressLabel(new QLabel), mLastResultWidget(nullptr), mPGPRB(nullptr), mCMSRB(nullptr), mImportProto(GpgME::UnknownProtocol) { auto vLay = new QVBoxLayout(q); auto btnLay = new QHBoxLayout; vLay->addLayout(btnLay); btnLay->addWidget(mCryptBtn); btnLay->addWidget(mDecryptBtn); btnLay->addWidget(mRevertBtn); mRevertBtn->setVisible(false); btnLay->addWidget(mAdditionalInfoLabel); btnLay->addStretch(-1); mMessageWidget->setMessageType(KMessageWidget::Warning); mMessageWidget->setIcon(q->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, q)); mMessageWidget->setText(i18n("Signing and encryption is not possible.")); mMessageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "You cannot use Kleopatra for signing or encryption " "because the GnuPG system used by Kleopatra is not %1.", - Formatting::deVsString())); + DeVSCompliance::name(true))); mMessageWidget->setCloseButtonVisible(false); mMessageWidget->setVisible(false); vLay->addWidget(mMessageWidget); mProgressBar->setRange(0, 0); mProgressBar->setVisible(false); mProgressLabel->setVisible(false); auto progLay = new QHBoxLayout; progLay->addWidget(mProgressLabel); progLay->addWidget(mProgressBar); mStatusLay = new QVBoxLayout; mStatusLay->addLayout(progLay); vLay->addLayout(mStatusLay, 0); auto tabWidget = new QTabWidget; vLay->addWidget(tabWidget, 1); tabWidget->addTab(mEdit, QIcon::fromTheme(QStringLiteral("edittext")), i18n("Notepad")); // The recipients area auto recipientsWidget = new QWidget; auto recipientsVLay = new QVBoxLayout(recipientsWidget); auto protocolSelectionLay = new QHBoxLayout; bool pgpOnly = KeyCache::instance()->pgpOnly(); if (!pgpOnly) { recipientsVLay->addLayout(protocolSelectionLay); } protocolSelectionLay->addWidget(new QLabel(i18n("

Protocol:

"))); protocolSelectionLay->addStretch(-1); // Once S/MIME is supported add radio for S/MIME here. recipientsVLay->addWidget(mSigEncWidget); tabWidget->addTab(recipientsWidget, QIcon::fromTheme(QStringLiteral("contact-new-symbolic")), i18n("Recipients")); mEdit->setPlaceholderText(i18n("Enter a message to encrypt or decrypt...")); auto fixedFont = QFont(QStringLiteral("Monospace")); fixedFont.setStyleHint(QFont::TypeWriter); // This does not work well // QFontDatabase::systemFont(QFontDatabase::FixedFont); mEdit->setFont(fixedFont); mEdit->setAcceptRichText(false); mEdit->setMinimumWidth(QFontMetrics(fixedFont).averageCharWidth() * 70); if (KeyCache::instance()->pgpOnly() || !Settings{}.cmsEnabled()) { mSigEncWidget->setProtocol(GpgME::OpenPGP); } else { auto grp = new QButtonGroup(q); auto mPGPRB = new QRadioButton(i18n("OpenPGP")); auto mCMSRB = new QRadioButton(i18n("S/MIME")); grp->addButton(mPGPRB); grp->addButton(mCMSRB); KConfigGroup config(KSharedConfig::openConfig(), "Notepad"); if (config.readEntry("wasCMS", false)) { mCMSRB->setChecked(true); mSigEncWidget->setProtocol(GpgME::CMS); } else { mPGPRB->setChecked(true); mSigEncWidget->setProtocol(GpgME::OpenPGP); } protocolSelectionLay->addWidget(mPGPRB); protocolSelectionLay->addWidget(mCMSRB); connect(mPGPRB, &QRadioButton::toggled, q, [this] (bool value) { if (value) { mSigEncWidget->setProtocol(GpgME::OpenPGP); } }); connect(mCMSRB, &QRadioButton::toggled, q, [this] (bool value) { if (value) { mSigEncWidget->setProtocol(GpgME::CMS); } }); } updateCommitButton(); connect(mEdit, &QTextEdit::textChanged, q, [this] () { updateCommitButton(); }); connect(mCryptBtn, &QPushButton::clicked, q, [this] () { if (mImportProto != GpgME::UnknownProtocol) { doImport(); } else { doEncryptSign(); } }); connect(mSigEncWidget, &SignEncryptWidget::operationChanged, q, [this] (const QString &) { updateCommitButton(); }); connect(mDecryptBtn, &QPushButton::clicked, q, [this] () { doDecryptVerify(); }); connect(mRevertBtn, &QPushButton::clicked, q, [this] () { revert(); }); } void revert() { mEdit->setPlainText(QString::fromUtf8(mInputData)); mRevertBtn->setVisible(false); } void updateRecipientsFromResult(const Kleo::Crypto::DecryptVerifyResult &result) { const auto decResult = result.decryptionResult(); for (const auto &recipient: decResult.recipients()) { if (!recipient.keyID()) { continue; } GpgME::Key key; if (strlen(recipient.keyID()) < 16) { key = KeyCache::instance()->findByShortKeyID(recipient.keyID()); } else { key = KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID()); } if (key.isNull()) { std::vector subids; subids.push_back(std::string(recipient.keyID())); for (const auto &subkey: KeyCache::instance()->findSubkeysByKeyID(subids)) { key = subkey.parent(); break; } } if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Unknown key" << recipient.keyID(); mSigEncWidget->addUnknownRecipient(recipient.keyID()); continue; } bool keyFound = false; for (const auto &existingKey: mSigEncWidget->recipients()) { if (existingKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp (existingKey.primaryFingerprint(), key.primaryFingerprint())) { keyFound = true; break; } } if (!keyFound) { mSigEncWidget->addRecipient(key); } } } void cryptDone(const std::shared_ptr &result) { updateCommitButton(); mProgressBar->setVisible(false); mProgressLabel->setVisible(false); mLastResultWidget = new ResultItemWidget(result); mLastResultWidget->showCloseButton(true); mStatusLay->addWidget(mLastResultWidget); connect(mLastResultWidget, &ResultItemWidget::closeButtonClicked, q, [this] () { removeLastResultItem(); }); // Check result protocol if (mPGPRB) { auto proto = getProtocol(result); if (proto == GpgME::UnknownProtocol) { proto = mPGPRB->isChecked() ? GpgME::OpenPGP : GpgME::CMS; } else if (proto == GpgME::OpenPGP) { mPGPRB->setChecked(true); } else if (proto == GpgME::CMS) { mCMSRB->setChecked(true); } KConfigGroup config(KSharedConfig::openConfig(), "Notepad"); config.writeEntry("wasCMS", proto == GpgME::CMS); } if (result->errorCode()) { if (!result->errorString().isEmpty()) { KMessageBox::error(q, result->errorString(), i18nc("@title", "Error in crypto action")); } return; } mEdit->setPlainText(QString::fromUtf8(mOutputData)); mOutputData.clear(); mRevertBtn->setVisible(true); mDecryptBtn->setEnabled(true); const auto decryptVerifyResult = dynamic_cast(result.get()); if (decryptVerifyResult) { updateRecipientsFromResult(*decryptVerifyResult); } } void doDecryptVerify() { doCryptoCommon(); mSigEncWidget->clearAddedRecipients(); mProgressLabel->setText(i18n("Decrypt / Verify") + QStringLiteral("...")); auto input = Input::createFromByteArray(&mInputData, i18n("Notepad")); auto output = Output::createFromByteArray(&mOutputData, i18n("Notepad")); AbstractDecryptVerifyTask *task; auto classification = input->classification(); if (classification & Class::OpaqueSignature || classification & Class::ClearsignedMessage) { auto verifyTask = new VerifyOpaqueTask(); verifyTask->setInput(input); verifyTask->setOutput(output); task = verifyTask; } else { auto decTask = new DecryptVerifyTask(); decTask->setInput(input); decTask->setOutput(output); task = decTask; } try { task->autodetectProtocolFromInput(); } catch (const Kleo::Exception &e) { KMessageBox::error(q, e.message(), i18nc("@title", "Error in crypto action")); updateCommitButton(); mProgressBar->setVisible(false); mProgressLabel->setVisible(false); return; } connect (task, &Task::result, q, [this, task] (const std::shared_ptr &result) { qCDebug(KLEOPATRA_LOG) << "Decrypt / Verify done. Err:" << result->errorCode(); task->deleteLater(); cryptDone(result); }); task->start(); } void removeLastResultItem() { if (mLastResultWidget) { mStatusLay->removeWidget(mLastResultWidget); delete mLastResultWidget; mLastResultWidget = nullptr; } } void doCryptoCommon() { mCryptBtn->setEnabled(false); mDecryptBtn->setEnabled(false); mProgressBar->setVisible(true); mProgressLabel->setVisible(true); mInputData = mEdit->toPlainText().toUtf8(); removeLastResultItem(); } void doEncryptSign() { - if (Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()) { + if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) { KMessageBox::sorry(q->topLevelWidget(), xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "Sorry! You cannot use Kleopatra for signing or encryption " "because the GnuPG system used by Kleopatra is not %1.", - Formatting::deVsString())); + DeVSCompliance::name(true))); return; } doCryptoCommon(); mProgressLabel->setText(mSigEncWidget->currentOp() + QStringLiteral("...")); auto input = Input::createFromByteArray(&mInputData, i18n("Notepad")); auto output = Output::createFromByteArray(&mOutputData, i18n("Notepad")); auto task = new SignEncryptTask(); task->setInput(input); task->setOutput(output); const auto sigKey = mSigEncWidget->signKey(); const std::vector recipients = mSigEncWidget->recipients(); const bool encrypt = mSigEncWidget->encryptSymmetric() || !recipients.empty(); const bool sign = !sigKey.isNull(); if (sign) { task->setSign(true); std::vector signVector; signVector.push_back(sigKey); task->setSigners(signVector); } else { task->setSign(false); } task->setEncrypt(encrypt); task->setRecipients(recipients); task->setEncryptSymmetric(mSigEncWidget->encryptSymmetric()); task->setAsciiArmor(true); if (sign && !encrypt && sigKey.protocol() == GpgME::OpenPGP) { task->setClearsign(true); } connect (task, &Task::result, q, [this, task] (const std::shared_ptr &result) { qCDebug(KLEOPATRA_LOG) << "Encrypt / Sign done. Err:" << result->errorCode(); task->deleteLater(); cryptDone(result); }); task->start(); } void doImport() { doCryptoCommon(); mProgressLabel->setText(i18n("Importing...")); auto cmd = new Kleo::ImportCertificateFromDataCommand(mInputData, mImportProto); connect(cmd, &Kleo::ImportCertificatesCommand::finished, q, [this] () { mCryptBtn->setEnabled(true); mDecryptBtn->setEnabled(true); mProgressBar->setVisible(false); mProgressLabel->setVisible(false); updateCommitButton(); mRevertBtn->setVisible(true); mEdit->setPlainText(QString()); }); cmd->start(); } void checkImportProtocol() { QGpgME::QByteArrayDataProvider dp(mEdit->toPlainText().toUtf8()); GpgME::Data data(&dp); auto type = data.type(); if (type == GpgME::Data::PGPKey) { mImportProto = GpgME::OpenPGP; } else if (type == GpgME::Data::X509Cert || type == GpgME::Data::PKCS12) { mImportProto = GpgME::CMS; } else { mImportProto = GpgME::UnknownProtocol; } } void updateCommitButton() { mAdditionalInfoLabel->setVisible(false); checkImportProtocol(); if (mImportProto != GpgME::UnknownProtocol) { mCryptBtn->setText(i18nc("1 is an operation to apply to the notepad. " "Like Sign/Encrypt or just Encrypt.", "%1 Notepad", i18n("Import"))); mCryptBtn->setEnabled(true); return; } if (!mSigEncWidget->currentOp().isEmpty()) { mCryptBtn->setEnabled(true); mCryptBtn->setText(i18nc("1 is an operation to apply to the notepad. " "Like Sign/Encrypt or just Encrypt.", "%1 Notepad", mSigEncWidget->currentOp())); } else { mCryptBtn->setText(i18n("Sign / Encrypt Notepad")); mCryptBtn->setEnabled(false); } - if (Kleo::gnupgUsesDeVsCompliance()) { - const bool de_vs = Kleo::gnupgIsDeVsCompliant() && mSigEncWidget->isDeVsAndValid(); + if (DeVSCompliance::isActive()) { + const bool de_vs = DeVSCompliance::isCompliant() && mSigEncWidget->isDeVsAndValid(); mCryptBtn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); if (!SystemInfo::isHighContrastModeActive()) { mCryptBtn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); } mAdditionalInfoLabel->setText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "%1 communication possible.", Formatting::deVsString()) + "%1 communication possible.", DeVSCompliance::name(true)) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", - "%1 communication not possible.", Formatting::deVsString())); + "%1 communication not possible.", DeVSCompliance::name(true))); mAdditionalInfoLabel->setVisible(true); - if (!Kleo::gnupgIsDeVsCompliant()) { + if (!DeVSCompliance::isCompliant()) { mCryptBtn->setEnabled(false); } - mMessageWidget->setVisible(!Kleo::gnupgIsDeVsCompliant()); + mMessageWidget->setVisible(!DeVSCompliance::isCompliant()); } } private: PadWidget *const q; QTextEdit *mEdit; QPushButton *mCryptBtn; QPushButton *mDecryptBtn; QPushButton *mRevertBtn; KMessageWidget *mMessageWidget; QLabel *mAdditionalInfoLabel; QByteArray mInputData; QByteArray mOutputData; SignEncryptWidget *mSigEncWidget; QProgressBar *mProgressBar; QLabel *mProgressLabel; QVBoxLayout *mStatusLay; ResultItemWidget *mLastResultWidget; QList mAutoAddedKeys; QRadioButton *mPGPRB; QRadioButton *mCMSRB; GpgME::Protocol mImportProto; }; PadWidget::PadWidget(QWidget *parent): QWidget(parent), d(new Private(this)) { } void PadWidget::focusFirstChild(Qt::FocusReason reason) { d->mEdit->setFocus(reason); }