File indexing completed on 2024-11-10 04:50:03
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 * 0006 */ 0007 0008 #include "filteractionencrypt.h" 0009 #include "mailcommon_debug.h" 0010 #include "util/cryptoutils.h" 0011 0012 #include <QCheckBox> 0013 #include <QEventLoop> 0014 #include <QLabel> 0015 #include <QVBoxLayout> 0016 0017 #include <KLocalizedString> 0018 0019 #include <KMime/Message> 0020 0021 #include <QGpgME/EncryptJob> 0022 #include <QGpgME/KeyListJob> 0023 #include <QGpgME/ListAllKeysJob> 0024 #include <QGpgME/Protocol> 0025 #include <gpgme++/encryptionresult.h> 0026 #include <gpgme++/keylistresult.h> 0027 0028 #include <Libkleo/DefaultKeyFilter> 0029 #include <Libkleo/KeySelectionCombo> 0030 0031 #include <MessageComposer/EncryptJob> 0032 0033 #include <Akonadi/MessageFlags> 0034 0035 #include <KColorScheme> 0036 0037 using namespace MailCommon; 0038 0039 #define LISTING_FINISHED "listingFinished" 0040 #define IGNORE_KEY_CHANGE "ignoreKeyChange" 0041 0042 FilterActionEncrypt::FilterActionEncrypt(QObject *parent) 0043 : FilterActionWithCrypto(QStringLiteral("encrypt"), i18n("Encrypt"), parent) 0044 , mKeyCache(Kleo::KeyCache::instance()) 0045 { 0046 } 0047 0048 FilterActionEncrypt::~FilterActionEncrypt() = default; 0049 0050 FilterAction *FilterActionEncrypt::newAction() 0051 { 0052 return new FilterActionEncrypt(); 0053 } 0054 0055 QString FilterActionEncrypt::displayString() const 0056 { 0057 return label(); 0058 } 0059 0060 QString FilterActionEncrypt::argsAsString() const 0061 { 0062 if (mKey.isNull()) { 0063 return {}; 0064 } 0065 0066 const auto proto = ((mKey.protocol() == GpgME::OpenPGP) ? QStringLiteral("PGP") : QStringLiteral("SMIME")); 0067 return QStringLiteral("%1:%2:%3").arg(proto, QString::number(int(mReencrypt)), QString::fromLatin1(mKey.primaryFingerprint())); 0068 } 0069 0070 void FilterActionEncrypt::argsFromString(const QString &argsStr) 0071 { 0072 const int pos = argsStr.indexOf(QLatin1Char(':')); 0073 const QStringView strView(argsStr); 0074 const auto protoStr = strView.left(pos); 0075 0076 QGpgME::Protocol *proto = {}; 0077 if (protoStr == QLatin1StringView("PGP")) { 0078 proto = QGpgME::openpgp(); 0079 } else if (protoStr == QLatin1StringView("SMIME")) { 0080 proto = QGpgME::smime(); 0081 } else { 0082 qCWarning(MAILCOMMON_LOG) << "Unknown protocol specified:" << protoStr; 0083 return; 0084 } 0085 mReencrypt = static_cast<bool>(QStringView(argsStr).mid(pos + 1, 1).toInt()); 0086 0087 const auto fp = argsStr.mid(pos + 3); 0088 auto listJob = proto->keyListJob(false, true, true); 0089 0090 std::vector<GpgME::Key> keys; 0091 auto result = listJob->exec({fp}, true, keys); 0092 listJob->deleteLater(); 0093 0094 if (result.error()) { 0095 qCWarning(MAILCOMMON_LOG) << "Failed to retrieve keys:" << result.error().asString(); 0096 return; 0097 } 0098 0099 if (keys.empty()) { 0100 qCWarning(MAILCOMMON_LOG) << "Could not obtain configured key: key expired or removed?"; 0101 // TODO: Interactively ask user to re-configure the filter 0102 return; 0103 } 0104 0105 mKey = keys[0]; 0106 } 0107 0108 SearchRule::RequiredPart FilterActionEncrypt::requiredPart() const 0109 { 0110 return SearchRule::CompleteMessage; 0111 } 0112 0113 FilterAction::ReturnCode FilterActionEncrypt::process(ItemContext &context, bool) const 0114 { 0115 if (mKey.isNull()) { 0116 qCWarning(MAILCOMMON_LOG) << "FilterActionEncrypt::process called without filter having a key!"; 0117 return ErrorButGoOn; 0118 } 0119 0120 auto &item = context.item(); 0121 if (!item.hasPayload<KMime::Message::Ptr>()) { 0122 qCWarning(MAILCOMMON_LOG) << "Item" << item.id() << "does not contain KMime::Message payload!"; 0123 return ErrorNeedComplete; 0124 } 0125 0126 auto msg = item.payload<KMime::Message::Ptr>(); 0127 if (KMime::isEncrypted(msg.data())) { 0128 if (mReencrypt) { 0129 // Make sure the email is not already encrypted by the mKey - this is 0130 // a little expensive, but still much cheaper than modifying and 0131 // re-uploading the email to the server 0132 const auto encryptionKeys = getEncryptionKeysFromContent(msg, mKey.protocol()); 0133 qCDebug(MAILCOMMON_LOG) << "Item" << item.id() << "encrypted by following keys: " << encryptionKeys; 0134 if (!encryptionKeys.isEmpty()) { 0135 if (mKey.protocol() == GpgME::OpenPGP) { 0136 std::vector<std::string> ids; 0137 ids.reserve(encryptionKeys.size()); 0138 for (const auto &key : encryptionKeys) { 0139 ids.push_back(key.toStdString()); 0140 } 0141 for (const auto &key : mKeyCache->findByKeyIDOrFingerprint(ids)) { 0142 if (qstrcmp(key.primaryFingerprint(), mKey.primaryFingerprint()) == 0) { 0143 // This email is already encrypted with the target key, 0144 // so there's no need to re-encrypt it 0145 qCDebug(MAILCOMMON_LOG) << "Item" << item.id() << "already encrypted with" << mKey.primaryFingerprint() << ", not re-encrypting"; 0146 return GoOn; 0147 } 0148 } 0149 } else if (mKey.protocol() == GpgME::CMS) { 0150 // We are only able to get serial 0151 for (const auto &key : mKeyCache->secretKeys()) { 0152 if (qstrcmp(key.issuerSerial(), mKey.issuerSerial()) == 0) { 0153 // This email is already encrypted with the target key, 0154 // so there's no need to re-encrypt it 0155 qCDebug(MAILCOMMON_LOG) << "Item" << item.id() << "already encrypted with" << mKey.primaryFingerprint() << ", not re-encrypting"; 0156 return GoOn; 0157 } 0158 } 0159 } 0160 } 0161 bool dummy; // dummy 0162 const auto decrypted = CryptoUtils::decryptMessage(msg, dummy); 0163 if (!decrypted) { 0164 // We failed to decrypt the encrypted email - very likely we just don't 0165 // have the right key, so don't consider it an error 0166 return GoOn; 0167 } else { 0168 msg = decrypted; 0169 } 0170 } else { 0171 return GoOn; 0172 } 0173 } 0174 0175 MessageComposer::EncryptJob encrypt; 0176 encrypt.setContent(msg.data()); 0177 encrypt.setCryptoMessageFormat(mKey.protocol() == GpgME::OpenPGP ? Kleo::OpenPGPMIMEFormat : Kleo::SMIMEFormat); 0178 encrypt.setEncryptionKeys({mKey}); 0179 encrypt.exec(); 0180 if (encrypt.error()) { 0181 qCWarning(MAILCOMMON_LOG) << "Encryption error:" << encrypt.errorString(); 0182 return ErrorButGoOn; 0183 } 0184 0185 KMime::Content *result = encrypt.content(); 0186 result->assemble(); 0187 0188 auto nec = CryptoUtils::assembleMessage(msg, result); 0189 context.item().setPayload(nec); 0190 context.item().setFlag(Akonadi::MessageFlags::Encrypted); 0191 context.setNeedsPayloadStore(); 0192 context.setNeedsFlagStore(); 0193 0194 delete result; 0195 0196 return GoOn; 0197 } 0198 0199 bool FilterActionEncrypt::isEmpty() const 0200 { 0201 return mKey.isNull(); 0202 } 0203 0204 QString FilterActionEncrypt::informationAboutNotValidAction() const 0205 { 0206 return i18n("No encryption key has been selected"); 0207 } 0208 0209 QWidget *FilterActionEncrypt::createParamWidget(QWidget *parent) const 0210 { 0211 auto w = new QWidget(parent); 0212 auto l = new QVBoxLayout; 0213 w->setLayout(l); 0214 0215 auto combo = new Kleo::KeySelectionCombo(w); 0216 combo->setDefaultKey(QString::fromLatin1(mKey.primaryFingerprint())); 0217 0218 std::shared_ptr<Kleo::DefaultKeyFilter> filter(new Kleo::DefaultKeyFilter); 0219 filter->setIsOpenPGP(Kleo::DefaultKeyFilter::DoesNotMatter); 0220 filter->setCanEncrypt(Kleo::DefaultKeyFilter::Set); 0221 filter->setHasSecret(Kleo::DefaultKeyFilter::Set); 0222 combo->setKeyFilter(filter); 0223 0224 combo->setProperty(LISTING_FINISHED, false); 0225 combo->setProperty(IGNORE_KEY_CHANGE, false); 0226 connect(combo, &Kleo::KeySelectionCombo::keyListingFinished, combo, [combo] { 0227 combo->setProperty(LISTING_FINISHED, true); 0228 combo->setProperty(IGNORE_KEY_CHANGE, true); 0229 }); 0230 connect(combo, &Kleo::KeySelectionCombo::currentKeyChanged, this, [this, combo]() { 0231 // Ignore key change due to the combo box populating itself after 0232 // finish 0233 if (!combo->property(IGNORE_KEY_CHANGE).toBool()) { 0234 Q_EMIT const_cast<FilterActionEncrypt *>(this)->filterActionModified(); 0235 } else { 0236 combo->setProperty(IGNORE_KEY_CHANGE, false); 0237 } 0238 }); 0239 l->addWidget(combo); 0240 0241 auto chkBox = new QCheckBox(w); 0242 chkBox->setText(i18n("Re-encrypt encrypted emails with this key")); 0243 chkBox->setChecked(mReencrypt); 0244 connect(chkBox, &QCheckBox::toggled, this, &FilterActionEncrypt::filterActionModified); 0245 l->addWidget(chkBox); 0246 0247 auto lbl = new QLabel(w); 0248 auto palette = lbl->palette(); 0249 palette.setColor(lbl->foregroundRole(), KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText).color()); 0250 lbl->setPalette(palette); 0251 lbl->setWordWrap(true); 0252 lbl->setText(i18n("<b>Warning:</b> Seckey necessary to read emails.")); 0253 lbl->setToolTip( 0254 i18n("<p>Once an email has been encrypted you will need a crypto setup with " 0255 "your secret key to access the contents again.</p>" 0256 "<p>If you keep emails stored on an email server and use several clients, " 0257 "each of them must be configured to enable decryption.</p>")); 0258 l->addWidget(lbl); 0259 0260 return w; 0261 } 0262 0263 void FilterActionEncrypt::setParamWidgetValue(QWidget *paramWidget) const 0264 { 0265 if (auto combo = paramWidget->findChild<Kleo::KeySelectionCombo *>()) { 0266 combo->setDefaultKey(QString::fromLatin1(mKey.primaryFingerprint())); 0267 combo->setCurrentKey(QString::fromLatin1(mKey.primaryFingerprint())); 0268 } 0269 if (auto chkBox = paramWidget->findChild<QCheckBox *>()) { 0270 chkBox->setChecked(mReencrypt); 0271 } 0272 } 0273 0274 void FilterActionEncrypt::applyParamWidgetValue(QWidget *paramWidget) 0275 { 0276 if (auto combo = paramWidget->findChild<Kleo::KeySelectionCombo *>()) { 0277 // FIXME: This is super-ugly, but unfortunately the filtering code generates 0278 // several instances of this filter and passes the paramWidgets from one 0279 // instance to another to "copy" stuff in between, which in our case leads 0280 // to this method being called on an un-populated combobox 0281 if (!combo->property(LISTING_FINISHED).toBool()) { 0282 QEventLoop ev; 0283 connect(combo, &Kleo::KeySelectionCombo::keyListingFinished, &ev, &QEventLoop::quit, Qt::QueuedConnection); 0284 ev.exec(); 0285 } 0286 mKey = combo->currentKey(); 0287 } 0288 if (auto chkBox = paramWidget->findChild<QCheckBox *>()) { 0289 mReencrypt = chkBox->isChecked(); 0290 } 0291 } 0292 0293 GpgME::Key FilterActionEncrypt::key() const 0294 { 0295 return mKey; 0296 } 0297 0298 bool FilterActionEncrypt::reencrypt() const 0299 { 0300 return mReencrypt; 0301 } 0302 0303 #include "moc_filteractionencrypt.cpp"