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"