Page MenuHome GnuPG

No OneTemporary

diff --git a/src/pass.cpp b/src/pass.cpp
index 6eff292..d8f7c61 100644
--- a/src/pass.cpp
+++ b/src/pass.cpp
@@ -1,441 +1,441 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2017 Jason A. Donenfeld <Jason@zx2c4.com>
SPDX-FileCopyrightText: 2020 Charlie Waters <cawiii@me.com>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "pass.h"
#include "gpgmehelpers.h"
#include "settings.h"
#include <KLocalizedString>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QGpgME/DecryptJob>
#include <QGpgME/EncryptJob>
#include <QGpgME/KeyListJob>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QSaveFile>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/encryptionresult.h>
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/keylistresult.h>
// TODO remove
#include <QMessageBox>
using namespace std;
// TODO remove
Q_REQUIRED_RESULT static bool ensurePassStore(const QString &file, const char *location)
{
if (file.startsWith(Settings::getPassStore())) {
return true;
}
QMessageBox::critical(nullptr,
QStringLiteral("error"),
QStringLiteral("file not in pass store called from %1 %2 %3").arg(QString::fromLocal8Bit(location), file, Settings::getPassStore()));
return false;
}
// TODO remove
Q_REQUIRED_RESULT static bool ensureSuffix(const QString &file, const char *location)
{
if (file.endsWith(QStringLiteral(".gpg"))) {
return true;
}
qWarning() << "file without suffix called from " << location;
QMessageBox::critical(nullptr,
QStringLiteral("error"),
QStringLiteral("file without suffix called from %1 %2").arg(QString::fromLocal8Bit(location), file));
return false;
}
/**
* @brief Pass::Pass wrapper for using either pass or the pass imitation
*/
Pass::Pass()
{
}
/**
* @brief Pass::Generate use either pwgen or internal password
* generator
* @param length of the desired password
* @param charset to use for generation
* @return the password
*/
QString Pass::Generate_b(unsigned int length, const QString &charset)
{
if (charset.length() > 0) {
return generateRandomPassword(charset, length);
} else {
Q_EMIT critical(i18n("No characters chosen"),
i18n("Can't generate password, there are no characters to choose from "
"set in the configuration!"));
}
return {};
}
/**
* @brief Pass::listKeys list users
* @param keystrings
* @param secret list private keys
* @return QList<UserInfo> users
*/
QList<UserInfo> Pass::listKeys(const QStringList &keystrings, bool secret)
{
auto job = protocol->keyListJob();
std::vector<GpgME::Key> keys;
job->addMode(GpgME::KeyListMode::WithSecret);
auto result = job->exec(keystrings, secret, keys);
if (!isSuccess(result.error())) {
return {};
}
QList<UserInfo> users;
for (const auto &key : keys) {
UserInfo ui;
ui.created = QDateTime::fromSecsSinceEpoch(key.subkey(0).creationTime());
ui.key_id = fromGpgmeCharStar(key.keyID());
ui.name = createCombinedNameString(key.userID(0));
ui.validity = key.userID(0).validityAsString();
ui.expiry = QDateTime::fromSecsSinceEpoch(key.subkey(0).expirationTime());
ui.have_secret = key.hasSecret();
users.append(ui);
}
return users;
}
/**
* @brief Pass::listKeys list users
* @param keystring
* @param secret list private keys
* @return QList<UserInfo> users
*/
QList<UserInfo> Pass::listKeys(const QString &keystring, bool secret)
{
return listKeys(QStringList(keystring), secret);
}
/**
* @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(const QString &for_file)
{
if (!ensurePassStore(for_file, Q_FUNC_INFO)) {
return {};
}
QDir gpgIdPath(QFileInfo(for_file).absoluteDir());
bool found = false;
while (gpgIdPath.exists() && gpgIdPath.absolutePath().startsWith(Settings::getPassStore())) {
if (QFile(gpgIdPath.absoluteFilePath(QStringLiteral(".gpg-id"))).exists()) {
found = true;
break;
}
if (!gpgIdPath.cdUp())
break;
}
QFile gpgId(found ? gpgIdPath.absoluteFilePath(QStringLiteral(".gpg-id")) : Settings::getPassStore() + QStringLiteral(".gpg-id"));
if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text))
return QStringList();
QStringList recipients;
while (!gpgId.atEnd()) {
QString recipient(QString::fromLocal8Bit(gpgId.readLine()));
recipient = recipient.trimmed();
if (!recipient.isEmpty())
recipients += recipient;
}
return recipients;
}
void Pass::Show(const QString &file)
{
if (!(ensureSuffix(file, Q_FUNC_INFO) && ensurePassStore(file, Q_FUNC_INFO))) {
return;
}
QFile f(file);
f.open(QIODevice::ReadOnly);
auto data = f.readAll();
auto job = protocol->decryptJob();
connect(job, &QGpgME::DecryptJob::result, this, [this](auto &&result, QByteArray plainText, QString auditLog, auto &&logError) {
if (isSuccess(result.error())) {
Q_EMIT finishedShow(QString::fromUtf8(plainText));
} else {
Q_EMIT errorString(QString::fromLocal8Bit(result.error().asString()));
Q_EMIT decryptionError();
}
// Q_EMIT log(auditLog);
Q_UNUSED(logError);
Q_UNUSED(auditLog);
});
job->start(data);
}
void Pass::Insert(const QString &file, const QString &newValue)
{
if (!(ensureSuffix(file, Q_FUNC_INFO) && ensurePassStore(file, Q_FUNC_INFO))) {
return;
}
QStringList recipients = Pass::getRecipientList(file);
auto job = protocol->encryptJob();
std::vector<GpgME::Key> keys;
auto ctx = QGpgME::Job::context(job);
- for (const auto &keyId : recipients) {
+ for (const auto &keyId : std::as_const(recipients)) {
GpgME::Error error;
auto key = ctx->key(keyId.toUtf8().data(), error, false);
if (!error && !key.isNull()) {
keys.push_back(key);
}
}
if (keys.empty()) {
Q_EMIT critical(i18n("Can not edit"),
i18n("Could not read encryption key to use, .gpg-id "
"file missing or invalid."));
return;
}
connect(job, &QGpgME::EncryptJob::result, this, [this, file](auto &&result, const QByteArray &ciphertext, const QString &log, auto &&auditResult) {
if (isSuccess(result.error())) {
QSaveFile f(file);
bool open = f.open(QIODevice::WriteOnly | QIODevice::Truncate);
if (!open) {
Q_EMIT errorString(QStringLiteral("File open failed: %1").arg(f.errorString()));
return;
}
f.write(ciphertext);
if (f.error() == QFileDevice::NoError) {
f.commit();
Q_EMIT finishedInsert();
} else {
Q_EMIT errorString(QStringLiteral("File write failed: %1").arg(f.errorString()));
}
} else {
Q_EMIT errorString(QString::fromUtf8(result.error().asString()));
}
Q_UNUSED(log);
Q_UNUSED(auditResult);
});
job->start(keys, newValue.toUtf8());
}
void Pass::Remove(const QString &file, bool isDir)
{
if (!ensurePassStore(file, Q_FUNC_INFO)) {
return;
}
if (!isDir) {
if (!ensureSuffix(file, Q_FUNC_INFO)) {
return;
}
QFile(file).remove();
} else {
QDir dir(file);
dir.removeRecursively();
}
}
void Pass::Init(const QString &path, const QList<UserInfo> &users)
{
if (!ensurePassStore(path, Q_FUNC_INFO)) {
return;
}
QString gpgIdFile = path + QStringLiteral(".gpg-id");
QFile gpgId(gpgIdFile);
if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
Q_EMIT critical(i18n("Cannot update"), i18n("Failed to open .gpg-id for writing."));
return;
}
bool secret_selected = false;
for (const UserInfo &user : users) {
if (user.enabled) {
gpgId.write((user.key_id + QStringLiteral("\n")).toUtf8());
secret_selected |= user.have_secret;
}
}
gpgId.close();
if (!secret_selected) {
Q_EMIT critical(i18n("Check selected users!"),
i18n("None of the selected keys have a secret key available.\n"
"You will not be able to decrypt any newly added passwords!"));
return;
}
reencryptPath(path);
}
/**
* @brief ImitatePass::reencryptPath reencrypt all files under the chosen
* directory
*
* This is stil quite experimental..
* @param dir
*/
void Pass::reencryptPath(const QString &dir)
{
if (!ensurePassStore(dir, Q_FUNC_INFO)) {
return;
}
Q_EMIT startReencryptPath();
QDir currentDir;
QDirIterator gpgFiles(dir, QStringList() << QStringLiteral("*.gpg"), QDir::Files, QDirIterator::Subdirectories);
QStringList gpgId;
while (gpgFiles.hasNext()) {
QString fileName = gpgFiles.next();
if (gpgFiles.fileInfo().path() != currentDir.path()) {
gpgId = getRecipientList(fileName);
gpgId.sort();
}
QByteArray plainText;
QFile f(fileName);
if (!f.open(QIODevice::ReadOnly)) {
Q_EMIT errorString(QStringLiteral("Failed to open file: %1").arg(fileName));
continue;
}
QByteArray cipherText = f.readAll();
f.close();
auto decryptJob = protocol->decryptJob();
auto context = QGpgME::Job::context(decryptJob);
auto decryptResult = decryptJob->exec(cipherText, plainText);
if (!isSuccess(decryptResult.error())) {
Q_EMIT errorString(fromGpgmeCharStar(decryptResult.error().asString()));
continue;
}
auto actualRecipients = decryptResult.recipients();
QStringList actualIds;
for (auto &recipient : actualRecipients) {
GpgME::Error error;
auto key = context->key(recipient.keyID(), error, false);
if (!error) {
actualIds.append(fromGpgmeCharStar(key.keyID()));
}
}
actualIds.sort();
if (actualIds != gpgId) {
auto encryptJob = protocol->encryptJob();
std::vector<GpgME::Key> keys;
auto ctx = QGpgME::Job::context(encryptJob);
for (const auto &keyId : std::as_const(gpgId)) {
GpgME::Error error;
auto key = ctx->key(keyId.toUtf8().data(), error, false);
if (!error && !key.isNull()) {
keys.push_back(key);
}
}
auto encryptResult = encryptJob->exec(keys, plainText, false, cipherText);
if (!isSuccess(encryptResult.error())) {
Q_EMIT errorString(fromGpgmeCharStar(encryptResult.error().asString()));
continue;
}
QSaveFile save(fileName);
bool open = save.open(QIODevice::WriteOnly | QIODevice::Truncate);
if (!open) {
Q_EMIT errorString(QStringLiteral("open file failed %1").arg(fileName));
continue;
}
save.write(cipherText);
if (save.error() != QFileDevice::NoError) {
Q_EMIT errorString(QStringLiteral("Writing file failed: %1").arg(fileName));
continue;
} else {
save.commit();
}
}
}
Q_EMIT endReencryptPath();
}
void Pass::Move(const QString &src, const QString &dest, const bool force)
{
QFileInfo srcFileInfo(src);
QFileInfo destFileInfo(dest);
QString destFile;
QString srcFileBaseName = srcFileInfo.fileName();
if (srcFileInfo.isFile()) {
if (destFileInfo.isFile()) {
if (!force) {
return;
}
} else if (destFileInfo.isDir()) {
destFile = QDir(dest).filePath(srcFileBaseName);
} else {
destFile = dest;
}
if (destFile.endsWith(QStringLiteral(".gpg"), Qt::CaseInsensitive))
destFile.chop(4); // make sure suffix is lowercase
destFile.append(QStringLiteral(".gpg"));
} else if (srcFileInfo.isDir()) {
if (destFileInfo.isDir()) {
destFile = QDir(dest).filePath(srcFileBaseName);
} else if (destFileInfo.isFile()) {
return;
} else {
destFile = dest;
}
} else {
return;
}
QDir qDir;
if (force) {
qDir.remove(destFile);
}
qDir.rename(src, destFile);
}
void Pass::Copy(const QString &src, const QString &dest, const bool force)
{
QFileInfo destFileInfo(dest);
QDir qDir;
if (force) {
qDir.remove(dest);
}
if (!QFile::copy(src, dest)) {
Q_EMIT errorString(QStringLiteral("Failed to copy file"));
}
// reecrypt all files under the new folder
if (destFileInfo.isDir()) {
reencryptPath(destFileInfo.absoluteFilePath());
} else if (destFileInfo.isFile()) {
reencryptPath(destFileInfo.dir().path());
}
}
quint32 Pass::boundedRandom(quint32 bound)
{
if (bound < 2) {
return 0;
}
quint32 randval;
const quint32 max_mod_bound = (1 + ~bound) % bound;
do {
randval = QRandomGenerator::system()->generate();
} while (randval < max_mod_bound);
return randval % bound;
}
QString Pass::generateRandomPassword(const QString &charset, unsigned int length)
{
QString out;
for (unsigned int i = 0; i < length; ++i) {
out.append(charset.at(static_cast<int>(boundedRandom(static_cast<quint32>(charset.length())))));
}
return out;
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Feb 26, 6:26 PM (11 h, 41 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0b/c9/18338dda4ad101c36c111864b964

Event Timeline