Messagelib

autocryptheadersjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Sandro KnauƟ <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "job/autocryptheadersjob.h"
8 
9 #include "contentjobbase_p.h"
10 
11 #include "job/singlepartjob.h"
12 #include "utils/util_p.h"
13 
14 #include "messagecomposer_debug.h"
15 
16 #include <QGpgME/ExportJob>
17 #include <QGpgME/Protocol>
18 #include <gpgme++/context.h>
19 #include <gpgme++/gpgmepp_version.h>
20 
21 #include <KCodecs>
22 #include <KLocalizedString>
23 #include <KMime/Content>
24 #include <KMime/Headers>
25 
26 #include <QByteArray>
27 
28 #include <map>
29 
30 using namespace MessageComposer;
31 
32 class MessageComposer::AutocryptHeadersJobPrivate : public ContentJobBasePrivate
33 {
34 public:
35  AutocryptHeadersJobPrivate(AutocryptHeadersJob *qq)
36  : ContentJobBasePrivate(qq)
37  {
38  }
39 
40  ~AutocryptHeadersJobPrivate() override
41  {
42  // clean up in case of cancelled job
43  for (const auto &[key, header] : gossipHeaders) {
44  delete header;
45  }
46  gossipHeaders.clear();
47  }
48 
49  void emitGpgError(const GpgME::Error &error);
50  void emitNotFoundError(const QByteArray &addr, const QByteArray &fingerprint);
51  void fillHeaderData(KMime::Headers::Generic *header, const QByteArray &addr, bool preferEncrypted, const QByteArray &keydata);
52  void finishOnLastSubJob();
53 
54  KMime::Content *content = nullptr;
55  KMime::Message *skeletonMessage = nullptr;
56  // used to ensure consistent order based on key order, not random one by async subjobs delivering
57  std::map<QByteArray, KMime::Headers::Generic *> gossipHeaders;
58 
59  bool preferEncrypted = false;
60  int subJobs = 0;
61 
62  QString gnupgHome;
63  GpgME::Key recipientKey;
64  std::vector<GpgME::Key> gossipKeys;
65 
66  Q_DECLARE_PUBLIC(AutocryptHeadersJob)
67 };
68 
69 void AutocryptHeadersJobPrivate::finishOnLastSubJob()
70 {
71  Q_Q(AutocryptHeadersJob);
72 
73  if (subJobs > 0) {
74  return;
75  }
76 
77  for (const auto &[key, header] : gossipHeaders) {
78  content->appendHeader(header);
79  }
80  gossipHeaders.clear();
81  resultContent = content;
82 
83  q->emitResult();
84 }
85 
86 void AutocryptHeadersJobPrivate::emitGpgError(const GpgME::Error &error)
87 {
88  Q_Q(AutocryptHeadersJob);
89 
90  Q_ASSERT(error);
91  const QString msg = i18n(
92  "<p>An error occurred while trying to export "
93  "the key from the backend:</p>"
94  "<p><b>%1</b></p>",
95  QString::fromLocal8Bit(error.asString()));
96  q->setError(KJob::UserDefinedError);
97  q->setErrorText(msg);
98  q->emitResult();
99 }
100 
101 void AutocryptHeadersJobPrivate::emitNotFoundError(const QByteArray &addr, const QByteArray &fingerprint)
102 {
103  Q_Q(AutocryptHeadersJob);
104  const QString msg = i18n(
105  "<p>An error occurred while trying to export "
106  "the key from the backend:</p>"
107  "<p><b>No valid key found for user %1 (%2)</b></p>",
108  QString::fromLatin1(addr),
109  QString::fromLatin1(fingerprint));
110  q->setError(KJob::UserDefinedError);
111  q->setErrorText(msg);
112  q->emitResult();
113 }
114 
115 void AutocryptHeadersJobPrivate::fillHeaderData(KMime::Headers::Generic *header, const QByteArray &addr, bool preferEncrypted, const QByteArray &keydata)
116 {
117  QByteArray parameters = "addr=" + addr + "; ";
118  if (preferEncrypted) {
119  parameters += "prefer-encrypt=mutual; ";
120  }
121  parameters += "keydata=\n ";
122  auto encoded = KCodecs::base64Encode(keydata).replace('\n', QByteArray());
123  const auto length = encoded.size();
124  const auto lineLength = 76;
125  auto start = 0;
126  auto column = 1;
127  while (start < length) {
128  const auto midLength = std::min(length - start, lineLength - column);
129  parameters += encoded.mid(start, midLength);
130  start += midLength;
131  column += midLength;
132  if (column >= lineLength) {
133  parameters += "\n ";
134  column = 1;
135  }
136  }
137  header->from7BitString(parameters);
138 }
139 
140 AutocryptHeadersJob::AutocryptHeadersJob(QObject *parent)
141  : ContentJobBase(*new AutocryptHeadersJobPrivate(this), parent)
142 {
143 }
144 
145 AutocryptHeadersJob::~AutocryptHeadersJob() = default;
146 
147 void AutocryptHeadersJob::setContent(KMime::Content *content)
148 {
149  Q_D(AutocryptHeadersJob);
150 
151  d->content = content;
152  if (content) {
153  d->content->assemble();
154  }
155 }
156 
157 void AutocryptHeadersJob::setSkeletonMessage(KMime::Message *skeletonMessage)
158 {
159  Q_D(AutocryptHeadersJob);
160 
161  d->skeletonMessage = skeletonMessage;
162 }
163 
164 void AutocryptHeadersJob::setGnupgHome(const QString &path)
165 {
166  Q_D(AutocryptHeadersJob);
167 
168  d->gnupgHome = path;
169 }
170 
171 void AutocryptHeadersJob::setSenderKey(const GpgME::Key &key)
172 {
173  Q_D(AutocryptHeadersJob);
174 
175  d->recipientKey = key;
176 }
177 
178 void AutocryptHeadersJob::setPreferEncrypted(bool preferEncrypted)
179 {
180  Q_D(AutocryptHeadersJob);
181 
182  d->preferEncrypted = preferEncrypted;
183 }
184 
185 void AutocryptHeadersJob::setGossipKeys(const std::vector<GpgME::Key> &gossipKeys)
186 {
187  Q_D(AutocryptHeadersJob);
188 
189  d->gossipKeys = gossipKeys;
190 }
191 
192 void AutocryptHeadersJob::process()
193 {
194  Q_D(AutocryptHeadersJob);
195  Q_ASSERT(d->resultContent == nullptr); // Not processed before.
196 
197  // if setContent hasn't been called, we assume that a subjob was added
198  // and we want to use that
199  if (!d->content) {
200  Q_ASSERT(d->subjobContents.size() == 1);
201  d->content = d->subjobContents.constFirst();
202  }
203 
204  auto job = QGpgME::openpgp()->publicKeyExportJob(false);
205  Q_ASSERT(job);
206 
207  if (!d->gnupgHome.isEmpty()) {
208  QGpgME::Job::context(job)->setEngineHomeDirectory(d->gnupgHome.toUtf8().constData());
209  }
210  if (!d->recipientKey.isNull() && !d->recipientKey.isInvalid()) {
211  connect(job, &QGpgME::ExportJob::result, this, [this, d](const GpgME::Error &error, const QByteArray &keydata) {
212  d->subJobs--;
213  if (AutocryptHeadersJob::error()) {
214  // When the job already has failed do nothing.
215  return;
216  }
217  if (error) {
218  d->emitGpgError(error);
219  return;
220  }
221  if (keydata.isEmpty()) {
222  d->emitNotFoundError(d->skeletonMessage->from()->addresses()[0], d->recipientKey.primaryFingerprint());
223  return;
224  }
225 
226  auto autocrypt = new KMime::Headers::Generic("Autocrypt");
227  d->fillHeaderData(autocrypt, d->skeletonMessage->from()->addresses()[0], d->preferEncrypted, keydata);
228 
229  d->skeletonMessage->setHeader(autocrypt);
230  d->skeletonMessage->assemble();
231 
232  d->finishOnLastSubJob();
233  });
234  d->subJobs++;
235  job->start(QStringList(QString::fromLatin1(d->recipientKey.primaryFingerprint())));
236 #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0
237  job->setExportFlags(GpgME::Context::ExportMinimal);
238 #endif
239  }
240 
241  const auto keys = d->gossipKeys;
242  for (const auto &key : keys) {
243  if (QByteArray(key.primaryFingerprint()) == QByteArray(d->recipientKey.primaryFingerprint())) {
244  continue;
245  }
246 
247  auto gossipJob = QGpgME::openpgp()->publicKeyExportJob(false);
248  Q_ASSERT(gossipJob);
249 
250  if (!d->gnupgHome.isEmpty()) {
251  QGpgME::Job::context(gossipJob)->setEngineHomeDirectory(d->gnupgHome.toUtf8().constData());
252  }
253 
254  connect(gossipJob, &QGpgME::ExportJob::result, this, [this, d, key](const GpgME::Error &error, const QByteArray &keydata) {
255  d->subJobs--;
256  if (AutocryptHeadersJob::error()) {
257  // When the job already has failed do nothing.
258  return;
259  }
260  if (error) {
261  d->emitGpgError(error);
262  return;
263  }
264  if (keydata.isEmpty()) {
265  d->emitNotFoundError(key.userID(0).email(), key.primaryFingerprint());
266  return;
267  }
268 
269  auto header = new KMime::Headers::Generic("Autocrypt-Gossip");
270  d->fillHeaderData(header, key.userID(0).email(), false, keydata);
271 
272  d->gossipHeaders.insert({QByteArray(key.primaryFingerprint()), header});
273 
274  d->finishOnLastSubJob();
275  });
276 
277  d->subJobs++;
278  gossipJob->start(QStringList(QString::fromLatin1(key.primaryFingerprint())));
279 #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0
280  gossipJob->setExportFlags(GpgME::Context::ExportMinimal);
281 #endif
282  }
283  if (d->subJobs == 0) {
284  d->resultContent = d->content;
285  emitResult();
286  }
287 }
void emitResult()
bool isEmpty() const const
virtual void from7BitString(const char *s, size_t len)
QString fromLocal8Bit(const char *str, int size)
KCODECS_EXPORT QByteArray base64Encode(const QByteArray &in)
QByteArray & replace(int pos, int len, const char *after)
QByteArray mid(int pos, int len) const const
QString i18n(const char *text, const TYPE &arg...)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
QString fromLatin1(const char *str, int size)
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
The ContentJobBase class.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Dec 4 2021 23:12:52 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.