diff --git a/src/commands/newopenpgpcertificatecommand.cpp b/src/commands/newopenpgpcertificatecommand.cpp index d92ef1c1a..1dd8433f7 100644 --- a/src/commands/newopenpgpcertificatecommand.cpp +++ b/src/commands/newopenpgpcertificatecommand.cpp @@ -1,277 +1,330 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/newopenpgpcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "newopenpgpcertificatecommand.h" #include "command_p.h" #include "dialogs/newopenpgpcertificatedetailsdialog.h" #include "kleopatraapplication.h" #include "utils/emptypassphraseprovider.h" #include "utils/keyparameters.h" #include "utils/userinfo.h" #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include using namespace Kleo; using namespace GpgME; class NewOpenPGPCertificateCommand::Private : public Command::Private { friend class ::Kleo::NewOpenPGPCertificateCommand; NewOpenPGPCertificateCommand *q_func() const { return static_cast(q); } public: explicit Private(NewOpenPGPCertificateCommand *qq, KeyListController *c) : Command::Private{qq, c} { } void getCertificateDetails(); void createCertificate(); - void showResult(const KeyGenerationResult &result); + void showResult(const KeyGenerationResult &result, const Key &key, const GpgME::Error &error); void showErrorDialog(const KeyGenerationResult &result); + void addAdsk(const Key &key, const QString &fingerprint, const KeyGenerationResult &result); + void handleKeyGenerationResult(const KeyGenerationResult &result); private: KeyParameters keyParameters; bool protectKeyWithPassword = false; EmptyPassphraseProvider emptyPassphraseProvider; QPointer detailsDialog; QPointer job; QPointer progressDialog; + QString adskfpr; }; NewOpenPGPCertificateCommand::Private *NewOpenPGPCertificateCommand::d_func() { return static_cast(d.get()); } const NewOpenPGPCertificateCommand::Private *NewOpenPGPCertificateCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() void NewOpenPGPCertificateCommand::Private::getCertificateDetails() { detailsDialog = new NewOpenPGPCertificateDetailsDialog; detailsDialog->setAttribute(Qt::WA_DeleteOnClose); applyWindowID(detailsDialog); if (keyParameters.protocol() == KeyParameters::NoProtocol) { const auto settings = Kleo::Settings{}; const KConfigGroup config{KSharedConfig::openConfig(), QLatin1StringView("CertificateCreationWizard")}; // prefer the last used name and email address over the values retrieved from the system detailsDialog->setName(config.readEntry("NAME", QString{})); if (detailsDialog->name().isEmpty() && settings.prefillName()) { detailsDialog->setName(userFullName()); } detailsDialog->setEmail(config.readEntry("EMAIL", QString{})); if (detailsDialog->email().isEmpty() && settings.prefillEmail()) { detailsDialog->setEmail(userEmailAddress()); } } else { detailsDialog->setKeyParameters(keyParameters); detailsDialog->setProtectKeyWithPassword(protectKeyWithPassword); } connect(detailsDialog, &QDialog::accepted, q, [this]() { keyParameters = detailsDialog->keyParameters(); protectKeyWithPassword = detailsDialog->protectKeyWithPassword(); QMetaObject::invokeMethod( q, [this] { createCertificate(); }, Qt::QueuedConnection); }); connect(detailsDialog, &QDialog::rejected, q, [this]() { canceled(); }); detailsDialog->show(); } void NewOpenPGPCertificateCommand::Private::createCertificate() { Q_ASSERT(keyParameters.protocol() == KeyParameters::OpenPGP); auto keyGenJob = QGpgME::openpgp()->keyGenerationJob(); if (!keyGenJob) { finished(); return; } if (!protectKeyWithPassword) { auto ctx = QGpgME::Job::context(keyGenJob); ctx->setPassphraseProvider(&emptyPassphraseProvider); ctx->setPinentryMode(Context::PinentryLoopback); } auto settings = KleopatraApplication::instance()->distributionSettings(); if (settings) { keyParameters.setComment(settings->value(QStringLiteral("uidcomment"), {}).toString()); } if (auto settings = Settings{}; !settings.designatedRevoker().isEmpty()) { keyParameters.addDesignatedRevoker(settings.designatedRevoker()); } connect(keyGenJob, &QGpgME::KeyGenerationJob::result, q, [this](const KeyGenerationResult &result) { - QMetaObject::invokeMethod( - q, - [this, result] { - showResult(result); - }, - Qt::QueuedConnection); + handleKeyGenerationResult(result); }); + if (const Error err = keyGenJob->start(keyParameters.toString())) { error(i18n("Could not start key pair creation: %1", Formatting::errorAsString(err))); finished(); return; } else { job = keyGenJob; } progressDialog = new QProgressDialog; progressDialog->setAttribute(Qt::WA_DeleteOnClose); applyWindowID(progressDialog); progressDialog->setModal(true); progressDialog->setWindowTitle(i18nc("@title", "Creating Key Pair...")); progressDialog->setLabelText(i18n("The process of creating a key requires large amounts of random numbers. This may require several minutes...")); progressDialog->setRange(0, 0); connect(progressDialog, &QProgressDialog::canceled, job, &QGpgME::Job::slotCancel); connect(job, &QGpgME::Job::done, q, [this]() { if (progressDialog) { progressDialog->accept(); } }); progressDialog->show(); } -void NewOpenPGPCertificateCommand::Private::showResult(const KeyGenerationResult &result) +void NewOpenPGPCertificateCommand::Private::showResult(const KeyGenerationResult &result, const Key &key, const GpgME::Error &adskError) { - if (result.error().isCanceled()) { - finished(); - return; - } - - // Ensure that we have the key in the cache - Key key; - if (!result.error().code() && result.fingerprint()) { - std::unique_ptr ctx{Context::createForProtocol(OpenPGP)}; - if (ctx) { - Error err; - key = ctx->key(result.fingerprint(), err, /*secret=*/true); - if (!key.isNull()) { - KeyCache::mutableInstance()->insert(key); - } - } - } - if (!key.isNull()) { - success( - xi18n("A new OpenPGP certificate was created successfully." - "Fingerprint of the new certificate: %1", - Formatting::prettyID(key.primaryFingerprint()))); + if (adskError) { + success( + xi18n("A new OpenPGP certificate was created successfully, but adding an ADSK failed: %1" + "Fingerprint of the new certificate: %2", + Formatting::errorAsString(adskError), + Formatting::prettyID(key.primaryFingerprint()))); + } else { + success( + xi18n("A new OpenPGP certificate was created successfully." + "Fingerprint of the new certificate: %1", + Formatting::prettyID(key.primaryFingerprint()))); + } finished(); } else { showErrorDialog(result); } } void NewOpenPGPCertificateCommand::Private::showErrorDialog(const KeyGenerationResult &result) { QString text; if (result.error() || !result.fingerprint()) { text = xi18n( "The creation of a new OpenPGP certificate failed." "Error: %1", Formatting::errorAsString(result.error())); } else { // no error and we have a fingerprint, but there was no corresponding key in the key ring text = xi18n( "A new OpenPGP certificate was created successfully, but it has not been found in the key ring." "Fingerprint of the new certificate:%1", Formatting::prettyID(result.fingerprint())); } auto dialog = new QDialog; applyWindowID(dialog); dialog->setWindowTitle(i18nc("@title:window", "Error")); auto buttonBox = new QDialogButtonBox{QDialogButtonBox::Retry | QDialogButtonBox::Ok, dialog}; const auto buttonCode = KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Critical, text, {}, {}, nullptr, {}); if (buttonCode == QDialogButtonBox::Retry) { QMetaObject::invokeMethod( q, [this]() { getCertificateDetails(); }, Qt::QueuedConnection); } else { finished(); } } NewOpenPGPCertificateCommand::NewOpenPGPCertificateCommand() : NewOpenPGPCertificateCommand(nullptr, nullptr) { } NewOpenPGPCertificateCommand::NewOpenPGPCertificateCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { } NewOpenPGPCertificateCommand::~NewOpenPGPCertificateCommand() = default; void NewOpenPGPCertificateCommand::doStart() { d->getCertificateDetails(); } void NewOpenPGPCertificateCommand::doCancel() { if (d->detailsDialog) { d->detailsDialog->close(); } if (d->job) { d->job->slotCancel(); } } +void NewOpenPGPCertificateCommand::Private::addAdsk(const Key &key, const QString &fingerprint, const KeyGenerationResult &result) +{ + const auto backend = QGpgME::openpgp(); + if (!backend) { + finished(); + return; + } + + const auto job = backend->quickJob(); + if (!job) { + finished(); + return; + } + + job->startAddAdsk(key, fingerprint.toLatin1().data()); + connect(job, &QGpgME::QuickJob::result, q, [key, this, result](const auto &error) { + QMetaObject::invokeMethod( + q, + [key, error, this, result] { + showResult(result, key, error); + }, + Qt::QueuedConnection); + }); +} + +void NewOpenPGPCertificateCommand::Private::handleKeyGenerationResult(const KeyGenerationResult &result) +{ + if (result.error().isCanceled()) { + finished(); + return; + } + + // Ensure that we have the key in the cache + Key key; + if (!result.error() && result.fingerprint()) { + std::unique_ptr ctx{Context::createForProtocol(OpenPGP)}; + if (ctx) { + Error err; + key = ctx->key(result.fingerprint(), err, /*secret=*/true); + if (!key.isNull()) { + KeyCache::mutableInstance()->insert(key); + } + } + } + + adskfpr = Settings{}.mandatoryADSK(); + if (!adskfpr.isEmpty() && !key.isNull()) { + QMetaObject::invokeMethod( + q, + [this, result, key] { + addAdsk(key, adskfpr, result); + }, + Qt::QueuedConnection); + return; + } + QMetaObject::invokeMethod( + q, + [this, result, key] { + showResult(result, key, GpgME::Error()); + }, + Qt::QueuedConnection); +} + #undef d #undef q #include "moc_newopenpgpcertificatecommand.cpp" diff --git a/src/kcfg/settings.kcfg b/src/kcfg/settings.kcfg index 659e13872..b92ce8cca 100644 --- a/src/kcfg/settings.kcfg +++ b/src/kcfg/settings.kcfg @@ -1,220 +1,224 @@ This text will be used as placeholder text for the common name (CN) field of S/MIME certificates. If true, then the common name (CN) field of S/MIME certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true This text will be shown above the email address field of OpenPGP certificates and as placeholder in that field for S/MIME certificates. If true, then the email address field of OpenPGP and S/MIME certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true Prefilled value for the email address field of OpenPGP and S/MIME certificates. This will override EMAIL_prefill. It is useful if no or unsuitable system settings are found for EMAIL_prefill. This text will be shown above the name field of OpenPGP certificates. If true, then the name field of OpenPGP certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true Prefilled value for the name field of OpenPGP certificates. This will override NAME_prefill. It is useful if no or an unsuitable system setting is found for NAME_prefill. Specifies the default validity period of new or extended OpenPGP keys in days. This setting specifies how many days an OpenPGP key will be valid by default at creation or change of validity, or, in other words, after how many days the key will expire. Set this to 0 for unlimited validity. If this setting is not set or is set to a negative value, then new or extended OpenPGP keys will be valid for three years (possibly clamped to the allowed minimum or maximum validity period) by default. -1 Specifies the minimum allowed validity period of new or extended OpenPGP keys in days. This setting specifies the minimum number of days a user can choose for the validity period of OpenPGP certificates. It applies at key creation and change of validity. 1 Specifies the maximum allowed validity period of new or extended OpenPGP keys in days. This setting specifies the maximum number of days a user can choose for the validity period of OpenPGP certificates. It applies at key creation and change of validity. If this setting is not set or is set to a negative value, then unlimited validity is allowed. -1 The fingerprint of a designated revoker to be added to each new key. Must be given in the form "algo:fpr [sensitive]". Algo is the public key algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.) fpr is the fingerprint of the designated revoker. The optional ‘sensitive’ flag marks the designated revoker as sensitive information. Only v4 keys may be designated revokers. GnuPG 2.4.4 is required for adding sensitive revokers. If true, hides the advanced settings button in the new certificate wizard. false + + + Fingerprint of a subkey to be added as an ADSK to all new OpenPGP keys, or empty if no ADSK is needed. + Specifies the default validity of certifications in days. This setting specifies how many days a certification is valid by default, or, in other words, after how many days a new certification will expire. Set this to 0 for unlimited validity of certifications. 0 sha256sum If true, then the results are shown after successfully signing the clipboard. true If true, then the results are shown after successfully encrypting the clipboard. true Enables support for S/MIME (CMS). If false, then Kleopatra's main UI will not offer any functionality related to S/MIME (CMS). true Allows the creation of S/MIME certificate signing requests. If false, then Kleopatra will not offer the creation of S/MIME certificate signing requests. true Allows signing of text or files with S/MIME certificates. If false, then Kleopatra will not offer functionality for creating signatures with S/MIME certificates. true true true true true true true Specifies the display order of the DN attributes of X.509 certificates. Enable usage of groups of keys. Enable usage of groups of keys to create lists of recipients. true If enabled, then Kleopatra will automatically try to retrieve the keys that were used to certify the user ids of newly imported OpenPGP keys. This is useful in combination with trusted introducers. false By default, Kleopatra only queries the certificate directories of providers (WKD) for user IDs that were originally retrieved from a WKD when you update an OpenPGP certificate. If this option is enabled, then Kleopatra will query WKDs for all user IDs. false If enabled, then Kleopatra will show notifications in some place when using certificates that are about to expire soon. true This is a list of URL schemes that shall be blocked by the application. This can be used to prevent the application from opening external applications for certain URLs. Searches for the certificates belonging the smartcard keys on the configured keyserver. Searches on keyservers regardless of the protocol for the smartcards key, regardless of the keyserver protocol. Default behavior is to only do this for LDAP keyservers. false Automatically load S/MIME certificates from PKCS#15 (CardOS) smartcards If true, then Kleopatra will call gpgsm --learn if a PKCS#15 Smartcard is inserted with unknown certificates. This can take a while and blocks the smartcard while the command is running. true false