Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F36622916
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
14 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rGPGPASS GnuPG Password Manager
Event Timeline
Log In to Comment