From 52a5be3d1086275c02ee63a3f25dd0eeb15e55e7 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Mon, 26 Jun 2023 12:17:10 +0200 Subject: Add pass store signing key feature for native gpg --- src/imitatepass.cpp | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/imitatepass.h | 1 + src/pass.cpp | 39 ++++++++++++++-------- src/pass.h | 1 + 4 files changed, 121 insertions(+), 14 deletions(-) diff --git a/src/imitatepass.cpp b/src/imitatepass.cpp index 1cdb8575..7b41975e 100644 --- a/src/imitatepass.cpp +++ b/src/imitatepass.cpp @@ -89,6 +89,12 @@ void ImitatePass::OtpGenerate(QString file) { */ void ImitatePass::Insert(QString file, QString newValue, bool overwrite) { file = file + ".gpg"; + QString gpgIdPath = Pass::getGpgIdPath(file); + if (!verifyGpgIdFile(gpgIdPath)) { + emit critical(tr("Check .gpgid file signature!"), + tr("Signature for %1 is invalid.").arg(gpgIdPath)); + return; + } transactionHelper trans(this, PASS_INSERT); QStringList recipients = Pass::getRecipientList(file); if (recipients.isEmpty()) { @@ -166,6 +172,33 @@ void ImitatePass::Remove(QString file, bool isDir) { * path */ void ImitatePass::Init(QString path, const QList &users) { + QStringList signingKeys = + QtPassSettings::getPassSigningKey().split(" ", Qt::SkipEmptyParts); + QString gpgIdSigFile = path + ".gpg-id.sig"; + bool addSigFile = false; + if (!signingKeys.isEmpty()) { + QString out; + QStringList args = + QStringList{"--status-fd=1", "--list-secret-keys"} + signingKeys; + exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &out); + bool found = false; + for (auto &key : signingKeys) { + if (out.contains("[GNUPG:] KEY_CONSIDERED " + key)) { + found = true; + break; + } + } + if (!found) { + emit critical(tr("No signing key!"), + 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"; QFile gpgId(gpgIdFile); bool addFile = false; @@ -196,6 +229,20 @@ void ImitatePass::Init(QString path, const QList &users) { return; } + if (!signingKeys.isEmpty()) { + QStringList args; + for (auto &key : signingKeys) { + args.append(QStringList{"--default-key", key}); + } + args.append(QStringList{"--yes", "--detach-sign", gpgIdFile}); + exec.executeBlocking(QtPassSettings::getGpgExecutable(), args); + if (!verifyGpgIdFile(gpgIdFile)) { + emit critical(tr("Check .gpgid file signature!"), + tr("Signature for %1 is invalid.").arg(gpgIdFile)); + return; + } + } + if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit() && !QtPassSettings::getGitExecutable().isEmpty()) { if (addFile) @@ -203,10 +250,46 @@ void ImitatePass::Init(QString path, const QList &users) { QString commitPath = gpgIdFile; commitPath.replace(QRegularExpression("\\.gpg$"), ""); GitCommit(gpgIdFile, "Added " + commitPath + " using QtPass."); + if (!signingKeys.isEmpty()) { + if (addSigFile) + executeGit(GIT_ADD, {"add", pgit(gpgIdSigFile)}); + commitPath = gpgIdSigFile; + commitPath.replace(QRegularExpression("\\.gpg$"), ""); + GitCommit(gpgIdSigFile, "Added " + commitPath + " using QtPass."); + } } reencryptPath(path); } +/** + * @brief ImitatePass::verifyGpgIdFile verify detached gpgid file signature. + * @param file which gpgid file. + * @return was verification succesful? + */ +bool ImitatePass::verifyGpgIdFile(const QString &file) { + QStringList signingKeys = + QtPassSettings::getPassSigningKey().split(" ", Qt::SkipEmptyParts); + if (signingKeys.isEmpty()) + return true; + QString out; + QStringList args = + QStringList{"--verify", "--status-fd=1", pgpg(file) + ".sig", pgpg(file)}; + exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &out); + QRegularExpression re( + "^\\[GNUPG:\\] VALIDSIG ([A-F0-9]{40}) .* ([A-F0-9]{40})$", + 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; +} + /** * @brief ImitatePass::removeDir delete folder recursive. * @param dirName which folder. @@ -252,10 +335,21 @@ void ImitatePass::reencryptPath(const QString &dir) { QDir currentDir; QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files, QDirIterator::Subdirectories); + QStringList gpgIdFilesVerified; QStringList gpgId; while (gpgFiles.hasNext()) { QString fileName = gpgFiles.next(); if (gpgFiles.fileInfo().path() != currentDir.path()) { + QString gpgIdPath = Pass::getGpgIdPath(fileName); + if (!gpgIdFilesVerified.contains(gpgIdPath)) { + if (!verifyGpgIdFile(gpgIdPath)) { + emit critical(tr("Check .gpgid file signature!"), + tr("Signature for %1 is invalid.").arg(gpgIdPath)); + emit endReencryptPath(); + return; + } + gpgIdFilesVerified.append(gpgIdPath); + } gpgId = getRecipientList(fileName); gpgId.sort(); } diff --git a/src/imitatepass.h b/src/imitatepass.h index 4bfd3a94..d29a25d4 100644 --- a/src/imitatepass.h +++ b/src/imitatepass.h @@ -11,6 +11,7 @@ class ImitatePass : public Pass, private simpleTransaction { Q_OBJECT + bool verifyGpgIdFile(const QString &file); bool removeDir(const QString &dirName); void GitCommit(const QString &file, const QString &msg); diff --git a/src/pass.cpp b/src/pass.cpp index 3848661f..4547ba9c 100644 --- a/src/pass.cpp +++ b/src/pass.cpp @@ -279,27 +279,38 @@ void Pass::updateEnv() { } /** - * @brief Pass::getRecipientList return list of gpg-id's to encrypt for - * @param for_file which file (folder) would you like recepients for - * @return recepients gpg-id contents + * @brief Pass::getGpgIdPath return gpgid file path for some file (folder). + * @param for_file which file (folder) would you like the gpgid file path for. + * @return path to the gpgid file. */ -QStringList Pass::getRecipientList(QString for_file) { - QDir gpgIdPath(QFileInfo(for_file.startsWith(QtPassSettings::getPassStore()) - ? for_file - : QtPassSettings::getPassStore() + for_file) - .absoluteDir()); +QString Pass::getGpgIdPath(QString for_file) { + QDir gpgIdDir(QFileInfo(for_file.startsWith(QtPassSettings::getPassStore()) + ? for_file + : QtPassSettings::getPassStore() + for_file) + .absoluteDir()); bool found = false; - while (gpgIdPath.exists() && - gpgIdPath.absolutePath().startsWith(QtPassSettings::getPassStore())) { - if (QFile(gpgIdPath.absoluteFilePath(".gpg-id")).exists()) { + while (gpgIdDir.exists() && + gpgIdDir.absolutePath().startsWith(QtPassSettings::getPassStore())) { + if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) { found = true; break; } - if (!gpgIdPath.cdUp()) + if (!gpgIdDir.cdUp()) break; } - QFile gpgId(found ? gpgIdPath.absoluteFilePath(".gpg-id") - : QtPassSettings::getPassStore() + ".gpg-id"); + QString gpgIdPath(found ? gpgIdDir.absoluteFilePath(".gpg-id") + : QtPassSettings::getPassStore() + ".gpg-id"); + + return gpgIdPath; +} + +/** + * @brief Pass::getRecipientList return list of gpg-id's to encrypt for + * @param for_file which file (folder) would you like recepients for + * @return recepients gpg-id contents + */ +QStringList Pass::getRecipientList(QString for_file) { + QFile gpgId(getGpgIdPath(for_file)); if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) return QStringList(); QStringList recipients; diff --git a/src/pass.h b/src/pass.h index 3ef3c7db..fb254fde 100644 --- a/src/pass.h +++ b/src/pass.h @@ -57,6 +57,7 @@ public: QList listKeys(QStringList keystrings, bool secret = false); QList listKeys(QString keystring = "", bool secret = false); void updateEnv(); + static QString getGpgIdPath(QString for_file); static QStringList getRecipientList(QString for_file); // TODO(bezet): getRecipientString is useless, refactor static QStringList getRecipientString(QString for_file, -- cgit v1.2.3