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