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"