File indexing completed on 2024-11-24 04:52:57

0001 /* Copyright (C) 2006 - 2016 Jan Kundrát <jkt@kde.org>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include <cstdint> // workaround broken mimetic header
0024 #include <cstring>
0025 #include <future>
0026 #include <mimetic/mimetic.h>
0027 #include <gpgme++/context.h>
0028 #include <gpgme++/data.h>
0029 #include <gpgme++/decryptionresult.h>
0030 #include <gpgme++/key.h>
0031 #include <gpgme++/interfaces/progressprovider.h>
0032 #include <qgpgme/dataprovider.h>
0033 #include <QElapsedTimer>
0034 #include "Common/InvokeMethod.h"
0035 #include "Cryptography/GpgMe++.h"
0036 #include "Cryptography/MessagePart.h"
0037 #include "Cryptography/MessageModel.h"
0038 #include "Cryptography/MimeticUtils.h"
0039 #include "Imap/Model/ItemRoles.h"
0040 #include "Imap/Model/MailboxTree.h"
0041 
0042 using namespace Imap::Mailbox;
0043 
0044 namespace {
0045 
0046 #if 0
0047 QByteArray getAuditLog(GpgME::Context *ctx)
0048 {
0049     QByteArray buf;
0050     buf.reserve(666);
0051     GpgME::Data bufData(buf.data_ptr()->data(), buf.capacity(), false);
0052     auto err = ctx->getAuditLog(bufData);
0053     if (err) {
0054         return Cryptography::GpgMePart::tr("[getAuditLog failed: %1]").arg(QString::fromUtf8(err.asString())).toUtf8();
0055     }
0056     return buf;
0057 }
0058 #endif
0059 
0060 bool is_running(const std::future<void> &future)
0061 {
0062     return future.wait_for(std::chrono::duration_values<std::chrono::seconds>::zero()) == std::future_status::timeout;
0063 }
0064 
0065 }
0066 
0067 namespace Cryptography {
0068 
0069 QString protocolToString(const Protocol protocol)
0070 {
0071     switch (protocol) {
0072     case Protocol::OpenPGP:
0073         return QStringLiteral("OpenPGP");
0074     case Protocol::SMime:
0075         return QStringLiteral("S/MIME");
0076     }
0077     Q_UNREACHABLE();
0078 }
0079 
0080 GpgMeReplacer::GpgMeReplacer()
0081     : PartReplacer()
0082 {
0083     GpgME::initializeLibrary();
0084     qRegisterMetaType<SignatureDataBundle>();
0085 }
0086 
0087 GpgMeReplacer::~GpgMeReplacer()
0088 {
0089     if (!m_orphans.empty()) {
0090         QElapsedTimer t;
0091         t.start();
0092         qDebug() << "Cleaning" << m_orphans.size() << "orphaned crypto task: ";
0093         for (auto &task: m_orphans) {
0094             if (is_running(task)) {
0095                 qDebug() << " [waiting]";
0096                 task.get();
0097             } else {
0098                 qDebug() << " [already completed]";
0099             }
0100         }
0101         qDebug() << " ...finished after" << t.elapsed() << "ms.";
0102     }
0103 }
0104 
0105 MessagePart::Ptr GpgMeReplacer::createPart(MessageModel *model, MessagePart *parentPart, MessagePart::Ptr original,
0106                                              const QModelIndex &sourceItemIndex, const QModelIndex &proxyParentIndex)
0107 {
0108     // Just - say - wow, because this is for sure much easier then typing QMap<QByteArray, QByteArray>, isn't it.
0109     // Yep, I just got a new toy.
0110     using bodyFldParam_t = std::result_of<decltype(&TreeItemPart::bodyFldParam)(TreeItemPart)>::type;
0111 
0112     auto mimeType = sourceItemIndex.data(RolePartMimeType).toByteArray();
0113     if (mimeType == "multipart/encrypted") {
0114         const auto bodyFldParam = sourceItemIndex.data(RolePartBodyFldParam).value<bodyFldParam_t>();
0115         if (bodyFldParam[QByteArrayLiteral("PROTOCOL")].toLower() == QByteArrayLiteral("application/pgp-encrypted")) {
0116             return MessagePart::Ptr(new GpgMeEncrypted(Protocol::OpenPGP, this, model, parentPart, std::move(original),
0117                                                        sourceItemIndex, proxyParentIndex));
0118         }
0119     } else if (mimeType == "multipart/signed") {
0120         const auto bodyFldParam = sourceItemIndex.data(RolePartBodyFldParam).value<bodyFldParam_t>();
0121         auto protocol = bodyFldParam[QByteArray("PROTOCOL")].toLower();
0122         if (protocol == QByteArrayLiteral("application/pgp-signature")) {
0123             return MessagePart::Ptr(new GpgMeSigned(Protocol::OpenPGP, this, model, parentPart, std::move(original),
0124                                                     sourceItemIndex, proxyParentIndex));
0125         } else if (protocol == QByteArrayLiteral("application/pkcs7-signature")
0126                    || protocol == QByteArrayLiteral("application/x-pkcs7-signature")) {
0127             return MessagePart::Ptr(new GpgMeSigned(Protocol::SMime, this, model, parentPart, std::move(original),
0128                                                     sourceItemIndex, proxyParentIndex));
0129         }
0130     } else if (mimeType == "application/pkcs7-mime" || mimeType == "application/x-pkcs7-mime") {
0131         return MessagePart::Ptr(new GpgMeEncrypted(Protocol::SMime, this, model, parentPart, std::move(original),
0132                                                    sourceItemIndex, proxyParentIndex));
0133     }
0134 
0135     return original;
0136 }
0137 
0138 void GpgMeReplacer::registerOrhpanedCryptoTask(std::future<void> task)
0139 {
0140     auto it = m_orphans.begin();
0141     while (it != m_orphans.end()) {
0142         if (it->valid() && is_running(*it)) {
0143             ++it;
0144         } else {
0145             qDebug() << "[cleaning an already-finished crypto orphan]";
0146             it = m_orphans.erase(it);
0147         }
0148     }
0149     if (task.valid() && is_running(task)) {
0150         m_orphans.emplace_back(std::move(task));
0151     }
0152     if (!m_orphans.empty()) {
0153         qDebug() << "We have" << m_orphans.size() << "orphaned crypto tasks";
0154     }
0155 }
0156 
0157 GpgMePart::GpgMePart(const Protocol protocol, GpgMeReplacer *replacer, MessageModel *model, MessagePart *parentPart,
0158                      const QModelIndex &sourceItemIndex, const QModelIndex &proxyParentIndex)
0159     : QObject(model)
0160     , LocalMessagePart(parentPart, sourceItemIndex.row(), sourceItemIndex.data(RolePartMimeType).toByteArray())
0161     , m_replacer(replacer)
0162     , m_model(model)
0163     , m_sourceIndex(sourceItemIndex)
0164     , m_proxyParentIndex(proxyParentIndex)
0165     , m_waitingForData(false)
0166     , m_wasSigned(false)
0167     , m_isAllegedlyEncrypted(false)
0168     , m_signatureOkDisregardingTrust(false)
0169     , m_signatureValidVerifiedTrusted(false)
0170     , m_statusTLDR(tr("Waiting for data..."))
0171     , m_statusIcon(QStringLiteral("clock"))
0172     , m_ctx(
0173           protocol == Protocol::OpenPGP ?
0174               (std::shared_ptr<GpgME::Context>(GpgME::checkEngine(GpgME::OpenPGP) ?
0175                                                    nullptr
0176                                                  : GpgME::Context::createForProtocol(GpgME::OpenPGP)))
0177             : (protocol == Protocol::SMime ?
0178                    (std::shared_ptr<GpgME::Context>(GpgME::checkEngine(GpgME::CMS) ?
0179                                                         nullptr
0180                                                       : GpgME::Context::createForProtocol(GpgME::CMS)))
0181                  : nullptr)
0182           )
0183 {
0184     Q_ASSERT(sourceItemIndex.isValid());
0185 
0186     // Find "an email" which encloses the current part.
0187     // This will be used later for figuring our whether "the sender" and the key which signed this part correspond to each other.
0188     QModelIndex index = m_proxyParentIndex;
0189     while (index.isValid()) {
0190         if (index.data(RolePartMimeType).toByteArray() == "message/rfc822") {
0191             m_enclosingMessage = index;
0192             break;
0193         } else {
0194             index = index.parent();
0195         }
0196     }
0197 
0198     // do the periodic cleanup
0199     m_replacer->registerOrhpanedCryptoTask(std::future<void>());
0200 }
0201 
0202 GpgMePart::~GpgMePart()
0203 {
0204     if (m_ctx && m_crypto.valid()) {
0205         // this is documented to be thread safe at all times
0206         m_ctx->cancelPendingOperation();
0207 
0208         // Because std::future's destructor blocks/joins, we have a problem if the operation that is running
0209         // in the std::async gets stuck for some reason. If we make no additional precautions, the GUI would freeze
0210         // at the destruction time, which means that everything will run "smoothly" (with the GUI remaining responsive)
0211         // until the time we move to another message -- and that's quite nasty surprise.
0212         //
0213         // To solve this thing, we send this background operation to a central registry in this destructor. Every now
0214         // and then, that registry is checked and those which have already finished are reaped. This happens whenever
0215         // a new crypto operation is started.
0216         m_replacer->registerOrhpanedCryptoTask(std::move(m_crypto));
0217     }
0218 }
0219 
0220 /** @short This slot is typically invoked from another thread, but we're always running in the "correct one" */
0221 void GpgMePart::internalUpdateState(const SignatureDataBundle &d)
0222 {
0223     m_wasSigned = d.wasSigned;
0224     m_signatureOkDisregardingTrust = d.isValidDisregardingTrust;
0225     m_signatureValidVerifiedTrusted = d.isValidTrusted;
0226     m_statusTLDR = d.tldrStatus;
0227     m_statusLong = d.longStatus;
0228     m_statusIcon = d.statusIcon;
0229     m_signatureIdentityName = d.signatureUid;
0230     m_signDate = d.signatureDate;
0231     m_crypto.get();
0232     emitDataChanged();
0233 }
0234 
0235 void GpgMePart::forwardFailure(const QString &statusTLDR, const QString &statusLong, const QString &statusIcon)
0236 {
0237     disconnect(m_dataChanged);
0238     m_waitingForData = false;
0239     m_wasSigned = false;
0240     m_statusTLDR = statusTLDR;
0241     m_statusLong = statusLong;
0242     m_statusIcon = statusIcon;
0243 
0244     if (m_sourceIndex.isValid()) {
0245         std::vector<MessagePart::Ptr> children;
0246         for (int i = 0; i < m_sourceIndex.model()->rowCount(m_sourceIndex); ++i) {
0247             children.emplace_back(MessagePart::Ptr(new ProxyMessagePart(nullptr, 0, m_sourceIndex.model()->index(i, 0, m_sourceIndex), m_model)));
0248         }
0249         // This has to happen prior to emitting error()
0250         m_model->insertSubtree(m_proxyParentIndex.model()->index(m_row, 0, m_proxyParentIndex), std::move(children));
0251     }
0252 
0253     // This forward is needed because we migth be emitting this indirectly, from the item's constructor.
0254     // At the time the ctor runs, the multipart/encrypted has not been inserted into the proxy model yet,
0255     // so we cannot obtain its index.
0256     emit m_model->error(m_proxyParentIndex.model()->index(m_row, 0, m_proxyParentIndex), m_statusTLDR, m_statusLong);
0257     emitDataChanged();
0258 }
0259 
0260 void GpgMePart::emitDataChanged()
0261 {
0262     auto idx = m_proxyParentIndex.model()->index(m_row, 0, m_proxyParentIndex);
0263     emit m_model->dataChanged(idx, idx);
0264 }
0265 
0266 QVariant GpgMePart::data(int role) const
0267 {
0268     switch (role) {
0269     case Imap::Mailbox::RolePartSignatureVerifySupported:
0270         return m_wasSigned;
0271     case RolePartDecryptionSupported:
0272         return m_isAllegedlyEncrypted;
0273     case RolePartCryptoNotFinishedYet:
0274         return m_waitingForData ||
0275                 (m_crypto.valid() &&
0276                  m_crypto.wait_for(std::chrono::duration_values<std::chrono::seconds>::zero()) == std::future_status::timeout);
0277     case RolePartCryptoTLDR:
0278         return m_statusTLDR;
0279     case RolePartCryptoDetailedMessage:
0280         return m_statusLong;
0281     case RolePartCryptoStatusIconName:
0282         return m_statusIcon;
0283     case Imap::Mailbox::RolePartSignatureValidTrusted:
0284         return m_wasSigned ? QVariant(m_signatureValidVerifiedTrusted) : QVariant();
0285     case Imap::Mailbox::RolePartSignatureValidDisregardingTrust:
0286         return m_wasSigned ? QVariant(m_signatureOkDisregardingTrust) : QVariant();
0287     case RolePartSignatureSignerName:
0288         return m_signatureIdentityName;
0289     case RolePartSignatureSignDate:
0290         return m_signDate;
0291     default:
0292         return LocalMessagePart::data(role);
0293     }
0294 }
0295 
0296 #define ENSURE_LINE_LF(X) do { if (!X.isEmpty()) { X += LF; } } while (0)
0297 
0298 void GpgMePart::extractSignatureStatus(std::shared_ptr<GpgME::Context> ctx, const GpgME::Signature &sig,
0299                                        const std::vector<std::string> messageUids, const bool wasSigned, const bool wasEncrypted,
0300                                        bool &sigOkDisregardingTrust, bool &sigValidVerified,
0301                                        bool &uidMatched, QString &tldr, QString &longStatus, QString &icon, QString &signer, QDateTime &signDate)
0302 {
0303     Q_UNUSED(wasSigned);
0304 
0305 #if 0
0306     qDebug() << "signature summary" << sig.summary() << "status err code" << sig.status() << sig.status().asString()
0307              << "validity" << sig.validity() << "fingerprint" << sig.fingerprint();
0308 #endif
0309 
0310     if (sig.summary() & GpgME::Signature::KeyMissing) {
0311         // that's right, there won't be any Green or Red labeling from GpgME; is we don't have the key, we cannot
0312         // do anything, period.
0313         tldr = wasEncrypted ? tr("Encrypted; some signature: missing key") : tr("Some signature: missing key");
0314         longStatus = tr("Key %1 is not available in the keyring.\n"
0315                         "Cannot verify signature validity or do anything else. "
0316                         "The message might or might not have been tampered with.")
0317                 .arg(QString::fromUtf8(sig.fingerprint()));
0318         icon = QStringLiteral("emblem-information");
0319         return;
0320     }
0321 
0322     GpgME::Error keyError;
0323     auto key = ctx->key(sig.fingerprint(), keyError, false);
0324     if (keyError) {
0325         tldr = tr("Internal error");
0326         longStatus = tr("Error when verifying signature: cannot retrieve key %1: %2")
0327                 .arg(QString::fromUtf8(sig.fingerprint()), QString::fromUtf8(keyError.asString()));
0328         icon = QStringLiteral("script-error");
0329         return;
0330     }
0331 
0332     const auto &uids = key.userIDs();
0333     auto needle = std::find_if(uids.begin(), uids.end(), [&messageUids](const GpgME::UserID &uid) {
0334         std::string email = uid.email();
0335         if (email.empty())
0336             return false;
0337         if (email[0] == '<' && email[email.size() - 1] == '>') {
0338             // this happens in the CMS, so let's kill the wrapping
0339             email = email.substr(1, email.size() - 2);
0340         }
0341         return std::find(messageUids.begin(), messageUids.end(), email) != messageUids.end();
0342     });
0343 
0344     if (needle != uids.end()) {
0345         uidMatched = true;
0346         switch (ctx->protocol()) {
0347         case GpgME::Protocol::CMS:
0348             signer = QString::fromUtf8(key.userID(0).id());
0349             break;
0350         case GpgME::Protocol::OpenPGP:
0351             signer = QStringLiteral("%1 (%2)").arg(QString::fromUtf8(needle->id()), QString::fromUtf8(sig.fingerprint()));
0352             break;
0353         case GpgME::Protocol::UnknownProtocol:
0354             Q_ASSERT(0);
0355         }
0356     } else if (!uids.empty()) {
0357         switch (ctx->protocol()) {
0358         case GpgME::Protocol::CMS:
0359             signer = QString::fromUtf8(key.userID(0).id());
0360             break;
0361         case GpgME::Protocol::OpenPGP:
0362             signer = QStringLiteral("%1 (%2)").arg(QString::fromUtf8(key.userID(0).id()), QString::fromUtf8(sig.fingerprint()));
0363             break;
0364         case GpgME::Protocol::UnknownProtocol:
0365             Q_ASSERT(0);
0366         }
0367     } else {
0368         signer = QString::fromUtf8(sig.fingerprint());
0369     }
0370     signDate = QDateTime::fromSecsSinceEpoch(sig.creationTime());
0371 
0372     if (sig.summary() & GpgME::Signature::Green) {
0373         // FIXME: change the above to GpgME::Signature::Valid and react to expired keys/signatures by checking the timestamp
0374         sigOkDisregardingTrust = true;
0375         if (uidMatched) {
0376             tldr = wasEncrypted ? tr("Encrypted, verified signature") : tr("Verified signature");
0377             longStatus = tr("Verified signature from %1 on %2")
0378                     .arg(signer, QLocale().toString(signDate , QLocale::ShortFormat));
0379             icon = QStringLiteral("emblem-success");
0380             sigValidVerified = true;
0381         } else {
0382             tldr = wasEncrypted ? tr("Encrypted, signed by stranger") : tr("Signed by stranger");
0383             longStatus = tr("Verified signature, but the signer is someone else:\n%1\nSignature was made on %2.")
0384                     .arg(signer, QLocale().toString(signDate, QLocale::ShortFormat));
0385             icon = QStringLiteral("emblem-warning");
0386         }
0387     } else if (sig.summary() & GpgME::Signature::Red) {
0388         if (uidMatched) {
0389             if (sig.status().code() == GPG_ERR_BAD_SIGNATURE) {
0390                 tldr = wasEncrypted ? tr("Encrypted, bad signature") : tr("Bad signature");
0391             } else {
0392                 tldr = wasEncrypted ?
0393                             tr("Encrypted, bad signature: %1").arg(QString::fromUtf8(sig.status().asString()))
0394                           : tr("Bad signature: %1").arg(QString::fromUtf8(sig.status().asString()));
0395             }
0396             longStatus = tr("Bad signature by %1 on %2").arg(signer, QLocale().toString(signDate, QLocale::ShortFormat));
0397         } else {
0398             if (sig.status().code() == GPG_ERR_BAD_SIGNATURE) {
0399                 tldr = wasEncrypted ? tr("Encrypted, bad signature by stranger") : tr("Bad signature by stranger");
0400             } else {
0401                 tldr = wasEncrypted ?
0402                             tr("Encrypted, bad signature by stranger: %1").arg(QString::fromUtf8(sig.status().asString()))
0403                           : tr("Bad signature by stranger: %1").arg(QString::fromUtf8(sig.status().asString()));
0404             }
0405             longStatus = tr("Bad signature by someone else: %1 on %2.")
0406                              .arg(signer, QLocale().toString(signDate, QLocale::ShortFormat));
0407         }
0408         icon = QStringLiteral("emblem-error");
0409     } else {
0410         switch (sig.validity()) {
0411         case GpgME::Signature::Full:
0412         case GpgME::Signature::Ultimate:
0413         case GpgME::Signature::Never:
0414             Q_ASSERT(false);
0415             // these are handled by GpgME by setting the appropriate Red/Green flags
0416             break;
0417         case GpgME::Signature::Unknown:
0418         case GpgME::Signature::Undefined:
0419             sigOkDisregardingTrust = true;
0420             if (uidMatched) {
0421                 tldr = wasEncrypted ? tr("Encrypted, some signature") : tr("Some signature");
0422                 longStatus = tr("Unknown signature from %1 on %2")
0423                                  .arg(signer, QLocale().toString(signDate, QLocale::ShortFormat));
0424                 icon = wasEncrypted ? QStringLiteral("emblem-encrypted-unlocked") : QStringLiteral("emblem-information");
0425             } else {
0426                 tldr = wasEncrypted ? tr("Encrypted, some signature by stranger") : tr("Some signature by stranger");
0427                 longStatus = tr("Unknown signature by somebody else: %1 on %2")
0428                         .arg(signer, QLocale().toString(signDate, QLocale::ShortFormat));
0429                 icon = QStringLiteral("emblem-warning");
0430             }
0431             break;
0432         case GpgME::Signature::Marginal:
0433             sigOkDisregardingTrust = true;
0434             if (uidMatched) {
0435                 tldr = wasEncrypted ? tr("Encrypted, semi-trusted signature") : tr("Semi-trusted signature");
0436                 longStatus = tr("Semi-trusted signature from %1 on %2")
0437                         .arg(signer, QLocale().toString(signDate, QLocale::ShortFormat));
0438                 icon = wasEncrypted ? QStringLiteral("emblem-encrypted-unlocked") : QStringLiteral("emblem-information");
0439             } else {
0440                 tldr = wasEncrypted ?
0441                             tr("Encrypted, semi-trusted signature by stranger")
0442                           : tr("Semi-trusted signature by stranger");
0443                 longStatus = tr("Semi-trusted signature by somebody else: %1 on %2")
0444                         .arg(signer, QLocale().toString(signDate, QLocale::ShortFormat));
0445                 icon = QStringLiteral("emblem-warning");
0446             }
0447             break;
0448         }
0449     }
0450 
0451     const auto LF = QLatin1Char('\n');
0452 
0453     // extract the individual error bits
0454     if (sig.summary() & GpgME::Signature::KeyRevoked) {
0455         ENSURE_LINE_LF(longStatus);
0456         longStatus += tr("The key or at least one certificate has been revoked.");
0457     }
0458     if (sig.summary() & GpgME::Signature::KeyExpired) {
0459         // FIXME: how to get the expiration date?
0460         ENSURE_LINE_LF(longStatus);
0461         longStatus += tr("The key or one of the certificates has expired.");
0462     }
0463     if (sig.summary() & GpgME::Signature::SigExpired) {
0464         ENSURE_LINE_LF(longStatus);
0465         longStatus += tr("Signature expired on %1.")
0466                 .arg(QLocale().toString(QDateTime::fromSecsSinceEpoch(sig.expirationTime()), QLocale::ShortFormat));
0467     }
0468     if (sig.summary() & GpgME::Signature::KeyMissing) {
0469         ENSURE_LINE_LF(longStatus);
0470         longStatus += tr("Can't verify due to a missing key or certificate.");
0471     }
0472     if (sig.summary() & GpgME::Signature::CrlMissing) {
0473         ENSURE_LINE_LF(longStatus);
0474         longStatus += tr("The CRL (or an equivalent mechanism) is not available.");
0475     }
0476     if (sig.summary() & GpgME::Signature::CrlTooOld) {
0477         ENSURE_LINE_LF(longStatus);
0478         longStatus += tr("Available CRL is too old.");
0479     }
0480     if (sig.summary() & GpgME::Signature::BadPolicy) {
0481         ENSURE_LINE_LF(longStatus);
0482         longStatus += tr("A policy requirement was not met.");
0483     }
0484     if (sig.summary() & GpgME::Signature::SysError) {
0485         ENSURE_LINE_LF(longStatus);
0486         longStatus += tr("A system error occurred. %1")
0487                 .arg(QString::fromUtf8(sig.status().asString()));
0488     }
0489 
0490     if (sig.summary() & GpgME::Signature::Valid) {
0491         // Extract signature validity
0492         switch (sig.validity()) {
0493         case GpgME::Signature::Undefined:
0494             ENSURE_LINE_LF(longStatus);
0495             longStatus += tr("Signature validity is undefined.");
0496             break;
0497         case GpgME::Signature::Never:
0498             ENSURE_LINE_LF(longStatus);
0499             longStatus += tr("Signature validity is never to be trusted.");
0500             break;
0501         case GpgME::Signature::Marginal:
0502             ENSURE_LINE_LF(longStatus);
0503             longStatus += tr("Signature validity is marginal.");
0504             break;
0505         case GpgME::Signature::Full:
0506             ENSURE_LINE_LF(longStatus);
0507             longStatus += tr("Signature validity is full.");
0508             break;
0509         case GpgME::Signature::Ultimate:
0510             ENSURE_LINE_LF(longStatus);
0511             longStatus += tr("Signature validity is ultimate.");
0512             break;
0513         case GpgME::Signature::Unknown:
0514             ENSURE_LINE_LF(longStatus);
0515             longStatus += tr("Signature validity is unknown.");
0516             break;
0517         }
0518     }
0519 
0520     // Show the certificate chain -- which only applies to S/MIME cryptography
0521     if (ctx->protocol() == GpgME::Protocol::CMS) {
0522         auto parentCert = key;
0523         int depth = 1;
0524 
0525         longStatus += LF + LF + tr("Trust chain:");
0526         while (!parentCert.isNull()) {
0527             QString indent(depth, QLatin1Char(' '));
0528             const char *chainId = parentCert.chainID();
0529             const char *primaryFp = parentCert.primaryFingerprint();
0530             if (!chainId || !primaryFp) {
0531                 longStatus += LF + tr("%1(Unavailable)").arg(indent);
0532                 break;
0533             } else if (std::strcmp(chainId, primaryFp) == 0) {
0534                 // a self-signed cert -> break
0535                 longStatus += LF + tr("%1(self-signed)").arg(indent);
0536                 break;
0537             }
0538             longStatus += LF + QStringLiteral("%1%2 (%3)").arg(indent,
0539                                                                QString::fromUtf8(parentCert.issuerName()),
0540                                                                QString::fromUtf8(parentCert.primaryFingerprint()));
0541             ++depth;
0542             parentCert = ctx->key(parentCert.chainID(), keyError, false);
0543             if (keyError) {
0544                 longStatus += LF + tr("Error when retrieving key for the trust chain: %1")
0545                         .arg(QString::fromUtf8(keyError.asString()));
0546                 break;
0547             }
0548         }
0549 
0550     }
0551 
0552 #if 0
0553     // this always shows "Success" in my limited testing, so...
0554     longStatus += LF + tr("Signature invalidity reason: %1")
0555             .arg(QString::fromUtf8(sig.nonValidityReason().asString()));
0556 #endif
0557 }
0558 
0559 void GpgMePart::submitVerifyResult(QPointer<QObject> p, const SignatureDataBundle &data)
0560 {
0561     if (p) {
0562         bool ok = QMetaObject::invokeMethod(p, "internalUpdateState", Qt::QueuedConnection,
0563                                             // must use full namespace qualification
0564                                             Q_ARG(Cryptography::SignatureDataBundle, data));
0565         Q_ASSERT(ok); Q_UNUSED(ok);
0566     } else {
0567         qDebug() << "[async crypto: GpgMePart is gone, not doing anything]";
0568     }
0569 }
0570 
0571 std::vector<std::string> GpgMePart::extractMessageUids()
0572 {
0573     // Extract the list of candidate e-mail addresses based on Sender and From headers.
0574     // This will be used to check if the signer and the author of the message are the same later on.
0575     // We're checking against the nearest parent message, so we support forwarding just fine.
0576 
0577     Q_ASSERT(m_enclosingMessage.isValid());
0578     std::vector<std::string> messageUids;
0579     auto envelopeVariant = m_enclosingMessage.data(RoleMessageEnvelope);
0580     Q_ASSERT(envelopeVariant.isValid());
0581     const auto envelope = envelopeVariant.value<Imap::Message::Envelope>();
0582     messageUids.reserve(envelope.from.size() + envelope.sender.size());
0583     auto storeSenderUid = [&messageUids](const Imap::Message::MailAddress &identity) {
0584         messageUids.emplace_back(identity.asSMTPMailbox().data());
0585     };
0586     std::for_each(envelope.from.begin(), envelope.from.end(), storeSenderUid);
0587     std::for_each(envelope.sender.begin(), envelope.sender.end(), storeSenderUid);
0588     return messageUids;
0589 }
0590 
0591 
0592 
0593 GpgMeSigned::GpgMeSigned(const Protocol protocol, GpgMeReplacer *replacer, MessageModel *model, MessagePart *parentPart, Ptr original,
0594                          const QModelIndex &sourceItemIndex, const QModelIndex &proxyParentIndex)
0595     : GpgMePart(protocol, replacer, model, parentPart, sourceItemIndex, proxyParentIndex)
0596     , m_plaintextPart(sourceItemIndex.model()->index(0, 0, sourceItemIndex).model()->index(0, TreeItem::OFFSET_RAW_CONTENTS, sourceItemIndex.model()->index(0, 0, sourceItemIndex)))
0597     , m_plaintextMimePart(sourceItemIndex.model()->index(0, 0, sourceItemIndex).model()->index(0, TreeItem::OFFSET_MIME, sourceItemIndex.model()->index(0, 0, sourceItemIndex)))
0598     , m_signaturePart(sourceItemIndex.model()->index(1, 0, sourceItemIndex))
0599 {
0600     m_wasSigned = true;
0601     Q_ASSERT(sourceItemIndex.model()->index(0, 0, sourceItemIndex).isValid());
0602 
0603     if (m_ctx) {
0604         const auto rowCount = sourceItemIndex.model()->rowCount(sourceItemIndex);
0605         if (rowCount == 2) {
0606             Q_ASSERT(m_plaintextPart.isValid());
0607             Q_ASSERT(m_signaturePart.isValid());
0608             m_dataChanged = connect(sourceItemIndex.model(), &QAbstractItemModel::dataChanged, this, &GpgMeSigned::handleDataChanged);
0609             Q_ASSERT(m_dataChanged);
0610             // Trigger lazy loading of the required message parts
0611             m_plaintextPart.data(RolePartData);
0612             m_plaintextMimePart.data(RolePartData);
0613             m_signaturePart.data(RolePartData);
0614             CALL_LATER(this, handleDataChanged, Q_ARG(QModelIndex, m_plaintextPart), Q_ARG(QModelIndex, m_plaintextPart));
0615         } else {
0616             CALL_LATER(this, forwardFailure, Q_ARG(QString, tr("Malformed Signed Message")),
0617                        Q_ARG(QString, tr("Expected 2 parts, but found %1.").arg(rowCount)),
0618                        Q_ARG(QString, QStringLiteral("emblem-error")));
0619         }
0620     } else {
0621         CALL_LATER(this, forwardFailure, Q_ARG(QString, tr("Cannot verify signature")),
0622                    Q_ARG(QString, tr("Failed to initialize GpgME (%1)").arg(protocolToString(protocol))),
0623                    Q_ARG(QString, QStringLiteral("script-error")));
0624     }
0625 
0626     auto oldState = m_localState;
0627     // the raw data are available on the part itself
0628     setData(original->data(Imap::Mailbox::RolePartData).toByteArray());
0629     m_localState = oldState;
0630     // we might change this in future; maybe having access to the OFFSET_RAW_CONTENTS makes some sense
0631     setSpecialParts(nullptr, nullptr, nullptr, nullptr);
0632 }
0633 
0634 GpgMeSigned::~GpgMeSigned()
0635 {
0636 }
0637 
0638 void GpgMeSigned::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0639 {
0640     Q_ASSERT(topLeft == bottomRight);
0641     if (!m_plaintextPart.isValid()) {
0642         forwardFailure(tr("Signed message is gone"), QString(), QStringLiteral("state-offline"));
0643         return;
0644     }
0645     if (topLeft != m_plaintextPart && topLeft != m_plaintextMimePart && topLeft != m_signaturePart &&
0646             topLeft != m_enclosingMessage) {
0647         return;
0648     }
0649     Q_ASSERT(m_plaintextPart.isValid());
0650     Q_ASSERT(m_plaintextMimePart.isValid());
0651     Q_ASSERT(m_signaturePart.isValid());
0652     if (m_plaintextPart.data(RoleIsUnavailable).toBool() || m_plaintextMimePart.data(RoleIsUnavailable).toBool()
0653             || m_signaturePart.data(RoleIsUnavailable).toBool() || m_enclosingMessage.data(RoleIsUnavailable).toBool()) {
0654         forwardFailure(tr("Data Unavailable"),
0655                        tr("Some data are not available, perhaps due to an offline network connection"),
0656                        QStringLiteral("state-offline"));
0657         return;
0658     }
0659     if (!m_plaintextPart.data(RoleIsFetched).toBool() || !m_plaintextMimePart.data(RoleIsFetched).toBool() ||
0660             !m_signaturePart.data(RoleIsFetched).toBool() || !m_enclosingMessage.data(RoleMessageEnvelope).isValid()) {
0661         return;
0662     }
0663 
0664     // Now that we have the data, let's make the content of the message immediately visible.
0665     // There is no point in delaying this until the moment the signature gets checked.
0666     QByteArray rawData = m_plaintextMimePart.data(RolePartData).toByteArray() + m_plaintextPart.data(RolePartData).toByteArray();
0667     mimetic::MimeEntity me(rawData.begin(), rawData.end());
0668     auto idx = m_proxyParentIndex.model()->index(m_row, 0, m_proxyParentIndex);
0669     Q_ASSERT(idx.isValid());
0670     m_model->insertSubtree(idx, MimeticUtils::mimeEntityToPart(me, nullptr, 0));
0671 
0672     disconnect(m_dataChanged);
0673     m_waitingForData = false;
0674 
0675     m_statusTLDR = tr("Verifying signature...");
0676 
0677     auto signatureData = m_signaturePart.data(RolePartData).toByteArray();
0678     bool wasEncrypted = m_isAllegedlyEncrypted;
0679     auto messageUids = extractMessageUids();
0680     auto ctx = m_ctx;
0681 
0682     m_crypto = std::async(std::launch::async, [this, ctx, rawData, signatureData, messageUids, wasEncrypted](){
0683         QPointer<QObject> p(this);
0684 
0685         GpgME::Data sigData(signatureData.data(), signatureData.size(), false);
0686         GpgME::Data msgData(rawData.data(), rawData.size(), false);
0687 
0688         auto verificationResult = ctx->verifyDetachedSignature(sigData, msgData);
0689         bool wasSigned = false;
0690         bool sigOkDisregardingTrust = false;
0691         bool sigValidVerified = false;
0692         bool uidMatched = false;
0693         QString tldr;
0694         QString longStatus;
0695         QString icon;
0696         QString signer;
0697         QDateTime signDate;
0698 
0699         if (verificationResult.numSignatures() == 0) {
0700             tldr = tr("No signatures in the signed message");
0701             icon = QStringLiteral("script-error");
0702         }
0703 
0704         for (const auto &sig: verificationResult.signatures()) {
0705             wasSigned = true;
0706             extractSignatureStatus(ctx, sig, messageUids, wasSigned, wasEncrypted,
0707                                    sigOkDisregardingTrust, sigValidVerified, uidMatched, tldr, longStatus, icon, signer, signDate);
0708 
0709             // FIXME: add support for multiple signatures at once. How do we want to handle them in the UI?
0710             break;
0711         }
0712         submitVerifyResult(p, {wasSigned, sigOkDisregardingTrust, sigValidVerified, tldr, longStatus, icon, signer, signDate});
0713     });
0714     emitDataChanged();
0715 }
0716 
0717 
0718 GpgMeEncrypted::GpgMeEncrypted(const Protocol protocol, GpgMeReplacer *replacer, MessageModel *model, MessagePart *parentPart, Ptr original,
0719                                const QModelIndex &sourceItemIndex, const QModelIndex &proxyParentIndex)
0720     : GpgMePart(protocol, replacer, model, parentPart, sourceItemIndex, proxyParentIndex)
0721     , m_decryptionSupported(false)
0722     , m_decryptionFailed(false)
0723 {
0724     m_isAllegedlyEncrypted = true;
0725     if (m_ctx) {
0726         const auto rowCount = sourceItemIndex.model()->rowCount(sourceItemIndex);
0727 
0728         switch (m_ctx->protocol()) {
0729         case GpgME::Protocol::OpenPGP:
0730             m_versionPart = sourceItemIndex.model()->index(0, 0, sourceItemIndex);
0731             m_encPart = sourceItemIndex.model()->index(1, 0, sourceItemIndex);
0732             if (rowCount == 2) {
0733                 m_dataChanged = connect(sourceItemIndex.model(), &QAbstractItemModel::dataChanged, this, &GpgMeEncrypted::handleDataChanged);
0734                 Q_ASSERT(m_dataChanged);
0735                 // Trigger lazy loading of the required message parts
0736                 m_versionPart.data(RolePartData);
0737                 m_encPart.data(RolePartData);
0738                 CALL_LATER(this, handleDataChanged, Q_ARG(QModelIndex, m_encPart), Q_ARG(QModelIndex, m_encPart));
0739             } else {
0740                 CALL_LATER(this, forwardFailure, Q_ARG(QString, tr("Malformed Encrypted Message")),
0741                            Q_ARG(QString, tr("Expected 2 parts for an encrypted OpenPGP message, but found %1.").arg(rowCount)),
0742                            Q_ARG(QString, QStringLiteral("emblem-error")));
0743             }
0744             break;
0745 
0746         case GpgME::Protocol::CMS:
0747             // We need to override the MIME type handling because the GUI only really expects encrypted messages using this type.
0748             // The application/pkcs7-mime is an opaque leaf node in a MIME tree, there are no other relevant parts.
0749             // This is very different from, say, an OpenPGP message.
0750             m_mimetype = QByteArrayLiteral("multipart/encrypted");
0751             m_versionPart = m_encPart = sourceItemIndex;
0752             m_dataChanged = connect(sourceItemIndex.model(), &QAbstractItemModel::dataChanged, this, &GpgMeEncrypted::handleDataChanged);
0753             Q_ASSERT(m_dataChanged);
0754             // Trigger lazy loading of the required message parts
0755             m_versionPart.data(RolePartData);
0756             m_encPart.data(RolePartData);
0757             CALL_LATER(this, handleDataChanged, Q_ARG(QModelIndex, m_encPart), Q_ARG(QModelIndex, m_encPart));
0758             break;
0759 
0760         case GpgME::Protocol::UnknownProtocol:
0761             Q_ASSERT(false);
0762             break;
0763 
0764         }
0765     } else {
0766         CALL_LATER(this, forwardFailure, Q_ARG(QString, tr("Cannot descrypt")),
0767                    Q_ARG(QString, tr("Failed to initialize GpgME (%1)").arg(protocolToString(protocol))),
0768                    Q_ARG(QString, QStringLiteral("script-error")));
0769     }
0770 
0771     auto oldState = m_localState;
0772     // the raw data are available on the part itself
0773     setData(original->data(Imap::Mailbox::RolePartData).toByteArray());
0774     m_localState = oldState;
0775     // we might change this in future; maybe having access to the OFFSET_RAW_CONTENTS makes some sense
0776     setSpecialParts(nullptr, nullptr, nullptr, nullptr);
0777 }
0778 
0779 GpgMeEncrypted::~GpgMeEncrypted()
0780 {
0781 }
0782 
0783 void GpgMeEncrypted::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0784 {
0785     Q_ASSERT(topLeft == bottomRight);
0786     if (!m_encPart.isValid()) {
0787         forwardFailure(tr("Encrypted message is gone"), QString(), QStringLiteral("state-offline"));
0788         return;
0789     }
0790     if (topLeft != m_versionPart && topLeft != m_encPart && topLeft != m_enclosingMessage) {
0791         return;
0792     }
0793     Q_ASSERT(m_versionPart.isValid());
0794     Q_ASSERT(m_encPart.isValid());
0795     if (m_versionPart.data(RoleIsUnavailable).toBool() || m_encPart.data(RoleIsUnavailable).toBool()
0796             || m_enclosingMessage.data(RoleIsUnavailable).toBool()) {
0797         forwardFailure(tr("Data Unavailable"),
0798                        tr("Cannot decrypt. Some data are not available, perhaps due to an offline network connection."),
0799                        QStringLiteral("state-offline"));
0800         return;
0801     }
0802     if (!m_versionPart.data(RoleIsFetched).toBool() || !m_encPart.data(RoleIsFetched).toBool()
0803             || !m_enclosingMessage.data(RoleMessageEnvelope).isValid()) {
0804         return;
0805     }
0806 
0807     disconnect(m_dataChanged);
0808     m_waitingForData = false;
0809 
0810     if (m_ctx->protocol() == GpgME::Protocol::OpenPGP) {
0811         // Check compliance with RFC3156
0812         QString versionString = m_versionPart.data(RolePartData).toString();
0813         if (!versionString.contains(QLatin1String("Version: 1"))) {
0814             forwardFailure(tr("Malformed Encrypted Message"),
0815                            tr("Unsupported PGP/MIME version. Expected \"Version: 1\", got \"%1\".").arg(versionString),
0816                            QStringLiteral("emblem-error"));
0817             return;
0818         }
0819     }
0820 
0821     m_statusTLDR = tr("Decrypting...");
0822 
0823     auto cipherData = m_encPart.data(RolePartData).toByteArray();
0824     auto messageUids = extractMessageUids();
0825     auto ctx = m_ctx;
0826 
0827     m_crypto = std::async(std::launch::async, [this, ctx, cipherData, messageUids ](){
0828         QPointer<QObject> p(this);
0829 
0830         GpgME::Data encData(cipherData.data(), cipherData.size(), false);
0831         QGpgME::QByteArrayDataProvider dp;
0832         GpgME::Data plaintextData(&dp);
0833 
0834         auto combinedResult = ctx->decryptAndVerify(encData, plaintextData);
0835 
0836         bool wasSigned = false;
0837         bool wasEncrypted = true;
0838         bool sigOkDisregardingTrust = false;
0839         bool sigValidVerified = false;
0840         bool uidMatched = false;
0841         QString tldr;
0842         QString longStatus;
0843         QString icon;
0844         QString signer;
0845         QDateTime signDate;
0846 
0847         if (combinedResult.second.numSignatures() != 0) {
0848             for (const auto &sig: combinedResult.second.signatures()) {
0849                 wasSigned = true;
0850                 extractSignatureStatus(ctx, sig, messageUids, wasSigned, wasEncrypted,
0851                                        sigOkDisregardingTrust, sigValidVerified, uidMatched, tldr, longStatus, icon, signer, signDate);
0852 
0853                 // FIXME: add support for multiple signatures at once. How do we want to handle them in the UI?
0854                 break;
0855             }
0856         }
0857 
0858         constexpr QChar LF = QLatin1Char('\n');
0859 
0860         bool decryptedOk = !combinedResult.first.error();
0861 
0862         if (!decryptedOk) {
0863             if (tldr.isEmpty()) {
0864                 tldr = tr("Broken encrypted message");
0865             }
0866             ENSURE_LINE_LF(longStatus);
0867             longStatus += tr("Decryption error: %1").arg(QString::fromUtf8(combinedResult.first.error().asString()));
0868             icon = QStringLiteral("emblem-error");
0869         } else if (tldr.isEmpty()) {
0870             tldr = tr("Encrypted message");
0871             icon = QStringLiteral("emblem-encrypted-unlocked");
0872         }
0873 
0874         if (combinedResult.first.isWrongKeyUsage()) {
0875             ENSURE_LINE_LF(longStatus);
0876             longStatus += tr("Wrong key usage, not for encryption");
0877         }
0878         if (auto msg = combinedResult.first.unsupportedAlgorithm()) {
0879             ENSURE_LINE_LF(longStatus);
0880             longStatus += tr("Unsupported algorithm: %1").arg(QString::fromUtf8(msg));
0881         }
0882 
0883         for (const auto &recipient: combinedResult.first.recipients()) {
0884             GpgME::Error keyError;
0885             auto key = ctx->key(recipient.keyID(), keyError, false);
0886             if (keyError) {
0887                 ENSURE_LINE_LF(longStatus);
0888                 longStatus += tr("Cannot extract recipient %1: %2")
0889                         .arg(QString::fromUtf8(recipient.keyID()), QString::fromUtf8(keyError.asString()));
0890             } else {
0891                 if (key.numUserIDs()) {
0892                     ENSURE_LINE_LF(longStatus);
0893                     longStatus += tr("Encrypted to %1 (%2)")
0894                             .arg(QString::fromUtf8(key.userID(0).id()), QString::fromUtf8(recipient.keyID()));
0895                 } else {
0896                     ENSURE_LINE_LF(longStatus);
0897                     longStatus += tr("Encrypted to %1").arg(QString::fromUtf8(recipient.keyID()));
0898                 }
0899             }
0900         }
0901         if (auto fname = combinedResult.first.fileName()) {
0902             ENSURE_LINE_LF(longStatus);
0903             longStatus += tr("Original filename: %1").arg(QString::fromUtf8(fname));
0904         }
0905 
0906         if (p) {
0907             bool ok = QMetaObject::invokeMethod(p, "processDecryptedData", Qt::QueuedConnection,
0908                                                 Q_ARG(bool, decryptedOk),
0909                                                 Q_ARG(QByteArray, dp.data()));
0910             Q_ASSERT(ok); Q_UNUSED(ok);
0911         } else {
0912             qDebug() << "[async crypto: GpgMeEncrypted is gone, not sending cleartext data]";
0913         }
0914         submitVerifyResult(p, {wasSigned, sigOkDisregardingTrust, sigValidVerified, tldr, longStatus, icon, signer, signDate});
0915     });
0916 
0917     emitDataChanged();
0918 }
0919 
0920 void GpgMeEncrypted::processDecryptedData(const bool ok, const QByteArray &data)
0921 {
0922     if (!m_versionPart.isValid() || !m_encPart.isValid() || !m_proxyParentIndex.isValid()) {
0923         forwardFailure(tr("Encrypted message is gone"), QString(), QStringLiteral("state-offline"));
0924     } else {
0925         auto idx = m_proxyParentIndex.model()->index(m_row, 0, m_proxyParentIndex);
0926         Q_ASSERT(idx.isValid());
0927         if (ok) {
0928             mimetic::MimeEntity me(data.begin(), data.end());
0929             m_model->insertSubtree(idx, MimeticUtils::mimeEntityToPart(me, nullptr, 0));
0930         } else {
0931             // It's important that we do not render this message if the decryption actually failed.
0932             // One form of the EFAIL attack from 2018 relied on MUAs which HTML-rendered the decrypted plaintext
0933             // which was however mangled by an attacker.
0934             // We just offer access to the original, encrypted part as-is for further processing.
0935             std::unique_ptr<LocalMessagePart> part(new LocalMessagePart(nullptr, 0, m_encPart.data(RolePartMimeType).toByteArray()));
0936             part->setBodyDisposition("attachment");
0937             part->setFilename(m_encPart.data(RolePartFileName).toString());
0938             part->setOctets(m_encPart.data(RolePartOctets).toULongLong());
0939             part->setData(m_encPart.data(RolePartData).toByteArray());
0940             m_model->insertSubtree(idx, std::move(part));
0941         }
0942         emitDataChanged();
0943     }
0944 }
0945 
0946 
0947 }