File indexing completed on 2024-12-22 05:05:19

0001 // SPDX-FileCopyrightText: 2001,2002 the KPGP authors
0002 // SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
0003 // SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org>
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 #include "cryptohelper.h"
0007 
0008 #include "mimetreeparser_core_debug.h"
0009 
0010 #include <QGpgME/DecryptJob>
0011 #include <QGpgME/Protocol>
0012 #include <QGpgME/VerifyOpaqueJob>
0013 
0014 #include <gpgme++/context.h>
0015 #include <gpgme++/decryptionresult.h>
0016 #include <gpgme++/verificationresult.h>
0017 
0018 using namespace MimeTreeParser;
0019 
0020 PGPBlockType Block::determineType() const
0021 {
0022     const QByteArray data = text();
0023     if (data.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----")) {
0024         return NoPgpBlock;
0025     } else if (data.startsWith("-----BEGIN PGP SIGNED")) {
0026         return ClearsignedBlock;
0027     } else if (data.startsWith("-----BEGIN PGP SIGNATURE")) {
0028         return SignatureBlock;
0029     } else if (data.startsWith("-----BEGIN PGP PUBLIC")) {
0030         return PublicKeyBlock;
0031     } else if (data.startsWith("-----BEGIN PGP PRIVATE") || data.startsWith("-----BEGIN PGP SECRET")) {
0032         return PrivateKeyBlock;
0033     } else if (data.startsWith("-----BEGIN PGP MESSAGE")) {
0034         if (data.startsWith("-----BEGIN PGP MESSAGE PART")) {
0035             return MultiPgpMessageBlock;
0036         } else {
0037             return PgpMessageBlock;
0038         }
0039     } else if (data.startsWith("-----BEGIN PGP ARMORED FILE")) {
0040         return PgpMessageBlock;
0041     } else if (data.startsWith("-----BEGIN PGP ")) {
0042         return UnknownBlock;
0043     } else {
0044         return NoPgpBlock;
0045     }
0046 }
0047 
0048 QList<Block> MimeTreeParser::prepareMessageForDecryption(const QByteArray &msg)
0049 {
0050     PGPBlockType pgpBlock = NoPgpBlock;
0051     QList<Block> blocks;
0052     int start = -1; // start of the current PGP block
0053     int lastEnd = -1; // end of the last PGP block
0054     const int length = msg.length();
0055 
0056     if (msg.isEmpty()) {
0057         return blocks;
0058     }
0059     if (msg.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----")) {
0060         return blocks;
0061     }
0062 
0063     if (msg.startsWith("-----BEGIN PGP ")) {
0064         start = 0;
0065     } else {
0066         start = msg.indexOf("\n-----BEGIN PGP ") + 1;
0067         if (start == 0) {
0068             blocks.append(Block(msg, NoPgpBlock));
0069             return blocks;
0070         }
0071     }
0072 
0073     while (start != -1) {
0074         int nextEnd;
0075         int nextStart;
0076 
0077         // is the PGP block a clearsigned block?
0078         if (!strncmp(msg.constData() + start + 15, "SIGNED", 6)) {
0079             pgpBlock = ClearsignedBlock;
0080         } else {
0081             pgpBlock = UnknownBlock;
0082         }
0083 
0084         nextEnd = msg.indexOf("\n-----END PGP ", start + 15);
0085         nextStart = msg.indexOf("\n-----BEGIN PGP ", start + 15);
0086 
0087         if (nextEnd == -1) { // Missing END PGP line
0088             if (lastEnd != -1) {
0089                 blocks.append(Block(msg.mid(lastEnd + 1), UnknownBlock));
0090             } else {
0091                 blocks.append(Block(msg.mid(start), UnknownBlock));
0092             }
0093             break;
0094         }
0095 
0096         if ((nextStart == -1) || (nextEnd < nextStart) || (pgpBlock == ClearsignedBlock)) {
0097             // most likely we found a PGP block (but we don't check if it's valid)
0098 
0099             // store the preceding non-PGP block
0100             if (start - lastEnd - 1 > 0) {
0101                 blocks.append(Block(msg.mid(lastEnd + 1, start - lastEnd - 1), NoPgpBlock));
0102             }
0103 
0104             lastEnd = msg.indexOf("\n", nextEnd + 14);
0105             if (lastEnd == -1) {
0106                 if (start < length) {
0107                     blocks.append(Block(msg.mid(start)));
0108                 }
0109                 break;
0110             } else {
0111                 blocks.append(Block(msg.mid(start, lastEnd + 1 - start)));
0112                 if ((nextStart != -1) && (nextEnd > nextStart)) {
0113                     nextStart = msg.indexOf("\n-----BEGIN PGP ", lastEnd + 1);
0114                 }
0115             }
0116         }
0117 
0118         start = nextStart;
0119 
0120         if (start == -1) {
0121             if (lastEnd + 1 < length) {
0122                 // rest of mail is no PGP Block
0123                 blocks.append(Block(msg.mid(lastEnd + 1), NoPgpBlock));
0124             }
0125             break;
0126         } else {
0127             start++; // move start behind the '\n'
0128         }
0129     }
0130 
0131     return blocks;
0132 }
0133 
0134 Block::Block() = default;
0135 
0136 Block::Block(const QByteArray &m)
0137     : msg(m)
0138 {
0139     mType = determineType();
0140 }
0141 
0142 Block::Block(const QByteArray &m, PGPBlockType t)
0143     : msg(m)
0144     , mType(t)
0145 {
0146 }
0147 
0148 QByteArray MimeTreeParser::Block::text() const
0149 {
0150     return msg;
0151 }
0152 
0153 PGPBlockType Block::type() const
0154 {
0155     return mType;
0156 }
0157 
0158 namespace
0159 {
0160 
0161 bool isPGP(const KMime::Content *part, bool allowOctetStream = false)
0162 {
0163     const auto ct = static_cast<KMime::Headers::ContentType *>(part->headerByType("Content-Type"));
0164     return ct && (ct->isSubtype("pgp-encrypted") || ct->isSubtype("encrypted") || (allowOctetStream && ct->isMimeType("application/octet-stream")));
0165 }
0166 
0167 bool isSMIME(const KMime::Content *part)
0168 {
0169     const auto ct = static_cast<KMime::Headers::ContentType *>(part->headerByType("Content-Type"));
0170     return ct && (ct->isSubtype("pkcs7-mime") || ct->isSubtype("x-pkcs7-mime"));
0171 }
0172 
0173 void copyHeader(const KMime::Headers::Base *header, KMime::Message::Ptr msg)
0174 {
0175     auto newHdr = KMime::Headers::createHeader(header->type());
0176     if (!newHdr) {
0177         newHdr = new KMime::Headers::Generic(header->type());
0178     }
0179     newHdr->from7BitString(header->as7BitString(false));
0180     msg->appendHeader(newHdr);
0181 }
0182 
0183 bool isContentHeader(const KMime::Headers::Base *header)
0184 {
0185     return header->is("Content-Type") || header->is("Content-Transfer-Encoding") || header->is("Content-Disposition");
0186 }
0187 
0188 KMime::Message::Ptr assembleMessage(const KMime::Message::Ptr &orig, const KMime::Content *newContent)
0189 {
0190     auto out = KMime::Message::Ptr::create();
0191     // Use the new content as message content
0192     out->setBody(const_cast<KMime::Content *>(newContent)->encodedBody());
0193     out->parse();
0194 
0195     // remove default explicit content headers added by KMime::Content::parse()
0196     QList<KMime::Headers::Base *> headers = out->headers();
0197     for (const auto hdr : std::as_const(headers)) {
0198         if (isContentHeader(hdr)) {
0199             out->removeHeader(hdr->type());
0200         }
0201     }
0202 
0203     // Copy over headers from the original message, except for CT, CTE and CD
0204     // headers, we want to preserve those from the new content
0205     headers = orig->headers();
0206     for (const auto hdr : std::as_const(headers)) {
0207         if (isContentHeader(hdr)) {
0208             continue;
0209         }
0210 
0211         copyHeader(hdr, out);
0212     }
0213 
0214     // Overwrite some headers by those provided by the new content
0215     headers = newContent->headers();
0216     for (const auto hdr : std::as_const(headers)) {
0217         if (isContentHeader(hdr)) {
0218             copyHeader(hdr, out);
0219         }
0220     }
0221 
0222     out->assemble();
0223     out->parse();
0224 
0225     return out;
0226 }
0227 }
0228 
0229 KMime::Message::Ptr CryptoUtils::decryptMessage(const KMime::Message::Ptr &msg, bool &wasEncrypted)
0230 {
0231     GpgME::Protocol protoName = GpgME::UnknownProtocol;
0232     bool multipart = false;
0233     if (msg->contentType(false) && msg->contentType(false)->isMimeType("multipart/encrypted")) {
0234         multipart = true;
0235         const auto subparts = msg->contents();
0236         for (auto subpart : subparts) {
0237             if (isPGP(subpart, true)) {
0238                 protoName = GpgME::OpenPGP;
0239                 break;
0240             } else if (isSMIME(subpart)) {
0241                 protoName = GpgME::CMS;
0242                 break;
0243             }
0244         }
0245     } else {
0246         if (isPGP(msg.data())) {
0247             protoName = GpgME::OpenPGP;
0248         } else if (isSMIME(msg.data())) {
0249             protoName = GpgME::CMS;
0250         } else {
0251             const auto blocks = prepareMessageForDecryption(msg->body());
0252             QByteArray content;
0253             for (const auto &block : blocks) {
0254                 if (block.type() == PgpMessageBlock) {
0255                     const auto proto = QGpgME::openpgp();
0256                     wasEncrypted = true;
0257                     QByteArray outData;
0258                     auto inData = block.text();
0259                     auto decrypt = proto->decryptJob();
0260                     auto ctx = QGpgME::Job::context(decrypt);
0261                     ctx->setDecryptionFlags(GpgME::Context::DecryptUnwrap);
0262                     auto result = decrypt->exec(inData, outData);
0263                     if (result.error()) {
0264                         // unknown key, invalid algo, or general error
0265                         qCWarning(MIMETREEPARSER_CORE_LOG) << "Failed to decrypt:" << result.error().asString();
0266                         return {};
0267                     }
0268 
0269                     inData = outData;
0270                     auto verify = proto->verifyOpaqueJob(true);
0271                     auto resultVerify = verify->exec(inData, outData);
0272                     if (resultVerify.error()) {
0273                         qCWarning(MIMETREEPARSER_CORE_LOG) << "Failed to verify:" << resultVerify.error().asString();
0274                         return {};
0275                     }
0276 
0277                     content += KMime::CRLFtoLF(outData);
0278                 } else if (block.type() == NoPgpBlock) {
0279                     content += block.text();
0280                 }
0281             }
0282 
0283             KMime::Content decCt;
0284             decCt.setBody(content);
0285             decCt.parse();
0286             decCt.assemble();
0287 
0288             return assembleMessage(msg, &decCt);
0289         }
0290     }
0291 
0292     if (protoName == GpgME::UnknownProtocol) {
0293         // Not encrypted, or we don't recognize the encryption
0294         wasEncrypted = false;
0295         return {};
0296     }
0297 
0298     const auto proto = (protoName == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
0299 
0300     wasEncrypted = true;
0301     QByteArray outData;
0302     auto inData = multipart ? msg->encodedContent() : msg->decodedContent(); // decodedContent in fact returns decoded body
0303     auto decrypt = proto->decryptJob();
0304     auto result = decrypt->exec(inData, outData);
0305     if (result.error()) {
0306         // unknown key, invalid algo, or general error
0307         qCWarning(MIMETREEPARSER_CORE_LOG) << "Failed to decrypt:" << result.error().asString();
0308         return {};
0309     }
0310 
0311     KMime::Content decCt;
0312     decCt.setContent(KMime::CRLFtoLF(outData));
0313     decCt.parse();
0314     decCt.assemble();
0315 
0316     return assembleMessage(msg, &decCt);
0317 }