8#include "backendpersisthandler.h"
9#include "kwalletbackend_debug.h"
11#include <KLocalizedString>
13#include <QCryptographicHash>
19#include <gpgme++/context.h>
20#include <gpgme++/data.h>
21#include <gpgme++/decryptionresult.h>
22#include <gpgme++/encryptionresult.h>
23#include <gpgme++/key.h>
24#include <gpgme++/keylistresult.h>
28#include "kwalletbackend.h"
37#define KWALLET_CIPHER_BLOWFISH_ECB 0
38#define KWALLET_CIPHER_3DES_CBC 1
39#define KWALLET_CIPHER_GPG 2
40#define KWALLET_CIPHER_BLOWFISH_CBC 3
42#define KWALLET_HASH_SHA1 0
43#define KWALLET_HASH_MD5 1
44#define KWALLET_HASH_PBKDF2_SHA512 2
48typedef char Digest[16];
50static int getRandomBlock(
QByteArray &randBlock)
58 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
62 if (!CryptGenRandom(hProv,
static_cast<DWORD
>(randBlock.
size()), (BYTE *)randBlock.
data())) {
67 CryptReleaseContext(hProv, 0);
69 return randBlock.
size();
75 QFile devrand(QStringLiteral(
"/dev/urandom"));
77 int rc = devrand.read(randBlock.
data(), randBlock.
size());
79 if (rc != randBlock.
size()) {
90 QFile devrand(QStringLiteral(
"/dev/random"));
96 int rc2 = devrand.read(randBlock.
data() + rc, randBlock.
size());
104 if (cnt > randBlock.
size()) {
107 }
while (rc < randBlock.
size());
117 QFile devrand(randFilename);
119 int rc = devrand.read(randBlock.
data(), randBlock.
size());
120 if (rc != randBlock.
size()) {
134BackendPersistHandler *BackendPersistHandler::getPersistHandler(BackendCipherType cipherType)
136 switch (cipherType) {
137 case BACKEND_CIPHER_BLOWFISH:
138 return new BlowfishPersistHandler;
140 case BACKEND_CIPHER_GPG:
141 return new GpgPersistHandler;
149BackendPersistHandler *BackendPersistHandler::getPersistHandler(
char magicBuf[12])
151 if ((magicBuf[2] == KWALLET_CIPHER_BLOWFISH_ECB || magicBuf[2] == KWALLET_CIPHER_BLOWFISH_CBC)
152 && (magicBuf[3] == KWALLET_HASH_SHA1 || magicBuf[3] == KWALLET_HASH_PBKDF2_SHA512)) {
153 bool useECBforReading = magicBuf[2] == KWALLET_CIPHER_BLOWFISH_ECB;
154 if (useECBforReading) {
155 qCDebug(KWALLETBACKEND_LOG) <<
"this wallet uses ECB encryption. It'll be converted to CBC on next save.";
157 return new BlowfishPersistHandler(useECBforReading);
160 if (magicBuf[2] == KWALLET_CIPHER_GPG && magicBuf[3] == 0) {
161 return new GpgPersistHandler;
169 assert(wb->_cipherType == BACKEND_CIPHER_BLOWFISH);
171 if (_useECBforReading) {
172 qCDebug(KWALLETBACKEND_LOG) <<
"This wallet used ECB and is now saved using CBC";
173 _useECBforReading =
false;
176 version[2] = KWALLET_CIPHER_BLOWFISH_CBC;
177 if (!wb->_useNewHash) {
178 version[3] = KWALLET_HASH_SHA1;
180 version[3] = KWALLET_HASH_PBKDF2_SHA512;
183 if (sf.
write(version) != 4) {
192 hashStream << static_cast<quint32>(wb->_entries.count());
205 dStream << static_cast<quint32>(i.value().count());
208 md5.addData(i.key().toUtf8());
209 hashStream.writeRawData(md5.result().constData(), 16);
210 hashStream << static_cast<quint32>(i.value().count());
214 dStream << static_cast<qint32>(j.value()->type());
215 dStream << j.value()->value();
218 md5.addData(j.key().toUtf8());
219 hashStream.writeRawData(md5.result().constData(), 16);
231 CipherBlockChain bf(&_bf);
233 sha.process(decrypted.
data(), decrypted.
size());
237 long blksz = bf.blockSize();
238 long newsize = decrypted.
size() + blksz +
242 int delta = (blksz - (newsize % blksz));
244 wholeFile.
resize(newsize);
247 randBlock.
resize(blksz + delta);
248 if (getRandomBlock(randBlock) < 0) {
255 for (
int i = 0; i < blksz; i++) {
256 wholeFile[i] = randBlock[i];
259 for (
int i = 0; i < 4; i++) {
260 wholeFile[(int)(i + blksz)] = (decrypted.
size() >> 8 * (3 - i)) & 0xff;
263 for (
int i = 0; i < decrypted.
size(); i++) {
264 wholeFile[(int)(i + blksz + 4)] = decrypted[i];
267 for (
int i = 0; i < delta; i++) {
268 wholeFile[(int)(i + blksz + 4 + decrypted.
size())] = randBlock[(int)(i + blksz)];
271 const char *hash = (
const char *)sha.hash();
272 for (
int i = 0; i < 20; i++) {
273 wholeFile[(int)(newsize - 20 + i)] = hash[i];
280 if (!bf.setKey(wb->_passhash.data(), wb->_passhash.size() * 8)) {
286 int rc = bf.encrypt(wholeFile.
data(), wholeFile.
size());
294 auto written = sf.
write(wholeFile);
295 if (written != wholeFile.
size()) {
301 qCDebug(KWALLETBACKEND_LOG) <<
"WARNING: wallet sync to disk failed! QSaveFile status was " << sf.
errorString();
311int BlowfishPersistHandler::read(Backend *wb,
QFile &db, WId)
313 wb->_cipherType = BACKEND_CIPHER_BLOWFISH;
323 for (
size_t i = 0; i < n; ++i) {
332 hds.readRawData(d, 16);
334 ba = MD5Digest(
reinterpret_cast<char *
>(d));
336 for (
size_t j = 0; j < fsz; ++j) {
337 hds.readRawData(d2, 16);
345 assert(encrypted.
size() < db.
size());
348 CipherBlockChain bf(&_bf, _useECBforReading);
349 int blksz = bf.blockSize();
350 if ((encrypted.
size() % blksz) != 0) {
354 bf.setKey((
void *)wb->_passhash.data(), wb->_passhash.size() * 8);
356 if (!encrypted.
data()) {
357 wb->_passhash.fill(0);
362 int rc = bf.decrypt(encrypted.
data(), encrypted.
size());
364 wb->_passhash.fill(0);
369 const char *t = encrypted.
data();
377 fsize |= (long(*t) << 24) & 0xff000000;
379 fsize |= (long(*t) << 16) & 0x00ff0000;
381 fsize |= (long(*t) << 8) & 0x0000ff00;
383 fsize |= long(*t) & 0x000000ff;
386 if (fsize < 0 || fsize >
long(encrypted.
size()) - blksz - 4) {
387 qCDebug(KWALLETBACKEND_LOG) <<
"fsize: " << fsize <<
" encrypted.size(): " << encrypted.
size() <<
" blksz: " << blksz;
394 sha.process(t, fsize);
395 const char *testhash = (
const char *)sha.hash();
398 int sz = encrypted.
size();
399 for (
int i = 0; i < 20; i++) {
400 if (testhash[i] != encrypted[sz - 20 + i]) {
417 while (!eStream.atEnd()) {
425 wb->_entries[folder].
clear();
427 for (
size_t i = 0; i < n; ++i) {
429 KWallet::Wallet::EntryType et = KWallet::Wallet::Unknown;
430 Entry *e =
new Entry;
434 et =
static_cast<KWallet::Wallet::EntryType
>(x);
437 case KWallet::Wallet::Password:
438 case KWallet::Wallet::Stream:
439 case KWallet::Wallet::Map:
451 wb->_entries[folder][key] = e;
461GpgME::Error initGpgME()
464 static bool alreadyInitialized =
false;
465 if (!alreadyInitialized) {
466 GpgME::initializeLibrary();
467 err = GpgME::checkEngine(GpgME::OpenPGP);
469 qCDebug(KWALLETBACKEND_LOG) <<
"OpenPGP not supported!";
471 alreadyInitialized =
true;
478 version[2] = KWALLET_CIPHER_GPG;
480 if (sf.
write(version) != 4) {
485 GpgME::Error err = initGpgME();
487 qCDebug(KWALLETBACKEND_LOG) <<
"initGpgME returned " << err.code();
489 i18n(
"<qt>Error when attempting to initialize OpenPGP while attempting to save the wallet <b>%1</b>. Error code is <b>%2</b>. "
490 "Please fix your system configuration, then try again.</qt>",
491 wb->_name.toHtmlEscaped(),
497 std::shared_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP));
499 qCDebug(KWALLETBACKEND_LOG) <<
"Cannot setup OpenPGP context!";
501 i18n(
"<qt>Error when attempting to initialize OpenPGP while attempting to save the wallet <b>%1</b>. Please fix your system "
502 "configuration, then try again.</qt>"),
507 assert(wb->_cipherType == BACKEND_CIPHER_GPG);
512 hashStream << static_cast<quint32>(wb->_entries.count());
518 for (; i != ie; ++i) {
519 valueStream << i.key();
520 valueStream << static_cast<quint32>(i.value().count());
523 md5.addData(i.key().toUtf8());
524 hashStream.writeRawData(md5.result().constData(), 16);
525 hashStream << static_cast<quint32>(i.value().count());
529 for (; j != je; ++j) {
530 valueStream << j.key();
531 valueStream << static_cast<qint32>(j.value()->type());
532 valueStream << j.value()->value();
535 md5.addData(j.key().toUtf8());
536 hashStream.writeRawData(md5.result().constData(), 16);
542 QString keyID(wb->_gpgKey.keyID());
544 dataStream << hashes;
545 dataStream << values;
547 GpgME::Data decryptedData(dataBuffer.
data(),
size_t(dataBuffer.
size()),
false);
548 GpgME::Data encryptedData;
549 std::vector<GpgME::Key> keys;
550 keys.push_back(wb->_gpgKey);
551 const GpgME::EncryptionResult res = ctx->encrypt(keys, decryptedData, encryptedData, GpgME::Context::None);
553 const int gpgerr = res.error().code();
555 i18n(
"<qt>Encryption error while attempting to save the wallet <b>%1</b>. Error code is <b>%2 (%3)</b>. Please fix your system "
556 "configuration, then try again. This error may occur if you are not using a full trust GPG key. Please ensure you have the "
557 "secret key for the key you are using.</qt>",
558 wb->_name.toHtmlEscaped(),
560 res.error().asString()));
561 qCDebug(KWALLETBACKEND_LOG) <<
"GpgME encryption error: " << gpgerr;
568 encryptedData.seek(0, SEEK_SET);
569 while ((bytes = encryptedData.read(buffer,
sizeof(buffer) /
sizeof(buffer[0]))) > 0) {
570 if (sf.
write(buffer, bytes) != bytes) {
572 i18n(
"<qt>File handling error while attempting to save the wallet <b>%1</b>. Error was <b>%2</b>. Please fix your system "
573 "configuration, then try again.</qt>",
574 wb->_name.toHtmlEscaped(),
582 qCDebug(KWALLETBACKEND_LOG) <<
"WARNING: wallet sync to disk failed! QSaveFile status was " << sf.
errorString();
589int GpgPersistHandler::read(Backend *wb,
QFile &sf, WId w)
591 GpgME::Error err = initGpgME();
594 i18n(
"<qt>Error when attempting to initialize OpenPGP while attempting to open the wallet <b>%1</b>. Error code is <b>%2</b>. "
595 "Please fix your system configuration, then try again.</qt>",
596 wb->_name.toHtmlEscaped(),
601 wb->_cipherType = BACKEND_CIPHER_GPG;
605 GpgME::Data encryptedData;
608 while ((bytes = sf.
read(buffer,
sizeof(buffer) /
sizeof(buffer[0])))) {
609 encryptedData.write(buffer, bytes);
613 std::shared_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP));
614 if (
nullptr == ctx) {
616 i18n(
"<qt>Error when attempting to initialize OpenPGP while attempting to open the wallet <b>%1</b>. Please fix your system "
617 "configuration, then try again.</qt>",
618 wb->_name.toHtmlEscaped()));
619 qCDebug(KWALLETBACKEND_LOG) <<
"Cannot setup OpenPGP context!";
623 GpgME::Data decryptedData;
624 encryptedData.seek(0, SEEK_SET);
625 GpgME::DecryptionResult res = ctx->decrypt(encryptedData, decryptedData);
627 qCDebug(KWALLETBACKEND_LOG) <<
"Error decrypting message: " << res.error().asString() <<
", code " << res.error().code() <<
", source "
628 << res.error().source();
634 i18n(
"<qt>Error when attempting to decrypt the wallet <b>%1</b> using GPG. If you're using a SmartCard, "
635 "please ensure it's inserted then try again.<br><br>GPG error was <b>%2</b></qt>",
636 wb->_name.toHtmlEscaped(),
637 res.error().asString()),
638 i18n(
"kwalletd GPG backend"),
642 decryptedData.seek(0, SEEK_SET);
648 decryptedData.seek(0, SEEK_SET);
650 while ((bytes = decryptedData.read(buffer,
sizeof(buffer) /
sizeof(buffer[0])))) {
651 dataBuffer.
append(buffer, bytes);
660 dataStream >> hashes;
661 dataStream >> values;
665 fileStream.setDevice(
nullptr);
666 qCDebug(KWALLETBACKEND_LOG) <<
"This wallet was encrypted using GPG key with ID " << keyID;
668 ctx->setKeyListMode(GpgME::KeyListMode::Local);
669 err = ctx->startKeyListing();
671 GpgME::Key k = ctx->nextKey(err);
675 if (keyID == k.keyID()) {
676 qCDebug(KWALLETBACKEND_LOG) <<
"The key was found.";
681 ctx->endKeyListing();
682 if (wb->_gpgKey.isNull()) {
684 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 "
685 "key was not found on your system.</qt>",
686 wb->_name.toHtmlEscaped(),
695 hashStream >> hashCount;
696 if (hashCount > 0xFFFF) {
700 quint32 folderCount = hashCount;
701 while (hashCount--) {
703 hashStream.readRawData(d, 16);
706 hashStream >> folderSize;
708 MD5Digest ba = MD5Digest(
reinterpret_cast<char *
>(d));
710 while (folderSize--) {
712 hashStream.readRawData(d2, 16);
718 while (folderCount--) {
720 valueStream >> folder;
723 valueStream >> entryCount;
725 wb->_entries[folder].
clear();
727 while (entryCount--) {
728 KWallet::Wallet::EntryType et = KWallet::Wallet::Unknown;
729 Entry *e =
new Entry;
736 et =
static_cast<KWallet::Wallet::EntryType
>(x);
739 case KWallet::Wallet::Password:
740 case KWallet::Wallet::Stream:
741 case KWallet::Wallet::Map:
753 wb->_entries[folder][key] = e;
QString i18n(const char *text, const TYPE &arg...)
KDB_EXPORT KDbVersionInfo version()
ButtonCode warningTwoActionsWId(WId parent_id, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
void errorWId(WId parent_id, const QString &text, const QString &title=QString(), Options options=Notify)
QByteArray & append(QByteArrayView data)
QByteArray & fill(char ch, qsizetype size)
void resize(qsizetype newSize, char c)
qsizetype size() const const
bool exists() const const
virtual qint64 size() const const override
QString errorString() const const
QByteArray read(qint64 maxSize)
qint64 write(const QByteArray &data)
iterator insert(const Key &key, const T &value)
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QString toHtmlEscaped() const const