Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp
index 03c20a79b..150d48e27 100644
--- a/src/commands/keytocardcommand.cpp
+++ b/src/commands/keytocardcommand.cpp
@@ -1,782 +1,781 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2020,2022 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 "keytocardcommand.h"
#include "cardcommand_p.h"
#include "authenticatepivcardapplicationcommand.h"
#include "smartcard/algorithminfo.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "smartcard/utils.h"
#include <utils/applicationstate.h>
#include <utils/filedialog.h>
#include <Libkleo/Algorithm>
-#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <Libkleo/KeySelectionDialog>
#include <KLocalizedString>
#include <QDateTime>
#include <QDir>
#include <QInputDialog>
#include <QSaveFile>
#include <QStringList>
#include <gpg-error.h>
#if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
#define GPG_ERROR_HAS_NO_AUTH
#endif
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
namespace
{
QString cardDisplayName(const std::shared_ptr<const Card> &card)
{
return i18nc("smartcard application - serial number of smartcard", "%1 - %2", displayAppName(card->appName()), card->displaySerialNumber());
}
}
class KeyToCardCommand::Private : public CardCommand::Private
{
friend class ::Kleo::Commands::KeyToCardCommand;
KeyToCardCommand *q_func() const
{
return static_cast<KeyToCardCommand *>(q);
}
public:
explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey);
explicit Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName, QWidget *parent);
private:
enum Confirmation {
AskForConfirmation,
SkipConfirmation,
};
void start();
void startKeyToOpenPGPCard();
Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &card);
void startKeyToPIVCard();
void authenticate();
void authenticationFinished();
void authenticationCanceled();
void keyToCardDone(const GpgME::Error &err);
void keyToPIVCardDone(const GpgME::Error &err);
void updateDone();
void keyHasBeenCopiedToCard();
void backupHasBeenCreated(const QString &backupFilename);
QString backupKey();
std::vector<QByteArray> readSecretKeyFile();
bool writeSecretKeyBackup(const QString &filename, const std::vector<QByteArray> &keydata);
void startDeleteSecretKeyLocally(Confirmation confirmation);
void deleteSecretKeyLocallyFinished(const GpgME::Error &err);
private:
std::string appName;
GpgME::Subkey subkey;
std::string cardSlot;
bool overwriteExistingAlreadyApproved = false;
bool hasBeenCanceled = false;
QMetaObject::Connection updateConnection;
};
KeyToCardCommand::Private *KeyToCardCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const KeyToCardCommand::Private *KeyToCardCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define q q_func()
#define d d_func()
KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey_)
: CardCommand::Private(qq, "", nullptr)
, subkey(subkey_)
{
}
KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName_, QWidget *parent)
: CardCommand::Private(qq, serialNumber, parent)
, appName(appName_)
, cardSlot(slot)
{
}
namespace
{
static std::shared_ptr<Card> getCardToTransferSubkeyTo(const Subkey &subkey, QWidget *parent)
{
const std::vector<std::shared_ptr<Card>> suitableCards = KeyToCardCommand::getSuitableCards(subkey);
if (suitableCards.empty()) {
return std::shared_ptr<Card>();
} else if (suitableCards.size() == 1) {
return suitableCards[0];
}
QStringList options;
for (const auto &card : suitableCards) {
options.push_back(cardDisplayName(card));
}
bool ok;
const QString choice = QInputDialog::getItem(parent,
i18n("Select Card"),
i18n("Please select the card the key should be written to:"),
options,
/* current= */ 0,
/* editable= */ false,
&ok);
if (!ok) {
return std::shared_ptr<Card>();
}
const int index = options.indexOf(choice);
return suitableCards[index];
}
}
void KeyToCardCommand::Private::start()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()";
if (!subkey.isNull() && serialNumber().empty()) {
const auto card = getCardToTransferSubkeyTo(subkey, parentWidgetOrView());
if (!card) {
finished();
return;
}
setSerialNumber(card->serialNumber());
appName = card->appName();
}
const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (card->appName() == SmartCard::OpenPGPCard::AppName) {
startKeyToOpenPGPCard();
} else if (card->appName() == SmartCard::PIVCard::AppName) {
startKeyToPIVCard();
} else {
error(xi18nc("@info", "Sorry! Writing keys to the card <emphasis>%1</emphasis> is not supported.", cardDisplayName(card)));
finished();
return;
}
}
namespace
{
static std::string getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent)
{
// Check if we need to ask the user for the slot
if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) {
// Signing only
return OpenPGPCard::pgpSigKeyRef();
}
if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) {
// Encrypt only
return OpenPGPCard::pgpEncKeyRef();
}
if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) {
// Auth only
return OpenPGPCard::pgpAuthKeyRef();
}
// Multiple uses, ask user.
QStringList options;
std::vector<std::string> cardSlots;
if (subKey.canSign() || subKey.canCertify()) {
options.push_back(i18nc("@item:inlistbox", "Signature"));
cardSlots.push_back(OpenPGPCard::pgpSigKeyRef());
}
if (subKey.canEncrypt()) {
options.push_back(i18nc("@item:inlistbox", "Encryption"));
cardSlots.push_back(OpenPGPCard::pgpEncKeyRef());
}
if (subKey.canAuthenticate()) {
options.push_back(i18nc("@item:inlistbox", "Authentication"));
cardSlots.push_back(OpenPGPCard::pgpAuthKeyRef());
}
bool ok;
const QString choice = QInputDialog::getItem(parent,
i18n("Select Card Slot"),
i18n("Please select the card slot the key should be written to:"),
options,
/* current= */ 0,
/* editable= */ false,
&ok);
const int choiceIndex = options.indexOf(choice);
if (ok && choiceIndex >= 0) {
return cardSlots[choiceIndex];
} else {
return {};
}
}
}
void KeyToCardCommand::Private::startKeyToOpenPGPCard()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToOpenPGPCard()";
const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard<OpenPGPCard>(serialNumber());
if (!pgpCard) {
error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (subkey.isNull()) {
finished();
return;
}
if (subkey.parent().protocol() != GpgME::OpenPGP) {
error(i18n("Sorry! This key cannot be transferred to an OpenPGP card."));
finished();
return;
}
cardSlot = getOpenPGPCardSlotForKey(subkey, parentWidgetOrView());
if (cardSlot.empty()) {
finished();
return;
}
// Check if we need to do the overwrite warning.
const std::string existingKey = pgpCard->keyFingerprint(cardSlot);
if (!existingKey.empty()) {
const auto encKeyWarning = (cardSlot == OpenPGPCard::pgpEncKeyRef())
? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.")
: QString{};
const QString message = i18nc("@info",
"<p>The card <em>%1</em> already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
"<p>If there is no backup the existing key will be irrecoverably lost.</p>",
cardDisplayName(pgpCard))
+ i18n("The existing key has the fingerprint:") + QStringLiteral("<pre>%1</pre>").arg(Formatting::prettyID(existingKey.c_str())) + encKeyWarning;
const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(),
message,
i18nc("@title:window", "Overwrite existing key"),
KGuiItem{i18nc("@action:button", "Overwrite Existing Key")},
KStandardGuiItem::cancel(),
QString(),
KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
finished();
return;
}
}
// Now do the deed
const auto time = QDateTime::fromSecsSinceEpoch(quint32(subkey.creationTime()), QTimeZone::utc());
const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss"));
const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3 %4")
.arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()), QString::fromStdString(cardSlot), timestamp);
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
keyToCardDone(err);
});
}
namespace
{
static std::vector<Key> getSigningCertificates()
{
std::vector<Key> signingCertificates = KeyCache::instance()->secretKeys();
const auto it = std::remove_if(signingCertificates.begin(), signingCertificates.end(), [](const Key &key) {
return !(key.protocol() == GpgME::CMS && !key.subkey(0).isNull() && key.subkey(0).canSign() && !key.subkey(0).canEncrypt() && key.subkey(0).isSecret()
&& !key.subkey(0).isCardKey());
});
signingCertificates.erase(it, signingCertificates.end());
return signingCertificates;
}
static std::vector<Key> getEncryptionCertificates()
{
std::vector<Key> encryptionCertificates = KeyCache::instance()->secretKeys();
const auto it = std::remove_if(encryptionCertificates.begin(), encryptionCertificates.end(), [](const Key &key) {
return !(key.protocol() == GpgME::CMS && !key.subkey(0).isNull() && key.subkey(0).canEncrypt() && key.subkey(0).isSecret()
&& !key.subkey(0).isCardKey());
});
encryptionCertificates.erase(it, encryptionCertificates.end());
return encryptionCertificates;
}
}
Subkey KeyToCardCommand::Private::getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> & /*card*/)
{
if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) {
return Subkey();
}
const std::vector<Key> certificates = cardSlot == PIVCard::cardAuthenticationKeyRef() ? getSigningCertificates() : getEncryptionCertificates();
if (certificates.empty()) {
error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
return Subkey();
}
auto dialog = new KeySelectionDialog(parentWidgetOrView());
dialog->setWindowTitle(i18nc("@title:window", "Select Certificate"));
dialog->setText(i18n("Please select the certificate whose key pair you want to write to the card:"));
dialog->setKeys(certificates);
if (dialog->exec() == QDialog::Rejected) {
return Subkey();
}
return dialog->selectedKey().subkey(0);
}
void KeyToCardCommand::Private::startKeyToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) {
// key to card is only supported for the Card Authentication key and the Key Management key
finished();
return;
}
if (subkey.isNull()) {
subkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard);
}
if (subkey.isNull()) {
finished();
return;
}
if (subkey.parent().protocol() != GpgME::CMS) {
error(i18n("Sorry! This key cannot be transferred to a PIV card."));
finished();
return;
}
if (!subkey.canEncrypt() && !subkey.canSign()) {
error(i18n("Sorry! Only encryption keys and signing keys can be transferred to a PIV card."));
finished();
return;
}
// Check if we need to do the overwrite warning.
if (!overwriteExistingAlreadyApproved) {
const std::string existingKey = pivCard->keyInfo(cardSlot).grip;
if (!existingKey.empty() && (existingKey != subkey.keyGrip())) {
const QString decryptionWarning = (cardSlot == PIVCard::keyManagementKeyRef())
? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.")
: QString();
const QString message = i18nc("@info",
"<p>The card <em>%1</em> already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
"<p>If there is no backup the existing key will be irrecoverably lost.</p>",
cardDisplayName(pivCard))
+ i18n("The existing key has the key grip:") + QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(existingKey)) + decryptionWarning;
const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(),
message,
i18nc("@title:window", "Overwrite existing key"),
KGuiItem{i18nc("@action:button", "Overwrite Existing Key")},
KStandardGuiItem::cancel(),
QString(),
KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
finished();
return;
}
overwriteExistingAlreadyApproved = true;
}
}
const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3")
.arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()))
.arg(QString::fromStdString(cardSlot));
ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) {
keyToPIVCardDone(err);
});
}
void KeyToCardCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()";
auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
cmd->setAutoResetCardToOpenPGP(false);
connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() {
authenticationFinished();
});
connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() {
authenticationCanceled();
});
cmd->start();
}
void KeyToCardCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()";
if (!hasBeenCanceled) {
startKeyToPIVCard();
}
}
void KeyToCardCommand::Private::authenticationCanceled()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()";
hasBeenCanceled = true;
canceled();
}
void KeyToCardCommand::Private::updateDone()
{
disconnect(updateConnection);
const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
const std::string keyGripOnCard = card->keyInfo(cardSlot).grip;
if (keyGripOnCard != subkey.keyGrip()) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "KEYTOCARD succeeded, but key on card doesn't match copied key";
error(i18nc("@info", "Copying the key to the card failed."));
finished();
return;
}
keyHasBeenCopiedToCard();
}
void KeyToCardCommand::Private::keyHasBeenCopiedToCard()
{
const auto answer = KMessageBox::questionTwoActionsCancel(parentWidgetOrView(),
xi18nc("@info",
"<para>The key has been copied to the card.</para>"
"<para>You can now delete the copy of the key stored on this computer. "
"Optionally, you can first create a backup of the key.</para>"),
i18nc("@title:window", "Success"),
KGuiItem{i18nc("@action:button", "Create backup")},
KGuiItem{i18nc("@action:button", "Delete copy on disk")},
KGuiItem{i18nc("@action:button", "Keep copy on disk")});
if (answer == KMessageBox::ButtonCode::PrimaryAction) {
const QString backupFilename = backupKey();
if (backupFilename.isEmpty()) {
// user canceled the backup or there was an error; repeat above question
QMetaObject::invokeMethod(q, [this]() {
keyHasBeenCopiedToCard();
});
}
backupHasBeenCreated(backupFilename);
} else if (answer == KMessageBox::ButtonCode::SecondaryAction) {
startDeleteSecretKeyLocally(AskForConfirmation);
} else {
finished();
}
}
void KeyToCardCommand::Private::backupHasBeenCreated(const QString &backupFilename)
{
const auto answer =
KMessageBox::questionTwoActions(parentWidgetOrView(),
xi18nc("@info",
"<para>The key has been copied to the card and a backup has been written to <filename>%1</filename>.</para>"
"<para>Do you want to delete the copy of the key stored on this computer?</para>",
backupFilename),
i18nc("@title:window", "Success"),
KGuiItem{i18nc("@action:button", "Delete copy on disk")},
KGuiItem{i18nc("@action:button", "Keep copy on disk")});
if (answer == KMessageBox::ButtonCode::PrimaryAction) {
// the user has created a backup; don't ask again for confirmation before deleting the copy on disk
startDeleteSecretKeyLocally(SkipConfirmation);
} else {
finished();
}
}
namespace
{
QString gnupgPrivateKeyBackupExtension()
{
return QStringLiteral(".gpgsk");
}
QString proposeFilename(const Subkey &subkey)
{
QString filename;
const auto key = subkey.parent();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto keyID = Formatting::prettyKeyID(key.keyID());
const auto subkeyID = Formatting::prettyKeyID(subkey.keyID());
const auto usage = Formatting::usageString(subkey).replace(QLatin1StringView{", "}, QLatin1StringView{"_"});
/* Not translated so it's better to use in tutorials etc. */
filename = ((keyID == subkeyID) //
? QStringView{u"%1_%2_SECRET_KEY_BACKUP_%3"}.arg(name, keyID, usage)
: QStringView{u"%1_%2_SECRET_KEY_BACKUP_%3_%4"}.arg(name, keyID, subkeyID, usage));
filename.replace(u'/', u'_');
return QDir{ApplicationState::lastUsedExportDirectory()}.filePath(filename + gnupgPrivateKeyBackupExtension());
}
QString requestPrivateKeyBackupFilename(const QString &proposedFilename, QWidget *parent)
{
auto filename = FileDialog::getSaveFileNameEx(parent,
i18nc("@title:window", "Backup Secret Key"),
QStringLiteral("imp"),
proposedFilename,
i18nc("description of filename filter", "Secret Key Backup Files") + QLatin1StringView{" (*.gpgsk)"});
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += gnupgPrivateKeyBackupExtension();
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
}
QString KeyToCardCommand::Private::backupKey()
{
static const QByteArray backupInfoName = "Backup-info:";
auto keydata = readSecretKeyFile();
if (keydata.empty()) {
return {};
}
const auto filename = requestPrivateKeyBackupFilename(proposeFilename(subkey), parentWidgetOrView());
if (filename.isEmpty()) {
return {};
}
// remove old backup info
Kleo::erase_if(keydata, [](const auto &line) {
return line.startsWith(backupInfoName);
});
// prepend new backup info
const QByteArrayList backupInfo = {
backupInfoName,
subkey.keyGrip(),
QDateTime::currentDateTimeUtc().toString(Qt::ISODate).toUtf8(),
"Kleopatra",
Formatting::prettyNameAndEMail(subkey.parent()).toUtf8(),
};
keydata.insert(keydata.begin(), backupInfo.join(' ') + '\n');
if (writeSecretKeyBackup(filename, keydata)) {
return filename;
} else {
return {};
}
}
std::vector<QByteArray> KeyToCardCommand::Private::readSecretKeyFile()
{
const auto filename = QString::fromLatin1(subkey.keyGrip()) + QLatin1StringView{".key"};
const auto path = QDir{Kleo::gnupgPrivateKeysDirectory()}.filePath(filename);
QFile file{path};
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
error(xi18nc("@info", "Cannot open the private key file <filename>%1</filename> for reading.", path));
return {};
}
std::vector<QByteArray> lines;
while (!file.atEnd()) {
lines.push_back(file.readLine());
}
if (lines.empty()) {
error(xi18nc("@info", "The private key file <filename>%1</filename> is empty.", path));
}
return lines;
}
bool KeyToCardCommand::Private::writeSecretKeyBackup(const QString &filename, const std::vector<QByteArray> &keydata)
{
QSaveFile file{filename};
// open the file in binary format because we want to write Unix line endings
if (!file.open(QIODevice::WriteOnly)) {
error(xi18nc("@info", "Cannot open the file <filename>%1</filename> for writing.", filename));
return false;
}
for (const auto &line : keydata) {
file.write(line);
}
if (!file.commit()) {
error(xi18nc("@info", "Writing the backup of the secret key to <filename>%1</filename> failed.", filename));
return false;
};
return true;
}
void KeyToCardCommand::Private::startDeleteSecretKeyLocally(Confirmation confirmation)
{
const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName);
if (!card) {
error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber())));
finished();
return;
}
if (confirmation == AskForConfirmation) {
const auto answer = KMessageBox::questionTwoActions(parentWidgetOrView(),
xi18nc("@info", "Do you really want to delete the copy of the key stored on this computer?"),
i18nc("@title:window", "Confirm Deletion"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
if (answer != KMessageBox::ButtonCode::PrimaryAction) {
finished();
return;
}
}
const auto cmd = QByteArray{"DELETE_KEY --force "} + subkey.keyGrip();
ReaderStatus::mutableInstance()->startSimpleTransaction(card, cmd, q, [this](const GpgME::Error &err) {
deleteSecretKeyLocallyFinished(err);
});
}
void KeyToCardCommand::Private::deleteSecretKeyLocallyFinished(const GpgME::Error &err)
{
ReaderStatus::mutableInstance()->updateStatus();
if (err) {
error(xi18nc("@info",
"<para>Failed to delete the copy of the key stored on this computer:</para><para><message>%1</message></para>",
Formatting::errorAsString(err)));
} else if (!err.isCanceled()) {
success(i18nc("@info", "Successfully deleted the copy of the key stored on this computer."));
}
finished();
}
KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &subkey)
: CardCommand(new Private(this, subkey))
{
}
KeyToCardCommand::KeyToCardCommand(const std::string &cardSlot, const std::string &serialNumber, const std::string &appName, QWidget *parent)
: CardCommand(new Private(this, cardSlot, serialNumber, appName, parent))
{
}
KeyToCardCommand::~KeyToCardCommand()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()";
}
namespace
{
bool cardSupportsKeyAlgorithm(const std::shared_ptr<const Card> &card, const std::string &keyAlgo)
{
if (card->appName() == OpenPGPCard::AppName) {
const auto pgpCard = static_cast<const OpenPGPCard *>(card.get());
const auto cardAlgos = pgpCard->supportedAlgorithms();
return std::ranges::any_of(cardAlgos, [keyAlgo](const auto &algo) {
return (keyAlgo == algo.id) //
|| (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpEncKeyRef()))
|| (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpSigKeyRef()));
});
}
return false;
}
}
// static
std::vector<std::shared_ptr<Card>> KeyToCardCommand::getSuitableCards(const GpgME::Subkey &subkey)
{
std::vector<std::shared_ptr<Card>> suitableCards;
if (subkey.isNull() || subkey.parent().protocol() != GpgME::OpenPGP) {
return suitableCards;
}
const auto keyAlgo = subkey.algoName();
Kleo::copy_if(ReaderStatus::instance()->getCards(), std::back_inserter(suitableCards), [keyAlgo](const auto &card) {
return cardSupportsKeyAlgorithm(card, keyAlgo);
});
return suitableCards;
}
void KeyToCardCommand::Private::keyToCardDone(const GpgME::Error &err)
{
if (!err && !err.isCanceled()) {
updateConnection = connect(ReaderStatus::instance(), &ReaderStatus::updateFinished, q, [this]() {
updateDone();
});
ReaderStatus::mutableInstance()->updateCard(serialNumber(), appName);
return;
}
if (err) {
error(xi18nc("@info", "<para>Copying the key to the card failed:</para><para><message>%1</message></para>", Formatting::errorAsString(err)));
}
finished();
}
void KeyToCardCommand::Private::keyToPIVCardDone(const GpgME::Error &err)
{
qCDebug(KLEOPATRA_LOG) << q << __func__ << Formatting::errorAsString(err) << "(" << err.code() << ")";
#ifdef GPG_ERROR_HAS_NO_AUTH
// gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
authenticate();
return;
}
#endif
keyToCardDone(err);
}
void KeyToCardCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()";
d->start();
}
void KeyToCardCommand::doCancel()
{
}
#undef q_func
#undef d_func
#include "moc_keytocardcommand.cpp"
diff --git a/src/crypto/decryptverifytask.cpp b/src/crypto/decryptverifytask.cpp
index b27bd9d9f..467fde706 100644
--- a/src/crypto/decryptverifytask.cpp
+++ b/src/crypto/decryptverifytask.cpp
@@ -1,1575 +1,1574 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "decryptverifytask.h"
#include <QGpgME/DecryptJob>
#include <QGpgME/DecryptVerifyArchiveJob>
#include <QGpgME/DecryptVerifyJob>
#include <QGpgME/Protocol>
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/VerifyOpaqueJob>
#include <Libkleo/AuditLogEntry>
#include <Libkleo/Classify>
#include <Libkleo/Compliance>
-#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KleoException>
#include <Libkleo/Predicates>
#include <Libkleo/Stl_Util>
#include <Libkleo/GnuPG>
#include <utils/detail_p.h>
#include <utils/input.h>
#include <utils/kleo_assert.h>
#include <utils/output.h>
#include <KEmailAddress>
#include <KMime/Types>
#include <gpgme++/context.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <gpgme++/verificationresult.h>
#include <gpg-error.h>
#include "kleopatra_debug.h"
#include <KFileUtils>
#include <KLocalizedString>
#include <QByteArray>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QIODevice>
#include <QLocale>
#include <QMimeDatabase>
#include <QStringList>
#include <algorithm>
#include <sstream>
using namespace Kleo::Crypto;
using namespace Kleo;
using namespace GpgME;
using namespace KMime::Types;
using namespace Qt::Literals::StringLiterals;
using namespace Qt::Literals::StringLiterals;
namespace
{
static AuditLogEntry auditLogFromSender(QObject *sender)
{
return AuditLogEntry::fromJob(qobject_cast<const QGpgME::Job *>(sender));
}
static std::vector<QString> extractEmails(const Key &key)
{
std::vector<QString> res;
const auto userIDs{key.userIDs()};
for (const UserID &id : userIDs) {
const auto email = Kleo::Formatting::email(id);
if (!email.isEmpty()) {
res.push_back(email);
}
}
return res;
}
static bool keyContainsEmail(const Key &key, const QString &email)
{
return std::ranges::any_of(extractEmails(key), [email](const QString &emailItem) {
return emailItem.compare(email, Qt::CaseInsensitive) == 0;
});
}
static void updateKeys(const VerificationResult &result)
{
// This little hack works around the problem that GnuPG / GpgME does not
// provide Key information in a verification result. The Key object is
// a dummy just holding the KeyID. This hack ensures that all available
// keys are fetched from the backend and are populated
for (const auto &sig : result.signatures()) {
// Update key information
sig.key(true, true);
}
}
static QString ensureUniqueDirectory(const QString &path)
{
// make sure that we don't use an existing directory
QString uniquePath = path;
const QFileInfo outputInfo{path};
if (outputInfo.exists()) {
const auto uniqueName = KFileUtils::suggestName(QUrl::fromLocalFile(outputInfo.absolutePath()), outputInfo.fileName());
uniquePath = outputInfo.dir().filePath(uniqueName);
}
if (!QDir{}.mkpath(uniquePath)) {
return {};
}
return uniquePath;
}
static bool mimeTypeInherits(const QMimeType &mimeType, const QString &mimeTypeName)
{
// inherits is expensive on an invalid mimeType
return mimeType.isValid() && mimeType.inherits(mimeTypeName);
}
}
namespace
{
static QString formatRecipientsDetails(const std::vector<Key> &knownRecipients, unsigned int numRecipients)
{
if (numRecipients == 0) {
return {};
}
QString details = i18np("Recipient:", "Recipients:", numRecipients);
if (numRecipients == 1) {
if (knownRecipients.empty()) {
details += QLatin1Char(' ') + i18nc("@info", "One unknown recipient");
} else {
details += QLatin1Char(' ') + Formatting::summaryLine(knownRecipients.front()).toHtmlEscaped();
}
} else {
details += QLatin1StringView("<ul>");
for (const Key &key : knownRecipients) {
details += QLatin1StringView("<li>") + Formatting::summaryLine(key).toHtmlEscaped() + QLatin1StringView("</li>");
}
if (knownRecipients.size() < numRecipients) {
details += QLatin1StringView("<li>") + i18np("One unknown recipient", "%1 unknown recipients", numRecipients - knownRecipients.size())
+ QLatin1StringView("</li>");
}
details += QLatin1StringView("</ul>");
}
return details;
}
} // anon namespace
class DecryptVerifyResult::Private
{
DecryptVerifyResult *const q;
public:
Private(const VerificationResult &vr,
const DecryptionResult &dr,
const QByteArray &stuff,
const QString &fileName,
const GpgME::Error &error,
const QString &errString,
const QString &input,
const QString &output,
const AuditLogEntry &auditLog,
Task *parentTask,
const Mailbox &informativeSender,
Task::DataSource dataSource,
DecryptVerifyResult *qq)
: q(qq)
, m_verificationResult(vr)
, m_decryptionResult(dr)
, m_stuff(stuff)
, m_fileName(fileName)
, m_error(error)
, m_errorString(errString)
, m_inputLabel(input)
, m_outputLabel(output)
, m_auditLog(auditLog)
, m_parentTask(QPointer<Task>(parentTask))
, m_informativeSender(informativeSender)
{
q->setDataSource(dataSource);
}
QString label() const
{
const auto verify = m_verificationResult.numSignatures() > 0;
const auto decrypt = !m_decryptionResult.isNull();
const auto recipients = KeyCache::instance()->findRecipients(m_decryptionResult);
Q_ASSERT(verify || decrypt);
Error error;
if (m_error.code()) {
error = m_error;
} else if (m_decryptionResult.error().code()) {
error = m_decryptionResult.error();
} else {
error = m_verificationResult.error();
}
QString label;
if (verify && decrypt) {
if (error.isCanceled()) {
return i18nc("@info", "Decryption and verification of <filename>%1</filename> canceled.", m_inputLabel);
} else if (error.code() == GPG_ERR_NO_SECKEY) {
label = xi18nc("@info 'Unable' meaning that decryption can't be started because there is no secret key",
"Unable to decrypt and verify <filename>%1</filename>:",
m_inputLabel);
} else if (error.code() != GPG_ERR_NO_ERROR) {
label = xi18nc("@info 'Failed' meaning that there was an error during decryption",
"Failed to decrypt and verify <filename>%1</filename>:",
m_inputLabel);
} else {
label = xi18nc("@info Successfully decrypted and verified <file> as <file>.",
"Successfully decrypted and verified <filename>%1</filename> as <filename>%2</filename>.",
m_inputLabel,
m_outputLabel);
}
} else if (verify) {
if (error.isCanceled()) {
return i18nc("@info", "Verification of <filename>%1</filename> canceled.", m_inputLabel);
} else if (error) {
label = xi18nc("@info Failed to verify <file>:", "Failed to verify <filename>%1</filename>:", m_inputLabel);
} else {
label = xi18nc("@info Verified <file> with signature in <file>.",
"Verified <filename>%1</filename> with signature in <filename>%2</filename>.",
m_outputLabel,
m_inputLabel);
}
} else {
if (error.isCanceled()) {
return i18nc("@info", "Decryption of <filename>%1</filename> canceled.", m_inputLabel);
} else if (error.code() == GPG_ERR_NO_SECKEY) {
label = xi18nc("@info 'Unable' meaning that decryption can't be started because there is no secret key",
"Unable to decrypt <filename>%1</filename>:",
m_inputLabel);
} else if (error.code() != GPG_ERR_NO_ERROR) {
label = xi18nc("@info 'Failed' meaning that there was an error during decryption", "Failed to decrypt <filename>%1</filename>:", m_inputLabel);
} else {
label = xi18nc("@info Successfully decrypted <file> as <file>.",
"Successfully decrypted <filename>%1</filename> as <filename>%2</filename>.",
m_inputLabel,
m_outputLabel);
}
}
if (error) {
label += u' ' + Formatting::errorAsString(error) + u'.';
if (error.code() == GPG_ERR_NO_SECKEY) {
label += "<br />"_L1
+ i18nc("@info",
"The data was not encrypted for any "
"secret key in your certificate list.");
}
} else if (m_decryptionResult.isLegacyCipherNoMDC()) {
label += u' ' + i18n("No integrity protection (MDC).");
} else if (!m_errorString.isEmpty()) {
if (error.code()) {
label += u' ' + Formatting::errorAsString(error) + ": "_L1;
}
label += m_errorString.toHtmlEscaped();
}
if (error) {
return label;
}
if (DeVSCompliance::isCompliant()) {
label += "<br />"_L1
+ ((m_decryptionResult.isDeVs()
? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The decryption is %1.",
DeVSCompliance::name(true))
: i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The decryption <b>is not</b> %1.",
DeVSCompliance::name(true))));
}
if (m_decryptionResult.fileName()) {
const auto decVerifyTask = qobject_cast<AbstractDecryptVerifyTask *>(m_parentTask.data());
if (decVerifyTask) {
const auto embedFileName = QString::fromUtf8(m_decryptionResult.fileName()).toHtmlEscaped();
if (embedFileName != decVerifyTask->outputLabel()) {
label += "<br />"_L1 + i18n("Embedded file name: '%1'", embedFileName);
}
}
}
if (!verify) {
label += "<br/>"_L1 + i18n("<b>Note:</b> You cannot be sure who encrypted this message as it is not signed.");
}
if (m_decryptionResult.isLegacyCipherNoMDC()) {
label += "<br />"_L1
+ i18nc("Integrity protection was missing because an old cipher was used.",
"<b>Hint:</b> If this file was encrypted before the year 2003 it is "
"likely that the file is legitimate. This is because back "
"then integrity protection was not widely used.")
+ QStringLiteral("<br/><br/>")
+ i18nc("The user is offered to force decrypt a non integrity protected message. With the strong advice to re-encrypt it.",
"If you are confident that the file was not manipulated you should re-encrypt it after you have forced the decryption.")
+ QStringLiteral("<br/><br/>");
}
if (decrypt) {
label += "<br/>"_L1 + formatRecipientsDetails(recipients, m_decryptionResult.numRecipients());
}
return label;
}
VerificationResult m_verificationResult;
DecryptionResult m_decryptionResult;
QByteArray m_stuff;
QString m_fileName;
GpgME::Error m_error;
QString m_errorString;
QString m_inputLabel;
QString m_outputLabel;
const AuditLogEntry m_auditLog;
QPointer<Task> m_parentTask;
const Mailbox m_informativeSender;
};
std::shared_ptr<DecryptVerifyResult>
AbstractDecryptVerifyTask::fromDecryptResult(const DecryptionResult &dr, const QByteArray &plaintext, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(VerificationResult(), //
dr,
plaintext,
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender(),
dataSource()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptResult(const GpgME::Error &err, const QString &what, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(VerificationResult(), //
DecryptionResult(err),
QByteArray(),
{},
err,
what,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender(),
dataSource()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptVerifyResult(const DecryptionResult &dr,
const VerificationResult &vr,
const QByteArray &plaintext,
const QString &fileName,
const AuditLogEntry &auditLog)
{
const auto err = dr.error().code() ? dr.error() : vr.error();
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(vr, //
dr,
plaintext,
fileName,
err,
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender(),
dataSource()));
}
std::shared_ptr<DecryptVerifyResult>
AbstractDecryptVerifyTask::fromDecryptVerifyResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(VerificationResult(), //
DecryptionResult(err),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender(),
dataSource()));
}
std::shared_ptr<DecryptVerifyResult>
AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const VerificationResult &vr, const QByteArray &plaintext, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(vr, //
DecryptionResult(),
plaintext,
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender(),
dataSource()));
}
std::shared_ptr<DecryptVerifyResult>
AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(VerificationResult(err), //
DecryptionResult(),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender(),
dataSource()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyDetachedResult(const VerificationResult &vr, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(vr, //
DecryptionResult(),
QByteArray(),
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender(),
dataSource()));
}
std::shared_ptr<DecryptVerifyResult>
AbstractDecryptVerifyTask::fromVerifyDetachedResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(VerificationResult(err), //
DecryptionResult(),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender(),
dataSource()));
}
DecryptVerifyResult::DecryptVerifyResult(const VerificationResult &vr,
const DecryptionResult &dr,
const QByteArray &stuff,
const QString &fileName,
const GpgME::Error &error,
const QString &errString,
const QString &inputLabel,
const QString &outputLabel,
const AuditLogEntry &auditLog,
Task *parentTask,
const Mailbox &informativeSender,
Task::DataSource dataSource)
: Task::Result()
, d(new Private(vr, dr, stuff, fileName, error, errString, inputLabel, outputLabel, auditLog, parentTask, informativeSender, dataSource, this))
{
}
Task::Result::ContentType DecryptVerifyResult::viewableContentType() const
{
if (decryptionResult().isMime()) {
return Task::Result::ContentType::Mime;
}
if (fileName().isEmpty()) {
return Task::Result::ContentType::None;
}
if (fileName().endsWith(QStringLiteral("openpgp-encrypted-message"))) {
return Task::Result::ContentType::Mime;
}
QMimeDatabase mimeDatabase;
const auto mimeType = mimeDatabase.mimeTypeForFile(fileName());
if (mimeTypeInherits(mimeType, QStringLiteral("message/rfc822"))) {
return Task::Result::ContentType::Mime;
}
if (mimeTypeInherits(mimeType, QStringLiteral("application/mbox"))) {
return Task::Result::ContentType::Mbox;
}
return Task::Result::ContentType::None;
}
QString DecryptVerifyResult::details() const
{
return {};
}
QString DecryptVerifyResult::overview() const
{
const auto decrypting = !d->m_decryptionResult.isNull();
const auto verifying = d->m_verificationResult.numSignatures() > 0;
if (dataSource() == Task::Notepad) {
if (decrypting && d->m_decryptionResult.error()) {
return i18nc("@info", "Failed to decrypt the notepad: %1", Formatting::errorAsString(d->m_decryptionResult.error()));
}
if (verifying && d->m_verificationResult.error()) {
return i18nc("@info", "Failed to verify the notepad: %1", Formatting::errorAsString(d->m_verificationResult.error()));
}
if (decrypting && verifying) {
return i18nc("@info", "Successfully decrypted and verified the notepad");
}
if (decrypting) {
return i18nc("@info", "Successfully decrypted the notepad");
}
if (verifying) {
return i18nc("@info", "Successfully verified the notepad");
}
return {};
} else if (dataSource() == Task::Clipboard) {
if (decrypting && d->m_decryptionResult.error()) {
return i18nc("@info", "Failed to decrypt the clipboard: %1", Formatting::errorAsString(d->m_decryptionResult.error()));
}
if (verifying && d->m_verificationResult.error()) {
return i18nc("@info", "Failed to verify the clipboard: %1", Formatting::errorAsString(d->m_verificationResult.error()));
}
if (decrypting && verifying) {
return i18nc("@info", "Successfully decrypted and verified the clipboard");
}
if (decrypting) {
return i18nc("@info", "Successfully decrypted the clipboard");
}
if (verifying) {
return i18nc("@info", "Successfully verified the clipboard");
}
return {};
}
return d->label();
}
static Task::Result::VisualCode codeForSignature(const Signature &signature)
{
if (signature.summary() & Signature::Red) {
return Task::Result::VisualCode::Danger;
}
if ((signature.summary() & Signature::Valid) || (signature.summary() & Signature::Green)) {
return Task::Result::AllGood;
}
return Task::Result::Warning;
}
QList<Task::Result::ResultListItem> DecryptVerifyResult::detailsList() const
{
QList<Task::Result::ResultListItem> details;
for (const Signature &sig : d->m_verificationResult.signatures()) {
const auto signerKey = KeyCache::instance()->findSigner(sig);
const auto informativeMailAddress = QString::fromUtf8(d->m_informativeSender.address());
auto text = Kleo::Formatting::prettySignature(sig, informativeMailAddress);
if (!informativeMailAddress.isEmpty() && !signerKey.isNull() && !keyContainsEmail(signerKey, informativeMailAddress)) {
QString emailsList;
for (const auto &email : extractEmails(signerKey)) {
emailsList += "<li>"_L1 + email + "</li>"_L1;
}
text += "<br />"_L1
+ i18nc("@info",
"Warning: The sender's mail address is not stored in the <a href=\"key:%1\">certificate</a> used for signing. Stored:<ul>%2</ul>",
QString::fromLatin1(sig.key().primaryFingerprint()),
emailsList);
}
details += Task::Result::ResultListItem{
.details = text,
.code = codeForSignature(sig),
};
}
return details;
}
GpgME::Error DecryptVerifyResult::error() const
{
return d->m_error;
}
QString DecryptVerifyResult::errorString() const
{
return d->m_errorString;
}
AuditLogEntry DecryptVerifyResult::auditLog() const
{
return d->m_auditLog;
}
QPointer<Task> DecryptVerifyResult::parentTask() const
{
return d->m_parentTask;
}
GpgME::VerificationResult DecryptVerifyResult::verificationResult() const
{
return d->m_verificationResult;
}
GpgME::DecryptionResult DecryptVerifyResult::decryptionResult() const
{
return d->m_decryptionResult;
}
QString DecryptVerifyResult::fileName() const
{
return d->m_fileName;
}
class AbstractDecryptVerifyTask::Private
{
public:
Mailbox informativeSender;
QPointer<QGpgME::Job> job;
Task::DataSource dataSource = Task::Files;
};
AbstractDecryptVerifyTask::AbstractDecryptVerifyTask(QObject *parent)
: Task(parent)
, d(new Private)
{
}
AbstractDecryptVerifyTask::~AbstractDecryptVerifyTask()
{
}
void AbstractDecryptVerifyTask::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->job) {
d->job->slotCancel();
}
}
Mailbox AbstractDecryptVerifyTask::informativeSender() const
{
return d->informativeSender;
}
void AbstractDecryptVerifyTask::setInformativeSender(const Mailbox &sender)
{
d->informativeSender = sender;
}
QGpgME::Job *AbstractDecryptVerifyTask::job() const
{
return d->job;
}
void AbstractDecryptVerifyTask::setJob(QGpgME::Job *job)
{
d->job = job;
}
class DecryptVerifyTask::Private
{
DecryptVerifyTask *const q;
public:
explicit Private(DecryptVerifyTask *qq)
: q{qq}
{
}
void startDecryptVerifyJob();
void startDecryptVerifyArchiveJob();
void slotResult(const DecryptionResult &, const VerificationResult &, const QByteArray & = {});
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
bool m_ignoreMDCError = false;
bool m_extractArchive = false;
QString m_inputFilePath;
QString m_outputFilePath;
QString m_outputDirectory;
};
void DecryptVerifyTask::Private::slotResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plainText)
{
updateKeys(vr);
{
std::stringstream ss;
ss << dr << '\n' << vr;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (m_output) {
if (dr.error().code() || vr.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!dr.isNull() || !vr.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(
q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
}
const int drErr = dr.error().code();
const QString errorString = m_output ? m_output->errorString() : QString{};
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || (m_output && m_output->failed())) {
q->emitResult(q->fromDecryptResult(drErr ? dr.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog));
return;
}
q->emitResult(q->fromDecryptVerifyResult(dr, vr, plainText, m_output ? m_output->fileName() : m_outputFilePath, auditLog));
}
DecryptVerifyTask::DecryptVerifyTask(QObject *parent)
: AbstractDecryptVerifyTask(parent)
, d(new Private(this))
{
}
DecryptVerifyTask::~DecryptVerifyTask()
{
}
void DecryptVerifyTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void DecryptVerifyTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void DecryptVerifyTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = prot == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void DecryptVerifyTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(
gpg_error(GPG_ERR_NOTHING_FOUND),
i18n("Could not determine whether this is an S/MIME or an OpenPGP signature/ciphertext - maybe it is neither ciphertext nor a signature?"),
Exception::MessageOnly);
}
setProtocol(p);
}
QString DecryptVerifyTask::label() const
{
return i18n("Decrypting %1...", inputLabel());
}
unsigned long long DecryptVerifyTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString DecryptVerifyTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QFileInfo{d->m_inputFilePath}.fileName();
}
QString DecryptVerifyTask::outputLabel() const
{
if (d->m_output) {
return d->m_output->label();
} else if (!d->m_outputFilePath.isEmpty()) {
return QFileInfo{d->m_outputFilePath}.fileName();
} else {
return d->m_outputDirectory;
}
}
Protocol DecryptVerifyTask::protocol() const
{
return d->m_protocol;
}
static void ensureIOOpen(QIODevice *input, QIODevice *output)
{
if (input && !input->isOpen()) {
input->open(QIODevice::ReadOnly);
}
if (output && !output->isOpen()) {
output->open(QIODevice::WriteOnly);
}
}
void DecryptVerifyTask::setIgnoreMDCError(bool value)
{
d->m_ignoreMDCError = value;
}
void DecryptVerifyTask::setExtractArchive(bool extract)
{
d->m_extractArchive = extract;
}
void DecryptVerifyTask::setInputFile(const QString &path)
{
d->m_inputFilePath = path;
}
void DecryptVerifyTask::setOutputFile(const QString &path)
{
d->m_outputFilePath = path;
}
void DecryptVerifyTask::setOutputDirectory(const QString &directory)
{
d->m_outputDirectory = directory;
}
static bool archiveJobsCanBeUsed(GpgME::Protocol protocol)
{
return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported();
}
void DecryptVerifyTask::doStart()
{
kleo_assert(d->m_backend);
if (d->m_extractArchive && archiveJobsCanBeUsed(d->m_protocol)) {
d->startDecryptVerifyArchiveJob();
} else {
d->startDecryptVerifyJob();
}
}
static void setIgnoreMDCErrorFlag(QGpgME::Job *job, bool ignoreMDCError)
{
if (ignoreMDCError) {
qCDebug(KLEOPATRA_LOG) << "Modifying job to ignore MDC errors.";
auto ctx = QGpgME::Job::context(job);
if (!ctx) {
qCWarning(KLEOPATRA_LOG) << "Failed to get context for job";
} else {
const auto err = ctx->setFlag("ignore-mdc-error", "1");
if (err) {
qCWarning(KLEOPATRA_LOG) << "Failed to set ignore mdc errors" << Formatting::errorAsString(err);
}
}
}
}
void DecryptVerifyTask::Private::startDecryptVerifyJob()
{
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (!m_outputFilePath.isEmpty() && QFile::exists(m_outputFilePath)) {
// The output files are always written to a temporary location. Therefore, this can only occur
// if two signed/encrypted files with the same name in different folders are verified/decrypted
// because they would be written to the same temporary location.
QMetaObject::invokeMethod(
q,
[this]() {
slotResult(DecryptionResult{Error::fromCode(GPG_ERR_EEXIST)}, VerificationResult{});
},
Qt::QueuedConnection);
return;
}
#endif
try {
std::unique_ptr<QGpgME::DecryptVerifyJob> job{m_backend->decryptVerifyJob()};
kleo_assert(job);
setIgnoreMDCErrorFlag(job.get(), m_ignoreMDCError);
QObject::connect(job.get(),
&QGpgME::DecryptVerifyJob::result,
q,
[this](const GpgME::DecryptionResult &decryptResult, const GpgME::VerificationResult &verifyResult, const QByteArray &plainText) {
slotResult(decryptResult, verifyResult, plainText);
});
connect(job.get(), &QGpgME::Job::jobProgress, q, &DecryptVerifyTask::setProgress);
#if QGPGME_SUPPORTS_PROCESS_ALL_SIGNATURES
job->setProcessAllSignatures(true);
#endif
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (!m_inputFilePath.isEmpty() && !m_outputFilePath.isEmpty()) {
job->setInputFile(m_inputFilePath);
job->setOutputFile(m_outputFilePath);
const auto err = job->startIt();
} else {
ensureIOOpen(m_input->ioDevice().get(), m_output->ioDevice().get());
job->start(m_input->ioDevice(), m_output->ioDevice());
}
#else
ensureIOOpen(m_input->ioDevice().get(), m_output->ioDevice().get());
job->start(m_input->ioDevice(), m_output->ioDevice());
#endif
q->setJob(job.release());
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptVerifyResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
q->emitResult(
q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
void DecryptVerifyTask::Private::startDecryptVerifyArchiveJob()
{
std::unique_ptr<QGpgME::DecryptVerifyArchiveJob> job{m_backend->decryptVerifyArchiveJob()};
kleo_assert(job);
setIgnoreMDCErrorFlag(job.get(), m_ignoreMDCError);
connect(job.get(),
&QGpgME::DecryptVerifyArchiveJob::result,
q,
[this](const GpgME::DecryptionResult &decryptResult, const GpgME::VerificationResult &verifyResult) {
slotResult(decryptResult, verifyResult);
});
connect(job.get(), &QGpgME::Job::jobProgress, q, &DecryptVerifyTask::setProgress);
// make sure that we don't use an existing output directory
const auto outputDirectory = ensureUniqueDirectory(m_outputDirectory);
if (outputDirectory.isEmpty()) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_GENERAL), {}, {}));
return;
}
m_outputDirectory = outputDirectory;
m_outputFilePath = outputDirectory;
#if QGPGME_SUPPORTS_PROCESS_ALL_SIGNATURES
job->setProcessAllSignatures(true);
#endif
job->setInputFile(m_inputFilePath);
job->setOutputDirectory(m_outputDirectory);
const auto err = job->startIt();
q->setJob(job.release());
if (err) {
q->emitResult(q->fromDecryptVerifyResult(err, {}, {}));
}
}
class DecryptTask::Private
{
DecryptTask *const q;
public:
explicit Private(DecryptTask *qq)
: q{qq}
{
}
void slotResult(const DecryptionResult &, const QByteArray &);
void registerJob(QGpgME::DecryptJob *job)
{
q->connect(job, SIGNAL(result(GpgME::DecryptionResult, QByteArray)), q, SLOT(slotResult(GpgME::DecryptionResult, QByteArray)));
q->connect(job, &QGpgME::Job::jobProgress, q, &DecryptTask::setProgress);
}
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
};
void DecryptTask::Private::slotResult(const DecryptionResult &result, const QByteArray &plainText)
{
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (result.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!result.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
const int drErr = result.error().code();
const QString errorString = m_output->errorString();
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || m_output->failed()) {
q->emitResult(q->fromDecryptResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog));
return;
}
q->emitResult(q->fromDecryptResult(result, plainText, auditLog));
}
DecryptTask::DecryptTask(QObject *parent)
: AbstractDecryptVerifyTask(parent)
, d(new Private(this))
{
}
DecryptTask::~DecryptTask()
{
}
void DecryptTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void DecryptTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void DecryptTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void DecryptTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND),
i18n("Could not determine whether this was S/MIME- or OpenPGP-encrypted - maybe it is not ciphertext at all?"),
Exception::MessageOnly);
}
setProtocol(p);
}
QString DecryptTask::label() const
{
return i18n("Decrypting: %1...", d->m_input->label());
}
unsigned long long DecryptTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString DecryptTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QString();
}
QString DecryptTask::outputLabel() const
{
return d->m_output ? d->m_output->label() : QString();
}
Protocol DecryptTask::protocol() const
{
return d->m_protocol;
}
void DecryptTask::doStart()
{
kleo_assert(d->m_backend);
try {
std::unique_ptr<QGpgME::DecryptJob> job{d->m_backend->decryptJob()};
kleo_assert(job);
d->registerJob(job.get());
ensureIOOpen(d->m_input->ioDevice().get(), d->m_output->ioDevice().get());
job->start(d->m_input->ioDevice(), d->m_output->ioDevice());
setJob(job.release());
} catch (const GpgME::Exception &e) {
emitResult(fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
class VerifyOpaqueTask::Private
{
VerifyOpaqueTask *const q;
public:
explicit Private(VerifyOpaqueTask *qq)
: q{qq}
{
}
void startVerifyOpaqueJob();
void startDecryptVerifyArchiveJob();
void slotResult(const VerificationResult &, const QByteArray & = {});
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
bool m_extractArchive = false;
QString m_inputFilePath;
QString m_outputFilePath;
QString m_outputDirectory;
};
void VerifyOpaqueTask::Private::slotResult(const VerificationResult &result, const QByteArray &plainText)
{
updateKeys(result);
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (m_output) {
if (result.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!result.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(
q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
}
const int drErr = result.error().code();
const QString errorString = m_output ? m_output->errorString() : QString{};
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || (m_output && m_output->failed())) {
q->emitResult(q->fromVerifyOpaqueResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog));
return;
}
q->emitResult(q->fromVerifyOpaqueResult(result, plainText, auditLog));
}
VerifyOpaqueTask::VerifyOpaqueTask(QObject *parent)
: AbstractDecryptVerifyTask(parent)
, d(new Private(this))
{
}
VerifyOpaqueTask::~VerifyOpaqueTask()
{
}
void VerifyOpaqueTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void VerifyOpaqueTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void VerifyOpaqueTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void VerifyOpaqueTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND),
i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"),
Exception::MessageOnly);
}
setProtocol(p);
}
QString VerifyOpaqueTask::label() const
{
return i18n("Verifying %1...", inputLabel());
}
unsigned long long VerifyOpaqueTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString VerifyOpaqueTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QFileInfo{d->m_inputFilePath}.fileName();
}
QString VerifyOpaqueTask::outputLabel() const
{
if (d->m_output) {
return d->m_output->label();
} else if (!d->m_outputFilePath.isEmpty()) {
return QFileInfo{d->m_outputFilePath}.fileName();
} else {
return d->m_outputDirectory;
}
}
Protocol VerifyOpaqueTask::protocol() const
{
return d->m_protocol;
}
void VerifyOpaqueTask::setExtractArchive(bool extract)
{
d->m_extractArchive = extract;
}
void VerifyOpaqueTask::setInputFile(const QString &path)
{
d->m_inputFilePath = path;
}
void VerifyOpaqueTask::setOutputFile(const QString &path)
{
d->m_outputFilePath = path;
}
void VerifyOpaqueTask::setOutputDirectory(const QString &directory)
{
d->m_outputDirectory = directory;
}
void VerifyOpaqueTask::doStart()
{
kleo_assert(d->m_backend);
if (d->m_extractArchive && archiveJobsCanBeUsed(d->m_protocol)) {
d->startDecryptVerifyArchiveJob();
} else {
d->startVerifyOpaqueJob();
}
}
void VerifyOpaqueTask::Private::startVerifyOpaqueJob()
{
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (!m_outputFilePath.isEmpty() && QFile::exists(m_outputFilePath)) {
// The output files are always written to a temporary location. Therefore, this can only occur
// if two signed/encrypted files with the same name in different folders are verified/decrypted
// because they would be written to the same temporary location.
QMetaObject::invokeMethod(
q,
[this]() {
slotResult(VerificationResult{Error::fromCode(GPG_ERR_EEXIST)});
},
Qt::QueuedConnection);
return;
}
#endif
try {
std::unique_ptr<QGpgME::VerifyOpaqueJob> job{m_backend->verifyOpaqueJob()};
kleo_assert(job);
connect(job.get(), &QGpgME::VerifyOpaqueJob::result, q, [this](const GpgME::VerificationResult &result, const QByteArray &plainText) {
slotResult(result, plainText);
});
connect(job.get(), &QGpgME::Job::jobProgress, q, &VerifyOpaqueTask::setProgress);
#if QGPGME_SUPPORTS_PROCESS_ALL_SIGNATURES
job->setProcessAllSignatures(true);
#endif
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (!m_inputFilePath.isEmpty() && !m_outputFilePath.isEmpty()) {
job->setInputFile(m_inputFilePath);
job->setOutputFile(m_outputFilePath);
const auto err = job->startIt();
} else {
ensureIOOpen(m_input->ioDevice().get(), m_output ? m_output->ioDevice().get() : nullptr);
job->start(m_input->ioDevice(), m_output ? m_output->ioDevice() : std::shared_ptr<QIODevice>());
}
#else
ensureIOOpen(m_input->ioDevice().get(), m_output ? m_output->ioDevice().get() : nullptr);
job->start(m_input->ioDevice(), m_output ? m_output->ioDevice() : std::shared_ptr<QIODevice>());
#endif
q->setJob(job.release());
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
q->emitResult(
q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
void VerifyOpaqueTask::Private::startDecryptVerifyArchiveJob()
{
std::unique_ptr<QGpgME::DecryptVerifyArchiveJob> job{m_backend->decryptVerifyArchiveJob()};
kleo_assert(job);
connect(job.get(), &QGpgME::DecryptVerifyArchiveJob::result, q, [this](const DecryptionResult &, const VerificationResult &verifyResult) {
slotResult(verifyResult);
});
connect(job.get(), &QGpgME::DecryptVerifyArchiveJob::dataProgress, q, &VerifyOpaqueTask::setProgress);
// make sure that we don't use an existing output directory
const auto outputDirectory = ensureUniqueDirectory(m_outputDirectory);
if (outputDirectory.isEmpty()) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_GENERAL), {}, {}));
return;
}
m_outputFilePath = outputDirectory;
m_outputDirectory = outputDirectory;
#if QGPGME_SUPPORTS_PROCESS_ALL_SIGNATURES
job->setProcessAllSignatures(true);
#endif
job->setInputFile(m_inputFilePath);
job->setOutputDirectory(m_outputDirectory);
const auto err = job->startIt();
q->setJob(job.release());
if (err) {
q->emitResult(q->fromVerifyOpaqueResult(err, {}, {}));
}
}
class VerifyDetachedTask::Private
{
VerifyDetachedTask *const q;
public:
explicit Private(VerifyDetachedTask *qq)
: q{qq}
{
}
void slotResult(const VerificationResult &);
void registerJob(QGpgME::VerifyDetachedJob *job)
{
q->connect(job, SIGNAL(result(GpgME::VerificationResult)), q, SLOT(slotResult(GpgME::VerificationResult)));
q->connect(job, &QGpgME::Job::jobProgress, q, &VerifyDetachedTask::setProgress);
}
QString signatureLabel() const;
QString signedDataLabel() const;
std::shared_ptr<Input> m_input, m_signedData;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
QString m_signatureFilePath;
QString m_signedFilePath;
};
void VerifyDetachedTask::Private::slotResult(const VerificationResult &result)
{
updateKeys(result);
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
try {
kleo_assert(!result.isNull());
q->emitResult(q->fromVerifyDetachedResult(result, auditLog));
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
} catch (const std::exception &e) {
q->emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
} catch (...) {
q->emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
}
}
QString VerifyDetachedTask::Private::signatureLabel() const
{
return m_input ? m_input->label() : m_signatureFilePath;
}
QString VerifyDetachedTask::Private::signedDataLabel() const
{
return m_signedData ? m_signedData->label() : m_signedFilePath;
}
VerifyDetachedTask::VerifyDetachedTask(QObject *parent)
: AbstractDecryptVerifyTask(parent)
, d(new Private(this))
{
}
VerifyDetachedTask::~VerifyDetachedTask()
{
}
void VerifyDetachedTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void VerifyDetachedTask::setSignedData(const std::shared_ptr<Input> &signedData)
{
d->m_signedData = signedData;
kleo_assert(d->m_signedData && d->m_signedData->ioDevice());
}
void VerifyDetachedTask::setSignatureFile(const QString &path)
{
d->m_signatureFilePath = path;
}
void VerifyDetachedTask::setSignedFile(const QString &path)
{
d->m_signedFilePath = path;
}
void VerifyDetachedTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void VerifyDetachedTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND),
i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"),
Exception::MessageOnly);
}
setProtocol(p);
}
unsigned long long VerifyDetachedTask::inputSize() const
{
return d->m_signedData ? d->m_signedData->size() : 0;
}
QString VerifyDetachedTask::label() const
{
const QString signedDataLabel = d->signedDataLabel();
if (!signedDataLabel.isEmpty()) {
return xi18nc(
"Verification of a detached signature in progress. The first file contains the data."
"The second file is the signature file.",
"Verifying <filename>%1</filename> with <filename>%2</filename>...",
signedDataLabel,
d->signatureLabel());
}
return i18n("Verifying signature %1...", d->signatureLabel());
}
QString VerifyDetachedTask::inputLabel() const
{
return d->signatureLabel();
}
QString VerifyDetachedTask::outputLabel() const
{
return d->signedDataLabel();
}
Protocol VerifyDetachedTask::protocol() const
{
return d->m_protocol;
}
void VerifyDetachedTask::doStart()
{
kleo_assert(d->m_backend);
try {
std::unique_ptr<QGpgME::VerifyDetachedJob> job{d->m_backend->verifyDetachedJob()};
kleo_assert(job);
d->registerJob(job.get());
#if QGPGME_SUPPORTS_PROCESS_ALL_SIGNATURES
job->setProcessAllSignatures(true);
#endif
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (d->m_protocol == GpgME::OpenPGP && !d->m_signatureFilePath.isEmpty() && !d->m_signedFilePath.isEmpty()) {
job->setSignatureFile(d->m_signatureFilePath);
job->setSignedFile(d->m_signedFilePath);
job->startIt();
} else {
ensureIOOpen(d->m_input->ioDevice().get(), nullptr);
ensureIOOpen(d->m_signedData->ioDevice().get(), nullptr);
job->start(d->m_input->ioDevice(), d->m_signedData->ioDevice());
}
#else
ensureIOOpen(d->m_input->ioDevice().get(), nullptr);
ensureIOOpen(d->m_signedData->ioDevice().get(), nullptr);
job->start(d->m_input->ioDevice(), d->m_signedData->ioDevice());
#endif
setJob(job.release());
} catch (const GpgME::Exception &e) {
emitResult(fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
emitResult(
fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
emitResult(fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
void AbstractDecryptVerifyTask::setDataSource(Task::DataSource dataSource)
{
d->dataSource = dataSource;
}
Task::DataSource AbstractDecryptVerifyTask::dataSource() const
{
return d->dataSource;
}
#include "moc_decryptverifytask.cpp"

File Metadata

Mime Type
text/x-diff
Expires
Tue, Apr 22, 3:59 AM (4 h, 13 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ad/88/f61bb8cd755f0b5aa6b2450b0f2c

Event Timeline