qt: Allow appending a detached signature to an existing file

* lang/qt/src/qgpgmesignjob.cpp (sign_to_filename): Add argument
"appendSignature". Append new detached signature to an existing file if
requested.
* lang/qt/src/signjob.cpp, lang/qt/src/signjob.h (class SignJob): Add
member functions setAppendSignature, appendSignatureEnabled.
* lang/qt/src/signjob_p.h (struct SignJobPrivate): Add member
m_appendSignature.

* lang/qt/tests/run-signjob.cpp (struct CommandLineOptions): Add members
signingFlags, appendSignature. Initialize armor.
(parseCommandLine): Add command line options --detach-sign and --append.
(main): Do not exit if output file exists and append is enabled.
Pass new options to the job.
--

This change simplifies cross-signing a document by appending additional
detached signatures to a file with already existing detached signatures.

GnuPG-bug-id: 6867
This commit is contained in:
Ingo Klöcker 2024-06-18 16:36:40 +02:00
parent 09827ffc77
commit 7d5df0bf0d
No known key found for this signature in database
GPG Key ID: F5A5D1692277A1E9
6 changed files with 79 additions and 10 deletions

4
NEWS
View File

@ -24,6 +24,8 @@ Noteworthy changes in version 1.24.0 (unrelease)
* qt: Allow specifying import options when importing keys. [T7152] * qt: Allow specifying import options when importing keys. [T7152]
* qt: Allow appending a detached signature to an existing file. [T6867]
* Interface changes relative to the 1.23.2 release: * Interface changes relative to the 1.23.2 release:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
GPGME_ENCRYPT_FILE NEW. GPGME_ENCRYPT_FILE NEW.
@ -67,6 +69,8 @@ Noteworthy changes in version 1.24.0 (unrelease)
qt: SignJob::outputFile NEW. qt: SignJob::outputFile NEW.
qt: SignJob::setSigningFlags NEW. qt: SignJob::setSigningFlags NEW.
qt: SignJob::signingFlags NEW. qt: SignJob::signingFlags NEW.
qt: SignJob::setAppendSignature NEW.
qt: SignJob::appendSignatureEnabled NEW.
qt: VerifyDetachedJob::setSignatureFile NEW. qt: VerifyDetachedJob::setSignatureFile NEW.
qt: VerifyDetachedJob::signatureFile NEW. qt: VerifyDetachedJob::signatureFile NEW.
qt: VerifyDetachedJob::setSignedFile NEW. qt: VerifyDetachedJob::setSignedFile NEW.

View File

@ -170,7 +170,8 @@ static QGpgMESignJob::result_type sign_to_filename(Context *ctx,
const std::vector<Key> &signers, const std::vector<Key> &signers,
const QString &inputFilePath, const QString &inputFilePath,
const QString &outputFilePath, const QString &outputFilePath,
SignatureMode flags) SignatureMode flags,
bool appendSignature)
{ {
Data indata; Data indata;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -206,13 +207,42 @@ static QGpgMESignJob::result_type sign_to_filename(Context *ctx,
flags = static_cast<SignatureMode>(flags | SignFile); flags = static_cast<SignatureMode>(flags | SignFile);
const auto signingResult = ctx->sign(indata, outdata, flags); 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; Error ae;
const QString log = _detail::audit_log_as_html(ctx, ae); const QString log = _detail::audit_log_as_html(ctx, ae);
if (!signingResult.error().code()) {
// the operation succeeded
const bool appendSignatureToExistingFile = appendSignature && (flags & Detached) && QFile::exists(outputFilePath);
if (appendSignatureToExistingFile) {
// append the result to the existing file
QFile newSignatureFile{partFileGuard.tempFileName()};
if (!newSignatureFile.open(QIODevice::ReadOnly)) {
qCDebug(QGPGME_LOG) << "Failed to open detached signature file" << newSignatureFile.fileName() << "(" << newSignatureFile.errorString() << ")";
return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_GENERAL)}, QByteArray{}, log, ae);
}
const QByteArray newSigData = newSignatureFile.readAll();
if (newSigData.isEmpty()) {
qCDebug(QGPGME_LOG) << "Failed to read detached signature from file" << newSignatureFile.fileName() << "(" << newSignatureFile.errorString() << ")";
return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_GENERAL)}, QByteArray{}, log, ae);
}
newSignatureFile.close();
QFile existingSignatureFile{outputFilePath};
if (!existingSignatureFile.open(QIODevice::WriteOnly | QIODevice::Append)) {
qCDebug(QGPGME_LOG) << "Failed to open existing detached signature file for appending" << existingSignatureFile.fileName() << "(" << existingSignatureFile.errorString() << ")";
return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_GENERAL)}, QByteArray{}, log, ae);
}
const auto bytesWritten = existingSignatureFile.write(newSigData);
if (bytesWritten != newSigData.size()) {
qCDebug(QGPGME_LOG) << "Failed to write new signature to existing detached signature file" << existingSignatureFile.fileName() << "(" << existingSignatureFile.errorString() << ")";
return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_GENERAL)}, QByteArray{}, log, ae);
}
} else {
// save the result under the requested file name
partFileGuard.commit();
}
}
return std::make_tuple(signingResult, QByteArray{}, log, ae); return std::make_tuple(signingResult, QByteArray{}, log, ae);
} }
@ -241,7 +271,7 @@ GpgME::Error QGpgMESignJobPrivate::startIt()
} }
q->run([=](Context *ctx) { q->run([=](Context *ctx) {
return sign_to_filename(ctx, m_signers, m_inputFilePath, m_outputFilePath, m_signingFlags); return sign_to_filename(ctx, m_signers, m_inputFilePath, m_outputFilePath, m_signingFlags, m_appendSignature);
}); });
return {}; return {};

View File

@ -95,4 +95,16 @@ GpgME::SignatureMode SignJob::signingFlags() const
return d->m_signingFlags; return d->m_signingFlags;
} }
void SignJob::setAppendSignature(bool append)
{
auto d = jobPrivate<SignJobPrivate>(this);
d->m_appendSignature = append;
}
bool SignJob::appendSignatureEnabled() const
{
auto d = jobPrivate<SignJobPrivate>(this);
return d->m_appendSignature;
}
#include "signjob.moc" #include "signjob.moc"

View File

@ -113,7 +113,8 @@ public:
* *
* \note If a file with this path exists, then the job will fail, i.e. you * \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 * need to delete an existing file that shall be overwritten before you
* start the job. * start the job. If you create a detached signature then you can tell
* the job to append the new detached signature to an existing file.
*/ */
void setOutputFile(const QString &path); void setOutputFile(const QString &path);
QString outputFile() const; QString outputFile() const;
@ -129,6 +130,17 @@ public:
void setSigningFlags(GpgME::SignatureMode flags); void setSigningFlags(GpgME::SignatureMode flags);
GpgME::SignatureMode signingFlags() const; GpgME::SignatureMode signingFlags() const;
/**
* If @c true then a new detached signature is appended to an already
* existing detached signature.
*
* Defaults to \c false.
*
* Used if the job is started with startIt().
*/
void setAppendSignature(bool append);
bool appendSignatureEnabled() const;
/** /**
Starts the signing operation. \a signers is the list of keys to Starts the signing operation. \a signers is the list of keys to
sign \a plainText with. Empty (null) keys are ignored. sign \a plainText with. Empty (null) keys are ignored.

View File

@ -48,6 +48,7 @@ struct SignJobPrivate : public JobPrivate
QString m_inputFilePath; QString m_inputFilePath;
QString m_outputFilePath; QString m_outputFilePath;
GpgME::SignatureMode m_signingFlags = GpgME::SignFile; GpgME::SignatureMode m_signingFlags = GpgME::SignFile;
bool m_appendSignature = false;
}; };
} }

View File

@ -56,7 +56,9 @@ std::ostream &operator<<(std::ostream &os, const QString &s)
} }
struct CommandLineOptions { struct CommandLineOptions {
bool armor; GpgME::SignatureMode signingFlags = GpgME::NormalSignatureMode;
bool armor = false;
bool appendSignature = false;
QString inputFile; QString inputFile;
QString outputFile; QString outputFile;
std::chrono::seconds cancelTimeout{0}; std::chrono::seconds cancelTimeout{0};
@ -72,6 +74,8 @@ CommandLineOptions parseCommandLine(const QStringList &arguments)
parser.addOptions({ parser.addOptions({
{{"o", "output"}, "Write output to FILE.", "FILE"}, {{"o", "output"}, "Write output to FILE.", "FILE"},
{{"a", "armor"}, "Create ASCII armored output."}, {{"a", "armor"}, "Create ASCII armored output."},
{{"b", "detach-sign"}, "Create a detached signature."},
{"append", "Append new (detached) signature to existing file."},
{"cancel-after", "Cancel the running job after SECONDS seconds.", "SECONDS"}, {"cancel-after", "Cancel the running job after SECONDS seconds.", "SECONDS"},
}); });
parser.addPositionalArgument("file", "File to sign", "FILE"); parser.addPositionalArgument("file", "File to sign", "FILE");
@ -84,6 +88,10 @@ CommandLineOptions parseCommandLine(const QStringList &arguments)
} }
options.armor = parser.isSet("armor"); options.armor = parser.isSet("armor");
if (parser.isSet("detach-sign")) {
options.signingFlags = GpgME::Detached;
options.appendSignature = parser.isSet("append");
}
options.inputFile = args.front(); options.inputFile = args.front();
options.outputFile = parser.value("output"); options.outputFile = parser.value("output");
if (parser.isSet("cancel-after")) { if (parser.isSet("cancel-after")) {
@ -114,7 +122,7 @@ int main(int argc, char **argv)
output.reset(new QFile); output.reset(new QFile);
output->open(stdout, QIODevice::WriteOnly); output->open(stdout, QIODevice::WriteOnly);
} else { } else {
if (QFile::exists(options.outputFile)) { if (QFile::exists(options.outputFile) && !options.appendSignature) {
qCritical() << "File" << options.outputFile << "exists. Bailing out."; qCritical() << "File" << options.outputFile << "exists. Bailing out.";
return 1; return 1;
} }
@ -146,6 +154,8 @@ int main(int argc, char **argv)
} else { } else {
job->setInputFile(options.inputFile); job->setInputFile(options.inputFile);
job->setOutputFile(options.outputFile); job->setOutputFile(options.outputFile);
job->setSigningFlags(options.signingFlags);
job->setAppendSignature(options.appendSignature);
err = job->startIt(); err = job->startIt();
} }
if (err) { if (err) {