Mailcommon

cryptoutils.cpp
1 /*
2  * SPDX-FileCopyrightText: 2017 Daniel Vr├ítil <[email protected]>
3  *
4  * SPDX-License-Identifier: GPL-2.0-or-later
5  *
6  */
7 
8 #include "cryptoutils.h"
9 #include "mailcommon_debug.h"
10 
11 #include <QGpgME/DecryptJob>
12 #include <QGpgME/Protocol>
13 #include <QGpgME/VerifyOpaqueJob>
14 
15 #include <gpgme++/context.h>
16 #include <gpgme++/decryptionresult.h>
17 #include <gpgme++/verificationresult.h>
18 
19 using namespace MailCommon;
20 
21 bool CryptoUtils::isInlinePGP(const KMime::Content *part)
22 {
23  // Find if the message body starts with --BEGIN PGP MESSAGE-- - we can't just
24  // use contains(), because that would also qualify messages that mention the
25  // string, but are not actually encrypted
26  const auto body = part->body();
27  for (auto c = body.cbegin(), end = body.cend(); c != end; ++c) {
28  if (!c) { // huh?
29  return false; // empty body -> not encrypted
30  }
31  // Is it a white space? Let's check next one
32  if (isspace(*c)) {
33  continue;
34  }
35 
36  // First non-white space character in the body - if it's BEGIN PGP MESSAGE
37  // then the message is encrypted, otherwise it's not.
38  if (strncmp(c, "-----BEGIN PGP MESSAGE-----", sizeof("-----BEGIN PGP MESSAGE-----") - 1) == 0) {
39  return true;
40  } else {
41  return false;
42  }
43  }
44 
45  return false;
46 }
47 
48 bool CryptoUtils::isPGP(const KMime::Content *part, bool allowOctetStream)
49 {
50  const auto ct = static_cast<KMime::Headers::ContentType *>(part->headerByType("Content-Type"));
51  return ct && (ct->isSubtype("pgp-encrypted") || ct->isSubtype("encrypted") || (allowOctetStream && ct->isMimeType("application/octet-stream")));
52 }
53 
54 bool CryptoUtils::isSMIME(const KMime::Content *part)
55 {
56  const auto ct = static_cast<KMime::Headers::ContentType *>(part->headerByType("Content-Type"));
57  return ct && (ct->isSubtype("pkcs7-mime") || ct->isSubtype("x-pkcs7-mime"));
58 }
59 
60 bool CryptoUtils::isEncrypted(const KMime::Message *msg)
61 {
62  // KMime::isEncrypted does not cover all cases - mostly only deals with
63  // mime types.
64  if (KMime::isEncrypted(const_cast<KMime::Message *>(msg))) {
65  return true;
66  }
67 
68  return isInlinePGP(msg);
69 }
70 
71 KMime::Message::Ptr CryptoUtils::decryptMessage(const KMime::Message::Ptr &msg, bool &wasEncrypted)
72 {
73  GpgME::Protocol protoName = GpgME::UnknownProtocol;
74  bool inlinePGP = false;
75  bool multipart = false;
76  if (msg->contentType(false) && msg->contentType(false)->isMimeType("multipart/encrypted")) {
77  multipart = true;
78  const auto subparts = msg->contents();
79  for (auto subpart : subparts) {
80  if (isPGP(subpart, true)) {
81  protoName = GpgME::OpenPGP;
82  break;
83  } else if (isSMIME(subpart)) {
84  protoName = GpgME::CMS;
85  break;
86  }
87  }
88  } else {
89  if (isPGP(msg.data())) {
90  protoName = GpgME::OpenPGP;
91  } else if (isSMIME(msg.data())) {
92  protoName = GpgME::CMS;
93  } else if (isInlinePGP(msg.data())) {
94  protoName = GpgME::OpenPGP;
95  inlinePGP = true;
96  }
97  }
98 
99  if (protoName == GpgME::UnknownProtocol) {
100  // Not encrypted, or we don't recognize the encryption
101  wasEncrypted = false;
102  return {};
103  }
104 
105  const auto proto = (protoName == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
106 
107  wasEncrypted = true;
108  QByteArray outData;
109  auto inData = multipart ? msg->encodedContent() : msg->decodedContent(); // decodedContent in fact returns decoded body
110  auto decrypt = proto->decryptJob();
111  if (inlinePGP) {
112  auto ctx = QGpgME::Job::context(decrypt);
113  ctx->setDecryptionFlags(GpgME::Context::DecryptUnwrap);
114  }
115  auto result = decrypt->exec(inData, outData);
116  if (result.error()) {
117  // unknown key, invalid algo, or general error
118  qCWarning(MAILCOMMON_LOG) << "Failed to decrypt:" << result.error().asString();
119  return {};
120  }
121 
122  if (inlinePGP) {
123  inData = outData;
124  auto verify = proto->verifyOpaqueJob(true);
125  auto result = verify->exec(inData, outData);
126  if (result.error()) {
127  qCWarning(MAILCOMMON_LOG) << "Failed to verify:" << result.error().asString();
128  return {};
129  }
130  }
131 
132  KMime::Content decCt;
133  if (inlinePGP) {
134  decCt.setBody(KMime::CRLFtoLF(outData));
135  } else {
136  decCt.setContent(KMime::CRLFtoLF(outData));
137  }
138  decCt.parse();
139  decCt.assemble();
140 
141  return assembleMessage(msg, &decCt);
142 }
143 
144 void CryptoUtils::copyHeader(const KMime::Headers::Base *header, KMime::Message::Ptr msg)
145 {
146  auto newHdr = KMime::Headers::createHeader(header->type());
147  if (!newHdr) {
148  newHdr = new KMime::Headers::Generic(header->type());
149  }
150  newHdr->from7BitString(header->as7BitString(false));
151  msg->appendHeader(newHdr);
152 }
153 
154 bool CryptoUtils::isContentHeader(const KMime::Headers::Base *header)
155 {
156  return header->is("Content-Type") || header->is("Content-Transfer-Encoding") || header->is("Content-Disposition");
157 }
158 
159 KMime::Message::Ptr CryptoUtils::assembleMessage(const KMime::Message::Ptr &orig, const KMime::Content *newContent)
160 {
161  auto out = KMime::Message::Ptr::create();
162  // Use the new content as message content
163  out->setBody(const_cast<KMime::Content *>(newContent)->encodedBody());
164  out->parse();
165 
166  // remove default explicit content headers added by KMime::Content::parse()
167  QVector<KMime::Headers::Base *> headers = out->headers();
168  for (const auto hdr : std::as_const(headers)) {
169  if (isContentHeader(hdr)) {
170  out->removeHeader(hdr->type());
171  }
172  }
173 
174  // Copy over headers from the original message, except for CT, CTE and CD
175  // headers, we want to preserve those from the new content
176  headers = orig->headers();
177  for (const auto hdr : std::as_const(headers)) {
178  if (isContentHeader(hdr)) {
179  continue;
180  }
181 
182  copyHeader(hdr, out);
183  }
184 
185  // Overwrite some headers by those provided by the new content
186  headers = newContent->headers();
187  for (const auto hdr : std::as_const(headers)) {
188  if (isContentHeader(hdr)) {
189  copyHeader(hdr, out);
190  }
191  }
192 
193  out->assemble();
194  out->parse();
195 
196  return out;
197 }
QByteArray body() const
T * data() const const
bool is(const char *t) const
QSharedPointer< Message > create(Args &&... args)
void setBody(const QByteArray &body)
QVector< Headers::Base * > headers() const
Headers::Base * headerByType(const char *type) const
void setContent(const QByteArray &s)
virtual QByteArray as7BitString(bool withHeaderType=true) const=0
The filter dialog.
virtual const char * type() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Oct 1 2022 04:00:52 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.