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 }