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