diff --git a/CMakeLists.txt b/CMakeLists.txt index 6444c8d06..4668bbe9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,244 +1,247 @@ # 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 "24") 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.99.0") set(KIDENTITYMANAGEMENT_VERSION "5.21.40") set(KMAILTRANSPORT_VERSION "5.21.40") set(KMIME_VERSION "5.21.40") set(LIBKLEO_VERSION "5.21.49") set(QT_REQUIRED_VERSION "5.15.2") if (QT_MAJOR_VERSION STREQUAL "6") set(QT_REQUIRED_VERSION "6.4.0") endif() 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.18.0") set(QGPGME_SUPPORTS_KEY_REVOCATION 1) set(QGPGME_SUPPORTS_KEY_REFRESH 1) set(QGPGME_SUPPORTS_SET_FILENAME 1) set(QGPGME_SUPPORTS_SET_PRIMARY_UID 1) endif() +if (Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.18.1") + set(GPGMEPP_SUPPORTS_SET_CURVE 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.99.0) else () ecm_set_disabled_deprecation_versions(QT 5.14.0 KF 5.99.0) endif () if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces -Wno-parentheses -Wno-ignored-qualifiers") endif() if(MINGW) # we do not care about different signedness of passed pointer arguments add_compile_options(-Wno-pointer-sign) 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/config-kleopatra.h.cmake b/config-kleopatra.h.cmake index 7d37e2e94..be13e2cbc 100644 --- a/config-kleopatra.h.cmake +++ b/config-kleopatra.h.cmake @@ -1,61 +1,64 @@ /* Define to 1 if you have a recent enough libassuan */ #cmakedefine HAVE_USABLE_ASSUAN 1 /* Define to 1 if you have libassuan v2 */ #cmakedefine HAVE_ASSUAN2 1 #ifndef HAVE_ASSUAN2 /* Define to 1 if your libassuan has the assuan_fd_t type */ #cmakedefine HAVE_ASSUAN_FD_T 1 /* Define to 1 if your libassuan has the assuan_inquire_ext function */ #cmakedefine HAVE_ASSUAN_INQUIRE_EXT 1 /* Define to 1 if your assuan_inquire_ext puts the buffer arguments into the callback signature */ #cmakedefine HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT 1 /* Define to 1 if your libassuan has the assuan_sock_get_nonce function */ #cmakedefine HAVE_ASSUAN_SOCK_GET_NONCE 1 #endif /* Define to 1 if you build libkleopatraclient */ #cmakedefine HAVE_KLEOPATRACLIENT_LIBRARY 1 /* DBus available */ #cmakedefine01 HAVE_QDBUS /* Defined if QGpgME supports changing the expiration date of the primary key and the subkeys simultaneously */ #cmakedefine QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY 1 /* Defined if QGpgME supports retrieving the default value of a config entry */ #cmakedefine QGPGME_CRYPTOCONFIGENTRY_HAS_DEFAULT_VALUE 1 /* Defined if QGpgME supports WKD lookup */ #cmakedefine QGPGME_SUPPORTS_WKDLOOKUP 1 /* Defined if QGpgME supports specifying an import filter when importing keys */ #cmakedefine QGPGME_SUPPORTS_IMPORT_WITH_FILTER 1 /* Defined if QGpgME supports setting key origin when importing keys */ #cmakedefine QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN 1 /* Defined if QGpgME supports the export of secret keys */ #cmakedefine QGPGME_SUPPORTS_SECRET_KEY_EXPORT 1 /* Defined if QGpgME supports the export of secret subkeys */ #cmakedefine QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 1 /* Defined if QGpgME supports receiving keys by their key ids */ #cmakedefine QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID 1 /* Defined if QGpgME supports revoking own OpenPGP keys */ #cmakedefine QGPGME_SUPPORTS_KEY_REVOCATION 1 /* Defined if QGpgME supports refreshing keys */ #cmakedefine QGPGME_SUPPORTS_KEY_REFRESH 1 /* Defined if QGpgME supports setting the file name of encrypted data */ #cmakedefine QGPGME_SUPPORTS_SET_FILENAME 1 /* Defined if QGpgME supports setting the primary user id of a key */ #cmakedefine QGPGME_SUPPORTS_SET_PRIMARY_UID 1 + +/* Defined if GpgME++ supports setting the curve when generating ECC card keys */ +#cmakedefine GPGMEPP_SUPPORTS_SET_CURVE 1 diff --git a/src/smartcard/openpgpcard.cpp b/src/smartcard/openpgpcard.cpp index 062428a42..ffa2c0817 100644 --- a/src/smartcard/openpgpcard.cpp +++ b/src/smartcard/openpgpcard.cpp @@ -1,196 +1,205 @@ /* smartcard/openpgpcard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ /* Code in this file is partly based on the GNU Privacy Assistant * (cm-openpgp.c) git rev. 0a78795146661234070681737b3e08228616441f * * Whis is: * SPDX-FileCopyrightText: 2008, 2009 g 10 Code GmbH * * And may be licensed under the GNU General Public License * as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. */ #include "openpgpcard.h" #include "algorithminfo.h" #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; static QDebug operator<<(QDebug s, const std::string &string) { return s << QString::fromStdString(string); } // static const std::string OpenPGPCard::AppName = "openpgp"; OpenPGPCard::OpenPGPCard(const Card &card) : Card(card) { setAppName(AppName); setInitialKeyInfos(OpenPGPCard::supportedKeys()); } // static std::string OpenPGPCard::pgpSigKeyRef() { return std::string("OPENPGP.1"); } // static std::string OpenPGPCard::pgpEncKeyRef() { return std::string("OPENPGP.2"); } // static std::string OpenPGPCard::pgpAuthKeyRef() { return std::string("OPENPGP.3"); } // static std::string OpenPGPCard::pinKeyRef() { return std::string("OPENPGP.1"); } // static std::string OpenPGPCard::adminPinKeyRef() { return std::string("OPENPGP.3"); } // static std::string OpenPGPCard::resetCodeKeyRef() { return std::string("OPENPGP.2"); } // static const std::vector & OpenPGPCard::supportedKeys() { static const std::vector keyInfos = { {OpenPGPCard::pgpSigKeyRef(), "", "sc", "", ""}, {OpenPGPCard::pgpEncKeyRef(), "", "e", "", ""}, {OpenPGPCard::pgpAuthKeyRef(), "", "a", "", ""} }; return keyInfos; } // static QString OpenPGPCard::keyDisplayName(const std::string &keyRef) { static const QMap displayNames = { { OpenPGPCard::pgpSigKeyRef(), i18n("Signature") }, { OpenPGPCard::pgpEncKeyRef(), i18n("Encryption") }, { OpenPGPCard::pgpAuthKeyRef(), i18n("Authentication") } }; return displayNames.value(keyRef); } // static std::string OpenPGPCard::getAlgorithmName(const std::string &algorithm, const std::string &keyRef) { static const std::map ecdhAlgorithmMapping = { { "curve25519", "cv25519" }, { "curve448", "cv448" }, }; static const std::map eddsaAlgorithmMapping = { { "curve25519", "ed25519" }, { "curve448", "ed448" }, }; if (keyRef == OpenPGPCard::pgpEncKeyRef()) { const auto it = ecdhAlgorithmMapping.find(algorithm); if (it != ecdhAlgorithmMapping.end()) { return it->second; } } else { const auto it = eddsaAlgorithmMapping.find(algorithm); if (it != eddsaAlgorithmMapping.end()) { return it->second; } } return algorithm; } void OpenPGPCard::setSupportedAlgorithms(const std::vector &algorithms) { static const std::vector allowedAlgorithms = { "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "curve25519", "curve448", "nistp256", "nistp384", "nistp521", "rsa2048", "rsa3072", "rsa4096", }; // Curve secp256k1 is explicitly ignored static const std::vector ignoredAlgorithms = { "secp256k1", }; mAlgorithms.clear(); std::copy_if(algorithms.begin(), algorithms.end(), std::back_inserter(mAlgorithms), [](const auto &algo) { return Kleo::contains(allowedAlgorithms, algo); }); if (mAlgorithms.size() < algorithms.size()) { std::vector unsupportedAlgos; std::copy_if(algorithms.begin(), algorithms.end(), std::back_inserter(unsupportedAlgos), [](const auto &algo) { return !Kleo::contains(ignoredAlgorithms, algo) && !Kleo::contains(allowedAlgorithms, algo); }); if (unsupportedAlgos.size() > 0) { qWarning(KLEOPATRA_LOG).nospace() << "OpenPGPCard::" << __func__ << " Unsupported algorithms: " << unsupportedAlgos << " (supported: " << allowedAlgorithms << ")"; } } } std::string OpenPGPCard::pubkeyUrl() const { return cardInfo("PUBKEY-URL"); } std::vector OpenPGPCard::supportedAlgorithms() const { static const std::map displayNames = { { "brainpoolP256r1", i18nc("@info", "ECC (Brainpool P-256)") }, { "brainpoolP384r1", i18nc("@info", "ECC (Brainpool P-384)") }, { "brainpoolP512r1", i18nc("@info", "ECC (Brainpool P-512)") }, { "curve25519", i18nc("@info", "ECC (Curve25519)") }, { "curve448", i18nc("@info", "ECC (Curve448)") }, { "nistp256", i18nc("@info", "ECC (NIST P-256)") }, { "nistp384", i18nc("@info", "ECC (NIST P-384)") }, { "nistp521", i18nc("@info", "ECC (NIST P-521)") }, { "rsa2048", i18nc("@info", "RSA 2048") }, { "rsa3072", i18nc("@info", "RSA 3072") }, { "rsa4096", i18nc("@info", "RSA 4096") }, }; std::vector algos; std::transform(mAlgorithms.cbegin(), mAlgorithms.cend(), std::back_inserter(algos), [](const auto &algo) { return AlgorithmInfo{algo, displayNames.at(algo)}; }); return algos; } + +std::string OpenPGPCard::defaultAlgorithm() const +{ + if (Kleo::contains(mAlgorithms, "curve25519")) { + return "curve25519"; + } + // we assume that all OpenPGP smart cards support at least RSA with 2048 bits + return "rsa2048"; +} diff --git a/src/smartcard/openpgpcard.h b/src/smartcard/openpgpcard.h index bcab28a67..cb3b97fe8 100644 --- a/src/smartcard/openpgpcard.h +++ b/src/smartcard/openpgpcard.h @@ -1,75 +1,80 @@ /* smartcard/openpgpcard.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "card.h" namespace Kleo { namespace SmartCard { struct AlgorithmInfo; struct KeyPairInfo; /** Class to work with OpenPGP smartcards or compatible tokens */ class OpenPGPCard: public Card { public: explicit OpenPGPCard(const Card &card); static const std::string AppName; static std::string pgpSigKeyRef(); static std::string pgpEncKeyRef(); static std::string pgpAuthKeyRef(); static std::string pinKeyRef(); static std::string adminPinKeyRef(); static std::string resetCodeKeyRef(); static const std::vector & supportedKeys(); static QString keyDisplayName(const std::string &keyRef); /** * Returns an algorithm name for the algorithm \p algorithm that is suitable * for passing to scdaemon for the card slot specified by \p keyRef. * * For example, it maps "curve25519" to either "ed25519" or "cv25519". */ static std::string getAlgorithmName(const std::string &algorithm, const std::string &keyRef); /** * Sets the algorithms supported by this smart card to \p algorithms. * The following values for algorithms are allowed: * brainpoolP256r1, brainpoolP384r1, brainpoolP512r1, * curve25519, * nistp256, nistp384, nistp521, * rsa2048, rsa3072, rsa4096. */ void setSupportedAlgorithms(const std::vector &algorithms); std::string pubkeyUrl() const; /** * Returns a list of algorithm IDs and corresponding display names. * * \note: You have to use getAlgorithmName to map the algorithm ID to * an algorithm name suitable for a certain card slot. */ std::vector supportedAlgorithms() const; + /** + * Returns the ID of the default algorithm for this smart card. + */ + std::string defaultAlgorithm() const; + private: std::vector mAlgorithms; }; } // namespace Smartcard } // namespace Kleopatra diff --git a/src/view/pgpcardwidget.cpp b/src/view/pgpcardwidget.cpp index c4678fdc4..58f2c1c47 100644 --- a/src/view/pgpcardwidget.cpp +++ b/src/view/pgpcardwidget.cpp @@ -1,521 +1,569 @@ /* view/pgpcardwiget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ +#include + #include "pgpcardwidget.h" #include "openpgpkeycardwidget.h" #include "kleopatra_debug.h" #include "commands/createcsrforcardkeycommand.h" #include "commands/createopenpgpkeyfromcardkeyscommand.h" #include "commands/openpgpgeneratecardkeycommand.h" #include "smartcard/algorithminfo.h" #include "smartcard/openpgpcard.h" #include "smartcard/readerstatus.h" #include "dialogs/gencardkeydialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; +static QDebug operator<<(QDebug s, const std::string &string) +{ + return s << QString::fromStdString(string); +} + namespace { class GenKeyThread: public QThread { Q_OBJECT public: explicit GenKeyThread(const GenCardKeyDialog::KeyParams ¶ms, const std::string &serial): mSerial(serial), mParams(params) { } GpgME::Error error() { return mErr; } std::string bkpFile() { return mBkpFile; } protected: void run() override { - auto ei = new GpgME::GpgGenCardKeyInteractor(mSerial); - ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA); - ei->setKeySize(QByteArray::fromStdString(mParams.algorithm).toInt()); + // the index of the curves in this list has to match the enum values + // minus 1 of GpgGenCardKeyInteractor::Curve + static const std::vector curves = { + "curve25519", +#ifdef GPGMEPP_SUPPORTS_SET_CURVE + "curve448", + "nistp256", + "nistp384", + "nistp521", + "brainpoolP256r1", + "brainpoolP384r1", + "brainpoolP512r1", + "secp256k1", // keep it, even if we don't support it in Kleopatra +#endif + }; + + auto ei = std::make_unique(mSerial); + if (mParams.algorithm.starts_with("rsa")) { + ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA); + ei->setKeySize(QByteArray::fromStdString(mParams.algorithm.substr(3)).toInt()); + } else { + ei->setAlgo(GpgME::GpgGenCardKeyInteractor::ECC); + const auto curveIt = std::find(curves.cbegin(), curves.cend(), mParams.algorithm); + if (curveIt != curves.end()) { +#ifdef GPGMEPP_SUPPORTS_SET_CURVE + ei->setCurve(static_cast(curveIt - curves.cbegin() + 1)); +#endif + } else { + qCWarning(KLEOPATRA_LOG) << this << __func__ << "Invalid curve name:" << mParams.algorithm; + mErr = GpgME::Error::fromCode(GPG_ERR_INV_VALUE); + return; + } + } ei->setNameUtf8(mParams.name.toStdString()); ei->setEmailUtf8(mParams.email.toStdString()); ei->setDoBackup(mParams.backup); const auto ctx = std::shared_ptr (GpgME::Context::createForProtocol(GpgME::OpenPGP)); + ctx->setFlag("extended-edit", "1"); // we want to be able to select all curves QGpgME::QByteArrayDataProvider dp; GpgME::Data data(&dp); - mErr = ctx->cardEdit(GpgME::Key(), std::unique_ptr (ei), data); - mBkpFile = ei->backupFileName(); + mErr = ctx->cardEdit(GpgME::Key(), std::move(ei), data); + mBkpFile = static_cast(ctx->lastCardEditInteractor())->backupFileName(); } private: GpgME::Error mErr; std::string mSerial; GenCardKeyDialog::KeyParams mParams; std::string mBkpFile; }; } // Namespace PGPCardWidget::PGPCardWidget(QWidget *parent): QWidget(parent), mSerialNumber(new QLabel(this)), mCardHolderLabel(new QLabel(this)), mVersionLabel(new QLabel(this)), mUrlLabel(new QLabel(this)), mCardIsEmpty(false) { // Set up the scroll area auto myLayout = new QVBoxLayout(this); myLayout->setContentsMargins(0, 0, 0, 0); auto area = new QScrollArea; area->setFrameShape(QFrame::NoFrame); area->setWidgetResizable(true); myLayout->addWidget(area); auto areaWidget = new QWidget; area->setWidget(areaWidget); auto areaVLay = new QVBoxLayout(areaWidget); auto cardInfoGrid = new QGridLayout; { int row = 0; // Version and Serialnumber cardInfoGrid->addWidget(mVersionLabel, row, 0, 1, 2); mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); row++; cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0); cardInfoGrid->addWidget(mSerialNumber, row, 1); mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); row++; // Cardholder Row cardInfoGrid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", "Cardholder:")), row, 0); cardInfoGrid->addWidget(mCardHolderLabel, row, 1); mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); { auto button = new QPushButton; button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); button->setAccessibleName(i18nc("@action:button", "Edit")); button->setToolTip(i18n("Change")); cardInfoGrid->addWidget(button, row, 2); connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested); } row++; // URL Row cardInfoGrid->addWidget(new QLabel(i18nc("The URL under which a public key that " "corresponds to a smartcard can be downloaded", "Pubkey URL:")), row, 0); cardInfoGrid->addWidget(mUrlLabel, row, 1); mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); { auto button = new QPushButton; button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); button->setAccessibleName(i18nc("@action:button", "Edit")); button->setToolTip(i18n("Change")); cardInfoGrid->addWidget(button, row, 2); connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested); } cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1); } areaVLay->addLayout(cardInfoGrid); areaVLay->addWidget(new KSeparator(Qt::Horizontal)); // The keys areaVLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Keys:")))); mKeysWidget = new OpenPGPKeyCardWidget{this}; areaVLay->addWidget(mKeysWidget); connect(mKeysWidget, &OpenPGPKeyCardWidget::createCSRRequested, this, &PGPCardWidget::createCSR); connect(mKeysWidget, &OpenPGPKeyCardWidget::generateKeyRequested, this, &PGPCardWidget::generateKey); areaVLay->addWidget(new KSeparator(Qt::Horizontal)); areaVLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:")))); auto actionLayout = new QHBoxLayout; { auto generateButton = new QPushButton(i18n("Generate New Keys")); generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card.")); actionLayout->addWidget(generateButton); connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested); } { auto pinButton = new QPushButton(i18n("Change PIN")); pinButton->setToolTip(i18n("Change the PIN required for using the keys on the smartcard.")); actionLayout->addWidget(pinButton); connect(pinButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::pinKeyRef()); }); } { auto unblockButton = new QPushButton(i18n("Unblock Card")); unblockButton->setToolTip(i18n("Unblock the smartcard and set a new PIN.")); actionLayout->addWidget(unblockButton); connect(unblockButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef()); }); } { auto pukButton = new QPushButton(i18n("Change Admin PIN")); pukButton->setToolTip(i18n("Change the PIN required for administrative operations.")); actionLayout->addWidget(pukButton); connect(pukButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::adminPinKeyRef()); }); } { auto resetCodeButton = new QPushButton(i18n("Change Reset Code")); resetCodeButton->setToolTip(i18n("Change the PIN required to unblock the smartcard and set a new PIN.")); actionLayout->addWidget(resetCodeButton); connect(resetCodeButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef(), ChangePinCommand::ResetMode); }); } if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) { mKeyForCardKeysButton = new QPushButton(this); mKeyForCardKeysButton->setText(i18n("Create OpenPGP Key")); mKeyForCardKeysButton->setToolTip(i18n("Create an OpenPGP key for the keys stored on the card.")); actionLayout->addWidget(mKeyForCardKeysButton); connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &PGPCardWidget::createKeyFromCardKeys); } actionLayout->addStretch(-1); areaVLay->addLayout(actionLayout); areaVLay->addStretch(1); } void PGPCardWidget::setCard(const OpenPGPCard *card) { const QString version = card->displayAppVersion(); mIs21 = card->appVersion() >= 0x0201; const QString manufacturer = QString::fromStdString(card->manufacturer()); const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1String("unknown"); mVersionLabel->setText(manufacturerIsUnknown ? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", version) : i18nc("First placeholder is manufacturer, second placeholder is a version number", "%1 OpenPGP v%2 card", manufacturer, version)); mSerialNumber->setText(card->displaySerialNumber()); mRealSerial = card->serialNumber(); const auto holder = card->cardHolder(); const auto url = QString::fromStdString(card->pubkeyUrl()); mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder); mUrl = url; mUrlLabel->setText(url.isEmpty() ? i18n("not set") : QStringLiteral("%1").arg(url.toHtmlEscaped())); mUrlLabel->setOpenExternalLinks(true); mKeysWidget->update(card); mCardIsEmpty = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpAuthKeyRef()).empty(); if (mKeyForCardKeysButton) { mKeyForCardKeysButton->setEnabled(card->hasSigningKey() && card->hasEncryptionKey()); } } void PGPCardWidget::doChangePin(const std::string &keyRef, ChangePinCommand::ChangePinMode mode) { auto cmd = new ChangePinCommand(mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->setMode(mode); cmd->start(); } void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg) { const GpgME::Error err = ReaderStatus::switchCardAndApp(mRealSerial, OpenPGPCard::AppName); if (err) { return; } const auto params = dlg->getKeyParams(); auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setMaximum(0); progress->setMinimum(0); progress->setModal(true); progress->setCancelButton(nullptr); progress->setWindowTitle(i18nc("@title:window", "Generating Keys")); progress->setLabel(new QLabel(i18n("This may take several minutes..."))); auto workerThread = new GenKeyThread(params, mRealSerial); connect(workerThread, &QThread::finished, this, [this, workerThread, progress] { progress->accept(); progress->deleteLater(); genKeyDone(workerThread->error(), workerThread->bkpFile()); delete workerThread; }); workerThread->start(); progress->exec(); } void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup) { if (err) { KMessageBox::error(this, i18nc("@info", "Failed to generate new key: %1", QString::fromLatin1(err.asString()))); return; } if (err.isCanceled()) { return; } if (!backup.empty()) { const auto bkpFile = QString::fromStdString(backup); QFileInfo fi(bkpFile); const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"), fi.fileName(), QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key"))); if (!target.isEmpty() && !QFile::copy(bkpFile, target)) { KMessageBox::error(this, i18nc("@info", "Failed to move backup. The backup key is still stored under: %1", bkpFile)); } else if (!target.isEmpty()) { QFile::remove(bkpFile); } } KMessageBox::information(this, i18nc("@info", "Successfully generated a new key for this card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } void PGPCardWidget::genkeyRequested() { + const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); + if (!pgpCard) { + KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); + return; + } + if (!mCardIsEmpty) { auto ret = KMessageBox::warningContinueCancel(this, i18n("The existing keys on this card will be deleted " "and replaced by new keys.") + QStringLiteral("

") + i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."), i18n("Secret Key Deletion"), KStandardGuiItem::guiItem(KStandardGuiItem::Delete), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (ret != KMessageBox::Continue) { return; } } auto dlg = new GenCardKeyDialog(GenCardKeyDialog::AllKeyAttributes, this); +#ifdef GPGMEPP_SUPPORTS_SET_CURVE + dlg->setSupportedAlgorithms(pgpCard->supportedAlgorithms(), pgpCard->defaultAlgorithm()); +#else std::vector algos = { - { "1024", QStringLiteral("RSA 1024") }, - { "2048", QStringLiteral("RSA 2048") }, - { "3072", QStringLiteral("RSA 3072") } + { "curve25519", i18nc("@info", "ECC (Curve25519)") }, + { "rsa2048", i18nc("@info", "RSA 2048") }, + { "rsa3072", i18nc("@info", "RSA 3072") }, }; // There is probably a better way to check for capabilities if (mIs21) { - algos.push_back({"4096", QStringLiteral("RSA 4096")}); + algos.push_back({"rsa4096", i18nc("@info", "RSA 4096")}); } - dlg->setSupportedAlgorithms(algos, "2048"); + dlg->setSupportedAlgorithms(algos, "rsa2048"); +#endif connect(dlg, &QDialog::accepted, this, [this, dlg] () { doGenKey(dlg); dlg->deleteLater(); }); dlg->setModal(true); dlg->show(); } void PGPCardWidget::changeNameRequested() { QString text = mCardHolderLabel->text(); while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change cardholder"), i18n("New name:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.contains(QLatin1Char('<'))) { KMessageBox::error(this, i18nc("@info", "The \"<\" character may not be used.")); continue; } if (text.contains(QLatin1String(" "))) { KMessageBox::error(this, i18nc("@info", "Double spaces are not allowed")); continue; } if (text.size() > 38) { KMessageBox::error(this, i18nc("@info", "The size of the name may not exceed 38 characters.")); } break; } auto parts = text.split(QLatin1Char(' ')); const auto lastName = parts.takeLast(); const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR DISP-NAME ") + formatted.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) { changeNameResult(err); }); } void PGPCardWidget::changeNameResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "Name change failed: %1", QString::fromLatin1(err.asString()))); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "Name successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::changeUrlRequested() { QString text = mUrl; while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"), i18n("New pubkey URL:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.size() > 254) { KMessageBox::error(this, i18nc("@info", "The size of the URL may not exceed 254 characters.")); } break; } const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR PUBKEY-URL ") + text.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) { changeUrlResult(err); }); } void PGPCardWidget::changeUrlResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "URL change failed: %1", QString::fromLatin1(err.asString()))); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "URL successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::createKeyFromCardKeys() { auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } void PGPCardWidget::createCSR(const std::string &keyref) { auto cmd = new CreateCSRForCardKeyCommand(keyref, mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } void PGPCardWidget::generateKey(const std::string &keyref) { auto cmd = new OpenPGPGenerateCardKeyCommand(keyref, mRealSerial, this); this->setEnabled(false); connect(cmd, &OpenPGPGenerateCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } #include "pgpcardwidget.moc"