/**
* This file is part of GPGFrontend.
*
* GPGFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Foobar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar. If not, see .
*
* The initial version of the source code is inherited from gpg4usb-team.
* Their source code version also complies with GNU General Public License.
*
* The source code version of this software was modified and released
* by Saturneric starting on May 12, 2021.
*
*/
#include "gpg/GpgContext.h"
#include "ui/WaitingDialog.h"
#include
#include /* contains read/write */
#ifdef _WIN32
#include
#endif
#define INT2VOIDP(i) (void*)(uintptr_t)(i)
namespace GpgFrontend {
/**
* Constructor
* Set up gpgme-context, set paths to app-run path
*/
GpgContext::GpgContext() {
gpgme_ctx_t _p_ctx;
err = gpgme_new(&_p_ctx);
check_gpg_error(err);
_ctx_ref = CtxRefHandler(_p_ctx, [&](gpgme_ctx_t ctx) { gpgme_release(ctx); });
gpgme_engine_info_t engineInfo;
engineInfo = gpgme_ctx_get_engine_info(*this);
// Check ENV before running
bool check_pass = false, find_openpgp = false, find_gpgconf = false, find_assuan = false, find_cms = false;
while (engineInfo != nullptr) {
qDebug() << gpgme_get_protocol_name(engineInfo->protocol) << engineInfo->file_name << engineInfo->protocol
<< engineInfo->home_dir << engineInfo->version;
if (engineInfo->protocol == GPGME_PROTOCOL_GPGCONF && strcmp(engineInfo->version, "1.0.0") != 0)
find_gpgconf = true;
if (engineInfo->protocol == GPGME_PROTOCOL_OpenPGP && strcmp(engineInfo->version, "1.0.0") != 0)
find_openpgp = true, info.appPath = engineInfo->file_name;
if (engineInfo->protocol == GPGME_PROTOCOL_CMS && strcmp(engineInfo->version, "1.0.0") != 0)
find_cms = true;
if (engineInfo->protocol == GPGME_PROTOCOL_ASSUAN)
find_assuan = true;
engineInfo = engineInfo->next;
}
if (find_gpgconf && find_openpgp && find_cms && find_assuan)
check_pass = true;
if (!check_pass) {
good = false;
return;
} else good = true;
/** Setting the output type must be done at the beginning */
/** think this means ascii-armor --> ? */
gpgme_set_armor(*this, 1) ;
// Speed up loading process
gpgme_set_offline(*this, 1);
/** passphrase-callback */
gpgme_set_passphrase_cb(*this, passphraseCb, this);
gpgme_set_keylist_mode(*this,
GPGME_KEYLIST_MODE_LOCAL
| GPGME_KEYLIST_MODE_WITH_SECRET
| GPGME_KEYLIST_MODE_SIGS
| GPGME_KEYLIST_MODE_SIG_NOTATIONS
| GPGME_KEYLIST_MODE_WITH_TOFU);
/** check if app is called with -d from command line */
if (qApp->arguments().contains("-d")) {
qDebug() << "gpgme_data_t debug on";
debug = true;
} else debug = false;
slotRefreshKeyList();
}
bool GpgContext::isGood() const {
return good;
}
/**
* The Passphrase window, if not provided by env-Var GPG_AGENT_INFO
* originally copied from http://basket.kde.org/ (kgpgme.cpp), but modified
*/
gpgme_error_t GpgContext::passphraseCb(void *hook, const char *uid_hint,
const char *passphrase_info,
int last_was_bad, int fd) {
auto *gpg = static_cast(hook);
return gpg->passphrase(uid_hint, passphrase_info, last_was_bad, fd);
}
gpgme_error_t GpgContext::passphrase(const char *uid_hint,
const char * /*passphrase_info*/,
int last_was_bad, int fd) {
gpgme_error_t returnValue = GPG_ERR_CANCELED;
QString passwordDialogMessage;
QString gpgHint = QString::fromUtf8(uid_hint);
bool result;
#ifdef _WIN32
DWORD written;
auto hd = INT2VOIDP(fd);
#endif
if (last_was_bad) {
passwordDialogMessage += " Wrong password.
\n\n";
clearPasswordCache();
}
/** if uid provided */
if (!gpgHint.isEmpty()) {
// remove UID, leave only username & email
gpgHint.remove(0, gpgHint.indexOf(" "));
passwordDialogMessage += " Enter Password for
" + gpgHint + "
";
}
if (mPasswordCache.isEmpty()) {
QString password = QInputDialog::getText(QApplication::activeWindow(), "Enter Password",
passwordDialogMessage, QLineEdit::Password,
"", &result);
if (result) mPasswordCache = password.toUtf8();
} else result = true;
if (result) {
#ifndef _WIN32
if (write(fd, mPasswordCache.data(), mPasswordCache.length()) == -1) qDebug() << "something is terribly broken";
#else
WriteFile(hd, mPasswordCache.data(), mPasswordCache.length(), &written, 0);
#endif
returnValue = GPG_ERR_NO_ERROR;
}
#ifndef _WIN32
if (write(fd, "\n", 1) == -1) qDebug() << "something is terribly broken";
#else
WriteFile(hd, "\n", 1, &written, nullptr);
/* program will hang on cancel if hd not closed */
if (!result) CloseHandle(hd);
#endif
return returnValue;
}
/** also from kgpgme.cpp, seems to clear password from mem */
void GpgContext::clearPasswordCache() {
if (mPasswordCache.size() > 0) {
mPasswordCache.fill('\0');
mPasswordCache.truncate(0);
}
}
// error-handling
gpgme_error_t check_gpg_error(gpgme_error_t err, const QString &comment) {
//if (gpgmeError != GPG_ERR_NO_ERROR && gpgmeError != GPG_ERR_CANCELED) {
if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
qDebug() << "[Error " << gpg_err_code(err)
<< "] Source: " << gpgme_strsource(err) << " Description: " << gpgme_strerror(err);
}
return err;
}
gpgme_error_t check_gpg_error(gpgme_error_t err) {
//if (gpgmeError != GPG_ERR_NO_ERROR && gpgmeError != GPG_ERR_CANCELED) {
if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
qDebug() << "[Error " << gpg_err_code(err)
<< "] Source: " << gpgme_strsource(err) << " Description: " << gpgme_strerror(err);
}
return err;
}
/** return type should be gpgme_error_t*/
/*
* if there is no '\n' before the PGP-Begin-Block, but for example a whitespace,
* GPGME doesn't recognise the Message as encrypted. This function adds '\n'
* before the PGP-Begin-Block, if missing.
*/
void GpgContext::preventNoDataErr(QByteArray *in) {
int block_start = in->indexOf(GpgConstants::PGP_CRYPT_BEGIN);
if (block_start > 0 && in->at(block_start - 1) != '\n') {
in->insert(block_start, '\n');
}
block_start = in->indexOf(GpgConstants::PGP_SIGNED_BEGIN);
if (block_start > 0 && in->at(block_start - 1) != '\n') {
in->insert(block_start, '\n');
}
}
/*
* isSigned returns:
* - 0, if text isn't signed at all
* - 1, if text is partially signed
* - 2, if text is completly signed
*/
int GpgContext::textIsSigned(const QByteArray &text) {
if (text.trimmed().startsWith(GpgConstants::PGP_SIGNED_BEGIN) &&
text.trimmed().endsWith(GpgConstants::PGP_SIGNED_END))
return 2;
else if (text.contains(GpgConstants::PGP_SIGNED_BEGIN) && text.contains(GpgConstants::PGP_SIGNED_END))
return 1;
else return 0;
}
QString GpgContext::beautifyFingerprint(QString fingerprint) {
uint len = fingerprint.length();
if ((len > 0) && (len % 4 == 0))
for (uint n = 0; 4 * (n + 1) < len; ++n) fingerprint.insert(static_cast(5u * n + 4u), ' ');
return fingerprint;
}
void GpgContext::slotRefreshKeyList() {
qDebug() << "Refreshing Keys";
this->fetch_keys();
emit signalKeyInfoChanged();
}
QString GpgContext::getGpgmeVersion() {
return {gpgme_check_version(nullptr)};
}
const GpgKeyList &GpgContext::getKeys() const {
return mKeyList;
}
void GpgContext::slotUpdateKeyList(const QString &key_id) {
auto it = mKeyMap.find(key_id);
if (it != mKeyMap.end()) {
gpgme_key_t new_key_ref;
auto gpgmeErr = gpgme_get_key(*this, key_id.toUtf8().constData(), &new_key_ref, 0);
if (gpgme_err_code(gpgmeErr) == GPG_ERR_EOF) {
gpgmeErr = gpgme_get_key(*this, key_id.toUtf8().constData(), &new_key_ref, 1);
if (gpgme_err_code(gpgmeErr) == GPG_ERR_EOF)
throw std::runtime_error("key_id not found in key database");
}
if (new_key_ref != nullptr) {
it->second = std::move(GpgKey(std::move(new_key_ref)));
emit signalKeyInfoChanged();
}
}
}
}