11#include <QRandomGenerator>
12#include <QRegularExpression>
34Pass::Pass() : wrapperRunning(false), env(QProcess::systemEnvironment()) {
36 static_cast<void (
Executor::*)(
int,
int,
const QString &,
37 const QString &)
>(&Executor::finished),
46 env.append(
"WSLENV=PASSWORD_STORE_DIR/p");
58 const QStringList &args,
bool readStdout,
64 const QStringList &args, QString input,
65 bool readStdout,
bool readStderr) {
70 readStdout, readStderr);
79 if (QFile(
"/usr/local/MacGPG2/bin").exists())
80 env.replaceInStrings(
"PATH=",
"PATH=/usr/local/MacGPG2/bin:");
82 if (env.filter(
"/usr/local/bin").isEmpty())
83 env.replaceInStrings(
"PATH=",
"PATH=/usr/local/bin:");
88 absHome.makeAbsolute();
89 env <<
"GNUPGHOME=" + absHome.path();
108 args.append(
"--secure");
115 args.append(
"--symbols");
117 args.append(QString::number(length));
121 static const QRegularExpression literalNewLines{
"[\\n\\r]"};
122 passwd.remove(literalNewLines);
126 qDebug() << __FILE__ <<
":" << __LINE__ <<
"\t"
135 QString effectiveCharset = charset;
136 if (effectiveCharset.isEmpty()) {
140 if (effectiveCharset.length() > 0) {
144 tr(
"No characters chosen"),
145 tr(
"Can't generate password, there are no characters to choose from "
146 "set in the configuration!"));
160 {
"--version"}, &out, &err) != 0) {
163 QRegularExpression versionRegex(R
"(gpg \(GnuPG\) (\d+)\.(\d+))");
164 QRegularExpressionMatch match = versionRegex.match(out);
165 if (!match.hasMatch()) {
168 int major = match.captured(1).toInt();
169 int minor = match.captured(2).toInt();
170 return major > 2 || (major == 2 && minor >= 1);
180 return QStringLiteral(
"%echo Generating a default key\n"
182 "Key-Curve: Ed25519\n"
183 "Subkey-Type: ECDH\n"
184 "Subkey-Curve: Curve25519\n"
186 "Name-Comment: QtPass\n"
193 return QStringLiteral(
"%echo Generating a default key\n"
197 "Name-Comment: QtPass\n"
206auto resolveWslGpgconfPath(
const QString &lastPart) -> QString {
207 int lastSep = lastPart.lastIndexOf(
'/');
209 lastSep = lastPart.lastIndexOf(
'\\');
212 return lastPart.left(lastSep + 1) +
"gpgconf";
214 return QStringLiteral(
"gpgconf");
230QString findGpgconfInGpgDir(
const QString &gpgPath) {
231 QFileInfo gpgInfo(gpgPath);
232 if (!gpgInfo.isAbsolute()) {
236 QDir dir(gpgInfo.absolutePath());
239 QFileInfo candidateExe(dir.filePath(
"gpgconf.exe"));
240 if (candidateExe.isExecutable()) {
241 return candidateExe.filePath();
245 QFileInfo candidate(dir.filePath(
"gpgconf"));
246 if (candidate.isExecutable()) {
247 return candidate.filePath();
252#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
264QStringList splitCommandCompat(
const QString &command) {
267 bool inSingleQuote =
false;
268 bool inDoubleQuote =
false;
269 bool escaping =
false;
270 for (QChar ch : command) {
280 if (ch ==
'\'' && !inDoubleQuote) {
281 inSingleQuote = !inSingleQuote;
284 if (ch ==
'"' && !inSingleQuote) {
285 inDoubleQuote = !inDoubleQuote;
288 if (ch.isSpace() && !inSingleQuote && !inDoubleQuote) {
289 if (!current.isEmpty()) {
290 result.append(current);
298 current.append(
'\\');
300 if (!current.isEmpty()) {
301 result.append(current);
324 if (gpgPath.trimmed().isEmpty()) {
325 return {
"gpgconf", {}};
328#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
329 QStringList parts = QProcess::splitCommand(gpgPath);
331 QStringList parts = splitCommandCompat(gpgPath);
334 if (parts.isEmpty()) {
335 return {
"gpgconf", {}};
338 const QString first = parts.first();
339 if (first ==
"wsl" || first ==
"wsl.exe") {
340 if (parts.size() >= 2 && parts.at(1).startsWith(
"sh")) {
341 return {
"gpgconf", {}};
343 if (parts.size() >= 2 &&
344 QFileInfo(parts.last()).fileName().startsWith(
"gpg")) {
345 QString wslGpgconf = resolveWslGpgconfPath(parts.last());
347 parts.append(wslGpgconf);
348 return {parts.first(), parts.mid(1)};
350 return {
"gpgconf", {}};
353 if (!first.contains(
'/') && !first.contains(
'\\')) {
354 return {
"gpgconf", {}};
357 QString gpgconfPath = findGpgconfInGpgDir(gpgPath);
358 if (!gpgconfPath.isEmpty()) {
359 return {gpgconfPath, {}};
362 return {
"gpgconf", {}};
373 if (!gpgPath.isEmpty()) {
375 QStringList killArgs = gpgconf.
arguments;
376 killArgs <<
"--kill";
377 killArgs <<
"gpg-agent";
393 QStringList args = {
"--no-tty",
"--with-colons",
"--with-fingerprint"};
394 args.append(secret ?
"--list-secret-keys" :
"--list-keys");
396 for (
const QString &keystring :
AS_CONST(keystrings)) {
397 if (!keystring.isEmpty()) {
398 args.append(keystring);
404 return QList<UserInfo>();
416 return listKeys(QStringList(keystring), secret);
430 const QString &err) {
431 auto pid =
static_cast<PROCESS>(id);
472 dbg() <<
"Unhandled process type" << pid;
484 QStringList envSigningKey = env.filter(
"PASSWORD_STORE_SIGNING_KEY=");
486 if (envSigningKey.isEmpty()) {
487 if (!currentSigningKey.isEmpty()) {
490 env.append(
"PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
493 if (currentSigningKey.isEmpty()) {
494 env.removeAll(envSigningKey.first());
498 env.replaceInStrings(envSigningKey.first(),
499 "PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
503 QStringList store = env.filter(
"PASSWORD_STORE_DIR=");
504 if (store.isEmpty()) {
509 env.replaceInStrings(store.first(),
"PASSWORD_STORE_DIR=" +
512 exec.setEnvironment(env);
523 QString normalizedFile = QDir::fromNativeSeparators(for_file);
524 QString fullPath = normalizedFile.startsWith(passStore)
526 : passStore +
"/" + normalizedFile;
527 QDir gpgIdDir(QFileInfo(fullPath).absoluteDir());
529 while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) {
530 if (QFile(gpgIdDir.absoluteFilePath(
".gpg-id")).exists()) {
534 if (!gpgIdDir.cdUp()) {
539 found ? gpgIdDir.absoluteFilePath(
".gpg-id")
552 if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
555 QStringList recipients;
556 while (!gpgId.atEnd()) {
557 QString recipient(gpgId.readLine());
558 recipient = recipient.split(
"#")[0].trimmed();
560 recipients += recipient;
574 int *count) -> QStringList {
578 *count = recipients.size();
602 const quint32 max_mod_bound = (1 + ~bound) % bound;
605 randval = QRandomGenerator::system()->generate();
606 }
while (randval < max_mod_bound);
608 return randval % bound;
619 if (charset.isEmpty() || length == 0U) {
623 for (
unsigned int i = 0; i < length; ++i) {
624 out.append(charset.at(
static_cast<int>(
id Identifier provided by the caller for this queued request.
static auto executeBlocking(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 critical(const QString &, const QString &)
Emit critical error.
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.
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 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. Validates a GPG key ID after normalization:
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.