7#include <QRegularExpression>
42static auto pgit(
const QString &path) -> QString {
46 QString res =
"$(wslpath " + path +
")";
47 return res.replace(
'\\',
'/');
50static auto pgpg(
const QString &path) -> QString {
54 QString res =
"$(wslpath " + path +
")";
55 return res.replace(
'\\',
'/');
91 QStringList args = {
"-d",
"--quiet",
"--yes",
"--no-encrypt-to",
92 "--batch",
"--use-agent", pgpg(file)};
101 dbg() <<
"No OTP generation code for fake pass yet, attempting for file: " +
116 file = file +
".gpg";
119 emit
critical(tr(
"Check .gpgid file signature!"),
120 tr(
"Signature for %1 is invalid.").arg(gpgIdPath));
125 if (recipients.isEmpty()) {
128 tr(
"Could not read encryption key to use, .gpg-id "
129 "file missing or invalid."));
132 QStringList args = {
"--batch",
"-eq",
"--output", pgpg(file)};
133 for (
auto &r : recipients) {
138 args.append(
"--yes");
150 QString(overwrite ?
"Edit" :
"Add") +
" for " + path +
" using QtPass.";
162 if (file.isEmpty()) {
183 gitCommit(file,
"Remove for " + path +
" using QtPass.");
187 dir.removeRecursively();
189 QFile(file).remove();
204 QStringList{
"--status-fd=1",
"--list-secret-keys"} + signingKeys;
209 dbg() <<
"GPG list-secret-keys failed with code:" << result;
213 for (
auto &key : signingKeys) {
214 if (out.contains(
"[GNUPG:] KEY_CONSIDERED " + key)) {
234 const QList<UserInfo> &users) {
235 QFile gpgId(gpgIdFile);
236 if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
238 tr(
"Failed to open .gpg-id for writing."));
241 bool secret_selected =
false;
242 for (
const UserInfo &user : users) {
244 gpgId.write((user.key_id +
"\n").toUtf8());
245 secret_selected |= user.have_secret;
249 if (!secret_selected) {
251 tr(
"Check selected users!"),
252 tr(
"None of the selected keys have a secret key available.\n"
253 "You will not be able to decrypt any newly added passwords!"));
271 const QStringList &signingKeys) ->
bool {
275 if (!signingKeys.isEmpty()) {
277 if (signingKeys.size() > 1) {
278 dbg() <<
"Multiple signing keys configured; using only the first key:"
279 << signingKeys.first();
282 args.append(QStringList{
"--default-key", signingKeys.first()});
284 args.append(QStringList{
"--yes",
"--detach-sign", gpgIdFile});
289 dbg() <<
"GPG signing failed with code:" << result;
291 emit
critical(tr(
"GPG signing failed!"),
292 tr(
"Failed to sign %1.").arg(gpgIdFile));
296 emit
critical(tr(
"Check .gpgid file signature!"),
297 tr(
"Signature for %1 is invalid.").arg(gpgIdFile));
317 const QString &gpgIdSigFile,
bool addFile,
322 QString commitPath = gpgIdFile;
324 gitCommit(gpgIdFile,
"Added " + commitPath +
" using QtPass.");
329 commitPath = gpgIdSigFile;
330 commitPath.replace(QRegularExpression(
"\\.gpg$"),
"");
331 gitCommit(gpgIdSigFile,
"Added " + commitPath +
" using QtPass.");
348#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
349 QStringList signingKeys =
352 QStringList signingKeys =
355 QString gpgIdSigFile = path +
".gpg-id.sig";
356 bool addSigFile =
false;
357 if (!signingKeys.isEmpty()) {
359 emit
critical(tr(
"No signing key!"),
360 tr(
"None of the secret signing keys is available.\n"
361 "You will not be able to change the user list!"));
364 QFileInfo checkFile(gpgIdSigFile);
365 if (!checkFile.exists() || !checkFile.isFile()) {
370 QString gpgIdFile = path +
".gpg-id";
371 bool addFile =
false;
374 QFileInfo checkFile(gpgIdFile);
375 if (!checkFile.exists() || !checkFile.isFile()) {
381 if (!signingKeys.isEmpty()) {
389 gitAddGpgId(gpgIdFile, gpgIdSigFile, addFile, addSigFile);
400#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
401 QStringList signingKeys =
404 QStringList signingKeys =
407 if (signingKeys.isEmpty()) {
412 QStringList{
"--verify",
"--status-fd=1", pgpg(file) +
".sig", pgpg(file)};
417 dbg() <<
"GPG verify failed with code:" << result;
421 QRegularExpression re(
422 R
"(^\[GNUPG:\] VALIDSIG ([A-F0-9]{40}) .* ([A-F0-9]{40})\r?$)",
423 QRegularExpression::MultilineOption);
424 QRegularExpressionMatch m = re.match(out);
428 QStringList fingerprints = m.capturedTexts();
429 fingerprints.removeFirst();
430 for (
auto &key : signingKeys) {
431 if (fingerprints.contains(key)) {
447 if (dir.exists(dirName)) {
448 for (
const QFileInfo &info :
449 dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
450 QDir::AllDirs | QDir::Files,
453 result =
removeDir(info.absoluteFilePath());
455 result = QFile::remove(info.absoluteFilePath());
462 result = dir.rmdir(dirName);
475 QStringList &gpgIdFilesVerified,
476 QStringList &gpgId) ->
bool {
478 if (gpgIdFilesVerified.contains(gpgIdPath)) {
482 emit
critical(tr(
"Check .gpgid file signature!"),
483 tr(
"Signature for %1 is invalid.").arg(gpgIdPath));
486 gpgIdFilesVerified.append(gpgIdPath);
506 "-v",
"--no-secmem-warning",
"--no-permission-warning",
507 "--list-only",
"--keyid-format=long", pgpg(fileName)};
512 if (result != 0 && keys.isEmpty() && err.isEmpty()) {
513 return QStringList();
515 QStringList actualKeys;
517#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
522 QListIterator<QString> itr(key);
523 while (itr.hasNext()) {
524 QString current = itr.next();
525 QStringList cur = current.split(
" ");
526 if (cur.length() > 4) {
527 QString actualKey = cur.takeAt(4);
528 if (actualKey.length() == 16) {
529 actualKeys << actualKey;
551 const QStringList &recipients) ->
bool {
553 dbg() <<
"reencrypt " << fileName <<
" for " << recipients;
555 QString local_lastDecrypt;
557 "-d",
"--quiet",
"--yes",
"--no-encrypt-to",
558 "--batch",
"--use-agent", pgpg(fileName)};
560 args, &local_lastDecrypt);
562 if (result != 0 || local_lastDecrypt.isEmpty()) {
564 dbg() <<
"Decrypt error on re-encrypt for:" << fileName;
569 if (local_lastDecrypt.right(1) !=
"\n") {
570 local_lastDecrypt +=
"\n";
574 if (recipients.isEmpty()) {
576 tr(
"Could not read encryption key to use, .gpg-id "
577 "file missing or invalid."));
582 QString tempPath = fileName +
".reencrypt.tmp";
583 args = QStringList{
"--yes",
"--batch",
"-eq",
"--output", pgpg(tempPath)};
584 for (
const auto &i : recipients) {
594 dbg() <<
"Encrypt error on re-encrypt for:" << fileName;
596 QFile::remove(tempPath);
601 QString verifyOutput;
602 args = QStringList{
"-d",
"--quiet",
"--batch",
"--use-agent", pgpg(tempPath)};
605 if (result != 0 || verifyOutput.isEmpty()) {
607 dbg() <<
"Verification failed for:" << tempPath;
609 QFile::remove(tempPath);
613 if (verifyOutput.trimmed() != local_lastDecrypt.trimmed()) {
615 dbg() <<
"Verification content mismatch for:" << tempPath;
617 QFile::remove(tempPath);
623 QString backupPath = fileName +
".reencrypt.bak";
624 if (!QFile::rename(fileName, backupPath)) {
626 dbg() <<
"Failed to backup original file:" << fileName;
628 QFile::remove(tempPath);
631 if (!QFile::rename(tempPath, fileName)) {
633 dbg() <<
"Failed to rename temp file to:" << fileName;
636 QFile::rename(backupPath, fileName);
637 QFile::remove(tempPath);
639 tr(
"Re-encryption failed"),
640 tr(
"Failed to replace %1. Original has been restored.").arg(fileName));
644 QFile::remove(backupPath);
648 {
"add", pgit(fileName)});
653 {
"commit", pgit(fileName),
"-m",
654 "Re-encrypt for " + path +
" using QtPass."});
669 emit
statusMsg(tr(
"Creating backup commit"), 2000);
675 tr(
"Backup commit failed"),
676 tr(
"Could not inspect git status. Re-encryption was aborted."));
679 if (!statusOut.trimmed().isEmpty()) {
682 git, {
"commit",
"-m",
"Backup before re-encryption"}) != 0) {
683 emit
critical(tr(
"Backup commit failed"),
684 tr(
"Re-encryption was aborted because a git backup could "
706 emit
statusMsg(tr(
"Re-encrypting from folder %1").arg(dir), 3000);
709 emit
statusMsg(tr(
"Updating password-store"), 2000);
720 QDirIterator gpgFiles(dir, QStringList() <<
"*.gpg", QDir::Files,
721 QDirIterator::Subdirectories);
722 QStringList gpgIdFilesVerified;
724 int successCount = 0;
726 while (gpgFiles.hasNext()) {
727 QString fileName = gpgFiles.next();
728 if (gpgFiles.fileInfo().path() != currentDir.path()) {
733 if (gpgId.isEmpty() && !gpgIdFilesVerified.isEmpty()) {
734 emit
critical(tr(
"GPG ID verification failed"),
735 tr(
"Could not verify .gpg-id for directory."));
741 if (actualKeys != gpgId) {
746 emit
critical(tr(
"Re-encryption failed"),
747 tr(
"Failed to re-encrypt %1").arg(fileName));
753 emit
statusMsg(tr(
"Re-encryption completed: %1 succeeded, %2 failed")
759 tr(
"Re-encryption completed: %1 files re-encrypted").arg(successCount),
764 emit
statusMsg(tr(
"Updating password-store"), 2000);
785 const QString &dest,
bool force)
787 QFileInfo srcFileInfo(src);
788 QFileInfo destFileInfo(dest);
790 QString srcFileBaseName = srcFileInfo.fileName();
792 if (srcFileInfo.isFile()) {
793 if (destFileInfo.isFile()) {
796 dbg() <<
"Destination file already exists";
801 }
else if (destFileInfo.isDir()) {
802 destFile = QDir(dest).filePath(srcFileBaseName);
807 if (destFile.endsWith(
".gpg", Qt::CaseInsensitive)) {
810 destFile.append(
".gpg");
811 }
else if (srcFileInfo.isDir()) {
812 if (destFileInfo.isDir()) {
813 destFile = QDir(dest).filePath(srcFileBaseName);
814 }
else if (destFileInfo.isFile()) {
816 dbg() <<
"Destination is a file";
824 dbg() <<
"Source file does not exist";
850 args << pgit(destFile);
858 QString message = QString(
"Moved for %1 to %2 using QtPass.");
859 message = message.arg(relSrc, relDest);
878 if (destFile.isEmpty()) {
883 dbg() <<
"Move Source: " << src;
884 dbg() <<
"Move Destination: " << destFile;
892 qDir.remove(destFile);
894 qDir.rename(src, destFile);
912 QFileInfo destFileInfo(dest);
924 QString message = QString(
"Copied from %1 to %2 using QtPass.");
925 message = message.arg(src, dest);
932 QFile::copy(src, dest);
935 if (destFileInfo.isDir()) {
937 }
else if (destFileInfo.isFile()) {
947 bool readStdout,
bool readStderr) {
949 readStdout, readStderr);
957 bool readStdout,
bool readStderr) {
959 readStdout, readStderr);
973 const QString &err) {
975 dbg() <<
"Imitate Pass";
977 static QString transactionOutput;
979 transactionOutput.append(out);
987 id =
exec.cancelNext();
991 dbg() <<
"No such transaction!";
999 transactionOutput.clear();
1012 const QStringList &args, QString input,
1013 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.
Stores key info lines including validity, creation date and more.