diff --git a/src/crypto/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp index 9ce806b1d..646a9fb4d 100644 --- a/src/crypto/autodecryptverifyfilescontroller.cpp +++ b/src/crypto/autodecryptverifyfilescontroller.cpp @@ -1,615 +1,644 @@ /* -*- mode: c++; c-basic-offset:4 -*- autodecryptverifyfilescontroller.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 #include "autodecryptverifyfilescontroller.h" #include "fileoperationspreferences.h" #include #include #include #include #include "commands/decryptverifyfilescommand.h" #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #include #endif #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; class AutoDecryptVerifyFilesController::Private { AutoDecryptVerifyFilesController *const q; public: explicit Private(AutoDecryptVerifyFilesController *qq); void schedule(); QString getEmbeddedFileName(const QString &fileName) const; void exec(); std::vector> buildTasks(const QStringList &, QStringList &); struct CryptoFile { QString baseName; QString fileName; GpgME::Protocol protocol = GpgME::UnknownProtocol; int classification = 0; std::shared_ptr output; }; QVector classifyAndSortFiles(const QStringList &files); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void cancelAllTasks(); QStringList m_passedFiles, m_filesAfterPreparation; std::vector> m_results; std::vector> m_runnableTasks, m_completedTasks; std::shared_ptr m_runningTask; bool m_errorDetected = false; DecryptVerifyOperation m_operation = DecryptVerify; QPointer m_dialog; std::unique_ptr m_workDir; }; AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq) : q(qq) { qRegisterMetaType(); } void AutoDecryptVerifyFilesController::Private::schedule() { if (!m_runningTask && !m_runnableTasks.empty()) { const std::shared_ptr t = m_runnableTasks.back(); m_runnableTasks.pop_back(); t->start(); m_runningTask = t; } if (!m_runningTask) { kleo_assert(m_runnableTasks.empty()); for (const std::shared_ptr &i : std::as_const(m_results)) { Q_EMIT q->verificationResult(i->verificationResult()); } } } QString AutoDecryptVerifyFilesController::Private::getEmbeddedFileName(const QString &fileName) const { auto it = std::find_if(m_results.cbegin(), m_results.cend(), [fileName](const auto &r) { return r->fileName() == fileName; }); if (it != m_results.cend()) { const auto embeddedFilePath = QString::fromUtf8((*it)->decryptionResult().fileName()); if (embeddedFilePath.contains(QLatin1Char{'\\'})) { // ignore embedded file names containing '\' return {}; } // strip the path from the embedded file name return QFileInfo{embeddedFilePath}.fileName(); } else { return {}; } } void AutoDecryptVerifyFilesController::Private::exec() { Q_ASSERT(!m_dialog); QStringList undetected; std::vector> tasks = buildTasks(m_passedFiles, undetected); if (!undetected.isEmpty()) { // Since GpgME 1.7.0 Classification is supposed to be reliable // so we really can't do anything with this data. reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to find encrypted or signed data in one or more files." "You can manually select what to do with the files now." "If they contain signed or encrypted data please report a bug (see Help->Report Bug).")); auto cmd = new Commands::DecryptVerifyFilesCommand(undetected, nullptr, true); cmd->start(); } if (tasks.empty()) { q->emitDoneOrError(); return; } Q_ASSERT(m_runnableTasks.empty()); m_runnableTasks.swap(tasks); std::shared_ptr coll(new TaskCollection); for (const std::shared_ptr &i : std::as_const(m_runnableTasks)) { q->connectTask(i); } coll->setTasks(m_runnableTasks); DecryptVerifyFilesDialog dialog{coll}; m_dialog = &dialog; m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles)); QTimer::singleShot(0, q, SLOT(schedule())); const auto result = m_dialog->exec(); if (result == QDialog::Rejected) { q->cancel(); } else if (result == QDialog::Accepted && m_workDir) { // Without workdir there is nothing to move. const QDir workdir(m_workDir->path()); const QDir outDir(m_dialog->outputLocation()); bool overWriteAll = false; qCDebug(KLEOPATRA_LOG) << workdir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &fi : workdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)) { const auto inpath = fi.absoluteFilePath(); if (fi.isDir()) { // A directory. Assume that the input was an archive // and avoid directory merges by trying to find a non // existing directory. auto candidate = fi.fileName(); if (candidate.startsWith(QLatin1Char('-'))) { // Bug in GpgTar Extracts stdout passed archives to a dir named - candidate = QFileInfo(m_passedFiles.first()).baseName(); } QString suffix; QFileInfo ofi; int i = 0; do { ofi = QFileInfo(outDir.absoluteFilePath(candidate + suffix)); if (!ofi.exists()) { break; } suffix = QStringLiteral("_%1").arg(++i); } while (i < 1000); const auto destPath = ofi.absoluteFilePath(); #ifndef Q_OS_WIN auto job = KIO::moveAs(QUrl::fromLocalFile(inpath), QUrl::fromLocalFile(destPath)); qCDebug(KLEOPATRA_LOG) << "Moving" << job->srcUrls().front().toLocalFile() << "to" << job->destUrl().toLocalFile(); if (!job->exec()) { if (job->error() == KIO::ERR_USER_CANCELED) { break; } reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18nc("@info", "Failed to move %1 to %2." "%3", inpath, destPath, job->errorString())); } #else // On Windows, KIO::move does not work for folders when crossing partition boundaries if (!moveDir(inpath, destPath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18nc("@info", "Failed to move %1 to %2.", inpath, destPath)); } #endif continue; } const auto embeddedFileName = getEmbeddedFileName(inpath); QString outFileName = fi.fileName(); if (!embeddedFileName.isEmpty() && embeddedFileName != fi.fileName()) { // we switch "Yes" and "No" because Yes is default, but saving with embedded file name could be dangerous const auto answer = KMessageBox::questionTwoActionsCancel( m_dialog, xi18n("Shall the file be saved with the original file name %1?", embeddedFileName), i18n("Use Original File Name?"), KGuiItem(xi18n("No, Save As %1", fi.fileName())), KGuiItem(xi18n("Yes, Save As %1", embeddedFileName))); if (answer == KMessageBox::Cancel) { qCDebug(KLEOPATRA_LOG) << "Saving canceled for:" << inpath; continue; } else if (answer == KMessageBox::ButtonCode::SecondaryAction) { outFileName = embeddedFileName; } } const auto outpath = outDir.absoluteFilePath(outFileName); qCDebug(KLEOPATRA_LOG) << "Moving " << inpath << " to " << outpath; const QFileInfo ofi(outpath); if (ofi.exists()) { int sel = KMessageBox::Cancel; if (!overWriteAll) { sel = KMessageBox::questionTwoActionsCancel(m_dialog, i18n("The file %1 already exists.\n" "Overwrite?", outpath), i18n("Overwrite Existing File?"), KStandardGuiItem::overwrite(), KGuiItem(i18n("Overwrite All")), KStandardGuiItem::cancel()); } if (sel == KMessageBox::Cancel) { qCDebug(KLEOPATRA_LOG) << "Overwriting canceled for: " << outpath; continue; } if (sel == KMessageBox::ButtonCode::SecondaryAction) { // Overwrite All overWriteAll = true; } if (!QFile::remove(outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to delete %1.", outpath)); continue; } } if (!QFile::rename(inpath, outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move %1 to %2.", inpath, outpath)); } } } q->emitDoneOrError(); } QVector AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files) { const auto isSignature = [](int classification) -> bool { return mayBeDetachedSignature(classification) // || mayBeOpaqueSignature(classification) // || (classification & Class::TypeMask) == Class::ClearsignedMessage; }; QVector out; for (const auto &file : files) { CryptoFile cFile; cFile.fileName = file; cFile.baseName = stripSuffix(file); cFile.classification = classify(file); cFile.protocol = findProtocol(cFile.classification); auto it = std::find_if(out.begin(), out.end(), [&cFile](const CryptoFile &other) { return other.protocol == cFile.protocol && other.baseName == cFile.baseName; }); if (it != out.end()) { // If we found a file with the same basename, make sure that encrypted // file is before the signature file, so that we first decrypt and then // verify if (isSignature(cFile.classification) && isCipherText(it->classification)) { out.insert(it + 1, cFile); } else if (isCipherText(cFile.classification) && isSignature(it->classification)) { out.insert(it, cFile); } else { // both are signatures or both are encrypted files, in which // case order does not matter out.insert(it, cFile); } } else { out.push_back(cFile); } } return out; } static bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol) { return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported(); } std::vector> AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected) { // sort files so that we make sure we first decrypt and then verify QVector cryptoFiles = classifyAndSortFiles(fileNames); std::vector> tasks; for (auto it = cryptoFiles.begin(), end = cryptoFiles.end(); it != end; ++it) { auto &cFile = (*it); QFileInfo fi(cFile.fileName); qCDebug(KLEOPATRA_LOG) << "classified" << cFile.fileName << "as" << printableClassification(cFile.classification); if (!fi.isReadable()) { reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("Cannot open %1 for reading.", cFile.fileName)); continue; } if (mayBeAnyCertStoreType(cFile.classification)) { // Trying to verify a certificate. Possible because extensions are often similar // for PGP Keys. reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("The file %1 contains certificates and can't be decrypted or verified.", cFile.fileName)); qCDebug(KLEOPATRA_LOG) << "reported error"; continue; } // We can't reliably detect CMS detached signatures, so we will try to do // our best to use the current file as a detached signature and fallback to // opaque signature otherwise. if (cFile.protocol == GpgME::CMS && mayBeDetachedSignature(cFile.classification)) { // First, see if previous task was a decryption task for the same file // and "pipe" it's output into our input std::shared_ptr input; bool prepend = false; if (it != cryptoFiles.begin()) { const auto prev = it - 1; if (prev->protocol == cFile.protocol && prev->baseName == cFile.baseName) { input = Input::createFromOutput(prev->output); prepend = true; } } if (!input) { if (QFile::exists(cFile.baseName)) { input = Input::createFromFile(cFile.baseName); } } if (input) { qCDebug(KLEOPATRA_LOG) << "Detached CMS verify: " << cFile.fileName; std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(input); t->setProtocol(cFile.protocol); if (prepend) { // Put the verify task BEFORE the decrypt task in the tasks queue, // because the tasks are executed in reverse order! tasks.insert(tasks.end() - 1, t); } else { tasks.push_back(t); } continue; } else { // No signed data, maybe not a detached signature } } if (isDetachedSignature(cFile.classification)) { // Detached signature, try to find data or ask the user. QString signedDataFileName = cFile.baseName; if (!QFile::exists(signedDataFileName)) { signedDataFileName = QFileDialog::getOpenFileName(nullptr, xi18n("Select the file to verify with the signature %1", fi.fileName()), fi.path()); } if (signedDataFileName.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify aborted."; } else { qCDebug(KLEOPATRA_LOG) << "Detached verify: " << cFile.fileName << " Data: " << signedDataFileName; std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(Input::createFromFile(signedDataFileName)); t->setProtocol(cFile.protocol); tasks.push_back(t); } continue; } if (!mayBeAnyMessageType(cFile.classification)) { // Not a Message? Maybe there is a signature for this file? const auto signatures = findSignatures(cFile.fileName); bool foundSig = false; if (!signatures.empty()) { for (const QString &sig : signatures) { const auto classification = classify(sig); qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << cFile.fileName << "Classification: " << classification; const auto proto = findProtocol(classification); if (proto == GpgME::UnknownProtocol) { qCDebug(KLEOPATRA_LOG) << "Could not determine protocol. Skipping guess."; continue; } foundSig = true; std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(sig)); t->setSignedData(Input::createFromFile(cFile.fileName)); t->setProtocol(proto); tasks.push_back(t); } } if (!foundSig) { undetected << cFile.fileName; qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected."; } } else { const FileOperationsPreferences fileOpSettings; // Any Message type so we have input and output. - const auto input = Input::createFromFile(cFile.fileName); + std::shared_ptr input; +#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO + if (cFile.protocol != GpgME::OpenPGP) { + input = Input::createFromFile(cFile.fileName); + } +#else + input = Input::createFromFile(cFile.fileName); +#endif std::shared_ptr ad; if (fileOpSettings.autoExtractArchives()) { const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName); } if (fileOpSettings.dontUseTmpDir()) { if (!m_workDir) { m_workDir = std::make_unique(heuristicBaseDirectory(fileNames) + QStringLiteral("/kleopatra-XXXXXX")); } if (!m_workDir->isValid()) { qCDebug(KLEOPATRA_LOG) << heuristicBaseDirectory(fileNames) << "not a valid temporary directory."; m_workDir.reset(); } } if (!m_workDir) { m_workDir = std::make_unique(); } qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory."; const auto wd = QDir(m_workDir->path()); std::shared_ptr output; + QString outputFilePath; if (ad) { if ((ad->id() == QLatin1String{"tar"}) && archiveJobsCanBeUsed(cFile.protocol)) { // we don't need an output } else { output = ad->createOutputFromUnpackCommand(cFile.protocol, ad->stripExtension(cFile.protocol, cFile.baseName), wd); } } else { - output = Output::createFromFile(wd.absoluteFilePath(outputFileName(fi.fileName())), false); + outputFilePath = wd.absoluteFilePath(outputFileName(fi.fileName())); +#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO + if (cFile.protocol != GpgME::OpenPGP) { + output = Output::createFromFile(outputFilePath, false); + } +#else + output = Output::createFromFile(outputFilePath, false); +#endif } // If this might be opaque CMS signature, then try that. We already handled // detached CMS signature above const auto isCMSOpaqueSignature = cFile.protocol == GpgME::CMS && mayBeOpaqueSignature(cFile.classification); if (isOpaqueSignature(cFile.classification) || isCMSOpaqueSignature) { qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask"; std::shared_ptr t(new VerifyOpaqueTask); - t->setInput(input); + if (input) { + t->setInput(input); + } if (output) { t->setOutput(output); } t->setProtocol(cFile.protocol); if (ad) { t->setExtractArchive(true); t->setInputFile(cFile.fileName); if (output) { t->setOutputDirectory(m_workDir->path()); } else { // make gpgtar extract to a subfolder of the work directory based on the input file name const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName(); t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName)); } +#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO + } else if (cFile.protocol == GpgME::OpenPGP) { + t->setInputFile(cFile.fileName); + t->setOutputFile(outputFilePath); +#endif } tasks.push_back(t); } else { // Any message. That is not an opaque signature needs to be // decrypted. Verify we always do because we can't know if // an encrypted message is also signed. qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask"; std::shared_ptr t(new DecryptVerifyTask); - t->setInput(input); + if (input) { + t->setInput(input); + } if (output) { t->setOutput(output); } t->setProtocol(cFile.protocol); if (ad) { t->setExtractArchive(true); t->setInputFile(cFile.fileName); if (output) { t->setOutputDirectory(m_workDir->path()); } else { // make gpgtar extract to a subfolder of the work directory based on the input file name const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName(); t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName)); } +#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO + } else if (cFile.protocol == GpgME::OpenPGP) { + t->setInputFile(cFile.fileName); + t->setOutputFile(outputFilePath); +#endif } cFile.output = output; tasks.push_back(t); } } } return tasks; } void AutoDecryptVerifyFilesController::setFiles(const QStringList &files) { d->m_passedFiles = files; } AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(QObject *parent) : DecryptVerifyFilesController(parent) , d(new Private(this)) { } AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(const std::shared_ptr &ctx, QObject *parent) : DecryptVerifyFilesController(ctx, parent) , d(new Private(this)) { } AutoDecryptVerifyFilesController::~AutoDecryptVerifyFilesController() { qCDebug(KLEOPATRA_LOG); } void AutoDecryptVerifyFilesController::start() { d->exec(); } void AutoDecryptVerifyFilesController::setOperation(DecryptVerifyOperation op) { d->m_operation = op; } DecryptVerifyOperation AutoDecryptVerifyFilesController::operation() const { return d->m_operation; } void AutoDecryptVerifyFilesController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. m_runnableTasks.clear(); // a cancel() will result in a call to if (m_runningTask) { m_runningTask->cancel(); } } void AutoDecryptVerifyFilesController::cancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; try { d->m_errorDetected = true; if (d->m_dialog) { d->m_dialog->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void AutoDecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_ASSERT(task); Q_UNUSED(task) // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container d->m_completedTasks.push_back(d->m_runningTask); d->m_runningTask.reset(); if (const std::shared_ptr &dvr = std::dynamic_pointer_cast(result)) { d->m_results.push_back(dvr); } QTimer::singleShot(0, this, SLOT(schedule())); } #include "moc_autodecryptverifyfilescontroller.cpp" diff --git a/src/crypto/decryptverifytask.cpp b/src/crypto/decryptverifytask.cpp index bc501f5b4..a643d41b9 100644 --- a/src/crypto/decryptverifytask.cpp +++ b/src/crypto/decryptverifytask.cpp @@ -1,1822 +1,1887 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifytask.cpp 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 #include "decryptverifytask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include +#include #include #include #include #include #include #include // Qt::escape #include #include using namespace Kleo::Crypto; using namespace Kleo; using namespace GpgME; using namespace KMime::Types; namespace { static AuditLogEntry auditLogFromSender(QObject *sender) { return AuditLogEntry::fromJob(qobject_cast(sender)); } static bool addrspec_equal(const AddrSpec &lhs, const AddrSpec &rhs, Qt::CaseSensitivity cs) { return lhs.localPart.compare(rhs.localPart, cs) == 0 && lhs.domain.compare(rhs.domain, Qt::CaseInsensitive) == 0; } static bool mailbox_equal(const Mailbox &lhs, const Mailbox &rhs, Qt::CaseSensitivity cs) { return addrspec_equal(lhs.addrSpec(), rhs.addrSpec(), cs); } static std::string stripAngleBrackets(const std::string &str) { if (str.empty()) { return str; } if (str[0] == '<' && str[str.size() - 1] == '>') { return str.substr(1, str.size() - 2); } return str; } static std::string email(const UserID &uid) { if (uid.parent().protocol() == OpenPGP) { if (const char *const email = uid.email()) { return stripAngleBrackets(email); } else { return std::string(); } } Q_ASSERT(uid.parent().protocol() == CMS); if (const char *const id = uid.id()) if (*id == '<') { return stripAngleBrackets(id); } else { return DN(id)[QStringLiteral("EMAIL")].trimmed().toUtf8().constData(); } else { return std::string(); } } static Mailbox mailbox(const UserID &uid) { const std::string e = email(uid); Mailbox mbox; if (!e.empty()) { mbox.setAddress(e.c_str()); } return mbox; } static std::vector extractMailboxes(const Key &key) { std::vector res; const auto userIDs{key.userIDs()}; for (const UserID &id : userIDs) { const Mailbox mbox = mailbox(id); if (!mbox.addrSpec().isEmpty()) { res.push_back(mbox); } } return res; } static std::vector extractMailboxes(const std::vector &signers) { std::vector res; for (const Key &i : signers) { const std::vector bxs = extractMailboxes(i); res.insert(res.end(), bxs.begin(), bxs.end()); } return res; } static bool keyContainsMailbox(const Key &key, const Mailbox &mbox) { const std::vector mbxs = extractMailboxes(key); return std::find_if(mbxs.cbegin(), mbxs.cend(), [mbox](const Mailbox &m) { return mailbox_equal(mbox, m, Qt::CaseInsensitive); }) != mbxs.cend(); } static bool keysContainMailbox(const std::vector &keys, const Mailbox &mbox) { return std::find_if(keys.cbegin(), keys.cend(), [mbox](const Key &key) { return keyContainsMailbox(key, mbox); }) != keys.cend(); } static bool relevantInDecryptVerifyContext(const VerificationResult &r) { // for D/V operations, we ignore verification results which are not errors and contain // no signatures (which means that the data was just not signed) return (r.error() && r.error().code() != GPG_ERR_DECRYPT_FAILED) || r.numSignatures() > 0; } static QString signatureSummaryToString(int summary) { if (summary & Signature::None) { return i18n("Error: Signature not verified"); } else if (summary & Signature::Valid || summary & Signature::Green) { return i18n("Good signature"); } else if (summary & Signature::KeyRevoked) { return i18n("Signing certificate was revoked"); } else if (summary & Signature::KeyExpired) { return i18n("Signing certificate is expired"); } else if (summary & Signature::KeyMissing) { return i18n("Certificate is not available"); } else if (summary & Signature::SigExpired) { return i18n("Signature expired"); } else if (summary & Signature::CrlMissing) { return i18n("CRL missing"); } else if (summary & Signature::CrlTooOld) { return i18n("CRL too old"); } else if (summary & Signature::BadPolicy) { return i18n("Bad policy"); } else if (summary & Signature::SysError) { return i18n("System error"); // ### retrieve system error details? } else if (summary & Signature::Red) { return i18n("Bad signature"); } return QString(); } static QString formatValidSignatureWithTrustLevel(const UserID &id) { if (id.isNull()) { return QString(); } switch (id.validity()) { case UserID::Marginal: return i18n("The signature is valid but the trust in the certificate's validity is only marginal."); case UserID::Full: return i18n("The signature is valid and the certificate's validity is fully trusted."); case UserID::Ultimate: return i18n("The signature is valid and the certificate's validity is ultimately trusted."); case UserID::Never: return i18n("The signature is valid but the certificate's validity is not trusted."); case UserID::Unknown: return i18n("The signature is valid but the certificate's validity is unknown."); case UserID::Undefined: default: return i18n("The signature is valid but the certificate's validity is undefined."); } } static QString renderKeyLink(const QString &fpr, const QString &text) { return QStringLiteral("%2").arg(fpr, text); } static QString renderKey(const Key &key) { if (key.isNull()) { return i18n("Unknown certificate"); } if (key.primaryFingerprint() && strlen(key.primaryFingerprint()) > 16 && key.numUserIDs()) { const QString text = QStringLiteral("%1 (%2)") .arg(Formatting::prettyNameAndEMail(key).toHtmlEscaped()) .arg(Formatting::prettyID(QString::fromLocal8Bit(key.primaryFingerprint()).right(16).toLatin1().constData())); return renderKeyLink(QLatin1String(key.primaryFingerprint()), text); } return renderKeyLink(QLatin1String(key.primaryFingerprint()), Formatting::prettyID(key.primaryFingerprint())); } static QString renderKeyEMailOnlyNameAsFallback(const Key &key) { if (key.isNull()) { return i18n("Unknown certificate"); } const QString email = Formatting::prettyEMail(key); const QString user = !email.isEmpty() ? email : Formatting::prettyName(key); return renderKeyLink(QLatin1String(key.primaryFingerprint()), user); } static QString formatDate(const QDateTime &dt) { return QLocale().toString(dt); } static QString formatSigningInformation(const Signature &sig) { if (sig.isNull()) { return QString(); } const QDateTime dt = sig.creationTime() != 0 ? QDateTime::fromSecsSinceEpoch(quint32(sig.creationTime())) : QDateTime(); QString text; Key key = sig.key(); if (dt.isValid()) { text = i18nc("1 is a date", "Signature created on %1", formatDate(dt)) + QStringLiteral("
"); } if (key.isNull()) { return text += i18n("With unavailable certificate:") + QStringLiteral("
ID: 0x%1").arg(QString::fromLatin1(sig.fingerprint()).toUpper()); } text += i18n("With certificate:") + QStringLiteral("
") + renderKey(key); if (DeVSCompliance::isCompliant()) { text += (QStringLiteral("
") + (sig.isDeVs() ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "The signature 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 signature is not %1.", DeVSCompliance::name(true)))); } return text; } static QString strikeOut(const QString &str, bool strike) { return QString(strike ? QStringLiteral("%1") : QStringLiteral("%1")).arg(str.toHtmlEscaped()); } static QString formatInputOutputLabel(const QString &input, const QString &output, bool inputDeleted, bool outputDeleted) { if (output.isEmpty()) { return strikeOut(input, inputDeleted); } return i18nc("Input file --> Output file (rarr is arrow", "%1 → %2", strikeOut(input, inputDeleted), strikeOut(output, outputDeleted)); } static bool IsErrorOrCanceled(const GpgME::Error &err) { return err || err.isCanceled(); } static bool IsErrorOrCanceled(const Result &res) { return IsErrorOrCanceled(res.error()); } static bool IsBad(const Signature &sig) { return sig.summary() & Signature::Red; } static bool IsGoodOrValid(const Signature &sig) { return (sig.summary() & Signature::Valid) || (sig.summary() & Signature::Green); } static UserID findUserIDByMailbox(const Key &key, const Mailbox &mbox) { const auto userIDs{key.userIDs()}; for (const UserID &id : userIDs) if (mailbox_equal(mailbox(id), mbox, Qt::CaseInsensitive)) { return id; } return UserID(); } 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); } } class DecryptVerifyResult::SenderInfo { public: explicit SenderInfo(const Mailbox &infSender, const std::vector &signers_) : informativeSender(infSender) , signers(signers_) { } const Mailbox informativeSender; const std::vector signers; bool hasInformativeSender() const { return !informativeSender.addrSpec().isEmpty(); } bool conflicts() const { return hasInformativeSender() && hasKeys() && !keysContainMailbox(signers, informativeSender); } bool hasKeys() const { return std::any_of(signers.cbegin(), signers.cend(), [](const Key &key) { return !key.isNull(); }); } std::vector signerMailboxes() const { return extractMailboxes(signers); } }; namespace { static Task::Result::VisualCode codeForVerificationResult(const VerificationResult &res) { if (res.isNull()) { return Task::Result::NeutralSuccess; } const std::vector sigs = res.signatures(); if (sigs.empty()) { return Task::Result::Warning; } if (std::find_if(sigs.begin(), sigs.end(), IsBad) != sigs.end()) { return Task::Result::Danger; } if ((size_t)std::count_if(sigs.begin(), sigs.end(), IsGoodOrValid) == sigs.size()) { return Task::Result::AllGood; } return Task::Result::Warning; } static QString formatVerificationResultOverview(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info) { if (res.isNull()) { return QString(); } const Error err = res.error(); if (err.isCanceled()) { return i18n("Verification canceled."); } else if (err) { return i18n("Verification failed: %1.", Formatting::errorAsString(err).toHtmlEscaped()); } const std::vector sigs = res.signatures(); if (sigs.empty()) { return i18n("No signatures found."); } const uint bad = std::count_if(sigs.cbegin(), sigs.cend(), IsBad); if (bad > 0) { return i18np("Invalid signature.", "%1 invalid signatures.", bad); } const uint warn = std::count_if(sigs.cbegin(), sigs.cend(), [](const Signature &sig) { return !IsGoodOrValid(sig); }); if (warn == sigs.size()) { return i18np("The data could not be verified.", "%1 signatures could not be verified.", warn); } // Good signature: QString text; if (sigs.size() == 1) { text = i18n("Valid signature by %1", renderKeyEMailOnlyNameAsFallback(sigs[0].key())); if (info.conflicts()) text += i18n("
Warning: The sender's mail address is not stored in the %1 used for signing.", renderKeyLink(QLatin1String(sigs[0].key().primaryFingerprint()), i18n("certificate"))); } else { text = i18np("Valid signature.", "%1 valid signatures.", sigs.size()); if (info.conflicts()) { text += i18n("
Warning: The sender's mail address is not stored in the certificates used for signing."); } } return text; } static QString formatDecryptionResultOverview(const DecryptionResult &result, const QString &errorString = QString()) { const Error err = result.error(); if (err.isCanceled()) { return i18n("Decryption canceled."); } else if (result.isLegacyCipherNoMDC()) { return i18n("Decryption failed: %1.", i18n("No integrity protection (MDC).")); } else if (!errorString.isEmpty()) { return i18n("Decryption failed: %1.", errorString.toHtmlEscaped()); } else if (err) { return i18n("Decryption failed: %1.", Formatting::errorAsString(err).toHtmlEscaped()); } return i18n("Decryption succeeded."); } static QString formatSignature(const Signature &sig, const DecryptVerifyResult::SenderInfo &info) { if (sig.isNull()) { return QString(); } const QString text = formatSigningInformation(sig) + QLatin1String("
"); const Key key = sig.key(); // Green if (sig.summary() & Signature::Valid) { const UserID id = findUserIDByMailbox(key, info.informativeSender); return text + formatValidSignatureWithTrustLevel(!id.isNull() ? id : key.userID(0)); } // Red if ((sig.summary() & Signature::Red)) { const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary())); if (sig.summary() & Signature::SysError) { return ret + QStringLiteral(" (%1)").arg(Formatting::errorAsString(sig.status())); } return ret; } // Key missing if ((sig.summary() & Signature::KeyMissing)) { return text + i18n("You can search the certificate on a keyserver or import it from a file."); } // Yellow if ((sig.validity() & Signature::Validity::Undefined) // || (sig.validity() & Signature::Validity::Unknown) // || (sig.summary() == Signature::Summary::None)) { return text + (key.protocol() == OpenPGP ? i18n("The used key is not certified by you or any trusted person.") : i18n("The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown.")); } // Catch all fall through const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary())); if (sig.summary() & Signature::SysError) { return ret + QStringLiteral(" (%1)").arg(Formatting::errorAsString(sig.status())); } return ret; } static QStringList format(const std::vector &mbxs) { QStringList res; std::transform(mbxs.cbegin(), mbxs.cend(), std::back_inserter(res), [](const Mailbox &mbox) { return mbox.prettyAddress(); }); return res; } static QString formatVerificationResultDetails(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info, const QString &errorString) { if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) { return i18n("Input error: %1", errorString); } const std::vector sigs = res.signatures(); QString details; for (const Signature &sig : sigs) { details += formatSignature(sig, info) + QLatin1Char('\n'); } details = details.trimmed(); details.replace(QLatin1Char('\n'), QStringLiteral("

")); if (info.conflicts()) { details += i18n("

The sender's address %1 is not stored in the certificate. Stored: %2

", info.informativeSender.prettyAddress(), format(info.signerMailboxes()).join(i18nc("separator for a list of e-mail addresses", ", "))); } return details; } static QString formatRecipientsDetails(const std::vector &knownRecipients, unsigned int numRecipients) { if (numRecipients == 0) { return {}; } if (knownRecipients.empty()) { return QLatin1String("") + i18np("One unknown recipient.", "%1 unknown recipients.", numRecipients) + QLatin1String(""); } QString details = i18np("Recipient:", "Recipients:", numRecipients); if (numRecipients == 1) { details += QLatin1Char(' ') + renderKey(knownRecipients.front()); } else { details += QLatin1String("
    "); for (const Key &key : knownRecipients) { details += QLatin1String("
  • ") + renderKey(key) + QLatin1String("
  • "); } if (knownRecipients.size() < numRecipients) { details += QLatin1String("
  • ") + i18np("One unknown recipient", "%1 unknown recipients", numRecipients - knownRecipients.size()) + QLatin1String("
  • "); } details += QLatin1String("
"); } return details; } static QString formatDecryptionResultDetails(const DecryptionResult &res, const std::vector &recipients, const QString &errorString, bool isSigned, const QPointer &task) { if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) { return i18n("Input error: %1", errorString); } if (res.isNull() || res.error() || res.error().isCanceled()) { return formatRecipientsDetails(recipients, res.numRecipients()); } QString details; if (DeVSCompliance::isCompliant()) { details += ((res.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 is not %1.", DeVSCompliance::name(true))) + QStringLiteral("
")); } if (res.fileName()) { const auto decVerifyTask = qobject_cast(task.data()); if (decVerifyTask) { const auto embedFileName = QString::fromUtf8(res.fileName()).toHtmlEscaped(); if (embedFileName != decVerifyTask->outputLabel()) { details += i18n("Embedded file name: '%1'", embedFileName); details += QStringLiteral("
"); } } } if (!isSigned) { details += i18n("Note: You cannot be sure who encrypted this message as it is not signed.") + QLatin1String("
"); } if (res.isLegacyCipherNoMDC()) { details += i18nc("Integrity protection was missing because an old cipher was used.", "Hint: 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("

") + 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("

"); } details += formatRecipientsDetails(recipients, res.numRecipients()); return details; } static QString formatDecryptVerifyResultOverview(const DecryptionResult &dr, const VerificationResult &vr, const DecryptVerifyResult::SenderInfo &info) { if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) { return formatDecryptionResultOverview(dr); } return formatVerificationResultOverview(vr, info); } static QString formatDecryptVerifyResultDetails(const DecryptionResult &dr, const VerificationResult &vr, const std::vector &recipients, const DecryptVerifyResult::SenderInfo &info, const QString &errorString, const QPointer &task) { const QString drDetails = formatDecryptionResultDetails(dr, recipients, errorString, relevantInDecryptVerifyContext(vr), task); if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) { return drDetails; } return drDetails + (drDetails.isEmpty() ? QString() : QStringLiteral("
")) + formatVerificationResultDetails(vr, info, errorString); } } // anon namespace class DecryptVerifyResult::Private { DecryptVerifyResult *const q; public: Private(DecryptVerifyOperation type, 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, DecryptVerifyResult *qq) : q(qq) , m_type(type) , 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(parentTask)) , m_informativeSender(informativeSender) { } QString label() const { return formatInputOutputLabel(m_inputLabel, m_outputLabel, false, q->hasError()); } DecryptVerifyResult::SenderInfo makeSenderInfo() const; bool isDecryptOnly() const { return m_type == Decrypt; } bool isVerifyOnly() const { return m_type == Verify; } bool isDecryptVerify() const { return m_type == DecryptVerify; } DecryptVerifyOperation m_type; 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 m_parentTask; const Mailbox m_informativeSender; }; DecryptVerifyResult::SenderInfo DecryptVerifyResult::Private::makeSenderInfo() const { return SenderInfo(m_informativeSender, KeyCache::instance()->findSigners(m_verificationResult)); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptResult(const DecryptionResult &dr, const QByteArray &plaintext, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Decrypt, // VerificationResult(), dr, plaintext, {}, {}, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptResult(const GpgME::Error &err, const QString &what, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Decrypt, // VerificationResult(), DecryptionResult(err), QByteArray(), {}, err, what, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr 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(new DecryptVerifyResult(DecryptVerify, // vr, dr, plaintext, fileName, err, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptVerifyResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(DecryptVerify, // VerificationResult(), DecryptionResult(err), QByteArray(), {}, err, details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const VerificationResult &vr, const QByteArray &plaintext, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Verify, // vr, DecryptionResult(), plaintext, {}, {}, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Verify, // VerificationResult(err), DecryptionResult(), QByteArray(), {}, err, details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyDetachedResult(const VerificationResult &vr, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Verify, // vr, DecryptionResult(), QByteArray(), {}, {}, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyDetachedResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Verify, // VerificationResult(err), DecryptionResult(), QByteArray(), {}, err, details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } DecryptVerifyResult::DecryptVerifyResult(DecryptVerifyOperation type, 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::Result() , d(new Private(type, vr, dr, stuff, fileName, error, errString, inputLabel, outputLabel, auditLog, parentTask, informativeSender, this)) { } Task::Result::ContentType DecryptVerifyResult::viewableContentType() const { #if QGPGME_SUPPORTS_IS_MIME if (decryptionResult().isMime()) { return Task::Result::ContentType::Mime; } #endif 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::overview() const { QString ov; if (d->isDecryptOnly()) { ov += formatDecryptionResultOverview(d->m_decryptionResult); } else if (d->isVerifyOnly()) { ov += formatVerificationResultOverview(d->m_verificationResult, d->makeSenderInfo()); } else { ov += formatDecryptVerifyResultOverview(d->m_decryptionResult, d->m_verificationResult, d->makeSenderInfo()); } if (ov.size() + d->label().size() > 120) { // Avoid ugly breaks ov = QStringLiteral("
") + ov; } return i18nc("label: result example: foo.sig: Verification failed. ", "%1: %2", d->label(), ov); } QString DecryptVerifyResult::details() const { if (d->isDecryptOnly()) { return formatDecryptionResultDetails(d->m_decryptionResult, KeyCache::instance()->findRecipients(d->m_decryptionResult), errorString(), false, d->m_parentTask); } if (d->isVerifyOnly()) { return formatVerificationResultDetails(d->m_verificationResult, d->makeSenderInfo(), errorString()); } return formatDecryptVerifyResultDetails(d->m_decryptionResult, d->m_verificationResult, KeyCache::instance()->findRecipients(d->m_decryptionResult), d->makeSenderInfo(), errorString(), d->m_parentTask); } 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 DecryptVerifyResult::parentTask() const { return d->m_parentTask; } Task::Result::VisualCode DecryptVerifyResult::code() const { if ((d->m_type == DecryptVerify || d->m_type == Verify) && relevantInDecryptVerifyContext(verificationResult())) { return codeForVerificationResult(verificationResult()); } return hasError() ? NeutralError : NeutralSuccess; } 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 job; }; 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 m_input; std::shared_ptr 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() : QString{}, auditLog)); } DecryptVerifyTask::DecryptVerifyTask(QObject *parent) : AbstractDecryptVerifyTask(parent) , d(new Private(this)) { } DecryptVerifyTask::~DecryptVerifyTask() { } void DecryptVerifyTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void DecryptVerifyTask::setOutput(const std::shared_ptr &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...", d->m_input->label()); + 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() : QString(); + return d->m_input ? d->m_input->label() : QFileInfo{d->m_inputFilePath}.fileName(); } QString DecryptVerifyTask::outputLabel() const { - return d->m_output ? d->m_output->label() : d->m_outputDirectory; + 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 (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 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_FILE_JOBS_SUPPORT_DIRECT_FILE_IO + 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()); +#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 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); #if QGPGME_ARCHIVE_JOBS_SUPPORT_INPUT_FILENAME // 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; job->setInputFile(m_inputFilePath); job->setOutputDirectory(m_outputDirectory); const auto err = job->startIt(); #else ensureIOOpen(m_input->ioDevice().get(), nullptr); job->setOutputDirectory(m_outputDirectory); const auto err = job->start(m_input->ioDevice()); #endif 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 m_input; std::shared_ptr 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) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void DecryptTask::setOutput(const std::shared_ptr &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 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 m_input; std::shared_ptr 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) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void VerifyOpaqueTask::setOutput(const std::shared_ptr &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...", d->m_input->label()); + 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() : QString(); + return d->m_input ? d->m_input->label() : QFileInfo{d->m_inputFilePath}.fileName(); } QString VerifyOpaqueTask::outputLabel() const { - return d->m_output ? d->m_output->label() : d->m_outputDirectory; + 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 (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 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_FILE_JOBS_SUPPORT_DIRECT_FILE_IO + 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()); +#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 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); #if QGPGME_ARCHIVE_JOBS_SUPPORT_INPUT_FILENAME // 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; job->setInputFile(m_inputFilePath); job->setOutputDirectory(m_outputDirectory); const auto err = job->startIt(); #else ensureIOOpen(m_input->ioDevice().get(), nullptr); job->setOutputDirectory(m_outputDirectory); const auto err = job->start(m_input->ioDevice()); #endif 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); } std::shared_ptr m_input, m_signedData; const QGpgME::Protocol *m_backend = nullptr; Protocol m_protocol = UnknownProtocol; }; 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)); } } VerifyDetachedTask::VerifyDetachedTask(QObject *parent) : AbstractDecryptVerifyTask(parent) , d(new Private(this)) { } VerifyDetachedTask::~VerifyDetachedTask() { } void VerifyDetachedTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void VerifyDetachedTask::setSignedData(const std::shared_ptr &signedData) { d->m_signedData = signedData; kleo_assert(d->m_signedData && d->m_signedData->ioDevice()); } 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 { if (d->m_signedData) { return xi18nc( "Verification of a detached signature in progress. The first file contains the data." "The second file is the signature file.", "Verifying: %1 with %2...", d->m_signedData->label(), d->m_input->label()); } return i18n("Verifying signature: %1...", d->m_input->label()); } QString VerifyDetachedTask::inputLabel() const { if (d->m_signedData && d->m_input) { return xi18nc( "Verification of a detached signature summary. The first file contains the data." "The second file is signature.", "Verified %1 with %2", d->m_signedData->label(), d->m_input->label()); } return d->m_input ? d->m_input->label() : QString(); } QString VerifyDetachedTask::outputLabel() const { return QString(); } Protocol VerifyDetachedTask::protocol() const { return d->m_protocol; } void VerifyDetachedTask::doStart() { kleo_assert(d->m_backend); try { std::unique_ptr job{d->m_backend->verifyDetachedJob()}; kleo_assert(job); d->registerJob(job.get()); ensureIOOpen(d->m_input->ioDevice().get(), nullptr); ensureIOOpen(d->m_signedData->ioDevice().get(), nullptr); job->start(d->m_input->ioDevice(), d->m_signedData->ioDevice()); 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())); } } #include "moc_decryptverifytask.cpp" diff --git a/src/crypto/decryptverifytask.h b/src/crypto/decryptverifytask.h index 1ea4a0fba..8e7d4e0c6 100644 --- a/src/crypto/decryptverifytask.h +++ b/src/crypto/decryptverifytask.h @@ -1,267 +1,273 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifytask.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "task.h" #include #include #include namespace KMime { namespace Types { class Mailbox; } } namespace GpgME { class DecryptionResult; class VerificationResult; class Key; class Signature; } namespace QGpgME { class Job; } namespace Kleo { class Input; class Output; class AuditLogEntry; } namespace Kleo { namespace Crypto { class DecryptVerifyResult; class AbstractDecryptVerifyTask : public Task { Q_OBJECT public: explicit AbstractDecryptVerifyTask(QObject *parent = nullptr); ~AbstractDecryptVerifyTask() override; virtual void autodetectProtocolFromInput() = 0; KMime::Types::Mailbox informativeSender() const; void setInformativeSender(const KMime::Types::Mailbox &senders); virtual QString inputLabel() const = 0; virtual QString outputLabel() const = 0; public Q_SLOTS: void cancel() override; protected: std::shared_ptr fromDecryptResult(const GpgME::DecryptionResult &dr, const QByteArray &plaintext, const AuditLogEntry &auditLog); std::shared_ptr fromDecryptResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog); std::shared_ptr fromDecryptVerifyResult(const GpgME::DecryptionResult &dr, const GpgME::VerificationResult &vr, const QByteArray &plaintext, const QString &fileName, const AuditLogEntry &auditLog); std::shared_ptr fromDecryptVerifyResult(const GpgME::Error &err, const QString &what, const AuditLogEntry &auditLog); std::shared_ptr fromVerifyOpaqueResult(const GpgME::VerificationResult &vr, const QByteArray &plaintext, const AuditLogEntry &auditLog); std::shared_ptr fromVerifyOpaqueResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog); std::shared_ptr fromVerifyDetachedResult(const GpgME::VerificationResult &vr, const AuditLogEntry &auditLog); std::shared_ptr fromVerifyDetachedResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog); public: // public to allow access from the Private classes of the concrete tasks QGpgME::Job *job() const; void setJob(QGpgME::Job *job); private: class Private; kdtools::pimpl_ptr d; }; class DecryptTask : public AbstractDecryptVerifyTask { Q_OBJECT public: explicit DecryptTask(QObject *parent = nullptr); ~DecryptTask() override; void setInput(const std::shared_ptr &input); void setOutput(const std::shared_ptr &output); void setProtocol(GpgME::Protocol prot); void autodetectProtocolFromInput() override; QString label() const override; GpgME::Protocol protocol() const override; QString inputLabel() const override; QString outputLabel() const override; private: void doStart() override; unsigned long long inputSize() const override; private: class Private; kdtools::pimpl_ptr d; Q_PRIVATE_SLOT(d, void slotResult(GpgME::DecryptionResult, QByteArray)) }; class VerifyDetachedTask : public AbstractDecryptVerifyTask { Q_OBJECT public: explicit VerifyDetachedTask(QObject *parent = nullptr); ~VerifyDetachedTask() override; void setInput(const std::shared_ptr &input); void setSignedData(const std::shared_ptr &signedData); void setProtocol(GpgME::Protocol prot); void autodetectProtocolFromInput() override; QString label() const override; GpgME::Protocol protocol() const override; QString inputLabel() const override; QString outputLabel() const override; private: void doStart() override; unsigned long long inputSize() const override; private: class Private; kdtools::pimpl_ptr d; Q_PRIVATE_SLOT(d, void slotResult(GpgME::VerificationResult)) }; class VerifyOpaqueTask : public AbstractDecryptVerifyTask { Q_OBJECT public: explicit VerifyOpaqueTask(QObject *parent = nullptr); ~VerifyOpaqueTask() override; void setInput(const std::shared_ptr &input); void setOutput(const std::shared_ptr &output); void setProtocol(GpgME::Protocol prot); void autodetectProtocolFromInput() override; QString label() const override; GpgME::Protocol protocol() const override; void setExtractArchive(bool extract); void setInputFile(const QString &path); + // for files + void setOutputFile(const QString &path); + // for archives void setOutputDirectory(const QString &directory); QString inputLabel() const override; QString outputLabel() const override; private: void doStart() override; unsigned long long inputSize() const override; private: class Private; kdtools::pimpl_ptr d; Q_PRIVATE_SLOT(d, void slotResult(GpgME::VerificationResult, QByteArray)) }; class DecryptVerifyTask : public AbstractDecryptVerifyTask { Q_OBJECT public: explicit DecryptVerifyTask(QObject *parent = nullptr); ~DecryptVerifyTask() override; void setInput(const std::shared_ptr &input); void setOutput(const std::shared_ptr &output); void setProtocol(GpgME::Protocol prot); void autodetectProtocolFromInput() override; QString label() const override; GpgME::Protocol protocol() const override; void setIgnoreMDCError(bool value); void setExtractArchive(bool extract); void setInputFile(const QString &path); + // for files + void setOutputFile(const QString &path); + // for archives void setOutputDirectory(const QString &directory); QString inputLabel() const override; QString outputLabel() const override; private: void doStart() override; unsigned long long inputSize() const override; private: class Private; kdtools::pimpl_ptr d; Q_PRIVATE_SLOT(d, void slotResult(GpgME::DecryptionResult, GpgME::VerificationResult, QByteArray)) }; class DecryptVerifyResult : public Task::Result { friend class ::Kleo::Crypto::AbstractDecryptVerifyTask; public: class SenderInfo; QString overview() const override; QString details() const override; GpgME::Error error() const override; QString errorString() const override; VisualCode code() const override; AuditLogEntry auditLog() const override; QPointer parentTask() const override; Task::Result::ContentType viewableContentType() const override; GpgME::VerificationResult verificationResult() const; GpgME::DecryptionResult decryptionResult() const; QString fileName() const; private: DecryptVerifyResult(); DecryptVerifyResult(const DecryptVerifyResult &); DecryptVerifyResult &operator=(const DecryptVerifyResult &other); DecryptVerifyResult(DecryptVerifyOperation op, const GpgME::VerificationResult &vr, const GpgME::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 KMime::Types::Mailbox &informativeSender); private: class Private; kdtools::pimpl_ptr d; }; } }