File indexing completed on 2024-05-12 05:06:12

0001 /*
0002     SPDX-FileCopyrightText: 2004, 2005, 2009 Thomas Baumgart <ipwizard@users.sourceforge.net>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include <config-kmymoney.h>
0007 
0008 // ----------------------------------------------------------------------------
0009 // Project Includes
0010 
0011 #include "kgpgfile.h"
0012 
0013 // ----------------------------------------------------------------------------
0014 // QT Includes
0015 
0016 #include <QByteArray>
0017 #include <QDateTime>
0018 #include <QDebug>
0019 #include <QDir>
0020 #include <QFile>
0021 #include <QFileInfo>
0022 #include <QList>
0023 #include <QSaveFile>
0024 #include <QStandardPaths>
0025 #include <QString>
0026 #include <QStringList>
0027 #include <qglobal.h>
0028 #include <vector>
0029 
0030 // ----------------------------------------------------------------------------
0031 // KDE Includes
0032 
0033 #ifdef ENABLE_GPG
0034 #include <gpgme++/context.h>
0035 #include <gpgme++/data.h>
0036 #include <gpgme++/decryptionresult.h>
0037 #include <gpgme++/encryptionresult.h>
0038 #include <gpgme++/engineinfo.h>
0039 #include <gpgme++/key.h>
0040 #include <gpgme++/keylistresult.h>
0041 
0042 class GPGConfig
0043 {
0044 private:
0045     GPGConfig()
0046         : m_isInitialized(false)
0047     {
0048         GpgME::initializeLibrary();
0049 
0050         auto ctx = GpgME::Context::createForProtocol(GpgME::OpenPGP);
0051         if (!ctx) {
0052             qDebug("Failed to create the GpgME context for the OpenPGP protocol");
0053             return;
0054         }
0055 
0056         // we search the directory that GPG provides as default
0057         if (ctx->engineInfo().homeDirectory() == nullptr) {
0058             m_homeDir = QString::fromUtf8(GpgME::dirInfo("homedir"));
0059         } else {
0060             m_homeDir = QString::fromUtf8(ctx->engineInfo().homeDirectory());
0061         }
0062 
0063         // search secret keys location
0064         // GPG >= 2.1     - private-keys-v1.d subdirectory
0065         // older versions - secring.pgp
0066         const auto secKeyDir = QStringLiteral("%1/private-keys-v1.d").arg(m_homeDir);
0067         if (!QFileInfo::exists(secKeyDir)) {
0068             const auto fileName = QString("%1/%2").arg(m_homeDir, "secring.gpg");
0069             qDebug() << "GPG search" << fileName;
0070             if (!QFileInfo::exists(fileName)) {
0071                 qDebug() << "GPG no secure keyring found.";
0072             }
0073         }
0074 
0075         m_homeDir = QDir::toNativeSeparators(m_homeDir);
0076         /// FIXME This might be nasty if the underlying gpgme lib does not work on UTF-8
0077         auto lastError = ctx->setEngineHomeDirectory(m_homeDir.toUtf8());
0078         if (lastError.encodedError()) {
0079             qDebug() << "Failure while setting GPG home directory to" << m_homeDir << "\n" << QLatin1String(lastError.asString());
0080         }
0081 
0082         qDebug() << "GPG Home directory located in" << ctx->engineInfo().homeDirectory();
0083         qDebug() << "GPG binary located in" << ctx->engineInfo().fileName();
0084 
0085         m_isInitialized = true;
0086     }
0087 
0088     QString m_homeDir;
0089     bool m_isInitialized;
0090 
0091 public:
0092     static GPGConfig* instance()
0093     {
0094         static GPGConfig* gpgConfig = nullptr;
0095         if (!gpgConfig) {
0096             gpgConfig = new GPGConfig;
0097         }
0098         return gpgConfig;
0099     }
0100 
0101     bool isInitialized() const
0102     {
0103         return m_isInitialized;
0104     }
0105 
0106     QString homeDir() const
0107     {
0108         return m_homeDir;
0109     }
0110 };
0111 
0112 class KGPGFile::Private
0113 {
0114 public:
0115     Private()
0116         : m_fileRead(nullptr)
0117         , m_fileWrite(nullptr)
0118         , m_ctx(nullptr)
0119     {
0120         const auto gpgConfig(GPGConfig::instance());
0121 
0122         if (!gpgConfig->isInitialized()) {
0123             qDebug() << "GPGConfig not initialized";
0124             return;
0125         }
0126 
0127         m_ctx = GpgME::Context::createForProtocol(GpgME::OpenPGP);
0128         if (!m_ctx) {
0129             qDebug("Failed to create the GpgME context for the OpenPGP protocol");
0130             return;
0131         }
0132 
0133         /// FIXME This might be nasty if the underlying gpgme lib does not work on UTF-8
0134         m_lastError = m_ctx->setEngineHomeDirectory(QDir::toNativeSeparators(gpgConfig->homeDir()).toUtf8());
0135         if (m_lastError.encodedError()) {
0136             qDebug() << "Failure while setting GPG home directory to" << gpgConfig->homeDir() << "\n" << QLatin1String(m_lastError.asString());
0137         }
0138     }
0139 
0140     ~Private()
0141     {
0142         delete m_ctx;
0143     }
0144 
0145     QString m_fn;
0146     QFile* m_fileRead;
0147     QSaveFile* m_fileWrite;
0148 
0149     GpgME::Error m_lastError;
0150 
0151     GpgME::Context* m_ctx;
0152     GpgME::Data m_data;
0153 
0154     std::vector<GpgME::Key> m_recipients;
0155 
0156     // the result set of the last key list job
0157     std::vector<GpgME::Key> m_keys;
0158 };
0159 
0160 KGPGFile::KGPGFile(const QString& fn, const QString& homedir, const QString& options)
0161     : d(new Private)
0162 {
0163     // only kept for interface compatibility
0164     Q_UNUSED(homedir);
0165     Q_UNUSED(options);
0166 
0167     KGPGFile::setFileName(fn);
0168 }
0169 
0170 KGPGFile::~KGPGFile()
0171 {
0172     close();
0173     delete d;
0174 }
0175 
0176 void KGPGFile::setFileName(const QString& fn)
0177 {
0178     d->m_fn = fn;
0179     if (!fn.isEmpty() && fn[0] == '~') {
0180         d->m_fn = QDir::homePath() + fn.mid(1);
0181 
0182     } else if (QDir::isRelativePath(d->m_fn)) {
0183         QDir dir(fn);
0184         d->m_fn = dir.absolutePath();
0185     }
0186     // qDebug("setName: '%s'", d->m_fn.toLatin1().data());
0187 }
0188 
0189 void KGPGFile::flush()
0190 {
0191     // no functionality
0192 }
0193 
0194 void KGPGFile::addRecipient(const QString& recipient)
0195 {
0196     // skip a possible leading 0x in the id
0197     QString cmp = recipient;
0198     if (cmp.startsWith(QLatin1String("0x")))
0199         cmp = cmp.mid(2);
0200 
0201     QStringList keylist;
0202     keyList(keylist, false, cmp);
0203 
0204     if (d->m_keys.size() > 0)
0205         d->m_recipients.push_back(d->m_keys.front());
0206 }
0207 
0208 bool KGPGFile::open(OpenMode mode)
0209 {
0210     if (isOpen()) {
0211         return false;
0212     }
0213 
0214     if (d->m_fn.isEmpty()) {
0215         setOpenMode(NotOpen);
0216         return false;
0217     }
0218 
0219     if (!d->m_ctx) {
0220         setOpenMode(NotOpen);
0221         return false;
0222     }
0223 
0224     setOpenMode(mode);
0225 
0226     if (!(isReadable() || isWritable())) {
0227         setOpenMode(NotOpen);
0228         return false;
0229     }
0230 
0231     if (isWritable()) {
0232         if (d->m_recipients.empty()) {
0233             setOpenMode(NotOpen);
0234             return false;
0235         }
0236 
0237         // write out in ASCII armor mode
0238         d->m_ctx->setArmor(true);
0239         d->m_fileWrite = new QSaveFile;
0240 
0241     } else if (isReadable()) {
0242         d->m_fileRead = new QFile;
0243     }
0244 
0245     // open the 'physical' file
0246     // Since some of the methods in QFile are not virtual, we need to
0247     // differentiate here between the QFile* and the QSaveFile* case
0248     if (isReadable()) {
0249         d->m_fileRead->setFileName(d->m_fn);
0250         if (!d->m_fileRead->open(mode)) {
0251             setOpenMode(NotOpen);
0252             return false;
0253         }
0254         GpgME::Data dcipher(d->m_fileRead->handle());
0255         d->m_lastError = d->m_ctx->decrypt(dcipher, d->m_data).error();
0256         if (d->m_lastError.encodedError()) {
0257             return false;
0258         }
0259         d->m_data.seek(0, SEEK_SET);
0260 
0261     } else if (isWritable()) {
0262         d->m_fileWrite->setFileName(d->m_fn);
0263         if (!d->m_fileWrite->open(mode)) {
0264             setOpenMode(NotOpen);
0265             return false;
0266         }
0267     }
0268 
0269     return true;
0270 }
0271 
0272 void KGPGFile::close()
0273 {
0274     if (!isOpen()) {
0275         return;
0276     }
0277 
0278     if (!d->m_ctx)
0279         return;
0280 
0281     if (isWritable()) {
0282         d->m_data.seek(0, SEEK_SET);
0283         GpgME::Data dcipher(d->m_fileWrite->handle());
0284         d->m_lastError = d->m_ctx->encrypt(d->m_recipients, d->m_data, dcipher, GpgME::Context::AlwaysTrust).error();
0285         if (d->m_lastError.encodedError()) {
0286             setErrorString(QLatin1String("Failure while writing temporary file for file: '") + QLatin1String(d->m_lastError.asString()) + QLatin1String("'"));
0287         } else if (!d->m_fileWrite->commit()) {
0288             setErrorString("Failure while committing file changes.");
0289         }
0290     }
0291 
0292     delete d->m_fileWrite;
0293     delete d->m_fileRead;
0294     d->m_fileWrite = 0;
0295     d->m_fileRead = 0;
0296     d->m_recipients.clear();
0297     setOpenMode(NotOpen);
0298 }
0299 
0300 qint64 KGPGFile::writeData(const char* data, qint64 maxlen)
0301 {
0302     if (!isOpen())
0303         return EOF;
0304 
0305     if (!isWritable())
0306         return EOF;
0307 
0308     // qDebug("write %d bytes", qint32(maxlen & 0xFFFFFFFF));
0309 
0310     // write out the data and make sure that we do not cross
0311     // size_t boundaries.
0312     qint64 bytesWritten = 0;
0313     while (maxlen) {
0314         qint64 len = 1LL << 31;
0315         if (len > maxlen)
0316             len = maxlen;
0317         bytesWritten += d->m_data.write(data, len);
0318         data = &data[len];
0319         maxlen -= len;
0320     }
0321     // qDebug("%d bytes written", qint32(bytesWritten & 0xFFFFFFFF));
0322     return bytesWritten;
0323 }
0324 
0325 qint64 KGPGFile::readData(char* data, qint64 maxlen)
0326 {
0327     if (maxlen == 0)
0328         return 0;
0329 
0330     if (!isOpen())
0331         return EOF;
0332     if (!isReadable())
0333         return EOF;
0334 
0335     // read requested block of data and make sure that we do not cross
0336     // size_t boundaries.
0337     qint64 bytesRead = 0;
0338     while (maxlen) {
0339         qint64 len = 1LL << 31;
0340         if (len > maxlen)
0341             len = maxlen;
0342         bytesRead += d->m_data.read(data, len);
0343         data = &data[len];
0344         maxlen -= len;
0345     }
0346     return bytesRead;
0347 }
0348 
0349 QString KGPGFile::errorToString() const
0350 {
0351     return QString::fromUtf8(d->m_lastError.asString());
0352 }
0353 
0354 bool KGPGFile::GPGAvailable()
0355 {
0356     GpgME::initializeLibrary();
0357     const auto engineCheck = GpgME::checkEngine(GpgME::OpenPGP);
0358     if (engineCheck.code() != 0) {
0359         qDebug() << "GpgME::checkEngine returns" << engineCheck.code() << engineCheck.asString();
0360         return false;
0361     }
0362     return true;
0363 }
0364 
0365 bool KGPGFile::keyAvailable(const QString& name)
0366 {
0367     KGPGFile file;
0368     QStringList keys;
0369     file.keyList(keys, false, name);
0370     // qDebug("keyAvailable returns %d for '%s'", keys.count(), qPrintable(name));
0371     return keys.count() != 0;
0372 }
0373 
0374 void KGPGFile::publicKeyList(QStringList& list)
0375 {
0376     // qDebug("Reading public keys");
0377     KGPGFile file;
0378     file.keyList(list);
0379 }
0380 
0381 void KGPGFile::secretKeyList(QStringList& list)
0382 {
0383     // qDebug("Reading secrect keys");
0384     KGPGFile file;
0385     file.keyList(list, true);
0386 }
0387 
0388 QDateTime KGPGFile::keyExpires(const QString& name)
0389 {
0390     QDateTime expirationDate;
0391 
0392     // skip a possible leading 0x in the id
0393     QString cmp = name;
0394     if (cmp.startsWith(QLatin1String("0x")))
0395         cmp = cmp.mid(2);
0396 
0397     QStringList keylist;
0398     keyList(keylist, false, cmp);
0399 
0400     // in case we have no or more than one matching key
0401     // or the key does not have subkeys, we return an invalid date
0402     if (d->m_keys.size() == 1 && d->m_keys[0].subkeys().size() > 0 && !d->m_keys[0].subkeys()[0].neverExpires()) {
0403         expirationDate.setSecsSinceEpoch(d->m_keys[0].subkeys()[0].expirationTime());
0404     }
0405     return expirationDate;
0406 }
0407 
0408 void KGPGFile::keyList(QStringList& list, bool secretKeys, const QString& pattern)
0409 {
0410     d->m_keys.clear();
0411     list.clear();
0412     if (d->m_ctx && !d->m_ctx->startKeyListing(pattern.toUtf8().constData(), secretKeys)) {
0413         GpgME::Error error;
0414         for (;;) {
0415             GpgME::Key key;
0416             key = d->m_ctx->nextKey(error);
0417             if (error.encodedError() != GPG_ERR_NO_ERROR)
0418                 break;
0419 
0420             bool needPushBack = true;
0421 
0422             std::vector<GpgME::UserID> userIDs = key.userIDs();
0423             std::vector<GpgME::Subkey> subkeys = key.subkeys();
0424             for (unsigned int i = 0; i < userIDs.size(); ++i) {
0425                 if (subkeys.size() > 0) {
0426                     for (unsigned int j = 0; j < subkeys.size(); ++j) {
0427                         const GpgME::Subkey& skey = subkeys[j];
0428 
0429                         if (((skey.canEncrypt() && !secretKeys) || (skey.isSecret() && secretKeys))
0430 
0431                             && !(skey.isRevoked() || skey.isExpired() || skey.isInvalid() || skey.isDisabled())) {
0432                             QString entry = QString("%1:%2").arg(key.shortKeyID(), userIDs[i].id());
0433                             list += entry;
0434                             if (needPushBack) {
0435                                 d->m_keys.push_back(key);
0436                                 needPushBack = false;
0437                             }
0438                         } else {
0439                             // qDebug("Skip key '%s'", key.shortKeyID());
0440                         }
0441                     }
0442                 } else {
0443                     // we have no subkey, so we operate on the main key
0444                     if (((key.canEncrypt() && !secretKeys) || (key.hasSecret() && secretKeys))
0445                         && !(key.isRevoked() || key.isExpired() || key.isInvalid() || key.isDisabled())) {
0446                         QString entry = QString("%1:%2").arg(key.shortKeyID(), userIDs[i].id());
0447                         list += entry;
0448                         if (needPushBack) {
0449                             d->m_keys.push_back(key);
0450                             needPushBack = false;
0451                         }
0452                     } else {
0453                         // qDebug("Skip key '%s'", key.shortKeyID());
0454                     }
0455                 }
0456             }
0457         }
0458         d->m_ctx->endKeyListing();
0459     }
0460 }
0461 
0462 #else // not ENABLE_GPG
0463 
0464 // NOOP implementation
0465 KGPGFile::KGPGFile(const QString& fn, const QString& homedir, const QString& options)
0466     : d(0)
0467 {
0468     Q_UNUSED(fn);
0469     Q_UNUSED(homedir);
0470     Q_UNUSED(options);
0471 }
0472 
0473 KGPGFile::~KGPGFile()
0474 {
0475 }
0476 
0477 bool KGPGFile::open(OpenMode mode)
0478 {
0479     Q_UNUSED(mode);
0480     return false;
0481 }
0482 
0483 void KGPGFile::close()
0484 {
0485 }
0486 
0487 void KGPGFile::flush()
0488 {
0489 }
0490 
0491 qint64 KGPGFile::readData(char* data, qint64 maxlen)
0492 {
0493     Q_UNUSED(data);
0494     Q_UNUSED(maxlen);
0495     return 0;
0496 }
0497 
0498 qint64 KGPGFile::writeData(const char* data, qint64 maxlen)
0499 {
0500     Q_UNUSED(data);
0501     Q_UNUSED(maxlen);
0502     return 0;
0503 }
0504 
0505 void KGPGFile::addRecipient(const QString& recipient)
0506 {
0507     Q_UNUSED(recipient);
0508 }
0509 
0510 QString KGPGFile::errorToString() const
0511 {
0512     return QString();
0513 }
0514 
0515 bool KGPGFile::GPGAvailable(void)
0516 {
0517     return false;
0518 }
0519 
0520 bool KGPGFile::keyAvailable(const QString& name)
0521 {
0522     Q_UNUSED(name);
0523     return false;
0524 }
0525 
0526 void KGPGFile::secretKeyList(QStringList& list)
0527 {
0528     Q_UNUSED(list);
0529 }
0530 
0531 void KGPGFile::publicKeyList(QStringList& list)
0532 {
0533     Q_UNUSED(list);
0534 }
0535 
0536 QDateTime KGPGFile::keyExpires(const QString& name)
0537 {
0538     Q_UNUSED(name);
0539     return QDateTime();
0540 }
0541 
0542 #endif