Messagelib

signjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, [email protected]
3  SPDX-FileCopyrightText: 2009 Leo Franchi <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "job/signjob.h"
9 
10 #include "contentjobbase_p.h"
11 #include "job/protectedheadersjob.h"
12 #include "utils/util_p.h"
13 
14 #include <QGpgME/Protocol>
15 #include <QGpgME/SignJob>
16 #include <QVector>
17 
18 #include "messagecomposer_debug.h"
19 #include <KMime/Content>
20 #include <KMime/Headers>
21 #include <KMime/KMimeMessage>
22 
23 #include <gpgme++/encryptionresult.h>
24 #include <gpgme++/global.h>
25 #include <gpgme++/signingresult.h>
26 #include <sstream>
27 
28 using namespace MessageComposer;
29 
30 class MessageComposer::SignJobPrivate : public ContentJobBasePrivate
31 {
32 public:
33  SignJobPrivate(SignJob *qq)
34  : ContentJobBasePrivate(qq)
35  {
36  }
37 
38  KMime::Content *content = nullptr;
39  KMime::Message *skeletonMessage = nullptr;
40  std::vector<GpgME::Key> signers;
41  Kleo::CryptoMessageFormat format;
42 
43  bool protectedHeaders = true;
44 
45  // copied from messagecomposer.cpp
46  Q_REQUIRED_RESULT bool binaryHint(Kleo::CryptoMessageFormat f)
47  {
48  switch (f) {
49  case Kleo::SMIMEFormat:
50  case Kleo::SMIMEOpaqueFormat:
51  return true;
52  default:
53  case Kleo::OpenPGPMIMEFormat:
54  case Kleo::InlineOpenPGPFormat:
55  return false;
56  }
57  }
58 
59  Q_REQUIRED_RESULT GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f)
60  {
61  switch (f) {
62  case Kleo::SMIMEOpaqueFormat:
63  return GpgME::NormalSignatureMode;
64  case Kleo::InlineOpenPGPFormat:
65  return GpgME::Clearsigned;
66  default:
67  case Kleo::SMIMEFormat:
68  case Kleo::OpenPGPMIMEFormat:
69  return GpgME::Detached;
70  }
71  }
72 
73  Q_DECLARE_PUBLIC(SignJob)
74 };
75 
76 SignJob::SignJob(QObject *parent)
77  : ContentJobBase(*new SignJobPrivate(this), parent)
78 {
79 }
80 
81 SignJob::~SignJob() = default;
82 
83 void SignJob::setContent(KMime::Content *content)
84 {
85  Q_D(SignJob);
86 
87  d->content = content;
88 }
89 
90 void SignJob::setCryptoMessageFormat(Kleo::CryptoMessageFormat format)
91 {
92  Q_D(SignJob);
93 
94  // There *must* be a concrete format set at this point.
95  Q_ASSERT(format == Kleo::OpenPGPMIMEFormat || format == Kleo::InlineOpenPGPFormat || format == Kleo::SMIMEFormat || format == Kleo::SMIMEOpaqueFormat);
96  d->format = format;
97 }
98 
99 void SignJob::setSigningKeys(const std::vector<GpgME::Key> &signers)
100 {
101  Q_D(SignJob);
102 
103  d->signers = signers;
104 }
105 
106 void SignJob::setSkeletonMessage(KMime::Message *skeletonMessage)
107 {
108  Q_D(SignJob);
109 
110  d->skeletonMessage = skeletonMessage;
111 }
112 
113 void SignJob::setProtectedHeaders(bool protectedHeaders)
114 {
115  Q_D(SignJob);
116 
117  d->protectedHeaders = protectedHeaders;
118 }
119 
120 KMime::Content *SignJob::origContent()
121 {
122  Q_D(SignJob);
123 
124  return d->content;
125 }
126 
127 void SignJob::doStart()
128 {
129  Q_D(SignJob);
130  Q_ASSERT(d->resultContent == nullptr); // Not processed before.
131 
132  if (d->protectedHeaders && d->skeletonMessage && d->format & Kleo::OpenPGPMIMEFormat) {
133  auto pJob = new ProtectedHeadersJob;
134  pJob->setContent(d->content);
135  pJob->setSkeletonMessage(d->skeletonMessage);
136  pJob->setObvoscate(false);
137  QObject::connect(pJob, &ProtectedHeadersJob::finished, this, [d, pJob](KJob *job) {
138  if (job->error()) {
139  return;
140  }
141  d->content = pJob->content();
142  });
143  appendSubjob(pJob);
144  }
145 
147 }
148 
149 void SignJob::slotResult(KJob *job)
150 {
151  if (error() || job->error()) {
152  ContentJobBase::slotResult(job);
153  return;
154  }
155  if (subjobs().size() == 2) {
156  auto pjob = static_cast<ProtectedHeadersJob *>(subjobs().last());
157  if (pjob) {
158  auto cjob = qobject_cast<ContentJobBase *>(job);
159  Q_ASSERT(cjob);
160  pjob->setContent(cjob->content());
161  }
162  }
163 
164  ContentJobBase::slotResult(job);
165 }
166 
167 void SignJob::process()
168 {
169  Q_D(SignJob);
170  Q_ASSERT(d->resultContent == nullptr); // Not processed before.
171 
172  // if setContent hasn't been called, we assume that a subjob was added
173  // and we want to use that
174  if (!d->content) {
175  Q_ASSERT(d->subjobContents.size() == 1);
176  d->content = d->subjobContents.constFirst();
177  }
178 
179  // d->resultContent = new KMime::Content;
180 
181  const QGpgME::Protocol *proto = nullptr;
182  if (d->format & Kleo::AnyOpenPGP) {
183  proto = QGpgME::openpgp();
184  } else if (d->format & Kleo::AnySMIME) {
185  proto = QGpgME::smime();
186  }
187 
188  Q_ASSERT(proto);
189 
190  qCDebug(MESSAGECOMPOSER_LOG) << "creating signJob from:" << proto->name() << proto->displayName();
191  // for now just do the main recipients
192  QByteArray signature;
193 
194  d->content->assemble();
195 
196  // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
197  // according to RfC 2633, 3.1.1 Canonicalization
199  if (d->format & Kleo::InlineOpenPGPFormat) {
200  content = d->content->body();
201  } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
202  // replace "From " and "--" at the beginning of lines
203  // with encoded versions according to RfC 3156, 3
204  // Note: If any line begins with the string "From ", it is strongly
205  // suggested that either the Quoted-Printable or Base64 MIME encoding
206  // be applied.
207  const auto encoding = d->content->contentTransferEncoding()->encoding();
208  if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit) && !d->content->contentType(false)) {
209  QByteArray body = d->content->encodedBody();
210  bool changed = false;
211  QVector<QByteArray> search;
212  search.reserve(3);
213  QVector<QByteArray> replacements;
214  replacements.reserve(3);
215  search << "From "
216  << "from "
217  << "-";
218  replacements << "From=20"
219  << "from=20"
220  << "=2D";
221 
222  if (d->content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
223  for (int i = 0, total = search.size(); i < total; ++i) {
224  const auto pos = body.indexOf(search[i]);
225  if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
226  changed = true;
227  break;
228  }
229  }
230  if (changed) {
231  d->content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
232  d->content->assemble();
233  body = d->content->encodedBody();
234  }
235  }
236 
237  for (int i = 0; i < search.size(); ++i) {
238  const auto pos = body.indexOf(search[i]);
239  if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
240  changed = true;
241  body.replace(pos, search[i].size(), replacements[i]);
242  }
243  }
244 
245  if (changed) {
246  qCDebug(MESSAGECOMPOSER_LOG) << "Content changed";
247  d->content->setBody(body);
248  d->content->contentTransferEncoding()->setDecoded(false);
249  }
250  }
251 
252  content = KMime::LFtoCRLF(d->content->encodedContent());
253  } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
254  content = d->content->encodedContent();
255  }
256 
257  QGpgME::SignJob *job(proto->signJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat));
259  job,
260  &QGpgME::SignJob::result,
261  this,
262  [this, d](const GpgME::SigningResult &result, const QByteArray &signature, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) {
263  Q_UNUSED(auditLogAsHtml)
264  Q_UNUSED(auditLogError)
265  if (result.error().code()) {
266  qCDebug(MESSAGECOMPOSER_LOG) << "signing failed:" << result.error().asString();
267  // job->showErrorDialog( globalPart()->parentWidgetForGui() );
268  setError(result.error().code());
269  setErrorText(QString::fromLocal8Bit(result.error().asString()));
270  emitResult();
271  return;
272  }
273 
274  QByteArray signatureHashAlgo = result.createdSignature(0).hashAlgorithmAsString();
275  d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, signature, d->format, true, signatureHashAlgo);
276 
277  emitResult();
278  });
279 
280  const auto error = job->start(d->signers, content, d->signingMode(d->format));
281  if (error.code()) {
282  job->deleteLater();
283  setError(error.code());
284  setErrorText(QString::fromLocal8Bit(error.asString()));
285  emitResult();
286  }
287 }
void emitResult()
char at(int i) const const
void setError(int errorCode)
KMime::Content * content() const
Get the resulting KMime::Content that the ContentJobBase has generated.
virtual void doStart()
Reimplement to do additional stuff before processing children, such as adding more subjobs...
void setErrorText(const QString &errorText)
int indexOf(char ch, int from) const const
QString fromLocal8Bit(const char *str, int size)
void finished(KJob *job)
Copies headers from skeleton message to content.
QByteArray & replace(int pos, int len, const char *after)
void deleteLater()
void reserve(int size)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
bool appendSubjob(ContentJobBase *job)
This is meant to be used instead of KCompositeJob::addSubjob(), making it possible to add subjobs fro...
virtual Q_SCRIPTABLE void start()=0
const QList< KJob * > & subjobs() const
Signs the contents of a message.
Definition: signjob.h:31
void result(KJob *job)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
int size() const const
int error() const
The ContentJobBase class.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Dec 2 2021 23:06:09 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.