File indexing completed on 2024-05-12 05:28:16

0001 // SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0002 // SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
0003 // SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
0004 // SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
0005 // SPDX-License-Identifier: LGPL-2.0-or-later
0006 //
0007 #include "mailcrypto.h"
0008 
0009 #include <QDebug>
0010 
0011 #include <future>
0012 #include <utility>
0013 
0014 using namespace MailCrypto;
0015 using namespace Crypto;
0016 
0017 #ifdef _WIN32
0018 Expected<Error, std::unique_ptr<KMime::Content>>
0019 MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<Key> &signingKeys, const std::vector<Key> &encryptionKeys)
0020 {
0021     Q_UNUSED(content)
0022     Q_UNUSED(signingKeys)
0023     Q_UNUSED(encryptionKeys)
0024     return makeUnexpected(Error{});
0025 }
0026 #else
0027 static QByteArray canonicalizeContent(KMime::Content *content)
0028 {
0029     // if (d->format & Kleo::InlineOpenPGPFormat) {
0030     //     return d->content->body();
0031     // } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
0032 
0033     // replace "From " and "--" at the beginning of lines
0034     // with encoded versions according to RfC 3156, 3
0035     // Note: If any line begins with the string "From ", it is strongly
0036     //   suggested that either the Quoted-Printable or Base64 MIME encoding
0037     //   be applied.
0038     const auto encoding = content->contentTransferEncoding()->encoding();
0039     if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit) && !content->contentType(false)) {
0040         QByteArray body = content->encodedBody();
0041         bool changed = false;
0042         QList<QByteArray> search;
0043         QList<QByteArray> replacements;
0044 
0045         search << "From "
0046                << "from "
0047                << "-";
0048         replacements << "From=20"
0049                      << "from=20"
0050                      << "=2D";
0051 
0052         if (content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
0053             for (int i = 0; i < search.size(); ++i) {
0054                 const auto pos = body.indexOf(search[i]);
0055                 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
0056                     changed = true;
0057                     break;
0058                 }
0059             }
0060             if (changed) {
0061                 content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
0062                 content->assemble();
0063                 body = content->encodedBody();
0064             }
0065         }
0066 
0067         for (int i = 0; i < search.size(); ++i) {
0068             const auto pos = body.indexOf(search[i]);
0069             if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
0070                 changed = true;
0071                 body.replace(pos, search[i].size(), replacements[i]);
0072             }
0073         }
0074 
0075         if (changed) {
0076             qDebug() << "Content changed";
0077             content->setBody(body);
0078             content->contentTransferEncoding()->setDecoded(false);
0079         }
0080     }
0081 
0082     return KMime::LFtoCRLF(content->encodedContent());
0083     // } else {                    // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
0084     //     return content->encodedContent();
0085     // }
0086 }
0087 
0088 /**
0089  * Create a message part like this (according to RFC 3156 Section 4):
0090  *
0091  * - multipart/encrypted
0092  *   - application/pgp-encrypted (version information)
0093  *   - application/octet-stream (given encrypted data)
0094  *
0095  * The encrypted data can be generated by the `encrypt` or `signAndEncrypt` functions.
0096  */
0097 std::unique_ptr<KMime::Content> createEncryptedPart(QByteArray encryptedData)
0098 {
0099     auto result = std::unique_ptr<KMime::Content>(new KMime::Content);
0100 
0101     result->contentType()->setMimeType("multipart/encrypted");
0102     result->contentType()->setBoundary(KMime::multiPartBoundary());
0103     result->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted"));
0104 
0105     KMime::Content *controlInformation = new KMime::Content;
0106     {
0107         controlInformation->contentType()->setMimeType("application/pgp-encrypted");
0108         controlInformation->contentDescription()->from7BitString("PGP/MIME version identification");
0109         controlInformation->setBody("Version: 1");
0110 
0111         result->addContent(controlInformation);
0112     }
0113 
0114     KMime::Content *encryptedPartPart = new KMime::Content;
0115     {
0116         const QString filename = QStringLiteral("msg.asc");
0117 
0118         encryptedPartPart->contentType()->setMimeType("application/octet-stream");
0119         encryptedPartPart->contentType()->setName(filename, "utf-8");
0120 
0121         encryptedPartPart->contentDescription()->from7BitString("OpenPGP encrypted message");
0122 
0123         encryptedPartPart->contentDisposition()->setDisposition(KMime::Headers::CDinline);
0124         encryptedPartPart->contentDisposition()->setFilename(filename);
0125 
0126         encryptedPartPart->setBody(encryptedData);
0127 
0128         result->addContent(encryptedPartPart);
0129     }
0130 
0131     return result;
0132 }
0133 
0134 /**
0135  * Create a message part like this (according to RFC 3156 Section 5):
0136  *
0137  * + `multipart/signed`
0138  *   - whatever the given original `message` is (should be canonicalized)
0139  *   - `application/octet-stream` (the given `signature`)
0140  *
0141  * The signature can be generated by the `sign` function.
0142  */
0143 std::unique_ptr<KMime::Content> createSignedPart(std::unique_ptr<KMime::Content> message, const QByteArray &signature, const QString &micAlg)
0144 {
0145     auto result = std::unique_ptr<KMime::Content>(new KMime::Content);
0146 
0147     result->contentType()->setMimeType("multipart/signed");
0148     result->contentType()->setBoundary(KMime::multiPartBoundary());
0149     result->contentType()->setParameter(QStringLiteral("micalg"), micAlg);
0150     result->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature"));
0151 
0152     result->addContent(message.release());
0153 
0154     KMime::Content *signedPartPart = new KMime::Content;
0155     signedPartPart->contentType()->setMimeType("application/pgp-signature");
0156     signedPartPart->contentType()->setName(QStringLiteral("signature.asc"), "utf-8");
0157     signedPartPart->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment);
0158     signedPartPart->contentDisposition(true)->setFilename(QStringLiteral("signature.asc"));
0159     signedPartPart->contentDescription()->from7BitString("OpenPGP digital signature");
0160     signedPartPart->setBody(signature);
0161     result->addContent(signedPartPart);
0162 
0163     return result;
0164 }
0165 
0166 Expected<Error, std::unique_ptr<KMime::Content>>
0167 MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<Key> &signingKeys, const std::vector<Key> &encryptionKeys)
0168 {
0169     if (!encryptionKeys.empty()) {
0170         auto encryptionResult = signAndEncrypt(canonicalizeContent(content.get()), encryptionKeys, signingKeys);
0171         if (!encryptionResult) {
0172             return makeUnexpected(Error{encryptionResult.error()});
0173         }
0174         return createEncryptedPart(encryptionResult.value());
0175     } else if (!signingKeys.empty()) {
0176         auto signingResult = sign(canonicalizeContent(content.get()), signingKeys);
0177         if (!signingResult) {
0178             return makeUnexpected(Error{signingResult.error()});
0179         }
0180         QByteArray signingData;
0181         QString micAlg;
0182         std::tie(signingData, micAlg) = signingResult.value();
0183         return createSignedPart(std::move(content), signingData, micAlg);
0184     } else {
0185         qWarning() << "Processing cryptography, but neither signing nor encrypting";
0186         Q_ASSERT(false);
0187         return content;
0188     }
0189 }
0190 #endif