Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/exportpaperkeycommand.cpp b/src/commands/exportpaperkeycommand.cpp
index 8eef6b9f9..44cda9ba6 100644
--- a/src/commands/exportpaperkeycommand.cpp
+++ b/src/commands/exportpaperkeycommand.cpp
@@ -1,128 +1,128 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportpaperkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportpaperkeycommand.h"
#include <Libkleo/GnuPG>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <QPrinter>
#include <QPrintDialog>
#include <QTextDocument>
#include <QFontDatabase>
#include "kleopatra_debug.h"
#include "command_p.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
ExportPaperKeyCommand::ExportPaperKeyCommand(QAbstractItemView *v, KeyListController *c) :
GnuPGProcessCommand(v, c),
mParent(v)
{
#if QT_DEPRECATED_SINCE(5, 13)
connect(&mPkProc, qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
#else
connect(&mPkProc, &QProcess::finished,
#endif
this, &ExportPaperKeyCommand::pkProcFinished);
mPkProc.setProgram(paperKeyInstallPath());
mPkProc.setArguments(QStringList() << QStringLiteral("--output-type=base16"));
process()->setStandardOutputProcess(&mPkProc);
qCDebug(KLEOPATRA_LOG) << "Starting PaperKey process.";
mPkProc.start();
setAutoDelete(false);
}
QStringList ExportPaperKeyCommand::arguments() const
{
const Key key = d->key();
QStringList result;
result << gpgPath() << QStringLiteral("--batch");
result << QStringLiteral("--export-secret-key");
result << QLatin1String(key.primaryFingerprint());
return result;
}
bool ExportPaperKeyCommand::preStartHook(QWidget *parent) const
{
if (paperKeyInstallPath().isNull()) {
- KMessageBox::sorry(parent, xi18nc("@info", "<para><application>Kleopatra</application> uses "
+ KMessageBox::error(parent, xi18nc("@info", "<para><application>Kleopatra</application> uses "
"<application>PaperKey</application> to create a minimized and"
" printable version of your secret key.</para>"
"<para>Please make sure it is installed.</para>"),
i18nc("@title", "Failed to find PaperKey executable."));
return false;
}
return true;
}
void ExportPaperKeyCommand::pkProcFinished(int code, QProcess::ExitStatus status)
{
qCDebug(KLEOPATRA_LOG) << "Paperkey export finished: " << code << "status: " << status;
if (status == QProcess::CrashExit || code) {
qCDebug(KLEOPATRA_LOG) << "Aborting because paperkey failed";
deleteLater();
return;
}
QPrinter printer;
const Key key = d->key();
printer.setDocName(QStringLiteral("0x%1-sec").arg(QString::fromLatin1(key.shortKeyID())));
QPrintDialog printDialog(&printer, mParent);
printDialog.setWindowTitle(i18nc("@title:window", "Print Secret Key"));
if (printDialog.exec() != QDialog::Accepted) {
qCDebug(KLEOPATRA_LOG) << "Printing aborted.";
deleteLater();
return;
}
QTextDocument doc(QString::fromLatin1(mPkProc.readAllStandardOutput()));
doc.setDefaultFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
doc.print(&printer);
deleteLater();
}
QString ExportPaperKeyCommand::errorCaption() const
{
return i18nc("@title:window", "Error printing secret key");
}
QString ExportPaperKeyCommand::crashExitMessage(const QStringList &args) const
{
return xi18nc("@info",
"<para>The GPG process that tried to export the secret key "
"ended prematurely because of an unexpected error.</para>"
"<para>Please check the output of <icode>%1</icode> for details.</para>",
args.join(QLatin1Char(' ')));
}
QString ExportPaperKeyCommand::errorExitMessage(const QStringList &args) const
{
return xi18nc("@info",
"<para>An error occurred while trying to export the secret key.</para> "
"<para>The output from <command>%1</command> was: <message>%2</message></para>",
args[0], errorString());
}
diff --git a/src/commands/importpaperkeycommand.cpp b/src/commands/importpaperkeycommand.cpp
index 8652b5fdc..18fba732b 100644
--- a/src/commands/importpaperkeycommand.cpp
+++ b/src/commands/importpaperkeycommand.cpp
@@ -1,220 +1,220 @@
/* commands/importperkeycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "importpaperkeycommand.h"
#include <Libkleo/GnuPG>
#include <gpgme++/key.h>
#include <gpgme++/importresult.h>
#include <QGpgME/Protocol>
#include <QGpgME/ImportJob>
#include <QGpgME/ExportJob>
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <KMessageBox>
#include <QFileDialog>
#include <QTextStream>
#include "kleopatra_debug.h"
#include "command_p.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
ImportPaperKeyCommand::ImportPaperKeyCommand(const GpgME::Key &k) :
GnuPGProcessCommand(k)
{
}
QStringList ImportPaperKeyCommand::arguments() const
{
const Key key = d->key();
QStringList result;
result << paperKeyInstallPath() << QStringLiteral("--pubring")
<< mTmpDir.path() + QStringLiteral("/pubkey.gpg")
<< QStringLiteral("--secrets")
<< mTmpDir.path() + QStringLiteral("/secrets.txt")
<< QStringLiteral("--output")
<< mTmpDir.path() + QStringLiteral("/seckey.gpg");
return result;
}
void ImportPaperKeyCommand::exportResult(const GpgME::Error &err, const QByteArray &data)
{
if (err) {
d->error(QString::fromUtf8(err.asString()), errorCaption());
d->finished();
return;
}
if (!mTmpDir.isValid()) {
// Should not happen so no i18n
d->error(QStringLiteral("Failed to get temporary directory"), errorCaption());
qCWarning(KLEOPATRA_LOG) << "Failed to get temporary dir";
d->finished();
return;
}
const QString fileName = mTmpDir.path() + QStringLiteral("/pubkey.gpg");
QFile f(fileName);
if (!f.open(QIODevice::WriteOnly)) {
d->error(QStringLiteral("Failed to create temporary file"), errorCaption());
qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file";
d->finished();
return;
}
f.write(data);
f.close();
// Copy and sanitize input a bit
QFile input(mFileName);
if (!input.open(QIODevice::ReadOnly)) {
d->error(xi18n("Cannot open <filename>%1</filename> for reading.", mFileName), errorCaption());
d->finished();
return;
}
const QString outName = mTmpDir.path() + QStringLiteral("/secrets.txt");
QFile out(outName);
if (!out.open(QIODevice::WriteOnly)) {
// Should not happen
d->error(QStringLiteral("Failed to create temporary file"), errorCaption());
qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file for writing";
d->finished();
return;
}
QTextStream in(&input);
while (!in.atEnd()) {
// Paperkey is picky, tabs may not be part. Neither may be empty lines.
const QString line = in.readLine().trimmed().replace(QLatin1Char('\t'), QStringLiteral(" ")) +
QLatin1Char('\n');
out.write(line.toUtf8());
}
input.close();
out.close();
GnuPGProcessCommand::doStart();
}
void ImportPaperKeyCommand::postSuccessHook(QWidget *)
{
qCDebug(KLEOPATRA_LOG) << "Paperkey secrets restore finished successfully.";
QFile secKey(mTmpDir.path() + QStringLiteral("/seckey.gpg"));
if (!secKey.open(QIODevice::ReadOnly)) {
d->error(QStringLiteral("Failed to open temporary secret"), errorCaption());
qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file";
Q_EMIT finished();
return;
}
auto data = secKey.readAll();
secKey.close();
auto importjob = QGpgME::openpgp()->importJob();
auto result = importjob->exec(data);
delete importjob;
if (result.error()) {
d->error(QString::fromUtf8(result.error().asString()), errorCaption());
Q_EMIT finished();
return;
}
if (!result.numSecretKeysImported() ||
(result.numSecretKeysUnchanged() == result.numSecretKeysImported())) {
d->error(i18n("Failed to restore any secret keys."), errorCaption());
Q_EMIT finished();
return;
}
// Refresh the key after success
KeyCache::mutableInstance()->reload(OpenPGP);
Q_EMIT finished();
d->information(xi18nc("@info", "Successfully restored the secret key parts from <filename>%1</filename>",
mFileName));
return;
}
void ImportPaperKeyCommand::doStart()
{
if (paperKeyInstallPath().isNull()) {
- KMessageBox::sorry(d->parentWidgetOrView(),
+ KMessageBox::error(d->parentWidgetOrView(),
xi18nc("@info", "<para><application>Kleopatra</application> uses "
"<application>PaperKey</application> to import your "
"text backup.</para>"
"<para>Please make sure it is installed.</para>"),
i18nc("@title", "Failed to find PaperKey executable."));
return;
}
mFileName = QFileDialog::getOpenFileName(d->parentWidgetOrView(), i18n("Select input file"),
QString(),
QStringLiteral("%1 (*.txt)").arg(i18n("Paper backup"))
#ifdef Q_OS_WIN
/* For whatever reason at least with Qt 5.6.1 the native file dialog crashes in
* my (aheinecke) Windows 10 environment when invoked here.
* In other places it works, with the same arguments as in other places (e.g. import)
* it works. But not here. Maybe it's our (gpg4win) build? But why did it only
* crash here?
*
* It does not crash immediately, the program flow continues for a while before it
* crashes so this is hard to debug.
*
* There are some reports about this
* QTBUG-33119 QTBUG-41416 where different people describe "bugs" but they
* describe them differently also not really reproducible.
* Anyway this works for now and for such an exotic feature its good enough for now.
*/
, 0, QFileDialog::DontUseNativeDialog
#endif
);
if (mFileName.isEmpty()) {
d->finished();
return;
}
auto exportJob = QGpgME::openpgp()->publicKeyExportJob();
connect(exportJob, &QGpgME::ExportJob::result, this, &ImportPaperKeyCommand::exportResult);
exportJob->start(QStringList() << QLatin1String(d->key().primaryFingerprint()));
}
QString ImportPaperKeyCommand::errorCaption() const
{
return i18nc("@title:window", "Error importing secret key");
}
QString ImportPaperKeyCommand::crashExitMessage(const QStringList &args) const
{
return xi18nc("@info",
"<para>The GPG process that tried to restore the secret key "
"ended prematurely because of an unexpected error.</para>"
"<para>Please check the output of <icode>%1</icode> for details.</para>",
args.join(QLatin1Char(' ')));
}
QString ImportPaperKeyCommand::errorExitMessage(const QStringList &args) const
{
return xi18nc("@info",
"<para>An error occurred while trying to restore the secret key.</para> "
"<para>The output from <command>%1</command> was:</para>"
"<para><message>%2</message></para>",
args[0], errorString());
}
QString ImportPaperKeyCommand::successMessage(const QStringList &) const
{
return QString();
}
diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp
index 600b72908..1d796f7a4 100644
--- a/src/crypto/gui/signencryptfileswizard.cpp
+++ b/src/crypto/gui/signencryptfileswizard.cpp
@@ -1,687 +1,687 @@
/* crypto/gui/signencryptfileswizard.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "signencryptfileswizard.h"
#include "signencryptwidget.h"
#include "newresultpage.h"
#include <fileoperationspreferences.h>
#include <settings.h>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KColorScheme>
#include <KConfigGroup>
#include <KWindowConfig>
#include <KMessageBox>
#include <KMessageWidget>
#include "kleopatra_debug.h"
#include <Libkleo/Compliance>
#include <Libkleo/GnuPG>
#include <Libkleo/Formatting>
#include <Libkleo/SystemInfo>
#include <Libkleo/FileNameRequester>
#include <QWindow>
#include <QVBoxLayout>
#include <QWizardPage>
#include <QGroupBox>
#include <QLabel>
#include <QIcon>
#include <QCheckBox>
#include <QPushButton>
#include <QStyle>
#include <gpgme++/key.h>
#include <array>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Crypto::Gui;
enum Page {
SigEncPageId,
ResultPageId,
NumPages
};
class FileNameRequesterWithIcon : public QWidget
{
Q_OBJECT
public:
explicit FileNameRequesterWithIcon(QDir::Filters filter, QWidget *parent = nullptr)
: QWidget(parent)
{
auto layout = new QHBoxLayout{this};
layout->setContentsMargins(0, 0, 0, 0);
mIconLabel = new QLabel{this};
mRequester = new FileNameRequester{filter, this};
mRequester->setExistingOnly(false);
layout->addWidget(mIconLabel);
layout->addWidget(mRequester);
setFocusPolicy(mRequester->focusPolicy());
setFocusProxy(mRequester);
connect(mRequester, &FileNameRequester::fileNameChanged,
this, &FileNameRequesterWithIcon::fileNameChanged);
}
void setIcon(const QIcon &icon)
{
mIconLabel->setPixmap(icon.pixmap(32, 32));
}
void setFileName(const QString &name)
{
mRequester->setFileName(name);
}
QString fileName() const
{
return mRequester->fileName();
}
void setNameFilter(const QString &nameFilter)
{
mRequester->setNameFilter(nameFilter);
}
QString nameFilter() const
{
return mRequester->nameFilter();
}
FileNameRequester *requester()
{
return mRequester;
}
Q_SIGNALS:
void fileNameChanged(const QString &filename);
protected:
bool event(QEvent *e) override
{
if (e->type() == QEvent::ToolTipChange) {
mRequester->setToolTip(toolTip());
}
return QWidget::event(e);
}
private:
QLabel *mIconLabel;
FileNameRequester *mRequester;
};
class SigEncPage: public QWizardPage
{
Q_OBJECT
public:
explicit SigEncPage(QWidget *parent = nullptr)
: QWizardPage(parent),
mParent((SignEncryptFilesWizard *) parent),
mWidget(new SignEncryptWidget),
mOutLayout(new QVBoxLayout),
mOutputLabel{nullptr},
mArchive(false),
mUseOutputDir(false),
mSingleFile{true}
{
setTitle(i18nc("@title", "Sign / Encrypt Files"));
auto vLay = new QVBoxLayout(this);
vLay->setContentsMargins(0, 0, 0, 0);
if (!Settings{}.cmsEnabled()) {
mWidget->setProtocol(GpgME::OpenPGP);
}
mWidget->setSignAsText(i18nc("@option:check on SignEncryptPage",
"&Sign as:"));
mWidget->setEncryptForMeText(i18nc("@option:check on SignEncryptPage",
"Encrypt for &me:"));
mWidget->setEncryptForOthersText(i18nc("@option:check on SignEncryptPage",
"Encrypt for &others:"));
mWidget->setEncryptWithPasswordText(i18nc("@option:check on SignEncryptPage",
"Encrypt with &password. Anyone you share the password with can read the data."));
vLay->addWidget(mWidget);
connect(mWidget, &SignEncryptWidget::operationChanged, this,
&SigEncPage::updateCommitButton);
connect(mWidget, &SignEncryptWidget::keysChanged, this,
&SigEncPage::updateFileWidgets);
auto outputGrp = new QGroupBox(i18nc("@title:group", "Output"));
outputGrp->setLayout(mOutLayout);
mPlaceholderWidget = new QLabel(i18n("Please select an action."));
mOutLayout->addWidget(mPlaceholderWidget);
mOutputLabel = new QLabel(i18nc("@label on SignEncryptPage", "Output &files/folder:"));
mOutLayout->addWidget(mOutputLabel);
createRequesters(mOutLayout);
mUseOutputDirChk = new QCheckBox(i18nc("@option:check on SignEncryptPage",
"Encrypt / Sign &each file separately."));
mUseOutputDirChk->setToolTip(i18nc("@info:tooltip",
"Keep each file separate instead of creating an archive for all."));
mOutLayout->addWidget(mUseOutputDirChk);
connect (mUseOutputDirChk, &QCheckBox::toggled, this, [this] (bool state) {
mUseOutputDir = state;
mArchive = !mUseOutputDir && !mSingleFile;
updateFileWidgets();
});
vLay->addWidget(outputGrp);
auto messageWidget = new KMessageWidget;
messageWidget->setMessageType(KMessageWidget::Error);
messageWidget->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this));
messageWidget->setText(i18n("Signing and encrypting files is not possible."));
messageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"<para>You cannot use <application>Kleopatra</application> for signing or encrypting files "
"because the <application>GnuPG</application> system used by <application>Kleopatra</application> is not %1.</para>",
DeVSCompliance::name(true)));
messageWidget->setCloseButtonVisible(false);
messageWidget->setVisible(DeVSCompliance::isActive() && !DeVSCompliance::isCompliant());
vLay->addWidget(messageWidget);
setMinimumHeight(300);
}
void setEncryptionPreset(bool value)
{
mWidget->setEncryptionChecked(value);
}
void setSigningPreset(bool value)
{
mWidget->setSigningChecked(value);
}
bool isComplete() const override
{
if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) {
return false;
}
return mWidget->isComplete();
}
int nextId() const override
{
return ResultPageId;
}
void initializePage() override
{
setCommitPage(true);
updateCommitButton(mWidget->currentOp());
}
void setArchiveForced(bool archive)
{
mArchive = archive;
setArchiveMutable(!archive);
}
void setArchiveMutable(bool archive)
{
mUseOutputDirChk->setVisible(archive);
if (archive) {
const KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard");
mUseOutputDirChk->setChecked(archCfg.readEntry("LastUseOutputDir", false));
} else {
mUseOutputDirChk->setChecked(false);
}
}
void setSingleFile(bool singleFile)
{
mSingleFile = singleFile;
mArchive = !mUseOutputDir && !mSingleFile;
}
bool validatePage() override
{
if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) {
- KMessageBox::sorry(topLevelWidget(),
+ KMessageBox::error(topLevelWidget(),
xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"<para>Sorry! You cannot use <application>Kleopatra</application> for signing or encrypting files "
"because the <application>GnuPG</application> system used by <application>Kleopatra</application> is not %1.</para>",
DeVSCompliance::name(true)));
return false;
}
bool sign = !mWidget->signKey().isNull();
bool encrypt = !mWidget->selfKey().isNull() || !mWidget->recipients().empty();
if (!mWidget->validate()) {
return false;
}
mWidget->saveOwnKeys();
if (mUseOutputDirChk->isVisible()) {
KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard");
archCfg.writeEntry("LastUseOutputDir", mUseOutputDir);
}
if (sign && !encrypt && mArchive) {
return KMessageBox::warningContinueCancel(this,
xi18nc("@info",
"<para>Archiving in combination with sign-only currently requires what are known as opaque signatures - "
"unlike detached ones, these embed the content in the signature.</para>"
"<para>This format is rather unusual. You might want to archive the files separately, "
"and then sign the archive as one file with Kleopatra.</para>"
"<para>Future versions of Kleopatra are expected to also support detached signatures in this case.</para>"),
i18nc("@title:window", "Unusual Signature Warning"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QStringLiteral("signencryptfileswizard-archive+sign-only-warning"))
== KMessageBox::Continue;
} else if (sign && !encrypt) {
return true;
}
if (!mWidget->selfKey().isNull() || mWidget->encryptSymmetric()) {
return true;
}
const auto recipientKeys = recipients();
const bool hasSecret = std::any_of(std::begin(recipientKeys), std::end(recipientKeys),
[](const auto &k) { return k.hasSecret(); });
if (!hasSecret) {
if (KMessageBox::warningContinueCancel(this,
xi18nc("@info",
"<para>None of the recipients you are encrypting to seems to be your own.</para>"
"<para>This means that you will not be able to decrypt the data anymore, once encrypted.</para>"
"<para>Do you want to continue, or cancel to change the recipient selection?</para>"),
i18nc("@title:window", "Encrypt-To-Self Warning"),
KStandardGuiItem::cont(),
KStandardGuiItem::cancel(),
QStringLiteral("warn-encrypt-to-non-self"), KMessageBox::Notify | KMessageBox::Dangerous)
== KMessageBox::Cancel) {
return false;
}
}
return true;
}
std::vector<Key> recipients() const
{
return mWidget->recipients();
}
/* In the future we might find a usecase for multiple
* signers */
std::vector<Key> signers() const
{
const Key k = mWidget->signKey();
if (!k.isNull()) {
return {k};
}
return {};
}
private:
struct RequesterInfo {
SignEncryptFilesWizard::KindNames id;
QString icon;
QString toolTip;
QString accessibleName;
QString nameFilterBinary;
QString nameFilterAscii;
};
void createRequesters(QBoxLayout *lay) {
static const std::array<RequesterInfo, 6> requestersInfo = {{
{
SignEncryptFilesWizard::SignatureCMS,
QStringLiteral("document-sign"),
i18nc("@info:tooltip", "This is the filename of the S/MIME signature."),
i18nc("Lineedit accessible name", "S/MIME signature file"),
i18nc("Name filter binary", "S/MIME Signatures (*.p7s)"),
i18nc("Name filter ASCII", "S/MIME Signatures (*.p7s *.pem)"),
},
{
SignEncryptFilesWizard::SignaturePGP,
QStringLiteral("document-sign"),
i18nc("@info:tooltip", "This is the filename of the detached OpenPGP signature."),
i18nc("Lineedit accessible name", "OpenPGP signature file"),
i18nc("Name filter binary", "OpenPGP Signatures (*.sig *.pgp)"),
i18nc("Name filter ASCII", "OpenPGP Signatures (*.asc *.sig)"),
},
{
SignEncryptFilesWizard::CombinedPGP,
QStringLiteral("document-edit-sign-encrypt"),
i18nc("@info:tooltip", "This is the filename of the OpenPGP-signed and encrypted file."),
i18nc("Lineedit accessible name", "OpenPGP signed and encrypted file"),
i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"),
i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"),
},
{
SignEncryptFilesWizard::EncryptedPGP,
QStringLiteral("document-encrypt"),
i18nc("@info:tooltip", "This is the filename of the OpenPGP encrypted file."),
i18nc("Lineedit accessible name", "OpenPGP encrypted file"),
i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"),
i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"),
},
{
SignEncryptFilesWizard::EncryptedCMS,
QStringLiteral("document-encrypt"),
i18nc("@info:tooltip", "This is the filename of the S/MIME encrypted file."),
i18nc("Lineedit accessible name", "S/MIME encrypted file"),
i18nc("Name filter binary", "S/MIME Files (*.p7m)"),
i18nc("Name filter ASCII", "S/MIME Files (*.p7m *.pem)"),
},
{
SignEncryptFilesWizard::Directory,
QStringLiteral("folder"),
i18nc("@info:tooltip", "The resulting files are written to this directory."),
i18nc("Lineedit accessible name", "Output directory"),
{},
{},
},
}};
if (!mRequesters.empty()) {
return;
}
const bool isAscii = FileOperationsPreferences().addASCIIArmor();
for (const auto &requester : requestersInfo) {
const auto id = requester.id;
auto requesterWithIcon = new FileNameRequesterWithIcon{id == SignEncryptFilesWizard::Directory ? QDir::Dirs : QDir::Files, this};
requesterWithIcon->setIcon(QIcon::fromTheme(requester.icon));
requesterWithIcon->setToolTip(requester.toolTip);
requesterWithIcon->requester()->setAccessibleNameOfLineEdit(requester.accessibleName);
requesterWithIcon->setNameFilter(isAscii ? requester.nameFilterAscii : requester.nameFilterBinary);
lay->addWidget(requesterWithIcon);
connect(requesterWithIcon, &FileNameRequesterWithIcon::fileNameChanged, this, [this, id](const QString &newName) {
mOutNames[id] = newName;
});
mRequesters.insert(id, requesterWithIcon);
}
}
public:
void setOutputNames(const QMap<int, QString> &names) {
Q_ASSERT(mOutNames.isEmpty());
for (auto it = std::begin(names); it != std::end(names); ++it) {
mRequesters.value(it.key())->setFileName(it.value());
}
mOutNames = names;
updateFileWidgets();
}
QMap <int, QString> outputNames() const {
if (!mUseOutputDir) {
auto ret = mOutNames;
ret.remove(SignEncryptFilesWizard::Directory);
return ret;
}
return mOutNames;
}
bool encryptSymmetric() const
{
return mWidget->encryptSymmetric();
}
private Q_SLOTS:
void updateCommitButton(const QString &label)
{
if (mParent->currentPage() != this) {
return;
}
auto btn = qobject_cast<QPushButton*>(mParent->button(QWizard::CommitButton));
if (!label.isEmpty()) {
mParent->setButtonText(QWizard::CommitButton, label);
if (DeVSCompliance::isActive()) {
const bool de_vs = DeVSCompliance::isCompliant() && mWidget->isDeVsAndValid();
DeVSCompliance::decorate(btn, de_vs);
mParent->setLabelText(de_vs
? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"%1 communication possible.", DeVSCompliance::name(true))
: i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"%1 communication not possible.", DeVSCompliance::name(true)));
}
} else {
mParent->setButtonText(QWizard::CommitButton, i18n("Next"));
btn->setIcon(QIcon());
btn->setStyleSheet(QString());
}
Q_EMIT completeChanged();
}
void updateFileWidgets()
{
if (mRequesters.isEmpty()) {
return;
}
const std::vector<Key> recipients = mWidget->recipients();
const Key sigKey = mWidget->signKey();
const bool pgp = mWidget->encryptSymmetric() ||
std::any_of(std::cbegin(recipients), std::cend(recipients),
[](const auto &k) { return k.protocol() == Protocol::OpenPGP; });
const bool cms = std::any_of(std::cbegin(recipients), std::cend(recipients),
[](const auto &k) { return k.protocol() == Protocol::CMS; });
mOutLayout->setEnabled(false);
if (cms || pgp || !sigKey.isNull()) {
mPlaceholderWidget->setVisible(false);
mOutputLabel->setVisible(true);
mRequesters[SignEncryptFilesWizard::SignatureCMS]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::CMS);
mRequesters[SignEncryptFilesWizard::EncryptedCMS]->setVisible(!mUseOutputDir && cms);
mRequesters[SignEncryptFilesWizard::CombinedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && pgp);
mRequesters[SignEncryptFilesWizard::EncryptedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() != Protocol::OpenPGP && pgp);
mRequesters[SignEncryptFilesWizard::SignaturePGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && !pgp);
mRequesters[SignEncryptFilesWizard::Directory]->setVisible(mUseOutputDir);
auto firstNotHidden = std::find_if(std::cbegin(mRequesters), std::cend(mRequesters),
[](auto w) { return !w->isHidden(); });
mOutputLabel->setBuddy(*firstNotHidden);
} else {
mPlaceholderWidget->setVisible(true);
mOutputLabel->setVisible(false);
std::for_each(std::cbegin(mRequesters), std::cend(mRequesters),
[](auto w) { w->setVisible(false); });
mOutputLabel->setBuddy(nullptr);
}
mOutLayout->setEnabled(true);
}
private:
SignEncryptFilesWizard *mParent;
SignEncryptWidget *mWidget;
QMap <int, QString> mOutNames;
QMap <int, FileNameRequesterWithIcon *> mRequesters;
QVBoxLayout *mOutLayout;
QWidget *mPlaceholderWidget;
QCheckBox *mUseOutputDirChk;
QLabel *mOutputLabel;
bool mArchive;
bool mUseOutputDir;
bool mSingleFile;
};
class ResultPage : public NewResultPage
{
Q_OBJECT
public:
explicit ResultPage(QWidget *parent = nullptr)
: 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)
: QWizard(parent, f)
{
readConfig();
const bool de_vs = DeVSCompliance::isActive();
#ifdef Q_OS_WIN
// Enforce modern style to avoid vista style ugliness.
setWizardStyle(QWizard::ModernStyle);
#endif
mSigEncPage = new SigEncPage(this);
mResultPage = new ResultPage(this);
connect(this, &QWizard::currentIdChanged, this,
&SignEncryptFilesWizard::slotCurrentIdChanged);
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. */
auto btn = button(QWizard::CustomButton1);
/* We style the button so that it looks and acts like a
label. */
btn->setStyleSheet(QStringLiteral("border: none"));
btn->setFocusPolicy(Qt::NoFocus);
}
}
void SignEncryptFilesWizard::setLabelText(const QString &label)
{
button(QWizard::CommitButton)->setToolTip(label);
setButtonText(QWizard::CustomButton1, label);
}
void SignEncryptFilesWizard::slotCurrentIdChanged(int id)
{
if (id == ResultPageId) {
Q_EMIT operationPrepared();
}
}
SignEncryptFilesWizard::~SignEncryptFilesWizard()
{
qCDebug(KLEOPATRA_LOG);
writeConfig();
}
void SignEncryptFilesWizard::setSigningPreset(bool preset)
{
mSigEncPage->setSigningPreset(preset);
}
void SignEncryptFilesWizard::setSigningUserMutable(bool mut)
{
if (mut == mSigningUserMutable) {
return;
}
mSigningUserMutable = mut;
}
void SignEncryptFilesWizard::setEncryptionPreset(bool preset)
{
mSigEncPage->setEncryptionPreset(preset);
}
void SignEncryptFilesWizard::setEncryptionUserMutable(bool mut)
{
if (mut == mEncryptionUserMutable) {
return;
}
mEncryptionUserMutable = mut;
}
void SignEncryptFilesWizard::setArchiveForced(bool archive)
{
mSigEncPage->setArchiveForced(archive);
}
void SignEncryptFilesWizard::setArchiveMutable(bool archive)
{
mSigEncPage->setArchiveMutable(archive);
}
void SignEncryptFilesWizard::setSingleFile(bool singleFile)
{
mSigEncPage->setSingleFile(singleFile);
}
std::vector<Key> SignEncryptFilesWizard::resolvedRecipients() const
{
return mSigEncPage->recipients();
}
std::vector<Key> SignEncryptFilesWizard::resolvedSigners() const
{
return mSigEncPage->signers();
}
void SignEncryptFilesWizard::setTaskCollection(const std::shared_ptr<Kleo::Crypto::TaskCollection> &coll)
{
mResultPage->setTaskCollection(coll);
}
void SignEncryptFilesWizard::setOutputNames(const QMap<int, QString> &map) const
{
mSigEncPage->setOutputNames(map);
}
QMap<int, QString> SignEncryptFilesWizard::outputNames() const
{
return mSigEncPage->outputNames();
}
bool SignEncryptFilesWizard::encryptSymmetric() const
{
return mSigEncPage->encryptSymmetric();
}
void SignEncryptFilesWizard::readConfig()
{
winId(); // ensure there's a window created
// set default window size
windowHandle()->resize(640, 480);
// restore size from config file
KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard");
KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup);
// NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform
// window was created -> QTBUG-40584. We therefore copy the size here.
// TODO: remove once this was resolved in QWidget QPA
resize(windowHandle()->size());
}
void SignEncryptFilesWizard::writeConfig()
{
KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard");
KWindowConfig::saveWindowSize(windowHandle(), cfgGroup);
cfgGroup.sync();
}
#include "signencryptfileswizard.moc"
diff --git a/src/dialogs/certifywidget.cpp b/src/dialogs/certifywidget.cpp
index bf11d5de8..21b88e786 100644
--- a/src/dialogs/certifywidget.cpp
+++ b/src/dialogs/certifywidget.cpp
@@ -1,720 +1,720 @@
/* dialogs/certifywidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2019, 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certifywidget.h"
#include <utils/accessibility.h>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <KConfigGroup>
#include <KDateComboBox>
#include <KMessageBox>
#include <KMessageWidget>
#include <KSeparator>
#include <KSharedConfig>
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/Predicates>
#include <QGpgME/ChangeOwnerTrustJob>
#include <QGpgME/Protocol>
#include <QAction>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QListView>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QScrollArea>
#include <QStandardItemModel>
#include <QToolButton>
#include <QToolTip>
#include <QVBoxLayout>
#include <gpgme++/key.h>
using namespace Kleo;
namespace {
// Maybe move this in its own file
// based on code from StackOverflow
class AnimatedExpander: public QWidget
{
Q_OBJECT
public:
explicit AnimatedExpander(const QString &title = QString(),
QWidget *parent = nullptr);
void setContentLayout(QLayout *contentLayout);
private:
QGridLayout mainLayout;
QToolButton toggleButton;
QFrame headerLine;
QParallelAnimationGroup toggleAnimation;
QWidget contentArea;
int animationDuration{300};
};
AnimatedExpander::AnimatedExpander(const QString &title, QWidget *parent):
QWidget(parent)
{
toggleButton.setStyleSheet(QStringLiteral("QToolButton { border: none; }"));
toggleButton.setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toggleButton.setArrowType(Qt::ArrowType::RightArrow);
toggleButton.setText(title);
toggleButton.setCheckable(true);
toggleButton.setChecked(false);
headerLine.setFrameShape(QFrame::HLine);
headerLine.setFrameShadow(QFrame::Sunken);
headerLine.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
contentArea.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// start out collapsed
contentArea.setMaximumHeight(0);
contentArea.setMinimumHeight(0);
// let the entire widget grow and shrink with its content
toggleAnimation.addAnimation(new QPropertyAnimation(this, "minimumHeight"));
toggleAnimation.addAnimation(new QPropertyAnimation(this, "maximumHeight"));
toggleAnimation.addAnimation(new QPropertyAnimation(&contentArea, "maximumHeight"));
mainLayout.setVerticalSpacing(0);
mainLayout.setContentsMargins(0, 0, 0, 0);
int row = 0;
mainLayout.addWidget(&toggleButton, row, 0, 1, 1, Qt::AlignLeft);
mainLayout.addWidget(&headerLine, row++, 2, 1, 1);
mainLayout.addWidget(&contentArea, row, 0, 1, 3);
setLayout(&mainLayout);
QObject::connect(&toggleButton, &QToolButton::clicked, [this](const bool checked) {
toggleButton.setArrowType(checked ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow);
toggleAnimation.setDirection(checked ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
toggleAnimation.start();
});
}
void AnimatedExpander::setContentLayout(QLayout *contentLayout)
{
delete contentArea.layout();
contentArea.setLayout(contentLayout);
const auto collapsedHeight = sizeHint().height() - contentArea.maximumHeight();
auto contentHeight = contentLayout->sizeHint().height();
for (int i = 0; i < toggleAnimation.animationCount() - 1; ++i) {
auto expanderAnimation = static_cast<QPropertyAnimation *>(toggleAnimation.animationAt(i));
expanderAnimation->setDuration(animationDuration);
expanderAnimation->setStartValue(collapsedHeight);
expanderAnimation->setEndValue(collapsedHeight + contentHeight);
}
auto contentAnimation = static_cast<QPropertyAnimation *>(toggleAnimation.animationAt(toggleAnimation.animationCount() - 1));
contentAnimation->setDuration(animationDuration);
contentAnimation->setStartValue(0);
contentAnimation->setEndValue(contentHeight);
}
class SecKeyFilter: public DefaultKeyFilter
{
public:
SecKeyFilter() : DefaultKeyFilter()
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setHasSecret(DefaultKeyFilter::Set);
setCanCertify(DefaultKeyFilter::Set);
setIsOpenPGP(DefaultKeyFilter::Set);
}
bool matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const override
{
if (!(availableMatchContexts() & contexts)) {
return false;
}
if (_detail::ByFingerprint<std::equal_to>()(key, mExcludedKey)) {
return false;
}
return DefaultKeyFilter::matches(key, contexts);
}
void setExcludedKey(const GpgME::Key &key)
{
mExcludedKey = key;
}
private:
GpgME::Key mExcludedKey;
};
class UserIDModel : public QStandardItemModel
{
Q_OBJECT
public:
enum Role {
UserIDIndex = Qt::UserRole
};
explicit UserIDModel(QObject *parent = nullptr) : QStandardItemModel(parent) {}
GpgME::Key certificateToCertify() const
{
return m_key;
}
void setKey(const GpgME::Key &key)
{
m_key = key;
clear();
const std::vector<GpgME::UserID> ids = key.userIDs();
int i = 0;
for (const auto &uid: key.userIDs()) {
if (uid.isRevoked() || uid.isInvalid()) {
// Skip user IDs that cannot really be certified.
i++;
continue;
}
auto const item = new QStandardItem;
item->setText(Formatting::prettyUserID(uid));
item->setData(i, UserIDIndex);
item->setCheckable(true);
item->setEditable(false);
item->setCheckState(Qt::Checked);
appendRow(item);
i++;
}
}
void setCheckedUserIDs(const std::vector<unsigned int> &uids)
{
std::vector<unsigned int> sorted = uids;
std::sort(sorted.begin(), sorted.end());
for (int i = 0, end = rowCount(); i != end; ++i) {
item(i)->setCheckState(std::binary_search(sorted.begin(), sorted.end(), i) ? Qt::Checked : Qt::Unchecked);
}
}
std::vector<unsigned int> checkedUserIDs() const
{
std::vector<unsigned int> ids;
for (int i = 0; i < rowCount(); ++i) {
if (item(i)->checkState() == Qt::Checked) {
ids.push_back(item(i)->data(UserIDIndex).toUInt());
}
}
qCDebug(KLEOPATRA_LOG) << "Checked uids are: " << ids;
return ids;
}
private:
GpgME::Key m_key;
};
static bool uidEqual(const GpgME::UserID &lhs, const GpgME::UserID &rhs)
{
return qstrcmp(lhs.parent().primaryFingerprint(),
rhs.parent().primaryFingerprint()) == 0
&& qstrcmp(lhs.id(), rhs.id()) == 0;
}
auto checkBoxSize(const QCheckBox *checkBox)
{
QStyleOptionButton opt;
return checkBox->style()->sizeFromContents(QStyle::CT_CheckBox, &opt, QSize(), checkBox);
}
auto createInfoButton(const QString &text, QWidget *parent)
{
auto infoBtn = new QPushButton{parent};
infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
infoBtn->setFlat(true);
QObject::connect(infoBtn, &QPushButton::clicked, infoBtn, [infoBtn, text] () {
const auto pos = infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0);
showToolTip(pos, text, infoBtn);
});
return infoBtn;
}
QString dateFormatWithFourDigitYear(QLocale::FormatType format)
{
// Force the year to be formatted as four digit number, so that
// the user can distinguish between 2006 and 2106.
return QLocale{}.dateFormat(format).
replace(QLatin1String("yy"), QLatin1String("yyyy")).
replace(QLatin1String("yyyyyyyy"), QLatin1String("yyyy"));
}
QString formatDate(const QDate &date, QLocale::FormatType format)
{
return QLocale{}.toString(date, dateFormatWithFourDigitYear(format));
}
}
class CertifyWidget::Private
{
public:
Private(CertifyWidget *qq)
: q{qq}
{
auto mainLay = new QVBoxLayout{q};
mFprLabel = new QLabel{q};
mainLay->addWidget(mFprLabel);
auto secKeyLay = new QHBoxLayout;
secKeyLay->addWidget(new QLabel(i18n("Certify with:")));
mSecKeySelect = new KeySelectionCombo{/* secretOnly= */true, q};
mSecKeySelect->setKeyFilter(std::make_shared<SecKeyFilter>());
secKeyLay->addWidget(mSecKeySelect, 1);
mainLay->addLayout(secKeyLay);
mMissingOwnerTrustInfo = new KMessageWidget{q};
mSetOwnerTrustAction = new QAction{q};
mSetOwnerTrustAction->setText(i18nc("@action:button", "Set Owner Trust"));
mSetOwnerTrustAction->setToolTip(QLatin1String("<html>") +
i18nc("@info:tooltip",
"Click to set the trust level of the selected certification key to ultimate trust. "
"This is what you usually want to do for your own keys.") +
QLatin1String("</html>"));
connect(mSetOwnerTrustAction, &QAction::triggered, q, [this] () { setOwnerTrust(); });
mMissingOwnerTrustInfo->addAction(mSetOwnerTrustAction);
mMissingOwnerTrustInfo->setVisible(false);
mainLay->addWidget(mMissingOwnerTrustInfo);
mainLay->addWidget(new KSeparator{Qt::Horizontal, q});
auto listView = new QListView{q};
listView->setModel(&mUserIDModel);
mainLay->addWidget(listView, 1);
// Setup the advanced area
auto expander = new AnimatedExpander{i18n("Advanced"), q};
mainLay->addWidget(expander);
auto advLay = new QVBoxLayout;
mExportCB = new QCheckBox{q};
mExportCB->setText(i18n("Certify for everyone to see (exportable)"));
advLay->addWidget(mExportCB);
{
auto layout = new QHBoxLayout;
mPublishCB = new QCheckBox{q};
mPublishCB->setText(i18n("Publish on keyserver afterwards"));
mPublishCB->setEnabled(mExportCB->isChecked());
layout->addSpacing(checkBoxSize(mExportCB).width());
layout->addWidget(mPublishCB);
advLay->addLayout(layout);
}
{
auto tagsLay = new QHBoxLayout;
mTagsLE = new QLineEdit{q};
mTagsLE->setPlaceholderText(i18n("Tags"));
auto infoBtn = createInfoButton(i18n("You can use this to add additional info to a certification.") +
QStringLiteral("<br/><br/>") +
i18n("Tags created by anyone with full certification trust "
"are shown in the keylist and can be searched."),
q);
infoBtn->setAccessibleName(i18n("Explain tags"));
tagsLay->addWidget(new QLabel{i18n("Tags:"), q});
tagsLay->addWidget(mTagsLE, 1);
tagsLay->addWidget(infoBtn);
advLay->addLayout(tagsLay);
}
{
auto layout = new QHBoxLayout;
mExpirationCheckBox = new QCheckBox{q};
mExpirationCheckBox->setText(i18n("Expiration:"));
mExpirationDateEdit = new KDateComboBox{q};
mExpirationDateEdit->setOptions(KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker |
KDateComboBox::DateKeywords | KDateComboBox::WarnOnInvalid);
static const QDate maxAllowedDate{2106, 2, 5};
const QDate today = QDate::currentDate();
mExpirationDateEdit->setDateRange(today.addDays(1), maxAllowedDate,
i18n("The certification must be valid at least until tomorrow."),
i18n("The latest allowed certification date is %1.",
formatDate(maxAllowedDate, QLocale::ShortFormat)));
mExpirationDateEdit->setDateMap({
{today.addYears(2), i18nc("Date for expiration of certification", "Two years from now")},
{today.addYears(1), i18nc("Date for expiration of certification", "One year from now")}
});
mExpirationDateEdit->setDate(today.addYears(2));
mExpirationDateEdit->setEnabled(mExpirationCheckBox->isChecked());
auto infoBtn = createInfoButton(i18n("You can use this to set an expiration date for a certification.") +
QStringLiteral("<br/><br/>") +
i18n("By setting an expiration date, you can limit the validity of "
"your certification to a certain amount of time. Once the expiration "
"date has passed, your certification is no longer valid."),
q);
infoBtn->setAccessibleName(i18n("Explain expiration"));
layout->addWidget(mExpirationCheckBox);
layout->addWidget(mExpirationDateEdit, 1);
layout->addWidget(infoBtn);
advLay->addLayout(layout);
}
{
auto layout = new QHBoxLayout;
mTrustSignatureCB = new QCheckBox{q};
mTrustSignatureCB->setText(i18n("Certify as trusted introducer"));
auto infoBtn = createInfoButton(i18n("You can use this to certify a trusted introducer for a domain.") +
QStringLiteral("<br/><br/>") +
i18n("All certificates with email addresses belonging to the domain "
"that have been certified by the trusted introducer are treated "
"as certified, i.e. a trusted introducer acts as a kind of "
"intermediate CA for a domain."),
q);
infoBtn->setAccessibleName(i18n("Explain trusted introducer"));
layout->addWidget(mTrustSignatureCB, 1);
layout->addWidget(infoBtn);
advLay->addLayout(layout);
}
{
auto layout = new QHBoxLayout;
mTrustSignatureDomainLE = new QLineEdit{q};
mTrustSignatureDomainLE->setPlaceholderText(i18n("Domain"));
mTrustSignatureDomainLE->setEnabled(mTrustSignatureCB->isChecked());
layout->addSpacing(checkBoxSize(mTrustSignatureCB).width());
layout->addWidget(mTrustSignatureDomainLE);
advLay->addLayout(layout);
}
expander->setContentLayout(advLay);
connect(&mUserIDModel, &QStandardItemModel::itemChanged, q, &CertifyWidget::changed);
connect(mExportCB, &QCheckBox::toggled, [this] (bool on) {
mPublishCB->setEnabled(on);
});
connect(mSecKeySelect, &KeySelectionCombo::currentKeyChanged, [this] (const GpgME::Key &) {
updateTags();
checkOwnerTrust();
Q_EMIT q->changed();
});
connect(mExpirationCheckBox, &QCheckBox::toggled, q, [this] (bool checked) {
mExpirationDateEdit->setEnabled(checked);
Q_EMIT q->changed();
});
connect(mExpirationDateEdit, &KDateComboBox::dateChanged, q, &CertifyWidget::changed);
connect(mTrustSignatureCB, &QCheckBox::toggled, q, [this] (bool on) {
mTrustSignatureDomainLE->setEnabled(on);
Q_EMIT q->changed();
});
connect(mTrustSignatureDomainLE, &QLineEdit::textChanged, q, &CertifyWidget::changed);
loadConfig();
}
~Private() = default;
void loadConfig()
{
const KConfigGroup conf(KSharedConfig::openConfig(), "CertifySettings");
mSecKeySelect->setDefaultKey(conf.readEntry("LastKey", QString()));
mExportCB->setChecked(conf.readEntry("ExportCheckState", false));
mPublishCB->setChecked(conf.readEntry("PublishCheckState", false));
}
void updateTags()
{
if (mTagsLE->isModified()) {
return;
}
GpgME::Key remarkKey = mSecKeySelect->currentKey();
if (!remarkKey.isNull()) {
std::vector<GpgME::UserID> uidsWithRemark;
QString remark;
for (const auto &uid: mTarget.userIDs()) {
GpgME::Error err;
const char *c_remark = uid.remark(remarkKey, err);
if (c_remark) {
const QString candidate = QString::fromUtf8(c_remark);
if (candidate != remark) {
qCDebug(KLEOPATRA_LOG) << "Different remarks on user IDs. Taking last.";
remark = candidate;
uidsWithRemark.clear();
}
uidsWithRemark.push_back(uid);
}
}
// Only select the user IDs with the correct remark
if (!remark.isEmpty()) {
selectUserIDs(uidsWithRemark);
}
mTagsLE->setText(remark);
}
}
void updateTrustSignatureDomain()
{
if (mTrustSignatureDomainLE->text().isEmpty() && mTarget.numUserIDs() == 1) {
// try to guess the domain to use for the trust signature
const auto address = mTarget.userID(0).addrSpec();
const auto atPos = address.find('@');
if (atPos != std::string::npos) {
const auto domain = address.substr(atPos + 1);
mTrustSignatureDomainLE->setText(QString::fromUtf8(domain.c_str(), domain.size()));
}
}
}
void setTarget(const GpgME::Key &key)
{
mFprLabel->setText(i18n("Fingerprint: <b>%1</b>",
Formatting::prettyID(key.primaryFingerprint())) + QStringLiteral("<br/>") +
i18n("<i>Only the fingerprint clearly identifies the key and its owner.</i>"));
mUserIDModel.setKey(key);
mTarget = key;
auto keyFilter = std::make_shared<SecKeyFilter>();
keyFilter->setExcludedKey(mTarget);
mSecKeySelect->setKeyFilter(keyFilter);
updateTags();
updateTrustSignatureDomain();
}
GpgME::Key secKey() const
{
return mSecKeySelect->currentKey();
}
void selectUserIDs(const std::vector<GpgME::UserID> &uids)
{
const auto all = mTarget.userIDs();
std::vector<unsigned int> indexes;
indexes.reserve(uids.size());
for (const auto &uid: uids) {
const unsigned int idx =
std::distance(all.cbegin(), std::find_if(all.cbegin(), all.cend(),
[uid](const GpgME::UserID &other) { return uidEqual(uid, other); }));
if (idx < all.size()) {
indexes.push_back(idx);
}
}
mUserIDModel.setCheckedUserIDs(indexes);
}
std::vector<unsigned int> selectedUserIDs() const
{
return mUserIDModel.checkedUserIDs();
}
bool exportableSelected() const
{
return mExportCB->isChecked();
}
bool publishSelected() const
{
return mPublishCB->isChecked();
}
QString tags() const
{
return mTagsLE->text().trimmed();
}
GpgME::Key target() const
{
return mTarget;
}
bool isValid() const
{
static const QRegularExpression domainNameRegExp{QStringLiteral(R"(^\s*((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}\s*$)"),
QRegularExpression::CaseInsensitiveOption};
// do not accept null keys
if (mTarget.isNull() || mSecKeySelect->currentKey().isNull()) {
return false;
}
// do not accept empty list of user IDs
if (selectedUserIDs().empty()) {
return false;
}
// do not accept if the key to certify is selected as certification key;
// this shouldn't happen because the key to certify is excluded from the choice, but better safe than sorry
if (_detail::ByFingerprint<std::equal_to>()(mTarget, mSecKeySelect->currentKey())) {
return false;
}
if (mExpirationCheckBox->isChecked() && !mExpirationDateEdit->isValid()) {
return false;
}
if (mTrustSignatureCB->isChecked() && !domainNameRegExp.match(mTrustSignatureDomainLE->text()).hasMatch()) {
return false;
}
return true;
}
void checkOwnerTrust()
{
const auto secretKey = secKey();
if (secretKey.ownerTrust() != GpgME::Key::Ultimate) {
mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Information);
mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("question")));
mMissingOwnerTrustInfo->setText(i18n("Is this your own key?"));
mSetOwnerTrustAction->setEnabled(true);
mMissingOwnerTrustInfo->animatedShow();
} else {
mMissingOwnerTrustInfo->animatedHide();
}
}
void setOwnerTrust()
{
mSetOwnerTrustAction->setEnabled(false);
QGpgME::ChangeOwnerTrustJob *const j = QGpgME::openpgp()->changeOwnerTrustJob();
connect(j, &QGpgME::ChangeOwnerTrustJob::result, q, [this] (const GpgME::Error &err) {
if (err) {
- KMessageBox::sorry(q,
+ KMessageBox::error(q,
i18n("<p>Changing the certification trust of the key <b>%1</b> failed:</p><p>%2</p>",
Formatting::formatForComboBox(secKey()),
QString::fromLocal8Bit(err.asString())),
i18n("Certification Trust Change Failed"));
}
if (err || err.isCanceled()) {
mSetOwnerTrustAction->setEnabled(true);
} else {
mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Positive);
mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("checkmark")));
mMissingOwnerTrustInfo->setText(i18n("Owner trust set successfully."));
}
});
j->start(secKey(), GpgME::Key::Ultimate);
}
public:
CertifyWidget *const q;
QLabel *mFprLabel = nullptr;
KeySelectionCombo *mSecKeySelect = nullptr;
KMessageWidget *mMissingOwnerTrustInfo = nullptr;
QCheckBox *mExportCB = nullptr;
QCheckBox *mPublishCB = nullptr;
QLineEdit *mTagsLE = nullptr;
QCheckBox *mTrustSignatureCB = nullptr;
QLineEdit *mTrustSignatureDomainLE = nullptr;
QCheckBox *mExpirationCheckBox = nullptr;
KDateComboBox *mExpirationDateEdit = nullptr;
QAction *mSetOwnerTrustAction = nullptr;
UserIDModel mUserIDModel;
GpgME::Key mTarget;
};
CertifyWidget::CertifyWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
Kleo::CertifyWidget::~CertifyWidget() = default;
void CertifyWidget::setTarget(const GpgME::Key &key)
{
d->setTarget(key);
}
GpgME::Key CertifyWidget::target() const
{
return d->target();
}
void CertifyWidget::selectUserIDs(const std::vector<GpgME::UserID> &uids)
{
d->selectUserIDs(uids);
}
std::vector<unsigned int> CertifyWidget::selectedUserIDs() const
{
return d->selectedUserIDs();
}
GpgME::Key CertifyWidget::secKey() const
{
return d->secKey();
}
bool CertifyWidget::exportableSelected() const
{
return d->exportableSelected();
}
QString CertifyWidget::tags() const
{
return d->tags();
}
bool CertifyWidget::publishSelected() const
{
return d->publishSelected();
}
bool CertifyWidget::trustSignatureSelected() const
{
return d->mTrustSignatureCB->isChecked();
}
QString CertifyWidget::trustSignatureDomain() const
{
return d->mTrustSignatureDomainLE->text().trimmed();
}
QDate CertifyWidget::expirationDate() const
{
return d->mExpirationCheckBox->isChecked() ? d->mExpirationDateEdit->date() : QDate{};
}
bool CertifyWidget::isValid() const
{
return d->isValid();
}
// For UserID model
#include "certifywidget.moc"
diff --git a/src/dialogs/revokekeydialog.cpp b/src/dialogs/revokekeydialog.cpp
index eaf764f09..d85995cca 100644
--- a/src/dialogs/revokekeydialog.cpp
+++ b/src/dialogs/revokekeydialog.cpp
@@ -1,299 +1,299 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/revokekeydialog.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "revokekeydialog.h"
#include "utils/accessibility.h"
#include "view/errorlabel.h"
#include <Libkleo/Formatting>
#include <KConfigGroup>
#include <KGuiItem>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QAccessible>
#include <QApplication>
#include <QButtonGroup>
#include <QDialogButtonBox>
#include <QFocusEvent>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include <QRadioButton>
#include <QRegularExpression>
#include <QTextEdit>
#include <QVBoxLayout>
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
#include <gpgme++/global.h>
#endif
#include <gpgme++/key.h>
#include <kleopatra_debug.h>
using namespace Kleo;
using namespace GpgME;
namespace
{
class TextEdit : public QTextEdit
{
Q_OBJECT
public:
using QTextEdit::QTextEdit;
Q_SIGNALS:
void editingFinished();
protected:
void focusOutEvent(QFocusEvent *event) override
{
Qt::FocusReason reason = event->reason();
if (reason != Qt::PopupFocusReason
|| !(QApplication::activePopupWidget() && QApplication::activePopupWidget()->parentWidget() == this)) {
Q_EMIT editingFinished();
}
QTextEdit::focusOutEvent(event);
}
};
}
class RevokeKeyDialog::Private
{
friend class ::Kleo::RevokeKeyDialog;
RevokeKeyDialog *const q;
struct {
QLabel *infoLabel = nullptr;
QLabel *descriptionLabel = nullptr;
TextEdit *description = nullptr;
ErrorLabel *descriptionError = nullptr;
QDialogButtonBox *buttonBox = nullptr;
} ui;
Key key;
QButtonGroup reasonGroup;
bool descriptionEditingInProgress = false;
QString descriptionAccessibleName;
public:
Private(RevokeKeyDialog *qq)
: q(qq)
{
q->setWindowTitle(i18nc("title:window", "Revoke Key"));
auto mainLayout = new QVBoxLayout{q};
ui.infoLabel = new QLabel{q};
mainLayout->addWidget(ui.infoLabel);
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
auto groupBox = new QGroupBox{i18nc("@title:group", "Reason for revocation"), q};
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "No reason specified"), q},
static_cast<int>(RevocationReason::Unspecified));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key has been compromised"), q},
static_cast<int>(RevocationReason::Compromised));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is superseded"), q},
static_cast<int>(RevocationReason::Superseded));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is no longer used"), q},
static_cast<int>(RevocationReason::NoLongerUsed));
reasonGroup.button(static_cast<int>(RevocationReason::Unspecified))->setChecked(true);
{
auto boxLayout = new QVBoxLayout{groupBox};
for (auto radio : reasonGroup.buttons()) {
boxLayout->addWidget(radio);
}
}
mainLayout->addWidget(groupBox);
#endif
{
ui.descriptionLabel = new QLabel{i18nc("@label:textbox", "Description (optional):"), q};
ui.description = new TextEdit{q};
ui.description->setAcceptRichText(false);
// do not accept Tab as input; this is better for accessibility and
// tabulators are not really that useful in the description
ui.description->setTabChangesFocus(true);
ui.descriptionLabel->setBuddy(ui.description);
ui.descriptionError = new ErrorLabel{q};
ui.descriptionError->setVisible(false);
mainLayout->addWidget(ui.descriptionLabel);
mainLayout->addWidget(ui.description);
mainLayout->addWidget(ui.descriptionError);
}
connect(ui.description, &TextEdit::editingFinished,
q, [this]() { onDescriptionEditingFinished(); });
connect(ui.description, &TextEdit::textChanged,
q, [this]() { onDescriptionTextChanged(); });
ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto okButton = ui.buttonBox->button(QDialogButtonBox::Ok);
okButton->setText(i18nc("@action:button", "Revoke Key"));
okButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete-remove")));
mainLayout->addWidget(ui.buttonBox);
connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() { checkAccept(); });
connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
restoreGeometry();
}
~Private()
{
saveGeometry();
}
private:
void saveGeometry()
{
KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "RevokeKeyDialog");
cfgGroup.writeEntry("Size", q->size());
cfgGroup.sync();
}
void restoreGeometry(const QSize &defaultSize = {})
{
KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "RevokeKeyDialog");
const QSize size = cfgGroup.readEntry("Size", defaultSize);
if (size.isValid()) {
q->resize(size);
}
}
void checkAccept()
{
if (!descriptionHasAcceptableInput()) {
- KMessageBox::sorry(q, descriptionErrorMessage());
+ KMessageBox::error(q, descriptionErrorMessage());
} else {
q->accept();
}
}
bool descriptionHasAcceptableInput() const
{
return !q->description().contains(QLatin1String{"\n\n"});
}
QString descriptionErrorMessage() const
{
QString message;
if (!descriptionHasAcceptableInput()) {
message = i18n("Error: The description must not contain empty lines.");
}
return message;
}
void updateDescriptionError()
{
const auto currentErrorMessage = ui.descriptionError->text();
const auto newErrorMessage = descriptionErrorMessage();
if (newErrorMessage == currentErrorMessage) {
return;
}
if (currentErrorMessage.isEmpty() && descriptionEditingInProgress) {
// delay showing the error message until editing is finished, so that we
// do not annoy the user with an error message while they are still
// entering the recipient;
// on the other hand, we clear the error message immediately if it does
// not apply anymore and we update the error message immediately if it
// changed
return;
}
ui.descriptionError->setVisible(!newErrorMessage.isEmpty());
ui.descriptionError->setText(newErrorMessage);
updateAccessibleNameAndDescription();
}
void updateAccessibleNameAndDescription()
{
// fall back to default accessible name if accessible name wasn't set explicitly
if (descriptionAccessibleName.isEmpty()) {
descriptionAccessibleName = getAccessibleName(ui.description);
}
const bool errorShown = ui.descriptionError->isVisible();
// Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute);
// emulate this by setting the error message as accessible description of the input field
const auto description = errorShown ? ui.descriptionError->text() : QString{};
if (ui.description->accessibleDescription() != description) {
ui.description->setAccessibleDescription(description);
}
// Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute);
// screen readers say something like "invalid entry" if this state is set;
// emulate this by adding "invalid entry" to the accessible name of the input field
// and its label
const auto name = errorShown ? descriptionAccessibleName + QLatin1String{", "} + invalidEntryText()
: descriptionAccessibleName;
if (ui.descriptionLabel->accessibleName() != name) {
ui.descriptionLabel->setAccessibleName(name);
}
if (ui.description->accessibleName() != name) {
ui.description->setAccessibleName(name);
}
}
void onDescriptionTextChanged()
{
descriptionEditingInProgress = true;
updateDescriptionError();
}
void onDescriptionEditingFinished()
{
descriptionEditingInProgress = false;
updateDescriptionError();
}
};
RevokeKeyDialog::RevokeKeyDialog(QWidget *parent, Qt::WindowFlags f)
: QDialog{parent, f}
, d{new Private{this}}
{
}
RevokeKeyDialog::~RevokeKeyDialog() = default;
void RevokeKeyDialog::setKey(const GpgME::Key &key)
{
d->key = key;
d->ui.infoLabel->setText(
xi18n("<para>You are about to revoke the following key:<nl/>%1</para>")
.arg(Formatting::summaryLine(key)));
}
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
GpgME::RevocationReason RevokeKeyDialog::reason() const
{
return static_cast<RevocationReason>(d->reasonGroup.checkedId());
}
#endif
QString RevokeKeyDialog::description() const
{
static const QRegularExpression whitespaceAtEndOfLine{QStringLiteral(R"([ \t\r]+\n)")};
static const QRegularExpression trailingWhitespace{QStringLiteral(R"(\s*$)")};
return d->ui.description->toPlainText().remove(whitespaceAtEndOfLine).remove(trailingWhitespace);
}
#include "revokekeydialog.moc"
diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp
index 6657adb35..f381bf69c 100644
--- a/src/kleopatraapplication.cpp
+++ b/src/kleopatraapplication.cpp
@@ -1,804 +1,804 @@
/*
kleopatraapplication.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "kleopatraapplication.h"
#include "mainwindow.h"
#include "kleopatra_options.h"
#include "systrayicon.h"
#include "settings.h"
#include "smimevalidationpreferences.h"
#include <smartcard/readerstatus.h>
#include <conf/configuredialog.h>
#include <Libkleo/GnuPG>
#include <utils/kdpipeiodevice.h>
#include <utils/log.h>
#include <gpgme++/key.h>
#include <Libkleo/Dn>
#include <Libkleo/FileSystemWatcher>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilterManager>
#include <Libkleo/KeyGroupConfig>
#include <Libkleo/Classify>
#ifdef HAVE_USABLE_ASSUAN
# include <uiserver/uiserver.h>
#endif
#include "commands/signencryptfilescommand.h"
#include "commands/decryptverifyfilescommand.h"
#include "commands/lookupcertificatescommand.h"
#include "commands/checksumcreatefilescommand.h"
#include "commands/checksumverifyfilescommand.h"
#include "commands/detailscommand.h"
#include "commands/newcertificatecommand.h"
#include "dialogs/updatenotification.h"
#include <KIconLoader>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <KMessageBox>
#include <KWindowSystem>
#include <QDesktopServices>
#include <QFile>
#include <QDir>
#include <QFocusFrame>
#if QT_CONFIG(graphicseffect)
#include <QGraphicsEffect>
#endif
#include <QPointer>
#include <QStyleOption>
#include <QStylePainter>
#include <memory>
#include <KSharedConfig>
#ifdef Q_OS_WIN
#include <QtPlatformHeaders/QWindowsWindowFunctions>
#endif
using namespace Kleo;
using namespace Kleo::Commands;
static void add_resources()
{
KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra"));
KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg"));
}
static QList<QByteArray> default_logging_options()
{
QList<QByteArray> result;
result.push_back("io");
return result;
}
namespace
{
class FocusFrame : public QFocusFrame
{
Q_OBJECT
public:
using QFocusFrame::QFocusFrame;
protected:
void paintEvent(QPaintEvent *event) override;
};
static QRect effectiveWidgetRect(const QWidget *w)
{
// based on QWidgetPrivate::effectiveRectFor
#if QT_CONFIG(graphicseffect)
if (auto graphicsEffect = w->graphicsEffect(); graphicsEffect && graphicsEffect->isEnabled())
return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect();
#endif // QT_CONFIG(graphicseffect)
return w->rect();
}
static QRect clipRect(const QWidget *w)
{
// based on QWidgetPrivate::clipRect
if (!w->isVisible()) {
return QRect();
}
QRect r = effectiveWidgetRect(w);
int ox = 0;
int oy = 0;
while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) {
ox -= w->x();
oy -= w->y();
w = w->parentWidget();
r &= QRect(ox, oy, w->width(), w->height());
}
return r;
}
void FocusFrame::paintEvent(QPaintEvent *)
{
if (!widget()) {
return;
}
QStylePainter p(this);
QStyleOptionFocusRect option;
initStyleOption(&option);
const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option);
const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option);
const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin*2, vmargin*2);
p.setClipRect(rect);
p.drawPrimitive(QStyle::PE_FrameFocusRect, option);
}
}
class KleopatraApplication::Private
{
friend class ::KleopatraApplication;
KleopatraApplication *const q;
public:
explicit Private(KleopatraApplication *qq)
: q(qq)
, ignoreNewInstance(true)
, firstNewInstance(true)
, sysTray(nullptr)
, groupConfig{std::make_shared<KeyGroupConfig>(QStringLiteral("kleopatragroupsrc"))}
{
}
~Private() {
#ifndef QT_NO_SYSTEMTRAYICON
delete sysTray;
#endif
}
void setUpSysTrayIcon()
{
KDAB_SET_OBJECT_NAME(readerStatus);
#ifndef QT_NO_SYSTEMTRAYICON
sysTray = new SysTrayIcon();
sysTray->setFirstCardWithNullPin(readerStatus.firstCardWithNullPin());
sysTray->setAnyCardCanLearnKeys(readerStatus.anyCardCanLearnKeys());
connect(&readerStatus, &SmartCard::ReaderStatus::firstCardWithNullPinChanged,
sysTray, &SysTrayIcon::setFirstCardWithNullPin);
connect(&readerStatus, &SmartCard::ReaderStatus::anyCardCanLearnKeysChanged,
sysTray, &SysTrayIcon::setAnyCardCanLearnKeys);
#endif
}
private:
void connectConfigureDialog()
{
if (configureDialog) {
if (q->mainWindow()) {
connect(configureDialog, SIGNAL(configCommitted()),
q->mainWindow(), SLOT(slotConfigCommitted()));
}
connect(configureDialog, &ConfigureDialog::configCommitted,
q, &KleopatraApplication::configurationChanged);
}
}
void disconnectConfigureDialog()
{
if (configureDialog) {
if (q->mainWindow()) {
disconnect(configureDialog, SIGNAL(configCommitted()),
q->mainWindow(), SLOT(slotConfigCommitted()));
}
disconnect(configureDialog, &ConfigureDialog::configCommitted,
q, &KleopatraApplication::configurationChanged);
}
}
public:
bool ignoreNewInstance;
bool firstNewInstance;
QPointer<FocusFrame> focusFrame;
QPointer<ConfigureDialog> configureDialog;
QPointer<MainWindow> mainWindow;
SmartCard::ReaderStatus readerStatus;
#ifndef QT_NO_SYSTEMTRAYICON
SysTrayIcon *sysTray;
#endif
std::shared_ptr<KeyGroupConfig> groupConfig;
std::shared_ptr<KeyCache> keyCache;
std::shared_ptr<Log> log;
std::shared_ptr<FileSystemWatcher> watcher;
public:
void setupKeyCache()
{
keyCache = KeyCache::mutableInstance();
keyCache->setRefreshInterval(SMimeValidationPreferences{}.refreshInterval());
watcher.reset(new FileSystemWatcher);
watcher->whitelistFiles(gnupgFileWhitelist());
watcher->addPaths(gnupgFolderWhitelist());
watcher->setDelay(1000);
keyCache->addFileSystemWatcher(watcher);
keyCache->setGroupConfig(groupConfig);
keyCache->setGroupsEnabled(Settings().groupsEnabled());
}
void setUpFilterManager()
{
if (!Settings{}.cmsEnabled()) {
KeyFilterManager::instance()->alwaysFilterByProtocol(GpgME::OpenPGP);
}
}
void setupLogging()
{
log = Log::mutableInstance();
const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS");
const bool logAll = envOptions.trimmed() == "all";
const QList<QByteArray> options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(',');
const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR");
if (dirNative.isEmpty()) {
return;
}
const QString dir = QFile::decodeName(dirNative);
const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid()));
std::unique_ptr<QFile> logFile(new QFile(logFileName));
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) {
qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled";
return;
}
log->setOutputDirectory(dir);
if (logAll || options.contains("io")) {
log->setIOLoggingEnabled(true);
}
qInstallMessageHandler(Log::messageHandler);
#ifdef HAVE_USABLE_ASSUAN
if (logAll || options.contains("pipeio")) {
KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug);
}
UiServer::setLogStream(log->logFile());
#endif
}
void updateFocusFrame(QWidget *focusWidget)
{
if (focusWidget && focusWidget->inherits("QLabel")) {
if (!focusFrame) {
focusFrame = new FocusFrame{focusWidget};
}
focusFrame->setWidget(focusWidget);
} else if (focusFrame) {
focusFrame->setWidget(nullptr);
}
}
};
KleopatraApplication::KleopatraApplication(int &argc, char *argv[])
: QApplication(argc, argv), d(new Private(this))
{
// disable parent<->child navigation in tree views with left/right arrow keys
// because this interferes with column by column navigation that is required
// for accessibility
setStyleSheet(QStringLiteral("QTreeView { arrow-keys-navigate-into-children: 0; }"));
connect(this, &QApplication::focusChanged,
this, [this](QWidget *, QWidget *now) {
d->updateFocusFrame(now);
});
}
void KleopatraApplication::init()
{
#ifdef Q_OS_WIN
QWindowsWindowFunctions::setWindowActivationBehavior(
QWindowsWindowFunctions::AlwaysActivateWindow);
#endif
const auto blockedUrlSchemes = Settings{}.blockedUrlSchemes();
for (const auto &scheme : blockedUrlSchemes) {
QDesktopServices::setUrlHandler(scheme, this, "blockUrl");
}
add_resources();
DN::setAttributeOrder(Settings{}.attributeOrder());
/* Start the gpg-agent early, this is done explicitly
* because on an empty keyring our keylistings wont start
* the agent. In that case any assuan-connect calls to
* the agent will fail. The requested start via the
* connection is additionally done in case the gpg-agent
* is killed while Kleopatra is running. */
startGpgAgent();
connect(&d->readerStatus, &SmartCard::ReaderStatus::startOfGpgAgentRequested,
this, &KleopatraApplication::startGpgAgent);
d->setupKeyCache();
d->setUpSysTrayIcon();
d->setUpFilterManager();
d->setupLogging();
#ifndef QT_NO_SYSTEMTRAYICON
d->sysTray->show();
#endif
setQuitOnLastWindowClosed(false);
KWindowSystem::allowExternalProcessWindowActivation();
}
KleopatraApplication::~KleopatraApplication()
{
// main window doesn't receive "close" signal and cannot
// save settings before app exit
delete d->mainWindow;
// work around kdelibs bug https://bugs.kde.org/show_bug.cgi?id=162514
KSharedConfig::openConfig()->sync();
}
namespace
{
using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol);
}
void KleopatraApplication::slotActivateRequested(const QStringList &arguments,
const QString &workingDirectory)
{
QCommandLineParser parser;
kleopatra_options(&parser);
QString err;
if (!arguments.isEmpty() && !parser.parse(arguments)) {
err = parser.errorText();
} else if (arguments.isEmpty()) {
// KDBusServices omits the application name if no other
// arguments are provided. In that case the parser prints
// a warning.
parser.parse(QStringList() << QCoreApplication::applicationFilePath());
}
if (err.isEmpty()) {
err = newInstance(parser, workingDirectory);
}
if (!err.isEmpty()) {
- KMessageBox::sorry(nullptr, err.toHtmlEscaped(), i18n("Failed to execute command"));
+ KMessageBox::error(nullptr, err.toHtmlEscaped(), i18n("Failed to execute command"));
Q_EMIT setExitValue(1);
return;
}
Q_EMIT setExitValue(0);
}
QString KleopatraApplication::newInstance(const QCommandLineParser &parser,
const QString &workingDirectory)
{
if (d->ignoreNewInstance) {
qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance";
return QString();
}
QStringList files;
const QDir cwd = QDir(workingDirectory);
bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search"));
// Query and Search treat positional arguments differently, see below.
if (!queryMode) {
const auto positionalArguments = parser.positionalArguments();
for (const QString &file : positionalArguments) {
// We do not check that file exists here. Better handle
// these errors in the UI.
if (QFileInfo(file).isAbsolute()) {
files << file;
} else {
files << cwd.absoluteFilePath(file);
}
}
}
GpgME::Protocol protocol = GpgME::UnknownProtocol;
if (parser.isSet(QStringLiteral("openpgp"))) {
qCDebug(KLEOPATRA_LOG) << "found OpenPGP";
protocol = GpgME::OpenPGP;
}
if (parser.isSet(QStringLiteral("cms"))) {
qCDebug(KLEOPATRA_LOG) << "found CMS";
if (protocol == GpgME::OpenPGP) {
return i18n("Ambiguous protocol: --openpgp and --cms");
}
protocol = GpgME::CMS;
}
// Check for Parent Window id
WId parentId = 0;
if (parser.isSet(QStringLiteral("parent-windowid"))) {
#ifdef Q_OS_WIN
// WId is not a portable type as it is a pointer type on Windows.
// casting it from an integer is ok though as the values are guaranteed to
// be compatible in the documentation.
parentId = reinterpret_cast<WId>(parser.value(QStringLiteral("parent-windowid")).toUInt());
#else
parentId = parser.value(QStringLiteral("parent-windowid")).toUInt();
#endif
}
// Handle openpgp4fpr URI scheme
QString needle;
if (queryMode) {
needle = parser.positionalArguments().join(QLatin1Char(' '));
}
if (needle.startsWith(QLatin1String("openpgp4fpr:"))) {
needle.remove(0, 12);
}
// Check for --search command.
if (parser.isSet(QStringLiteral("search"))) {
// This is an extra command instead of a combination with the
// similar query to avoid changing the older query commands behavior
// and query's "show details if a certificate exist or search on a
// keyserver" logic is hard to explain and use consistently.
if (needle.isEmpty()) {
return i18n("No search string specified for --search");
}
auto const cmd = new LookupCertificatesCommand(needle, nullptr);
cmd->setParentWId(parentId);
cmd->start();
return QString();
}
// Check for --query command
if (parser.isSet(QStringLiteral("query"))) {
if (needle.isEmpty()) {
return i18n("No fingerprint argument specified for --query");
}
auto cmd = Command::commandForQuery(needle);
cmd->setParentWId(parentId);
cmd->start();
return QString();
}
// Check for --gen-key command
if (parser.isSet(QStringLiteral("gen-key"))) {
auto cmd = new NewCertificateCommand(nullptr);
cmd->setParentWId(parentId);
cmd->setProtocol(protocol);
cmd->start();
return QString();
}
// Check for --config command
if (parser.isSet(QStringLiteral("config"))) {
openConfigDialogWithForeignParent(parentId);
return QString();
}
struct FuncInfo {
QString optionName;
Func func;
};
static const std::vector<FuncInfo> funcMap {
{ QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile },
{ QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles },
{ QStringLiteral("sign"), &KleopatraApplication::signFiles },
{ QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles },
{ QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles },
{ QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles },
{ QStringLiteral("verify"), &KleopatraApplication::verifyFiles },
{ QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles },
{ QStringLiteral("checksum"), &KleopatraApplication::checksumFiles },
};
QString found;
Func foundFunc = nullptr;
for (const auto &[opt, fn] : funcMap) {
if (parser.isSet(opt) && found.isEmpty()) {
found = opt;
foundFunc = fn;
} else if (parser.isSet(opt)) {
return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt);
}
}
QStringList errors;
if (!found.isEmpty()) {
if (files.empty()) {
return i18n("No files specified for \"%1\" command", found);
}
qCDebug(KLEOPATRA_LOG) << "found" << found;
(this->*foundFunc)(files, protocol);
} else {
if (files.empty()) {
if (!(d->firstNewInstance && isSessionRestored())) {
qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow";
openOrRaiseMainWindow();
}
} else {
for (const QString& fileName : std::as_const(files)) {
QFileInfo fi(fileName);
if (!fi.isReadable()) {
errors << i18n("Cannot read \"%1\"", fileName);
}
}
const QVector<Command *> allCmds = Command::commandsForFiles(files);
for (Command *cmd : allCmds) {
if (parentId) {
cmd->setParentWId(parentId);
} else {
MainWindow *mw = mainWindow();
if (!mw) {
mw = new MainWindow;
mw->setAttribute(Qt::WA_DeleteOnClose);
setMainWindow(mw);
d->connectConfigureDialog();
}
cmd->setParentWidget(mw);
}
cmd->start();
}
}
}
d->firstNewInstance = false;
#ifdef Q_OS_WIN
// On Windows we might be started from the
// explorer in any working directory. E.g.
// a double click on a file. To avoid preventing
// the folder from deletion we set the
// working directory to the users homedir.
QDir::setCurrent(QDir::homePath());
#endif
return errors.join(QLatin1Char('\n'));
}
#ifndef QT_NO_SYSTEMTRAYICON
const SysTrayIcon *KleopatraApplication::sysTrayIcon() const
{
return d->sysTray;
}
SysTrayIcon *KleopatraApplication::sysTrayIcon()
{
return d->sysTray;
}
#endif
const MainWindow *KleopatraApplication::mainWindow() const
{
return d->mainWindow;
}
MainWindow *KleopatraApplication::mainWindow()
{
return d->mainWindow;
}
void KleopatraApplication::setMainWindow(MainWindow *mainWindow)
{
if (mainWindow == d->mainWindow) {
return;
}
d->disconnectConfigureDialog();
d->mainWindow = mainWindow;
#ifndef QT_NO_SYSTEMTRAYICON
d->sysTray->setMainWindow(mainWindow);
#endif
d->connectConfigureDialog();
}
static void open_or_raise(QWidget *w)
{
if (w->isMinimized()) {
KWindowSystem::unminimizeWindow(w->winId());
w->raise();
} else if (w->isVisible()) {
w->raise();
} else {
w->show();
}
}
void KleopatraApplication::toggleMainWindowVisibility()
{
if (mainWindow()) {
mainWindow()->setVisible(!mainWindow()->isVisible());
} else {
openOrRaiseMainWindow();
}
}
void KleopatraApplication::restoreMainWindow()
{
qCDebug(KLEOPATRA_LOG) << "restoring main window";
// Sanity checks
if (!isSessionRestored()) {
qCDebug(KLEOPATRA_LOG) << "Not in session restore";
return;
}
if (mainWindow()) {
qCDebug(KLEOPATRA_LOG) << "Already have main window";
return;
}
auto mw = new MainWindow;
if (KMainWindow::canBeRestored(1)) {
// restore to hidden state, Mainwindow::readProperties() will
// restore saved visibility.
mw->restore(1, false);
}
mw->setAttribute(Qt::WA_DeleteOnClose);
setMainWindow(mw);
d->connectConfigureDialog();
}
void KleopatraApplication::openOrRaiseMainWindow()
{
MainWindow *mw = mainWindow();
if (!mw) {
mw = new MainWindow;
mw->setAttribute(Qt::WA_DeleteOnClose);
setMainWindow(mw);
d->connectConfigureDialog();
}
open_or_raise(mw);
UpdateNotification::checkUpdate(mw);
}
void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId)
{
if (!d->configureDialog) {
d->configureDialog = new ConfigureDialog;
d->configureDialog->setAttribute(Qt::WA_DeleteOnClose);
d->connectConfigureDialog();
}
// This is similar to what the commands do.
if (parentWId) {
if (QWidget *pw = QWidget::find(parentWId)) {
d->configureDialog->setParent(pw, d->configureDialog->windowFlags());
} else {
d->configureDialog->setAttribute(Qt::WA_NativeWindow, true);
KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId);
}
}
open_or_raise(d->configureDialog);
// If we have a parent we want to raise over it.
if (parentWId) {
d->configureDialog->raise();
}
}
void KleopatraApplication::openOrRaiseConfigDialog()
{
openConfigDialogWithForeignParent(0);
}
#ifndef QT_NO_SYSTEMTRAYICON
void KleopatraApplication::startMonitoringSmartCard()
{
d->readerStatus.startMonitoring();
}
#endif // QT_NO_SYSTEMTRAYICON
void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/)
{
openOrRaiseMainWindow();
if (!files.empty()) {
mainWindow()->importCertificatesFromFile(files);
}
}
void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto)
{
auto const cmd = new SignEncryptFilesCommand(files, nullptr);
cmd->setEncryptionPolicy(Force);
cmd->setSigningPolicy(Allow);
if (proto != GpgME::UnknownProtocol) {
cmd->setProtocol(proto);
}
cmd->start();
}
void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto)
{
auto const cmd = new SignEncryptFilesCommand(files, nullptr);
cmd->setSigningPolicy(Force);
cmd->setEncryptionPolicy(Deny);
if (proto != GpgME::UnknownProtocol) {
cmd->setProtocol(proto);
}
cmd->start();
}
void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto)
{
auto const cmd = new SignEncryptFilesCommand(files, nullptr);
if (proto != GpgME::UnknownProtocol) {
cmd->setProtocol(proto);
}
cmd->start();
}
void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
cmd->setOperation(Decrypt);
cmd->start();
}
void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
cmd->setOperation(Verify);
cmd->start();
}
void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
cmd->start();
}
void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
QStringList verifyFiles, createFiles;
for (const QString &file : files) {
if (isChecksumFile(file)) {
verifyFiles << file;
} else {
createFiles << file;
}
}
if (!verifyFiles.isEmpty()) {
auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr);
cmd->start();
}
if (!createFiles.isEmpty()) {
auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr);
cmd->start();
}
}
void KleopatraApplication::setIgnoreNewInstance(bool ignore)
{
d->ignoreNewInstance = ignore;
}
bool KleopatraApplication::ignoreNewInstance() const
{
return d->ignoreNewInstance;
}
void KleopatraApplication::blockUrl(const QUrl &url)
{
qCDebug(KLEOPATRA_LOG) << "Blocking URL" << url;
- KMessageBox::sorry(mainWindow(),i18n ("Opening an external link is administratively prohibited."),
+ KMessageBox::error(mainWindow(),i18n ("Opening an external link is administratively prohibited."),
i18n ("Prohibited"));
}
void KleopatraApplication::startGpgAgent()
{
Kleo::launchGpgAgent();
}
#include "kleopatraapplication.moc"
diff --git a/src/kwatchgnupg/kwatchgnupgmainwin.cpp b/src/kwatchgnupg/kwatchgnupgmainwin.cpp
index 63d428cbc..b448631ea 100644
--- a/src/kwatchgnupg/kwatchgnupgmainwin.cpp
+++ b/src/kwatchgnupg/kwatchgnupgmainwin.cpp
@@ -1,293 +1,293 @@
/*
kwatchgnupgmainwin.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2001, 2002, 2004 Klar �vdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "kwatchgnupgmainwin.h"
#include "kwatchgnupgconfig.h"
#include "kwatchgnupg.h"
#include "tray.h"
#include "utils/qt-cxx20-compat.h"
#include <QGpgME/Protocol>
#include <QGpgME/CryptoConfig>
#include <QTextEdit>
#include <KMessageBox>
#include <KLocalizedString>
#include <QApplication>
#include <QAction>
#include <KActionCollection>
#include <KStandardAction>
#include <KProcess>
#include <KConfig>
#include <KEditToolBar>
#include <KShortcutsDialog>
#include <QIcon>
#include <KConfigGroup>
#include <QEventLoop>
#include <QTextStream>
#include <QDateTime>
#include <QFileDialog>
#include <KSharedConfig>
#include <gpgme++/gpgmepp_version.h>
#if GPGMEPP_VERSION >= 0x11000 // 1.16.0
# define CRYPTOCONFIG_HAS_GROUPLESS_ENTRY_OVERLOAD
#endif
KWatchGnuPGMainWindow::KWatchGnuPGMainWindow(QWidget *parent)
: KXmlGuiWindow(parent, Qt::Window), mConfig(nullptr)
{
createActions();
createGUI();
mCentralWidget = new QTextEdit(this);
mCentralWidget->setReadOnly(true);
setCentralWidget(mCentralWidget);
mWatcher = new KProcess;
connect(mWatcher, SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(slotWatcherExited(int,QProcess::ExitStatus)));
connect(mWatcher, &QProcess::readyReadStandardOutput,
this, &KWatchGnuPGMainWindow::slotReadStdout);
slotReadConfig();
mSysTray = new KWatchGnuPGTray(this);
QAction *act = mSysTray->action(QStringLiteral("quit"));
if (act) {
connect(act, &QAction::triggered, this, &KWatchGnuPGMainWindow::slotQuit);
}
setAutoSaveSettings();
}
KWatchGnuPGMainWindow::~KWatchGnuPGMainWindow()
{
delete mWatcher;
}
void KWatchGnuPGMainWindow::slotClear()
{
mCentralWidget->clear();
mCentralWidget->append(i18n("[%1] Log cleared", QDateTime::currentDateTime().toString(Qt::ISODate)));
}
void KWatchGnuPGMainWindow::createActions()
{
QAction *action = actionCollection()->addAction(QStringLiteral("clear_log"));
action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
action->setText(i18n("C&lear History"));
connect(action, &QAction::triggered, this, &KWatchGnuPGMainWindow::slotClear);
actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_L));
(void)KStandardAction::saveAs(this, &KWatchGnuPGMainWindow::slotSaveAs, actionCollection());
(void)KStandardAction::close(this, &KWatchGnuPGMainWindow::close, actionCollection());
(void)KStandardAction::quit(this, &KWatchGnuPGMainWindow::slotQuit, actionCollection());
(void)KStandardAction::preferences(this, &KWatchGnuPGMainWindow::slotConfigure, actionCollection());
(void)KStandardAction::keyBindings(this, &KWatchGnuPGMainWindow::configureShortcuts, actionCollection());
(void)KStandardAction::configureToolbars(this, &KWatchGnuPGMainWindow::slotConfigureToolbars, actionCollection());
}
void KWatchGnuPGMainWindow::configureShortcuts()
{
KShortcutsDialog::showDialog(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this);
}
void KWatchGnuPGMainWindow::slotConfigureToolbars()
{
KEditToolBar dlg(factory());
dlg.exec();
}
void KWatchGnuPGMainWindow::startWatcher()
{
disconnect(mWatcher, SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(slotWatcherExited(int,QProcess::ExitStatus)));
if (mWatcher->state() == QProcess::Running) {
mWatcher->kill();
while (mWatcher->state() == QProcess::Running) {
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
}
mCentralWidget->append(i18n("[%1] Log stopped", QDateTime::currentDateTime().toString(Qt::ISODate)));
mCentralWidget->ensureCursorVisible();
}
mWatcher->clearProgram();
{
const KConfigGroup config(KSharedConfig::openConfig(), "WatchGnuPG");
*mWatcher << config.readEntry("Executable", WATCHGNUPGBINARY);
*mWatcher << QStringLiteral("--force");
*mWatcher << config.readEntry("Socket", WATCHGNUPGSOCKET);
}
mWatcher->setOutputChannelMode(KProcess::OnlyStdoutChannel);
mWatcher->start();
const bool ok = mWatcher->waitForStarted();
if (!ok) {
- KMessageBox::sorry(this, i18n("The watchgnupg logging process could not be started.\nPlease install watchgnupg somewhere in your $PATH.\nThis log window is unable to display any useful information."));
+ KMessageBox::error(this, i18n("The watchgnupg logging process could not be started.\nPlease install watchgnupg somewhere in your $PATH.\nThis log window is unable to display any useful information."));
} else {
mCentralWidget->append(i18n("[%1] Log started", QDateTime::currentDateTime().toString(Qt::ISODate)));
mCentralWidget->ensureCursorVisible();
}
connect(mWatcher, SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(slotWatcherExited(int,QProcess::ExitStatus)));
}
namespace
{
QGpgME::CryptoConfigEntry *getCryptoConfigEntry(const QGpgME::CryptoConfig *config, const QString &componentName, const char *entryName)
{
// copied from utils/compat.cpp in libkleopatra
#ifdef CRYPTOCONFIG_HAS_GROUPLESS_ENTRY_OVERLOAD
return config->entry(componentName, QString::fromLatin1(entryName));
#else
using namespace QGpgME;
const CryptoConfigComponent *const comp = config->component(componentName);
const QStringList groupNames = comp->groupList();
for (const auto &groupName : groupNames) {
const CryptoConfigGroup *const group = comp ? comp->group(groupName) : nullptr;
if (CryptoConfigEntry *const entry = group->entry(QString::fromLatin1(entryName))) {
return entry;
}
}
return nullptr;
#endif
}
}
void KWatchGnuPGMainWindow::setGnuPGConfig()
{
QStringList logclients;
// Get config object
QGpgME::CryptoConfig *const cconfig = QGpgME::cryptoConfig();
if (!cconfig) {
return;
}
KConfigGroup config(KSharedConfig::openConfig(), "WatchGnuPG");
const QStringList comps = cconfig->componentList();
for (QStringList::const_iterator it = comps.constBegin(); it != comps.constEnd(); ++it) {
const QGpgME::CryptoConfigComponent *const comp = cconfig->component(*it);
Q_ASSERT(comp);
{
QGpgME::CryptoConfigEntry *const entry = getCryptoConfigEntry(cconfig, comp->name(), "log-file");
if (entry) {
entry->setStringValue(QLatin1String("socket://") + config.readEntry("Socket", WATCHGNUPGSOCKET));
logclients << QStringLiteral("%1 (%2)").arg(*it, comp->description());
}
}
{
QGpgME::CryptoConfigEntry *const entry = getCryptoConfigEntry(cconfig, comp->name(), "debug-level");
if (entry) {
entry->setStringValue(config.readEntry("LogLevel", "basic"));
}
}
}
cconfig->sync(true);
if (logclients.isEmpty()) {
- KMessageBox::sorry(nullptr, i18n("There are no components available that support logging."));
+ KMessageBox::error(nullptr, i18n("There are no components available that support logging."));
}
}
void KWatchGnuPGMainWindow::slotWatcherExited(int, QProcess::ExitStatus)
{
if (KMessageBox::questionYesNo(this, i18n("The watchgnupg logging process died.\nDo you want to try to restart it?"), QString(), KGuiItem(i18n("Try Restart")), KGuiItem(i18n("Do Not Try"))) == KMessageBox::Yes) {
mCentralWidget->append(i18n("====== Restarting logging process ====="));
mCentralWidget->ensureCursorVisible();
startWatcher();
} else {
- KMessageBox::sorry(this, i18n("The watchgnupg logging process is not running.\nThis log window is unable to display any useful information."));
+ KMessageBox::error(this, i18n("The watchgnupg logging process is not running.\nThis log window is unable to display any useful information."));
}
}
void KWatchGnuPGMainWindow::slotReadStdout()
{
if (!mWatcher) {
return;
}
while (mWatcher->canReadLine()) {
QString str = QString::fromUtf8(mWatcher->readLine());
if (str.endsWith(QLatin1Char('\n'))) {
str.chop(1);
}
if (str.endsWith(QLatin1Char('\r'))) {
str.chop(1);
}
mCentralWidget->append(str);
mCentralWidget->ensureCursorVisible();
if (!isVisible()) {
// Change tray icon to show something happened
// PENDING(steffen)
mSysTray->setAttention(true);
}
}
}
void KWatchGnuPGMainWindow::show()
{
mSysTray->setAttention(false);
KMainWindow::show();
}
void KWatchGnuPGMainWindow::slotSaveAs()
{
const QString filename = QFileDialog::getSaveFileName(this, i18n("Save Log to File"));
if (filename.isEmpty()) {
return;
}
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
QTextStream(&file) << mCentralWidget->document()->toRawText();
} else
KMessageBox::information(this, i18n("Could not save file %1: %2",
filename, file.errorString()));
}
void KWatchGnuPGMainWindow::slotQuit()
{
disconnect(mWatcher, SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(slotWatcherExited(int,QProcess::ExitStatus)));
mWatcher->kill();
qApp->quit();
}
void KWatchGnuPGMainWindow::slotConfigure()
{
if (!mConfig) {
mConfig = new KWatchGnuPGConfig(this);
mConfig->setObjectName(QStringLiteral("config dialog"));
connect(mConfig, &KWatchGnuPGConfig::reconfigure,
this, &KWatchGnuPGMainWindow::slotReadConfig);
}
mConfig->loadConfig();
mConfig->exec();
}
void KWatchGnuPGMainWindow::slotReadConfig()
{
const KConfigGroup config(KSharedConfig::openConfig(), "LogWindow");
const int maxLogLen = config.readEntry("MaxLogLen", 10000);
mCentralWidget->document()->setMaximumBlockCount(maxLogLen < 1 ? -1 : maxLogLen);
setGnuPGConfig();
startWatcher();
}
bool KWatchGnuPGMainWindow::queryClose()
{
if (!qApp->isSavingSession()) {
hide();
return false;
}
return KMainWindow::queryClose();
}
diff --git a/src/main.cpp b/src/main.cpp
index ad83b7b10..3177fb46b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,273 +1,273 @@
/*
main.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2001, 2002, 2004, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "aboutdata.h"
#include "kleopatraapplication.h"
#include "mainwindow.h"
#include "accessibility/accessiblewidgetfactory.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <Kdelibs4ConfigMigrator>
#endif
#include <commands/reloadkeyscommand.h>
#include <commands/selftestcommand.h>
#include <Libkleo/GnuPG>
#include <utils/archivedefinition.h>
#include "utils/kuniqueservice.h"
#include "utils/userinfo.h"
#include <uiserver/uiserver.h>
#include <uiserver/assuancommand.h>
#include <uiserver/echocommand.h>
#include <uiserver/decryptcommand.h>
#include <uiserver/verifycommand.h>
#include <uiserver/decryptverifyfilescommand.h>
#include <uiserver/decryptfilescommand.h>
#include <uiserver/verifyfilescommand.h>
#include <uiserver/prepencryptcommand.h>
#include <uiserver/prepsigncommand.h>
#include <uiserver/encryptcommand.h>
#include <uiserver/signcommand.h>
#include <uiserver/signencryptfilescommand.h>
#include <uiserver/selectcertificatecommand.h>
#include <uiserver/importfilescommand.h>
#include <uiserver/createchecksumscommand.h>
#include <uiserver/verifychecksumscommand.h>
#include <Libkleo/ChecksumDefinition>
#include "kleopatra_debug.h"
#include "kleopatra_options.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <KCrash>
#include <QAccessible>
#include <QTextDocument> // for Qt::escape
#include <QMessageBox>
#include <QTimer>
#include <QTime>
#include <QEventLoop>
#include <QThreadPool>
#include <QElapsedTimer>
#include <gpgme++/global.h>
#include <gpgme++/error.h>
#include <memory>
#include <iostream>
#include <QCommandLineParser>
static bool selfCheck()
{
Kleo::Commands::SelfTestCommand cmd(nullptr);
cmd.setAutoDelete(false);
cmd.setAutomaticMode(true);
QEventLoop loop;
QObject::connect(&cmd, &Kleo::Commands::SelfTestCommand::finished, &loop, &QEventLoop::quit);
QTimer::singleShot(0, &cmd, &Kleo::Command::start); // start() may Q_EMIT finished()...
loop.exec();
if (cmd.isCanceled()) {
return false;
} else {
return true;
}
}
static void fillKeyCache(Kleo::UiServer *server)
{
auto cmd = new Kleo::ReloadKeysCommand(nullptr);
QObject::connect(cmd, SIGNAL(finished()), server, SLOT(enableCryptoCommands()));
cmd->start();
}
int main(int argc, char **argv)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
#endif
KleopatraApplication app(argc, argv);
KCrash::initialize();
QAccessible::installFactory(Kleo::accessibleWidgetFactory);
QElapsedTimer timer;
timer.start();
// Initialize GpgME
{
const GpgME::Error gpgmeInitError = GpgME::initializeLibrary(0);
if (gpgmeInitError) {
- KMessageBox::sorry(nullptr, xi18nc("@info",
+ KMessageBox::error(nullptr, xi18nc("@info",
"<para>The version of the <application>GpgME</application> library you are running against "
"is older than the one that the <application>GpgME++</application> library was built against.</para>"
"<para><application>Kleopatra</application> will not function in this setting.</para>"
"<para>Please ask your administrator for help in resolving this issue.</para>"),
i18nc("@title", "GpgME Too Old"));
return EXIT_FAILURE;
}
qCDebug(KLEOPATRA_LOG) << "Startup timing:" << timer.elapsed() << "ms elapsed: GPGME Initialized";
}
KLocalizedString::setApplicationDomain("kleopatra");
AboutData aboutData;
KAboutData::setApplicationData(aboutData);
if (Kleo::userIsElevated()) {
/* This is a safeguard against bugreports that something fails because
* of permission problems on windows. Some users still have the Windows
* Vista behavior of running things as Administrator. This can break
* GnuPG in horrible ways for example if a stale lockfile is left that
* can't be removed without another elevation.
*
* Note: This is not the same as running as root on Linux. Elevated means
* that you are temporarily running with the "normal" user environment but
* with elevated permissions.
* */
if (KMessageBox::warningContinueCancel(nullptr, xi18nc("@info",
"<para><application>Kleopatra</application> cannot be run as adminstrator without "
"breaking file permissions in the GnuPG data folder.</para>"
"<para>To manage keys for other users please manage them as a normal user and "
"copy the <filename>AppData\\Roaming\\gnupg</filename> directory with proper permissions.</para>") +
xi18n("<para>Are you sure that you want to continue?</para>"),
i18nc("@title", "Running as Administrator")) != KMessageBox::Continue) {
return EXIT_FAILURE;
}
qCWarning(KLEOPATRA_LOG) << "User is running with administrative permissions.";
}
KUniqueService service;
QObject::connect(&service, &KUniqueService::activateRequested,
&app, &KleopatraApplication::slotActivateRequested);
QObject::connect(&app, &KleopatraApplication::setExitValue,
&service, [&service](int i) {
service.setExitValue(i);
});
// Delay init after KUniqueservice call as this might already
// have terminated us and so we can avoid overhead (e.g. keycache
// setup / systray icon).
qCDebug(KLEOPATRA_LOG) << "Startup timing:" << timer.elapsed() << "ms elapsed: Service created";
app.init();
qCDebug(KLEOPATRA_LOG) << "Startup timing:" << timer.elapsed() << "ms elapsed: Application initialized";
QCommandLineParser parser;
aboutData.setupCommandLine(&parser);
kleopatra_options(&parser);
parser.process(QApplication::arguments());
aboutData.processCommandLine(&parser);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
Kdelibs4ConfigMigrator migrate(QStringLiteral("kleopatra"));
migrate.setConfigFiles(QStringList() << QStringLiteral("kleopatrarc")
<< QStringLiteral("libkleopatrarc"));
migrate.setUiFiles(QStringList() << QStringLiteral("kleopatra.rc"));
migrate.migrate();
#endif
qCDebug(KLEOPATRA_LOG) << "Startup timing:" << timer.elapsed() << "ms elapsed: Application created";
{
const unsigned int threads = QThreadPool::globalInstance()->maxThreadCount();
QThreadPool::globalInstance()->setMaxThreadCount(qMax(2U, threads));
}
Kleo::ChecksumDefinition::setInstallPath(Kleo::gpg4winInstallPath());
Kleo::ArchiveDefinition::setInstallPath(Kleo::gnupgInstallPath());
int rc;
Kleo::UiServer server(parser.value(QStringLiteral("uiserver-socket")));
try {
qCDebug(KLEOPATRA_LOG) << "Startup timing:" << timer.elapsed() << "ms elapsed: UiServer created";
QObject::connect(&server, &Kleo::UiServer::startKeyManagerRequested, &app, &KleopatraApplication::openOrRaiseMainWindow);
QObject::connect(&server, &Kleo::UiServer::startConfigDialogRequested, &app, &KleopatraApplication::openOrRaiseConfigDialog);
#define REGISTER( Command ) server.registerCommandFactory( std::shared_ptr<Kleo::AssuanCommandFactory>( new Kleo::GenericAssuanCommandFactory<Kleo::Command> ) )
REGISTER(CreateChecksumsCommand);
REGISTER(DecryptCommand);
REGISTER(DecryptFilesCommand);
REGISTER(DecryptVerifyFilesCommand);
REGISTER(EchoCommand);
REGISTER(EncryptCommand);
REGISTER(EncryptFilesCommand);
REGISTER(EncryptSignFilesCommand);
REGISTER(ImportFilesCommand);
REGISTER(PrepEncryptCommand);
REGISTER(PrepSignCommand);
REGISTER(SelectCertificateCommand);
REGISTER(SignCommand);
REGISTER(SignEncryptFilesCommand);
REGISTER(SignFilesCommand);
REGISTER(VerifyChecksumsCommand);
REGISTER(VerifyCommand);
REGISTER(VerifyFilesCommand);
#undef REGISTER
server.start();
qCDebug(KLEOPATRA_LOG) << "Startup timing:" << timer.elapsed() << "ms elapsed: UiServer started";
} catch (const std::exception &e) {
qCDebug(KLEOPATRA_LOG) << "Failed to start UI Server: " << e.what();
#ifdef Q_OS_WIN
// Once there actually is a plugin for other systems then Windows this
// error should probably be shown, too. But currently only Windows users need
// to care.
QMessageBox::information(nullptr, i18n("GPG UI Server Error"),
i18n("<qt>The Kleopatra GPG UI Server Module could not be initialized.<br/>"
"The error given was: <b>%1</b><br/>"
"You can use Kleopatra as a certificate manager, but cryptographic plugins that "
"rely on a GPG UI Server being present might not work correctly, or at all.</qt>",
QString::fromUtf8(e.what()).toHtmlEscaped()));
#endif
}
const bool daemon = parser.isSet(QStringLiteral("daemon"));
if (!daemon && app.isSessionRestored()) {
app.restoreMainWindow();
}
if (!selfCheck()) {
return EXIT_FAILURE;
}
qCDebug(KLEOPATRA_LOG) << "Startup timing:" << timer.elapsed() << "ms elapsed: SelfCheck completed";
fillKeyCache(&server);
#ifndef QT_NO_SYSTEMTRAYICON
app.startMonitoringSmartCard();
#endif
app.setIgnoreNewInstance(false);
if (!daemon) {
const QString err = app.newInstance(parser);
if (!err.isEmpty()) {
std::cerr << i18n("Invalid arguments: %1", err).toLocal8Bit().constData() << "\n";
return EXIT_FAILURE;
}
qCDebug(KLEOPATRA_LOG) << "Startup timing:" << timer.elapsed() << "ms elapsed: new instance created";
}
rc = app.exec();
app.setIgnoreNewInstance(true);
QObject::disconnect(&server, &Kleo::UiServer::startKeyManagerRequested, &app, &KleopatraApplication::openOrRaiseMainWindow);
QObject::disconnect(&server, &Kleo::UiServer::startConfigDialogRequested, &app, &KleopatraApplication::openOrRaiseConfigDialog);
server.stop();
server.waitForStopped();
return rc;
}
diff --git a/src/view/openpgpkeycardwidget.cpp b/src/view/openpgpkeycardwidget.cpp
index 40df9a599..9e4dbe131 100644
--- a/src/view/openpgpkeycardwidget.cpp
+++ b/src/view/openpgpkeycardwidget.cpp
@@ -1,250 +1,250 @@
/* view/openpgpkeycardwidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "openpgpkeycardwidget.h"
#include "commands/detailscommand.h"
#include "smartcard/card.h"
#include "smartcard/keypairinfo.h"
#include "smartcard/openpgpcard.h"
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <KMessageBox>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace SmartCard;
namespace
{
struct KeyWidgets {
std::string cardKeyRef;
std::string keyGrip;
std::string keyFingerprint;
QLabel *keyTitleLabel = nullptr;
QLabel *keyInfoLabel = nullptr;
QPushButton *showCertificateDetailsButton = nullptr;
QPushButton *createCSRButton = nullptr;
};
KeyWidgets createKeyWidgets(const KeyPairInfo &keyInfo, QWidget *parent)
{
KeyWidgets keyWidgets;
keyWidgets.keyTitleLabel = new QLabel{OpenPGPCard::keyDisplayName(keyInfo.keyRef), parent};
keyWidgets.keyInfoLabel = new QLabel{parent};
keyWidgets.keyInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard);
keyWidgets.showCertificateDetailsButton = new QPushButton{i18nc("@action:button", "Show Details"), parent};
keyWidgets.showCertificateDetailsButton->setToolTip(i18nc("@action:tooltip", "Show detailed information about this key"));
keyWidgets.showCertificateDetailsButton->setEnabled(false);
if (keyInfo.canCertify() || keyInfo.canSign() || keyInfo.canAuthenticate())
{
keyWidgets.createCSRButton = new QPushButton{i18nc("@action:button", "Create CSR"), parent};
keyWidgets.createCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for this key"));
keyWidgets.createCSRButton->setEnabled(false);
}
return keyWidgets;
}
}
class OpenPGPKeyCardWidget::Private
{
public:
explicit Private(OpenPGPKeyCardWidget *q);
~Private() = default;
void setAllowedActions(Actions actions);
void update(const Card *card = nullptr);
private:
void updateCachedValues(const std::string &openPGPKeyRef, const std::string &cardKeyRef, const Card *card);
void updateKeyWidgets(const std::string &openPGPKeyRef);
void showCertificateDetails(const std::string &openPGPKeyRef);
private:
OpenPGPKeyCardWidget *const q;
Actions mAllowedActions = AllActions;
std::map<std::string, KeyWidgets> mKeyWidgets;
};
OpenPGPKeyCardWidget::Private::Private(OpenPGPKeyCardWidget *q)
: q{q}
{
auto grid = new QGridLayout{q};
grid->setContentsMargins(0, 0, 0, 0);
for (const auto &keyInfo : OpenPGPCard::supportedKeys()) {
const KeyWidgets keyWidgets = createKeyWidgets(keyInfo, q);
const std::string keyRef = keyInfo.keyRef;
connect(keyWidgets.showCertificateDetailsButton, &QPushButton::clicked,
q, [this, keyRef] () { showCertificateDetails(keyRef); });
if (keyWidgets.createCSRButton) {
connect(keyWidgets.createCSRButton, &QPushButton::clicked,
q, [q, keyRef] () { Q_EMIT q->createCSRRequested(keyRef); });
}
const int row = grid->rowCount();
grid->addWidget(keyWidgets.keyTitleLabel, row, 0, Qt::AlignTop);
grid->addWidget(keyWidgets.keyInfoLabel, row, 1, Qt::AlignTop);
auto buttons = new QHBoxLayout;
buttons->addWidget(keyWidgets.showCertificateDetailsButton);
if (keyWidgets.createCSRButton) {
buttons->addWidget(keyWidgets.createCSRButton);
}
buttons->addStretch(1);
grid->addLayout(buttons, row, 2, Qt::AlignTop);
mKeyWidgets.insert({keyInfo.keyRef, keyWidgets});
}
grid->setColumnStretch(grid->columnCount(), 1);
}
void OpenPGPKeyCardWidget::Private::setAllowedActions(Actions actions)
{
mAllowedActions = actions;
update();
}
void OpenPGPKeyCardWidget::Private::update(const Card *card)
{
if (card) {
updateCachedValues(OpenPGPCard::pgpSigKeyRef(), card->signingKeyRef(), card);
updateCachedValues(OpenPGPCard::pgpEncKeyRef(), card->encryptionKeyRef(), card);
updateCachedValues(OpenPGPCard::pgpAuthKeyRef(), card->authenticationKeyRef(), card);
}
updateKeyWidgets(OpenPGPCard::pgpSigKeyRef());
updateKeyWidgets(OpenPGPCard::pgpEncKeyRef());
updateKeyWidgets(OpenPGPCard::pgpAuthKeyRef());
}
void OpenPGPKeyCardWidget::Private::updateCachedValues(const std::string &openPGPKeyRef, const std::string &cardKeyRef, const Card *card)
{
KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef);
widgets.cardKeyRef = cardKeyRef;
widgets.keyGrip = card->keyInfo(cardKeyRef).grip;
widgets.keyFingerprint = card->keyFingerprint(openPGPKeyRef);
}
void OpenPGPKeyCardWidget::Private::updateKeyWidgets(const std::string &openPGPKeyRef)
{
const KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef);
const auto cardSupportsKey = !widgets.cardKeyRef.empty();
widgets.keyTitleLabel->setVisible(cardSupportsKey);
widgets.keyInfoLabel->setVisible(cardSupportsKey);
widgets.showCertificateDetailsButton->setVisible(cardSupportsKey);
if (widgets.createCSRButton) {
widgets.createCSRButton->setVisible(cardSupportsKey && (mAllowedActions & Action::CreateCSR));
}
if (!cardSupportsKey) {
return;
}
widgets.showCertificateDetailsButton->setEnabled(false);
if (widgets.keyFingerprint.empty()) {
widgets.keyInfoLabel->setTextFormat(Qt::RichText);
widgets.keyInfoLabel->setText(i18nc("@info", "<em>No key</em>"));
if (widgets.createCSRButton) {
widgets.createCSRButton->setEnabled(false);
}
} else {
QStringList lines;
if (widgets.keyFingerprint.size() >= 16) {
const std::string keyid = widgets.keyFingerprint.substr(widgets.keyFingerprint.size() - 16);
const auto subkeys = KeyCache::instance()->findSubkeysByKeyID({keyid});
if (subkeys.empty() || subkeys[0].isNull()) {
widgets.keyInfoLabel->setTextFormat(Qt::RichText);
lines.push_back(i18nc("@info", "<em>Public key not found locally</em>"));
widgets.keyInfoLabel->setToolTip({});
} else {
// force interpretation of text as plain text to avoid problems with HTML in user IDs
widgets.keyInfoLabel->setTextFormat(Qt::PlainText);
QStringList toolTips;
toolTips.reserve(subkeys.size());
for (const auto &sub: subkeys) {
// Yep you can have one subkey associated with multiple primary keys.
const GpgME::Key key = sub.parent();
toolTips << Formatting::toolTip(key,
Formatting::Validity |
Formatting::ExpiryDates |
Formatting::UserIDs |
Formatting::Fingerprint);
const auto uids = key.userIDs();
for (const auto &uid: uids) {
lines.push_back(Formatting::prettyUserID(uid));
}
}
widgets.keyInfoLabel->setToolTip(toolTips.join(QLatin1String("<br/>")));
widgets.showCertificateDetailsButton->setEnabled(true);
}
} else {
widgets.keyInfoLabel->setTextFormat(Qt::RichText);
lines.push_back(i18nc("@info", "<em>Invalid fingerprint</em>"));
}
const QString fingerprint = widgets.keyInfoLabel->textFormat() == Qt::RichText ?
Formatting::prettyID(widgets.keyFingerprint.c_str()).replace(QLatin1Char(' '), QLatin1String("&nbsp;")) :
Formatting::prettyID(widgets.keyFingerprint.c_str());
lines.insert(0, fingerprint);
const auto lineSeparator = widgets.keyInfoLabel->textFormat() == Qt::PlainText ? QLatin1String("\n") : QLatin1String("<br>");
widgets.keyInfoLabel->setText(lines.join(lineSeparator));
if (widgets.createCSRButton) {
widgets.createCSRButton->setEnabled(true);
}
}
}
void OpenPGPKeyCardWidget::Private::showCertificateDetails(const std::string &openPGPKeyRef)
{
const KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef);
if (widgets.keyFingerprint.size() >= 16) {
const std::string keyid = widgets.keyFingerprint.substr(widgets.keyFingerprint.size() - 16);
const auto subkeys = KeyCache::instance()->findSubkeysByKeyID({keyid});
if (!subkeys.empty() && !subkeys[0].isNull()) {
auto cmd = new Commands::DetailsCommand(subkeys[0].parent(), nullptr);
cmd->setParentWidget(q);
cmd->start();
return;
}
}
- KMessageBox::sorry(q, i18nc("@info", "Sorry, I cannot find the key with fingerprint %1.",
+ KMessageBox::error(q, i18nc("@info", "Sorry, I cannot find the key with fingerprint %1.",
Formatting::prettyID(widgets.keyFingerprint.c_str())));
}
OpenPGPKeyCardWidget::OpenPGPKeyCardWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
OpenPGPKeyCardWidget::~OpenPGPKeyCardWidget() = default;
void OpenPGPKeyCardWidget::setAllowedActions(Actions actions)
{
d->setAllowedActions(actions);
}
void OpenPGPKeyCardWidget::update(const Card *card)
{
d->update(card);
}
diff --git a/src/view/padwidget.cpp b/src/view/padwidget.cpp
index 8caddc0b0..5128001fe 100644
--- a/src/view/padwidget.cpp
+++ b/src/view/padwidget.cpp
@@ -1,540 +1,540 @@
/* -*- mode: c++; c-basic-offset:4 -*-
padwidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "padwidget.h"
#include "kleopatra_debug.h"
#include <settings.h>
#include <Libkleo/KleoException>
#include <Libkleo/Classify>
#include <Libkleo/Compliance>
#include <Libkleo/KeyCache>
#include <Libkleo/Formatting>
#include <Libkleo/SystemInfo>
#include "crypto/gui/signencryptwidget.h"
#include "crypto/gui/resultitemwidget.h"
#include "crypto/signencrypttask.h"
#include "crypto/decryptverifytask.h"
#include <Libkleo/GnuPG>
#include "utils/input.h"
#include "utils/output.h"
#include "commands/importcertificatefromdatacommand.h"
#include <gpgme++/data.h>
#include <gpgme++/decryptionresult.h>
#include <QGpgME/DataProvider>
#include <QButtonGroup>
#include <QFontDatabase>
#include <QFontMetrics>
#include <QLabel>
#include <QProgressBar>
#include <QPushButton>
#include <QRadioButton>
#include <QTabWidget>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QStyle>
#include <KLocalizedString>
#include <KColorScheme>
#include <KMessageBox>
#include <KMessageWidget>
#include <KSharedConfig>
#include <KConfigGroup>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace Kleo::Crypto::Gui;
static GpgME::Protocol getProtocol(const std::shared_ptr<const Kleo::Crypto::Task::Result> &result)
{
const auto dvResult = dynamic_cast<const Kleo::Crypto::DecryptVerifyResult*>(result.get());
if (dvResult) {
for (const auto &key: KeyCache::instance()->findRecipients(dvResult->decryptionResult())) {
return key.protocol();
}
for (const auto &key: KeyCache::instance()->findSigners(dvResult->verificationResult())) {
return key.protocol();
}
}
return GpgME::UnknownProtocol;
}
class PadWidget::Private
{
friend class ::Kleo::PadWidget;
public:
Private(PadWidget *qq):
q(qq),
mEdit(new QTextEdit),
mCryptBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit-sign-encrypt")), i18n("Sign / Encrypt Notepad"))),
mDecryptBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit-decrypt-verify")), i18n("Decrypt / Verify Notepad"))),
mRevertBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Revert"))),
mMessageWidget{new KMessageWidget},
mAdditionalInfoLabel(new QLabel),
mSigEncWidget(new SignEncryptWidget(nullptr, true)),
mProgressBar(new QProgressBar),
mProgressLabel(new QLabel),
mLastResultWidget(nullptr),
mPGPRB(nullptr),
mCMSRB(nullptr),
mImportProto(GpgME::UnknownProtocol)
{
auto vLay = new QVBoxLayout(q);
auto btnLay = new QHBoxLayout;
vLay->addLayout(btnLay);
btnLay->addWidget(mCryptBtn);
btnLay->addWidget(mDecryptBtn);
btnLay->addWidget(mRevertBtn);
mRevertBtn->setVisible(false);
btnLay->addWidget(mAdditionalInfoLabel);
btnLay->addStretch(-1);
mMessageWidget->setMessageType(KMessageWidget::Warning);
mMessageWidget->setIcon(q->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, q));
mMessageWidget->setText(i18n("Signing and encryption is not possible."));
mMessageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"<para>You cannot use <application>Kleopatra</application> for signing or encryption "
"because the <application>GnuPG</application> system used by <application>Kleopatra</application> is not %1.</para>",
DeVSCompliance::name(true)));
mMessageWidget->setCloseButtonVisible(false);
mMessageWidget->setVisible(false);
vLay->addWidget(mMessageWidget);
mProgressBar->setRange(0, 0);
mProgressBar->setVisible(false);
mProgressLabel->setVisible(false);
auto progLay = new QHBoxLayout;
progLay->addWidget(mProgressLabel);
progLay->addWidget(mProgressBar);
mStatusLay = new QVBoxLayout;
mStatusLay->addLayout(progLay);
vLay->addLayout(mStatusLay, 0);
auto tabWidget = new QTabWidget;
vLay->addWidget(tabWidget, 1);
tabWidget->addTab(mEdit, QIcon::fromTheme(QStringLiteral("edittext")), i18n("Notepad"));
// The recipients area
auto recipientsWidget = new QWidget;
auto recipientsVLay = new QVBoxLayout(recipientsWidget);
auto protocolSelectionLay = new QHBoxLayout;
bool pgpOnly = KeyCache::instance()->pgpOnly();
if (!pgpOnly) {
recipientsVLay->addLayout(protocolSelectionLay);
}
protocolSelectionLay->addWidget(new QLabel(i18n("<h3>Protocol:</h3>")));
protocolSelectionLay->addStretch(-1);
// Once S/MIME is supported add radio for S/MIME here.
recipientsVLay->addWidget(mSigEncWidget);
tabWidget->addTab(recipientsWidget, QIcon::fromTheme(QStringLiteral("contact-new-symbolic")),
i18n("Recipients"));
mEdit->setPlaceholderText(i18n("Enter a message to encrypt or decrypt..."));
auto fixedFont = QFont(QStringLiteral("Monospace"));
fixedFont.setStyleHint(QFont::TypeWriter);
// This does not work well
// QFontDatabase::systemFont(QFontDatabase::FixedFont);
mEdit->setFont(fixedFont);
mEdit->setAcceptRichText(false);
mEdit->setMinimumWidth(QFontMetrics(fixedFont).averageCharWidth() * 70);
if (KeyCache::instance()->pgpOnly() || !Settings{}.cmsEnabled()) {
mSigEncWidget->setProtocol(GpgME::OpenPGP);
} else {
auto grp = new QButtonGroup(q);
auto mPGPRB = new QRadioButton(i18n("OpenPGP"));
auto mCMSRB = new QRadioButton(i18n("S/MIME"));
grp->addButton(mPGPRB);
grp->addButton(mCMSRB);
KConfigGroup config(KSharedConfig::openConfig(), "Notepad");
if (config.readEntry("wasCMS", false)) {
mCMSRB->setChecked(true);
mSigEncWidget->setProtocol(GpgME::CMS);
} else {
mPGPRB->setChecked(true);
mSigEncWidget->setProtocol(GpgME::OpenPGP);
}
protocolSelectionLay->addWidget(mPGPRB);
protocolSelectionLay->addWidget(mCMSRB);
connect(mPGPRB, &QRadioButton::toggled, q, [this] (bool value) {
if (value) {
mSigEncWidget->setProtocol(GpgME::OpenPGP);
}
});
connect(mCMSRB, &QRadioButton::toggled, q, [this] (bool value) {
if (value) {
mSigEncWidget->setProtocol(GpgME::CMS);
}
});
}
updateCommitButton();
connect(mEdit, &QTextEdit::textChanged, q, [this] () {
updateCommitButton();
});
connect(mCryptBtn, &QPushButton::clicked, q, [this] () {
if (mImportProto != GpgME::UnknownProtocol) {
doImport();
} else {
doEncryptSign();
}
});
connect(mSigEncWidget, &SignEncryptWidget::operationChanged, q, [this] (const QString &) {
updateCommitButton();
});
connect(mDecryptBtn, &QPushButton::clicked, q, [this] () {
doDecryptVerify();
});
connect(mRevertBtn, &QPushButton::clicked, q, [this] () {
revert();
});
}
void revert()
{
mEdit->setPlainText(QString::fromUtf8(mInputData));
mRevertBtn->setVisible(false);
}
void updateRecipientsFromResult(const Kleo::Crypto::DecryptVerifyResult &result)
{
const auto decResult = result.decryptionResult();
for (const auto &recipient: decResult.recipients()) {
if (!recipient.keyID()) {
continue;
}
GpgME::Key key;
if (strlen(recipient.keyID()) < 16) {
key = KeyCache::instance()->findByShortKeyID(recipient.keyID());
} else {
key = KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID());
}
if (key.isNull()) {
std::vector<std::string> subids;
subids.push_back(std::string(recipient.keyID()));
for (const auto &subkey: KeyCache::instance()->findSubkeysByKeyID(subids)) {
key = subkey.parent();
break;
}
}
if (key.isNull()) {
qCDebug(KLEOPATRA_LOG) << "Unknown key" << recipient.keyID();
mSigEncWidget->addUnknownRecipient(recipient.keyID());
continue;
}
bool keyFound = false;
for (const auto &existingKey: mSigEncWidget->recipients()) {
if (existingKey.primaryFingerprint() && key.primaryFingerprint() &&
!strcmp (existingKey.primaryFingerprint(), key.primaryFingerprint())) {
keyFound = true;
break;
}
}
if (!keyFound) {
mSigEncWidget->addRecipient(key);
}
}
}
void cryptDone(const std::shared_ptr<const Kleo::Crypto::Task::Result> &result)
{
updateCommitButton();
mProgressBar->setVisible(false);
mProgressLabel->setVisible(false);
mLastResultWidget = new ResultItemWidget(result);
mLastResultWidget->showCloseButton(true);
mStatusLay->addWidget(mLastResultWidget);
connect(mLastResultWidget, &ResultItemWidget::closeButtonClicked, q, [this] () {
removeLastResultItem();
});
// Check result protocol
if (mPGPRB) {
auto proto = getProtocol(result);
if (proto == GpgME::UnknownProtocol) {
proto = mPGPRB->isChecked() ? GpgME::OpenPGP : GpgME::CMS;
} else if (proto == GpgME::OpenPGP) {
mPGPRB->setChecked(true);
} else if (proto == GpgME::CMS) {
mCMSRB->setChecked(true);
}
KConfigGroup config(KSharedConfig::openConfig(), "Notepad");
config.writeEntry("wasCMS", proto == GpgME::CMS);
}
if (result->errorCode()) {
if (!result->errorString().isEmpty()) {
KMessageBox::error(q,
result->errorString(),
i18nc("@title", "Error in crypto action"));
}
return;
}
mEdit->setPlainText(QString::fromUtf8(mOutputData));
mOutputData.clear();
mRevertBtn->setVisible(true);
mDecryptBtn->setEnabled(true);
const auto decryptVerifyResult = dynamic_cast<const Kleo::Crypto::DecryptVerifyResult*>(result.get());
if (decryptVerifyResult) {
updateRecipientsFromResult(*decryptVerifyResult);
}
}
void doDecryptVerify()
{
doCryptoCommon();
mSigEncWidget->clearAddedRecipients();
mProgressLabel->setText(i18n("Decrypt / Verify") + QStringLiteral("..."));
auto input = Input::createFromByteArray(&mInputData, i18n("Notepad"));
auto output = Output::createFromByteArray(&mOutputData, i18n("Notepad"));
AbstractDecryptVerifyTask *task;
auto classification = input->classification();
if (classification & Class::OpaqueSignature ||
classification & Class::ClearsignedMessage) {
auto verifyTask = new VerifyOpaqueTask();
verifyTask->setInput(input);
verifyTask->setOutput(output);
task = verifyTask;
} else {
auto decTask = new DecryptVerifyTask();
decTask->setInput(input);
decTask->setOutput(output);
task = decTask;
}
try {
task->autodetectProtocolFromInput();
} catch (const Kleo::Exception &e) {
KMessageBox::error(q,
e.message(),
i18nc("@title", "Error in crypto action"));
updateCommitButton();
mProgressBar->setVisible(false);
mProgressLabel->setVisible(false);
return;
}
connect (task, &Task::result, q, [this, task] (const std::shared_ptr<const Kleo::Crypto::Task::Result> &result) {
qCDebug(KLEOPATRA_LOG) << "Decrypt / Verify done. Err:" << result->errorCode();
task->deleteLater();
cryptDone(result);
});
task->start();
}
void removeLastResultItem()
{
if (mLastResultWidget) {
mStatusLay->removeWidget(mLastResultWidget);
delete mLastResultWidget;
mLastResultWidget = nullptr;
}
}
void doCryptoCommon()
{
mCryptBtn->setEnabled(false);
mDecryptBtn->setEnabled(false);
mProgressBar->setVisible(true);
mProgressLabel->setVisible(true);
mInputData = mEdit->toPlainText().toUtf8();
removeLastResultItem();
}
void doEncryptSign()
{
if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) {
- KMessageBox::sorry(q->topLevelWidget(),
+ KMessageBox::error(q->topLevelWidget(),
xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"<para>Sorry! You cannot use <application>Kleopatra</application> for signing or encryption "
"because the <application>GnuPG</application> system used by <application>Kleopatra</application> is not %1.</para>",
DeVSCompliance::name(true)));
return;
}
doCryptoCommon();
mProgressLabel->setText(mSigEncWidget->currentOp() + QStringLiteral("..."));
auto input = Input::createFromByteArray(&mInputData, i18n("Notepad"));
auto output = Output::createFromByteArray(&mOutputData, i18n("Notepad"));
auto task = new SignEncryptTask();
task->setInput(input);
task->setOutput(output);
const auto sigKey = mSigEncWidget->signKey();
const std::vector<GpgME::Key> recipients = mSigEncWidget->recipients();
const bool encrypt = mSigEncWidget->encryptSymmetric() || !recipients.empty();
const bool sign = !sigKey.isNull();
if (sign) {
task->setSign(true);
std::vector<GpgME::Key> signVector;
signVector.push_back(sigKey);
task->setSigners(signVector);
} else {
task->setSign(false);
}
task->setEncrypt(encrypt);
task->setRecipients(recipients);
task->setEncryptSymmetric(mSigEncWidget->encryptSymmetric());
task->setAsciiArmor(true);
if (sign && !encrypt && sigKey.protocol() == GpgME::OpenPGP) {
task->setClearsign(true);
}
connect (task, &Task::result, q, [this, task] (const std::shared_ptr<const Kleo::Crypto::Task::Result> &result) {
qCDebug(KLEOPATRA_LOG) << "Encrypt / Sign done. Err:" << result->errorCode();
task->deleteLater();
cryptDone(result);
});
task->start();
}
void doImport()
{
doCryptoCommon();
mProgressLabel->setText(i18n("Importing..."));
auto cmd = new Kleo::ImportCertificateFromDataCommand(mInputData, mImportProto);
connect(cmd, &Kleo::ImportCertificatesCommand::finished, q, [this] () {
mCryptBtn->setEnabled(true);
mDecryptBtn->setEnabled(true);
mProgressBar->setVisible(false);
mProgressLabel->setVisible(false);
updateCommitButton();
mRevertBtn->setVisible(true);
mEdit->setPlainText(QString());
});
cmd->start();
}
void checkImportProtocol()
{
QGpgME::QByteArrayDataProvider dp(mEdit->toPlainText().toUtf8());
GpgME::Data data(&dp);
auto type = data.type();
if (type == GpgME::Data::PGPKey) {
mImportProto = GpgME::OpenPGP;
} else if (type == GpgME::Data::X509Cert ||
type == GpgME::Data::PKCS12) {
mImportProto = GpgME::CMS;
} else {
mImportProto = GpgME::UnknownProtocol;
}
}
void updateCommitButton()
{
mAdditionalInfoLabel->setVisible(false);
checkImportProtocol();
if (mImportProto != GpgME::UnknownProtocol) {
mCryptBtn->setText(i18nc("1 is an operation to apply to the notepad. "
"Like Sign/Encrypt or just Encrypt.", "%1 Notepad",
i18n("Import")));
mCryptBtn->setEnabled(true);
return;
}
if (!mSigEncWidget->currentOp().isEmpty()) {
mCryptBtn->setEnabled(true);
mCryptBtn->setText(i18nc("1 is an operation to apply to the notepad. "
"Like Sign/Encrypt or just Encrypt.", "%1 Notepad",
mSigEncWidget->currentOp()));
} else {
mCryptBtn->setText(i18n("Sign / Encrypt Notepad"));
mCryptBtn->setEnabled(false);
}
if (DeVSCompliance::isActive()) {
const bool de_vs = DeVSCompliance::isCompliant() && mSigEncWidget->isDeVsAndValid();
DeVSCompliance::decorate(mCryptBtn, de_vs);
mAdditionalInfoLabel->setText(de_vs
? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"%1 communication possible.", DeVSCompliance::name(true))
: i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"%1 communication not possible.", DeVSCompliance::name(true)));
mAdditionalInfoLabel->setVisible(true);
if (!DeVSCompliance::isCompliant()) {
mCryptBtn->setEnabled(false);
}
mMessageWidget->setVisible(!DeVSCompliance::isCompliant());
}
}
private:
PadWidget *const q;
QTextEdit *mEdit;
QPushButton *mCryptBtn;
QPushButton *mDecryptBtn;
QPushButton *mRevertBtn;
KMessageWidget *mMessageWidget;
QLabel *mAdditionalInfoLabel;
QByteArray mInputData;
QByteArray mOutputData;
SignEncryptWidget *mSigEncWidget;
QProgressBar *mProgressBar;
QLabel *mProgressLabel;
QVBoxLayout *mStatusLay;
ResultItemWidget *mLastResultWidget;
QList<GpgME::Key> mAutoAddedKeys;
QRadioButton *mPGPRB;
QRadioButton *mCMSRB;
GpgME::Protocol mImportProto;
};
PadWidget::PadWidget(QWidget *parent):
QWidget(parent),
d(new Private(this))
{
}
void PadWidget::focusFirstChild(Qt::FocusReason reason)
{
d->mEdit->setFocus(reason);
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Dec 28, 10:13 PM (1 h, 57 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c5/ad/f891216a2aa83abe0ed5350909ef

Event Timeline