Mailcommon

cryptoutils.cpp
1/*
2 * SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org>
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
19using namespace MailCommon;
20
21bool 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
48bool 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
54bool 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
60bool 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
71KMime::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
144void 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
154bool 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
159KMime::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 QList<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 const auto newContentHeaders = newContent->headers();
187 for (const auto hdr : newContentHeaders) {
188 if (isContentHeader(hdr)) {
189 copyHeader(hdr, out);
190 }
191 }
192
193 out->assemble();
194 out->parse();
195
196 return out;
197}
Headers::Base * headerByType(QByteArrayView type) const
QList< Headers::Base * > headers()
QByteArray body() const
void setContent(const QByteArray &s)
void setBody(const QByteArray &body)
virtual const char * type() const
virtual QByteArray as7BitString(bool withHeaderType=true) const=0
bool is(QByteArrayView t) const
The filter dialog.
QSharedPointer< Message > create(Args &&... args)
T * data() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:02:04 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.