11#include <QCoreApplication>
13#include <QRegularExpression>
14#include <QTemporaryDir>
21 :
m_gpg(std::move(gpgExecutable)),
m_keys(std::move(signingKeys)),
24 m_exec = [](
const QString &app,
const QStringList &args,
25 const QString &input, QString *out, QString *err) {
33 QStringList
keys = passSigningKey.split(QLatin1Char(
' '), Qt::SkipEmptyParts);
36 for (QString &key :
keys) {
43 const QString &input)
const ->
int {
53 const QString &key =
m_keys.first();
55 const int rc =
run({QStringLiteral(
"--status-fd=1"),
56 QStringLiteral(
"--list-secret-keys"), key},
59 qCDebug(lcQtPass) <<
"GPG list-secret-keys failed with code:" << rc;
62 return out.contains(QStringLiteral(
"[GNUPG:] KEY_CONSIDERED ") + key);
66 QString *error)
const ->
bool {
72 <<
"Multiple signing keys configured; using only the first key:"
76 const QString text = QString::fromUtf8(contents);
77 if (text.toUtf8() != contents) {
79 *error = QCoreApplication::translate(
80 "GpgIdSigner",
"the recipient list is not valid UTF-8");
83 QTemporaryDir scratch;
84 if (!scratch.isValid()) {
86 *error = scratch.errorString();
89 const QString output = scratch.filePath(QStringLiteral(
"sig"));
94 {QStringLiteral(
"--local-user"),
m_keys.first(), QStringLiteral(
"--yes"),
95 QStringLiteral(
"--detach-sign"), QStringLiteral(
"--output"),
97 nullptr, error, text);
99 qCDebug(lcQtPass) <<
"GPG signing failed with code:" << rc;
102 QFile signature(output);
103 if (!signature.open(QIODevice::ReadOnly) || signature.size() <= 0) {
106 QCoreApplication::translate(
"GpgIdSigner",
"gpg wrote no signature");
109 const QByteArray bytes = signature.readAll();
114 if (!
verify(contents, output)) {
116 *error = QCoreApplication::translate(
118 "the new signature does not verify as one of the signing keys");
127 static const QRegularExpression re(
129 R
"(^\[GNUPG:\] VALIDSIG ([A-F0-9]{40}(?:[A-F0-9]{24})?) .* )"
130 R"(([A-F0-9]{40}(?:[A-F0-9]{24})?)\r?$)",
131 QRegularExpression::MultilineOption);
138 static const QRegularExpression revoked(
139 QStringLiteral(R
"(^\[GNUPG:\] (?:REVKEYSIG|KEYREVOKED)\b)"),
140 QRegularExpression::MultilineOption);
141 static const QRegularExpression newSig(
142 QStringLiteral(R
"(^\[GNUPG:\] NEWSIG\b)"),
143 QRegularExpression::MultilineOption);
144 const QRegularExpressionMatch m = re.match(statusOutput);
148 const qsizetype from =
149 statusOutput.lastIndexOf(newSig, m.capturedStart()) < 0
151 : statusOutput.lastIndexOf(newSig, m.capturedStart());
152 const qsizetype to = statusOutput.indexOf(newSig, m.capturedEnd());
153 const QString block = statusOutput.mid(from, to < 0 ? -1 : to - from);
154 if (revoked.match(block).hasMatch()) {
157 return {m.captured(1), m.captured(2)};
161 const QString &signatureFile)
const ->
bool {
169 const QString text = QString::fromUtf8(contents);
170 if (text.toUtf8() != contents) {
171 qCDebug(lcQtPass) <<
"Refusing to verify" << signatureFile
172 <<
": the signed file is not valid UTF-8";
178 qCDebug(lcQtPass) <<
"Refusing to verify" << signatureFile
179 <<
": the signature file is a link";
189 qCDebug(lcQtPass) <<
"Refusing to verify" << signatureFile
190 <<
": not a regular file of at most"
199 run({QStringLiteral(
"--verify"), QStringLiteral(
"--status-fd=1"),
200 QStringLiteral(
"--"), sig, QStringLiteral(
"-")},
201 &out,
nullptr, text);
203 qCDebug(lcQtPass) <<
"GPG verify failed with code:" << rc;
207 for (
const QString &key :
m_keys) {
208 if (fingerprints.contains(key)) {
216 QByteArray *contents)
const ->
bool {
225 std::optional<QByteArray> bytes =
231 *contents = *std::move(bytes);
232 return verify(*contents, gpgIdFile + QStringLiteral(
".sig"));
static auto executeBlocking(const QString &app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Run a command synchronously and return its exit code.
static auto translatePathForWsl(const QString &path, const QString &exe) -> QString
Translate a native path for a WSL-routed executable.
static auto validSigFingerprints(const QString &statusOutput) -> QStringList
Pick the fingerprints out of gpg's --status-fd VALIDSIG line.
Exec m_exec
How gpg is run; replaced by a fake in the unit tests.
auto enabled() const -> bool
Whether any signing key is configured at all.
auto run(const QStringList &args, QString *out=nullptr, QString *err=nullptr, const QString &input=QString()) const -> int
Run gpg with args through m_exec.
auto sign(const QString &gpgIdFile, const QByteArray &contents, QString *error=nullptr) const -> bool
Write <file>.sig, a detached signature by the first key over contents, the bytes the caller just wrot...
GpgIdSigner(QString gpgExecutable, QStringList signingKeys, Exec exec=Exec())
Create a signer.
std::function< int(const QString &app, const QStringList &args, const QString &input, QString *out, QString *err)> Exec
Blocking process runner: Executor::executeBlocking()'s shape.
QStringList m_keys
The configured signing keys. The first one signs; a signature by any of them verifies.
auto keys() const -> const QStringList &
The keys this signer was given.
auto verifyFile(const QString &gpgIdFile, QByteArray *contents) const -> bool
Read gpgIdFile and verify it against <file>.sig.
static constexpr qint64 kMaxSignatureBytes
Most bytes of a .gpg-id.sig verify() hands to gpg.
QString m_gpg
The gpg executable, as configured.
static auto keysFromSetting(const QString &passSigningKey) -> QStringList
Split the settings value into keys, upper-cased the way gpg prints fingerprints.
auto verify(const QByteArray &contents, const QString &signatureFile) const -> bool
Check a detached signature against the given bytes: gpg must report a VALIDSIG whose key or primary-k...
auto haveSecretKey() const -> bool
Whether gpg has the secret key that sign() will use, i.e. the first configured key.
static auto writeFileReplacing(const QString &path, const QByteArray &bytes, bool replace, QString *error=nullptr) -> bool
Write bytes as the file path through stageFileReplacing().
static constexpr qint64 kMaxGpgIdBytes
Most bytes of a .gpg-id QtPass reads.
static auto readRegularFile(const QString &path, qint64 maxBytes) -> std::optional< QByteArray >
Read path whole, as the regular file it is (openRegularFile()), when it holds at most maxBytes.
static auto isLinkedFolder(const QString &path) -> bool
Whether path names a symbolic link or NTFS junction, with or without a trailing separator (which woul...
static auto openRegularFile(const QString &path, QFile &file) -> bool
Open path for reading as the regular file it is, not as what a link under that name points to: the ob...
Executor, the FIFO queue every gpg, git and pass child process of QtPass goes through,...
GpgIdSigner, which signs a store's .gpg-id and verifies an existing .gpg-id.sig against the configure...
The "qtpass" logging category.
Util: static helpers for the process environment, binary discovery, store paths, URL safety and the l...