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 }