From f386cc08a2d4de3e9d51f78479eb1c6c4ca1c7f2 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Fri, 16 Jun 2023 19:26:02 +0200 Subject: [PATCH 1/9] 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 6ffd46c1d..43a126b3f 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 4a711b034..72b5f809a 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 a0f162b07..4460d4bf4 100644 --- a/src/configdialog.h +++ b/src/configdialog.h @@ -31,7 +31,7 @@ class ConfigDialog : public QDialog { 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 slots: 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 874bbb3f5..68281ca7c 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 60b3d0caa..e273dbb1d 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 f52c27a53..8509161f2 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 b32a6525f..83117021d 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 e3d5b3f08..d96c70c9a 100644 --- a/src/qtpasssettings.h +++ b/src/qtpasssettings.h @@ -108,6 +108,10 @@ class QtPassSettings : public QSettings { 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 @@ class QtPassSettings : public QSettings { 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 863d110f6..00b004fd8 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 bb237ab31..f95aeba21 100644 --- a/src/settingsconstants.h +++ b/src/settingsconstants.h @@ -31,6 +31,7 @@ class SettingsConstants { 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; From 88bb093dfacab7cc2d2ed8dbb25cb6fa8a1306de Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Sun, 18 Jun 2023 21:11:54 +0200 Subject: [PATCH 2/9] 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 83117021d..9a95c96bf 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()); From 3f25dc61554f3b8764d9d87bf0096bf1695d4cf5 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Tue, 20 Jun 2023 18:28:53 +0200 Subject: [PATCH 3/9] 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 72b5f809a..3794c6f1e 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 e273dbb1d..960fc62d6 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 8509161f2..3848661f1 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 9a95c96bf..e76ae8864 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 d96c70c9a..d63f37fd5 100644 --- a/src/qtpasssettings.h +++ b/src/qtpasssettings.h @@ -215,7 +215,8 @@ class QtPassSettings : public QSettings { 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(); From 52a5be3d1086275c02ee63a3f25dd0eeb15e55e7 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Mon, 26 Jun 2023 12:17:10 +0200 Subject: [PATCH 4/9] 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 1cdb85757..7b41975e9 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 4bfd3a94e..d29a25d47 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 3848661f1..4547ba9cb 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 3ef3c7dbb..fb254fde2 100644 --- a/src/pass.h +++ b/src/pass.h @@ -57,6 +57,7 @@ class Pass : public QObject { 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, From 88b636d6a27d46d529f5fb3e5ed17c5a4feb8d25 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Mon, 26 Jun 2023 15:51:23 +0200 Subject: [PATCH 5/9] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43a126b3f..32c1afacf 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**
From 60a7160ea7cf050aed20d209a5de85e8575f67a7 Mon Sep 17 00:00:00 2001 From: Alexander Blum Date: Mon, 26 Jun 2023 16:10:28 +0200 Subject: [PATCH 6/9] 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 7b41975e9..c6492813f 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; From a45da0a326fa7b3541d82bf7f4b93ec3751648e9 Mon Sep 17 00:00:00 2001 From: Thomas Mielke Date: Sun, 9 Jul 2023 14:25:06 +0200 Subject: [PATCH 7/9] 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 c6492813f..307670de6 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 4547ba9cb..fdc8fdc90 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; From 4dbfebf9471c75d61f006b3fdb2c3e3c645f2e95 Mon Sep 17 00:00:00 2001 From: Sune Vuorela Date: Mon, 21 Aug 2023 16:20:32 +0200 Subject: [PATCH 8/9] Restore licensing info for QProgressIndicator When importing QProgressInformation many years ago, the licensing information for that class was not included. Add it now. It doesn't look like the files has received copyright-worthy changes since the import. --- src/qprogressindicator.cpp | 26 ++++++++++++++++++++++++++ src/qprogressindicator.h | 27 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/qprogressindicator.cpp b/src/qprogressindicator.cpp index ae90f939b..1a47a5247 100644 --- a/src/qprogressindicator.cpp +++ b/src/qprogressindicator.cpp @@ -1,3 +1,29 @@ +/* + * This code is based on https://github.com/mojocorp/QProgressIndicator + * and published under + * + * The MIT License (MIT) + * + * Copyright (c) 2011 Morgan Leborgne + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ #include "qprogressindicator.h" #include diff --git a/src/qprogressindicator.h b/src/qprogressindicator.h index f3f61e667..dff39b714 100644 --- a/src/qprogressindicator.h +++ b/src/qprogressindicator.h @@ -1,3 +1,30 @@ +/* + * This code is based on https://github.com/mojocorp/QProgressIndicator + * and published under + * + * The MIT License (MIT) + * + * Copyright (c) 2011 Morgan Leborgne + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + #ifndef QPROGRESSINDICATOR_H_ #define QPROGRESSINDICATOR_H_ From c323a730f105e4e1e1017a4d387d4874ee2a386b Mon Sep 17 00:00:00 2001 From: Sune Vuorela Date: Thu, 24 Aug 2023 09:33:46 +0200 Subject: [PATCH 9/9] Fix taborder and add buddies in keygen dialog Tab was jumping weird, and clicking on labels didn't behave --- src/keygendialog.ui | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/keygendialog.ui b/src/keygendialog.ui index f6ec8b13f..4f3647d58 100644 --- a/src/keygendialog.ui +++ b/src/keygendialog.ui @@ -7,7 +7,7 @@ 0 0 606 - 480 + 497 @@ -72,18 +72,6 @@ - - 0 - - - 0 - - - 0 - - - 0 - @@ -101,6 +89,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + email + @@ -130,6 +121,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + name + @@ -159,6 +153,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + passphrase1 + @@ -203,6 +200,13 @@ + + + + Repeat pass + + +
@@ -289,6 +293,7 @@ Expire-Date: 0 email + name passphrase1 passphrase2 checkBox