File indexing completed on 2025-02-02 05:08:36
0001 /* 0002 SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "cryptopage.h" 0008 #include "accountwizard_debug.h" 0009 #include "dialog.h" 0010 #include "identity.h" 0011 #include "transport.h" 0012 0013 #include <Libkleo/Classify> 0014 #include <Libkleo/DefaultKeyFilter> 0015 #include <Libkleo/DefaultKeyGenerationJob> 0016 #include <Libkleo/ProgressDialog> 0017 #include <QGpgME/ImportJob> 0018 #include <QGpgME/Job> 0019 #include <QGpgME/KeyListJob> 0020 #include <QGpgME/Protocol> 0021 #include <QGpgME/WKSPublishJob> 0022 0023 #include <gpgme++/context.h> 0024 #include <gpgme++/importresult.h> 0025 #include <gpgme++/keygenerationresult.h> 0026 #include <gpgme++/keylistresult.h> 0027 0028 #include <KMessageBox> 0029 #include <KNewPasswordDialog> 0030 #include <KNotifications/KNotification> 0031 0032 #include <KIdentityManagementCore/Identity> 0033 #include <KIdentityManagementCore/IdentityManager> 0034 0035 #include <QEventLoopLocker> 0036 #include <QFileDialog> 0037 0038 class KeyGenerationJob : public QObject 0039 { 0040 Q_OBJECT 0041 0042 public: 0043 KeyGenerationJob(SetupManager *setupManager, const QString &passphrase, Key::PublishingMethod publishingMethod) 0044 : mSetupManager(setupManager) 0045 , mName(setupManager->name()) 0046 , mEmail(setupManager->email()) 0047 , mPassphrase(passphrase) 0048 , mTransportId(0) 0049 , mPublishingMethod(publishingMethod) 0050 { 0051 // Listen for when setup of Transport is finished so we can snatch 0052 // the transport ID 0053 connect(mSetupManager.data(), &SetupManager::setupFinished, this, &KeyGenerationJob::onObjectSetupFinished); 0054 0055 qCDebug(ACCOUNTWIZARD_LOG) << "Starting key generation"; 0056 auto job = new Kleo::DefaultKeyGenerationJob(this); 0057 job->setPassphrase(mPassphrase); 0058 connect(job, &Kleo::DefaultKeyGenerationJob::result, this, &KeyGenerationJob::keyGenerated); 0059 job->start(mEmail, mName); 0060 } 0061 0062 ~KeyGenerationJob() override = default; 0063 0064 Q_SIGNALS: 0065 void result(const QString &fingerprint); 0066 0067 private Q_SLOTS: 0068 void onObjectSetupFinished(SetupObject *obj) 0069 { 0070 if (auto *transport = qobject_cast<Transport *>(obj)) { 0071 mTransportId = transport->transportId(); 0072 } 0073 } 0074 0075 void keyGenerated(const GpgME::KeyGenerationResult &result) 0076 { 0077 if (result.error()) { 0078 qCWarning(ACCOUNTWIZARD_LOG) << "Key generation finished with error:" << result.error().asString(); 0079 KNotification::event(KNotification::Error, 0080 i18n("Account Wizard"), 0081 i18n("Error while generating new key pair for your account %1: %2", mEmail, QString::fromUtf8(result.error().asString())), 0082 QStringLiteral("akonadi")); 0083 deleteLater(); 0084 return; 0085 } 0086 0087 const QString fpr = QString::fromLatin1(result.fingerprint()); 0088 qCDebug(ACCOUNTWIZARD_LOG) << "Finished generating key" << fpr; 0089 Q_EMIT this->result(fpr); 0090 0091 auto listJob = QGpgME::openpgp()->keyListJob(false, true, true); 0092 connect(listJob, &QGpgME::KeyListJob::result, this, &KeyGenerationJob::keyRetrieved); 0093 listJob->start({fpr}, true); 0094 } 0095 0096 void keyRetrieved(const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &keys) 0097 { 0098 if (result.error() || keys.size() != 1) { 0099 qCWarning(ACCOUNTWIZARD_LOG) << "Key listing finished with error;" << result.error().asString(); 0100 KNotification::event(KNotification::Error, 0101 i18n("Account Wizard"), 0102 i18n("Error while generating new key pair for your account %1: %2", mEmail, QString::fromUtf8(result.error().asString())), 0103 QStringLiteral("akonadi")); 0104 deleteLater(); 0105 return; 0106 } 0107 0108 qCDebug(ACCOUNTWIZARD_LOG) << "Post-generation key listing finished"; 0109 0110 const auto key = keys[0]; 0111 0112 // SetupManager no longer exists -> all is set up, so we need to modify 0113 // the Identity ourselves 0114 if (!mSetupManager) { 0115 qDebug(ACCOUNTWIZARD_LOG) << "SetupManager no longer exists!"; 0116 updateIdentity(mEmail, key.primaryFingerprint()); 0117 publishKeyIfNeeded(key); 0118 return; 0119 } 0120 0121 { 0122 // SetupManager still lives, see if Identity has already been set up. 0123 // If not then pass it the new key and let it set up everything 0124 const auto objectsToSetup = mSetupManager->objectsToSetup(); 0125 const auto identIt = std::find_if(objectsToSetup.cbegin(), objectsToSetup.cend(), [](SetupObject *obj) -> bool { 0126 return qobject_cast<Identity *>(obj); 0127 }); 0128 const auto keyIt = std::find_if(objectsToSetup.cbegin(), objectsToSetup.cend(), [](SetupObject *obj) -> bool { 0129 return qobject_cast<Key *>(obj); 0130 }); 0131 if (identIt != objectsToSetup.cend()) { 0132 qCDebug(ACCOUNTWIZARD_LOG) << "Identity not set up yet, less work for us"; 0133 qobject_cast<Identity *>(*identIt)->setKey(GpgME::OpenPGP, key.primaryFingerprint()); 0134 // The Key depends on Identity, so if Identity wasn't set up 0135 // yet, neither was the Key. 0136 if (mPublishingMethod != Key::NoPublishing && keyIt != objectsToSetup.cend()) { 0137 auto keyObj = qobject_cast<Key *>(*keyIt); 0138 keyObj->setKey(key); 0139 keyObj->setPublishingMethod(mPublishingMethod); 0140 keyObj->setMailBox(mEmail); 0141 } 0142 return; 0143 } 0144 } 0145 0146 { 0147 // SetupManager still lives, but Identity has already been set up, 0148 // so update it. 0149 const auto setupObjects = mSetupManager->setupObjects(); 0150 const auto it = std::find_if(setupObjects.cbegin(), setupObjects.cend(), [](SetupObject *obj) -> bool { 0151 return qobject_cast<Identity *>(obj); 0152 }); 0153 if (it != setupObjects.cend()) { 0154 qCDebug(ACCOUNTWIZARD_LOG) << "Identity already set up, will modify existing Identity"; 0155 updateIdentity(mEmail, key.primaryFingerprint()); 0156 // Too late, we must publish the key ourselves now. 0157 publishKeyIfNeeded(key); 0158 return; 0159 } 0160 } 0161 0162 // SetupManager still lives, but Identity is not pending nor finished, 0163 // which means it was not even prepared yet in SetupManager. This will 0164 // also handle publishing for us if needed. 0165 qCDebug(ACCOUNTWIZARD_LOG) << "Identity not ready yet, passing the key to SetupManager"; 0166 mSetupManager->setKey(key); 0167 mSetupManager->setKeyPublishingMethod(mPublishingMethod); 0168 deleteLater(); 0169 } 0170 0171 void updateIdentity(const QString &email, const QByteArray &fingerprint) 0172 { 0173 auto manager = KIdentityManagementCore::IdentityManager::self(); 0174 for (auto it = manager->modifyBegin(), end = manager->modifyEnd(); it != end; ++it) { 0175 if (it->primaryEmailAddress() == email) { 0176 qCDebug(ACCOUNTWIZARD_LOG) << "Found matching identity for" << email << ":" << it->uoid(); 0177 it->setPGPSigningKey(fingerprint); 0178 it->setPGPEncryptionKey(fingerprint); 0179 manager->commit(); 0180 return; 0181 } 0182 } 0183 manager->rollback(); 0184 0185 qCWarning(ACCOUNTWIZARD_LOG) << "What? Could not find a matching identity for" << email << "!"; 0186 } 0187 0188 void publishKeyIfNeeded(const GpgME::Key &key) 0189 { 0190 if (mPublishingMethod == Key::NoPublishing) { 0191 qCDebug(ACCOUNTWIZARD_LOG) << "Key publishing not requested, we are done"; 0192 deleteLater(); 0193 return; 0194 } 0195 0196 auto keyObj = new Key(mSetupManager); // mSetupManager can be null, but that's OK 0197 keyObj->setKey(key); 0198 keyObj->setPublishingMethod(mPublishingMethod); 0199 keyObj->setMailBox(mEmail); 0200 keyObj->setTransportId(mTransportId); 0201 connect(keyObj, &Key::error, this, [this](const QString &msg) { 0202 KNotification::event(KNotification::Error, i18n("Account Wizard"), msg, QStringLiteral("akonadi")); 0203 deleteLater(); 0204 }); 0205 connect(keyObj, &Key::finished, this, &KeyGenerationJob::deleteLater); 0206 keyObj->create(); 0207 } 0208 0209 private: 0210 // This job may outlive the application, make sure QApplication won't 0211 // quit before we are done 0212 QEventLoopLocker mLocker; 0213 QPointer<SetupManager> mSetupManager; 0214 QString mName; 0215 QString mEmail; 0216 QString mPassphrase; 0217 int mTransportId; 0218 Key::PublishingMethod mPublishingMethod; 0219 }; 0220 0221 class KeyImportJob : public QGpgME::Job 0222 { 0223 Q_OBJECT 0224 public: 0225 KeyImportJob(const QString &file, Kleo::KeySelectionCombo *parent) 0226 : QGpgME::Job(parent) 0227 , mFile(file) 0228 , mJob(nullptr) 0229 { 0230 } 0231 0232 ~KeyImportJob() override = default; 0233 0234 void slotCancel() override 0235 { 0236 if (mJob) { 0237 mJob->slotCancel(); 0238 } 0239 } 0240 0241 void start() 0242 { 0243 QGpgME::ImportJob *job = nullptr; 0244 switch (Kleo::findProtocol(mFile)) { 0245 case GpgME::OpenPGP: 0246 job = QGpgME::openpgp()->importJob(); 0247 break; 0248 case GpgME::CMS: 0249 job = QGpgME::smime()->importJob(); 0250 break; 0251 default: 0252 job = nullptr; 0253 break; 0254 } 0255 0256 if (!job) { 0257 KMessageBox::error(qobject_cast<QWidget *>(parent()), i18n("Could not detect valid key type"), i18n("Import error")); 0258 Q_EMIT done(); 0259 return; 0260 } 0261 0262 QFile keyFile(mFile); 0263 if (!keyFile.open(QIODevice::ReadOnly)) { 0264 KMessageBox::error(qobject_cast<QWidget *>(parent()), 0265 i18n("Cannot read data from the certificate file: %1", keyFile.errorString()), 0266 i18n("Import error")); 0267 Q_EMIT done(); 0268 return; 0269 } 0270 0271 connect(job, &QGpgME::ImportJob::result, this, &KeyImportJob::keyImported); 0272 job->start(keyFile.readAll()); 0273 mJob = job; 0274 } 0275 0276 void keyImported(const GpgME::ImportResult &result) 0277 { 0278 mJob = nullptr; 0279 if (result.error()) { 0280 KMessageBox::error(qobject_cast<QWidget *>(parent()), 0281 i18n("Failed to import key: %1", QString::fromUtf8(result.error().asString())), 0282 i18n("Import error")); 0283 Q_EMIT done(); 0284 return; 0285 } 0286 0287 const auto imports = result.imports(); 0288 if (imports.size() == 0) { 0289 KMessageBox::error(qobject_cast<QWidget *>(parent()), i18n("Failed to import key."), i18n("Import error")); 0290 Q_EMIT done(); 0291 return; 0292 } 0293 0294 auto combo = qobject_cast<Kleo::KeySelectionCombo *>(parent()); 0295 combo->setDefaultKey(QLatin1StringView(result.import(0).fingerprint())); 0296 connect(combo, &Kleo::KeySelectionCombo::keyListingFinished, this, &KeyImportJob::done); 0297 combo->refreshKeys(); 0298 } 0299 0300 private: 0301 QString mFile; 0302 QGpgME::Job *mJob = nullptr; 0303 }; 0304 0305 CryptoPage::CryptoPage(Dialog *parent) 0306 : Page(parent) 0307 , mSetupManager(parent->setupManager()) 0308 { 0309 ui.setupUi(this); 0310 0311 std::shared_ptr<Kleo::DefaultKeyFilter> filter(new Kleo::DefaultKeyFilter); 0312 filter->setCanSign(Kleo::DefaultKeyFilter::Set); 0313 filter->setCanEncrypt(Kleo::DefaultKeyFilter::Set); 0314 filter->setHasSecret(Kleo::DefaultKeyFilter::Set); 0315 ui.keyCombo->setKeyFilter(filter); 0316 ui.keyCombo->prependCustomItem(QIcon(), i18n("No key"), NoKey); 0317 ui.keyCombo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey); 0318 ui.keyCombo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-import")), i18n("Import a key"), ImportKey); 0319 0320 connect(ui.keyCombo, &Kleo::KeySelectionCombo::customItemSelected, this, &CryptoPage::customItemSelected); 0321 connect(ui.keyCombo, &Kleo::KeySelectionCombo::currentKeyChanged, this, &CryptoPage::keySelected); 0322 } 0323 0324 void CryptoPage::enterPageNext() 0325 { 0326 ui.keyCombo->setIdFilter(mSetupManager->email()); 0327 if (ui.keyCombo->count() == 3) { 0328 // No key + Generate key + Import key options, no actual keys, so 0329 // pre-select the "Generate key" option 0330 const int idx = ui.keyCombo->findData(GenerateKey, Qt::UserRole); 0331 ui.keyCombo->setCurrentIndex(idx); 0332 } else { 0333 // We have at least one key, pre-select it 0334 // Index 0 is "No key" option, so index 1 will be a key 0335 ui.keyCombo->setCurrentIndex(1); 0336 } 0337 0338 ui.stackedWidget->setCurrentIndex(CheckingkWKSPage); 0339 auto job = QGpgME::openpgp()->wksPublishJob(); 0340 connect(job, &QGpgME::WKSPublishJob::result, this, [this](const GpgME::Error &error) { 0341 if (error) { 0342 ui.stackedWidget->setCurrentIndex(PKSPage); 0343 } else { 0344 ui.stackedWidget->setCurrentIndex(WKSPage); 0345 } 0346 }); 0347 job->startCheck(mSetupManager->email()); 0348 } 0349 0350 void CryptoPage::leavePageNext() 0351 { 0352 const auto key = ui.keyCombo->currentKey(); 0353 if (!key.isNull()) { 0354 mSetupManager->setKey(key); 0355 mSetupManager->setKeyPublishingMethod(currentPublishingMethod()); 0356 } else if (ui.keyCombo->currentData(Qt::UserRole).toInt() == GenerateKey) { 0357 if (!mKeyGenerationJob) { 0358 mKeyGenerationJob = new KeyGenerationJob(mSetupManager, ui.passwordWidget->password(), currentPublishingMethod()); 0359 ui.keyCombo->setEnabled(false); // disable until key is generated 0360 ui.passwordWidget->setEnabled(false); 0361 connect(mKeyGenerationJob.data(), &KeyGenerationJob::result, this, [this](const QString &fpr) { 0362 ui.keyCombo->setEnabled(true); 0363 ui.passwordWidget->setEnabled(true); 0364 ui.keyCombo->setDefaultKey(fpr); 0365 ui.keyCombo->refreshKeys(); 0366 }); 0367 } 0368 } 0369 mSetupManager->setPgpAutoEncrypt(ui.enableCryptoCheckBox->isChecked()); 0370 mSetupManager->setPgpAutoSign(ui.enableCryptoCheckBox->isChecked()); 0371 } 0372 0373 Key::PublishingMethod CryptoPage::currentPublishingMethod() const 0374 { 0375 if (ui.stackedWidget->currentIndex() == PKSPage && ui.pksCheckBox->isChecked()) { 0376 return Key::PKS; 0377 } else if (ui.stackedWidget->currentIndex() == WKSPage && ui.wksCheckBox->isChecked()) { 0378 return Key::WKS; 0379 } else { 0380 return Key::NoPublishing; 0381 } 0382 } 0383 0384 void CryptoPage::customItemSelected(const QVariant &data) 0385 { 0386 switch (data.toInt()) { 0387 case NoKey: 0388 setValid(true); 0389 setPublishingEnabled(false); 0390 ui.passwordWidget->setVisible(false); 0391 return; 0392 case GenerateKey: 0393 setValid(true); 0394 setPublishingEnabled(true); 0395 ui.passwordWidget->setVisible(true); 0396 break; 0397 case ImportKey: 0398 setValid(false); 0399 setPublishingEnabled(true); 0400 ui.passwordWidget->setVisible(false); 0401 importKey(); 0402 break; 0403 } 0404 } 0405 0406 void CryptoPage::keySelected(const GpgME::Key &key) 0407 { 0408 ui.passwordWidget->setVisible(false); 0409 0410 // We don't support publishing for S/MIME 0411 setPublishingEnabled(key.protocol() == GpgME::OpenPGP); 0412 setValid(!key.isNull()); 0413 } 0414 0415 void CryptoPage::setPublishingEnabled(bool enabled) 0416 { 0417 ui.wksCheckBox->setEnabled(enabled); 0418 ui.wksCheckBox->setChecked(enabled); 0419 ui.pksCheckBox->setEnabled(enabled); 0420 ui.pksCheckBox->setChecked(enabled); 0421 } 0422 0423 void CryptoPage::importKey() 0424 { 0425 const QString certificateFilter = i18n("Certificates") + QLatin1StringView(" (*.asc *.cer *.cert *.crt *.der *.pem *.gpg *.p7c *.p12 *.pfx *.pgp)"); 0426 const QString anyFilesFilter = i18n("Any files") + QLatin1StringView(" (*)"); 0427 0428 const QString file = 0429 QFileDialog::getOpenFileName(this, i18n("Select Certificate File"), QString(), certificateFilter + QLatin1StringView(";;") + anyFilesFilter); 0430 if (file.isEmpty()) { 0431 return; 0432 } 0433 0434 auto job = new KeyImportJob(file, ui.keyCombo); 0435 new Kleo::ProgressDialog(job, i18n("Importing key..."), ui.keyCombo->parentWidget()); 0436 ui.keyCombo->setEnabled(false); 0437 QObject::connect(job, &KeyImportJob::done, ui.keyCombo, [this]() { 0438 ui.keyCombo->setEnabled(true); 0439 }); 0440 job->start(); 0441 } 0442 0443 #include "cryptopage.moc" 0444 0445 #include "moc_cryptopage.cpp"