Skip to content

Commit

Permalink
Merge pull request #634 from timegrid/feature-passstoresigningkey
Browse files Browse the repository at this point in the history
Add pass store signing key feature
  • Loading branch information
annejan authored Aug 31, 2023
2 parents 695a899 + a45da0a commit 58f886e
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 42 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
A typical use case is to separate personal and work passwords.

> **Hint**<br>
Expand Down
33 changes: 22 additions & 11 deletions src/configdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ 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();

Expand Down Expand Up @@ -170,7 +171,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;
Expand All @@ -181,7 +182,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;
}
Expand Down Expand Up @@ -460,24 +461,27 @@ void ConfigDialog::genKey(QString batch, QDialog *dialog) {
* @param profiles
* @param profile
*/
void ConfigDialog::setProfiles(QHash<QString, QString> profiles,
QString profile) {
void ConfigDialog::setProfiles(QHash<QString, QHash<QString, QString>> profiles,
QString currentProfile) {
// dbg()<< profiles;
if (profiles.contains("")) {
profiles.remove("");
// remove weird "" key value pairs
}

ui->profileTable->setRowCount(profiles.count());
QHashIterator<QString, QString> i(profiles);
QHashIterator<QString, QHash<QString, QString>> 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, 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;
Expand All @@ -488,17 +492,23 @@ void ConfigDialog::setProfiles(QHash<QString, QString> profiles,
* @brief ConfigDialog::getProfiles return profile list.
* @return
*/
QHash<QString, QString> ConfigDialog::getProfiles() {
QHash<QString, QString> profiles;
QHash<QString, QHash<QString, QString>> ConfigDialog::getProfiles() {
QHash<QString, QHash<QString, QString>> profiles;
// Check?
for (int i = 0; i < ui->profileTable->rowCount(); ++i) {
QHash<QString, QString> 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;
Expand All @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions src/configdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ConfigDialog : public QDialog {
void useSelection(bool useSelection);
void useAutoclear(bool useAutoclear);
void useAutoclearPanel(bool useAutoclearPanel);
QHash<QString, QString> getProfiles();
QHash<QString, QHash<QString, QString>> getProfiles();
void wizard();
void genKey(QString, QDialog *);
void useTrayIcon(bool useSystray);
Expand Down Expand Up @@ -76,7 +76,7 @@ private slots:
QStringList getSecretKeys();

void setGitPath(QString);
void setProfiles(QHash<QString, QString>, QString);
void setProfiles(QHash<QString, QHash<QString, QString>>, QString);
void usePass(bool usePass);

void setGroupBoxState();
Expand Down
5 changes: 5 additions & 0 deletions src/configdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,11 @@
<string>Path</string>
</property>
</column>
<column>
<property name="text">
<string>Signing Key</string>
</property>
</column>
</widget>
</item>
<item>
Expand Down
104 changes: 104 additions & 0 deletions src/imitatepass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -166,6 +172,38 @@ void ImitatePass::Remove(QString file, bool isDir) {
* path
*/
void ImitatePass::Init(QString path, const QList<UserInfo> &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()) {
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;
Expand Down Expand Up @@ -196,17 +234,72 @@ void ImitatePass::Init(QString path, const QList<UserInfo> &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)
executeGit(GIT_ADD, {"add", pgit(gpgIdFile)});
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) {
#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;
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})\\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;
}

/**
* @brief ImitatePass::removeDir delete folder recursive.
* @param dirName which folder.
Expand Down Expand Up @@ -252,10 +345,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();
}
Expand Down
1 change: 1 addition & 0 deletions src/imitatepass.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 7 additions & 3 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,15 +766,16 @@ void MainWindow::generateKeyPair(QString batch, QDialog *keygenWindow) {
* select a more appropriate one to view too
*/
void MainWindow::updateProfileBox() {
QHash<QString, QString> profiles = QtPassSettings::getProfiles();
QHash<QString, QHash<QString, QString>> profiles =
QtPassSettings::getProfiles();

if (profiles.isEmpty()) {
ui->profileWidget->hide();
} else {
ui->profileWidget->show();
ui->profileBox->setEnabled(profiles.size() > 1);
ui->profileBox->clear();
QHashIterator<QString, QString> i(profiles);
QHashIterator<QString, QHash<QString, QString>> i(profiles);
while (i.hasNext()) {
i.next();
if (!i.key().isEmpty())
Expand All @@ -800,7 +801,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();
Expand Down
Loading

0 comments on commit 58f886e

Please sign in to comment.