File indexing completed on 2024-06-16 03:53:03

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2013 Valentin Rusu <kde@rusu.info>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "backendpersisthandler.h"
0009 #include "kwalletbackend_debug.h"
0010 
0011 #include <KLocalizedString>
0012 #include <KMessageBox>
0013 #include <QCryptographicHash>
0014 #include <QFile>
0015 #include <QIODevice>
0016 #include <QSaveFile>
0017 #include <assert.h>
0018 #ifdef HAVE_GPGMEPP
0019 #include <gpgme++/context.h>
0020 #include <gpgme++/data.h>
0021 #include <gpgme++/decryptionresult.h>
0022 #include <gpgme++/encryptionresult.h>
0023 #include <gpgme++/key.h>
0024 #include <gpgme++/keylistresult.h>
0025 #endif
0026 #include "blowfish.h"
0027 #include "cbc.h"
0028 #include "kwalletbackend.h"
0029 #include "sha1.h"
0030 
0031 #ifdef Q_OS_WIN
0032 #include <windows.h> // Must be included before wincrypt.h
0033 
0034 #include <wincrypt.h>
0035 #endif
0036 
0037 #define KWALLET_CIPHER_BLOWFISH_ECB 0 // this was the old KWALLET_CIPHER_BLOWFISH_CBC
0038 #define KWALLET_CIPHER_3DES_CBC 1 // unsupported
0039 #define KWALLET_CIPHER_GPG 2
0040 #define KWALLET_CIPHER_BLOWFISH_CBC 3
0041 
0042 #define KWALLET_HASH_SHA1 0
0043 #define KWALLET_HASH_MD5 1 // unsupported
0044 #define KWALLET_HASH_PBKDF2_SHA512 2 // used when using kwallet with pam or since 4.13 version
0045 
0046 namespace KWallet
0047 {
0048 typedef char Digest[16];
0049 
0050 static int getRandomBlock(QByteArray &randBlock)
0051 {
0052 #ifdef Q_OS_WIN // krazy:exclude=cpp
0053 
0054     // Use windows crypto API to get randomness on win32
0055     // HACK: this should be done using qca
0056     HCRYPTPROV hProv;
0057 
0058     if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
0059         return -1; // couldn't get random data
0060     }
0061 
0062     if (!CryptGenRandom(hProv, static_cast<DWORD>(randBlock.size()), (BYTE *)randBlock.data())) {
0063         return -3; // read error
0064     }
0065 
0066     // release the crypto context
0067     CryptReleaseContext(hProv, 0);
0068 
0069     return randBlock.size();
0070 
0071 #else
0072 
0073     // First try /dev/urandom
0074     if (QFile::exists(QStringLiteral("/dev/urandom"))) {
0075         QFile devrand(QStringLiteral("/dev/urandom"));
0076         if (devrand.open(QIODevice::ReadOnly)) {
0077             int rc = devrand.read(randBlock.data(), randBlock.size());
0078 
0079             if (rc != randBlock.size()) {
0080                 return -3; // not enough data read
0081             }
0082 
0083             return 0;
0084         }
0085     }
0086 
0087     // If that failed, try /dev/random
0088     // FIXME: open in noblocking mode!
0089     if (QFile::exists(QStringLiteral("/dev/random"))) {
0090         QFile devrand(QStringLiteral("/dev/random"));
0091         if (devrand.open(QIODevice::ReadOnly)) {
0092             int rc = 0;
0093             int cnt = 0;
0094 
0095             do {
0096                 int rc2 = devrand.read(randBlock.data() + rc, randBlock.size());
0097 
0098                 if (rc2 < 0) {
0099                     return -3; // read error
0100                 }
0101 
0102                 rc += rc2;
0103                 cnt++;
0104                 if (cnt > randBlock.size()) {
0105                     return -4; // reading forever?!
0106                 }
0107             } while (rc < randBlock.size());
0108 
0109             return 0;
0110         }
0111     }
0112 
0113     // EGD method
0114     QString randFilename = QString::fromLocal8Bit(qgetenv("RANDFILE"));
0115     if (!randFilename.isEmpty()) {
0116         if (QFile::exists(randFilename)) {
0117             QFile devrand(randFilename);
0118             if (devrand.open(QIODevice::ReadOnly)) {
0119                 int rc = devrand.read(randBlock.data(), randBlock.size());
0120                 if (rc != randBlock.size()) {
0121                     return -3; // not enough data read
0122                 }
0123                 return 0;
0124             }
0125         }
0126     }
0127 
0128     // Couldn't get any random data!!
0129     return -1;
0130 
0131 #endif
0132 }
0133 
0134 BackendPersistHandler *BackendPersistHandler::getPersistHandler(BackendCipherType cipherType)
0135 {
0136     switch (cipherType) {
0137     case BACKEND_CIPHER_BLOWFISH:
0138         return new BlowfishPersistHandler;
0139 #ifdef HAVE_GPGMEPP
0140     case BACKEND_CIPHER_GPG:
0141         return new GpgPersistHandler;
0142 #endif // HAVE_GPGMEPP
0143     default:
0144         Q_ASSERT(0);
0145         return nullptr;
0146     }
0147 }
0148 
0149 BackendPersistHandler *BackendPersistHandler::getPersistHandler(char magicBuf[12])
0150 {
0151     if ((magicBuf[2] == KWALLET_CIPHER_BLOWFISH_ECB || magicBuf[2] == KWALLET_CIPHER_BLOWFISH_CBC)
0152         && (magicBuf[3] == KWALLET_HASH_SHA1 || magicBuf[3] == KWALLET_HASH_PBKDF2_SHA512)) {
0153         bool useECBforReading = magicBuf[2] == KWALLET_CIPHER_BLOWFISH_ECB;
0154         if (useECBforReading) {
0155             qCDebug(KWALLETBACKEND_LOG) << "this wallet uses ECB encryption. It'll be converted to CBC on next save.";
0156         }
0157         return new BlowfishPersistHandler(useECBforReading);
0158     }
0159 #ifdef HAVE_GPGMEPP
0160     if (magicBuf[2] == KWALLET_CIPHER_GPG && magicBuf[3] == 0) {
0161         return new GpgPersistHandler;
0162     }
0163 #endif // HAVE_GPGMEPP
0164     return nullptr; // unknown cipher or hash
0165 }
0166 
0167 int BlowfishPersistHandler::write(Backend *wb, QSaveFile &sf, QByteArray &version, WId)
0168 {
0169     assert(wb->_cipherType == BACKEND_CIPHER_BLOWFISH);
0170 
0171     if (_useECBforReading) {
0172         qCDebug(KWALLETBACKEND_LOG) << "This wallet used ECB and is now saved using CBC";
0173         _useECBforReading = false;
0174     }
0175 
0176     version[2] = KWALLET_CIPHER_BLOWFISH_CBC;
0177     if (!wb->_useNewHash) {
0178         version[3] = KWALLET_HASH_SHA1;
0179     } else {
0180         version[3] = KWALLET_HASH_PBKDF2_SHA512; // Since 4.13 we always use PBKDF2_SHA512
0181     }
0182 
0183     if (sf.write(version) != 4) {
0184         sf.cancelWriting();
0185         return -4; // write error
0186     }
0187 
0188     // Holds the hashes we write out
0189     QByteArray hashes;
0190     QDataStream hashStream(&hashes, QIODevice::WriteOnly);
0191     QCryptographicHash md5(QCryptographicHash::Md5);
0192     hashStream << static_cast<quint32>(wb->_entries.count());
0193 
0194     // Holds decrypted data prior to encryption
0195     QByteArray decrypted;
0196 
0197     // FIXME: we should estimate the amount of data we will write in each
0198     // buffer and resize them approximately in order to avoid extra
0199     // resizes.
0200 
0201     // populate decrypted
0202     QDataStream dStream(&decrypted, QIODevice::WriteOnly);
0203     for (Backend::FolderMap::ConstIterator i = wb->_entries.constBegin(); i != wb->_entries.constEnd(); ++i) {
0204         dStream << i.key();
0205         dStream << static_cast<quint32>(i.value().count());
0206 
0207         md5.reset();
0208         md5.addData(i.key().toUtf8());
0209         hashStream.writeRawData(md5.result().constData(), 16);
0210         hashStream << static_cast<quint32>(i.value().count());
0211 
0212         for (Backend::EntryMap::ConstIterator j = i.value().constBegin(); j != i.value().constEnd(); ++j) {
0213             dStream << j.key();
0214             dStream << static_cast<qint32>(j.value()->type());
0215             dStream << j.value()->value();
0216 
0217             md5.reset();
0218             md5.addData(j.key().toUtf8());
0219             hashStream.writeRawData(md5.result().constData(), 16);
0220         }
0221     }
0222 
0223     if (sf.write(hashes) != hashes.size()) {
0224         sf.cancelWriting();
0225         return -4; // write error
0226     }
0227 
0228     // calculate the hash of the file
0229     SHA1 sha;
0230     BlowFish _bf;
0231     CipherBlockChain bf(&_bf);
0232 
0233     sha.process(decrypted.data(), decrypted.size());
0234 
0235     // prepend and append the random data
0236     QByteArray wholeFile;
0237     long blksz = bf.blockSize();
0238     long newsize = decrypted.size() + blksz + // encrypted block
0239         4 + // file size
0240         20; // size of the SHA hash
0241 
0242     int delta = (blksz - (newsize % blksz));
0243     newsize += delta;
0244     wholeFile.resize(newsize);
0245 
0246     QByteArray randBlock;
0247     randBlock.resize(blksz + delta);
0248     if (getRandomBlock(randBlock) < 0) {
0249         sha.reset();
0250         decrypted.fill(0);
0251         sf.cancelWriting();
0252         return -3; // Fatal error: can't get random
0253     }
0254 
0255     for (int i = 0; i < blksz; i++) {
0256         wholeFile[i] = randBlock[i];
0257     }
0258 
0259     for (int i = 0; i < 4; i++) {
0260         wholeFile[(int)(i + blksz)] = (decrypted.size() >> 8 * (3 - i)) & 0xff;
0261     }
0262 
0263     for (int i = 0; i < decrypted.size(); i++) {
0264         wholeFile[(int)(i + blksz + 4)] = decrypted[i];
0265     }
0266 
0267     for (int i = 0; i < delta; i++) {
0268         wholeFile[(int)(i + blksz + 4 + decrypted.size())] = randBlock[(int)(i + blksz)];
0269     }
0270 
0271     const char *hash = (const char *)sha.hash();
0272     for (int i = 0; i < 20; i++) {
0273         wholeFile[(int)(newsize - 20 + i)] = hash[i];
0274     }
0275 
0276     sha.reset();
0277     decrypted.fill(0);
0278 
0279     // encrypt the data
0280     if (!bf.setKey(wb->_passhash.data(), wb->_passhash.size() * 8)) {
0281         wholeFile.fill(0);
0282         sf.cancelWriting();
0283         return -2; // encrypt error
0284     }
0285 
0286     int rc = bf.encrypt(wholeFile.data(), wholeFile.size());
0287     if (rc < 0) {
0288         wholeFile.fill(0);
0289         sf.cancelWriting();
0290         return -2; // encrypt error
0291     }
0292 
0293     // write the file
0294     auto written = sf.write(wholeFile);
0295     if (written != wholeFile.size()) {
0296         wholeFile.fill(0);
0297         sf.cancelWriting();
0298         return -4; // write error
0299     }
0300     if (!sf.commit()) {
0301         qCDebug(KWALLETBACKEND_LOG) << "WARNING: wallet sync to disk failed! QSaveFile status was " << sf.errorString();
0302         wholeFile.fill(0);
0303         return -4; // write error
0304     }
0305 
0306     wholeFile.fill(0);
0307 
0308     return 0;
0309 }
0310 
0311 int BlowfishPersistHandler::read(Backend *wb, QFile &db, WId)
0312 {
0313     wb->_cipherType = BACKEND_CIPHER_BLOWFISH;
0314     wb->_hashes.clear();
0315     // Read in the hashes
0316     QDataStream hds(&db);
0317     quint32 n;
0318     hds >> n;
0319     if (n > 0xffff) { // sanity check
0320         return -43;
0321     }
0322 
0323     for (size_t i = 0; i < n; ++i) {
0324         Digest d;
0325         Digest d2; // judgment day
0326         MD5Digest ba;
0327         QMap<MD5Digest, QList<MD5Digest>>::iterator it;
0328         quint32 fsz;
0329         if (hds.atEnd()) {
0330             return -43;
0331         }
0332         hds.readRawData(d, 16);
0333         hds >> fsz;
0334         ba = MD5Digest(reinterpret_cast<char *>(d));
0335         it = wb->_hashes.insert(ba, QList<MD5Digest>());
0336         for (size_t j = 0; j < fsz; ++j) {
0337             hds.readRawData(d2, 16);
0338             ba = MD5Digest(d2);
0339             (*it).append(ba);
0340         }
0341     }
0342 
0343     // Read in the rest of the file.
0344     QByteArray encrypted = db.readAll();
0345     assert(encrypted.size() < db.size());
0346 
0347     BlowFish _bf;
0348     CipherBlockChain bf(&_bf, _useECBforReading);
0349     int blksz = bf.blockSize();
0350     if ((encrypted.size() % blksz) != 0) {
0351         return -5; // invalid file structure
0352     }
0353 
0354     bf.setKey((void *)wb->_passhash.data(), wb->_passhash.size() * 8);
0355 
0356     if (!encrypted.data()) {
0357         wb->_passhash.fill(0);
0358         encrypted.fill(0);
0359         return -7; // file structure error
0360     }
0361 
0362     int rc = bf.decrypt(encrypted.data(), encrypted.size());
0363     if (rc < 0) {
0364         wb->_passhash.fill(0);
0365         encrypted.fill(0);
0366         return -6; // decrypt error
0367     }
0368 
0369     const char *t = encrypted.data();
0370 
0371     // strip the leading data
0372     t += blksz; // one block of random data
0373 
0374     // strip the file size off
0375     long fsize = 0;
0376 
0377     fsize |= (long(*t) << 24) & 0xff000000;
0378     t++;
0379     fsize |= (long(*t) << 16) & 0x00ff0000;
0380     t++;
0381     fsize |= (long(*t) << 8) & 0x0000ff00;
0382     t++;
0383     fsize |= long(*t) & 0x000000ff;
0384     t++;
0385 
0386     if (fsize < 0 || fsize > long(encrypted.size()) - blksz - 4) {
0387         qCDebug(KWALLETBACKEND_LOG) << "fsize: " << fsize << " encrypted.size(): " << encrypted.size() << " blksz: " << blksz;
0388         encrypted.fill(0);
0389         return -9; // file structure error.
0390     }
0391 
0392     // compute the hash ourself
0393     SHA1 sha;
0394     sha.process(t, fsize);
0395     const char *testhash = (const char *)sha.hash();
0396 
0397     // compare hashes
0398     int sz = encrypted.size();
0399     for (int i = 0; i < 20; i++) {
0400         if (testhash[i] != encrypted[sz - 20 + i]) {
0401             encrypted.fill(0);
0402             sha.reset();
0403             return -8; // hash error.
0404         }
0405     }
0406 
0407     sha.reset();
0408 
0409     // chop off the leading blksz+4 bytes
0410     QByteArray tmpenc(encrypted.data() + blksz + 4, fsize);
0411     encrypted = tmpenc;
0412     tmpenc.fill(0);
0413 
0414     // Load the data structures up
0415     QDataStream eStream(encrypted);
0416 
0417     while (!eStream.atEnd()) {
0418         QString folder;
0419         quint32 n;
0420 
0421         eStream >> folder;
0422         eStream >> n;
0423 
0424         // Force initialisation
0425         wb->_entries[folder].clear();
0426 
0427         for (size_t i = 0; i < n; ++i) {
0428             QString key;
0429             KWallet::Wallet::EntryType et = KWallet::Wallet::Unknown;
0430             Entry *e = new Entry;
0431             eStream >> key;
0432             qint32 x = 0; // necessary to read properly
0433             eStream >> x;
0434             et = static_cast<KWallet::Wallet::EntryType>(x);
0435 
0436             switch (et) {
0437             case KWallet::Wallet::Password:
0438             case KWallet::Wallet::Stream:
0439             case KWallet::Wallet::Map:
0440                 break;
0441             default: // Unknown entry
0442                 delete e;
0443                 continue;
0444             }
0445 
0446             QByteArray a;
0447             eStream >> a;
0448             e->setValue(a);
0449             e->setType(et);
0450             e->setKey(key);
0451             wb->_entries[folder][key] = e;
0452         }
0453     }
0454 
0455     wb->_open = true;
0456     encrypted.fill(0);
0457     return 0;
0458 }
0459 
0460 #ifdef HAVE_GPGMEPP
0461 GpgME::Error initGpgME()
0462 {
0463     GpgME::Error err;
0464     static bool alreadyInitialized = false;
0465     if (!alreadyInitialized) {
0466         GpgME::initializeLibrary();
0467         err = GpgME::checkEngine(GpgME::OpenPGP);
0468         if (err) {
0469             qCDebug(KWALLETBACKEND_LOG) << "OpenPGP not supported!";
0470         }
0471         alreadyInitialized = true;
0472     }
0473     return err;
0474 }
0475 
0476 int GpgPersistHandler::write(Backend *wb, QSaveFile &sf, QByteArray &version, WId w)
0477 {
0478     version[2] = KWALLET_CIPHER_GPG;
0479     version[3] = 0;
0480     if (sf.write(version) != 4) {
0481         sf.cancelWriting();
0482         return -4; // write error
0483     }
0484 
0485     GpgME::Error err = initGpgME();
0486     if (err) {
0487         qCDebug(KWALLETBACKEND_LOG) << "initGpgME returned " << err.code();
0488         KMessageBox::errorWId(w,
0489                               i18n("<qt>Error when attempting to initialize OpenPGP while attempting to save the wallet <b>%1</b>. Error code is <b>%2</b>. "
0490                                    "Please fix your system configuration, then try again.</qt>",
0491                                    wb->_name.toHtmlEscaped(),
0492                                    err.code()));
0493         sf.cancelWriting();
0494         return -5;
0495     }
0496 
0497     std::shared_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP));
0498     if (!ctx) {
0499         qCDebug(KWALLETBACKEND_LOG) << "Cannot setup OpenPGP context!";
0500         KMessageBox::errorWId(w,
0501                               i18n("<qt>Error when attempting to initialize OpenPGP while attempting to save the wallet <b>%1</b>. Please fix your system "
0502                                    "configuration, then try again.</qt>"),
0503                               wb->_name.toHtmlEscaped());
0504         return -6;
0505     }
0506 
0507     assert(wb->_cipherType == BACKEND_CIPHER_GPG);
0508 
0509     QByteArray hashes;
0510     QDataStream hashStream(&hashes, QIODevice::WriteOnly);
0511     QCryptographicHash md5(QCryptographicHash::Md5);
0512     hashStream << static_cast<quint32>(wb->_entries.count());
0513 
0514     QByteArray values;
0515     QDataStream valueStream(&values, QIODevice::WriteOnly);
0516     Backend::FolderMap::ConstIterator i = wb->_entries.constBegin();
0517     Backend::FolderMap::ConstIterator ie = wb->_entries.constEnd();
0518     for (; i != ie; ++i) {
0519         valueStream << i.key();
0520         valueStream << static_cast<quint32>(i.value().count());
0521 
0522         md5.reset();
0523         md5.addData(i.key().toUtf8());
0524         hashStream.writeRawData(md5.result().constData(), 16);
0525         hashStream << static_cast<quint32>(i.value().count());
0526 
0527         Backend::EntryMap::ConstIterator j = i.value().constBegin();
0528         Backend::EntryMap::ConstIterator je = i.value().constEnd();
0529         for (; j != je; ++j) {
0530             valueStream << j.key();
0531             valueStream << static_cast<qint32>(j.value()->type());
0532             valueStream << j.value()->value();
0533 
0534             md5.reset();
0535             md5.addData(j.key().toUtf8());
0536             hashStream.writeRawData(md5.result().constData(), 16);
0537         }
0538     }
0539 
0540     QByteArray dataBuffer;
0541     QDataStream dataStream(&dataBuffer, QIODevice::WriteOnly);
0542     QString keyID(wb->_gpgKey.keyID());
0543     dataStream << keyID;
0544     dataStream << hashes;
0545     dataStream << values;
0546 
0547     GpgME::Data decryptedData(dataBuffer.data(), size_t(dataBuffer.size()), false);
0548     GpgME::Data encryptedData;
0549     std::vector<GpgME::Key> keys;
0550     keys.push_back(wb->_gpgKey);
0551     const GpgME::EncryptionResult res = ctx->encrypt(keys, decryptedData, encryptedData, GpgME::Context::None);
0552     if (res.error()) {
0553         const int gpgerr = res.error().code();
0554         KMessageBox::errorWId(w,
0555                               i18n("<qt>Encryption error while attempting to save the wallet <b>%1</b>. Error code is <b>%2 (%3)</b>. Please fix your system "
0556                                    "configuration, then try again. This error may occur if you are not using a full trust GPG key. Please ensure you have the "
0557                                    "secret key for the key you are using.</qt>",
0558                                    wb->_name.toHtmlEscaped(),
0559                                    gpgerr,
0560                                    res.error().asString()));
0561         qCDebug(KWALLETBACKEND_LOG) << "GpgME encryption error: " << gpgerr;
0562         sf.cancelWriting();
0563         return -7;
0564     }
0565 
0566     char buffer[4096];
0567     ssize_t bytes = 0;
0568     encryptedData.seek(0, SEEK_SET);
0569     while ((bytes = encryptedData.read(buffer, sizeof(buffer) / sizeof(buffer[0]))) > 0) {
0570         if (sf.write(buffer, bytes) != bytes) {
0571             KMessageBox::errorWId(w,
0572                                   i18n("<qt>File handling error while attempting to save the wallet <b>%1</b>. Error was <b>%2</b>. Please fix your system "
0573                                        "configuration, then try again.</qt>",
0574                                        wb->_name.toHtmlEscaped(),
0575                                        sf.errorString()));
0576             sf.cancelWriting();
0577             return -4; // write error
0578         }
0579     }
0580 
0581     if (!sf.commit()) {
0582         qCDebug(KWALLETBACKEND_LOG) << "WARNING: wallet sync to disk failed! QSaveFile status was " << sf.errorString();
0583         return -4; // write error
0584     }
0585 
0586     return 0;
0587 }
0588 
0589 int GpgPersistHandler::read(Backend *wb, QFile &sf, WId w)
0590 {
0591     GpgME::Error err = initGpgME();
0592     if (err) {
0593         KMessageBox::errorWId(w,
0594                               i18n("<qt>Error when attempting to initialize OpenPGP while attempting to open the wallet <b>%1</b>. Error code is <b>%2</b>. "
0595                                    "Please fix your system configuration, then try again.</qt>",
0596                                    wb->_name.toHtmlEscaped(),
0597                                    err.code()));
0598         return -1;
0599     }
0600 
0601     wb->_cipherType = BACKEND_CIPHER_GPG;
0602     wb->_hashes.clear();
0603 
0604     // the remainder of the file is GPG encrypted. Let's decrypt it
0605     GpgME::Data encryptedData;
0606     char buffer[4096];
0607     ssize_t bytes = 0;
0608     while ((bytes = sf.read(buffer, sizeof(buffer) / sizeof(buffer[0])))) {
0609         encryptedData.write(buffer, bytes);
0610     }
0611 
0612 retry_label:
0613     std::shared_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP));
0614     if (nullptr == ctx) {
0615         KMessageBox::errorWId(w,
0616                               i18n("<qt>Error when attempting to initialize OpenPGP while attempting to open the wallet <b>%1</b>. Please fix your system "
0617                                    "configuration, then try again.</qt>",
0618                                    wb->_name.toHtmlEscaped()));
0619         qCDebug(KWALLETBACKEND_LOG) << "Cannot setup OpenPGP context!";
0620         return -1;
0621     }
0622 
0623     GpgME::Data decryptedData;
0624     encryptedData.seek(0, SEEK_SET);
0625     GpgME::DecryptionResult res = ctx->decrypt(encryptedData, decryptedData);
0626     if (res.error()) {
0627         qCDebug(KWALLETBACKEND_LOG) << "Error decrypting message: " << res.error().asString() << ", code " << res.error().code() << ", source "
0628                                     << res.error().source();
0629         KGuiItem btnRetry(i18n("Retry"));
0630         // FIXME the logic here should be a little more elaborate; a dialog box should be used with "retry", "cancel", but also "troubleshoot" with options to
0631         // show card status and to kill scdaemon
0632         int userChoice =
0633             KMessageBox::warningTwoActionsWId(w,
0634                                               i18n("<qt>Error when attempting to decrypt the wallet <b>%1</b> using GPG. If you're using a SmartCard, "
0635                                                    "please ensure it's inserted then try again.<br><br>GPG error was <b>%2</b></qt>",
0636                                                    wb->_name.toHtmlEscaped(),
0637                                                    res.error().asString()),
0638                                               i18n("kwalletd GPG backend"),
0639                                               btnRetry,
0640                                               KStandardGuiItem::cancel());
0641         if (userChoice == KMessageBox::PrimaryAction) {
0642             decryptedData.seek(0, SEEK_SET);
0643             goto retry_label;
0644         }
0645         return -1;
0646     }
0647 
0648     decryptedData.seek(0, SEEK_SET);
0649     QByteArray dataBuffer;
0650     while ((bytes = decryptedData.read(buffer, sizeof(buffer) / sizeof(buffer[0])))) {
0651         dataBuffer.append(buffer, bytes);
0652     }
0653 
0654     // load the wallet from the decrypted data
0655     QDataStream dataStream(dataBuffer);
0656     QString keyID;
0657     QByteArray hashes;
0658     QByteArray values;
0659     dataStream >> keyID;
0660     dataStream >> hashes;
0661     dataStream >> values;
0662 
0663     // locate the GPG key having the ID found inside the file. This will be needed later, when writing changes to disk.
0664     QDataStream fileStream(&sf);
0665     fileStream.setDevice(nullptr);
0666     qCDebug(KWALLETBACKEND_LOG) << "This wallet was encrypted using GPG key with ID " << keyID;
0667 
0668     ctx->setKeyListMode(GpgME::KeyListMode::Local);
0669     err = ctx->startKeyListing();
0670     while (!err) {
0671         GpgME::Key k = ctx->nextKey(err);
0672         if (err) {
0673             break;
0674         }
0675         if (keyID == k.keyID()) {
0676             qCDebug(KWALLETBACKEND_LOG) << "The key was found.";
0677             wb->_gpgKey = k;
0678             break;
0679         }
0680     }
0681     ctx->endKeyListing();
0682     if (wb->_gpgKey.isNull()) {
0683         KMessageBox::errorWId(w,
0684                               i18n("<qt>Error when attempting to open the wallet <b>%1</b>. The wallet was encrypted using the GPG Key ID <b>%2</b> but this "
0685                                    "key was not found on your system.</qt>",
0686                                    wb->_name.toHtmlEscaped(),
0687                                    keyID));
0688         return -1;
0689     }
0690 
0691     QDataStream hashStream(hashes);
0692     QDataStream valueStream(values);
0693 
0694     quint32 hashCount;
0695     hashStream >> hashCount;
0696     if (hashCount > 0xFFFF) {
0697         return -43;
0698     }
0699 
0700     quint32 folderCount = hashCount;
0701     while (hashCount--) {
0702         Digest d;
0703         hashStream.readRawData(d, 16);
0704 
0705         quint32 folderSize;
0706         hashStream >> folderSize;
0707 
0708         MD5Digest ba = MD5Digest(reinterpret_cast<char *>(d));
0709         QMap<MD5Digest, QList<MD5Digest>>::iterator it = wb->_hashes.insert(ba, QList<MD5Digest>());
0710         while (folderSize--) {
0711             Digest d2;
0712             hashStream.readRawData(d2, 16);
0713             ba = MD5Digest(d2);
0714             (*it).append(ba);
0715         }
0716     }
0717 
0718     while (folderCount--) {
0719         QString folder;
0720         valueStream >> folder;
0721 
0722         quint32 entryCount;
0723         valueStream >> entryCount;
0724 
0725         wb->_entries[folder].clear();
0726 
0727         while (entryCount--) {
0728             KWallet::Wallet::EntryType et = KWallet::Wallet::Unknown;
0729             Entry *e = new Entry;
0730 
0731             QString key;
0732             valueStream >> key;
0733 
0734             qint32 x = 0; // necessary to read properly
0735             valueStream >> x;
0736             et = static_cast<KWallet::Wallet::EntryType>(x);
0737 
0738             switch (et) {
0739             case KWallet::Wallet::Password:
0740             case KWallet::Wallet::Stream:
0741             case KWallet::Wallet::Map:
0742                 break;
0743             default: // Unknown entry
0744                 delete e;
0745                 continue;
0746             }
0747 
0748             QByteArray a;
0749             valueStream >> a;
0750             e->setValue(a);
0751             e->setType(et);
0752             e->setKey(key);
0753             wb->_entries[folder][key] = e;
0754         }
0755     }
0756 
0757     wb->_open = true;
0758 
0759     return 0;
0760 }
0761 #endif // HAVE_GPGMEPP
0762 
0763 } // namespace