File indexing completed on 2025-01-05 04:54:56

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