Signs a GPG ID file and verifies its signature.
bool result = ImitatePass::signGpgIdFile(gpgIdFile, signingKeys); std::cout << result << std::endl; // Expected output: true if signing and verification succeed.
Signs a GPG ID file and verifies its signature.
bool result = ImitatePass::signGpgIdFile(gpgIdFile, signingKeys); std::cout << result << std::endl; // Expected output: true if signing and verification succeed
#include <QDirIterator>
#include <QRegularExpression>
#include <utility>
#ifdef QT_DEBUG
#endif
static auto pgit(const QString &path) -> QString {
return path;
}
QString res = "$(wslpath " + path + ")";
return res.replace('\\', '/');
}
static auto pgpg(const QString &path) -> QString {
return path;
}
QString res = "$(wslpath " + path + ")";
return res.replace('\\', '/');
}
}
}
}
}
QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to",
"--batch", "--use-agent", pgpg(file)};
}
#ifdef QT_DEBUG
dbg() <<
"No OTP generation code for fake pass yet, attempting for file: " +
file;
#else
Q_UNUSED(file)
#endif
}
file = file + ".gpg";
emit
critical(tr(
"Check .gpgid file signature!"),
tr("Signature for %1 is invalid.").arg(gpgIdPath));
return;
}
if (recipients.isEmpty()) {
tr("Could not read encryption key to use, .gpg-id "
"file missing or invalid."));
return;
}
QStringList args = {"--batch", "-eq", "--output", pgpg(file)};
for (auto &r : recipients) {
args.append("-r");
args.append(r);
}
if (overwrite) {
args.append("--yes");
}
args.append("-");
if (!overwrite) {
}
QString msg =
QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
}
}
if (file.isEmpty()) {
} else {
}
}
if (!isDir) {
file += ".gpg";
}
gitCommit(file,
"Remove for " + path +
" using QtPass.");
} else {
if (isDir) {
QDir dir(file);
dir.removeRecursively();
} else {
QFile(file).remove();
}
}
}
QString out;
QStringList args =
QStringList{"--status-fd=1", "--list-secret-keys"} + signingKeys;
int result =
if (result != 0) {
#ifdef QT_DEBUG
dbg() <<
"GPG list-secret-keys failed with code:" << result;
#endif
return false;
}
for (auto &key : signingKeys) {
if (out.contains("[GNUPG:] KEY_CONSIDERED " + key)) {
return true;
}
}
return false;
}
const QList<UserInfo> &users) {
QFile gpgId(gpgIdFile);
if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
tr("Failed to open .gpg-id for writing."));
return;
}
bool secret_selected = false;
for (const UserInfo &user : users) {
if (user.enabled) {
gpgId.write((user.key_id + "\n").toUtf8());
secret_selected |= user.have_secret;
}
}
gpgId.close();
if (!secret_selected) {
tr("Check selected users!"),
tr("None of the selected keys have a secret key available.\n"
"You will not be able to decrypt any newly added passwords!"));
}
}
const QStringList &signingKeys) -> bool {
QStringList args;
if (!signingKeys.isEmpty()) {
#ifdef QT_DEBUG
if (signingKeys.size() > 1) {
dbg() <<
"Multiple signing keys configured; using only the first key:"
<< signingKeys.first();
}
#endif
args.append(QStringList{"--default-key", signingKeys.first()});
}
args.append(QStringList{"--yes", "--detach-sign", gpgIdFile});
int result =
if (result != 0) {
#ifdef QT_DEBUG
dbg() <<
"GPG signing failed with code:" << result;
#endif
emit critical(tr("GPG signing failed!"),
tr("Failed to sign %1.").arg(gpgIdFile));
return false;
}
if (!verifyGpgIdFile(gpgIdFile)) {
emit critical(tr("Check .gpgid file signature!"),
tr("Signature for %1 is invalid.").arg(gpgIdFile));
return false;
}
return true;
}
const QString &gpgIdSigFile, bool addFile,
bool addSigFile) {
if (addFile) {
}
QString commitPath = gpgIdFile;
gitCommit(gpgIdFile,
"Added " + commitPath +
" using QtPass.");
if (!addSigFile) {
return;
}
commitPath = gpgIdSigFile;
commitPath.replace(QRegularExpression("\\.gpg$"), "");
gitCommit(gpgIdSigFile,
"Added " + commitPath +
" using QtPass.");
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
QStringList signingKeys =
#else
QStringList signingKeys =
#endif
QString gpgIdSigFile = path + ".gpg-id.sig";
bool addSigFile = false;
if (!signingKeys.isEmpty()) {
tr("None of the secret signing keys is available.\n"
"You will not be able to change the user list!"));
return;
}
QFileInfo checkFile(gpgIdSigFile);
if (!checkFile.exists() || !checkFile.isFile()) {
addSigFile = true;
}
}
QString gpgIdFile = path + ".gpg-id";
bool addFile = false;
QFileInfo checkFile(gpgIdFile);
if (!checkFile.exists() || !checkFile.isFile()) {
addFile = true;
}
}
if (!signingKeys.isEmpty()) {
return;
}
}
gitAddGpgId(gpgIdFile, gpgIdSigFile, addFile, addSigFile);
}
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
QStringList signingKeys =
#else
QStringList signingKeys =
#endif
if (signingKeys.isEmpty()) {
return true;
}
QString out;
QStringList args =
QStringList{"--verify", "--status-fd=1", pgpg(file) + ".sig", pgpg(file)};
int result =
if (result != 0) {
#ifdef QT_DEBUG
dbg() <<
"GPG verify failed with code:" << result;
#endif
return false;
}
QRegularExpression re(
R"(^\[GNUPG:\] VALIDSIG ([A-F0-9]{40}) .* ([A-F0-9]{40})\r?$)",
QRegularExpression::MultilineOption);
QRegularExpressionMatch m = re.match(out);
if (!m.hasMatch()) {
return false;
}
QStringList fingerprints = m.capturedTexts();
fingerprints.removeFirst();
for (auto &key : signingKeys) {
if (fingerprints.contains(key)) {
return true;
}
}
return false;
}
bool result = true;
QDir dir(dirName);
if (dir.exists(dirName)) {
for (const QFileInfo &info :
dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
QDir::AllDirs | QDir::Files,
QDir::DirsFirst)) {
if (info.isDir()) {
result = removeDir(info.absoluteFilePath());
} else {
result = QFile::remove(info.absoluteFilePath());
}
if (!result) {
return result;
}
}
result = dir.rmdir(dirName);
}
return result;
}
QStringList &gpgIdFilesVerified,
QStringList &gpgId) -> bool {
if (gpgIdFilesVerified.contains(gpgIdPath)) {
return true;
}
if (!verifyGpgIdFile(gpgIdPath)) {
emit critical(tr("Check .gpgid file signature!"),
tr("Signature for %1 is invalid.").arg(gpgIdPath));
return false;
}
gpgIdFilesVerified.append(gpgIdPath);
gpgId = getRecipientList(file);
gpgId.sort();
return true;
}
QStringList args = {
"-v", "--no-secmem-warning", "--no-permission-warning",
"--list-only", "--keyid-format=long", pgpg(fileName)};
QString keys;
QString err;
if (result != 0 && keys.isEmpty() && err.isEmpty()) {
return QStringList();
}
QStringList actualKeys;
keys += err;
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#else
#endif
QListIterator<QString> itr(key);
while (itr.hasNext()) {
QString current = itr.next();
QStringList cur = current.split(" ");
if (cur.length() > 4) {
QString actualKey = cur.takeAt(4);
if (actualKey.length() == 16) {
actualKeys << actualKey;
}
}
}
actualKeys.sort();
return actualKeys;
}
const QStringList &recipients) -> bool {
#ifdef QT_DEBUG
dbg() <<
"reencrypt " << fileName <<
" for " << recipients;
#endif
QString local_lastDecrypt;
QStringList args = {
"-d", "--quiet", "--yes", "--no-encrypt-to",
"--batch", "--use-agent", pgpg(fileName)};
args, &local_lastDecrypt);
if (result != 0 || local_lastDecrypt.isEmpty()) {
#ifdef QT_DEBUG
dbg() <<
"Decrypt error on re-encrypt for:" << fileName;
#endif
return false;
}
if (local_lastDecrypt.right(1) != "\n") {
local_lastDecrypt += "\n";
}
if (recipients.isEmpty()) {
emit critical(tr("Can not edit"),
tr("Could not read encryption key to use, .gpg-id "
"file missing or invalid."));
return false;
}
QString tempPath = fileName + ".reencrypt.tmp";
args = QStringList{"--yes", "--batch", "-eq", "--output", pgpg(tempPath)};
for (const auto &i : recipients) {
args.append("-r");
args.append(i);
}
args.append("-");
local_lastDecrypt);
if (result != 0) {
#ifdef QT_DEBUG
dbg() <<
"Encrypt error on re-encrypt for:" << fileName;
#endif
QFile::remove(tempPath);
return false;
}
QString verifyOutput;
args = QStringList{"-d", "--quiet", "--batch", "--use-agent", pgpg(tempPath)};
&verifyOutput);
if (result != 0 || verifyOutput.isEmpty()) {
#ifdef QT_DEBUG
dbg() <<
"Verification failed for:" << tempPath;
#endif
QFile::remove(tempPath);
return false;
}
if (verifyOutput.trimmed() != local_lastDecrypt.trimmed()) {
#ifdef QT_DEBUG
dbg() <<
"Verification content mismatch for:" << tempPath;
#endif
QFile::remove(tempPath);
return false;
}
QString backupPath = fileName + ".reencrypt.bak";
if (!QFile::rename(fileName, backupPath)) {
#ifdef QT_DEBUG
dbg() <<
"Failed to backup original file:" << fileName;
#endif
QFile::remove(tempPath);
return false;
}
if (!QFile::rename(tempPath, fileName)) {
#ifdef QT_DEBUG
dbg() <<
"Failed to rename temp file to:" << fileName;
#endif
QFile::rename(backupPath, fileName);
QFile::remove(tempPath);
emit critical(
tr("Re-encryption failed"),
tr("Failed to replace %1. Original has been restored.").arg(fileName));
return false;
}
QFile::remove(backupPath);
{"add", pgit(fileName)});
QString path =
{"commit", pgit(fileName), "-m",
"Re-encrypt for " + path + " using QtPass."});
}
return true;
}
return true;
}
emit statusMsg(tr("Creating backup commit"), 2000);
QString statusOut;
0) {
emit critical(
tr("Backup commit failed"),
tr("Could not inspect git status. Re-encryption was aborted."));
return false;
}
if (!statusOut.trimmed().isEmpty()) {
git, {"commit", "-m", "Backup before re-encryption"}) != 0) {
emit critical(tr("Backup commit failed"),
tr("Re-encryption was aborted because a git backup could "
"not be created."));
return false;
}
}
return true;
}
emit
statusMsg(tr(
"Re-encrypting from folder %1").arg(dir), 3000);
emit
statusMsg(tr(
"Updating password-store"), 2000);
}
return;
}
QDir currentDir;
QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
QDirIterator::Subdirectories);
QStringList gpgIdFilesVerified;
QStringList gpgId;
int successCount = 0;
int failCount = 0;
while (gpgFiles.hasNext()) {
QString fileName = gpgFiles.next();
if (gpgFiles.fileInfo().path() != currentDir.path()) {
return;
}
if (gpgId.isEmpty() && !gpgIdFilesVerified.isEmpty()) {
emit
critical(tr(
"GPG ID verification failed"),
tr("Could not verify .gpg-id for directory."));
return;
}
}
if (actualKeys != gpgId) {
successCount++;
} else {
failCount++;
emit
critical(tr(
"Re-encryption failed"),
tr("Failed to re-encrypt %1").arg(fileName));
}
}
}
if (failCount > 0) {
emit
statusMsg(tr(
"Re-encryption completed: %1 succeeded, %2 failed")
.arg(successCount)
.arg(failCount),
5000);
} else {
tr("Re-encryption completed: %1 files re-encrypted").arg(successCount),
3000);
}
emit
statusMsg(tr(
"Updating password-store"), 2000);
}
}
const QString &dest, bool force)
-> QString {
QFileInfo srcFileInfo(src);
QFileInfo destFileInfo(dest);
QString destFile;
QString srcFileBaseName = srcFileInfo.fileName();
if (srcFileInfo.isFile()) {
if (destFileInfo.isFile()) {
if (!force) {
#ifdef QT_DEBUG
dbg() <<
"Destination file already exists";
#endif
return QString();
}
destFile = dest;
} else if (destFileInfo.isDir()) {
destFile = QDir(dest).filePath(srcFileBaseName);
} else {
destFile = dest;
}
if (destFile.endsWith(".gpg", Qt::CaseInsensitive)) {
destFile.chop(4);
}
destFile.append(".gpg");
} else if (srcFileInfo.isDir()) {
if (destFileInfo.isDir()) {
destFile = QDir(dest).filePath(srcFileBaseName);
} else if (destFileInfo.isFile()) {
#ifdef QT_DEBUG
dbg() <<
"Destination is a file";
#endif
return QString();
} else {
destFile = dest;
}
} else {
#ifdef QT_DEBUG
dbg() <<
"Source file does not exist";
#endif
return QString();
}
return destFile;
}
bool force) {
QStringList args;
args << "mv";
if (force) {
args << "-f";
}
args << pgit(src);
args << pgit(destFile);
QString relDest =
QString message = QString("Moved for %1 to %2 using QtPass.");
message = message.arg(relSrc, relDest);
}
const bool force) {
if (destFile.isEmpty()) {
return;
}
#ifdef QT_DEBUG
dbg() <<
"Move Source: " << src;
dbg() <<
"Move Destination: " << destFile;
#endif
} else {
QDir qDir;
if (force) {
qDir.remove(destFile);
}
qDir.rename(src, destFile);
}
}
const bool force) {
QFileInfo destFileInfo(dest);
QStringList args;
args << "cp";
if (force) {
args << "-f";
}
args << pgit(src);
args << pgit(dest);
QString message = QString("Copied from %1 to %2 using QtPass.");
message = message.arg(src, dest);
} else {
QDir qDir;
if (force) {
qDir.remove(dest);
}
QFile::copy(src, dest);
}
if (destFileInfo.isDir()) {
} else if (destFileInfo.isFile()) {
}
}
bool readStdout, bool readStderr) {
readStdout, readStderr);
}
bool readStdout, bool readStderr) {
readStdout, readStderr);
}
const QString &err) {
#ifdef QT_DEBUG
#endif
static QString transactionOutput;
transactionOutput.append(out);
if (exitCode == 0) {
return;
}
} else {
if (id == -1) {
#ifdef QT_DEBUG
dbg() <<
"No such transaction!";
#endif
return;
}
}
}
transactionOutput.clear();
}
const QStringList &args, QString input,
bool readStdout, bool readStderr) {
}
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.
RAII helper for wrapping operations in transactions.
auto createBackupCommit() -> bool
Create git backup commit before re-encryption.
void OtpGenerate(QString file) override
Generate OTP.
void GitInit() override
Initialize Git repository.
void executeGpg(PROCESS id, const QStringList &args, QString input=QString(), bool readStdout=true, bool readStderr=true)
Execute GPG command.
void GitPull_b() override
Pull with rebase.
auto checkSigningKeys(const QStringList &signingKeys) -> bool
Check if signing keys are valid.
ImitatePass()
Construct ImitatePass instance.
auto reencryptSingleFile(const QString &fileName, const QStringList &recipients) -> bool
Re-encrypt single file with new recipients.
auto verifyGpgIdFile(const QString &file) -> bool
Verify .gpg-id file exists and is valid.
void executeGit(PROCESS id, const QStringList &args, QString input=QString(), bool readStdout=true, bool readStderr=true)
Execute git command.
void Init(QString path, const QList< UserInfo > &users) override
Initialize store.
auto verifyGpgIdForDir(const QString &file, QStringList &gpgIdFilesVerified, QStringList &gpgId) -> bool
Verify .gpg-id file for a directory.
void Insert(QString file, QString newValue, bool overwrite=false) override
Insert new password.
auto removeDir(const QString &dirName) -> bool
Remove directory recursively.
void executeMoveGit(const QString &src, const QString &destFile, bool force)
Execute git move operation.
void Move(const QString src, const QString dest, const bool force=false) override
Move password file.
void Copy(const QString src, const QString dest, const bool force=false) override
Copy password file.
void endReencryptPath()
Emitted after finishing re-encryption.
void finished(int id, int exitCode, const QString &out, const QString &err) override
Handle process completion.
void executeWrapper(PROCESS id, const QString &app, const QStringList &args, QString input, bool readStdout=true, bool readStderr=true) override
Execute command wrapper.
auto signGpgIdFile(const QString &gpgIdFile, const QStringList &signingKeys) -> bool
Sign .gpg-id file with signing keys.
void Remove(QString file, bool isDir=false) override
Remove password.
void GitPull() override
Pull from remote.
void Show(QString file) override
Show decrypted password.
auto resolveMoveDestination(const QString &src, const QString &dest, bool force) -> QString
Resolve destination for move operation.
void GitPush() override
Push to remote.
void writeGpgIdFile(const QString &gpgIdFile, const QList< UserInfo > &users)
Write recipients to .gpg-id file.
void gitAddGpgId(const QString &gpgIdFile, const QString &gpgIdSigFile, bool addFile, bool addSigFile)
Add .gpg-id to git staging.
void startReencryptPath()
Emitted before starting re-encryption.
auto getKeysFromFile(const QString &fileName) -> QStringList
Read recipients from file.
void reencryptPath(const QString &dir)
Re-encrypt entire directory.
void gitCommit(const QString &file, const QString &msg)
Commit changes to git.
void critical(const QString &, const QString &)
Emit critical error.
void statusMsg(const QString &, int)
Emit status message.
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 getGpgIdPath(const QString &for_file) -> QString
Get .gpg-id file path for a password file.
virtual void finished(int id, int exitCode, const QString &out, const QString &err)
Handle process completion.
static auto isAutoPull(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether automatic pull is enabled.
static auto isUseGit(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether Git integration is enabled.
static auto getPassStore(const QString &defaultValue=QVariant().toString()) -> QString
Get password store directory path.
static auto isAddGPGId(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to auto-add GPG ID when receiving files.
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 isUseWebDav(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether WebDAV integration is enabled.
static auto isAutoPush(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether automatic push is enabled.
static auto getGitExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get git executable path.
static auto endsWithGpg() -> const QRegularExpression &
Returns a regex to match .gpg file extensions.
static auto newLinesRegex() -> const QRegularExpression &
Returns a regex to match newline characters.
auto transactionIsOver(Enums::PROCESS) -> Enums::PROCESS
transactionIsOver checks wheather currently finished process is last in current transaction
void transactionAdd(Enums::PROCESS)
transactionAdd If called after call to transactionStart() and before transactionEnd(),...
Debug utilities for QtPass.
#define dbg()
Simple debug macro that includes file and line number.
PROCESS
Identifies different subprocess operations used in QtPass.