Commit 05bf1086 authored by Daniel Vrátil's avatar Daniel Vrátil 🤖
Browse files

Generate PGP keys in background

Since key generation can take a very long time we now start the
key generation in background and do all the other tasks in the
meantime. Even when everything finishes and the user closes the
window, the application will remain running in the background until
the key is generated.

We handle all different states that the application can be in when
the generation finishes and we set assign the newly generated key
to the identity using a method appropriate to the current state of
the application.
parent e2cedebc
......@@ -69,6 +69,8 @@ find_package(KF5DocTools ${KF5_VERSION} REQUIRED)
find_package(KF5TextEditor ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5Codecs ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5Crash ${KF5_VERSION} REQUIRED)
find_package(KF5Notifications ${KF5_VERSION} CONFIG REQUIRED)
# Find KdepimLibs Package
find_package(KF5PimTextEdit ${KPIMTEXTEDIT_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5Akonadi ${AKONADI_VERSION} CONFIG REQUIRED)
......
......@@ -54,6 +54,7 @@ set(accountwizard_libs
KF5::AkonadiWidgets
KF5::Crash
KF5::Libkleo
KF5::Notifications
)
if ( NOT ACCOUNTWIZARD_NO_GHNS )
......
......@@ -19,6 +19,8 @@
#include "cryptopage.h"
#include "dialog.h"
#include "identity.h"
#include "accountwizard_debug.h"
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/Job>
......@@ -33,64 +35,131 @@
#include <gpgme++/importresult.h>
#include <KMessageBox>
#include <KNotifications/KNotification>
#include <KNewPasswordDialog>
#include <KIdentityManagement/IdentityManager>
#include <KIdentityManagement/Identity>
#include <QFileDialog>
#include <QEventLoopLocker>
#include <QPointer>
#include <QTimer>
class DeleteLaterRAII
{
public:
DeleteLaterRAII(QObject *obj) : mObj(obj)
{}
~DeleteLaterRAII()
{
if (mObj) mObj->deleteLater();
}
private:
Q_DISABLE_COPY(DeleteLaterRAII)
QPointer<QObject> mObj;
};
class KeyGenerationJob : public Kleo::Job
class KeyGenerationJob : public QObject
{
Q_OBJECT
public:
KeyGenerationJob(const QString &name, const QString &email, Kleo::KeySelectionCombo *parent)
: Kleo::Job(parent)
, mName(name)
, mEmail(email)
, mJob(Q_NULLPTR)
KeyGenerationJob(SetupManager *setupManager, const QString &passphrase)
: mSetupManager(setupManager)
, mEmail(setupManager->email())
{
qCDebug(ACCOUNTWIZARD_LOG) << "Starting key generation";
auto job = new Kleo::DefaultKeyGenerationJob(this);
job->setPassphrase(passphrase);
connect(job, &Kleo::DefaultKeyGenerationJob::result,
this, [this](const GpgME::KeyGenerationResult &result) {
QTimer::singleShot(60000, this, [this, result]() {
keyGenerated(result);
});
});
job->start(setupManager->name(), setupManager->email());
}
~KeyGenerationJob()
{
}
void slotCancel() Q_DECL_OVERRIDE {
if (mJob)
{
mJob->slotCancel();
private Q_SLOTS:
void keyGenerated(const GpgME::KeyGenerationResult &result)
{
DeleteLaterRAII deleter(this);
if (result.error()) {
qCWarning(ACCOUNTWIZARD_LOG) << "Key generation finished with error:" << result.error().asString();
KNotification::event(KNotification::Error,
i18n("Account Wizard"),
i18n("Error while generating new key pair for your account %1: %2",
mEmail, QString::fromUtf8(result.error().asString())),
QStringLiteral("akonadi"));
return;
}
}
void start()
{
auto job = new Kleo::DefaultKeyGenerationJob(this);
connect(job, &Kleo::DefaultKeyGenerationJob::result,
this, &KeyGenerationJob::keyGenerated);
job->start(mEmail, mName);
mJob = job;
qCDebug(ACCOUNTWIZARD_LOG) << "Finished generating key" << result.fingerprint();
// SetupManager no longer exists -> all is set up, so we need to modify
// the Identity ourselves
if (!mSetupManager) {
qDebug(ACCOUNTWIZARD_LOG) << "SetupManager no longer exists!";
updateIdentity(mEmail, result.fingerprint());
return;
}
// SetupManager still lives, see if Identity has already been set up.
// If not then pass it the new key and let it to set up everything
const auto objectsToSetup = mSetupManager->objectsToSetup();
for (auto object : objectsToSetup) {
if (Identity *identity = qobject_cast<Identity*>(object)) {
qCDebug(ACCOUNTWIZARD_LOG) << "Identity not set up yet, less work for us";
identity->setKey(GpgME::OpenPGP, result.fingerprint());
return;
}
}
// SetupManager still lives, but Identity either was not even created yet
// or has already been set up
const auto setupObjects = mSetupManager->setupObjects();
for (auto object : setupObjects) {
if (qobject_cast<Identity*>(object)) {
qCDebug(ACCOUNTWIZARD_LOG) << "Identity already set up, will modify existing Identity";
updateIdentity(mEmail, result.fingerprint());
return;
}
}
// SetupManager still lives, but Identity is not pending nor finished,
// which means it was not even prepared yet in SetupManager.
qCDebug(ACCOUNTWIZARD_LOG) << "Identity not ready yet, passing the key to SetupManager";
mSetupManager->setKey(GpgME::OpenPGP, result.fingerprint());
}
void keyGenerated(const GpgME::KeyGenerationResult &result)
void updateIdentity(const QString &email, const QByteArray &fingerprint)
{
mJob = Q_NULLPTR;
if (result.error()) {
KMessageBox::error(qobject_cast<QWidget *>(parent()),
i18n("Error while generating new key pair: %1", QString::fromUtf8(result.error().asString())),
i18n("Key Generation Error"));
Q_EMIT done();
return;
auto manager = KIdentityManagement::IdentityManager::self();
for (auto it = manager->modifyBegin(), end = manager->modifyEnd(); it != end; ++it) {
if (it->primaryEmailAddress() == email) {
qCDebug(ACCOUNTWIZARD_LOG) << "Found matching identity for" << email <<":" << it->uoid();
it->setPGPSigningKey(fingerprint);
it->setPGPEncryptionKey(fingerprint);
manager->commit();
return;
}
}
auto combo = qobject_cast<Kleo::KeySelectionCombo *>(parent());
combo->setDefaultKey(QLatin1String(result.fingerprint()));
connect(combo, &Kleo::KeySelectionCombo::keyListingFinished,
this, &KeyGenerationJob::done);
combo->refreshKeys();
qCWarning(ACCOUNTWIZARD_LOG) << "What? Could not find a matching identity for" << email << "!";
}
private:
QString mName;
// This job may outlive the application, make sure QApplication won't
// quit before we are done
QEventLoopLocker mLocker;
QPointer<SetupManager> mSetupManager;
QString mEmail;
Kleo::Job *mJob;
};
class KeyImportJob : public Kleo::Job
......@@ -176,7 +245,7 @@ public:
auto combo = qobject_cast<Kleo::KeySelectionCombo *>(parent());
combo->setDefaultKey(QLatin1String(result.import(0).fingerprint()));
connect(combo, &Kleo::KeySelectionCombo::keyListingFinished,
this, &KeyGenerationJob::done);
this, &KeyImportJob::done);
combo->refreshKeys();
}
......@@ -220,7 +289,8 @@ void CryptoPage::enterPageNext()
void CryptoPage::leavePageNext()
{
mSetupManager->setKey(ui.keyCombo->currentKey());
const auto key = ui.keyCombo->currentKey();
mSetupManager->setKey(key.protocol(), key.primaryFingerprint());
}
void CryptoPage::customItemSelected(const QVariant &data)
......@@ -240,28 +310,14 @@ void CryptoPage::customItemSelected(const QVariant &data)
}
}
namespace
{
template<typename T, typename ... Args>
void runJob(Kleo::KeySelectionCombo *combo, const QString &title, const Args &... args)
{
auto job = new T(args ..., combo);
new Kleo::ProgressDialog(job, title, combo->parentWidget());
combo->setEnabled(false);
QObject::connect(job, &KeyGenerationJob::done,
combo, [combo]() {
combo->setEnabled(true);
});
job->start();
}
}
void CryptoPage::generateKeyPair()
{
runJob<KeyGenerationJob>(ui.keyCombo, i18n("Generating new key pair..."),
mSetupManager->name(), mSetupManager->email());
QScopedPointer<KNewPasswordDialog> dlg(new KNewPasswordDialog);
dlg->setAllowEmptyPasswords(true);
if (dlg->exec()) {
new KeyGenerationJob(mSetupManager, dlg->password());
nextPage();
}
}
void CryptoPage::importKey()
......@@ -276,7 +332,14 @@ void CryptoPage::importKey()
return;
}
runJob<KeyImportJob>(ui.keyCombo, i18n("Importing key..."), file);
auto job = new KeyImportJob(file, ui.keyCombo);
new Kleo::ProgressDialog(job, i18n("Importing key..."), ui.keyCombo->parentWidget());
ui.keyCombo->setEnabled(false);
QObject::connect(job, &KeyImportJob::done,
ui.keyCombo, [this]() {
ui.keyCombo->setEnabled(true);
});
job->start();
}
#include "cryptopage.moc"
......@@ -44,6 +44,7 @@
#include <KHelpMenu>
#include <gpgme++/engineinfo.h>
#include <gpgme++/error.h>
Dialog::Dialog(QWidget *parent)
: KAssistantDialog(parent)
......
......@@ -20,8 +20,6 @@
#include "identity.h"
#include "transport.h"
#include <gpgme++/key.h>
#include <kidentitymanagement/identitymanager.h>
#include <kidentitymanagement/identity.h>
......@@ -155,18 +153,18 @@ void Identity::setPgpAutoSign(bool autosign)
m_identity->setPgpAutoSign(autosign);
}
void Identity::setKey(const GpgME::Key &key)
void Identity::setKey(GpgME::Protocol protocol, const QByteArray &fingerprint)
{
if (key.isNull()) {
if (fingerprint.isEmpty()) {
m_identity->setPGPEncryptionKey(QByteArray());
m_identity->setPGPSigningKey(QByteArray());
m_identity->setSMIMEEncryptionKey(QByteArray());
m_identity->setSMIMESigningKey(QByteArray());
} else if (key.protocol() == GpgME::OpenPGP) {
m_identity->setPGPSigningKey(key.primaryFingerprint());
m_identity->setPGPEncryptionKey(key.primaryFingerprint());
} else if (key.protocol() == GpgME::CMS) {
m_identity->setSMIMESigningKey(key.primaryFingerprint());
m_identity->setSMIMEEncryptionKey(key.primaryFingerprint());
} else if (protocol == GpgME::OpenPGP) {
m_identity->setPGPSigningKey(fingerprint);
m_identity->setPGPEncryptionKey(fingerprint);
} else if (protocol == GpgME::CMS) {
m_identity->setSMIMESigningKey(fingerprint);
m_identity->setSMIMEEncryptionKey(fingerprint);
}
}
......@@ -22,12 +22,9 @@
#include "setupobject.h"
class Transport;
#include <gpgme++/global.h>
namespace GpgME
{
class Key;
}
class Transport;
namespace KIdentityManagement
{
......@@ -56,7 +53,7 @@ public Q_SLOTS:
Q_SCRIPTABLE void setXFace(const QString &xface);
Q_SCRIPTABLE void setPgpAutoSign(bool autosign);
Q_SCRIPTABLE void setPgpAutoEncrypt(bool autoencrypt);
Q_SCRIPTABLE void setKey(const GpgME::Key &key);
Q_SCRIPTABLE void setKey(GpgME::Protocol protocol, const QByteArray &fingerprint);
protected:
QString identityName() const;
......
......@@ -40,6 +40,7 @@ SetupManager::SetupManager(QWidget *parent) :
m_currentSetupObject(Q_NULLPTR),
m_page(Q_NULLPTR),
m_wallet(Q_NULLPTR),
m_keyProtocol(GpgME::UnknownProtocol),
m_personalDataAvailable(false),
m_rollbackRequested(false),
m_pgpAutoSign(false),
......@@ -87,10 +88,20 @@ QObject *SetupManager::createIdentity()
identity->setRealName(m_name);
identity->setPgpAutoSign(m_pgpAutoSign);
identity->setPgpAutoEncrypt(m_pgpAutoEncrypt);
identity->setKey(m_key);
identity->setKey(m_keyProtocol, m_keyFingerprint);
return connectObject(identity);
}
QList<SetupObject *> SetupManager::objectsToSetup() const
{
return m_objectToSetup;
}
QList<SetupObject *> SetupManager::setupObjects() const
{
return m_setupObjects;
}
static bool dependencyCompare(SetupObject *left, SetupObject *right)
{
if (!left->dependsOn() && right->dependsOn()) {
......@@ -235,9 +246,10 @@ void SetupManager::setPgpAutoSign(bool autosign)
m_pgpAutoSign = autosign;
}
void SetupManager::setKey(const GpgME::Key &key)
void SetupManager::setKey(GpgME::Protocol protocol, const QByteArray &fingerprint)
{
m_key = key;
m_keyProtocol = protocol;
m_keyFingerprint = fingerprint;
}
void SetupManager::openWallet()
......
......@@ -22,7 +22,7 @@
#include <QObject>
#include <gpgme++/key.h>
#include <gpgme++/global.h>
namespace KWallet
{
......@@ -46,7 +46,10 @@ public:
void setPersonalDataAvailable(bool available);
void setPgpAutoSign(bool autosign);
void setPgpAutoEncrypt(bool autoencrypt);
void setKey(const GpgME::Key &key);
void setKey(GpgME::Protocol protocol, const QByteArray &fingerprint);
QList<SetupObject *> objectsToSetup() const;
QList<SetupObject *> setupObjects() const;
public Q_SLOTS:
Q_SCRIPTABLE bool personalDataAvailable();
......@@ -81,12 +84,13 @@ private Q_SLOTS:
private:
QString m_name, m_email, m_password;
GpgME::Key m_key;
QByteArray m_keyFingerprint;
QList<SetupObject *> m_objectToSetup;
QList<SetupObject *> m_setupObjects;
SetupObject *m_currentSetupObject;
SetupPage *m_page;
KWallet::Wallet *m_wallet;
GpgME::Protocol m_keyProtocol;
bool m_personalDataAvailable;
bool m_rollbackRequested;
bool m_pgpAutoSign;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment