Commit 20107d80 authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Use separate dialogs instead of the wizard when creating OpenPGP keys

At least orca (screen reader) seems to have problems with wizards. If a
new page of the wizard is shown, then orca doesn't read the title/subtitle
of the new page, so that a user does really notice that a new page is
shown. Using separate dialogs for the different stages of key creation
avoids this problem.

GnuPG-bug-id: 5832
parent 8f1a3f1f
......@@ -175,6 +175,8 @@ set(_kleopatra_SRCS
commands/lookupcertificatescommand.h
commands/newcertificatecommand.cpp
commands/newcertificatecommand.h
commands/newopenpgpcertificatecommand.cpp
commands/newopenpgpcertificatecommand.h
commands/pivgeneratecardkeycommand.cpp
commands/pivgeneratecardkeycommand.h
commands/refreshcertificatecommand.cpp
......@@ -333,6 +335,10 @@ set(_kleopatra_SRCS
dialogs/lookupcertificatesdialog.h
dialogs/nameandemailwidget.cpp
dialogs/nameandemailwidget.h
dialogs/newopenpgpcertificatedetailsdialog.cpp
dialogs/newopenpgpcertificatedetailsdialog.h
dialogs/newopenpgpcertificateresultdialog.cpp
dialogs/newopenpgpcertificateresultdialog.h
dialogs/ownertrustdialog.cpp
dialogs/ownertrustdialog.h
dialogs/pivcardapplicationadministrationkeyinputdialog.cpp
......
......@@ -3,6 +3,8 @@
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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -12,12 +14,14 @@
#include "newcertificatecommand.h"
#include "command_p.h"
#include "newopenpgpcertificatecommand.h"
#include "dialogs/choosecertificateprotocoldialog.h"
#include "newcertificatewizard/newcertificatewizard.h"
#include <settings.h>
#include <kleopatra_debug.h>
using namespace Kleo;
using namespace Kleo::Commands;
......@@ -89,7 +93,20 @@ void NewCertificateCommand::Private::onProtocolChosen()
protocol = protocolDialog->protocol();
protocolDialog->deleteLater();
createCertificate();
if (protocol == GpgME::OpenPGP) {
auto cmd = new NewOpenPGPCertificateCommand{view(), controller()};
if (parentWidgetOrView() != view()) {
cmd->setParentWidget(parentWidgetOrView());
}
cmd->setParentWId(parentWId());
connect(cmd, &NewOpenPGPCertificateCommand::finished,
q, [this]() { finished(); });
connect(cmd, &NewOpenPGPCertificateCommand::canceled,
q, [this]() { canceled(); });
cmd->start();
} else {
createCertificate();
}
}
void Kleo::Commands::NewCertificateCommand::Private::createCertificate()
......
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "newopenpgpcertificatecommand.h"
#include "command_p.h"
#include "commands/detailscommand.h"
#include "dialogs/newopenpgpcertificatedetailsdialog.h"
#include "dialogs/newopenpgpcertificateresultdialog.h"
#include "utils/emptypassphraseprovider.h"
#include "utils/keyparameters.h"
#include "utils/userinfo.h"
#include <settings.h>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KConfigGroup>
#include <KGuiItem>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QGpgME/KeyGenerationJob>
#include <QGpgME/Protocol>
#include <QProgressDialog>
#include <gpgme++/context.h>
#include <gpgme++/keygenerationresult.h>
#include <kleopatra_debug.h>
using namespace Kleo;
using namespace GpgME;
class NewOpenPGPCertificateCommand::Private : public Command::Private
{
friend class ::Kleo::NewOpenPGPCertificateCommand;
NewOpenPGPCertificateCommand *q_func() const
{
return static_cast<NewOpenPGPCertificateCommand *>(q);
}
public:
explicit Private(NewOpenPGPCertificateCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
void getCertificateDetails();
void createCertificate();
void showResult(const KeyGenerationResult &result);
void showResultDialog(const KeyGenerationResult &result, const Key &key);
void showErrorDialog(const KeyGenerationResult &result);
private:
KeyParameters keyParameters;
bool protectKeyWithPassword = false;
EmptyPassphraseProvider emptyPassphraseProvider;
QPointer<NewOpenPGPCertificateDetailsDialog> detailsDialog;
QPointer<QGpgME::Job> job;
QPointer<QProgressDialog> progressDialog;
QPointer<NewOpenPGPCertificateResultDialog> resultDialog;
};
NewOpenPGPCertificateCommand::Private *NewOpenPGPCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const NewOpenPGPCertificateCommand::Private *NewOpenPGPCertificateCommand::d_func() const
{
return static_cast<const Private *>(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(), "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);
}
connect(keyGenJob, &QGpgME::KeyGenerationJob::result, q, [this](const KeyGenerationResult &result) {
QMetaObject::invokeMethod(
q,
[this, result] {
showResult(result);
},
Qt::QueuedConnection);
});
if (const Error err = keyGenJob->start(keyParameters.toString())) {
error(i18n("Could not start key pair creation: %1", QString::fromUtf8(err.asString())));
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)
{
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<Context> ctx{Context::createForProtocol(OpenPGP)};
if (ctx) {
Error err;
key = ctx->key(result.fingerprint(), err, /*secret=*/true);
if (!key.isNull()) {
KeyCache::mutableInstance()->insert(key);
}
}
}
const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard");
if (config.readEntry("SkipResultPage", false)) {
if (!key.isNull()) {
auto cmd = new Commands::DetailsCommand(key, view(), controller());
cmd->setParentWidget(parentWidgetOrView());
cmd->setParentWId(parentWId());
cmd->start();
}
finished();
return;
}
if (!key.isNull()) {
showResultDialog(result, key);
} else {
showErrorDialog(result);
}
}
void NewOpenPGPCertificateCommand::Private::showResultDialog(const KeyGenerationResult &result, const Key &key)
{
resultDialog = new NewOpenPGPCertificateResultDialog{result, key};
resultDialog->setAttribute(Qt::WA_DeleteOnClose);
applyWindowID(resultDialog);
connect(resultDialog, &NewOpenPGPCertificateResultDialog::retry, q, [this]() {
QMetaObject::invokeMethod(
q,
[this]() {
getCertificateDetails();
},
Qt::QueuedConnection);
});
connect(resultDialog, &QDialog::accepted, q, [this]() {
finished();
});
connect(resultDialog, &QDialog::rejected, q, [this]() {
finished();
});
resultDialog->show();
}
void NewOpenPGPCertificateCommand::Private::showErrorDialog(const KeyGenerationResult &result)
{
QString text;
if (result.error() || !result.fingerprint()) {
text = xi18n(
"<para>The creation of a new OpenPGP certificate failed.</para>"
"<para>Error: <message>%1</message></para>",
QString::fromLocal8Bit(result.error().asString()));
} else {
// no error and we have a fingerprint, but there was no corresponding key in the key ring
text = xi18n(
"<para>A new OpenPGP certificate was created successfully, but it has not been found in the key ring.</para>"
"<para>Fingerprint of the new certificate:<nl/>%1</para>",
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(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->resultDialog) {
d->resultDialog->close();
}
if (d->job) {
d->job->slotCancel();
}
}
#undef d
#undef q
#include "moc_newopenpgpcertificatecommand.cpp"
/* -*- mode: c++; c-basic-offset:4 -*-
commands/newopenpgpcertificatecommand.h
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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "command.h"
namespace Kleo
{
class NewOpenPGPCertificateCommand : public Command
{
Q_OBJECT
public:
NewOpenPGPCertificateCommand(QAbstractItemView *view, KeyListController *parent);
~NewOpenPGPCertificateCommand() override;
private:
void doStart() override;
void doCancel() override;
private:
class Private;
inline Private *d_func();
inline const Private *d_func() const;
};
}
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/newopenpgpcertificatedetailsdialog.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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "newopenpgpcertificatedetailsdialog.h"
#include "nameandemailwidget.h"
#include "newcertificatewizard/advancedsettingsdialog_p.h"
#include "newcertificatewizard/keyalgo_p.h"
#include "utils/keyparameters.h"
#include "utils/keyusage.h"
#include "utils/scrollarea.h"
#include <settings.h>
#include <Libkleo/Compat>
#include <KConfigGroup>
#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 "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::NewCertificateUi;
class NewOpenPGPCertificateDetailsDialog::Private
{
friend class ::Kleo::NewOpenPGPCertificateDetailsDialog;
NewOpenPGPCertificateDetailsDialog *const q;
struct UI {
QLabel *infoLabel;
ScrollArea *scrollArea;
NameAndEmailWidget *nameAndEmail;
QCheckBox *withPassCheckBox;
QPushButton *advancedButton;
QDialogButtonBox *buttonBox;
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 ScrollArea{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};
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);
{
auto layout = new QHBoxLayout;
advancedButton = new QPushButton{i18n("Advanced Settings..."), dialog};
advancedButton->setAutoDefault(false);
layout->addStretch(1);
layout->addWidget(advancedButton);
scrollAreaLayout->addLayout(layout);
}
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(NewOpenPGPCertificateDetailsDialog *qq)
: q{qq}
, ui{qq}
, advancedSettingsDlg{new AdvancedSettingsDialog{qq}}
, technicalParameters{KeyParameters::OpenPGP}
{
q->setWindowTitle(i18nc("title:window", "Create OpenPGP Certificate"));
const KConfigGroup config{KSharedConfig::openConfig(), "CertificateCreationWizard"};
const auto attrOrder = config.readEntry("OpenPGPAttributeOrder", QStringList{});
const auto nameIsRequired = attrOrder.contains(QLatin1String{"NAME!"}, Qt::CaseInsensitive);
const auto emailIsRequired = attrOrder.contains(QLatin1String{"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->setNamePattern(config.readEntry("NAME_regex"));
ui.nameAndEmail->setEmailIsRequired(emailIsRequired);
ui.nameAndEmail->setEmailPattern(config.readEntry("EMAIL_regex"));
Settings settings;
ui.advancedButton->setVisible(!settings.hideAdvanced());
const auto conf = QGpgME::cryptoConfig();
const auto entry = getCryptoConfigEntry(conf, "gpg-agent", "enforce-passphrase-constraints");
if (entry && entry->boolValue()) {
qCDebug(KLEOPATRA_LOG) << "Disabling passphrase check box because of agent config.";
ui.withPassCheckBox->setEnabled(false);
ui.withPassCheckBox->setChecked(true);
} else {
ui.withPassCheckBox->setChecked(config.readEntry("WithPassphrase", false));
ui.withPassCheckBox->setEnabled(!config.isEntryImmutable("WithPassphrase"));
}
advancedSettingsDlg->setProtocol(GpgME::OpenPGP);
updateTechnicalParameters(); // set key parameters to default values for OpenPGP
connect(advancedSettingsDlg, &QDialog::accepted, q, [this]() {
updateTechnicalParameters();
});
connect(ui.advancedButton, &QPushButton::clicked, q, [this]() {
advancedSettingsDlg->open();
});
connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() {
checkAccept();
});
connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
}
private:
KeyUsage keyUsage() const
{
KeyUsage usage;
if (advancedSettingsDlg->signingAllowed()) {
usage.setCanSign(true);
}
if (advancedSettingsDlg->encryptionAllowed() //
&& !is_ecdh(advancedSettingsDlg->subkeyType()) && !is_dsa(advancedSettingsDlg->keyType()) && !is_rsa(advancedSettingsDlg->subkeyType())) {
usage.setCanEncrypt(true);
}
if (advancedSettingsDlg->authenticationAllowed()) {
usage.setCanAuthenticate(true);
}
if (advancedSettingsDlg->certificationAllowed()) {
usage.setCanCertify(true);
}
return usage;
}
KeyUsage subkeyUsage() const
{
KeyUsage usage;
if (advancedSettingsDlg->encryptionAllowed()
&& (is_dsa(advancedSettingsDlg->keyType()) || is_rsa(advancedSettingsDlg->subkeyType()) || is_ecdh(advancedSettingsDlg->subkeyType()))) {
Q_ASSERT(advancedSettingsDlg->subkeyType());
usage.setCanEncrypt(true);
}
return usage;
}
void updateTechnicalParameters()
{
technicalParameters = KeyParameters{KeyParameters::OpenPGP};
const auto keyType = advancedSettingsDlg->keyType();