1 /* -*- mode: c++; c-basic-offset:4 -*-
2 crypto/newsignencryptemailcontroller.cpp
3
4 This file is part of Kleopatra, the KDE keymanager
5 SPDX-FileCopyrightText: 2009, 2010 Klarälvdalens Datakonsult AB
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include <config-kleopatra.h>
11
12 #include "newsignencryptemailcontroller.h"
13 #include "kleopatra_debug.h"
14 #include "encryptemailtask.h"
15 #include "signemailtask.h"
16 #include "taskcollection.h"
17 #include "sender.h"
18 #include "recipient.h"
19
20 #include "emailoperationspreferences.h"
21
22 #include <crypto/gui/signencryptemailconflictdialog.h>
23
24 #include "utils/input.h"
25 #include "utils/output.h"
26 #include <Libkleo/GnuPG>
27 #include "utils/kleo_assert.h"
28
29 #include <Libkleo/Stl_Util>
30 #include <Libkleo/KleoException>
31
32 #include <gpgme++/key.h>
33
34 #include <KMime/HeaderParsing>
35
36 #include <KLocalizedString>
37
38 #include <KMessageBox>
39
40 #include <QPointer>
41 #include <QTimer>
42
43 using namespace Kleo;
44 using namespace Kleo::Crypto;
45 using namespace Kleo::Crypto::Gui;
46 using namespace GpgME;
47 using namespace KMime::Types;
48
49 //
50 // BEGIN Conflict Detection
51 //
52
53 /*
54 This code implements the following conflict detection algorithm:
55
56 1. There is no conflict if and only if we have a Perfect Match.
57 2. A Perfect Match is defined as:
58 a. either a Perfect OpenPGP-Match and not even a Partial S/MIME Match
59 b. or a Perfect S/MIME-Match and not even a Partial OpenPGP-Match
60 c. or a Perfect OpenPGP-Match and preselected protocol=OpenPGP
61 d. or a Perfect S/MIME-Match and preselected protocol=S/MIME
62 3. For Protocol \in {OpenPGP,S/MIME}, a Perfect Protocol-Match is defined as:
63 a. If signing, \foreach Sender, there is exactly one
64 Matching Protocol-Certificate with
65 i. can-sign=true
66 ii. has-secret=true
67 b. and, if encrypting, \foreach Recipient, there is exactly one
68 Matching Protocol-Certificate with
69 i. can-encrypt=true
70 ii. (validity is not considered, cf. msg 24059)
71 4. For Protocol \in {OpenPGP,S/MIME}, a Partial Protocol-Match is defined as:
72 a. If signing, \foreach Sender, there is at least one
73 Matching Protocol-Certificate with
74 i. can-sign=true
75 ii. has-secret=true
76 b. and, if encrypting, \foreach Recipient, there is at least
77 one Matching Protocol-Certificate with
78 i. can-encrypt=true
79 ii. (validity is not considered, cf. msg 24059)
80 5. For Protocol \in {OpenPGP,S/MIME}, a Matching Protocol-Certificate is
81 defined as matching by email-address. A revoked, disabled, or expired
82 certificate is not considered a match.
83 6. Sender is defined as those mailboxes that have been set with the SENDER
84 command.
85 7. Recipient is defined as those mailboxes that have been set with either the
86 SENDER or the RECIPIENT commands.
87 */
88
89 namespace
90 {
91
count_signing_certificates(Protocol proto,const Sender & sender)92 static size_t count_signing_certificates(Protocol proto, const Sender &sender)
93 {
94 const size_t result = sender.signingCertificateCandidates(proto).size();
95 qDebug("count_signing_certificates( %9s %20s ) == %2lu",
96 proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : "<unknown>,",
97 qPrintable(sender.mailbox().prettyAddress()), result);
98 return result;
99 }
100
count_encrypt_certificates(Protocol proto,const Sender & sender)101 static size_t count_encrypt_certificates(Protocol proto, const Sender &sender)
102 {
103 const size_t result = sender.encryptToSelfCertificateCandidates(proto).size();
104 qDebug("count_encrypt_certificates( %9s %20s ) == %2lu",
105 proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : "<unknown>,",
106 qPrintable(sender.mailbox().prettyAddress()), result);
107 return result;
108 }
109
count_encrypt_certificates(Protocol proto,const Recipient & recipient)110 static size_t count_encrypt_certificates(Protocol proto, const Recipient &recipient)
111 { const size_t result = recipient.encryptionCertificateCandidates(proto).size();
112 qDebug("count_encrypt_certificates( %9s %20s ) == %2lu",
113 proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : "<unknown>,",
114 qPrintable(recipient.mailbox().prettyAddress()), result);
115 return result;
116 }
117
118 }
119
has_perfect_match(bool sign,bool encrypt,Protocol proto,const std::vector<Sender> & senders,const std::vector<Recipient> & recipients)120 static bool has_perfect_match(bool sign, bool encrypt, Protocol proto, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients)
121 {
122 if (sign)
123 if (!std::all_of(senders.cbegin(), senders.cend(),
124 [proto](const Sender &sender) { return count_signing_certificates(proto, sender) == 1; })) {
125 return false;
126 }
127 if (encrypt)
128 if (!std::all_of(senders.cbegin(), senders.cend(),
129 [proto](const Sender &sender) { return count_encrypt_certificates(proto, sender) == 1; })
130 || !std::all_of(recipients.cbegin(), recipients.cend(),
131 [proto](const Recipient &rec) { return count_encrypt_certificates(proto, rec) == 1; })) {
132 return false;
133 }
134 return true;
135 }
136
has_partial_match(bool sign,bool encrypt,Protocol proto,const std::vector<Sender> & senders,const std::vector<Recipient> & recipients)137 static bool has_partial_match(bool sign, bool encrypt, Protocol proto, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients)
138 {
139 if (sign)
140 if (std::all_of(senders.cbegin(), senders.cend(),
141 [proto](const Sender &sender) { return count_signing_certificates(proto, sender) >= 1; })) {
142 return false;
143 }
144 if (encrypt)
145 if (!std::all_of(senders.cbegin(), senders.cend(),
146 [proto](const Sender &sender) { return count_encrypt_certificates(proto, sender) >= 1; })
147 || !std::all_of(recipients.cbegin(), recipients.cend(),
148 [proto](const Recipient &rec) { return count_encrypt_certificates(proto, rec) >= 1; })) {
149 return false;
150 }
151 return true;
152 }
153
has_perfect_overall_match(bool sign,bool encrypt,const std::vector<Sender> & senders,const std::vector<Recipient> & recipients,Protocol presetProtocol)154 static bool has_perfect_overall_match(bool sign, bool encrypt, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients, Protocol presetProtocol)
155 {
156 return (presetProtocol == OpenPGP && has_perfect_match(sign, encrypt, OpenPGP, senders, recipients))
157 || (presetProtocol == CMS && has_perfect_match(sign, encrypt, CMS, senders, recipients))
158 || (has_perfect_match(sign, encrypt, OpenPGP, senders, recipients) && !has_partial_match(sign, encrypt, CMS, senders, recipients))
159 || (has_perfect_match(sign, encrypt, CMS, senders, recipients) && !has_partial_match(sign, encrypt, OpenPGP, senders, recipients));
160 }
161
has_conflict(bool sign,bool encrypt,const std::vector<Sender> & senders,const std::vector<Recipient> & recipients,Protocol presetProtocol)162 static bool has_conflict(bool sign, bool encrypt, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients, Protocol presetProtocol)
163 {
164 return !has_perfect_overall_match(sign, encrypt, senders, recipients, presetProtocol);
165 }
166
is_de_vs_compliant(bool sign,bool encrypt,const std::vector<Sender> & senders,const std::vector<Recipient> & recipients,Protocol presetProtocol)167 static bool is_de_vs_compliant(bool sign, bool encrypt, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients, Protocol presetProtocol)
168 {
169 if (presetProtocol == Protocol::UnknownProtocol) {
170 return false;
171 }
172 if (sign) {
173 for (const auto &sender: senders) {
174 const auto &key = sender.resolvedSigningKey(presetProtocol);
175 if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
176 return false;
177 }
178 }
179 }
180
181 if (encrypt) {
182 for (const auto &sender: senders) {
183 const auto &key = sender.resolvedSigningKey(presetProtocol);
184 if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
185 return false;
186 }
187 }
188
189 for (const auto &recipient: recipients) {
190 const auto &key = recipient.resolvedEncryptionKey(presetProtocol);
191 if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
192 return false;
193 }
194 }
195 }
196
197 return true;
198 }
199
200 //
201 // END Conflict Detection
202 //
203
mailbox2sender(const std::vector<Mailbox> & mbs)204 static std::vector<Sender> mailbox2sender(const std::vector<Mailbox> &mbs)
205 {
206 std::vector<Sender> senders;
207 senders.reserve(mbs.size());
208 for (const Mailbox &mb : mbs) {
209 senders.push_back(Sender(mb));
210 }
211 return senders;
212 }
213
mailbox2recipient(const std::vector<Mailbox> & mbs)214 static std::vector<Recipient> mailbox2recipient(const std::vector<Mailbox> &mbs)
215 {
216 std::vector<Recipient> recipients;
217 recipients.reserve(mbs.size());
218 for (const Mailbox &mb : mbs) {
219 recipients.push_back(Recipient(mb));
220 }
221 return recipients;
222 }
223
224 class NewSignEncryptEMailController::Private
225 {
226 friend class ::Kleo::Crypto::NewSignEncryptEMailController;
227 NewSignEncryptEMailController *const q;
228 public:
229 explicit Private(NewSignEncryptEMailController *qq);
230 ~Private();
231
232 private:
233 void slotDialogAccepted();
234 void slotDialogRejected();
235
236 private:
237 void ensureDialogVisible();
238 void cancelAllTasks();
239
240 void startSigning();
241 void startEncryption();
242 void schedule();
243 std::shared_ptr<Task> takeRunnable(GpgME::Protocol proto);
244
245 private:
246 bool sign : 1;
247 bool encrypt : 1;
248 bool resolvingInProgress : 1;
249 bool certificatesResolved : 1;
250 bool detached : 1;
251 Protocol presetProtocol;
252 std::vector<Key> signers, recipients;
253 std::vector< std::shared_ptr<Task> > runnable, completed;
254 std::shared_ptr<Task> cms, openpgp;
255 QPointer<SignEncryptEMailConflictDialog> dialog;
256 };
257
Private(NewSignEncryptEMailController * qq)258 NewSignEncryptEMailController::Private::Private(NewSignEncryptEMailController *qq)
259 : q(qq),
260 sign(false),
261 encrypt(false),
262 resolvingInProgress(false),
263 certificatesResolved(false),
264 detached(false),
265 presetProtocol(UnknownProtocol),
266 signers(),
267 recipients(),
268 runnable(),
269 cms(),
270 openpgp(),
271 dialog(new SignEncryptEMailConflictDialog)
272 {
273 connect(dialog, SIGNAL(accepted()), q, SLOT(slotDialogAccepted()));
274 connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected()));
275 }
276
~Private()277 NewSignEncryptEMailController::Private::~Private()
278 {
279 delete dialog;
280 }
281
NewSignEncryptEMailController(const std::shared_ptr<ExecutionContext> & xc,QObject * p)282 NewSignEncryptEMailController::NewSignEncryptEMailController(const std::shared_ptr<ExecutionContext> &xc, QObject *p)
283 : Controller(xc, p), d(new Private(this))
284 {
285
286 }
287
NewSignEncryptEMailController(QObject * p)288 NewSignEncryptEMailController::NewSignEncryptEMailController(QObject *p)
289 : Controller(p), d(new Private(this))
290 {
291
292 }
293
~NewSignEncryptEMailController()294 NewSignEncryptEMailController::~NewSignEncryptEMailController()
295 {
296 qCDebug(KLEOPATRA_LOG);
297 }
298
setSubject(const QString & subject)299 void NewSignEncryptEMailController::setSubject(const QString &subject)
300 {
301 d->dialog->setSubject(subject);
302 }
303
setProtocol(Protocol proto)304 void NewSignEncryptEMailController::setProtocol(Protocol proto)
305 {
306 d->presetProtocol = proto;
307 d->dialog->setPresetProtocol(proto);
308 }
309
protocol() const310 Protocol NewSignEncryptEMailController::protocol() const
311 {
312 return d->dialog->selectedProtocol();
313 }
314
protocolAsString() const315 const char *NewSignEncryptEMailController::protocolAsString() const
316 {
317 switch (protocol()) {
318 case OpenPGP: return "OpenPGP";
319 case CMS: return "CMS";
320 default:
321 throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL),
322 i18n("Call to NewSignEncryptEMailController::protocolAsString() is ambiguous."));
323 }
324 }
325
setSigning(bool sign)326 void NewSignEncryptEMailController::setSigning(bool sign)
327 {
328 d->sign = sign;
329 d->dialog->setSign(sign);
330 }
331
isSigning() const332 bool NewSignEncryptEMailController::isSigning() const
333 {
334 return d->sign;
335 }
336
setEncrypting(bool encrypt)337 void NewSignEncryptEMailController::setEncrypting(bool encrypt)
338 {
339 d->encrypt = encrypt;
340 d->dialog->setEncrypt(encrypt);
341 }
342
isEncrypting() const343 bool NewSignEncryptEMailController::isEncrypting() const
344 {
345 return d->encrypt;
346 }
347
setDetachedSignature(bool detached)348 void NewSignEncryptEMailController::setDetachedSignature(bool detached)
349 {
350 d->detached = detached;
351 }
352
isResolvingInProgress() const353 bool NewSignEncryptEMailController::isResolvingInProgress() const
354 {
355 return d->resolvingInProgress;
356 }
357
areCertificatesResolved() const358 bool NewSignEncryptEMailController::areCertificatesResolved() const
359 {
360 return d->certificatesResolved;
361 }
362
is_dialog_quick_mode(bool sign,bool encrypt)363 static bool is_dialog_quick_mode(bool sign, bool encrypt)
364 {
365 const EMailOperationsPreferences prefs;
366 return (!sign || prefs.quickSignEMail())
367 && (!encrypt || prefs.quickEncryptEMail())
368 ;
369 }
370
save_dialog_quick_mode(bool on)371 static void save_dialog_quick_mode(bool on)
372 {
373 EMailOperationsPreferences prefs;
374 prefs.setQuickSignEMail(on);
375 prefs.setQuickEncryptEMail(on);
376 prefs.save();
377 }
378
startResolveCertificates(const std::vector<Mailbox> & r,const std::vector<Mailbox> & s)379 void NewSignEncryptEMailController::startResolveCertificates(const std::vector<Mailbox> &r, const std::vector<Mailbox> &s)
380 {
381 d->certificatesResolved = false;
382 d->resolvingInProgress = true;
383
384 const std::vector<Sender> senders = mailbox2sender(s);
385 const std::vector<Recipient> recipients = mailbox2recipient(r);
386 const bool quickMode = is_dialog_quick_mode(d->sign, d->encrypt);
387
388 const bool conflict = quickMode && has_conflict(d->sign, d->encrypt, senders, recipients, d->presetProtocol);
389
390 d->dialog->setQuickMode(quickMode);
391 d->dialog->setSenders(senders);
392 d->dialog->setRecipients(recipients);
393 d->dialog->pickProtocol();
394 d->dialog->setConflict(conflict);
395
396 const bool compliant = !Kleo::gnupgUsesDeVsCompliance() ||
397 (Kleo::gnupgIsDeVsCompliant() && is_de_vs_compliant(d->sign,
398 d->encrypt,
399 senders,
400 recipients,
401 d->presetProtocol));
402
403 if (quickMode && !conflict && compliant) {
404 QMetaObject::invokeMethod(this, "slotDialogAccepted", Qt::QueuedConnection);
405 } else {
406 d->ensureDialogVisible();
407 }
408 }
409
slotDialogAccepted()410 void NewSignEncryptEMailController::Private::slotDialogAccepted()
411 {
412 if (dialog->isQuickMode() != is_dialog_quick_mode(sign, encrypt)) {
413 save_dialog_quick_mode(dialog->isQuickMode());
414 }
415 resolvingInProgress = false;
416 certificatesResolved = true;
417 signers = dialog->resolvedSigningKeys();
418 recipients = dialog->resolvedEncryptionKeys();
419 QMetaObject::invokeMethod(q, "certificatesResolved", Qt::QueuedConnection);
420 }
421
slotDialogRejected()422 void NewSignEncryptEMailController::Private::slotDialogRejected()
423 {
424 resolvingInProgress = false;
425 certificatesResolved = false;
426 QMetaObject::invokeMethod(q, "error", Qt::QueuedConnection,
427 Q_ARG(int, gpg_error(GPG_ERR_CANCELED)),
428 Q_ARG(QString, i18n("User cancel")));
429 }
430
startEncryption(const std::vector<std::shared_ptr<Input>> & inputs,const std::vector<std::shared_ptr<Output>> & outputs)431 void NewSignEncryptEMailController::startEncryption(const std::vector< std::shared_ptr<Input> > &inputs, const std::vector< std::shared_ptr<Output> > &outputs)
432 {
433
434 kleo_assert(d->encrypt);
435 kleo_assert(!d->resolvingInProgress);
436
437 kleo_assert(!inputs.empty());
438 kleo_assert(outputs.size() == inputs.size());
439
440 std::vector< std::shared_ptr<Task> > tasks;
441 tasks.reserve(inputs.size());
442
443 kleo_assert(!d->recipients.empty());
444
445 for (unsigned int i = 0, end = inputs.size(); i < end; ++i) {
446
447 const std::shared_ptr<EncryptEMailTask> task(new EncryptEMailTask);
448
449 task->setInput(inputs[i]);
450 task->setOutput(outputs[i]);
451 task->setRecipients(d->recipients);
452
453 tasks.push_back(task);
454 }
455
456 // append to runnable stack
457 d->runnable.insert(d->runnable.end(), tasks.begin(), tasks.end());
458
459 d->startEncryption();
460 }
461
startEncryption()462 void NewSignEncryptEMailController::Private::startEncryption()
463 {
464 std::shared_ptr<TaskCollection> coll(new TaskCollection);
465 std::vector<std::shared_ptr<Task> > tmp;
466 tmp.reserve(runnable.size());
467 std::copy(runnable.cbegin(), runnable.cend(), std::back_inserter(tmp));
468 coll->setTasks(tmp);
469 #if 0
470 #warning use a new result dialog
471 // ### use a new result dialog
472 dialog->setTaskCollection(coll);
473 #endif
474 for (const std::shared_ptr<Task> &t : std::as_const(tmp)) {
475 q->connectTask(t);
476 }
477 schedule();
478 }
479
startSigning(const std::vector<std::shared_ptr<Input>> & inputs,const std::vector<std::shared_ptr<Output>> & outputs)480 void NewSignEncryptEMailController::startSigning(const std::vector< std::shared_ptr<Input> > &inputs, const std::vector< std::shared_ptr<Output> > &outputs)
481 {
482
483 kleo_assert(d->sign);
484 kleo_assert(!d->resolvingInProgress);
485
486 kleo_assert(!inputs.empty());
487 kleo_assert(!outputs.empty());
488
489 std::vector< std::shared_ptr<Task> > tasks;
490 tasks.reserve(inputs.size());
491
492 kleo_assert(!d->signers.empty());
493 kleo_assert(std::none_of(d->signers.cbegin(), d->signers.cend(), std::mem_fn(&Key::isNull)));
494
495 for (unsigned int i = 0, end = inputs.size(); i < end; ++i) {
496
497 const std::shared_ptr<SignEMailTask> task(new SignEMailTask);
498
499 task->setInput(inputs[i]);
500 task->setOutput(outputs[i]);
501 task->setSigners(d->signers);
502 task->setDetachedSignature(d->detached);
503
504 tasks.push_back(task);
505 }
506
507 // append to runnable stack
508 d->runnable.insert(d->runnable.end(), tasks.begin(), tasks.end());
509
510 d->startSigning();
511 }
512
startSigning()513 void NewSignEncryptEMailController::Private::startSigning()
514 {
515 std::shared_ptr<TaskCollection> coll(new TaskCollection);
516 std::vector<std::shared_ptr<Task> > tmp;
517 tmp.reserve(runnable.size());
518 std::copy(runnable.cbegin(), runnable.cend(), std::back_inserter(tmp));
519 coll->setTasks(tmp);
520 #if 0
521 #warning use a new result dialog
522 // ### use a new result dialog
523 dialog->setTaskCollection(coll);
524 #endif
525 for (const std::shared_ptr<Task> &t : std::as_const(tmp)) {
526 q->connectTask(t);
527 }
528 schedule();
529 }
530
schedule()531 void NewSignEncryptEMailController::Private::schedule()
532 {
533
534 if (!cms)
535 if (const std::shared_ptr<Task> t = takeRunnable(CMS)) {
536 t->start();
537 cms = t;
538 }
539
540 if (!openpgp)
541 if (const std::shared_ptr<Task> t = takeRunnable(OpenPGP)) {
542 t->start();
543 openpgp = t;
544 }
545
546 if (cms || openpgp) {
547 return;
548 }
549 kleo_assert(runnable.empty());
550 q->emitDoneOrError();
551 }
552
takeRunnable(GpgME::Protocol proto)553 std::shared_ptr<Task> NewSignEncryptEMailController::Private::takeRunnable(GpgME::Protocol proto)
554 {
555 const auto it = std::find_if(runnable.begin(), runnable.end(),
556 [proto](const std::shared_ptr<Task> &task) { return task->protocol() == proto; });
557 if (it == runnable.end()) {
558 return std::shared_ptr<Task>();
559 }
560
561 const std::shared_ptr<Task> result = *it;
562 runnable.erase(it);
563 return result;
564 }
565
doTaskDone(const Task * task,const std::shared_ptr<const Task::Result> & result)566 void NewSignEncryptEMailController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result)
567 {
568 Q_ASSERT(task);
569
570 if (result && result->hasError()) {
571 QPointer<QObject> that = this;
572 if (result->details().isEmpty())
573 KMessageBox:: sorry(nullptr,
574 result->overview(),
575 i18nc("@title:window", "Error"));
576 else
577 KMessageBox::detailedSorry(nullptr,
578 result->overview(),
579 result->details(),
580 i18nc("@title:window", "Error"));
581 if (!that) {
582 return;
583 }
584 }
585
586 // We could just delete the tasks here, but we can't use
587 // Qt::QueuedConnection here (we need sender()) and other slots
588 // might not yet have executed. Therefore, we push completed tasks
589 // into a burial container
590
591 if (task == d->cms.get()) {
592 d->completed.push_back(d->cms);
593 d->cms.reset();
594 } else if (task == d->openpgp.get()) {
595 d->completed.push_back(d->openpgp);
596 d->openpgp.reset();
597 }
598
599 QTimer::singleShot(0, this, SLOT(schedule()));
600 }
601
cancel()602 void NewSignEncryptEMailController::cancel()
603 {
604 try {
605 d->dialog->close();
606 d->cancelAllTasks();
607 } catch (const std::exception &e) {
608 qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
609 }
610 }
611
cancelAllTasks()612 void NewSignEncryptEMailController::Private::cancelAllTasks()
613 {
614
615 // we just kill all runnable tasks - this will not result in
616 // signal emissions.
617 runnable.clear();
618
619 // a cancel() will result in a call to
620 if (cms) {
621 cms->cancel();
622 }
623 if (openpgp) {
624 openpgp->cancel();
625 }
626 }
627
ensureDialogVisible()628 void NewSignEncryptEMailController::Private::ensureDialogVisible()
629 {
630 q->bringToForeground(dialog, true);
631 }
632
633 #include "moc_newsignencryptemailcontroller.cpp"
634