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 }