From f386cc08a2d4de3e9d51f78479eb1c6c4ca1c7f2 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Fri, 16 Jun 2023 19:26:02 +0200 Subject: Add pass store signing key feature --- README.md | 1 + src/configdialog.cpp | 35 +++++++++++++++++++++++------------ src/configdialog.h | 4 ++-- src/configdialog.ui | 5 +++++ src/mainwindow.cpp | 9 ++++++--- src/pass.cpp | 23 ++++++++++++++++++++++- src/qtpasssettings.cpp | 31 +++++++++++++++++++++++-------- src/qtpasssettings.h | 8 ++++++-- src/settingsconstants.cpp | 1 + src/settingsconstants.h | 1 + 10 files changed, 90 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 6ffd46c1..43a126b3 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ qmake && make && make install ## Using profiles Profiles allow to group passwords. Each profile might use a different git repository and/or different gpg key. +Each profile also can be associated with a pass store singing key to verify the detached .gpg-id signature (pass only). A typical use case is to separate personal and work passwords. > **Hint**
diff --git a/src/configdialog.cpp b/src/configdialog.cpp index 4a711b03..72b5f809 100644 --- a/src/configdialog.cpp +++ b/src/configdialog.cpp @@ -97,7 +97,7 @@ ConfigDialog::ConfigDialog(MainWindow *parent) useTemplate(QtPassSettings::isUseTemplate()); ui->profileTable->verticalHeader()->hide(); - ui->profileTable->horizontalHeader()->setStretchLastSection(true); + ui->profileTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); ui->label->setText(ui->label->text() + VERSION); ui->comboBoxClipboard->clear(); @@ -170,7 +170,7 @@ void ConfigDialog::validate(QTableWidgetItem *item) { for (int j = 0; j < ui->profileTable->columnCount(); j++) { QTableWidgetItem *_item = ui->profileTable->item(i, j); - if (_item->text().isEmpty()) { + if (_item->text().isEmpty() && j != 2) { _item->setBackground(Qt::red); status = false; break; @@ -181,7 +181,7 @@ void ConfigDialog::validate(QTableWidgetItem *item) { break; } } else { - if (item->text().isEmpty()) { + if (item->text().isEmpty() && item->column() != 2) { item->setBackground(Qt::red); status = false; } @@ -460,8 +460,8 @@ void ConfigDialog::genKey(QString batch, QDialog *dialog) { * @param profiles * @param profile */ -void ConfigDialog::setProfiles(QHash profiles, - QString profile) { +void ConfigDialog::setProfiles(QHash> profiles, + QString currentProfile) { // dbg()<< profiles; if (profiles.contains("")) { profiles.remove(""); @@ -469,15 +469,19 @@ void ConfigDialog::setProfiles(QHash profiles, } ui->profileTable->setRowCount(profiles.count()); - QHashIterator i(profiles); + QHashIterator> i(profiles); int n = 0; while (i.hasNext()) { i.next(); if (!i.value().isEmpty() && !i.key().isEmpty()) { - ui->profileTable->setItem(n, 0, new QTableWidgetItem(i.key())); - ui->profileTable->setItem(n, 1, new QTableWidgetItem(i.value())); + ui->profileTable->setItem( + n, 0, new QTableWidgetItem(i.key())); + ui->profileTable->setItem( + n, 1, new QTableWidgetItem(i.value().value("path"))); + ui->profileTable->setItem( + n, 2, new QTableWidgetItem(i.value().value("signingKey"))); // dbg()<< "naam:" + i.key(); - if (i.key() == profile) + if (i.key() == currentProfile) ui->profileTable->selectRow(n); } ++n; @@ -488,17 +492,23 @@ void ConfigDialog::setProfiles(QHash profiles, * @brief ConfigDialog::getProfiles return profile list. * @return */ -QHash ConfigDialog::getProfiles() { - QHash profiles; +QHash> ConfigDialog::getProfiles() { + QHash> profiles; // Check? for (int i = 0; i < ui->profileTable->rowCount(); ++i) { + QHash profile; QTableWidgetItem *pathItem = ui->profileTable->item(i, 1); if (nullptr != pathItem) { QTableWidgetItem *item = ui->profileTable->item(i, 0); if (item == nullptr) { continue; } - profiles.insert(item->text(), pathItem->text()); + profile["path"] = pathItem->text(); + QTableWidgetItem *signingKeyItem = ui->profileTable->item(i, 2); + if (nullptr != signingKeyItem) { + profile["signingKey"] = signingKeyItem->text(); + } + profiles.insert(item->text(), profile); } } return profiles; @@ -512,6 +522,7 @@ void ConfigDialog::on_addButton_clicked() { ui->profileTable->insertRow(n); ui->profileTable->setItem(n, 0, new QTableWidgetItem()); ui->profileTable->setItem(n, 1, new QTableWidgetItem(ui->storePath->text())); + ui->profileTable->setItem(n, 2, new QTableWidgetItem()); ui->profileTable->selectRow(n); ui->deleteButton->setEnabled(true); diff --git a/src/configdialog.h b/src/configdialog.h index a0f162b0..4460d4bf 100644 --- a/src/configdialog.h +++ b/src/configdialog.h @@ -31,7 +31,7 @@ public: void useSelection(bool useSelection); void useAutoclear(bool useAutoclear); void useAutoclearPanel(bool useAutoclearPanel); - QHash getProfiles(); + QHash> getProfiles(); void wizard(); void genKey(QString, QDialog *); void useTrayIcon(bool useSystray); @@ -76,7 +76,7 @@ private: QStringList getSecretKeys(); void setGitPath(QString); - void setProfiles(QHash, QString); + void setProfiles(QHash>, QString); void usePass(bool usePass); void setGroupBoxState(); diff --git a/src/configdialog.ui b/src/configdialog.ui index 874bbb3f..68281ca7 100644 --- a/src/configdialog.ui +++ b/src/configdialog.ui @@ -913,6 +913,11 @@ Path + + + Signing Key + + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 60b3d0ca..e273dbb1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -766,7 +766,7 @@ void MainWindow::generateKeyPair(QString batch, QDialog *keygenWindow) { * select a more appropriate one to view too */ void MainWindow::updateProfileBox() { - QHash profiles = QtPassSettings::getProfiles(); + QHash> profiles = QtPassSettings::getProfiles(); if (profiles.isEmpty()) { ui->profileWidget->hide(); @@ -774,7 +774,7 @@ void MainWindow::updateProfileBox() { ui->profileWidget->show(); ui->profileBox->setEnabled(profiles.size() > 1); ui->profileBox->clear(); - QHashIterator i(profiles); + QHashIterator> i(profiles); while (i.hasNext()) { i.next(); if (!i.key().isEmpty()) @@ -800,7 +800,10 @@ void MainWindow::on_profileBox_currentIndexChanged(QString name) { QtPassSettings::setProfile(name); - QtPassSettings::setPassStore(QtPassSettings::getProfiles()[name]); + QtPassSettings::setPassStore( + QtPassSettings::getProfiles().value(name).value("path")); + QtPassSettings::setPassSigningKey( + QtPassSettings::getProfiles().value(name).value("signingKey")); ui->statusBar->showMessage(tr("Profile changed to %1").arg(name), 2000); QtPassSettings::getPass()->updateEnv(); diff --git a/src/pass.cpp b/src/pass.cpp index f52c27a5..8509161f 100644 --- a/src/pass.cpp +++ b/src/pass.cpp @@ -242,8 +242,29 @@ void Pass::finished(int id, int exitCode, const QString &out, * switching profiles) */ void Pass::updateEnv() { - QStringList store = env.filter("PASSWORD_STORE_DIR="); + // put PASSWORD_STORE_SIGNING_KEY in env + QStringList envSigningKey = env.filter("PASSWORD_STORE_SIGNING_KEY="); + QString currentSigningKey = QtPassSettings::getPassSigningKey(); + if (envSigningKey.isEmpty()) { + if (!currentSigningKey.isEmpty()) { + // dbg()<< "Added + // PASSWORD_STORE_SIGNING_KEY with" + currentSigningKey; + env.append("PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey); + } + } else { + if (currentSigningKey.isEmpty()) { + // dbg() << "Removed + // PASSWORD_STORE_SIGNING_KEY"; + env.removeAll(envSigningKey.first()); + } else { + // dbg()<< "Update + // PASSWORD_STORE_SIGNING_KEY with " + currentSigningKey; + env.replaceInStrings(envSigningKey.first(), + "PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey); + } + } // put PASSWORD_STORE_DIR in env + QStringList store = env.filter("PASSWORD_STORE_DIR="); if (store.isEmpty()) { // dbg()<< "Added // PASSWORD_STORE_DIR"; diff --git a/src/qtpasssettings.cpp b/src/qtpasssettings.cpp index b32a6525..83117021 100644 --- a/src/qtpasssettings.cpp +++ b/src/qtpasssettings.cpp @@ -58,13 +58,18 @@ void QtPassSettings::setPasswordConfiguration( config.Characters[PasswordConfiguration::CUSTOM]); } -QHash QtPassSettings::getProfiles() { +QHash> QtPassSettings::getProfiles() { getInstance()->beginGroup(SettingsConstants::profile); - QStringList childrenKeys = getInstance()->childKeys(); - QHash profiles; - foreach (QString key, childrenKeys) { - profiles.insert(key, getInstance()->value(key).toString()); + QStringList childGroups = getInstance()->childGroups(); + QHash> profiles; + foreach (QString group, childGroups) { + QHash profile; + profile.insert("path", getInstance()->value(group + "/path").toString()); + profile.insert("signingKey", + getInstance()->value(group + "/signingKey").toString()); + // profiles.insert(group, getInstance()->value(group).toString()); + profiles.insert(group, profile); } getInstance()->endGroup(); @@ -72,13 +77,14 @@ QHash QtPassSettings::getProfiles() { return profiles; } -void QtPassSettings::setProfiles(const QHash &profiles) { +void QtPassSettings::setProfiles(const QHash> &profiles) { getInstance()->remove(SettingsConstants::profile); getInstance()->beginGroup(SettingsConstants::profile); - QHash::const_iterator i = profiles.begin(); + QHash>::const_iterator i = profiles.begin(); for (; i != profiles.end(); ++i) { - getInstance()->setValue(i.key(), i.value()); + getInstance()->setValue(i.key() + "/path", i.value().value("path")); + getInstance()->setValue(i.key() + "/signingKey", i.value().value("signingKey")); } getInstance()->endGroup(); @@ -303,6 +309,15 @@ void QtPassSettings::setPassStore(const QString &passStore) { getInstance()->setValue(SettingsConstants::passStore, passStore); } +QString QtPassSettings::getPassSigningKey(const QString &defaultValue) { + return getInstance() + ->value(SettingsConstants::passSigningKey, defaultValue) + .toString(); +} +void QtPassSettings::setPassSigningKey(const QString &passSigningKey) { + getInstance()->setValue(SettingsConstants::passSigningKey, passSigningKey); +} + void QtPassSettings::initExecutables() { QString passExecutable = QtPassSettings::getPassExecutable(Util::findBinaryInPath("pass")); diff --git a/src/qtpasssettings.h b/src/qtpasssettings.h index e3d5b3f0..d96c70c9 100644 --- a/src/qtpasssettings.h +++ b/src/qtpasssettings.h @@ -108,6 +108,10 @@ public: getPassStore(const QString &defaultValue = QVariant().toString()); static void setPassStore(const QString &passStore); + static QString + getPassSigningKey(const QString &defaultValue = QVariant().toString()); + static void setPassSigningKey(const QString &passSigningKey); + static void initExecutables(); static QString getPassExecutable(const QString &defaultValue = QVariant().toString()); @@ -210,8 +214,8 @@ public: isTemplateAllFields(const bool &defaultValue = QVariant().toBool()); static void setTemplateAllFields(const bool &templateAllFields); - static QHash getProfiles(); - static void setProfiles(const QHash &profiles); + static QHash> getProfiles(); + static void setProfiles(const QHash> &profiles); static Pass *getPass(); static RealPass *getRealPass(); diff --git a/src/settingsconstants.cpp b/src/settingsconstants.cpp index 863d110f..00b004fd 100644 --- a/src/settingsconstants.cpp +++ b/src/settingsconstants.cpp @@ -32,6 +32,7 @@ const QString SettingsConstants::displayAsIs = "displayAsIs"; const QString SettingsConstants::noLineWrapping = "noLineWrapping"; const QString SettingsConstants::addGPGId = "addGPGId"; const QString SettingsConstants::passStore = "passStore"; +const QString SettingsConstants::passSigningKey = "passSigningKey"; const QString SettingsConstants::passExecutable = "passExecutable"; const QString SettingsConstants::gitExecutable = "gitExecutable"; const QString SettingsConstants::gpgExecutable = "gpgExecutable"; diff --git a/src/settingsconstants.h b/src/settingsconstants.h index bb237ab3..f95aeba2 100644 --- a/src/settingsconstants.h +++ b/src/settingsconstants.h @@ -31,6 +31,7 @@ public: const static QString noLineWrapping; const static QString addGPGId; const static QString passStore; + const static QString passSigningKey; const static QString passExecutable; const static QString gitExecutable; const static QString gpgExecutable; -- cgit v1.2.3 From 88bb093dfacab7cc2d2ed8dbb25cb6fa8a1306de Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Sun, 18 Jun 2023 21:11:54 +0200 Subject: Add migration for profile settings from 'profile/name'->path to 'profile/name/path'->path --- src/qtpasssettings.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/qtpasssettings.cpp b/src/qtpasssettings.cpp index 83117021..9a95c96b 100644 --- a/src/qtpasssettings.cpp +++ b/src/qtpasssettings.cpp @@ -60,9 +60,21 @@ void QtPassSettings::setPasswordConfiguration( QHash> QtPassSettings::getProfiles() { getInstance()->beginGroup(SettingsConstants::profile); + QHash> profiles; + + // migration from version <= v1.3.2: profiles datastructure + QStringList childKeys = getInstance()->childKeys(); + if (!childKeys.empty()) { + foreach (QString key, childKeys) { + QHash profile; + profile.insert("path", getInstance()->value(key).toString()); + profile.insert("signingKey", ""); + profiles.insert(key, profile); + } + } + // /migration from version <= v1.3.2 QStringList childGroups = getInstance()->childGroups(); - QHash> profiles; foreach (QString group, childGroups) { QHash profile; profile.insert("path", getInstance()->value(group + "/path").toString()); -- cgit v1.2.3 From 3f25dc61554f3b8764d9d87bf0096bf1695d4cf5 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Tue, 20 Jun 2023 18:28:53 +0200 Subject: fixes clang-format errors --- src/configdialog.cpp | 12 ++++++------ src/mainwindow.cpp | 3 ++- src/pass.cpp | 2 +- src/qtpasssettings.cpp | 6 ++++-- src/qtpasssettings.h | 3 ++- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/configdialog.cpp b/src/configdialog.cpp index 72b5f809..3794c6f1 100644 --- a/src/configdialog.cpp +++ b/src/configdialog.cpp @@ -97,7 +97,8 @@ ConfigDialog::ConfigDialog(MainWindow *parent) useTemplate(QtPassSettings::isUseTemplate()); ui->profileTable->verticalHeader()->hide(); - ui->profileTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + ui->profileTable->horizontalHeader()->setSectionResizeMode( + 1, QHeaderView::Stretch); ui->label->setText(ui->label->text() + VERSION); ui->comboBoxClipboard->clear(); @@ -474,12 +475,11 @@ void ConfigDialog::setProfiles(QHash> profiles, while (i.hasNext()) { i.next(); if (!i.value().isEmpty() && !i.key().isEmpty()) { + ui->profileTable->setItem(n, 0, new QTableWidgetItem(i.key())); + ui->profileTable->setItem(n, 1, + new QTableWidgetItem(i.value().value("path"))); ui->profileTable->setItem( - n, 0, new QTableWidgetItem(i.key())); - ui->profileTable->setItem( - n, 1, new QTableWidgetItem(i.value().value("path"))); - ui->profileTable->setItem( - n, 2, new QTableWidgetItem(i.value().value("signingKey"))); + n, 2, new QTableWidgetItem(i.value().value("signingKey"))); // dbg()<< "naam:" + i.key(); if (i.key() == currentProfile) ui->profileTable->selectRow(n); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e273dbb1..960fc62d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -766,7 +766,8 @@ void MainWindow::generateKeyPair(QString batch, QDialog *keygenWindow) { * select a more appropriate one to view too */ void MainWindow::updateProfileBox() { - QHash> profiles = QtPassSettings::getProfiles(); + QHash> profiles = + QtPassSettings::getProfiles(); if (profiles.isEmpty()) { ui->profileWidget->hide(); diff --git a/src/pass.cpp b/src/pass.cpp index 8509161f..3848661f 100644 --- a/src/pass.cpp +++ b/src/pass.cpp @@ -260,7 +260,7 @@ void Pass::updateEnv() { // dbg()<< "Update // PASSWORD_STORE_SIGNING_KEY with " + currentSigningKey; env.replaceInStrings(envSigningKey.first(), - "PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey); + "PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey); } } // put PASSWORD_STORE_DIR in env diff --git a/src/qtpasssettings.cpp b/src/qtpasssettings.cpp index 9a95c96b..e76ae886 100644 --- a/src/qtpasssettings.cpp +++ b/src/qtpasssettings.cpp @@ -89,14 +89,16 @@ QHash> QtPassSettings::getProfiles() { return profiles; } -void QtPassSettings::setProfiles(const QHash> &profiles) { +void QtPassSettings::setProfiles( + const QHash> &profiles) { getInstance()->remove(SettingsConstants::profile); getInstance()->beginGroup(SettingsConstants::profile); QHash>::const_iterator i = profiles.begin(); for (; i != profiles.end(); ++i) { getInstance()->setValue(i.key() + "/path", i.value().value("path")); - getInstance()->setValue(i.key() + "/signingKey", i.value().value("signingKey")); + getInstance()->setValue(i.key() + "/signingKey", + i.value().value("signingKey")); } getInstance()->endGroup(); diff --git a/src/qtpasssettings.h b/src/qtpasssettings.h index d96c70c9..d63f37fd 100644 --- a/src/qtpasssettings.h +++ b/src/qtpasssettings.h @@ -215,7 +215,8 @@ public: static void setTemplateAllFields(const bool &templateAllFields); static QHash> getProfiles(); - static void setProfiles(const QHash> &profiles); + static void + setProfiles(const QHash> &profiles); static Pass *getPass(); static RealPass *getRealPass(); -- cgit v1.2.3 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 From 88b636d6a27d46d529f5fb3e5ed17c5a4feb8d25 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Mon, 26 Jun 2023 15:51:23 +0200 Subject: Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43a126b3..32c1afac 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ qmake && make && make install ## Using profiles Profiles allow to group passwords. Each profile might use a different git repository and/or different gpg key. -Each profile also can be associated with a pass store singing key to verify the detached .gpg-id signature (pass only). +Each profile also can be associated with a pass store singing key to verify the detached .gpg-id signature. A typical use case is to separate personal and work passwords. > **Hint**
-- cgit v1.2.3 From 60a7160ea7cf050aed20d209a5de85e8575f67a7 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Mon, 26 Jun 2023 16:10:28 +0200 Subject: Fix SkipEmptyParts for different qt versions --- src/imitatepass.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/imitatepass.cpp b/src/imitatepass.cpp index 7b41975e..c6492813 100644 --- a/src/imitatepass.cpp +++ b/src/imitatepass.cpp @@ -172,8 +172,13 @@ void ImitatePass::Remove(QString file, bool isDir) { * path */ void ImitatePass::Init(QString path, const QList &users) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList signingKeys = QtPassSettings::getPassSigningKey().split(" ", Qt::SkipEmptyParts); +#else + QStringList signingKeys = + QtPassSettings::getPassSigningKey().split(" ", QString::SkipEmptyParts); +#endif QString gpgIdSigFile = path + ".gpg-id.sig"; bool addSigFile = false; if (!signingKeys.isEmpty()) { @@ -267,8 +272,13 @@ void ImitatePass::Init(QString path, const QList &users) { * @return was verification succesful? */ bool ImitatePass::verifyGpgIdFile(const QString &file) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList signingKeys = QtPassSettings::getPassSigningKey().split(" ", Qt::SkipEmptyParts); +#else + QStringList signingKeys = + QtPassSettings::getPassSigningKey().split(" ", QString::SkipEmptyParts); +#endif if (signingKeys.isEmpty()) return true; QString out; -- cgit v1.2.3 From a45da0a326fa7b3541d82bf7f4b93ec3751648e9 Mon Sep 17 00:00:00 2001 From: Thomas Mielke Date: Sun, 9 Jul 2023 14:25:06 +0200 Subject: solved two Windows-specific issues 1. harmoized path separators in Pass::getGpgIdPath() as the pass store path couldn't match with the gpgIdDir because of trailing backslashes so two absolute paths were concatenated, leading to checkmarks not set properly in usersdialog.cpp, for example. 2. added an optional \r in regex of ImitatePass::verifyGpgIdFile() to comply with Windows \r\n linebreaks as QRegularExpression::MultilineOption won't honor \r as part of a line separator. --- src/imitatepass.cpp | 2 +- src/pass.cpp | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/imitatepass.cpp b/src/imitatepass.cpp index c6492813..307670de 100644 --- a/src/imitatepass.cpp +++ b/src/imitatepass.cpp @@ -286,7 +286,7 @@ bool ImitatePass::verifyGpgIdFile(const QString &file) { 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})$", + "^\\[GNUPG:\\] VALIDSIG ([A-F0-9]{40}) .* ([A-F0-9]{40})\\r?$", QRegularExpression::MultilineOption); QRegularExpressionMatch m = re.match(out); if (!m.hasMatch()) diff --git a/src/pass.cpp b/src/pass.cpp index 4547ba9c..fdc8fdc9 100644 --- a/src/pass.cpp +++ b/src/pass.cpp @@ -284,13 +284,15 @@ void Pass::updateEnv() { * @return path to the gpgid file. */ QString Pass::getGpgIdPath(QString for_file) { - QDir gpgIdDir(QFileInfo(for_file.startsWith(QtPassSettings::getPassStore()) - ? for_file - : QtPassSettings::getPassStore() + for_file) - .absoluteDir()); + QString passStore = + QDir::fromNativeSeparators(QtPassSettings::getPassStore()); + QDir gpgIdDir( + QFileInfo(QDir::fromNativeSeparators(for_file).startsWith(passStore) + ? for_file + : QtPassSettings::getPassStore() + for_file) + .absoluteDir()); bool found = false; - while (gpgIdDir.exists() && - gpgIdDir.absolutePath().startsWith(QtPassSettings::getPassStore())) { + while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) { if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) { found = true; break; -- cgit v1.2.3