Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F35382246
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
57 KB
Subscribers
None
View Options
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 09cc6f3f..76667ffd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,163 +1,166 @@
# SPDX-License-Identifier: CC0-1.0
# SPDX-FileCopyrightText: none
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(PIM_VERSION "6.3.40")
project(libkleo VERSION ${PIM_VERSION})
set(KF_MIN_VERSION "6.7.0")
if(WIN32)
set(KF6_WANT_VERSION ${KF_MIN_VERSION})
add_compile_definitions(GPG_ERR_ENABLE_GETTEXT_MACROS=1)
else ()
set(KF6_WANT_VERSION ${KF_MIN_VERSION})
endif()
find_package(ECM ${KF6_WANT_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})
set(SHARE_INSTALL_DIR share
CACHE
PATH
"read-only architecture-independent data"
)
set(FIND_MODULES_INSTALL_DIR ${SHARE_INSTALL_DIR}/KPim6Libkleo/find-modules/)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMGenerateExportHeader)
include(ECMSetupVersion)
include(ECMGenerateHeaders)
include(FeatureSummary)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(ECMFeatureSummary)
include(ECMAddQch)
include(KDEClangFormat)
include(KDEGitCommitHooks)
option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
set(LIBKLEO_LIB_VERSION ${PIM_VERSION})
set(QT_REQUIRED_VERSION "6.7.0")
set(GPGME_REQUIRED_VERSION "1.23.2")
set(GPG_ERROR_REQUIRED_VERSION "1.36")
find_package(Qt6 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets)
find_package(KF6I18n ${KF6_WANT_VERSION} CONFIG REQUIRED)
find_package(KF6Config ${KF6_WANT_VERSION} CONFIG REQUIRED)
find_package(KF6WidgetsAddons ${KF6_WANT_VERSION} CONFIG REQUIRED)
find_package(KF6ColorScheme ${KF6_WANT_VERSION} CONFIG REQUIRED)
find_package(KF6Completion ${KF6_WANT_VERSION} CONFIG REQUIRED)
find_package(KF6CoreAddons ${KF6_WANT_VERSION} CONFIG REQUIRED)
find_package(KF6Codecs ${KF6_WANT_VERSION} CONFIG REQUIRED)
find_package(KF6ItemModels ${KF6_WANT_VERSION} CONFIG REQUIRED)
find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
set_package_properties(Gpgmepp PROPERTIES DESCRIPTION "GpgME++ Library" URL "https://www.gnupg.org" TYPE REQUIRED PURPOSE "GpgME++ is required for OpenPGP support")
message(STATUS "GpgME++ Version ${Gpgmepp_VERSION}")
set(QGPGME_NAME "QGpgmeQt6")
if(Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.24.0")
set(GPGMEPP_ERROR_HAS_ASSTDSTRING 1)
endif()
+if(Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.24.1")
+ set(GPGMEPP_SUPPORTS_KYBER 1)
+endif()
find_package(${QGPGME_NAME} ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
find_package(LibGpgError ${GPG_ERROR_REQUIRED_VERSION} REQUIRED)
set_package_properties(LibGpgError PROPERTIES
TYPE REQUIRED
)
find_package(Boost 1.34.0)
set_package_properties(Boost PROPERTIES DESCRIPTION "Boost C++ Libraries" URL "https://www.boost.org" TYPE REQUIRED PURPOSE "Boost is required for building most KDEPIM applications")
ecm_setup_version(PROJECT VARIABLE_PREFIX LIBKLEO
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/libkleo_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim6LibkleoConfigVersion.cmake"
SOVERSION 6
)
########### Targets ###########
ecm_set_disabled_deprecation_versions(QT 6.3 KF 6.8.0)
remove_definitions(-DQT_NO_FOREACH)
add_definitions(-DQT_NO_EMIT)
########### CMake Config Files ###########
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim6Libkleo")
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/libkleo_version.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/Libkleo COMPONENT Devel
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
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)
set(UNITY_BUILD ON)
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-libkleo.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-libkleo.h)
add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(autotests)
add_subdirectory(tests)
endif()
ecm_qt_install_logging_categories(
EXPORT LIBKLEO
FILE libkleo.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
ki18n_install(po)
if(BUILD_QCH)
ecm_install_qch_export(
TARGETS KPim6Libkleo_QCH
FILE KPim6LibkleoQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim6LibkleoQchTargets.cmake\")")
endif()
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KPimLibkleoConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KPim6LibkleoConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
PATH_VARS
FIND_MODULES_INSTALL_DIR
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KPim6LibkleoConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KPim6LibkleoConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT KPim6LibkleoTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KPim6LibkleoTargets.cmake NAMESPACE KPim6::)
file(GLOB installFindModuleFiles ${CMAKE_SOURCE_DIR}/cmake/modules/Find*.cmake)
install(FILES ${installFindModuleFiles} DESTINATION ${FIND_MODULES_INSTALL_DIR})
ecm_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
src/*.cpp src/*.h
tests/*.cpp tests/*.h
autotests/*.cpp autotests/*.h
)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
diff --git a/config-libkleo.h.in b/config-libkleo.h.in
index 236031bc..70353e20 100644
--- a/config-libkleo.h.in
+++ b/config-libkleo.h.in
@@ -1,4 +1,7 @@
/* Whether Error::asStdString exist */
#cmakedefine01 GPGMEPP_ERROR_HAS_ASSTDSTRING
+/* Whether Subkey::PubkeyAlgo::AlgoKyber exists */
+#cmakedefine01 GPGMEPP_SUPPORTS_KYBER
+
#cmakedefine01 UNITY_BUILD
diff --git a/src/ui/openpgpcertificatecreationdialog.cpp b/src/ui/openpgpcertificatecreationdialog.cpp
index 7ed702ef..4e30878e 100644
--- a/src/ui/openpgpcertificatecreationdialog.cpp
+++ b/src/ui/openpgpcertificatecreationdialog.cpp
@@ -1,421 +1,450 @@
/* -*- mode: c++; c-basic-offset:4 -*-
This file is part of Libkleo.
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
+#include <config-libkleo.h>
+
#include "openpgpcertificatecreationdialog.h"
#include "adjustingscrollarea.h"
#include "animatedexpander_p.h"
#include "nameandemailwidget.h"
#include "openpgpcertificatecreationconfig.h"
#include "utils/compat.h"
#include "utils/compliance.h"
#include "utils/expiration.h"
#include "utils/gnupg.h"
#include "utils/keyparameters.h"
#include "utils/keyusage.h"
#include <KConfigGroup>
#include <KDateComboBox>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <KSharedConfig>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include "libkleo_debug.h"
using namespace Kleo;
+using namespace Qt::Literals::StringLiterals;
static bool unlimitedValidityIsAllowed()
{
return !Kleo::Expiration::maximumExpirationDate().isValid();
}
class OpenPGPCertificateCreationDialog::Private
{
friend class ::Kleo::OpenPGPCertificateCreationDialog;
OpenPGPCertificateCreationDialog *const q;
struct UI {
QLabel *infoLabel;
AdjustingScrollArea *scrollArea;
NameAndEmailWidget *nameAndEmail;
QCheckBox *withPassCheckBox;
QDialogButtonBox *buttonBox;
QCheckBox *expiryCB;
QLabel *expiryLabel;
KDateComboBox *expiryDE;
QComboBox *keyAlgoCB;
QLabel *keyAlgoLabel;
AnimatedExpander *expander;
UI(QWidget *dialog)
{
auto mainLayout = new QVBoxLayout{dialog};
infoLabel = new QLabel{dialog};
infoLabel->setWordWrap(true);
mainLayout->addWidget(infoLabel);
mainLayout->addWidget(new KSeparator{Qt::Horizontal, dialog});
scrollArea = new AdjustingScrollArea{dialog};
scrollArea->setFocusPolicy(Qt::NoFocus);
scrollArea->setFrameStyle(QFrame::NoFrame);
scrollArea->setBackgroundRole(dialog->backgroundRole());
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
auto scrollAreaLayout = qobject_cast<QBoxLayout *>(scrollArea->widget()->layout());
scrollAreaLayout->setContentsMargins(0, 0, 0, 0);
nameAndEmail = new NameAndEmailWidget{dialog};
nameAndEmail->layout()->setContentsMargins(0, 0, 0, 0);
scrollAreaLayout->addWidget(nameAndEmail);
withPassCheckBox = new QCheckBox{i18n("Protect the generated key with a passphrase."), dialog};
withPassCheckBox->setToolTip(
i18n("Encrypts the secret key with an unrecoverable passphrase. You will be asked for the passphrase during key generation."));
scrollAreaLayout->addWidget(withPassCheckBox);
expander = new AnimatedExpander(i18n("Advanced options"), {}, dialog);
scrollAreaLayout->addWidget(expander);
auto advancedLayout = new QVBoxLayout;
expander->setContentLayout(advancedLayout);
keyAlgoLabel = new QLabel(dialog);
keyAlgoLabel->setText(i18nc("The algorithm and strength of encryption key", "Key Material"));
auto font = keyAlgoLabel->font();
font.setBold(true);
keyAlgoLabel->setFont(font);
advancedLayout->addWidget(keyAlgoLabel);
keyAlgoCB = new QComboBox(dialog);
keyAlgoLabel->setBuddy(keyAlgoCB);
advancedLayout->addWidget(keyAlgoCB);
{
auto hbox = new QHBoxLayout;
expiryCB = new QCheckBox{dialog};
expiryCB->setAccessibleName(Expiration::validUntilLabel());
hbox->addWidget(expiryCB);
expiryLabel = new QLabel{Expiration::validUntilLabel(), dialog};
hbox->addWidget(expiryLabel);
expiryDE = new KDateComboBox(dialog);
hbox->addWidget(expiryDE, 1);
advancedLayout->addLayout(hbox);
}
scrollAreaLayout->addStretch(1);
mainLayout->addWidget(scrollArea);
mainLayout->addWidget(new KSeparator{Qt::Horizontal, dialog});
buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog};
mainLayout->addWidget(buttonBox);
}
} ui;
public:
explicit Private(OpenPGPCertificateCreationDialog *qq)
: q{qq}
, ui{qq}
, technicalParameters{KeyParameters::OpenPGP}
{
q->setWindowTitle(i18nc("title:window", "Create OpenPGP Certificate"));
OpenPGPCertificateCreationConfig settings;
const auto requiredFields = settings.requiredFields();
const auto nameIsRequired = requiredFields.contains(QLatin1StringView{"NAME!"}, Qt::CaseInsensitive);
const auto emailIsRequired = requiredFields.contains(QLatin1StringView{"EMAIL!"}, Qt::CaseInsensitive);
ui.infoLabel->setText(nameIsRequired || emailIsRequired //
? i18n("Enter a name and an email address to use for the certificate.")
: i18n("Enter a name and/or an email address to use for the certificate."));
ui.nameAndEmail->setNameIsRequired(nameIsRequired);
ui.nameAndEmail->setNameLabel(settings.nameLabel());
const auto nameHint = settings.nameHint();
ui.nameAndEmail->setNameHint(nameHint.isEmpty() ? settings.namePlaceholder() : nameHint);
ui.nameAndEmail->setNamePattern(settings.nameRegex());
ui.nameAndEmail->setEmailIsRequired(emailIsRequired);
ui.nameAndEmail->setEmailLabel(settings.emailLabel());
const auto emailHint = settings.emailHint();
ui.nameAndEmail->setEmailHint(emailHint.isEmpty() ? settings.emailPlaceholder() : emailHint);
ui.nameAndEmail->setEmailPattern(settings.emailRegex());
ui.expander->setVisible(!settings.hideAdvanced());
const auto conf = QGpgME::cryptoConfig();
const auto entry = getCryptoConfigEntry(conf, "gpg-agent", "enforce-passphrase-constraints");
if (entry && entry->boolValue()) {
qCDebug(LIBKLEO_LOG) << "Disabling passphrase check box because of agent config.";
ui.withPassCheckBox->setEnabled(false);
ui.withPassCheckBox->setChecked(true);
} else {
ui.withPassCheckBox->setChecked(settings.withPassphrase());
ui.withPassCheckBox->setEnabled(!settings.isWithPassphraseImmutable());
}
connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() {
checkAccept();
});
connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
for (const auto &algorithm : DeVSCompliance::isActive() ? DeVSCompliance::compliantAlgorithms() : availableAlgorithms()) {
ui.keyAlgoCB->addItem(QString::fromStdString(algorithm), QString::fromStdString(algorithm));
}
auto cryptoConfig = QGpgME::cryptoConfig();
if (cryptoConfig) {
auto pubkeyEntry = getCryptoConfigEntry(QGpgME::cryptoConfig(), "gpg", "default_pubkey_algo");
if (pubkeyEntry) {
auto algo = pubkeyEntry->stringValue().split(QLatin1Char('/'))[0];
if (algo == QLatin1StringView("ed25519")) {
algo = QStringLiteral("curve25519");
} else if (algo == QLatin1StringView("ed448")) {
algo = QStringLiteral("curve448");
}
auto index = ui.keyAlgoCB->findData(algo);
if (index != -1) {
ui.keyAlgoCB->setCurrentIndex(index);
} else {
ui.keyAlgoCB->setCurrentIndex(0);
}
} else {
ui.keyAlgoCB->setCurrentIndex(0);
}
} else {
ui.keyAlgoCB->setCurrentIndex(0);
}
Kleo::Expiration::setUpExpirationDateComboBox(ui.expiryDE);
ui.expiryCB->setEnabled(true);
setExpiryDate(defaultExpirationDate(Kleo::Expiration::ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
if (unlimitedValidityIsAllowed()) {
ui.expiryLabel->setEnabled(ui.expiryCB->isChecked());
ui.expiryDE->setEnabled(ui.expiryCB->isChecked());
} else {
ui.expiryCB->setEnabled(false);
ui.expiryCB->setVisible(false);
}
connect(ui.expiryCB, &QAbstractButton::toggled, q, [this](bool checked) {
ui.expiryLabel->setEnabled(checked);
ui.expiryDE->setEnabled(checked);
if (checked && !ui.expiryDE->isValid()) {
setExpiryDate(defaultExpirationDate(Kleo::Expiration::ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
}
updateTechnicalParameters();
});
connect(ui.expiryDE, &KDateComboBox::dateChanged, q, [this]() {
updateTechnicalParameters();
});
connect(ui.keyAlgoCB, &QComboBox::currentIndexChanged, q, [this]() {
updateTechnicalParameters();
});
updateTechnicalParameters(); // set key parameters to default values for OpenPGP
connect(ui.expander, &AnimatedExpander::startExpanding, q, [this]() {
q->resize(std::max(q->sizeHint().width(), ui.expander->contentWidth()) + 20, q->sizeHint().height() + ui.expander->contentHeight() + 20);
});
}
private:
void updateTechnicalParameters()
{
technicalParameters = KeyParameters{KeyParameters::OpenPGP};
auto keyType = GpgME::Subkey::AlgoUnknown;
auto subkeyType = GpgME::Subkey::AlgoUnknown;
auto algoString = ui.keyAlgoCB->currentData().toString();
if (algoString.startsWith(QStringLiteral("rsa"))) {
keyType = GpgME::Subkey::AlgoRSA;
subkeyType = GpgME::Subkey::AlgoRSA;
const auto strength = algoString.mid(3).toInt();
technicalParameters.setKeyLength(strength);
technicalParameters.setSubkeyLength(strength);
} else if (algoString == QLatin1StringView("curve25519") || algoString == QLatin1StringView("curve448")) {
keyType = GpgME::Subkey::AlgoEDDSA;
subkeyType = GpgME::Subkey::AlgoECDH;
if (algoString.endsWith(QStringLiteral("25519"))) {
technicalParameters.setKeyCurve(QStringLiteral("ed25519"));
technicalParameters.setSubkeyCurve(QStringLiteral("cv25519"));
} else {
technicalParameters.setKeyCurve(QStringLiteral("ed448"));
technicalParameters.setSubkeyCurve(QStringLiteral("cv448"));
}
+#if GPGMEPP_SUPPORTS_KYBER
+ } else if (algoString == "ky768_bp256"_L1) {
+ keyType = GpgME::Subkey::AlgoECDSA;
+ subkeyType = GpgME::Subkey::AlgoKyber;
+ technicalParameters.setKeyCurve(u"brainpoolP256r1"_s);
+ technicalParameters.setSubkeyCurve(u"brainpoolP256r1"_s);
+ technicalParameters.setSubkeyLength(768);
+ } else if (algoString == "ky1024_bp384"_L1) {
+ keyType = GpgME::Subkey::AlgoECDSA;
+ subkeyType = GpgME::Subkey::AlgoKyber;
+ technicalParameters.setKeyCurve(u"brainpoolP384r1"_s);
+ technicalParameters.setSubkeyCurve(u"brainpoolP384r1"_s);
+ technicalParameters.setSubkeyLength(1024);
+#endif
} else {
keyType = GpgME::Subkey::AlgoECDSA;
subkeyType = GpgME::Subkey::AlgoECDH;
technicalParameters.setKeyCurve(algoString);
technicalParameters.setSubkeyCurve(algoString);
}
technicalParameters.setKeyType(keyType);
technicalParameters.setSubkeyType(subkeyType);
technicalParameters.setKeyUsage(KeyUsage(KeyUsage::Certify | KeyUsage::Sign));
technicalParameters.setSubkeyUsage(KeyUsage(KeyUsage::Encrypt));
technicalParameters.setExpirationDate(expiryDate());
// name and email are set later
}
QDate expiryDate() const
{
return ui.expiryCB->isChecked() ? ui.expiryDE->date() : QDate{};
}
void setTechnicalParameters(const KeyParameters ¶meters)
{
- int index;
+ int index = -1;
if (parameters.keyType() == GpgME::Subkey::AlgoRSA_S) {
index = ui.keyAlgoCB->findData(QStringLiteral("rsa%1").arg(parameters.keyLength()));
} else if (parameters.keyCurve() == QLatin1StringView("ed25519")) {
index = ui.keyAlgoCB->findData(QStringLiteral("curve25519"));
} else if (parameters.keyCurve() == QLatin1StringView("ed448")) {
index = ui.keyAlgoCB->findData(QStringLiteral("curve448"));
+#if GPGMEPP_SUPPORTS_KYBER
+ } else if (parameters.subkeyType() == GpgME::Subkey::AlgoKyber) {
+ if (parameters.subkeyLength() == 768 && parameters.keyCurve() == "brainpoolP256r1"_L1) {
+ index = ui.keyAlgoCB->findData("ky768_bp256"_L1);
+ } else if (parameters.subkeyLength() == 1024 && parameters.keyCurve() == "brainpoolP384r1"_L1) {
+ index = ui.keyAlgoCB->findData("ky1024_bp384"_L1);
+ } else {
+ qCDebug(LIBKLEO_LOG) << __func__ << "Unsupported Kyber parameters" << parameters.subkeyLength() << parameters.keyCurve();
+ }
+#endif
} else {
index = ui.keyAlgoCB->findData(parameters.keyCurve());
}
- ui.keyAlgoCB->setCurrentIndex(index);
+ if (index >= 0) {
+ ui.keyAlgoCB->setCurrentIndex(index);
+ }
setExpiryDate(parameters.expirationDate());
}
void checkAccept()
{
QStringList errors;
if (ui.nameAndEmail->userID().isEmpty() && !ui.nameAndEmail->nameIsRequired() && !ui.nameAndEmail->emailIsRequired()) {
errors.push_back(i18n("Enter a name or an email address."));
}
const auto nameError = ui.nameAndEmail->nameError();
if (!nameError.isEmpty()) {
errors.push_back(nameError);
}
const auto emailError = ui.nameAndEmail->emailError();
if (!emailError.isEmpty()) {
errors.push_back(emailError);
}
if (!Expiration::isValidExpirationDate(expiryDate())) {
errors.push_back(Expiration::validityPeriodHint());
}
if (errors.size() > 1) {
KMessageBox::errorList(q, i18n("There is a problem."), errors);
} else if (!errors.empty()) {
KMessageBox::error(q, errors.first());
} else {
q->accept();
}
}
QDate 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 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());
}
}
private:
KeyParameters technicalParameters;
};
OpenPGPCertificateCreationDialog::OpenPGPCertificateCreationDialog(QWidget *parent, Qt::WindowFlags f)
: QDialog{parent, f}
, d(new Private{this})
{
resize(std::max(sizeHint().width(), d->ui.expander->contentWidth()) + 20, sizeHint().height() + 20);
}
OpenPGPCertificateCreationDialog::~OpenPGPCertificateCreationDialog() = default;
void OpenPGPCertificateCreationDialog::setName(const QString &name)
{
d->ui.nameAndEmail->setName(name);
}
QString OpenPGPCertificateCreationDialog::name() const
{
return d->ui.nameAndEmail->name();
}
void OpenPGPCertificateCreationDialog::setEmail(const QString &email)
{
d->ui.nameAndEmail->setEmail(email);
}
QString OpenPGPCertificateCreationDialog::email() const
{
return d->ui.nameAndEmail->email();
}
void Kleo::OpenPGPCertificateCreationDialog::setKeyParameters(const Kleo::KeyParameters ¶meters)
{
setName(parameters.name());
const auto emails = parameters.emails();
if (!emails.empty()) {
setEmail(emails.front());
}
d->setTechnicalParameters(parameters);
}
KeyParameters OpenPGPCertificateCreationDialog::keyParameters() const
{
// set name and email on a copy of the technical parameters
auto parameters = d->technicalParameters;
if (!name().isEmpty()) {
parameters.setName(name());
}
if (!email().isEmpty()) {
parameters.setEmail(email());
}
return parameters;
}
void Kleo::OpenPGPCertificateCreationDialog::setProtectKeyWithPassword(bool protectKey)
{
d->ui.withPassCheckBox->setChecked(protectKey);
}
bool OpenPGPCertificateCreationDialog::protectKeyWithPassword() const
{
return d->ui.withPassCheckBox->isChecked();
}
#include "moc_openpgpcertificatecreationdialog.cpp"
diff --git a/src/utils/compliance.cpp b/src/utils/compliance.cpp
index 6e57d13f..22c97e4b 100644
--- a/src/utils/compliance.cpp
+++ b/src/utils/compliance.cpp
@@ -1,183 +1,199 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/compliance.cpp
This file is part of libkleopatra
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "compliance.h"
#include "algorithm.h"
#include "cryptoconfig.h"
#include "gnupg.h"
#include "keyhelpers.h"
#include "stringutils.h"
#include "systeminfo.h"
#include <libkleo/debug.h>
#include <libkleo/keyfiltermanager.h>
#include <libkleo_debug.h>
#include <KColorScheme>
#include <KLocalizedString>
#include <QPushButton>
#include <gpgme++/key.h>
using namespace Kleo;
bool Kleo::DeVSCompliance::isActive()
{
return getCryptoConfigStringValue("gpg", "compliance") == QLatin1StringView{"de-vs"};
}
bool Kleo::DeVSCompliance::isCompliant()
{
if (!isActive()) {
return false;
}
// The pseudo option compliance_de_vs was fully added in 2.2.34;
// For versions between 2.2.28 and 2.2.33 there was a broken config
// value with a wrong type. So for them we add an extra check. This
// can be removed in future versions because for GnuPG we could assume
// non-compliance for older versions as versions of Kleopatra for
// which this matters are bundled with new enough versions of GnuPG anyway.
if (engineIsVersion(2, 2, 28) && !engineIsVersion(2, 2, 34)) {
return true;
}
return getCryptoConfigIntValue("gpg", "compliance_de_vs", 0) != 0;
}
bool Kleo::DeVSCompliance::isBetaCompliance()
{
if (!isActive()) {
return false;
}
// compliance_de_vs > 2000: GnuPG has not yet been approved for VS-NfD or is beta, but we shall assume approval
return getCryptoConfigIntValue("gpg", "compliance_de_vs", 0) > 2000;
}
bool Kleo::DeVSCompliance::algorithmIsCompliant(std::string_view algo)
{
return !isActive() || Kleo::contains(compliantAlgorithms(), algo);
}
bool Kleo::DeVSCompliance::allSubkeysAreCompliant(const GpgME::Key &key)
{
if (!isActive()) {
return true;
}
// there is at least one usable subkey
const auto usableSubkeys = Kleo::count_if(key.subkeys(), [](const auto &sub) {
return !sub.isExpired() && !sub.isRevoked();
});
if (usableSubkeys == 0) {
qCDebug(LIBKLEO_LOG) << __func__ << "No usable subkeys found for key" << key;
return false;
}
// and all usable subkeys are compliant
return Kleo::all_of(key.subkeys(), [](const auto &sub) {
return sub.isDeVs() || sub.isExpired() || sub.isRevoked() || (!sub.canSign() && !sub.canEncrypt() && !sub.canCertify() && sub.canAuthenticate());
});
}
bool Kleo::DeVSCompliance::userIDIsCompliant(const GpgME::UserID &id)
{
if (!isActive()) {
return true;
}
return (id.parent().keyListMode() & GpgME::Validate) //
&& !id.isRevoked() //
&& id.validity() >= GpgME::UserID::Full //
&& allSubkeysAreCompliant(id.parent());
}
bool Kleo::DeVSCompliance::keyIsCompliant(const GpgME::Key &key)
{
if (!isActive()) {
return true;
}
return (key.keyListMode() & GpgME::Validate) //
&& allUserIDsHaveFullValidity(key) //
&& allSubkeysAreCompliant(key);
}
const std::vector<std::string> &Kleo::DeVSCompliance::compliantAlgorithms()
{
- static const std::vector<std::string> compliantAlgos = {
- "brainpoolP256r1",
- "brainpoolP384r1",
- "brainpoolP512r1",
- "rsa3072",
- "rsa4096",
+ static std::vector<std::string> compliantAlgos;
+ if (!isActive()) {
+ return Kleo::availableAlgorithms();
+ }
+ if (compliantAlgos.empty()) {
+ compliantAlgos.reserve(7);
+ compliantAlgos = {
+ "brainpoolP256r1",
+ "brainpoolP384r1",
+ "brainpoolP512r1",
+ "rsa3072",
+ "rsa4096",
+ };
+#if GPGMEPP_SUPPORTS_KYBER
+ if (engineIsVersion(2, 5, 2)) {
+ compliantAlgos.insert(compliantAlgos.end(),
+ {
+ "ky768_bp256",
+ "ky1024_bp384",
+ });
+ }
+#endif
};
- return isActive() ? compliantAlgos : Kleo::availableAlgorithms();
+ return compliantAlgos;
}
const std::vector<std::string> &Kleo::DeVSCompliance::preferredCompliantAlgorithms()
{
static std::vector<std::string> result;
if (result.empty()) {
const auto &preferredAlgos = Kleo::preferredAlgorithms();
result.reserve(preferredAlgos.size());
Kleo::copy_if(preferredAlgos, std::back_inserter(result), Kleo::DeVSCompliance::algorithmIsCompliant);
}
return result;
}
void Kleo::DeVSCompliance::decorate(QPushButton *button)
{
decorate(button, isCompliant());
}
void Kleo::DeVSCompliance::decorate(QPushButton *button, bool compliant)
{
if (!button) {
return;
}
if (compliant) {
button->setIcon(QIcon::fromTheme(QStringLiteral("security-high")));
if (!SystemInfo::isHighContrastModeActive()) {
const auto bgColor = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name();
button->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; };").arg(bgColor));
}
} else {
button->setIcon(QIcon::fromTheme(QStringLiteral("security-medium")));
if (!SystemInfo::isHighContrastModeActive()) {
const auto bgColor = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name();
button->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; };").arg(bgColor));
}
}
}
QString Kleo::DeVSCompliance::name()
{
return name(isCompliant());
}
static QString complianceName(bool compliant)
{
const auto filterId = compliant ? QStringLiteral("de-vs-filter") : QStringLiteral("not-de-vs-filter");
if (auto filter = KeyFilterManager::instance()->keyFilterByID(filterId)) {
return filter->name();
}
return compliant ? i18n("VS-NfD compliant") : i18n("Not VS-NfD compliant");
}
QString Kleo::DeVSCompliance::name(bool compliant)
{
if (!isActive()) {
return {};
}
if (compliant && isBetaCompliance()) {
return i18nc("@info append beta-marker to compliance", "%1 (beta)", complianceName(compliant));
}
return complianceName(compliant);
}
diff --git a/src/utils/gnupg.cpp b/src/utils/gnupg.cpp
index a2912667..aee999e1 100644
--- a/src/utils/gnupg.cpp
+++ b/src/utils/gnupg.cpp
@@ -1,747 +1,760 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/gnupg.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2020-2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "gnupg.h"
#include "assuan.h"
#include "compat.h"
#include "compliance.h"
#include "cryptoconfig.h"
#include "hex.h"
#include <libkleo_debug.h>
#include <KAboutComponent>
#include <KLocalizedString>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <QByteArray>
#include <QCoreApplication>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QPointer>
#include <QProcess>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QString>
#include <QThread>
#include <gpgme++/engineinfo.h>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <gpg-error.h>
#ifdef Q_OS_WIN
#include "gnupg-registry.h"
#endif // Q_OS_WIN
#include <algorithm>
#include <array>
using namespace GpgME;
QString Kleo::gnupgHomeDirectory()
{
static const QString homeDir = QString::fromUtf8(GpgME::dirInfo("homedir"));
return homeDir;
}
QString Kleo::gnupgPrivateKeysDirectory()
{
static const QString dir = QDir{gnupgHomeDirectory()}.filePath(QStringLiteral("private-keys-v1.d"));
return dir;
}
int Kleo::makeGnuPGError(int code)
{
return gpg_error(static_cast<gpg_err_code_t>(code));
}
static QString findGpgExe(GpgME::Engine engine, const QString &exe)
{
const GpgME::EngineInfo info = GpgME::engineInfo(engine);
return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(exe);
}
QString Kleo::gpgConfPath()
{
static const auto path = findGpgExe(GpgME::GpgConfEngine, QStringLiteral("gpgconf"));
return path;
}
QString Kleo::gpgSmPath()
{
static const auto path = findGpgExe(GpgME::GpgSMEngine, QStringLiteral("gpgsm"));
return path;
}
QString Kleo::gpgPath()
{
static const auto path = findGpgExe(GpgME::GpgEngine, QStringLiteral("gpg"));
return path;
}
QStringList Kleo::gnupgFileWhitelist()
{
return {
// The obvious pubring
QStringLiteral("pubring.gpg"),
// GnuPG 2.1 pubring
QStringLiteral("pubring.kbx"),
// Trust in X509 Certificates
QStringLiteral("trustlist.txt"),
// Trustdb controls ownertrust and thus WOT validity
QStringLiteral("trustdb.gpg"),
// We want to update when smartcard status changes
QStringLiteral("reader*.status"),
// No longer used in 2.1 but for 2.0 we want this
QStringLiteral("secring.gpg"),
// Secret keys (living under private-keys-v1.d/)
QStringLiteral("*.key"),
// the keyboxd database
QStringLiteral("pubring.db"),
// Changes to the trustmodel / compliance mode might
// affect validity so we check this, too.
// Globbing for gpg.conf* here will trigger too often
// as gpgconf creates files like gpg.conf.bak or
// gpg.conf.tmp12312.gpgconf that should not trigger
// a change.
QStringLiteral("gpg.conf"),
QStringLiteral("gpg.conf-?"),
QStringLiteral("gpg.conf-?.?"),
};
}
QStringList Kleo::gnupgFolderWhitelist()
{
static const QDir gnupgHome{gnupgHomeDirectory()};
return {
gnupgHome.path(),
gnupgPrivateKeysDirectory(),
// for the keyboxd database
gnupgHome.filePath(QStringLiteral("public-keys.d")),
};
}
QString Kleo::gpg4winInstallPath()
{
#ifdef Q_OS_WIN
// QApplication::applicationDirPath is only used as a fallback
// to support the case where Kleopatra is not installed from
// Gpg4win but Gpg4win is also installed.
char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", "Software\\GPG4Win", "Install Directory");
if (!instDir) {
// Fallback to HKCU
instDir = read_w32_registry_string("HKEY_CURRENT_USER", "Software\\GPG4Win", "Install Directory");
}
if (instDir) {
QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin");
free(instDir);
return ret;
}
qCDebug(LIBKLEO_LOG) << "Gpg4win not found. Falling back to Kleopatra instdir.";
#endif
return QCoreApplication::applicationDirPath();
}
QString Kleo::gnupgInstallPath()
{
#ifdef Q_OS_WIN
// QApplication::applicationDirPath is only used as a fallback
// to support the case where Kleopatra is not installed from
// Gpg4win but Gpg4win is also installed.
char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", "Software\\GnuPG", "Install Directory");
if (!instDir) {
// Fallback to HKCU
instDir = read_w32_registry_string("HKEY_CURRENT_USER", "Software\\GnuPG", "Install Directory");
}
if (instDir) {
QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin");
free(instDir);
return ret;
}
qCDebug(LIBKLEO_LOG) << "GnuPG not found. Falling back to gpgconf list dir.";
#endif
return gpgConfListDir("bindir");
}
QString Kleo::gpgConfListDir(const char *which)
{
if (!which || !*which) {
return QString();
}
const QString gpgConfPath = Kleo::gpgConfPath();
if (gpgConfPath.isEmpty()) {
return QString();
}
QProcess gpgConf;
qCDebug(LIBKLEO_LOG) << "gpgConfListDir: starting " << qPrintable(gpgConfPath) << " --list-dirs";
gpgConf.start(gpgConfPath, QStringList() << QStringLiteral("--list-dirs"));
if (!gpgConf.waitForFinished()) {
qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): failed to execute gpgconf: " << qPrintable(gpgConf.errorString());
qCDebug(LIBKLEO_LOG) << "output was:\n" << gpgConf.readAllStandardError().constData();
return QString();
}
const QList<QByteArray> lines = gpgConf.readAllStandardOutput().split('\n');
for (const QByteArray &line : lines) {
if (line.startsWith(which) && line[qstrlen(which)] == ':') {
const int begin = qstrlen(which) + 1;
int end = line.size();
while (end && (line[end - 1] == '\n' || line[end - 1] == '\r')) {
--end;
}
const QString result = QDir::fromNativeSeparators(QFile::decodeName(hexdecode(line.mid(begin, end - begin))));
qCDebug(LIBKLEO_LOG) << "gpgConfListDir: found " << qPrintable(result) << " for '" << which << "'entry";
return result;
}
}
qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): didn't find '" << which << "'"
<< "entry in output:\n"
<< gpgConf.readAllStandardError().constData();
return QString();
}
static std::array<int, 3> getVersionFromString(const char *actual, bool &ok)
{
std::array<int, 3> ret{-1, -1, -1};
ok = false;
if (!actual) {
return ret;
}
QString versionString = QString::fromLatin1(actual);
// Try to fix it up
QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1StringView(R"((\d+)\.(\d+)\.(\d+)(?:-svn\d+)?.*)")));
QRegularExpressionMatch match;
for (int i = 0; i < 3; i++) {
match = rx.match(versionString);
if (!match.hasMatch()) {
versionString += QStringLiteral(".0");
} else {
ok = true;
break;
}
}
if (!ok) {
qCDebug(LIBKLEO_LOG) << "Can't parse version " << actual;
return ret;
}
for (int i = 0; i < 3; ++i) {
ret[i] = match.capturedView(i + 1).toUInt(&ok);
if (!ok) {
return ret;
}
}
ok = true;
return ret;
}
bool Kleo::versionIsAtLeast(const char *minimum, const char *actual)
{
if (!minimum || !actual) {
return false;
}
bool ok;
const auto minimum_version = getVersionFromString(minimum, ok);
if (!ok) {
return false;
}
const auto actual_version = getVersionFromString(actual, ok);
if (!ok) {
return false;
}
return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(minimum_version), std::end(minimum_version));
}
bool Kleo::engineIsVersion(int major, int minor, int patch, GpgME::Engine engine)
{
static QMap<Engine, std::array<int, 3>> cachedVersions;
const int required_version[] = {major, minor, patch};
// Gpgconf means spawning processes which is expensive on windows.
std::array<int, 3> actual_version;
if (!cachedVersions.contains(engine)) {
const Error err = checkEngine(engine);
if (err.code() == GPG_ERR_INV_ENGINE) {
qCDebug(LIBKLEO_LOG) << "isVersion: invalid engine. '";
return false;
}
const char *actual = GpgME::engineInfo(engine).version();
bool ok;
actual_version = getVersionFromString(actual, ok);
qCDebug(LIBKLEO_LOG) << "Parsed" << actual << "as: " << actual_version[0] << '.' << actual_version[1] << '.' << actual_version[2] << '.';
if (!ok) {
return false;
}
cachedVersions.insert(engine, actual_version);
} else {
actual_version = cachedVersions.value(engine);
}
// return ! ( actual_version < required_version )
return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(required_version), std::end(required_version));
}
const QString &Kleo::paperKeyInstallPath()
{
static const QString pkPath = (QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()).isEmpty()
? QStandardPaths::findExecutable(QStringLiteral("paperkey"))
: QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()));
return pkPath;
}
bool Kleo::haveKeyserverConfigured()
{
if (engineIsVersion(2, 4, 4) //
|| (engineIsVersion(2, 2, 42) && !engineIsVersion(2, 3, 0))) {
return Kleo::keyserver() != QLatin1StringView{"none"};
}
if (engineIsVersion(2, 1, 19)) {
// since 2.1.19 there is a builtin keyserver
return true;
}
return !Kleo::keyserver().isEmpty();
}
QString Kleo::keyserver()
{
QString result = getCryptoConfigStringValue("gpg", "keyserver");
if (result.isEmpty()) {
result = getCryptoConfigStringValue("dirmngr", "keyserver");
}
if (result.endsWith(QLatin1StringView{"://none"})) {
// map hkps://none, etc., to "none"; see https://dev.gnupg.org/T6708
result = QStringLiteral("none");
}
return result;
}
bool Kleo::haveX509DirectoryServerConfigured()
{
return !getCryptoConfigUrlList("dirmngr", "ldapserver").empty() //
|| !getCryptoConfigUrlList("dirmngr", "LDAP Server").empty() //
|| !getCryptoConfigUrlList("gpgsm", "keyserver").empty();
}
bool Kleo::gpgComplianceP(const char *mode)
{
const auto conf = QGpgME::cryptoConfig();
const auto entry = getCryptoConfigEntry(conf, "gpg", "compliance");
return entry && entry->stringValue() == QString::fromLatin1(mode);
}
bool Kleo::gnupgUsesDeVsCompliance()
{
return DeVSCompliance::isActive();
}
bool Kleo::gnupgIsDeVsCompliant()
{
return DeVSCompliance::isCompliant();
}
#ifdef Q_OS_WIN
static unsigned int guessConsoleOutputCodePage()
{
/* Qt on Windows uses GetACP while GnuPG prefers
* GetConsoleOutputCP.
*
* As we are not a console application GetConsoleOutputCP
* usually returns 0.
* From experience the closest thing that let's us guess
* what GetConsoleOutputCP returns for a console application
* it appears to be the OEMCP.
*/
unsigned int cpno = GetConsoleOutputCP();
if (!cpno) {
cpno = GetOEMCP();
}
if (!cpno) {
cpno = GetACP();
}
if (!cpno) {
qCDebug(LIBKLEO_LOG) << __func__ << "Failed to find native codepage";
}
qCDebug(LIBKLEO_LOG) << __func__ << "returns" << cpno;
return cpno;
}
static QString fromEncoding(unsigned int src_encoding, const char *data)
{
if (!data || !*data) {
return {};
}
// returns necessary buffer size including the terminating null character
int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0);
if (n <= 0) {
qCDebug(LIBKLEO_LOG) << __func__ << "determining necessary buffer size failed with error code" << GetLastError();
return QString();
}
wchar_t *result = (wchar_t *)malloc((n + 1) * sizeof *result);
n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n);
if (n <= 0) {
free(result);
qCDebug(LIBKLEO_LOG) << __func__ << "conversion failed with error code" << GetLastError();
return QString();
}
const auto ret = QString::fromWCharArray(result, n - 1);
free(result);
return ret;
}
static QString stringFromGpgOutput_legacy(const QByteArray &ba)
{
static const unsigned int cpno = guessConsoleOutputCodePage();
if (cpno) {
qCDebug(LIBKLEO_LOG) << __func__ << "trying to decode" << ba << "using codepage" << cpno;
const auto rawData = QByteArray{ba}.replace("\r\n", "\n");
const auto s = fromEncoding(cpno, rawData.constData());
if (!s.isEmpty() || ba.isEmpty()) {
return s;
}
qCDebug(LIBKLEO_LOG) << __func__ << "decoding output failed; falling back to QString::fromLocal8Bit()";
}
qCDebug(LIBKLEO_LOG) << __func__ << "decoding from local encoding:" << ba;
return QString::fromLocal8Bit(ba);
}
#endif
QString Kleo::stringFromGpgOutput(const QByteArray &ba)
{
#ifdef Q_OS_WIN
// since 2.2.28, GnuPG always uses UTF-8 for console output (and input)
if (Kleo::engineIsVersion(2, 2, 28, GpgME::GpgEngine)) {
return QString::fromUtf8(ba);
} else {
return stringFromGpgOutput_legacy(ba);
}
#else
return QString::fromLocal8Bit(ba);
#endif
}
QStringList Kleo::backendVersionInfo()
{
const auto components = backendComponents();
QStringList versions;
for (const auto &component : components) {
versions.push_back(component.name() + u' ' + component.version());
}
return versions;
}
QList<KAboutComponent> Kleo::backendComponents()
{
QList<KAboutComponent> components;
if (Kleo::engineIsVersion(2, 2, 24, GpgME::GpgConfEngine)) {
QProcess p;
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions ...";
p.start(Kleo::gpgConfPath(), {QStringLiteral("--show-versions")});
// wait at most 1 second
if (!p.waitForFinished(1000)) {
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions timed out after 1 second.";
} else if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) {
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions failed:" << p.errorString();
qCDebug(LIBKLEO_LOG) << "gpgconf stderr:" << p.readAllStandardError();
qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput();
} else {
const QByteArray output = p.readAllStandardOutput().replace("\r\n", "\n");
qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << output;
const auto lines = output.split('\n');
for (const auto &line : lines) {
if (line.startsWith("* GnuPG")) {
const auto componentsLine = line.split(' ');
components.append(KAboutComponent(QStringLiteral("GnuPG"),
i18nc("@info", "GnuPG provides support for OpenPGP/LibrePGP and S/MIME."),
QString::fromLatin1(componentsLine.value(2)),
QStringLiteral("https://gnupg.org"),
KAboutLicense::GPL_V3));
}
if (line.startsWith("* Libgcrypt")) {
const auto componentsLine = line.split(' ');
components.append(KAboutComponent(QStringLiteral("Libgcrypt"),
i18nc("@info", "Libgcrypt is a general purpose cryptographic library."),
QString::fromLatin1(componentsLine.value(2)),
QStringLiteral("https://www.gnupg.org/software/libgcrypt/index.html"),
KAboutLicense::LGPL_V2_1));
}
}
}
}
return components;
}
namespace
{
void runGpgConf(const QStringList &arguments)
{
QProcess process;
process.setProgram(Kleo::gpgConfPath());
process.setArguments(arguments);
qCDebug(LIBKLEO_LOG) << "Starting gpgconf (" << &process << ") with arguments" << process.arguments().join(QLatin1Char(' ')) << " ...";
process.start();
if (!process.waitForStarted(5000 /* wait at most 5 seconds */)) {
qCDebug(LIBKLEO_LOG) << "gpgconf failed to start:" << process.errorString() << "\nstderr:" << process.readAllStandardError();
return;
}
if (!process.waitForFinished(5000 /* wait at most 5 seconds */)) {
qCDebug(LIBKLEO_LOG) << "gpgconf did not exit after 5 seconds:" << process.errorString() << "\nstderr:" << process.readAllStandardError();
return;
}
qCDebug(LIBKLEO_LOG) << "gpgconf (" << &process << ") exited with exit code" << process.exitCode() << ")";
if (process.exitCode() > 0) {
qCDebug(LIBKLEO_LOG) << "gpgconf stderr:" << process.readAllStandardError();
}
}
template<typename Function1, typename Function2>
auto startGpgConf(const QStringList &arguments, Function1 onSuccess, Function2 onFailure)
{
auto process = new QProcess;
process->setProgram(Kleo::gpgConfPath());
process->setArguments(arguments);
QObject::connect(process, &QProcess::started, process, [process]() {
qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") was started successfully";
});
QObject::connect(process, &QProcess::errorOccurred, process, [process, onFailure](auto error) {
qCDebug(LIBKLEO_LOG).nospace() << "Error while running gpgconf (" << process << "): " << error;
process->deleteLater();
onFailure();
});
QObject::connect(process, &QProcess::readyReadStandardError, process, [process]() {
for (const auto &line : process->readAllStandardError().trimmed().split('\n')) {
qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") stderr: " << line;
}
});
QObject::connect(process, &QProcess::readyReadStandardOutput, process, [process]() {
(void)process->readAllStandardOutput(); /* ignore stdout */
});
QObject::connect(process, &QProcess::finished, process, [process, onSuccess, onFailure](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::NormalExit) {
qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") exited (exit code: " << exitCode << ")";
if (exitCode == 0) {
onSuccess();
} else {
onFailure();
}
} else {
qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") crashed (exit code: " << exitCode << ")";
onFailure();
}
process->deleteLater();
});
qCDebug(LIBKLEO_LOG).nospace() << "Starting gpgconf (" << process << ") with arguments " << process->arguments().join(QLatin1Char(' ')) << " ...";
process->start();
return process;
}
static void launchGpgAgentWithEventLoop()
{
static thread_local QProcess *process = nullptr;
static thread_local qint64 mSecsSinceEpochOfLastLaunch = 0;
static thread_local int numberOfFailedLaunches = 0;
if (process) {
qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already being launched";
return;
}
const auto now = QDateTime::currentMSecsSinceEpoch();
if (now - mSecsSinceEpochOfLastLaunch < 1000) {
// reduce attempts to launch the agent to 1 attempt per second
return;
}
mSecsSinceEpochOfLastLaunch = now;
if (numberOfFailedLaunches > 5) {
qCWarning(LIBKLEO_LOG) << __func__ << ": Launching gpg-agent failed" << numberOfFailedLaunches << "times in a row. Giving up.";
return;
}
process = startGpgConf(
{QStringLiteral("--launch"), QStringLiteral("gpg-agent")},
[]() {
numberOfFailedLaunches = 0;
process = nullptr;
},
[]() {
numberOfFailedLaunches++;
process = nullptr;
});
}
}
void Kleo::launchGpgAgent(Kleo::LaunchGpgAgentOptions options)
{
if ((options == CheckForRunningAgent) && Kleo::Assuan::agentIsRunning()) {
qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already running";
return;
}
if (QThread::currentThread()->loopLevel() > 0) {
launchGpgAgentWithEventLoop();
} else {
runGpgConf({QStringLiteral("--launch"), QStringLiteral("gpg-agent")});
}
}
void Kleo::restartGpgAgent()
{
static QPointer<QProcess> process;
if (process) {
qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already being restarted";
return;
}
auto startAgent = []() {
Kleo::launchGpgAgent(SkipCheckForRunningAgent);
};
process = startGpgConf({QStringLiteral("--kill"), QStringLiteral("all")}, startAgent, startAgent);
}
const std::vector<std::string> &Kleo::availableAlgorithms()
{
- static const std::vector<std::string> algos = {
- "brainpoolP256r1",
- "brainpoolP384r1",
- "brainpoolP512r1",
- "curve25519",
- "curve448",
- "nistp256",
- "nistp384",
- "nistp521",
- "rsa2048",
- "rsa3072",
- "rsa4096",
- // "secp256k1", // Curve secp256k1 is explicitly ignored
+ static std::vector<std::string> algos;
+ if (algos.empty()) {
+ algos.reserve(13);
+ algos = {
+ "brainpoolP256r1",
+ "brainpoolP384r1",
+ "brainpoolP512r1",
+ "curve25519",
+ "curve448",
+ "nistp256",
+ "nistp384",
+ "nistp521",
+ "rsa2048",
+ "rsa3072",
+ "rsa4096",
+ // "secp256k1", // Curve secp256k1 is explicitly ignored
+ };
+#if GPGMEPP_SUPPORTS_KYBER
+ if (engineIsVersion(2, 5, 2)) {
+ algos.insert(algos.end(),
+ {
+ "ky768_bp256",
+ "ky1024_bp384",
+ });
+ }
+#endif
};
return algos;
}
const std::vector<std::string> &Kleo::preferredAlgorithms()
{
static const std::vector<std::string> algos = {
"curve25519",
"brainpoolP256r1",
"rsa3072",
"rsa2048",
};
return algos;
}
const std::vector<std::string> &Kleo::ignoredAlgorithms()
{
static const std::vector<std::string> algos = {
"secp256k1", // Curve secp256k1 is not useful
};
return algos;
}
bool Kleo::gpgvVerify(const QString &filePath, const QString &sigPath, const QString &keyring, const QStringList &additionalSearchPaths)
{
const QFileInfo verifyFi(filePath);
if (!verifyFi.isReadable()) {
return false;
} else {
qCDebug(LIBKLEO_LOG) << "Verifying" << filePath;
}
const auto gpgvPath = QStandardPaths::findExecutable(QStringLiteral("gpgv"), additionalSearchPaths);
if (gpgvPath.isEmpty()) {
qCDebug(LIBKLEO_LOG) << "Could not find gpgv";
return false;
}
QFileInfo sigFi;
if (!sigPath.isEmpty()) {
sigFi.setFile(sigPath);
} else {
sigFi.setFile(filePath + QStringLiteral(".sig"));
}
if (!sigFi.isReadable()) {
qCDebug(LIBKLEO_LOG) << "No signature found at" << sigFi.absoluteFilePath();
return false;
}
auto process = QProcess();
process.setProgram(gpgvPath);
QStringList args;
if (!keyring.isEmpty()) {
args << QStringLiteral("--keyring") << keyring;
}
args << QStringLiteral("--") << sigFi.absoluteFilePath() << verifyFi.absoluteFilePath();
process.setArguments(args);
qCDebug(LIBKLEO_LOG).nospace() << "Starting gpgv (" << gpgvPath << ") with arguments " << args.join(QLatin1Char(' ')) << " ...";
process.start();
if (!process.waitForFinished(-1)) {
qCDebug(LIBKLEO_LOG) << "Failed to execute gpgv" << process.errorString();
}
bool ret = (process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0);
if (!ret) {
qCDebug(LIBKLEO_LOG) << "Failed to verify file";
qCDebug(LIBKLEO_LOG) << "gpgv stdout:" << QString::fromUtf8(process.readAllStandardOutput());
qCDebug(LIBKLEO_LOG) << "gpgv stderr:" << QString::fromUtf8(process.readAllStandardError());
}
return ret;
}
std::vector<QByteArray> Kleo::readSecretKeyFile(const QString &keyGrip)
{
const auto filename = QStringLiteral("%1.key").arg(keyGrip);
const auto path = QDir{Kleo::gnupgPrivateKeysDirectory()}.filePath(filename);
QFile file{path};
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCDebug(LIBKLEO_LOG) << "Cannot open the private key file" << path << "for reading";
return {};
}
std::vector<QByteArray> lines;
while (!file.atEnd()) {
lines.push_back(file.readLine());
}
if (lines.empty()) {
qCDebug(LIBKLEO_LOG) << "The private key file" << path << "is empty";
}
return lines;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Feb 7, 7:37 AM (1 d, 9 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
88/98/c24756b3c3dca5b904971d88499a
Attached To
rLIBKLEO Libkleo
Event Timeline
Log In to Comment