summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Blum <alexander.blum@c3s.cc>2023-06-26 12:17:10 +0200
committerAlexander Blum <alexander.blum@c3s.cc>2023-06-26 12:17:10 +0200
commit52a5be3d1086275c02ee63a3f25dd0eeb15e55e7 (patch)
tree91717c0a2ba46da3abc9bfd55df3e171858fd828
parent3f25dc61554f3b8764d9d87bf0096bf1695d4cf5 (diff)
Add pass store signing key feature for native gpg
-rw-r--r--src/imitatepass.cpp94
-rw-r--r--src/imitatepass.h1
-rw-r--r--src/pass.cpp39
-rw-r--r--src/pass.h1
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<UserInfo> &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<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)
@@ -203,11 +250,47 @@ void ImitatePass::Init(QString path, const QList<UserInfo> &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.
* @return was removal succesful?
@@ -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<UserInfo> listKeys(QStringList keystrings, bool secret = false);
QList<UserInfo> 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,