File indexing completed on 2024-06-16 04:56:16

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     padwidget.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2018 Intevation GmbH
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "padwidget.h"
0010 
0011 #include "kleopatra_debug.h"
0012 
0013 #include <settings.h>
0014 
0015 #include <Libkleo/Classify>
0016 #include <Libkleo/Compliance>
0017 #include <Libkleo/Formatting>
0018 #include <Libkleo/KeyCache>
0019 #include <Libkleo/KleoException>
0020 #include <Libkleo/SystemInfo>
0021 
0022 #include "crypto/gui/resultitemwidget.h"
0023 #include "crypto/gui/signencryptwidget.h"
0024 
0025 #include "crypto/decryptverifytask.h"
0026 #include "crypto/signencrypttask.h"
0027 #include "utils/input.h"
0028 #include "utils/output.h"
0029 #include <Libkleo/GnuPG>
0030 
0031 #include "commands/importcertificatefromdatacommand.h"
0032 
0033 #include <gpgme++/data.h>
0034 #include <gpgme++/decryptionresult.h>
0035 
0036 #include <QGpgME/DataProvider>
0037 
0038 #include <QButtonGroup>
0039 #include <QFontDatabase>
0040 #include <QFontMetrics>
0041 #include <QLabel>
0042 #include <QProgressBar>
0043 #include <QPushButton>
0044 #include <QRadioButton>
0045 #include <QStyle>
0046 #include <QTabWidget>
0047 #include <QTextEdit>
0048 #include <QVBoxLayout>
0049 
0050 #include <KColorScheme>
0051 #include <KConfigGroup>
0052 #include <KLocalizedString>
0053 #include <KMessageBox>
0054 #include <KMessageWidget>
0055 #include <KSharedConfig>
0056 
0057 using namespace Kleo;
0058 using namespace Kleo::Crypto;
0059 using namespace Kleo::Crypto::Gui;
0060 
0061 static GpgME::Protocol getProtocol(const std::shared_ptr<const Kleo::Crypto::Task::Result> &result)
0062 {
0063     const auto dvResult = dynamic_cast<const Kleo::Crypto::DecryptVerifyResult *>(result.get());
0064     if (dvResult) {
0065         for (const auto &key : KeyCache::instance()->findRecipients(dvResult->decryptionResult())) {
0066             return key.protocol();
0067         }
0068         for (const auto &key : KeyCache::instance()->findSigners(dvResult->verificationResult())) {
0069             return key.protocol();
0070         }
0071     }
0072     return GpgME::UnknownProtocol;
0073 }
0074 
0075 class PadWidget::Private
0076 {
0077     friend class ::Kleo::PadWidget;
0078 
0079 public:
0080     Private(PadWidget *qq)
0081         : q(qq)
0082         , mEdit(new QTextEdit)
0083         , mCryptBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit-sign-encrypt")), i18n("Sign / Encrypt Notepad")))
0084         , mDecryptBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit-decrypt-verify")), i18n("Decrypt / Verify Notepad")))
0085         , mImportBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("view-certificate-import")), i18n("Import Notepad")))
0086         , mRevertBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Revert")))
0087         , mMessageWidget{new KMessageWidget}
0088         , mAdditionalInfoLabel(new QLabel)
0089         , mSigEncWidget(new SignEncryptWidget(nullptr, true))
0090         , mProgressBar(new QProgressBar)
0091         , mProgressLabel(new QLabel)
0092         , mLastResultWidget(nullptr)
0093         , mPGPRB(nullptr)
0094         , mCMSRB(nullptr)
0095         , mImportProto(GpgME::UnknownProtocol)
0096     {
0097         auto vLay = new QVBoxLayout(q);
0098 
0099         auto btnLay = new QHBoxLayout;
0100         vLay->addLayout(btnLay);
0101         btnLay->addWidget(mCryptBtn);
0102         btnLay->addWidget(mDecryptBtn);
0103         btnLay->addWidget(mImportBtn);
0104         btnLay->addWidget(mRevertBtn);
0105 
0106         mRevertBtn->setVisible(false);
0107 
0108         btnLay->addWidget(mAdditionalInfoLabel);
0109 
0110         btnLay->addStretch(-1);
0111 
0112         mMessageWidget->setMessageType(KMessageWidget::Warning);
0113         mMessageWidget->setIcon(q->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, q));
0114         mMessageWidget->setText(i18n("Signing and encryption is not possible."));
0115         mMessageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
0116                                           "<para>You cannot use <application>Kleopatra</application> for signing or encryption "
0117                                           "because the <application>GnuPG</application> system used by <application>Kleopatra</application> is not %1.</para>",
0118                                           DeVSCompliance::name(true)));
0119         mMessageWidget->setCloseButtonVisible(false);
0120         mMessageWidget->setVisible(false);
0121         vLay->addWidget(mMessageWidget);
0122 
0123         mProgressBar->setRange(0, 0);
0124         mProgressBar->setVisible(false);
0125         mProgressLabel->setVisible(false);
0126         auto progLay = new QHBoxLayout;
0127 
0128         progLay->addWidget(mProgressLabel);
0129         progLay->addWidget(mProgressBar);
0130 
0131         mStatusLay = new QVBoxLayout;
0132         mStatusLay->addLayout(progLay);
0133         vLay->addLayout(mStatusLay, 0);
0134 
0135         auto tabWidget = new QTabWidget;
0136         vLay->addWidget(tabWidget, 1);
0137 
0138         tabWidget->addTab(mEdit, QIcon::fromTheme(QStringLiteral("edittext")), i18n("Notepad"));
0139 
0140         // The recipients area
0141         auto recipientsWidget = new QWidget;
0142         auto recipientsVLay = new QVBoxLayout(recipientsWidget);
0143         auto protocolSelectionLay = new QHBoxLayout;
0144 
0145         bool pgpOnly = KeyCache::instance()->pgpOnly();
0146         if (!pgpOnly) {
0147             recipientsVLay->addLayout(protocolSelectionLay);
0148         }
0149 
0150         protocolSelectionLay->addWidget(new QLabel(i18n("<h3>Protocol:</h3>")));
0151         protocolSelectionLay->addStretch(-1);
0152         // Once S/MIME is supported add radio for S/MIME here.
0153 
0154         recipientsVLay->addWidget(mSigEncWidget);
0155         tabWidget->addTab(recipientsWidget, QIcon::fromTheme(QStringLiteral("contact-new-symbolic")), i18n("Recipients"));
0156 
0157         mEdit->setPlaceholderText(i18n("Enter a message to encrypt or decrypt..."));
0158 
0159         auto fixedFont = QFont(QStringLiteral("Monospace"));
0160         fixedFont.setStyleHint(QFont::TypeWriter);
0161         // This does not work well
0162         // QFontDatabase::systemFont(QFontDatabase::FixedFont);
0163 
0164         mEdit->setFont(fixedFont);
0165         mEdit->setAcceptRichText(false);
0166         mEdit->setMinimumWidth(QFontMetrics(fixedFont).averageCharWidth() * 70);
0167 
0168         if (KeyCache::instance()->pgpOnly() || !Settings{}.cmsEnabled()) {
0169             mSigEncWidget->setProtocol(GpgME::OpenPGP);
0170         } else {
0171             auto grp = new QButtonGroup(q);
0172             auto mPGPRB = new QRadioButton(i18n("OpenPGP"));
0173             auto mCMSRB = new QRadioButton(i18n("S/MIME"));
0174             grp->addButton(mPGPRB);
0175             grp->addButton(mCMSRB);
0176 
0177             KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Notepad"));
0178             if (config.readEntry("wasCMS", false)) {
0179                 mCMSRB->setChecked(true);
0180                 mSigEncWidget->setProtocol(GpgME::CMS);
0181             } else {
0182                 mPGPRB->setChecked(true);
0183                 mSigEncWidget->setProtocol(GpgME::OpenPGP);
0184             }
0185 
0186             protocolSelectionLay->addWidget(mPGPRB);
0187             protocolSelectionLay->addWidget(mCMSRB);
0188             connect(mPGPRB, &QRadioButton::toggled, q, [this](bool value) {
0189                 if (value) {
0190                     mSigEncWidget->setProtocol(GpgME::OpenPGP);
0191                 }
0192             });
0193             connect(mCMSRB, &QRadioButton::toggled, q, [this](bool value) {
0194                 if (value) {
0195                     mSigEncWidget->setProtocol(GpgME::CMS);
0196                 }
0197             });
0198         }
0199 
0200         updateButtons();
0201 
0202         connect(mEdit, &QTextEdit::textChanged, q, [this]() {
0203             updateButtons();
0204         });
0205 
0206         connect(mCryptBtn, &QPushButton::clicked, q, [this]() {
0207             doEncryptSign();
0208         });
0209 
0210         connect(mSigEncWidget, &SignEncryptWidget::operationChanged, q, [this]() {
0211             updateButtons();
0212         });
0213 
0214         connect(mDecryptBtn, &QPushButton::clicked, q, [this]() {
0215             doDecryptVerify();
0216         });
0217 
0218         connect(mImportBtn, &QPushButton::clicked, q, [this]() {
0219             doImport();
0220         });
0221 
0222         connect(mRevertBtn, &QPushButton::clicked, q, [this]() {
0223             revert();
0224         });
0225     }
0226 
0227     void revert()
0228     {
0229         mEdit->setPlainText(QString::fromUtf8(mInputData));
0230         mRevertBtn->setVisible(false);
0231     }
0232 
0233     void updateRecipientsFromResult(const Kleo::Crypto::DecryptVerifyResult &result)
0234     {
0235         const auto decResult = result.decryptionResult();
0236 
0237         for (const auto &recipient : decResult.recipients()) {
0238             if (!recipient.keyID()) {
0239                 continue;
0240             }
0241 
0242             GpgME::Key key;
0243             if (strlen(recipient.keyID()) < 16) {
0244                 key = KeyCache::instance()->findByShortKeyID(recipient.keyID());
0245             } else {
0246                 key = KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID());
0247             }
0248 
0249             if (key.isNull()) {
0250                 std::vector<std::string> subids;
0251                 subids.push_back(std::string(recipient.keyID()));
0252                 for (const auto &subkey : KeyCache::instance()->findSubkeysByKeyID(subids)) {
0253                     key = subkey.parent();
0254                     break;
0255                 }
0256             }
0257 
0258             if (key.isNull()) {
0259                 qCDebug(KLEOPATRA_LOG) << "Unknown key" << recipient.keyID();
0260                 mSigEncWidget->addUnknownRecipient(recipient.keyID());
0261                 continue;
0262             }
0263 
0264             bool keyFound = false;
0265             for (const auto &existingKey : mSigEncWidget->recipients()) {
0266                 if (existingKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp(existingKey.primaryFingerprint(), key.primaryFingerprint())) {
0267                     keyFound = true;
0268                     break;
0269                 }
0270             }
0271             if (!keyFound) {
0272                 mSigEncWidget->addRecipient(key);
0273             }
0274         }
0275     }
0276 
0277     void cryptDone(const std::shared_ptr<const Kleo::Crypto::Task::Result> &result)
0278     {
0279         updateButtons();
0280         mProgressBar->setVisible(false);
0281         mProgressLabel->setVisible(false);
0282 
0283         if (!result->error().isCanceled()) {
0284             mLastResultWidget = new ResultItemWidget(result);
0285             mLastResultWidget->showCloseButton(true);
0286             mStatusLay->addWidget(mLastResultWidget);
0287 
0288             connect(mLastResultWidget, &ResultItemWidget::closeButtonClicked, q, [this]() {
0289                 removeLastResultItem();
0290             });
0291         }
0292 
0293         // Check result protocol
0294         if (mPGPRB) {
0295             auto proto = getProtocol(result);
0296             if (proto == GpgME::UnknownProtocol) {
0297                 proto = mPGPRB->isChecked() ? GpgME::OpenPGP : GpgME::CMS;
0298             } else if (proto == GpgME::OpenPGP) {
0299                 mPGPRB->setChecked(true);
0300             } else if (proto == GpgME::CMS) {
0301                 mCMSRB->setChecked(true);
0302             }
0303 
0304             KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Notepad"));
0305             config.writeEntry("wasCMS", proto == GpgME::CMS);
0306         }
0307 
0308         if (result->error()) {
0309             if (!result->errorString().isEmpty()) {
0310                 KMessageBox::error(q, result->errorString(), i18nc("@title", "Error in crypto action"));
0311             }
0312         } else if (!result->error().isCanceled()) {
0313             mEdit->setPlainText(QString::fromUtf8(mOutputData));
0314             mOutputData.clear();
0315             mRevertBtn->setVisible(true);
0316 
0317             const auto decryptVerifyResult = dynamic_cast<const Kleo::Crypto::DecryptVerifyResult *>(result.get());
0318             if (decryptVerifyResult) {
0319                 updateRecipientsFromResult(*decryptVerifyResult);
0320             }
0321         }
0322     }
0323 
0324     void doDecryptVerify()
0325     {
0326         doCryptoCommon();
0327         mSigEncWidget->clearAddedRecipients();
0328         mProgressLabel->setText(i18n("Decrypt / Verify") + QStringLiteral("..."));
0329         auto input = Input::createFromByteArray(&mInputData, i18n("Notepad"));
0330         auto output = Output::createFromByteArray(&mOutputData, i18n("Notepad"));
0331 
0332         AbstractDecryptVerifyTask *task;
0333         auto classification = input->classification();
0334         if (classification & Class::OpaqueSignature || classification & Class::ClearsignedMessage) {
0335             auto verifyTask = new VerifyOpaqueTask();
0336             verifyTask->setInput(input);
0337             verifyTask->setOutput(output);
0338             task = verifyTask;
0339         } else {
0340             auto decTask = new DecryptVerifyTask();
0341             decTask->setInput(input);
0342             decTask->setOutput(output);
0343             task = decTask;
0344         }
0345         try {
0346             task->autodetectProtocolFromInput();
0347         } catch (const Kleo::Exception &e) {
0348             KMessageBox::error(q, e.message(), i18nc("@title", "Error in crypto action"));
0349             updateButtons();
0350             mProgressBar->setVisible(false);
0351             mProgressLabel->setVisible(false);
0352             return;
0353         }
0354 
0355         connect(task, &Task::result, q, [this, task](const std::shared_ptr<const Kleo::Crypto::Task::Result> &result) {
0356             qCDebug(KLEOPATRA_LOG) << "Decrypt / Verify done. Err:" << result->error().code();
0357             task->deleteLater();
0358             cryptDone(result);
0359         });
0360         task->start();
0361     }
0362 
0363     void removeLastResultItem()
0364     {
0365         if (mLastResultWidget) {
0366             mStatusLay->removeWidget(mLastResultWidget);
0367             delete mLastResultWidget;
0368             mLastResultWidget = nullptr;
0369         }
0370     }
0371 
0372     void doCryptoCommon()
0373     {
0374         mCryptBtn->setEnabled(false);
0375         mDecryptBtn->setEnabled(false);
0376         mImportBtn->setEnabled(false);
0377         mProgressBar->setVisible(true);
0378         mProgressLabel->setVisible(true);
0379         mInputData = mEdit->toPlainText().toUtf8();
0380         removeLastResultItem();
0381     }
0382 
0383     void doEncryptSign()
0384     {
0385         if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) {
0386             KMessageBox::error(q->topLevelWidget(),
0387                                xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
0388                                       "<para>Sorry! You cannot use <application>Kleopatra</application> for signing or encryption "
0389                                       "because the <application>GnuPG</application> system used by <application>Kleopatra</application> is not %1.</para>",
0390                                       DeVSCompliance::name(true)));
0391             return;
0392         }
0393 
0394         mSigEncWidget->saveOwnKeys();
0395         doCryptoCommon();
0396         switch (mSigEncWidget->currentOp()) {
0397         case SignEncryptWidget::Sign:
0398             mProgressLabel->setText(i18nc("@info:progress", "Signing notepad..."));
0399             break;
0400         case SignEncryptWidget::Encrypt:
0401             mProgressLabel->setText(i18nc("@info:progress", "Encrypting notepad..."));
0402             break;
0403         case SignEncryptWidget::SignAndEncrypt:
0404             mProgressLabel->setText(i18nc("@info:progress", "Signing and encrypting notepad..."));
0405             break;
0406         default:;
0407         };
0408         auto input = Input::createFromByteArray(&mInputData, i18n("Notepad"));
0409         auto output = Output::createFromByteArray(&mOutputData, i18n("Notepad"));
0410 
0411         auto task = new SignEncryptTask();
0412         task->setInput(input);
0413         task->setOutput(output);
0414 
0415         const auto sigKey = mSigEncWidget->signKey();
0416 
0417         const std::vector<GpgME::Key> recipients = mSigEncWidget->recipients();
0418         const bool encrypt = mSigEncWidget->encryptSymmetric() || !recipients.empty();
0419         const bool sign = !sigKey.isNull();
0420 
0421         if (sign) {
0422             task->setSign(true);
0423             std::vector<GpgME::Key> signVector;
0424             signVector.push_back(sigKey);
0425             task->setSigners(signVector);
0426         } else {
0427             task->setSign(false);
0428         }
0429         task->setEncrypt(encrypt);
0430         task->setRecipients(recipients);
0431         task->setEncryptSymmetric(mSigEncWidget->encryptSymmetric());
0432         task->setAsciiArmor(true);
0433 
0434         if (sign && !encrypt && sigKey.protocol() == GpgME::OpenPGP) {
0435             task->setClearsign(true);
0436         }
0437 
0438         connect(task, &Task::result, q, [this, task](const std::shared_ptr<const Kleo::Crypto::Task::Result> &result) {
0439             qCDebug(KLEOPATRA_LOG) << "Encrypt / Sign done. Err:" << result->error().code();
0440             task->deleteLater();
0441             cryptDone(result);
0442         });
0443         task->start();
0444     }
0445 
0446     void doImport()
0447     {
0448         doCryptoCommon();
0449         mProgressLabel->setText(i18n("Importing..."));
0450         auto cmd = new Kleo::ImportCertificateFromDataCommand(mInputData, mImportProto);
0451         connect(cmd, &Kleo::ImportCertificatesCommand::finished, q, [this]() {
0452             updateButtons();
0453             mProgressBar->setVisible(false);
0454             mProgressLabel->setVisible(false);
0455 
0456             mRevertBtn->setVisible(true);
0457             mEdit->setPlainText(QString());
0458         });
0459         cmd->start();
0460     }
0461 
0462     void checkImportProtocol()
0463     {
0464         QGpgME::QByteArrayDataProvider dp(mEdit->toPlainText().toUtf8());
0465         GpgME::Data data(&dp);
0466         auto type = data.type();
0467         if (type == GpgME::Data::PGPKey) {
0468             mImportProto = GpgME::OpenPGP;
0469         } else if (type == GpgME::Data::X509Cert || type == GpgME::Data::PKCS12) {
0470             mImportProto = GpgME::CMS;
0471         } else {
0472             mImportProto = GpgME::UnknownProtocol;
0473         }
0474     }
0475 
0476     void updateButtons()
0477     {
0478         mAdditionalInfoLabel->setVisible(false);
0479 
0480         mDecryptBtn->setEnabled(mEdit->document() && !mEdit->document()->isEmpty());
0481 
0482         checkImportProtocol();
0483         mImportBtn->setEnabled(mImportProto != GpgME::UnknownProtocol);
0484 
0485         mCryptBtn->setEnabled(mSigEncWidget->currentOp() != SignEncryptWidget::NoOperation);
0486         switch (mSigEncWidget->currentOp()) {
0487         case SignEncryptWidget::Sign:
0488             mCryptBtn->setText(i18nc("@action:button", "Sign Notepad"));
0489             break;
0490         case SignEncryptWidget::Encrypt:
0491             mCryptBtn->setText(i18nc("@action:button", "Encrypt Notepad"));
0492             break;
0493         case SignEncryptWidget::SignAndEncrypt:
0494         default:
0495             mCryptBtn->setText(i18nc("@action:button", "Sign / Encrypt Notepad"));
0496         };
0497         if (!mSigEncWidget->isComplete()) {
0498             mCryptBtn->setEnabled(false);
0499         }
0500 
0501         if (DeVSCompliance::isActive()) {
0502             const bool de_vs = DeVSCompliance::isCompliant() && mSigEncWidget->isDeVsAndValid();
0503             DeVSCompliance::decorate(mCryptBtn, de_vs);
0504             mAdditionalInfoLabel->setText(DeVSCompliance::name(de_vs));
0505             mAdditionalInfoLabel->setVisible(true);
0506             if (!DeVSCompliance::isCompliant()) {
0507                 mCryptBtn->setEnabled(false);
0508             }
0509             mMessageWidget->setVisible(!DeVSCompliance::isCompliant());
0510         }
0511     }
0512 
0513 private:
0514     PadWidget *const q;
0515     QTextEdit *mEdit;
0516     QPushButton *mCryptBtn;
0517     QPushButton *mDecryptBtn;
0518     QPushButton *mImportBtn;
0519     QPushButton *mRevertBtn;
0520     KMessageWidget *mMessageWidget;
0521     QLabel *mAdditionalInfoLabel;
0522     QByteArray mInputData;
0523     QByteArray mOutputData;
0524     SignEncryptWidget *mSigEncWidget;
0525     QProgressBar *mProgressBar;
0526     QLabel *mProgressLabel;
0527     QVBoxLayout *mStatusLay;
0528     ResultItemWidget *mLastResultWidget;
0529     QList<GpgME::Key> mAutoAddedKeys;
0530     QRadioButton *mPGPRB;
0531     QRadioButton *mCMSRB;
0532     GpgME::Protocol mImportProto;
0533 };
0534 
0535 PadWidget::PadWidget(QWidget *parent)
0536     : QWidget(parent)
0537     , d(new Private(this))
0538 {
0539 }
0540 
0541 void PadWidget::focusFirstChild(Qt::FocusReason reason)
0542 {
0543     d->mEdit->setFocus(reason);
0544 }
0545 
0546 #include "moc_padwidget.cpp"