Finds the path to the gpgconf executable in the same directory as the given GPG path.
QString result = findGpgconfInGpgDir(gpgPath); std::cout << result.toStdString() << std::endl; // Expected output: path to gpgconf or empty string.
Finds the path to the gpgconf executable in the same directory as the given GPG path.
QString result = findGpgconfInGpgDir(gpgPath); std::cout << result.toStdString() << std::endl; // Expected output: path to gpgconf or empty string
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <algorithm>
#include <utility>
#ifdef QT_DEBUG
#endif
namespace {
auto fallbackCharset(const QString &input, const QString &fallback) -> QString {
return input.isEmpty() ? fallback : input;
}
return fallbackCharset(
}
}
Pass::Pass() : wrapperRunning(false), env(QProcess::systemEnvironment()) {
connect(&exec,
static_cast<void (
Executor::*)(
int,
int,
const QString &,
const QString &)>(&Executor::finished),
const QStringList wslenvVars = {
QStringLiteral("PASSWORD_STORE_DIR/p"),
QStringLiteral("PASSWORD_STORE_GENERATED_LENGTH/w"),
QStringLiteral("PASSWORD_STORE_CHARACTER_SET/w")};
const QString wslenvPrefix = QStringLiteral("WSLENV=");
auto it =
std::find_if(env.begin(), env.end(), [&wslenvPrefix](const QString &s) {
return s.startsWith(wslenvPrefix);
});
if (it == env.end()) {
env.append(wslenvPrefix + wslenvVars.join(':'));
} else {
QStringList parts =
it->mid(wslenvPrefix.size()).split(':', Qt::SkipEmptyParts);
for (const QString &v : wslenvVars) {
if (!parts.contains(v))
parts.append(v);
}
*it = wslenvPrefix + parts.join(':');
}
}
const QStringList &args, bool readStdout,
bool readStderr) {
}
const QStringList &args, QString input,
bool readStdout, bool readStderr) {
#ifdef QT_DEBUG
#endif
readStdout, readStderr);
}
#ifdef __APPLE__
if (QFile("/usr/local/MacGPG2/bin").exists())
env.replaceInStrings("PATH=", "PATH=/usr/local/MacGPG2/bin:");
if (env.filter("/usr/local/bin").isEmpty())
env.replaceInStrings("PATH=", "PATH=/usr/local/bin:");
#endif
absHome.makeAbsolute();
env << "GNUPGHOME=" + absHome.path();
}
}
-> QString {
if (length == 0) {
emit critical(tr("Invalid password length"),
tr("Can't generate password with zero length."));
return {};
}
QString passwd;
QStringList args;
args.append("-1");
args.append("--secure");
}
: "--capitalize");
: "--numerals");
args.append("--symbols");
}
args.append(QString::number(length));
&passwd) == 0) {
static const QRegularExpression literalNewLines{"[\\n\\r]"};
passwd.remove(literalNewLines);
} else {
passwd.clear();
#ifdef QT_DEBUG
qDebug() << __FILE__ << ":" << __LINE__ << "\t"
<< "pwgen fail";
#endif
}
} else {
const QString cs = fallbackCharset(
if (cs.length() > 0) {
passwd = generateRandomPassword(cs, length);
} else {
emit critical(
tr("No characters chosen"),
tr("Can't generate password, there are no characters to choose from "
"set in the configuration!"));
}
}
return passwd;
}
QString out, err;
{"--version"}, &out, &err) != 0) {
return false;
}
QRegularExpression versionRegex(R"(gpg \(GnuPG\) (\d+)\.(\d+))");
QRegularExpressionMatch match = versionRegex.match(out);
if (!match.hasMatch()) {
return false;
}
int major = match.captured(1).toInt();
int minor = match.captured(2).toInt();
return major > 2 || (major == 2 && minor >= 1);
}
return QStringLiteral("%echo Generating a default key\n"
"Key-Type: EdDSA\n"
"Key-Curve: Ed25519\n"
"Subkey-Type: ECDH\n"
"Subkey-Curve: Curve25519\n"
"Name-Real: \n"
"Name-Comment: QtPass\n"
"Name-Email: \n"
"Expire-Date: 0\n"
"%no-protection\n"
"%commit\n"
"%echo done");
}
return QStringLiteral("%echo Generating a default key\n"
"Key-Type: RSA\n"
"Subkey-Type: RSA\n"
"Name-Real: \n"
"Name-Comment: QtPass\n"
"Name-Email: \n"
"Expire-Date: 0\n"
"%no-protection\n"
"%commit\n"
"%echo done");
}
namespace {
auto resolveWslGpgconfPath(const QString &lastPart) -> QString {
int lastSep = lastPart.lastIndexOf('/');
if (lastSep < 0) {
lastSep = lastPart.lastIndexOf('\\');
}
if (lastSep >= 0) {
return lastPart.left(lastSep + 1) + "gpgconf";
}
return QStringLiteral("gpgconf");
}
QString findGpgconfInGpgDir(const QString &gpgPath) {
QFileInfo gpgInfo(gpgPath);
if (!gpgInfo.isAbsolute()) {
return QString();
}
QDir dir(gpgInfo.absolutePath());
#ifdef Q_OS_WIN
QFileInfo candidateExe(dir.filePath("gpgconf.exe"));
if (candidateExe.isExecutable()) {
return candidateExe.filePath();
}
#endif
QFileInfo candidate(dir.filePath("gpgconf"));
if (candidate.isExecutable()) {
return candidate.filePath();
}
return QString();
}
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
QStringList splitCommandCompat(const QString &command) {
QStringList result;
QString current;
bool inSingleQuote = false;
bool inDoubleQuote = false;
bool escaping = false;
for (QChar ch : command) {
if (escaping) {
current.append(ch);
escaping = false;
continue;
}
if (ch == '\\') {
escaping = true;
continue;
}
if (ch == '\'' && !inDoubleQuote) {
inSingleQuote = !inSingleQuote;
continue;
}
if (ch == '"' && !inSingleQuote) {
inDoubleQuote = !inDoubleQuote;
continue;
}
if (ch.isSpace() && !inSingleQuote && !inDoubleQuote) {
if (!current.isEmpty()) {
result.append(current);
current.clear();
}
continue;
}
current.append(ch);
}
if (escaping) {
current.append('\\');
}
if (!current.isEmpty()) {
result.append(current);
}
return result;
}
#endif
}
if (gpgPath.trimmed().isEmpty()) {
return {"gpgconf", {}};
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
QStringList parts = QProcess::splitCommand(gpgPath);
#else
QStringList parts = splitCommandCompat(gpgPath);
#endif
if (parts.isEmpty()) {
return {"gpgconf", {}};
}
const QString first = parts.first();
if (first.compare("wsl", Qt::CaseInsensitive) == 0 ||
first.compare("wsl.exe", Qt::CaseInsensitive) == 0) {
if (parts.size() >= 2 && parts.at(1).startsWith("sh")) {
return {"gpgconf", {}};
}
if (parts.size() >= 2 &&
QFileInfo(parts.last()).fileName().startsWith("gpg")) {
QString wslGpgconf = resolveWslGpgconfPath(parts.last());
parts.removeLast();
parts.append(wslGpgconf);
return {parts.first(), parts.mid(1)};
}
return {"gpgconf", {}};
}
if (!first.contains('/') && !first.contains('\\')) {
return {"gpgconf", {}};
}
QString gpgconfPath = findGpgconfInGpgDir(first);
if (!gpgconfPath.isEmpty()) {
return {gpgconfPath, {}};
}
return {"gpgconf", {}};
}
if (!gpgPath.isEmpty()) {
QStringList killArgs = resolvedGpgconf.
arguments;
killArgs << "--kill";
killArgs << "gpg-agent";
}
std::move(batch));
}
auto Pass::listKeys(QStringList keystrings,
bool secret) -> QList<UserInfo> {
QStringList args = {"--no-tty", "--with-colons", "--with-fingerprint"};
args.append(secret ? "--list-secret-keys" : "--list-keys");
for (
const QString &keystring :
AS_CONST(keystrings)) {
if (!keystring.isEmpty()) {
args.append(keystring);
}
}
QString p_out;
&p_out) != 0) {
return QList<UserInfo>();
}
}
auto Pass::listKeys(
const QString &keystring,
bool secret) -> QList<UserInfo> {
return listKeys(QStringList(keystring), secret);
}
namespace {
auto containsAny(const QString &str, const QStringList &patterns) -> bool {
for (const QString &p : patterns) {
if (str.contains(p)) {
return true;
}
}
return false;
}
auto containsAnyCaseInsensitive(const QString &str, const QStringList &patterns)
-> bool {
const QString lower = str.toLower();
for (const QString &p : patterns) {
if (lower.contains(p)) {
return true;
}
}
return false;
}
}
if (containsAny(err, {QStringLiteral("[GNUPG:] KEYEXPIRED"),
QStringLiteral("[GNUPG:] INV_RECP 5 ")}))
return QCoreApplication::translate(
"Pass", "Encryption failed: GPG key has expired. Please renew or "
"replace it.");
if (containsAny(err, {QStringLiteral("[GNUPG:] KEYREVOKED"),
QStringLiteral("[GNUPG:] INV_RECP 4 ")}))
return QCoreApplication::translate(
"Pass", "Encryption failed: GPG key has been revoked.");
if (containsAny(err, {QStringLiteral("[GNUPG:] NO_PUBKEY"),
QStringLiteral("[GNUPG:] INV_RECP")}))
return QCoreApplication::translate(
"Pass", "Encryption failed: recipient GPG key not found or invalid. "
"Check that the key ID in .gpg-id is correct and imported.");
if (err.contains(QStringLiteral("[GNUPG:] FAILURE")))
return QCoreApplication::translate(
"Pass", "Encryption failed. Check that your GPG key is valid.");
if (containsAnyCaseInsensitive(err, {QLatin1String("key has expired"),
QLatin1String("key expired")}))
return QCoreApplication::translate(
"Pass", "Encryption failed: GPG key has expired. Please renew or "
"replace it.");
if (containsAnyCaseInsensitive(err, {QLatin1String("key has been revoked"),
QLatin1String("revoked")}))
return QCoreApplication::translate(
"Pass", "Encryption failed: GPG key has been revoked.");
if (containsAnyCaseInsensitive(err, {QLatin1String("no public key"),
QLatin1String("unusable public key"),
QLatin1String("no secret key")}))
return QCoreApplication::translate(
"Pass", "Encryption failed: recipient GPG key not found or invalid. "
"Check that the key ID in .gpg-id is correct and imported.");
if (containsAnyCaseInsensitive(err, {QLatin1String("encryption failed")}))
return QCoreApplication::translate(
"Pass", "Encryption failed. Check that your GPG key is valid.");
return {};
}
namespace {
auto isGrepHeaderLine(const QString &rawLine, const QString &trimmedLine)
-> bool {
return rawLine.startsWith(QStringLiteral("\x1B[94m")) ||
(!rawLine.startsWith(' ') && !rawLine.startsWith('\t') &&
trimmedLine.endsWith(':'));
}
}
-> QList<QPair<QString, QStringList>> {
static const QRegularExpression ansi(
QStringLiteral(R"(\x1B\[[0-9;]*[a-zA-Z])"));
QList<QPair<QString, QStringList>> results;
QString currentEntry;
QStringList currentMatches;
for (const QString &rawLine : rawOut.split('\n')) {
QString line = rawLine;
line.remove('\r');
line.remove(ansi);
line = line.trimmed();
const bool isHeader = isGrepHeaderLine(rawLine, line);
if (isHeader) {
if (!currentEntry.isEmpty() && !currentMatches.isEmpty())
results.append({currentEntry, currentMatches});
currentEntry = line.endsWith(':') ? line.chopped(1) : line;
currentMatches.clear();
} else if (!currentEntry.isEmpty()) {
if (!line.isEmpty())
currentMatches << line;
}
}
if (!currentEntry.isEmpty() && !currentMatches.isEmpty())
results.append({currentEntry, currentMatches});
return results;
}
const QString &err) {
auto pid =
static_cast<PROCESS>(id);
if (exitCode != 0) {
handleProcessError(pid, exitCode, out, err);
return;
}
emitProcessFinishedSignal(pid, out, err);
}
void Pass::handleProcessError(
PROCESS pid,
int exitCode,
const QString &out,
const QString &err) {
handleGrepError(exitCode, err);
return;
}
if (!friendly.isEmpty()) {
return;
}
}
}
void Pass::handleGrepError(int exitCode, const QString &err) {
if (exitCode == 1) {
} else {
}
}
auto Pass::formatInsertError(const QString &friendly, const QString &err)
-> QString {
QStringList humanLines;
for (const QString &line : err.split('\n')) {
QString cleanedLine = line;
cleanedLine.remove('\r');
if (!cleanedLine.startsWith(QLatin1String("[GNUPG:]")))
humanLines.append(cleanedLine);
}
const QString humanErr = humanLines.join('\n').trimmed();
return humanErr.isEmpty() ? friendly : friendly + "\n\n" + humanErr;
}
void Pass::emitProcessFinishedSignal(
PROCESS pid,
const QString &out,
const QString &err) {
switch (pid) {
break;
break;
break;
break;
break;
break;
break;
break;
break;
break;
break;
break;
default:
#ifdef QT_DEBUG
dbg() <<
"Unhandled process type" << pid;
#endif
break;
}
}
const bool hasEq = key.endsWith('=');
Q_ASSERT_X(hasEq, "Pass::setEnvVar",
"called with malformed key (missing '=')");
if (!hasEq) {
qWarning() << "Pass::setEnvVar called with malformed key (missing '='):"
<< key;
return;
}
const QStringList existing = env.filter(key);
for (const QString &entry : existing)
env.removeAll(entry);
if (!value.isEmpty())
env.append(key + value);
}
setEnvVar(QStringLiteral(
"PASSWORD_STORE_SIGNING_KEY="),
setEnvVar(QStringLiteral(
"PASSWORD_STORE_DIR="),
setEnvVar(QStringLiteral(
"PASSWORD_STORE_GENERATED_LENGTH="),
QString::number(passConfig.
length));
setEnvVar(QStringLiteral(
"PASSWORD_STORE_CHARACTER_SET="),
effectiveCharset(passConfig));
exec.setEnvironment(env);
}
QString passStore =
QString normalizedFile = QDir::fromNativeSeparators(for_file);
QString fullPath = normalizedFile.startsWith(passStore)
? normalizedFile
: passStore + "/" + normalizedFile;
QDir gpgIdDir(QFileInfo(fullPath).absoluteDir());
bool found = false;
while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) {
if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) {
found = true;
break;
}
if (!gpgIdDir.cdUp()) {
break;
}
}
QString gpgIdPath(
found ? gpgIdDir.absoluteFilePath(".gpg-id")
return gpgIdPath;
}
QFile gpgId(getGpgIdPath(for_file));
if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
return {};
}
QStringList recipients;
while (!gpgId.atEnd()) {
QString recipient(gpgId.readLine());
recipient = recipient.split("#")[0].trimmed();
recipients += recipient;
}
}
return recipients;
}
int *count) -> QStringList {
Q_UNUSED(separator)
if (count) {
*count = recipients.size();
}
return recipients;
}
if (bound < 2) {
return 0;
}
quint32 randval;
const quint32 rejectionThreshold = (1 + ~bound) % bound;
do {
randval = QRandomGenerator::system()->generate();
} while (randval < rejectionThreshold);
return randval % bound;
}
-> QString {
if (charset.isEmpty() || length == 0U) {
return {};
}
QString out;
for (unsigned int i = 0; i < length; ++i) {
out.append(charset.at(static_cast<int>(
boundedRandom(static_cast<quint32>(charset.length())))));
}
return out;
}
id Identifier provided by the caller for this queued request.
static auto executeBlocking(const QString &app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Executor::executeBlocking blocking version of the executor, takes input and presents it as stdin.
void starting()
starting signal that is emited when process starts
void init()
Initialize the Pass instance.
void startingExecuteWrapper()
Emitted before executing a command.
void GenerateGPGKeys(QString batch)
Generate GPG keys using batch script.
void finishedCopy(const QString &, const QString &)
Emitted when copy finishes.
void finishedShow(const QString &)
Emitted when show finishes.
void finishedRemove(const QString &, const QString &)
Emitted when remove finishes.
virtual auto generatePassword(unsigned int length, const QString &charset) -> QString
Generate random password.
void finishedMove(const QString &, const QString &)
Emitted when move finishes.
static bool gpgSupportsEd25519()
Check if GPG supports Ed25519 encryption.
void setEnvVar(const QString &key, const QString &value)
Set or remove an environment variable.
auto boundedRandom(quint32 bound) -> quint32
Generate random number in range.
void finishedGitInit(const QString &, const QString &)
Emitted when Git init finishes.
Pass()
Construct a Pass instance.
void executeWrapper(PROCESS id, const QString &app, const QStringList &args, bool readStdout=true, bool readStderr=true)
Execute external wrapper command.
static auto getRecipientList(const QString &for_file) -> QStringList
Get list of recipients for a password file.
static auto resolveGpgconfCommand(const QString &gpgPath) -> ResolvedGpgconfCommand
Resolve the gpgconf command to kill agents.
auto listKeys(QStringList keystrings, bool secret=false) -> QList< UserInfo >
List GPG keys matching patterns.
void finishedInsert(const QString &, const QString &)
Emitted when insert finishes.
void finishedGrep(const QList< QPair< QString, QStringList > > &results)
Emitted when grep finishes with matching results.
void finishedInit(const QString &, const QString &)
Emitted when init finishes.
static auto getGpgIdPath(const QString &for_file) -> QString
Get .gpg-id file path for a password file.
void finishedOtpGenerate(const QString &)
Emitted when OTP generation finishes.
void processErrorExit(int exitCode, const QString &err)
Emitted on process error exit.
void finishedGitPull(const QString &, const QString &)
Emitted when Git pull finishes.
void finishedGitPush(const QString &, const QString &)
Emitted when Git push finishes.
void updateEnv()
Update environment for subprocesses.
virtual void finished(int id, int exitCode, const QString &out, const QString &err)
Handle process completion.
static auto getRecipientString(const QString &for_file, const QString &separator=" ", int *count=nullptr) -> QStringList
Get recipients as string.
static QString getDefaultKeyTemplate()
Get default key template for new GPG keys.
void finishedGenerateGPGKeys(const QString &, const QString &)
Emitted when GPG key generation finishes.
auto generateRandomPassword(const QString &charset, unsigned int length) -> QString
Generate random password from charset.
static auto getPwgenExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get pwgen executable path.
static auto getPasswordConfiguration() -> PasswordConfiguration
Get complete password generation configuration.
static auto getGpgHome(const QString &defaultValue=QVariant().toString()) -> QString
Get GPG home directory.
static auto getPassStore(const QString &defaultValue=QVariant().toString()) -> QString
Get password store directory path.
static auto isAvoidCapitals(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether uppercase characters should be avoided.
static auto getGpgExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get GPG executable path.
static auto getPassSigningKey(const QString &defaultValue=QVariant().toString()) -> QString
Get GPG signing key for pass.
static auto isUseSymbols(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether symbol characters are enabled.
static auto isLessRandom(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether less random password generation is enabled.
static auto isUsePwgen(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether pwgen support is enabled.
static auto isAvoidNumbers(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether numeric characters should be avoided.
static auto isValidKeyId(const QString &keyId) -> bool
Check if a string looks like a valid GPG key ID. Accepts:
Debug utilities for QtPass.
#define dbg()
Simple debug macro that includes file and line number.
auto parseGpgColonOutput(const QString &output, bool secret) -> QList< UserInfo >
Parse GPG –with-colons output into a list of UserInfo.
Utility macros for QtPass.
#define AS_CONST(x)
Cross-platform const_cast for range-based for loops.
auto gpgErrorMessage(const QString &err) -> QString
Maps GPG stderr (which may include –status-fd 2 tokens) to a translated user-friendly encryption erro...
auto parseGrepOutput(const QString &rawOut) -> QList< QPair< QString, QStringList > >
Parses 'pass grep' raw output into (entry, matches) pairs.
QList< QPair< QString, QStringList > > parseGrepOutput(const QString &rawOut)
Parses 'pass grep' raw output into (entry, matches) pairs.
QString gpgErrorMessage(const QString &err)
Maps GPG stderr (which may include –status-fd 2 tokens) to a translated user-friendly encryption erro...
PROCESS
Identifies different subprocess operations used in QtPass.
Holds the Password configuration settings.
int length
Length of the password.
enum PasswordConfiguration::characterSet selected
QString Characters[CHARSETS_COUNT]
The different character sets.