File indexing completed on 2024-06-23 05:13:36

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     commands/certifycertificatecommand.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006     SPDX-FileCopyrightText: 2019 g10code GmbH
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include <config-kleopatra.h>
0012 
0013 #include "certifycertificatecommand.h"
0014 #include "newopenpgpcertificatecommand.h"
0015 
0016 #include "command_p.h"
0017 
0018 #include "dialogs/certifycertificatedialog.h"
0019 #include "exportopenpgpcertstoservercommand.h"
0020 #include "utils/tags.h"
0021 
0022 #include <Libkleo/Algorithm>
0023 #include <Libkleo/Compat>
0024 #include <Libkleo/Formatting>
0025 #include <Libkleo/KeyCache>
0026 #include <Libkleo/KeyHelpers>
0027 
0028 #include <QGpgME/Protocol>
0029 #include <QGpgME/SignKeyJob>
0030 
0031 #include <QDate>
0032 #include <QEventLoop>
0033 
0034 #include <gpgme++/key.h>
0035 
0036 #include "kleopatra_debug.h"
0037 #include <KLocalizedString>
0038 
0039 using namespace Kleo;
0040 using namespace Kleo::Commands;
0041 using namespace GpgME;
0042 using namespace QGpgME;
0043 
0044 class CertifyCertificateCommand::Private : public Command::Private
0045 {
0046     friend class ::Kleo::Commands::CertifyCertificateCommand;
0047     CertifyCertificateCommand *q_func() const
0048     {
0049         return static_cast<CertifyCertificateCommand *>(q);
0050     }
0051 
0052 public:
0053     explicit Private(CertifyCertificateCommand *qq, KeyListController *c);
0054     ~Private() override;
0055 
0056     void init();
0057 
0058 private:
0059     void slotDialogRejected();
0060     void slotResult(const Error &err);
0061     void slotCertificationPrepared();
0062 
0063 private:
0064     void ensureDialogCreated();
0065     void createJob();
0066 
0067 private:
0068     GpgME::Key target;
0069     std::vector<UserID> uids;
0070     QPointer<CertifyCertificateDialog> dialog;
0071     QPointer<QGpgME::SignKeyJob> job;
0072 };
0073 
0074 CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func()
0075 {
0076     return static_cast<Private *>(d.get());
0077 }
0078 const CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func() const
0079 {
0080     return static_cast<const Private *>(d.get());
0081 }
0082 
0083 #define d d_func()
0084 #define q q_func()
0085 
0086 CertifyCertificateCommand::Private::Private(CertifyCertificateCommand *qq, KeyListController *c)
0087     : Command::Private(qq, c)
0088     , uids()
0089     , dialog()
0090     , job()
0091 {
0092 }
0093 
0094 CertifyCertificateCommand::Private::~Private()
0095 {
0096     qCDebug(KLEOPATRA_LOG);
0097     if (dialog) {
0098         delete dialog;
0099         dialog = nullptr;
0100     }
0101 }
0102 
0103 CertifyCertificateCommand::CertifyCertificateCommand(KeyListController *c)
0104     : Command(new Private(this, c))
0105 {
0106     d->init();
0107 }
0108 
0109 CertifyCertificateCommand::CertifyCertificateCommand(QAbstractItemView *v, KeyListController *c)
0110     : Command(v, new Private(this, c))
0111 {
0112     d->init();
0113 }
0114 
0115 CertifyCertificateCommand::CertifyCertificateCommand(const GpgME::Key &key)
0116     : Command(key, new Private(this, nullptr))
0117 {
0118     d->init();
0119 }
0120 
0121 CertifyCertificateCommand::CertifyCertificateCommand(const GpgME::UserID &uid)
0122     : Command(uid.parent(), new Private(this, nullptr))
0123 {
0124     std::vector<UserID>(1, uid).swap(d->uids);
0125     d->init();
0126 }
0127 
0128 CertifyCertificateCommand::CertifyCertificateCommand(const std::vector<GpgME::UserID> &uids)
0129     : Command(uids.empty() ? Key() : uids.front().parent(), new Private(this, nullptr))
0130 {
0131     d->uids = uids;
0132     d->init();
0133 }
0134 
0135 void CertifyCertificateCommand::Private::init()
0136 {
0137 }
0138 
0139 CertifyCertificateCommand::~CertifyCertificateCommand()
0140 {
0141     qCDebug(KLEOPATRA_LOG);
0142 }
0143 
0144 void CertifyCertificateCommand::doStart()
0145 {
0146     const std::vector<Key> keys = d->keys();
0147     if (keys.size() != 1 || keys.front().protocol() != GpgME::OpenPGP) {
0148         d->finished();
0149         return;
0150     }
0151     // hold on to the key to certify to avoid invalidation during refreshes of the key cache
0152     d->target = keys.front();
0153 
0154     if (d->target.isExpired() || d->target.isRevoked()) {
0155         const auto title = d->target.isRevoked() ? i18nc("@title:window", "Key is Revoked") : i18nc("@title:window", "Key is Expired");
0156         const auto message = d->target.isRevoked() //
0157             ? i18nc("@info", "This key has been revoked. You cannot certify it.")
0158             : i18nc("@info", "This key has expired. You cannot certify it.");
0159         d->information(message, title);
0160         d->finished();
0161         return;
0162     }
0163 
0164     auto findAnyGoodKey = []() {
0165         const std::vector<Key> secKeys = KeyCache::instance()->secretKeys();
0166         return std::any_of(secKeys.cbegin(), secKeys.cend(), [](const Key &secKey) {
0167             return Kleo::keyHasCertify(secKey) && secKey.protocol() == OpenPGP && !secKey.isRevoked() && !secKey.isExpired() && !secKey.isInvalid();
0168         });
0169     };
0170 
0171     if (!findAnyGoodKey()) {
0172         auto sel =
0173             KMessageBox::questionTwoActions(d->parentWidgetOrView(),
0174                                             xi18nc("@info", "To certify other certificates, you first need to create an OpenPGP certificate for yourself.")
0175                                                 + QStringLiteral("<br><br>") + i18n("Do you wish to create one now?"),
0176                                             i18nc("@title:window", "Certification Not Possible"),
0177                                             KGuiItem(i18n("Create")),
0178                                             KStandardGuiItem::cancel());
0179         if (sel == KMessageBox::ButtonCode::PrimaryAction) {
0180             QEventLoop loop;
0181             auto cmd = new NewOpenPGPCertificateCommand;
0182             cmd->setParentWidget(d->parentWidgetOrView());
0183             connect(cmd, &Command::finished, &loop, &QEventLoop::quit);
0184             QMetaObject::invokeMethod(cmd, &NewOpenPGPCertificateCommand::start, Qt::QueuedConnection);
0185             loop.exec();
0186         } else {
0187             Q_EMIT(canceled());
0188             d->finished();
0189             return;
0190         }
0191 
0192         // Check again for secret keys
0193         if (!findAnyGoodKey()) {
0194             qCDebug(KLEOPATRA_LOG) << "Sec Keys still empty after keygen.";
0195             Q_EMIT(canceled());
0196             d->finished();
0197             return;
0198         }
0199     }
0200 
0201     const char *primary = keys.front().primaryFingerprint();
0202     const bool anyMismatch = std::any_of(d->uids.cbegin(), d->uids.cend(), [primary](const UserID &uid) {
0203         return qstricmp(uid.parent().primaryFingerprint(), primary) != 0;
0204     });
0205     if (anyMismatch) {
0206         qCWarning(KLEOPATRA_LOG) << "User ID <-> Key mismatch!";
0207         d->finished();
0208         return;
0209     }
0210 
0211     d->ensureDialogCreated();
0212     Q_ASSERT(d->dialog);
0213 
0214     d->dialog->setCertificateToCertify(d->target, d->uids);
0215     d->dialog->show();
0216 }
0217 
0218 void CertifyCertificateCommand::Private::slotDialogRejected()
0219 {
0220     Q_EMIT q->canceled();
0221     finished();
0222 }
0223 
0224 void CertifyCertificateCommand::Private::slotResult(const Error &err)
0225 {
0226     if (err.isCanceled()) {
0227         // do nothing
0228     } else if (err) {
0229         error(i18n("<p>An error occurred while trying to certify<br/><br/>"
0230                    "<b>%1</b>:</p><p>\t%2</p>",
0231                    Formatting::formatForComboBox(target),
0232                    Formatting::errorAsString(err)),
0233               i18n("Certification Error"));
0234     } else if (dialog && dialog->exportableCertificationSelected() && dialog->sendToServer()) {
0235         auto const cmd = new ExportOpenPGPCertsToServerCommand(target);
0236         cmd->start();
0237     } else {
0238         information(i18n("Certification successful."), i18n("Certification Succeeded"));
0239     }
0240 
0241     if (!dialog->tags().isEmpty()) {
0242         Tags::enableTags();
0243     }
0244     finished();
0245 }
0246 
0247 void CertifyCertificateCommand::Private::slotCertificationPrepared()
0248 {
0249     Q_ASSERT(dialog);
0250 
0251     const auto selectedUserIds = dialog->selectedUserIDs();
0252     std::vector<unsigned int> userIdIndexes;
0253     userIdIndexes.reserve(selectedUserIds.size());
0254     for (unsigned int i = 0, numUserIds = target.numUserIDs(); i < numUserIds; ++i) {
0255         const auto userId = target.userID(i);
0256         const bool userIdIsSelected = Kleo::any_of(selectedUserIds, [userId](const auto &uid) {
0257             return Kleo::userIDsAreEqual(userId, uid);
0258         });
0259         if (userIdIsSelected) {
0260             userIdIndexes.push_back(i);
0261         }
0262     }
0263 
0264     createJob();
0265     Q_ASSERT(job);
0266     job->setExportable(dialog->exportableCertificationSelected());
0267     job->setUserIDsToSign(userIdIndexes);
0268     job->setSigningKey(dialog->selectedSecretKey());
0269     if (!dialog->tags().isEmpty()) {
0270         // do not set an empty remark to avoid an empty signature notation (GnuPG bug T5142)
0271         job->setRemark(dialog->tags());
0272     }
0273     job->setDupeOk(true);
0274     if (dialog->trustSignatureSelected() && !dialog->trustSignatureDomain().isEmpty()) {
0275         // always create level 1 trust signatures with complete trust
0276         job->setTrustSignature(TrustSignatureTrust::Complete, 1, dialog->trustSignatureDomain());
0277     }
0278     if (!dialog->expirationDate().isNull()) {
0279         job->setExpirationDate(dialog->expirationDate());
0280     }
0281 
0282     if (const Error err = job->start(target)) {
0283         slotResult(err);
0284     }
0285 }
0286 
0287 void CertifyCertificateCommand::doCancel()
0288 {
0289     qCDebug(KLEOPATRA_LOG);
0290     if (d->job) {
0291         d->job->slotCancel();
0292     }
0293 }
0294 
0295 void CertifyCertificateCommand::Private::ensureDialogCreated()
0296 {
0297     if (dialog) {
0298         return;
0299     }
0300 
0301     dialog = new CertifyCertificateDialog;
0302     applyWindowID(dialog);
0303 
0304     connect(dialog, &QDialog::rejected, q, [this]() {
0305         slotDialogRejected();
0306     });
0307     connect(dialog, &QDialog::accepted, q, [this]() {
0308         slotCertificationPrepared();
0309     });
0310 }
0311 
0312 void CertifyCertificateCommand::Private::createJob()
0313 {
0314     Q_ASSERT(!job);
0315 
0316     Q_ASSERT(target.protocol() == OpenPGP);
0317     const auto backend = QGpgME::openpgp();
0318     if (!backend) {
0319         return;
0320     }
0321 
0322     SignKeyJob *const j = backend->signKeyJob();
0323     if (!j) {
0324         return;
0325     }
0326 
0327     connect(j, &QGpgME::Job::jobProgress, q, &Command::progress);
0328     connect(j, &SignKeyJob::result, q, [this](const GpgME::Error &result) {
0329         slotResult(result);
0330     });
0331 
0332     job = j;
0333 }
0334 
0335 #undef d
0336 #undef q
0337 
0338 #include "moc_certifycertificatecommand.cpp"