File indexing completed on 2024-06-23 05:18:33

0001 /*
0002   SPDX-FileCopyrightText: 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
0003   SPDX-FileCopyrightText: 2009 Leo Franchi <lfranchi@kde.org>
0004 
0005   SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "job/signjob.h"
0009 
0010 #include "contentjobbase_p.h"
0011 #include "job/protectedheadersjob.h"
0012 #include "utils/util_p.h"
0013 
0014 #include <QGpgME/Protocol>
0015 #include <QGpgME/SignJob>
0016 #include <QList>
0017 
0018 #include "messagecomposer_debug.h"
0019 #include <KMime/Content>
0020 #include <KMime/Headers>
0021 #include <KMime/KMimeMessage>
0022 
0023 #include <gpgme++/encryptionresult.h>
0024 #include <gpgme++/global.h>
0025 #include <gpgme++/signingresult.h>
0026 #include <sstream>
0027 
0028 using namespace MessageComposer;
0029 
0030 class MessageComposer::SignJobPrivate : public ContentJobBasePrivate
0031 {
0032 public:
0033     SignJobPrivate(SignJob *qq)
0034         : ContentJobBasePrivate(qq)
0035     {
0036     }
0037 
0038     KMime::Content *content = nullptr;
0039     KMime::Message *skeletonMessage = nullptr;
0040     std::vector<GpgME::Key> signers;
0041     Kleo::CryptoMessageFormat format;
0042 
0043     bool protectedHeaders = true;
0044 
0045     // copied from messagecomposer.cpp
0046     [[nodiscard]] bool binaryHint(Kleo::CryptoMessageFormat f)
0047     {
0048         switch (f) {
0049         case Kleo::SMIMEFormat:
0050         case Kleo::SMIMEOpaqueFormat:
0051             return true;
0052         default:
0053         case Kleo::OpenPGPMIMEFormat:
0054         case Kleo::InlineOpenPGPFormat:
0055             return false;
0056         }
0057     }
0058 
0059     [[nodiscard]] GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f)
0060     {
0061         switch (f) {
0062         case Kleo::SMIMEOpaqueFormat:
0063             return GpgME::NormalSignatureMode;
0064         case Kleo::InlineOpenPGPFormat:
0065             return GpgME::Clearsigned;
0066         default:
0067         case Kleo::SMIMEFormat:
0068         case Kleo::OpenPGPMIMEFormat:
0069             return GpgME::Detached;
0070         }
0071     }
0072 
0073     Q_DECLARE_PUBLIC(SignJob)
0074 };
0075 
0076 SignJob::SignJob(QObject *parent)
0077     : ContentJobBase(*new SignJobPrivate(this), parent)
0078 {
0079 }
0080 
0081 SignJob::~SignJob() = default;
0082 
0083 void SignJob::setContent(KMime::Content *content)
0084 {
0085     Q_D(SignJob);
0086 
0087     d->content = content;
0088 }
0089 
0090 void SignJob::setCryptoMessageFormat(Kleo::CryptoMessageFormat format)
0091 {
0092     Q_D(SignJob);
0093 
0094     // There *must* be a concrete format set at this point.
0095     Q_ASSERT(format == Kleo::OpenPGPMIMEFormat || format == Kleo::InlineOpenPGPFormat || format == Kleo::SMIMEFormat || format == Kleo::SMIMEOpaqueFormat);
0096     d->format = format;
0097 }
0098 
0099 void SignJob::setSigningKeys(const std::vector<GpgME::Key> &signers)
0100 {
0101     Q_D(SignJob);
0102 
0103     d->signers = signers;
0104 }
0105 
0106 void SignJob::setSkeletonMessage(KMime::Message *skeletonMessage)
0107 {
0108     Q_D(SignJob);
0109 
0110     d->skeletonMessage = skeletonMessage;
0111 }
0112 
0113 void SignJob::setProtectedHeaders(bool protectedHeaders)
0114 {
0115     Q_D(SignJob);
0116 
0117     d->protectedHeaders = protectedHeaders;
0118 }
0119 
0120 KMime::Content *SignJob::origContent()
0121 {
0122     Q_D(SignJob);
0123 
0124     return d->content;
0125 }
0126 
0127 void SignJob::doStart()
0128 {
0129     Q_D(SignJob);
0130     Q_ASSERT(d->resultContent == nullptr); // Not processed before.
0131 
0132     if (d->protectedHeaders && d->skeletonMessage && d->format & Kleo::OpenPGPMIMEFormat) {
0133         auto pJob = new ProtectedHeadersJob;
0134         pJob->setContent(d->content);
0135         pJob->setSkeletonMessage(d->skeletonMessage);
0136         pJob->setObvoscate(false);
0137         QObject::connect(pJob, &ProtectedHeadersJob::finished, this, [d, pJob](KJob *job) {
0138             if (job->error()) {
0139                 return;
0140             }
0141             d->content = pJob->content();
0142         });
0143         appendSubjob(pJob);
0144     }
0145 
0146     ContentJobBase::doStart();
0147 }
0148 
0149 void SignJob::slotResult(KJob *job)
0150 {
0151     if (error() || job->error()) {
0152         ContentJobBase::slotResult(job);
0153         return;
0154     }
0155     if (subjobs().size() == 2) {
0156         auto pjob = static_cast<ProtectedHeadersJob *>(subjobs().last());
0157         if (pjob) {
0158             auto cjob = qobject_cast<ContentJobBase *>(job);
0159             Q_ASSERT(cjob);
0160             pjob->setContent(cjob->content());
0161         }
0162     }
0163 
0164     ContentJobBase::slotResult(job);
0165 }
0166 
0167 void SignJob::process()
0168 {
0169     Q_D(SignJob);
0170     Q_ASSERT(d->resultContent == nullptr); // Not processed before.
0171 
0172     // if setContent hasn't been called, we assume that a subjob was added
0173     // and we want to use that
0174     if (!d->content) {
0175         Q_ASSERT(d->subjobContents.size() == 1);
0176         d->content = d->subjobContents.constFirst();
0177     }
0178 
0179     // d->resultContent = new KMime::Content;
0180 
0181     const QGpgME::Protocol *proto = nullptr;
0182     if (d->format & Kleo::AnyOpenPGP) {
0183         proto = QGpgME::openpgp();
0184     } else if (d->format & Kleo::AnySMIME) {
0185         proto = QGpgME::smime();
0186     }
0187 
0188     Q_ASSERT(proto);
0189 
0190     qCDebug(MESSAGECOMPOSER_LOG) << "creating signJob from:" << proto->name() << proto->displayName();
0191     // for now just do the main recipients
0192     QByteArray signature;
0193 
0194     d->content->assemble();
0195 
0196     // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
0197     // according to RfC 2633, 3.1.1 Canonicalization
0198     QByteArray content;
0199     if (d->format & Kleo::InlineOpenPGPFormat) {
0200         content = d->content->body();
0201     } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
0202         // replace "From " and "--" at the beginning of lines
0203         // with encoded versions according to RfC 3156, 3
0204         // Note: If any line begins with the string "From ", it is strongly
0205         //   suggested that either the Quoted-Printable or Base64 MIME encoding
0206         //   be applied.
0207         const auto encoding = d->content->contentTransferEncoding()->encoding();
0208         if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit) && !d->content->contentType(false)) {
0209             QByteArray body = d->content->encodedBody();
0210             bool changed = false;
0211             QList<QByteArray> search;
0212             search.reserve(3);
0213             QList<QByteArray> replacements;
0214             replacements.reserve(3);
0215             search << "From "
0216                    << "from "
0217                    << "-";
0218             replacements << "From=20"
0219                          << "from=20"
0220                          << "=2D";
0221 
0222             if (d->content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
0223                 for (int i = 0, total = search.size(); i < total; ++i) {
0224                     const auto pos = body.indexOf(search[i]);
0225                     if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
0226                         changed = true;
0227                         break;
0228                     }
0229                 }
0230                 if (changed) {
0231                     d->content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
0232                     d->content->assemble();
0233                     body = d->content->encodedBody();
0234                 }
0235             }
0236 
0237             for (int i = 0; i < search.size(); ++i) {
0238                 const auto pos = body.indexOf(search[i]);
0239                 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
0240                     changed = true;
0241                     body.replace(pos, search[i].size(), replacements[i]);
0242                 }
0243             }
0244 
0245             if (changed) {
0246                 qCDebug(MESSAGECOMPOSER_LOG) << "Content changed";
0247                 d->content->setBody(body);
0248                 d->content->contentTransferEncoding()->setDecoded(false);
0249             }
0250         }
0251 
0252         content = KMime::LFtoCRLF(d->content->encodedContent());
0253     } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
0254         content = d->content->encodedContent();
0255     }
0256 
0257     QGpgME::SignJob *job(proto->signJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat));
0258     QObject::connect(
0259         job,
0260         &QGpgME::SignJob::result,
0261         this,
0262         [this, d](const GpgME::SigningResult &result, const QByteArray &signature, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) {
0263             Q_UNUSED(auditLogAsHtml)
0264             Q_UNUSED(auditLogError)
0265             if (result.error().code()) {
0266                 qCDebug(MESSAGECOMPOSER_LOG) << "signing failed:" << result.error().asString();
0267                 //        job->showErrorDialog( globalPart()->parentWidgetForGui() );
0268                 setError(result.error().code());
0269                 setErrorText(QString::fromLocal8Bit(result.error().asString()));
0270                 emitResult();
0271                 return;
0272             }
0273 
0274             QByteArray signatureHashAlgo = result.createdSignature(0).hashAlgorithmAsString();
0275             d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, signature, d->format, true, signatureHashAlgo);
0276 
0277             emitResult();
0278         });
0279 
0280     const auto error = job->start(d->signers, content, d->signingMode(d->format));
0281     if (error.code()) {
0282         job->deleteLater();
0283         setError(error.code());
0284         setErrorText(QString::fromLocal8Bit(error.asString()));
0285         emitResult();
0286     }
0287 }
0288 
0289 #include "moc_signjob.cpp"