diff --git a/src/crypto/decryptverifytask.cpp b/src/crypto/decryptverifytask.cpp --- a/src/crypto/decryptverifytask.cpp +++ b/src/crypto/decryptverifytask.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include @@ -294,7 +295,18 @@ if (key.isNull()) { return text += i18n("With unavailable certificate:") + QStringLiteral("
ID: 0x%1").arg(QString::fromLatin1(sig.fingerprint()).toUpper()); } - return text += i18n("With certificate:") + QStringLiteral("
") + renderKey(key); + text += i18n("With certificate:") + QStringLiteral("
") + renderKey(key); + + if (Kleo::gpgComplianceP("de-vs")) + text += + (QStringLiteral("
") + + (IS_DE_VS(sig) + ? i18nc("VS-conforming is a German standard for restricted documents for which special restrictions about algorithms apply. The string states that a signature is compliant with that.", + "The signature is VS-compliant.") + : i18nc("VS-conforming is a German standard for restricted documents for which special restrictions about algorithms apply. The string states that a signature is not compliant with that.", + "The signature is not VS-compliant."))); + + return text; } static QString strikeOut(const QString &str, bool strike) @@ -531,22 +543,31 @@ static QString formatDecryptionResultDetails(const DecryptionResult &res, const std::vector &recipients, const QString &errorString, bool isSigned) { + QString details; + if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) { return i18n("Input error: %1", errorString); } + if (Kleo::gpgComplianceP("de-vs")) + details += ((IS_DE_VS(res) + ? i18nc("VS-conforming is a German standard for restricted documents for which special restrictions about algorithms apply. The string states that the decryption is compliant with that.", + "The decryption is VS-compliant.") + : i18nc("VS-conforming is a German standard for restricted documents for which special restrictions about algorithms apply. The string states that the decryption is compliant with that.", + "The decryption is not VS-compliant.")) + + QStringLiteral("
")); + if (res.isNull() || !res.error() || res.error().isCanceled()) { if (!isSigned) { - return i18n("Note: You cannot be sure who encrypted this message as it is not signed."); + return details + i18n("Note: You cannot be sure who encrypted this message as it is not signed."); } - return QString(); + return details; } if (recipients.empty() && res.numRecipients() > 0) { - return QLatin1String("") + i18np("One unknown recipient.", "%1 unknown recipients.", res.numRecipients()) + QLatin1String(""); + return details + QLatin1String("") + i18np("One unknown recipient.", "%1 unknown recipients.", res.numRecipients()) + QLatin1String(""); } - QString details; if (!recipients.empty()) { details += i18np("Recipient:", "Recipients:", res.numRecipients()); if (res.numRecipients() == 1) { diff --git a/src/crypto/gui/signencryptfileswizard.h b/src/crypto/gui/signencryptfileswizard.h --- a/src/crypto/gui/signencryptfileswizard.h +++ b/src/crypto/gui/signencryptfileswizard.h @@ -102,6 +102,8 @@ QVector resolvedSigners() const; bool encryptSymmetric() const; + void setLabelText(const QString &label) const; + Q_SIGNALS: void operationPrepared(); @@ -111,6 +113,7 @@ private: SigEncPage *mSigEncPage; ResultPage *mResultPage; + QAbstractButton *mLabel; bool mSigningPreset, mSigningUserMutable, mEncryptionUserMutable, diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp --- a/src/crypto/gui/signencryptfileswizard.cpp +++ b/src/crypto/gui/signencryptfileswizard.cpp @@ -45,6 +45,7 @@ #include #include "kleopatra_debug.h" +#include #include #include @@ -78,6 +79,7 @@ public: explicit SigEncPage(QWidget *parent = nullptr) : QWizardPage(parent), + mParent((SignEncryptFilesWizard *) parent), mWidget(new SignEncryptWidget), mOutLayout(new QVBoxLayout), mArchive(false), @@ -284,10 +286,27 @@ private Q_SLOTS: void updateCommitButton(const QString &label) { + auto btn = mParent->button(QWizard::CommitButton); if (!label.isEmpty()) { - setButtonText(QWizard::CommitButton, label); + btn->setText(label); + if (Kleo::gpgComplianceP("de-vs")) { + bool de_vs = mWidget->isDeVsAndValid(); + btn->setIcon(QIcon::fromTheme(de_vs + ? "security-high" + : "security-medium")); + btn->setStyleSheet(de_vs + ? "background-color: lime" + : "background-color: red"); + mParent->setLabelText(de_vs + ? i18nc("VS-conforming is a German standard for restricted documents for which special restrictions about algorithms apply. The string states that all cryptographic operations necessary for the communication are compliant with that.", + "VS-conforming communication possible.") + : i18nc("VS-conforming is a German standard for restricted documents for which special restrictions about algorithms apply. The string states that all cryptographic operations necessary for the communication are compliant with that.", + "VS-conforming communication not possible.")); + } } else { - setButtonText(QWizard::CommitButton, i18n("Next")); + btn->setText(i18n("Next")); + btn->setIcon(QIcon()); + btn->setStyleSheet(""); } Q_EMIT completeChanged(); } @@ -324,6 +343,7 @@ } private: + SignEncryptFilesWizard *mParent; SignEncryptWidget *mWidget; QMap mOutNames; QMap mRequester; @@ -337,15 +357,24 @@ class ResultPage : public NewResultPage { Q_OBJECT + public: explicit ResultPage(QWidget *parent = nullptr) - : NewResultPage(parent) + : NewResultPage(parent), + mParent((SignEncryptFilesWizard *) parent) { setTitle(i18nc("@title", "Results")); setSubTitle(i18nc("@title", "Status and progress of the crypto operations is shown here.")); } + void initializePage() override + { + mParent->setLabelText(QString()); + } + +private: + SignEncryptFilesWizard *mParent; }; SignEncryptFilesWizard::SignEncryptFilesWizard(QWidget *parent, Qt::WindowFlags f) @@ -355,6 +384,7 @@ , mEncryptionUserMutable(false) , mEncryptionPreset(false) { + bool de_vs = Kleo::gpgComplianceP("de-vs"); #ifdef Q_OS_WIN // Enforce modern style to avoid vista style uglyness. setWizardStyle(QWizard::ModernStyle); @@ -367,17 +397,35 @@ setPage(SigEncPageId, mSigEncPage); setPage(ResultPageId, mResultPage); setOptions(QWizard::IndependentPages | + (de_vs ? QWizard::HaveCustomButton1 : (QWizard::WizardOption) 0) | QWizard::NoBackButtonOnLastPage | QWizard::NoBackButtonOnStartPage); + if (de_vs) { + /* We use a custom button to display a label next to the + buttons. */ + mLabel = button(QWizard::CustomButton1); + /* We style the button so that it looks and acts like a + label. */ + mLabel->setStyleSheet("border: none"); + mLabel->setFocusPolicy(Qt::NoFocus); + } else + mLabel = NULL; + KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); const QByteArray geom = cfgGroup.readEntry("geometry", QByteArray()); if (!geom.isEmpty()) { restoreGeometry(geom); return; } } +void SignEncryptFilesWizard::setLabelText(const QString &label) const +{ + if (mLabel) + mLabel->setText(label); +} + void SignEncryptFilesWizard::slotCurrentIdChanged(int id) { if (id == ResultPageId) { diff --git a/src/crypto/gui/signencryptwidget.h b/src/crypto/gui/signencryptwidget.h --- a/src/crypto/gui/signencryptwidget.h +++ b/src/crypto/gui/signencryptwidget.h @@ -74,6 +74,11 @@ /** Save the currently selected signing and encrypt to self keys. */ void saveOwnKeys() const; + /** Return whether or not all keys involved in the operation are + compliant with CO_DE_VS, and all keys are valid (i.e. all + userIDs have Validity >= Full). */ + bool isDeVsAndValid() const; + protected Q_SLOTS: void updateOp(); void recipientsChanged(); diff --git a/src/crypto/gui/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp --- a/src/crypto/gui/signencryptwidget.cpp +++ b/src/crypto/gui/signencryptwidget.cpp @@ -49,6 +49,8 @@ #include #include +#include + #include #include #include @@ -278,6 +280,24 @@ return ret; } +bool SignEncryptWidget::isDeVsAndValid() const +{ + if (! signKey().isNull() + && (! IS_DE_VS(signKey()) || keyValidity(signKey()) < GpgME::UserID::Validity::Full)) + return false; + + if (! selfKey().isNull() + && (! IS_DE_VS(selfKey()) + || keyValidity(selfKey()) < GpgME::UserID::Validity::Full)) + return false; + + for (const auto &key: recipients()) + if (! IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) + return false; + + return true; +} + void SignEncryptWidget::updateOp() { const Key sigKey = signKey(); @@ -293,10 +313,8 @@ } else { newOp = QString(); } - if (newOp != mOp) { - mOp = newOp; - Q_EMIT operationChanged(mOp); - } + mOp = newOp; + Q_EMIT operationChanged(mOp); Q_EMIT keysChanged(); } diff --git a/src/utils/gnupg-helper.h b/src/utils/gnupg-helper.h --- a/src/utils/gnupg-helper.h +++ b/src/utils/gnupg-helper.h @@ -34,6 +34,22 @@ #define __KLEOPATRA_GNUPGHELPER_H__ #include +#include + +/* Support compilation with GPGME older than 1.9. */ +#include +#if GPGMEPP_VERSION >= 0x10900 +# define GPGME_HAS_KEY_IS_DEVS +#endif + +/* Does the given object comply with DE_VS? This macro can be used to + ensure that we can still build against older versions of GPGME + without cluttering the code with preprocessor conditionals. */ +#ifdef GPGME_HAS_KEY_IS_DEVS +# define IS_DE_VS(x) (x).isDeVs() +#else +# define IS_DE_VS(x) 0 +#endif class QString; class QStringList; @@ -59,6 +75,8 @@ bool engineIsVersion(int major, int minor, int patch, GpgME::Engine = GpgME::GpgConfEngine); bool haveKeyserverConfigured(); +bool gpgComplianceP(const char *mode); +enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key); } #endif // __KLEOPATRA_GNUPGHELPER_H__ diff --git a/src/utils/gnupg-helper.cpp b/src/utils/gnupg-helper.cpp --- a/src/utils/gnupg-helper.cpp +++ b/src/utils/gnupg-helper.cpp @@ -40,6 +40,7 @@ #include #include +#include #include #include @@ -275,3 +276,24 @@ const QGpgME::CryptoConfigEntry *const entry = config->entry(QStringLiteral("gpg"), QStringLiteral("Keyserver"), QStringLiteral("keyserver")); return entry && !entry->stringValue().isEmpty(); } + +bool Kleo::gpgComplianceP(const char *mode) +{ + auto conf = QGpgME::cryptoConfig(); + return (conf->entry(QStringLiteral("gpg"), + QStringLiteral("Configuration"), + QStringLiteral("compliance"))->stringValue() + == QString(mode)); +} + +enum GpgME::UserID::Validity Kleo::keyValidity(const GpgME::Key &key) +{ + enum UserID::Validity validity = UserID::Validity::Unknown; + + for (const auto &uid: key.userIDs()) + if (validity == UserID::Validity::Unknown + || validity > uid.validity()) + validity = uid.validity(); + + return validity; +} diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp --- a/src/view/keytreeview.cpp +++ b/src/view/keytreeview.cpp @@ -207,6 +207,7 @@ /* TODO: Make this configurable by the user. E.g. kdepim/src/todo/todoview.cpp */ rearangingModel->setSourceColumns(QVector() << KeyListModelInterface::PrettyName << KeyListModelInterface::PrettyEMail + << KeyListModelInterface::Validity << KeyListModelInterface::ValidFrom << KeyListModelInterface::ValidUntil << KeyListModelInterface::TechnicalDetails @@ -216,6 +217,7 @@ std::vector defaultSizes; defaultSizes.push_back(280); defaultSizes.push_back(280); + defaultSizes.push_back(120); defaultSizes.push_back(100); defaultSizes.push_back(100); defaultSizes.push_back(80); diff --git a/src/view/searchbar.h b/src/view/searchbar.h --- a/src/view/searchbar.h +++ b/src/view/searchbar.h @@ -75,6 +75,8 @@ class Private; kdtools::pimpl_ptr d; Q_PRIVATE_SLOT(d, void slotKeyFilterChanged(int)) + Q_PRIVATE_SLOT(d, void listNotCertifiedKeys()) + Q_PRIVATE_SLOT(d, void showOrHideCertifyButton()) }; } diff --git a/src/view/searchbar.cpp b/src/view/searchbar.cpp --- a/src/view/searchbar.cpp +++ b/src/view/searchbar.cpp @@ -42,9 +42,15 @@ #include #include +#include #include +#include +#include +#include +#include + using namespace Kleo; class SearchBar::Private @@ -81,9 +87,38 @@ } } + void listNotCertifiedKeys() const + { + lineEdit->clear(); + combo->setCurrentIndex(combo->findData(QStringLiteral("not-validated-certificates"))); + Q_EMIT q->keyFilterChanged(keyFilter(combo->currentIndex())); + } + + /* List all OpenPGP keys and see if we find one with a UID that is + * not at least fully valid. If we find one, show the certify + * button. */ + /* XXX: It would be nice to do this every time the user certifies + * a key. */ + void showOrHideCertifyButton() const + { + QGpgME::KeyListJob *job = QGpgME::openpgp()->keyListJob(); + connect(job, &QGpgME::KeyListJob::result, job, + [this, job](GpgME::KeyListResult, std::vector keys, QString, GpgME::Error) + { + for (const auto &key: keys) + if (Kleo::keyValidity(key) < GpgME::UserID::Validity::Full) { + certifyButton->show(); + return; + } + certifyButton->hide(); + }); + job->start(QStringList()); + } + private: QLineEdit *lineEdit; QComboBox *combo; + QPushButton *certifyButton; }; SearchBar::Private::Private(SearchBar *qq) @@ -97,15 +132,27 @@ layout->addWidget(lineEdit, /*stretch=*/1); combo = new QComboBox(q); layout->addWidget(combo); + certifyButton = new QPushButton(q); + certifyButton->setIcon(QIcon::fromTheme("security-medium")); + certifyButton->setToolTip(i18n("Some certificates are not yet certified. " + "Click here to see a list of these certificates." + "

" + "Certification is required to make sure that the certificates " + "actually belong to the identity they claim to belong to.")); + certifyButton->hide(); + layout->addWidget(certifyButton); + showOrHideCertifyButton(); combo->setModel(KeyFilterManager::instance()->model()); KDAB_SET_OBJECT_NAME(layout); KDAB_SET_OBJECT_NAME(lineEdit); KDAB_SET_OBJECT_NAME(combo); + KDAB_SET_OBJECT_NAME(certifyButton); connect(lineEdit, &QLineEdit::textChanged, q, &SearchBar::stringFilterChanged); connect(combo, SIGNAL(currentIndexChanged(int)), q, SLOT(slotKeyFilterChanged(int))); + connect(certifyButton, SIGNAL(clicked()), q, SLOT(listNotCertifiedKeys())); } SearchBar::Private::~Private() {}