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"