diff --git a/lang/qt/src/Makefile.am b/lang/qt/src/Makefile.am index 326db516..5e16373e 100644 --- a/lang/qt/src/Makefile.am +++ b/lang/qt/src/Makefile.am @@ -65,6 +65,7 @@ qgpgme_sources = \ signarchivejob.cpp \ signencryptjob.cpp \ signencryptarchivejob.cpp \ + signjob.cpp \ dn.cpp cryptoconfig.cpp wkdlookupresult.cpp \ util.cpp \ wkdrefreshjob.cpp @@ -226,6 +227,7 @@ private_qgpgme_headers = \ signarchivejob_p.h \ signencryptjob_p.h \ signencryptarchivejob_p.h \ + signjob_p.h \ threadedjobmixin.h \ util.h \ wkdrefreshjob_p.h diff --git a/lang/qt/src/encryptjob.cpp b/lang/qt/src/encryptjob.cpp index 48a0e24e..d4337bad 100644 --- a/lang/qt/src/encryptjob.cpp +++ b/lang/qt/src/encryptjob.cpp @@ -40,6 +40,13 @@ using namespace QGpgME; +EncryptJob::EncryptJob(QObject *parent) + : Job{parent} +{ +} + +EncryptJob::~EncryptJob() = default; + void EncryptJob::setFileName(const QString &fileName) { auto d = jobPrivate(this); @@ -63,3 +70,53 @@ GpgME::Data::Encoding EncryptJob::inputEncoding() const auto d = jobPrivate(this); return d->m_inputEncoding; } + +void EncryptJob::setRecipients(const std::vector &recipients) +{ + auto d = jobPrivate(this); + d->m_recipients = recipients; +} + +std::vector EncryptJob::recipients() const +{ + auto d = jobPrivate(this); + return d->m_recipients; +} + +void EncryptJob::setInputFile(const QString &path) +{ + auto d = jobPrivate(this); + d->m_inputFilePath = path; +} + +QString EncryptJob::inputFile() const +{ + auto d = jobPrivate(this); + return d->m_inputFilePath; +} + +void EncryptJob::setOutputFile(const QString &path) +{ + auto d = jobPrivate(this); + d->m_outputFilePath = path; +} + +QString EncryptJob::outputFile() const +{ + auto d = jobPrivate(this); + return d->m_outputFilePath; +} + +void EncryptJob::setEncryptionFlags(GpgME::Context::EncryptionFlags flags) +{ + auto d = jobPrivate(this); + d->m_encryptionFlags = static_cast(flags | GpgME::Context::EncryptFile); +} + +GpgME::Context::EncryptionFlags EncryptJob::encryptionFlags() const +{ + auto d = jobPrivate(this); + return d->m_encryptionFlags; +} + +#include "encryptjob.moc" diff --git a/lang/qt/src/encryptjob.h b/lang/qt/src/encryptjob.h index ac3664fa..48b36598 100644 --- a/lang/qt/src/encryptjob.h +++ b/lang/qt/src/encryptjob.h @@ -72,6 +72,15 @@ namespace QGpgME EncryptJob instance will have scheduled it's own destruction with a call to QObject::deleteLater(). + Alternatively, the job can be started with startIt() after setting + an input file and an output file and, optionally, recipients or flags. + If the job is started this way then the backend reads the input and + writes the output directly from/to the specified input file and output + file. In this case the cipherText value of the result signal will always + be empty. This direct IO mode is currently only supported for OpenPGP. + Note that startIt() does not schedule the job's destruction if starting + the job failed. + After result() is emitted, the EncryptJob will schedule it's own destruction by calling QObject::deleteLater(). */ @@ -81,14 +90,63 @@ class QGPGME_EXPORT EncryptJob : public Job protected: explicit EncryptJob(QObject *parent); public: - ~EncryptJob(); + ~EncryptJob() override; + /** + * Sets the file name to embed in the encryption result. + * + * This is only used if one of the start() functions is used. + */ void setFileName(const QString &fileName); QString fileName() const; + /** + * Sets the encoding of the plaintext. + * + * This is only used if one of the start() functions is used. + */ void setInputEncoding(GpgME::Data::Encoding); GpgME::Data::Encoding inputEncoding() const; + /** + * Sets the keys to use for encryption. + * + * Used if the job is started with startIt(). + */ + void setRecipients(const std::vector &recipients); + std::vector recipients() const; + + /** + * Sets the path of the file to encrypt. + * + * Used if the job is started with startIt(). + */ + void setInputFile(const QString &path); + QString inputFile() const; + + /** + * Sets the path of the file to write the encryption result to. + * + * Used if the job is started with startIt(). + * + * \note If a file with this path exists, then the job will fail, i.e. you + * need to delete an existing file that shall be overwritten before you + * start the job. + */ + void setOutputFile(const QString &path); + QString outputFile() const; + + /** + * Sets the flags to use for encryption. + * + * Defaults to \c EncryptFile. + * + * Used if the job is started with startIt(). The \c EncryptFile flag is + * always assumed set. + */ + void setEncryptionFlags(GpgME::Context::EncryptionFlags flags); + GpgME::Context::EncryptionFlags encryptionFlags() const; + /** Starts the encryption operation. \a recipients is the a list of keys to encrypt \a plainText to. Empty (null) keys are diff --git a/lang/qt/src/encryptjob_p.h b/lang/qt/src/encryptjob_p.h index 9bb9e952..b92d784e 100644 --- a/lang/qt/src/encryptjob_p.h +++ b/lang/qt/src/encryptjob_p.h @@ -35,15 +35,24 @@ #define __QGPGME_ENCRYPTJOB_P_H__ #include "job_p.h" -#include "data.h" + +#include +#include namespace QGpgME { struct EncryptJobPrivate : public JobPrivate { + // used by start() functions QString m_fileName; GpgME::Data::Encoding m_inputEncoding; + + // used by startIt() + std::vector m_recipients; + QString m_inputFilePath; + QString m_outputFilePath; + GpgME::Context::EncryptionFlags m_encryptionFlags = GpgME::Context::EncryptFile; }; } diff --git a/lang/qt/src/job.cpp b/lang/qt/src/job.cpp index 3e19e64a..3c9422bb 100644 --- a/lang/qt/src/job.cpp +++ b/lang/qt/src/job.cpp @@ -43,12 +43,9 @@ #include "keylistjob.h" #include "listallkeysjob.h" -#include "encryptjob.h" #include "decryptjob.h" #include "decryptverifyjob.h" -#include "signjob.h" #include "signkeyjob.h" -#include "signencryptjob.h" #include "verifydetachedjob.h" #include "verifyopaquejob.h" #include "keygenerationjob.h" @@ -164,11 +161,8 @@ void QGpgME::Job::startNow() make_job_subclass(KeyListJob) make_job_subclass(ListAllKeysJob) -make_job_subclass(EncryptJob) make_job_subclass(DecryptJob) make_job_subclass(DecryptVerifyJob) -make_job_subclass(SignJob) -make_job_subclass(SignEncryptJob) make_job_subclass(SignKeyJob) make_job_subclass(VerifyDetachedJob) make_job_subclass(VerifyOpaqueJob) @@ -202,11 +196,8 @@ make_job_subclass(SetPrimaryUserIDJob) #include "keylistjob.moc" #include "listallkeysjob.moc" -#include "encryptjob.moc" #include "decryptjob.moc" #include "decryptverifyjob.moc" -#include "signjob.moc" -#include "signencryptjob.moc" #include "signkeyjob.moc" #include "verifydetachedjob.moc" #include "verifyopaquejob.moc" diff --git a/lang/qt/src/qgpgmeencryptjob.cpp b/lang/qt/src/qgpgmeencryptjob.cpp index c11089ff..d254d8a4 100644 --- a/lang/qt/src/qgpgmeencryptjob.cpp +++ b/lang/qt/src/qgpgmeencryptjob.cpp @@ -40,13 +40,13 @@ #include "qgpgmeencryptjob.h" -#include "encryptjob_p.h" - #include "dataprovider.h" +#include "encryptjob_p.h" +#include "util.h" -#include "context.h" -#include "encryptionresult.h" -#include "data.h" +#include +#include +#include #include #include @@ -72,11 +72,7 @@ public: ~QGpgMEEncryptJobPrivate() override = default; private: - GpgME::Error startIt() override - { - Q_ASSERT(!"Not supported by this Job class."); - return Error::fromCode(GPG_ERR_NOT_SUPPORTED); - } + GpgME::Error startIt() override; void startNow() override { @@ -168,6 +164,44 @@ static QGpgMEEncryptJob::result_type encrypt_qba(Context *ctx, const std::vector return encrypt(ctx, nullptr, recipients, buffer, std::shared_ptr(), eflags, outputIsBsse64Encoded, inputEncoding, fileName); } +static QGpgMEEncryptJob::result_type encrypt_to_filename(Context *ctx, + const std::vector &recipients, + const QString &inputFilePath, + const QString &outputFilePath, + Context::EncryptionFlags flags) +{ + Data indata; +#ifdef Q_OS_WIN + indata.setFileName(inputFilePath().toUtf8().constData()); +#else + indata.setFileName(QFile::encodeName(inputFilePath).constData()); +#endif + + PartialFileGuard partFileGuard{outputFilePath}; + if (partFileGuard.tempFileName().isEmpty()) { + return std::make_tuple(EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)}, QByteArray{}, QString{}, Error{}); + } + + Data outdata; +#ifdef Q_OS_WIN + outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData()); +#else + outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData()); +#endif + + flags = static_cast(flags | Context::EncryptFile); + const auto encryptionResult = ctx->encrypt(recipients, indata, outdata, flags); + + if (!encryptionResult.error().code()) { + // the operation succeeded -> save the result under the requested file name + partFileGuard.commit(); + } + + Error ae; + const QString log = _detail::audit_log_as_html(ctx, ae); + return std::make_tuple(encryptionResult, QByteArray{}, log, ae); +} + Error QGpgMEEncryptJob::start(const std::vector &recipients, const QByteArray &plainText, bool alwaysTrust) { run(std::bind(&encrypt_qba, std::placeholders::_1, recipients, plainText, @@ -213,4 +247,17 @@ void QGpgMEEncryptJob::resultHook(const result_type &tuple) mResult = std::get<0>(tuple); } +GpgME::Error QGpgMEEncryptJobPrivate::startIt() +{ + if (m_inputFilePath.isEmpty() || m_outputFilePath.isEmpty()) { + return Error::fromCode(GPG_ERR_INV_VALUE); + } + + q->run([=](Context *ctx) { + return encrypt_to_filename(ctx, m_recipients, m_inputFilePath, m_outputFilePath, m_encryptionFlags); + }); + + return {}; +} + #include "qgpgmeencryptjob.moc" diff --git a/lang/qt/src/qgpgmesignencryptjob.cpp b/lang/qt/src/qgpgmesignencryptjob.cpp index 0f5642a9..27af7ae9 100644 --- a/lang/qt/src/qgpgmesignencryptjob.cpp +++ b/lang/qt/src/qgpgmesignencryptjob.cpp @@ -40,14 +40,14 @@ #include "qgpgmesignencryptjob.h" -#include "signencryptjob_p.h" - #include "dataprovider.h" +#include "signencryptjob_p.h" +#include "util.h" -#include "context.h" -#include "data.h" -#include "key.h" -#include "exception.h" +#include +#include +#include +#include #include #include @@ -73,11 +73,7 @@ public: ~QGpgMESignEncryptJobPrivate() override = default; private: - GpgME::Error startIt() override - { - Q_ASSERT(!"Not supported by this Job class."); - return Error::fromCode(GPG_ERR_NOT_SUPPORTED); - } + GpgME::Error startIt() override; void startNow() override { @@ -171,6 +167,60 @@ static QGpgMESignEncryptJob::result_type sign_encrypt_qba(Context *ctx, const st return sign_encrypt(ctx, nullptr, signers, recipients, buffer, std::shared_ptr(), eflags, outputIsBsse64Encoded, fileName); } +static QGpgMESignEncryptJob::result_type sign_encrypt_to_filename(Context *ctx, + const std::vector &signers, + const std::vector &recipients, + const QString &inputFilePath, + const QString &outputFilePath, + Context::EncryptionFlags flags) +{ + Data indata; +#ifdef Q_OS_WIN + indata.setFileName(inputFilePath().toUtf8().constData()); +#else + indata.setFileName(QFile::encodeName(inputFilePath).constData()); +#endif + + PartialFileGuard partFileGuard{outputFilePath}; + if (partFileGuard.tempFileName().isEmpty()) { + return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_EEXIST)}, + EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)}, + QByteArray{}, + QString{}, + Error{}); + } + + Data outdata; +#ifdef Q_OS_WIN + outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData()); +#else + outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData()); +#endif + + ctx->clearSigningKeys(); + for (const Key &signer : signers) { + if (!signer.isNull()) { + if (const Error err = ctx->addSigningKey(signer)) { + return std::make_tuple(SigningResult{err}, EncryptionResult{}, QByteArray{}, QString{}, Error{}); + } + } + } + + flags = static_cast(flags | Context::EncryptFile); + const auto results = ctx->signAndEncrypt(recipients, indata, outdata, flags); + const auto &signingResult = results.first; + const auto &encryptionResult = results.second; + + if (!signingResult.error().code() && !encryptionResult.error().code()) { + // the operation succeeded -> save the result under the requested file name + partFileGuard.commit(); + } + + Error ae; + const QString log = _detail::audit_log_as_html(ctx, ae); + return std::make_tuple(signingResult, encryptionResult, QByteArray{}, log, ae); +} + Error QGpgMESignEncryptJob::start(const std::vector &signers, const std::vector &recipients, const QByteArray &plainText, bool alwaysTrust) { run(std::bind(&sign_encrypt_qba, std::placeholders::_1, signers, recipients, plainText, alwaysTrust ? Context::AlwaysTrust : Context::None, mOutputIsBase64Encoded, fileName())); @@ -205,4 +255,18 @@ void QGpgMESignEncryptJob::resultHook(const result_type &tuple) { mResult = std::make_pair(std::get<0>(tuple), std::get<1>(tuple)); } + +GpgME::Error QGpgMESignEncryptJobPrivate::startIt() +{ + if (m_inputFilePath.isEmpty() || m_outputFilePath.isEmpty()) { + return Error::fromCode(GPG_ERR_INV_VALUE); + } + + q->run([=](Context *ctx) { + return sign_encrypt_to_filename(ctx, m_signers, m_recipients, m_inputFilePath, m_outputFilePath, m_encryptionFlags); + }); + + return {}; +} + #include "qgpgmesignencryptjob.moc" diff --git a/lang/qt/src/qgpgmesignjob.cpp b/lang/qt/src/qgpgmesignjob.cpp index 1913157b..5c1d9622 100644 --- a/lang/qt/src/qgpgmesignjob.cpp +++ b/lang/qt/src/qgpgmesignjob.cpp @@ -39,23 +39,52 @@ #include "qgpgmesignjob.h" #include "dataprovider.h" +#include "signjob_p.h" +#include "util.h" -#include "context.h" -#include "signingresult.h" -#include "data.h" +#include +#include +#include #include - +#include #include using namespace QGpgME; using namespace GpgME; +namespace +{ + +class QGpgMESignJobPrivate : public SignJobPrivate +{ + QGpgMESignJob *q = nullptr; + +public: + QGpgMESignJobPrivate(QGpgMESignJob *qq) + : q{qq} + { + } + + ~QGpgMESignJobPrivate() override = default; + +private: + GpgME::Error startIt() override; + + void startNow() override + { + q->run(); + } +}; + +} + QGpgMESignJob::QGpgMESignJob(Context *context) : mixin_type(context), mOutputIsBase64Encoded(false) { + setJobPrivate(this, std::unique_ptr{new QGpgMESignJobPrivate{this}}); lateInitialization(); } @@ -137,6 +166,56 @@ static QGpgMESignJob::result_type sign_qba(Context *ctx, return sign(ctx, nullptr, signers, buffer, std::shared_ptr(), mode, outputIsBsse64Encoded); } +static QGpgMESignJob::result_type sign_to_filename(Context *ctx, + const std::vector &signers, + const QString &inputFilePath, + const QString &outputFilePath, + SignatureMode flags) +{ + Data indata; +#ifdef Q_OS_WIN + indata.setFileName(inputFilePath().toUtf8().constData()); +#else + indata.setFileName(QFile::encodeName(inputFilePath).constData()); +#endif + + PartialFileGuard partFileGuard{outputFilePath}; + if (partFileGuard.tempFileName().isEmpty()) { + return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_EEXIST)}, + QByteArray{}, + QString{}, + Error{}); + } + + Data outdata; +#ifdef Q_OS_WIN + outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData()); +#else + outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData()); +#endif + + ctx->clearSigningKeys(); + for (const Key &signer : signers) { + if (!signer.isNull()) { + if (const Error err = ctx->addSigningKey(signer)) { + return std::make_tuple(SigningResult{err}, QByteArray{}, QString{}, Error{}); + } + } + } + + flags = static_cast(flags | SignFile); + const auto signingResult = ctx->sign(indata, outdata, flags); + + if (!signingResult.error().code()) { + // the operation succeeded -> save the result under the requested file name + partFileGuard.commit(); + } + + Error ae; + const QString log = _detail::audit_log_as_html(ctx, ae); + return std::make_tuple(signingResult, QByteArray{}, log, ae); +} + Error QGpgMESignJob::start(const std::vector &signers, const QByteArray &plainText, SignatureMode mode) { run(std::bind(&sign_qba, std::placeholders::_1, signers, plainText, mode, mOutputIsBase64Encoded)); @@ -161,4 +240,17 @@ void QGpgMESignJob::resultHook(const result_type &tuple) mResult = std::get<0>(tuple); } +GpgME::Error QGpgMESignJobPrivate::startIt() +{ + if (m_inputFilePath.isEmpty() || m_outputFilePath.isEmpty()) { + return Error::fromCode(GPG_ERR_INV_VALUE); + } + + q->run([=](Context *ctx) { + return sign_to_filename(ctx, m_signers, m_inputFilePath, m_outputFilePath, m_signingFlags); + }); + + return {}; +} + #include "qgpgmesignjob.moc" diff --git a/lang/qt/src/signencryptjob.cpp b/lang/qt/src/signencryptjob.cpp index 2f4c5fd6..e82730e6 100644 --- a/lang/qt/src/signencryptjob.cpp +++ b/lang/qt/src/signencryptjob.cpp @@ -40,6 +40,13 @@ using namespace QGpgME; +SignEncryptJob::SignEncryptJob(QObject *parent) + : Job{parent} +{ +} + +SignEncryptJob::~SignEncryptJob() = default; + void SignEncryptJob::setFileName(const QString &fileName) { auto d = jobPrivate(this); @@ -51,3 +58,65 @@ QString SignEncryptJob::fileName() const auto d = jobPrivate(this); return d->m_fileName; } + +void SignEncryptJob::setSigners(const std::vector &signers) +{ + auto d = jobPrivate(this); + d->m_signers = signers; +} + +std::vector SignEncryptJob::signers() const +{ + auto d = jobPrivate(this); + return d->m_signers; +} + +void SignEncryptJob::setRecipients(const std::vector &recipients) +{ + auto d = jobPrivate(this); + d->m_recipients = recipients; +} + +std::vector SignEncryptJob::recipients() const +{ + auto d = jobPrivate(this); + return d->m_recipients; +} + +void SignEncryptJob::setInputFile(const QString &path) +{ + auto d = jobPrivate(this); + d->m_inputFilePath = path; +} + +QString SignEncryptJob::inputFile() const +{ + auto d = jobPrivate(this); + return d->m_inputFilePath; +} + +void SignEncryptJob::setOutputFile(const QString &path) +{ + auto d = jobPrivate(this); + d->m_outputFilePath = path; +} + +QString SignEncryptJob::outputFile() const +{ + auto d = jobPrivate(this); + return d->m_outputFilePath; +} + +void SignEncryptJob::setEncryptionFlags(GpgME::Context::EncryptionFlags flags) +{ + auto d = jobPrivate(this); + d->m_encryptionFlags = static_cast(flags | GpgME::Context::EncryptFile); +} + +GpgME::Context::EncryptionFlags SignEncryptJob::encryptionFlags() const +{ + auto d = jobPrivate(this); + return d->m_encryptionFlags; +} + +#include "signencryptjob.moc" diff --git a/lang/qt/src/signencryptjob.h b/lang/qt/src/signencryptjob.h index ebb866d1..54f4aa53 100644 --- a/lang/qt/src/signencryptjob.h +++ b/lang/qt/src/signencryptjob.h @@ -76,6 +76,15 @@ namespace QGpgME SignEncryptJob instance will have scheduled it's own destruction with a call to QObject::deleteLater(). + Alternatively, the job can be started with startIt() after setting + an input file and an output file and, optionally, signers, recipients or flags. + If the job is started this way then the backend reads the input and + writes the output directly from/to the specified input file and output + file. In this case the cipherText value of the result signal will always + be empty. This direct IO mode is currently only supported for OpenPGP. + Note that startIt() does not schedule the job's destruction if starting + the job failed. + After result() is emitted, the SignEncryptJob will schedule it's own destruction by calling QObject::deleteLater(). */ @@ -85,11 +94,63 @@ class QGPGME_EXPORT SignEncryptJob : public Job protected: explicit SignEncryptJob(QObject *parent); public: - ~SignEncryptJob(); + ~SignEncryptJob() override; + /** + * Sets the file name to embed in the encryption result. + * + * This is only used if one of the start() functions is used. + */ void setFileName(const QString &fileName); QString fileName() const; + /** + * Sets the keys to use for signing. + * + * Used if the job is started with startIt(). + */ + void setSigners(const std::vector &signers); + std::vector signers() const; + + /** + * Sets the keys to use for encryption. + * + * Used if the job is started with startIt(). + */ + void setRecipients(const std::vector &recipients); + std::vector recipients() const; + + /** + * Sets the path of the file to encrypt. + * + * Used if the job is started with startIt(). + */ + void setInputFile(const QString &path); + QString inputFile() const; + + /** + * Sets the path of the file to write the encryption result to. + * + * Used if the job is started with startIt(). + * + * \note If a file with this path exists, then the job will fail, i.e. you + * need to delete an existing file that shall be overwritten before you + * start the job. + */ + void setOutputFile(const QString &path); + QString outputFile() const; + + /** + * Sets the flags to use for encryption. + * + * Defaults to \c EncryptFile. + * + * Used if the job is started with startIt(). The \c EncryptFile flag is + * always assumed set. + */ + void setEncryptionFlags(GpgME::Context::EncryptionFlags flags); + GpgME::Context::EncryptionFlags encryptionFlags() const; + /** Starts the combined signing and encrypting operation. \a signers is the list of keys to sign \a plainText with. \a recipients is diff --git a/lang/qt/src/signencryptjob_p.h b/lang/qt/src/signencryptjob_p.h index 85afae26..600d752b 100644 --- a/lang/qt/src/signencryptjob_p.h +++ b/lang/qt/src/signencryptjob_p.h @@ -36,12 +36,22 @@ #include "job_p.h" +#include + namespace QGpgME { struct SignEncryptJobPrivate : public JobPrivate { + // used by start() functions QString m_fileName; + + // used by startIt() + std::vector m_signers; + std::vector m_recipients; + QString m_inputFilePath; + QString m_outputFilePath; + GpgME::Context::EncryptionFlags m_encryptionFlags = GpgME::Context::EncryptFile; }; } diff --git a/lang/qt/src/signjob.cpp b/lang/qt/src/signjob.cpp new file mode 100644 index 00000000..0a9c8651 --- /dev/null +++ b/lang/qt/src/signjob.cpp @@ -0,0 +1,98 @@ +/* + signjob.cpp + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2023 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "signjob.h" +#include "signjob_p.h" + +using namespace QGpgME; + +SignJob::SignJob(QObject *parent) + : Job{parent} +{ +} + +SignJob::~SignJob() = default; + +void SignJob::setSigners(const std::vector &signers) +{ + auto d = jobPrivate(this); + d->m_signers = signers; +} + +std::vector SignJob::signers() const +{ + auto d = jobPrivate(this); + return d->m_signers; +} + +void SignJob::setInputFile(const QString &path) +{ + auto d = jobPrivate(this); + d->m_inputFilePath = path; +} + +QString SignJob::inputFile() const +{ + auto d = jobPrivate(this); + return d->m_inputFilePath; +} + +void SignJob::setOutputFile(const QString &path) +{ + auto d = jobPrivate(this); + d->m_outputFilePath = path; +} + +QString SignJob::outputFile() const +{ + auto d = jobPrivate(this); + return d->m_outputFilePath; +} + +void SignJob::setSigningFlags(GpgME::SignatureMode flags) +{ + auto d = jobPrivate(this); + d->m_signingFlags = static_cast(flags | GpgME::SignFile); +} + +GpgME::SignatureMode SignJob::signingFlags() const +{ + auto d = jobPrivate(this); + return d->m_signingFlags; +} + +#include "signjob.moc" diff --git a/lang/qt/src/signjob.h b/lang/qt/src/signjob.h index c05231cc..273277b5 100644 --- a/lang/qt/src/signjob.h +++ b/lang/qt/src/signjob.h @@ -70,6 +70,15 @@ namespace QGpgME will have scheduled it's own destruction with a call to QObject::deleteLater(). + Alternatively, the job can be started with startIt() after setting + an input file and an output file and, optionally, signers or flags. + If the job is started this way then the backend reads the input and + writes the output directly from/to the specified input file and output + file. In this case the signature value of the result signal will always + be empty. This direct IO mode is currently only supported for OpenPGP. + Note that startIt() does not schedule the job's destruction if starting + the job failed. + After result() is emitted, the SignJob will schedule it's own destruction by calling QObject::deleteLater(). */ @@ -79,7 +88,46 @@ class QGPGME_EXPORT SignJob : public Job protected: explicit SignJob(QObject *parent); public: - ~SignJob(); + ~SignJob() override; + + /** + * Sets the keys to use for signing. + * + * Used if the job is started with startIt(). + */ + void setSigners(const std::vector &signers); + std::vector signers() const; + + /** + * Sets the path of the file to sign. + * + * Used if the job is started with startIt(). + */ + void setInputFile(const QString &path); + QString inputFile() const; + + /** + * Sets the path of the file to write the signing result to. + * + * Used if the job is started with startIt(). + * + * \note If a file with this path exists, then the job will fail, i.e. you + * need to delete an existing file that shall be overwritten before you + * start the job. + */ + void setOutputFile(const QString &path); + QString outputFile() const; + + /** + * Sets the flags to use for signing. + * + * Defaults to \c SignFile. + * + * Used if the job is started with startIt(). The \c SignFile flag is + * always assumed set. + */ + void setSigningFlags(GpgME::SignatureMode flags); + GpgME::SignatureMode signingFlags() const; /** Starts the signing operation. \a signers is the list of keys to diff --git a/lang/qt/src/signjob_p.h b/lang/qt/src/signjob_p.h new file mode 100644 index 00000000..75309782 --- /dev/null +++ b/lang/qt/src/signjob_p.h @@ -0,0 +1,55 @@ +/* + signjob_p.h + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2023 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __QGPGME_SIGNJOB_P_H__ +#define __QGPGME_SIGNJOB_P_H__ + +#include "job_p.h" + +#include + +namespace QGpgME +{ + +struct SignJobPrivate : public JobPrivate +{ + // used by startIt() + std::vector m_signers; + QString m_inputFilePath; + QString m_outputFilePath; + GpgME::SignatureMode m_signingFlags = GpgME::SignFile; +}; + +} + +#endif // __QGPGME_SIGNJOB_P_H__ diff --git a/lang/qt/tests/Makefile.am b/lang/qt/tests/Makefile.am index 615daf6d..0007401c 100644 --- a/lang/qt/tests/Makefile.am +++ b/lang/qt/tests/Makefile.am @@ -91,12 +91,14 @@ t_revokekey_SOURCES = t-revokekey.cpp $(support_src) t_setprimaryuserid_SOURCES = t-setprimaryuserid.cpp $(support_src) run_decryptverifyarchivejob_SOURCES = run-decryptverifyarchivejob.cpp run_encryptarchivejob_SOURCES = run-encryptarchivejob.cpp +run_encryptjob_SOURCES = run-encryptjob.cpp run_exportjob_SOURCES = run-exportjob.cpp run_importjob_SOURCES = run-importjob.cpp run_keyformailboxjob_SOURCES = run-keyformailboxjob.cpp run_receivekeysjob_SOURCES = run-receivekeysjob.cpp run_refreshkeysjob_SOURCES = run-refreshkeysjob.cpp run_signarchivejob_SOURCES = run-signarchivejob.cpp +run_signjob_SOURCES = run-signjob.cpp run_wkdrefreshjob_SOURCES = run-wkdrefreshjob.cpp nodist_t_keylist_SOURCES = $(moc_files) @@ -112,8 +114,10 @@ noinst_PROGRAMS = \ t-setprimaryuserid \ run-decryptverifyarchivejob \ run-encryptarchivejob \ + run-encryptjob \ run-importjob run-exportjob run-receivekeysjob run-refreshkeysjob \ run-signarchivejob \ + run-signjob \ run-wkdrefreshjob diff --git a/lang/qt/tests/run-encryptjob.cpp b/lang/qt/tests/run-encryptjob.cpp new file mode 100644 index 00000000..c3d6c9ee --- /dev/null +++ b/lang/qt/tests/run-encryptjob.cpp @@ -0,0 +1,198 @@ +/* + run-encryptjob.cpp + + This file is part of QGpgME's test suite. + Copyright (c) 2023 by g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License, + version 2, as published by the Free Software Foundation. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace GpgME; + +std::ostream &operator<<(std::ostream &os, const QString &s) +{ + return os << s.toLocal8Bit().constData(); +} + +struct CommandLineOptions { + bool armor = false; + bool sign = false; + QString inputFile; + QString outputFile; + std::chrono::seconds cancelTimeout{0}; +}; + +CommandLineOptions parseCommandLine(const QStringList &arguments) +{ + CommandLineOptions options; + + QCommandLineParser parser; + parser.setApplicationDescription("Test program for EncryptJob and SignEncryptJob"); + parser.addHelpOption(); + parser.addOptions({ + {{"s", "sign"}, "Sign file before encryption."}, + {{"o", "output"}, "Write output to FILE.", "FILE"}, + {{"a", "armor"}, "Create ASCII armored output."}, + {"cancel-after", "Cancel the running job after SECONDS seconds.", "SECONDS"}, + }); + parser.addPositionalArgument("file", "File to encrypt", "FILE"); + + parser.process(arguments); + + const auto args = parser.positionalArguments(); + if (args.size() != 1) { + parser.showHelp(1); + } + + options.armor = parser.isSet("armor"); + options.sign = parser.isSet("sign"); + options.inputFile = args.front(); + options.outputFile = parser.value("output"); + if (parser.isSet("cancel-after")) { + bool ok; + options.cancelTimeout = std::chrono::seconds{parser.value("cancel-after").toInt(&ok)}; + if (!ok) { + options.cancelTimeout = std::chrono::seconds{-1}; + } + } + + return options; +} + +int main(int argc, char **argv) +{ + GpgME::initializeLibrary(); + + QCoreApplication app{argc, argv}; + app.setApplicationName("run-encryptjob"); + + const auto options = parseCommandLine(app.arguments()); + if (options.cancelTimeout.count() < 0) { + std::cerr << "Ignoring invalid timeout for cancel." << std::endl; + } + + std::shared_ptr output; + if (options.outputFile.isEmpty() || options.outputFile == QLatin1String{"-"}) { + output.reset(new QFile); + output->open(stdout, QIODevice::WriteOnly); + } else { + if (QFile::exists(options.outputFile)) { + qCritical() << "File" << options.outputFile << "exists. Bailing out."; + return 1; + } + } + + std::shared_ptr input; + + if (options.sign) { + auto job = QGpgME::openpgp()->signEncryptJob(options.armor); + if (!job) { + std::cerr << "Error: Could not create job" << std::endl; + return 1; + } + QObject::connect(job, &QGpgME::SignEncryptJob::result, &app, [](const GpgME::SigningResult &signingResult, const GpgME::EncryptionResult &encryptionResult, const QByteArray &, const QString &auditLog, const GpgME::Error &) { + std::cerr << "Diagnostics: " << auditLog << std::endl; + std::cerr << "Signing Result: " << signingResult << std::endl; + std::cerr << "Encryption Result: " << encryptionResult << std::endl; + qApp->quit(); + }); + if (options.cancelTimeout.count() > 0) { + QTimer::singleShot(options.cancelTimeout, job, [job]() { + std::cerr << "Canceling job" << std::endl; + job->slotCancel(); + }); + } + + GpgME::Error err; + if (output) { + input.reset(new QFile{options.inputFile}); + input->open(QIODevice::ReadOnly); + job->start({}, {}, input, output, GpgME::Context::None); + } else { + job->setInputFile(options.inputFile); + job->setOutputFile(options.outputFile); + err = job->startIt(); + } + if (err) { + std::cerr << "Error: Starting the job failed: " << err.asString() << std::endl; + return 1; + } + } else { + auto job = QGpgME::openpgp()->encryptJob(options.armor); + if (!job) { + std::cerr << "Error: Could not create job" << std::endl; + return 1; + } + QObject::connect(job, &QGpgME::EncryptJob::result, &app, [](const GpgME::EncryptionResult &result, const QByteArray &, const QString &auditLog, const GpgME::Error &) { + std::cerr << "Diagnostics: " << auditLog << std::endl; + std::cerr << "Result: " << result << std::endl; + qApp->quit(); + }); + if (options.cancelTimeout.count() > 0) { + QTimer::singleShot(options.cancelTimeout, job, [job]() { + std::cerr << "Canceling job" << std::endl; + job->slotCancel(); + }); + } + + GpgME::Error err; + if (output) { + input.reset(new QFile{options.inputFile}); + input->open(QIODevice::ReadOnly); + job->start({}, input, output, GpgME::Context::None); + } else { + job->setInputFile(options.inputFile); + job->setOutputFile(options.outputFile); + err = job->startIt(); + } + if (err) { + std::cerr << "Error: Starting the job failed: " << err.asString() << std::endl; + return 1; + } + } + + return app.exec(); +} diff --git a/lang/qt/tests/run-signjob.cpp b/lang/qt/tests/run-signjob.cpp new file mode 100644 index 00000000..14b0a406 --- /dev/null +++ b/lang/qt/tests/run-signjob.cpp @@ -0,0 +1,157 @@ +/* + run-signjob.cpp + + This file is part of QGpgME's test suite. + Copyright (c) 2023 by g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License, + version 2, as published by the Free Software Foundation. + + QGpgME is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace GpgME; + +std::ostream &operator<<(std::ostream &os, const QString &s) +{ + return os << s.toLocal8Bit().constData(); +} + +struct CommandLineOptions { + bool armor; + QString inputFile; + QString outputFile; + std::chrono::seconds cancelTimeout{0}; +}; + +CommandLineOptions parseCommandLine(const QStringList &arguments) +{ + CommandLineOptions options; + + QCommandLineParser parser; + parser.setApplicationDescription("Test program for SignJob"); + parser.addHelpOption(); + parser.addOptions({ + {{"o", "output"}, "Write output to FILE.", "FILE"}, + {{"a", "armor"}, "Create ASCII armored output."}, + {"cancel-after", "Cancel the running job after SECONDS seconds.", "SECONDS"}, + }); + parser.addPositionalArgument("file", "File to sign", "FILE"); + + parser.process(arguments); + + const auto args = parser.positionalArguments(); + if (args.size() != 1) { + parser.showHelp(1); + } + + options.armor = parser.isSet("armor"); + options.inputFile = args.front(); + options.outputFile = parser.value("output"); + if (parser.isSet("cancel-after")) { + bool ok; + options.cancelTimeout = std::chrono::seconds{parser.value("cancel-after").toInt(&ok)}; + if (!ok) { + options.cancelTimeout = std::chrono::seconds{-1}; + } + } + + return options; +} + +int main(int argc, char **argv) +{ + GpgME::initializeLibrary(); + + QCoreApplication app{argc, argv}; + app.setApplicationName("run-signjob"); + + const auto options = parseCommandLine(app.arguments()); + if (options.cancelTimeout.count() < 0) { + std::cerr << "Ignoring invalid timeout for cancel." << std::endl; + } + + std::shared_ptr output; + if (options.outputFile.isEmpty() || options.outputFile == QLatin1String{"-"}) { + output.reset(new QFile); + output->open(stdout, QIODevice::WriteOnly); + } else { + if (QFile::exists(options.outputFile)) { + qCritical() << "File" << options.outputFile << "exists. Bailing out."; + return 1; + } + } + + auto job = QGpgME::openpgp()->signJob(options.armor); + if (!job) { + std::cerr << "Error: Could not create job" << std::endl; + return 1; + } + QObject::connect(job, &QGpgME::SignJob::result, &app, [](const GpgME::SigningResult &result, const QByteArray &, const QString &auditLog, const GpgME::Error &) { + std::cerr << "Diagnostics: " << auditLog << std::endl; + std::cerr << "Result: " << result << std::endl; + qApp->quit(); + }); + if (options.cancelTimeout.count() > 0) { + QTimer::singleShot(options.cancelTimeout, job, [job]() { + std::cerr << "Canceling job" << std::endl; + job->slotCancel(); + }); + } + + std::shared_ptr input; + GpgME::Error err; + if (output) { + input.reset(new QFile{options.inputFile}); + input->open(QIODevice::ReadOnly); + job->start({}, input, output, GpgME::NormalSignatureMode); + } else { + job->setInputFile(options.inputFile); + job->setOutputFile(options.outputFile); + err = job->startIt(); + } + if (err) { + std::cerr << "Error: Starting the job failed: " << err.asString() << std::endl; + return 1; + } + + return app.exec(); +}