qt: Support writing/reading signed/encrypted files directly to/from file

* lang/qt/src/Makefile.am: Add new files.
* lang/qt/src/job.cpp (EncryptJob, SignJob, SignEncryptJob): Move
definition of constructor and destructor and inclusion of the moc file
to the corresponding .cpp files.
* lang/qt/src/encryptjob.cpp (EncryptJob): Define constructor. Define
destructor as default. Include moc file.
* lang/qt/src/encryptjob.cpp, lang/qt/src/encryptjob.h (EncryptJob):
Add member functions setRecipients, recipients, setInputFile, inputFile,
setOutputFile, outputFile, setEncryptionFlags, encryptionFlags.
* lang/qt/src/encryptjob_p.h (EncryptJobPrivate): Add members
m_recipients, m_inputFilePath, m_outputFilePath, m_encryptionFlags.
* lang/qt/src/qgpgmeencryptjob.cpp (encrypt_to_filename): New.
(QGpgMEEncryptJobPrivate::startIt): Start the job with the values
from the member variables.
* lang/qt/src/qgpgmesignencryptjob.cpp (sign_encrypt_to_filename): New.
(QGpgMESignEncryptJobPrivate::startIt): Start the job with the values
from the member variables.
* lang/qt/src/qgpgmesignjob.cpp (class QGpgMESignJobPrivate): New.
(QGpgMESignJob::QGpgMESignJob): Instantiate private job class.
(sign_to_filename): New.
* lang/qt/src/signencryptjob.cpp (SignEncryptJob): Define constructor.
Define destructor as default. Include moc file.
* lang/qt/src/signencryptjob.cpp, lang/qt/src/signencryptjob.h
(SignEncryptJob): Add member functions setSigners, signers,
setRecipients, recipients, setInputFile, inputFile, setOutputFile,
outputFile, setEncryptionFlags, encryptionFlags.
* lang/qt/src/signencryptjob_p.h (SignEncryptJobPrivate): Add members
m_signers, m_recipients, m_inputFilePath, m_outputFilePath,
m_encryptionFlags.
* lang/qt/src/signjob.cpp: New.
* lang/qt/src/signjob.h (SignJob): Add member functions setSigners,
signers, setInputFile, inputFile, setOutputFile, outputFile,
setSigningFlags, signingFlags.
* lang/qt/src/signjob_p.h: New.
* lang/qt/tests/Makefile.am: Add new test programs.
* lang/qt/tests/run-encryptjob.cpp: New.
* lang/qt/tests/run-signjob.cpp: New.
--

This makes it possible to tell gpg to read the input and write the
output directly to a specified file bypassing GpgME's Data IO when
signing and/or encrypting a file.

GnuPG-bug-id: 6550
This commit is contained in:
Ingo Klöcker 2023-12-21 09:38:52 +01:00
parent b166958b86
commit a44d84772d
No known key found for this signature in database
GPG Key ID: F5A5D1692277A1E9
17 changed files with 1058 additions and 38 deletions

View File

@ -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

View File

@ -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<EncryptJobPrivate>(this);
@ -63,3 +70,53 @@ GpgME::Data::Encoding EncryptJob::inputEncoding() const
auto d = jobPrivate<EncryptJobPrivate>(this);
return d->m_inputEncoding;
}
void EncryptJob::setRecipients(const std::vector<GpgME::Key> &recipients)
{
auto d = jobPrivate<EncryptJobPrivate>(this);
d->m_recipients = recipients;
}
std::vector<GpgME::Key> EncryptJob::recipients() const
{
auto d = jobPrivate<EncryptJobPrivate>(this);
return d->m_recipients;
}
void EncryptJob::setInputFile(const QString &path)
{
auto d = jobPrivate<EncryptJobPrivate>(this);
d->m_inputFilePath = path;
}
QString EncryptJob::inputFile() const
{
auto d = jobPrivate<EncryptJobPrivate>(this);
return d->m_inputFilePath;
}
void EncryptJob::setOutputFile(const QString &path)
{
auto d = jobPrivate<EncryptJobPrivate>(this);
d->m_outputFilePath = path;
}
QString EncryptJob::outputFile() const
{
auto d = jobPrivate<EncryptJobPrivate>(this);
return d->m_outputFilePath;
}
void EncryptJob::setEncryptionFlags(GpgME::Context::EncryptionFlags flags)
{
auto d = jobPrivate<EncryptJobPrivate>(this);
d->m_encryptionFlags = static_cast<GpgME::Context::EncryptionFlags>(flags | GpgME::Context::EncryptFile);
}
GpgME::Context::EncryptionFlags EncryptJob::encryptionFlags() const
{
auto d = jobPrivate<EncryptJobPrivate>(this);
return d->m_encryptionFlags;
}
#include "encryptjob.moc"

View File

@ -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<GpgME::Key> &recipients);
std::vector<GpgME::Key> 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

View File

@ -35,15 +35,24 @@
#define __QGPGME_ENCRYPTJOB_P_H__
#include "job_p.h"
#include "data.h"
#include <data.h>
#include <key.h>
namespace QGpgME
{
struct EncryptJobPrivate : public JobPrivate
{
// used by start() functions
QString m_fileName;
GpgME::Data::Encoding m_inputEncoding;
// used by startIt()
std::vector<GpgME::Key> m_recipients;
QString m_inputFilePath;
QString m_outputFilePath;
GpgME::Context::EncryptionFlags m_encryptionFlags = GpgME::Context::EncryptFile;
};
}

View File

@ -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"

View File

@ -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 <context.h>
#include <data.h>
#include <encryptionresult.h>
#include <QBuffer>
#include <QFileInfo>
@ -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<QIODevice>(), eflags, outputIsBsse64Encoded, inputEncoding, fileName);
}
static QGpgMEEncryptJob::result_type encrypt_to_filename(Context *ctx,
const std::vector<Key> &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<Context::EncryptionFlags>(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<Key> &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"

View File

@ -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 <context.h>
#include <data.h>
#include <exception.h>
#include <key.h>
#include <QBuffer>
#include <QFileInfo>
@ -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<QIODevice>(), eflags, outputIsBsse64Encoded, fileName);
}
static QGpgMESignEncryptJob::result_type sign_encrypt_to_filename(Context *ctx,
const std::vector<Key> &signers,
const std::vector<Key> &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<Context::EncryptionFlags>(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<Key> &signers, const std::vector<Key> &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"

View File

@ -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 <context.h>
#include <data.h>
#include <signingresult.h>
#include <QBuffer>
#include <QFile>
#include <cassert>
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<QGpgMESignJobPrivate>{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<QIODevice>(), mode, outputIsBsse64Encoded);
}
static QGpgMESignJob::result_type sign_to_filename(Context *ctx,
const std::vector<Key> &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<SignatureMode>(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<Key> &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"

View File

@ -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<SignEncryptJobPrivate>(this);
@ -51,3 +58,65 @@ QString SignEncryptJob::fileName() const
auto d = jobPrivate<SignEncryptJobPrivate>(this);
return d->m_fileName;
}
void SignEncryptJob::setSigners(const std::vector<GpgME::Key> &signers)
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
d->m_signers = signers;
}
std::vector<GpgME::Key> SignEncryptJob::signers() const
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
return d->m_signers;
}
void SignEncryptJob::setRecipients(const std::vector<GpgME::Key> &recipients)
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
d->m_recipients = recipients;
}
std::vector<GpgME::Key> SignEncryptJob::recipients() const
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
return d->m_recipients;
}
void SignEncryptJob::setInputFile(const QString &path)
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
d->m_inputFilePath = path;
}
QString SignEncryptJob::inputFile() const
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
return d->m_inputFilePath;
}
void SignEncryptJob::setOutputFile(const QString &path)
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
d->m_outputFilePath = path;
}
QString SignEncryptJob::outputFile() const
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
return d->m_outputFilePath;
}
void SignEncryptJob::setEncryptionFlags(GpgME::Context::EncryptionFlags flags)
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
d->m_encryptionFlags = static_cast<GpgME::Context::EncryptionFlags>(flags | GpgME::Context::EncryptFile);
}
GpgME::Context::EncryptionFlags SignEncryptJob::encryptionFlags() const
{
auto d = jobPrivate<SignEncryptJobPrivate>(this);
return d->m_encryptionFlags;
}
#include "signencryptjob.moc"

View File

@ -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<GpgME::Key> &signers);
std::vector<GpgME::Key> signers() const;
/**
* Sets the keys to use for encryption.
*
* Used if the job is started with startIt().
*/
void setRecipients(const std::vector<GpgME::Key> &recipients);
std::vector<GpgME::Key> 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

View File

@ -36,12 +36,22 @@
#include "job_p.h"
#include <key.h>
namespace QGpgME
{
struct SignEncryptJobPrivate : public JobPrivate
{
// used by start() functions
QString m_fileName;
// used by startIt()
std::vector<GpgME::Key> m_signers;
std::vector<GpgME::Key> m_recipients;
QString m_inputFilePath;
QString m_outputFilePath;
GpgME::Context::EncryptionFlags m_encryptionFlags = GpgME::Context::EncryptFile;
};
}

98
lang/qt/src/signjob.cpp Normal file
View File

@ -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 <dev@ingo-kloecker.de>
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<GpgME::Key> &signers)
{
auto d = jobPrivate<SignJobPrivate>(this);
d->m_signers = signers;
}
std::vector<GpgME::Key> SignJob::signers() const
{
auto d = jobPrivate<SignJobPrivate>(this);
return d->m_signers;
}
void SignJob::setInputFile(const QString &path)
{
auto d = jobPrivate<SignJobPrivate>(this);
d->m_inputFilePath = path;
}
QString SignJob::inputFile() const
{
auto d = jobPrivate<SignJobPrivate>(this);
return d->m_inputFilePath;
}
void SignJob::setOutputFile(const QString &path)
{
auto d = jobPrivate<SignJobPrivate>(this);
d->m_outputFilePath = path;
}
QString SignJob::outputFile() const
{
auto d = jobPrivate<SignJobPrivate>(this);
return d->m_outputFilePath;
}
void SignJob::setSigningFlags(GpgME::SignatureMode flags)
{
auto d = jobPrivate<SignJobPrivate>(this);
d->m_signingFlags = static_cast<GpgME::SignatureMode>(flags | GpgME::SignFile);
}
GpgME::SignatureMode SignJob::signingFlags() const
{
auto d = jobPrivate<SignJobPrivate>(this);
return d->m_signingFlags;
}
#include "signjob.moc"

View File

@ -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<GpgME::Key> &signers);
std::vector<GpgME::Key> 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

55
lang/qt/src/signjob_p.h Normal file
View File

@ -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 <dev@ingo-kloecker.de>
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 <key.h>
namespace QGpgME
{
struct SignJobPrivate : public JobPrivate
{
// used by startIt()
std::vector<GpgME::Key> m_signers;
QString m_inputFilePath;
QString m_outputFilePath;
GpgME::SignatureMode m_signingFlags = GpgME::SignFile;
};
}
#endif // __QGPGME_SIGNJOB_P_H__

View File

@ -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

View File

@ -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 <dev@ingo-kloecker.de>
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 <protocol.h>
#include <encryptjob.h>
#include <signencryptjob.h>
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QTimer>
#include <context.h>
#include <encryptionresult.h>
#include <signingresult.h>
#include <iostream>
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<QFile> 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<QFile> 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();
}

View File

@ -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 <dev@ingo-kloecker.de>
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 <protocol.h>
#include <signjob.h>
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QTimer>
#include <context.h>
#include <signingresult.h>
#include <iostream>
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<QFile> 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<QFile> 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();
}