Warning, file /pim/mailcommon/src/util/cryptoutils.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 * 0006 */ 0007 0008 #include "cryptoutils.h" 0009 #include "mailcommon_debug.h" 0010 0011 #include <QGpgME/DecryptJob> 0012 #include <QGpgME/Protocol> 0013 #include <QGpgME/VerifyOpaqueJob> 0014 0015 #include <gpgme++/context.h> 0016 #include <gpgme++/decryptionresult.h> 0017 #include <gpgme++/verificationresult.h> 0018 0019 using namespace MailCommon; 0020 0021 bool CryptoUtils::isInlinePGP(const KMime::Content *part) 0022 { 0023 // Find if the message body starts with --BEGIN PGP MESSAGE-- - we can't just 0024 // use contains(), because that would also qualify messages that mention the 0025 // string, but are not actually encrypted 0026 const auto body = part->body(); 0027 for (auto c = body.cbegin(), end = body.cend(); c != end; ++c) { 0028 if (!c) { // huh? 0029 return false; // empty body -> not encrypted 0030 } 0031 // Is it a white space? Let's check next one 0032 if (isspace(*c)) { 0033 continue; 0034 } 0035 0036 // First non-white space character in the body - if it's BEGIN PGP MESSAGE 0037 // then the message is encrypted, otherwise it's not. 0038 if (strncmp(c, "-----BEGIN PGP MESSAGE-----", sizeof("-----BEGIN PGP MESSAGE-----") - 1) == 0) { 0039 return true; 0040 } else { 0041 return false; 0042 } 0043 } 0044 0045 return false; 0046 } 0047 0048 bool CryptoUtils::isPGP(const KMime::Content *part, bool allowOctetStream) 0049 { 0050 const auto ct = static_cast<KMime::Headers::ContentType *>(part->headerByType("Content-Type")); 0051 return ct && (ct->isSubtype("pgp-encrypted") || ct->isSubtype("encrypted") || (allowOctetStream && ct->isMimeType("application/octet-stream"))); 0052 } 0053 0054 bool CryptoUtils::isSMIME(const KMime::Content *part) 0055 { 0056 const auto ct = static_cast<KMime::Headers::ContentType *>(part->headerByType("Content-Type")); 0057 return ct && (ct->isSubtype("pkcs7-mime") || ct->isSubtype("x-pkcs7-mime")); 0058 } 0059 0060 bool CryptoUtils::isEncrypted(const KMime::Message *msg) 0061 { 0062 // KMime::isEncrypted does not cover all cases - mostly only deals with 0063 // mime types. 0064 if (KMime::isEncrypted(const_cast<KMime::Message *>(msg))) { 0065 return true; 0066 } 0067 0068 return isInlinePGP(msg); 0069 } 0070 0071 KMime::Message::Ptr CryptoUtils::decryptMessage(const KMime::Message::Ptr &msg, bool &wasEncrypted) 0072 { 0073 GpgME::Protocol protoName = GpgME::UnknownProtocol; 0074 bool inlinePGP = false; 0075 bool multipart = false; 0076 if (msg->contentType(false) && msg->contentType(false)->isMimeType("multipart/encrypted")) { 0077 multipart = true; 0078 const auto subparts = msg->contents(); 0079 for (auto subpart : subparts) { 0080 if (isPGP(subpart, true)) { 0081 protoName = GpgME::OpenPGP; 0082 break; 0083 } else if (isSMIME(subpart)) { 0084 protoName = GpgME::CMS; 0085 break; 0086 } 0087 } 0088 } else { 0089 if (isPGP(msg.data())) { 0090 protoName = GpgME::OpenPGP; 0091 } else if (isSMIME(msg.data())) { 0092 protoName = GpgME::CMS; 0093 } else if (isInlinePGP(msg.data())) { 0094 protoName = GpgME::OpenPGP; 0095 inlinePGP = true; 0096 } 0097 } 0098 0099 if (protoName == GpgME::UnknownProtocol) { 0100 // Not encrypted, or we don't recognize the encryption 0101 wasEncrypted = false; 0102 return {}; 0103 } 0104 0105 const auto proto = (protoName == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); 0106 0107 wasEncrypted = true; 0108 QByteArray outData; 0109 auto inData = multipart ? msg->encodedContent() : msg->decodedContent(); // decodedContent in fact returns decoded body 0110 auto decrypt = proto->decryptJob(); 0111 if (inlinePGP) { 0112 auto ctx = QGpgME::Job::context(decrypt); 0113 ctx->setDecryptionFlags(GpgME::Context::DecryptUnwrap); 0114 } 0115 auto result = decrypt->exec(inData, outData); 0116 if (result.error()) { 0117 // unknown key, invalid algo, or general error 0118 qCWarning(MAILCOMMON_LOG) << "Failed to decrypt:" << result.error().asString(); 0119 return {}; 0120 } 0121 0122 if (inlinePGP) { 0123 inData = outData; 0124 auto verify = proto->verifyOpaqueJob(true); 0125 auto result = verify->exec(inData, outData); 0126 if (result.error()) { 0127 qCWarning(MAILCOMMON_LOG) << "Failed to verify:" << result.error().asString(); 0128 return {}; 0129 } 0130 } 0131 0132 KMime::Content decCt; 0133 if (inlinePGP) { 0134 decCt.setBody(KMime::CRLFtoLF(outData)); 0135 } else { 0136 decCt.setContent(KMime::CRLFtoLF(outData)); 0137 } 0138 decCt.parse(); 0139 decCt.assemble(); 0140 0141 return assembleMessage(msg, &decCt); 0142 } 0143 0144 void CryptoUtils::copyHeader(const KMime::Headers::Base *header, KMime::Message::Ptr msg) 0145 { 0146 auto newHdr = KMime::Headers::createHeader(header->type()); 0147 if (!newHdr) { 0148 newHdr = new KMime::Headers::Generic(header->type()); 0149 } 0150 newHdr->from7BitString(header->as7BitString(false)); 0151 msg->appendHeader(newHdr); 0152 } 0153 0154 bool CryptoUtils::isContentHeader(const KMime::Headers::Base *header) 0155 { 0156 return header->is("Content-Type") || header->is("Content-Transfer-Encoding") || header->is("Content-Disposition"); 0157 } 0158 0159 KMime::Message::Ptr CryptoUtils::assembleMessage(const KMime::Message::Ptr &orig, const KMime::Content *newContent) 0160 { 0161 auto out = KMime::Message::Ptr::create(); 0162 // Use the new content as message content 0163 out->setBody(const_cast<KMime::Content *>(newContent)->encodedBody()); 0164 out->parse(); 0165 0166 // remove default explicit content headers added by KMime::Content::parse() 0167 QList<KMime::Headers::Base *> headers = out->headers(); 0168 for (const auto hdr : std::as_const(headers)) { 0169 if (isContentHeader(hdr)) { 0170 out->removeHeader(hdr->type()); 0171 } 0172 } 0173 0174 // Copy over headers from the original message, except for CT, CTE and CD 0175 // headers, we want to preserve those from the new content 0176 headers = orig->headers(); 0177 for (const auto hdr : std::as_const(headers)) { 0178 if (isContentHeader(hdr)) { 0179 continue; 0180 } 0181 0182 copyHeader(hdr, out); 0183 } 0184 0185 // Overwrite some headers by those provided by the new content 0186 headers = newContent->headers(); 0187 for (const auto hdr : std::as_const(headers)) { 0188 if (isContentHeader(hdr)) { 0189 copyHeader(hdr, out); 0190 } 0191 } 0192 0193 out->assemble(); 0194 out->parse(); 0195 0196 return out; 0197 }