File indexing completed on 2024-06-23 05:13:37
0001 /* 0002 commands/certifygroupcommand.cpp 0003 0004 This file is part of Kleopatra, the KDE keymanager 0005 SPDX-FileCopyrightText: 2023 g10 Code GmbH 0006 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "certifygroupcommand.h" 0012 #include "command_p.h" 0013 0014 #include <commands/exportopenpgpcertstoservercommand.h> 0015 #include <dialogs/certifycertificatedialog.h> 0016 #include <utils/tags.h> 0017 0018 #include <Libkleo/Algorithm> 0019 #include <Libkleo/Formatting> 0020 #include <Libkleo/KeyGroup> 0021 #include <Libkleo/KeyHelpers> 0022 0023 #include <QGpgME/Protocol> 0024 #include <QGpgME/SignKeyJob> 0025 0026 #include <QDate> 0027 #include <QProgressDialog> 0028 0029 #include <gpgme++/key.h> 0030 0031 using namespace Kleo; 0032 using namespace Kleo::Commands; 0033 using namespace GpgME; 0034 0035 namespace 0036 { 0037 struct CertificationResultData { 0038 std::vector<UserID> userIds; 0039 GpgME::Error error; 0040 }; 0041 } 0042 0043 class CertifyGroupCommand::Private : public Command::Private 0044 { 0045 friend class ::Kleo::CertifyGroupCommand; 0046 CertifyGroupCommand *q_func() const 0047 { 0048 return static_cast<CertifyGroupCommand *>(q); 0049 } 0050 0051 public: 0052 explicit Private(CertifyGroupCommand *qq); 0053 ~Private() override; 0054 0055 void start(); 0056 0057 private: 0058 void showDialog(); 0059 void certifyCertificates(); 0060 void setUpProgressDialog(int numberOfKeysToCertify); 0061 void startNextCertification(); 0062 void createJob(); 0063 void slotResult(const Error &err); 0064 void wrapUp(); 0065 0066 private: 0067 KeyGroup group; 0068 std::vector<Key> certificates; 0069 QPointer<CertifyCertificateDialog> dialog; 0070 QPointer<QProgressDialog> progressDialog; 0071 std::vector<UserID> userIdsToCertify; 0072 struct { 0073 Key certificationKey; 0074 QDate expirationDate; 0075 QString tags; 0076 bool exportable = false; 0077 bool sendToServer = false; 0078 } certificationOptions; 0079 struct { 0080 std::vector<UserID> userIds; 0081 } jobData; 0082 QPointer<QGpgME::SignKeyJob> job; 0083 std::vector<CertificationResultData> results; 0084 }; 0085 0086 CertifyGroupCommand::Private *CertifyGroupCommand::d_func() 0087 { 0088 return static_cast<Private *>(d.get()); 0089 } 0090 const CertifyGroupCommand::Private *CertifyGroupCommand::d_func() const 0091 { 0092 return static_cast<const Private *>(d.get()); 0093 } 0094 0095 #define d d_func() 0096 #define q q_func() 0097 0098 CertifyGroupCommand::Private::Private(CertifyGroupCommand *qq) 0099 : Command::Private(qq) 0100 { 0101 } 0102 0103 CertifyGroupCommand::Private::~Private() = default; 0104 0105 void CertifyGroupCommand::Private::start() 0106 { 0107 if (!group.isNull()) { 0108 const auto &groupKeys = group.keys(); 0109 certificates = std::vector<GpgME::Key>(groupKeys.begin(), groupKeys.end()); 0110 } 0111 if (certificates.empty()) { 0112 finished(); 0113 return; 0114 } 0115 if (!allKeysHaveProtocol(certificates, GpgME::OpenPGP)) { 0116 const auto title = i18nc("@title:window", "Group Cannot Be Certified"); 0117 const auto message = i18nc("@info", "This group contains S/MIME certificates which cannot be certified."); 0118 information(message, title); 0119 finished(); 0120 return; 0121 } 0122 0123 showDialog(); 0124 } 0125 0126 void CertifyGroupCommand::Private::showDialog() 0127 { 0128 dialog = new CertifyCertificateDialog; 0129 dialog->setAttribute(Qt::WA_DeleteOnClose); 0130 applyWindowID(dialog); 0131 0132 connect(dialog, &QDialog::accepted, q, [this]() { 0133 certifyCertificates(); 0134 }); 0135 connect(dialog, &QDialog::rejected, q, [this]() { 0136 canceled(); 0137 }); 0138 0139 if (!group.isNull()) { 0140 dialog->setGroupName(group.name()); 0141 } 0142 dialog->setCertificatesToCertify(certificates); 0143 dialog->show(); 0144 } 0145 0146 void CertifyGroupCommand::Private::certifyCertificates() 0147 { 0148 userIdsToCertify = dialog->selectedUserIDs(); 0149 if (userIdsToCertify.empty()) { 0150 canceled(); 0151 return; 0152 } 0153 certificationOptions.certificationKey = dialog->selectedSecretKey(); 0154 certificationOptions.expirationDate = dialog->expirationDate(); 0155 certificationOptions.tags = dialog->tags(); 0156 certificationOptions.exportable = dialog->exportableCertificationSelected(); 0157 certificationOptions.sendToServer = dialog->sendToServer(); 0158 0159 setUpProgressDialog(userIdsToCertify.size()); 0160 startNextCertification(); 0161 } 0162 0163 void CertifyGroupCommand::Private::setUpProgressDialog(int numberOfKeysToCertify) 0164 { 0165 if (progressDialog) { 0166 return; 0167 } 0168 progressDialog = new QProgressDialog{parentWidgetOrView()}; 0169 progressDialog->setAttribute(Qt::WA_DeleteOnClose); 0170 progressDialog->setModal(true); 0171 progressDialog->setWindowTitle(i18nc("@title:window", "Certify Certificates")); 0172 progressDialog->setLabelText(i18nc("@info:progress", "Certifying certificates ...")); 0173 progressDialog->setMinimumDuration(1000); 0174 progressDialog->setMaximum(numberOfKeysToCertify); 0175 progressDialog->setValue(0); 0176 connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel); 0177 connect(q, &Command::finished, progressDialog, [this]() { 0178 progressDialog->accept(); 0179 }); 0180 } 0181 0182 void CertifyGroupCommand::Private::startNextCertification() 0183 { 0184 Q_ASSERT(!userIdsToCertify.empty()); 0185 0186 const auto nextKey = userIdsToCertify.front().parent(); 0187 // for now we only deal with primary user IDs 0188 jobData.userIds = {userIdsToCertify.front()}; 0189 userIdsToCertify.erase(userIdsToCertify.begin()); 0190 const std::vector<unsigned int> userIdIndexes = {0}; 0191 0192 createJob(); 0193 job->setUserIDsToSign(userIdIndexes); 0194 if (const Error err = job->start(nextKey)) { 0195 QMetaObject::invokeMethod( 0196 q, 0197 [this, err]() { 0198 slotResult(err); 0199 }, 0200 Qt::QueuedConnection); 0201 } 0202 } 0203 0204 void CertifyGroupCommand::Private::createJob() 0205 { 0206 Q_ASSERT(!job); 0207 0208 std::unique_ptr<QGpgME::SignKeyJob> newJob{QGpgME::openpgp()->signKeyJob()}; 0209 newJob->setDupeOk(true); 0210 newJob->setSigningKey(certificationOptions.certificationKey); 0211 newJob->setExportable(certificationOptions.exportable); 0212 if (!certificationOptions.tags.isEmpty()) { 0213 // do not set an empty remark to avoid an empty signature notation (GnuPG bug T5142) 0214 newJob->setRemark(certificationOptions.tags); 0215 } 0216 if (!certificationOptions.expirationDate.isNull()) { 0217 newJob->setExpirationDate(certificationOptions.expirationDate); 0218 } 0219 connect(newJob.get(), &QGpgME::SignKeyJob::result, q, [this](const GpgME::Error &result) { 0220 slotResult(result); 0221 }); 0222 0223 job = newJob.release(); 0224 } 0225 0226 void CertifyGroupCommand::Private::slotResult(const Error &err) 0227 { 0228 results.push_back({ 0229 jobData.userIds, 0230 err, 0231 }); 0232 progressDialog->setValue(results.size()); 0233 0234 if (err.isCanceled()) { 0235 finished(); 0236 } else if (err && (results.size() == 1) // 0237 && ((err.sourceID() == GPG_ERR_SOURCE_PINENTRY) // 0238 || (err.code() == GPG_ERR_BAD_PASSPHRASE))) { 0239 // abort the certification process on certain errors during the first 0240 // certification, e.g. bad passphrase or pinentry timeout, where it's 0241 // better to restart the whole process instead of continuing with the 0242 // next certificate 0243 error(xi18nc("@info", 0244 "<para>The certification of the certificates failed.</para>" 0245 "<para>Error: <message>%1</message></para>", 0246 Formatting::errorAsString(results.front().error))); 0247 finished(); 0248 } else if (!userIdsToCertify.empty()) { 0249 job.clear(); 0250 jobData.userIds.clear(); 0251 startNextCertification(); 0252 } else { 0253 wrapUp(); 0254 } 0255 } 0256 0257 static QString resultSummary(const std::vector<CertificationResultData> &results) 0258 { 0259 Q_ASSERT(!results.empty()); 0260 0261 const int totalCount = results.size(); 0262 const int successCount = Kleo::count_if(results, [](const auto &result) { 0263 return !result.error; 0264 }); 0265 0266 if (successCount == totalCount) { 0267 return i18nc("@info", "All certificates were certified successfully."); 0268 } 0269 if (successCount == 0) { 0270 // we assume that all attempted certifications failed for the same reason 0271 return xi18nc("@info", 0272 "<para>The certification of all certificates failed.</para>" 0273 "<para>Error: <message>%1</message></para>", 0274 Formatting::errorAsString(results.front().error)); 0275 } 0276 return i18ncp("@info", // 0277 "1 of %2 certificates was certified successfully.", 0278 "%1 of %2 certificates were certified successfully.", 0279 successCount, 0280 totalCount); 0281 } 0282 0283 void CertifyGroupCommand::Private::wrapUp() 0284 { 0285 Q_ASSERT(userIdsToCertify.empty()); 0286 Q_ASSERT(!results.empty()); 0287 0288 const int successCount = Kleo::count_if(results, [](const auto &result) { 0289 return !result.error; 0290 }); 0291 const bool sendToServer = (successCount > 0) && certificationOptions.exportable && certificationOptions.sendToServer; 0292 0293 QString message = QLatin1StringView{"<p>"} + resultSummary(results) + QLatin1String{"</p>"}; 0294 if (sendToServer) { 0295 message += i18nc("@info", "<p>Next the certified certificates will be uploaded to the configured certificate directory.</p>"); 0296 } 0297 const auto failedUserIdsInfo = std::accumulate(results.cbegin(), results.cend(), QStringList{}, [](auto failedUserIds, const auto &result) { 0298 if (result.error) { 0299 failedUserIds.push_back(i18nc("A user ID (an error description)", 0300 "%1 (%2)", 0301 Formatting::formatForComboBox(result.userIds.front().parent()), 0302 Formatting::errorAsString(result.error))); 0303 } 0304 return failedUserIds; 0305 }); 0306 0307 if (successCount > 0) { 0308 if (failedUserIdsInfo.size() > 0) { 0309 message += i18nc("@info", "<p>Certifying the following certificates failed:</p>"); 0310 } 0311 informationList(message, failedUserIdsInfo, i18nc("@title:window", "Certification Completed")); 0312 } else { 0313 error(message); 0314 } 0315 0316 if (sendToServer) { 0317 const auto certificatesToSendToServer = std::accumulate(results.cbegin(), results.cend(), std::vector<Key>{}, [](auto keys, const auto &result) { 0318 if (!result.error) { 0319 keys.push_back(result.userIds.front().parent()); 0320 } 0321 return keys; 0322 }); 0323 const auto cmd = new ExportOpenPGPCertsToServerCommand(certificatesToSendToServer); 0324 cmd->start(); 0325 } 0326 0327 if (!certificationOptions.tags.isEmpty()) { 0328 Tags::enableTags(); 0329 } 0330 finished(); 0331 } 0332 0333 CertifyGroupCommand::CertifyGroupCommand(const KeyGroup &group) 0334 : Command{new Private{this}} 0335 { 0336 d->group = group; 0337 } 0338 0339 CertifyGroupCommand::~CertifyGroupCommand() = default; 0340 0341 void CertifyGroupCommand::doStart() 0342 { 0343 d->start(); 0344 } 0345 0346 void CertifyGroupCommand::doCancel() 0347 { 0348 if (d->dialog) { 0349 d->dialog->close(); 0350 } 0351 if (d->job) { 0352 d->job->slotCancel(); 0353 } 0354 } 0355 0356 #undef d 0357 #undef q 0358 0359 #include "moc_certifygroupcommand.cpp"