8#include "kwalletbackend.h"
9#include "kwalletbackend_debug.h"
15#include <gpgme++/key.h>
18#include <KNotification>
19#include <KLocalizedString>
25#include <QCryptographicHash>
26#include <QRegularExpression>
27#include <QStandardPaths>
42#define KWALLETSTORAGE_VERSION_MAJOR 0
43#define KWALLETSTORAGE_VERSION_MINOR 1
45using namespace KWallet;
47#define KWMAGIC "KWALLET\n\r\0\r\n"
49static const QByteArray walletAllowedChars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789^&'@{}[],$=!-#()%.+_\r\n\t\f\v ";
65class Backend::BackendPrivate
74Backend::Backend(
const QString &name,
bool isPath)
77 _cipherType(KWallet::BACKEND_CIPHER_UNKNOWN)
83 _path = getSaveLocation() +
'/' + encodeWalletName(_name) +
".kwl";
97QString Backend::getSaveLocation()
100 QDir writeDir(writeLocation);
101 if (!writeDir.exists()) {
102 if (!writeDir.mkpath(writeLocation)) {
103 qFatal(
"Cannot create wallet save location!");
108 return writeLocation;
111void Backend::setCipherType(BackendCipherType ct)
114 assert(_cipherType == KWallet::BACKEND_CIPHER_UNKNOWN);
120 if (!gcry_check_version(
"1.5.0")) {
121 qCWarning(KWALLETBACKEND_LOG) <<
"libcrypt version is too old";
122 return GPG_ERR_USER_2;
126 bool static gcry_secmem_init =
false;
127 if (!gcry_secmem_init) {
128 error = gcry_control(GCRYCTL_INIT_SECMEM, 32768, 0);
130 qCWarning(KWALLETBACKEND_LOG) <<
"Can't get secure memory:" <<
error;
133 gcry_secmem_init =
true;
136 gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
139 GCRY_KDF_PBKDF2, GCRY_MD_SHA512,
141 PBKDF2_SHA512_ITERATIONS, PBKDF2_SHA512_KEYSIZE, hash.
data());
150 int shasz = sha.size() / 8;
156 sha.process(password.
data(), qMin(password.
size(), 16));
159 for (
int i = 0; i < 2000; i++) {
160 memcpy(block1.data(), sha.hash(), shasz);
162 sha.process(block1.data(), shasz);
167 if (password.
size() > 16) {
168 sha.process(password.
data() + 16, qMin(password.
size() - 16, 16));
171 for (
int i = 0; i < 2000; i++) {
172 memcpy(block2.data(), sha.hash(), shasz);
174 sha.process(block2.data(), shasz);
179 if (password.
size() > 32) {
180 sha.process(password.
data() + 32, qMin(password.
size() - 32, 16));
184 for (
int i = 0; i < 2000; i++) {
185 memcpy(block3.data(), sha.hash(), shasz);
187 sha.process(block3.data(), shasz);
192 if (password.
size() > 48) {
193 sha.process(password.
data() + 48, password.
size() - 48);
197 for (
int i = 0; i < 2000; i++) {
198 memcpy(block4.data(), sha.hash(), shasz);
200 sha.process(block4.data(), shasz);
206 memcpy(hash.
data(), block1.data(), 14);
207 memcpy(hash.
data() + 14, block2.data(), 14);
208 memcpy(hash.
data() + 28, block3.data(), 14);
209 memcpy(hash.
data() + 42, block4.data(), 14);
214 memcpy(hash.
data(), block1.data(), 20);
215 memcpy(hash.
data() + 20, block2.data(), 20);
216 memcpy(hash.
data() + 40, block3.data(), 16);
222 memcpy(hash.
data(), block1.data(), 20);
223 memcpy(hash.
data() + 20, block2.data(), 20);
229 memcpy(hash.
data(), block1.data(), 20);
240 qCDebug(KWALLETBACKEND_LOG) <<
"refCount negative!";
246bool Backend::exists(
const QString &wallet)
248 QString saveLocation = getSaveLocation();
255QString Backend::openRCToString(
int rc)
259 return i18n(
"Already open.");
261 return i18n(
"Error opening file.");
263 return i18n(
"Not a wallet file.");
265 return i18n(
"Unsupported file format revision.");
267 return QStringLiteral(
"Unknown cipher or hash");
269 return i18n(
"Unknown encryption scheme.");
271 return i18n(
"Corrupt file?");
273 return i18n(
"Error validating wallet integrity. Possibly corrupted.");
277 return i18n(
"Read error - possibly incorrect password.");
279 return i18n(
"Decryption error.");
285int Backend::open(
const QByteArray &password, WId w)
291 setPassword(password);
292 return openInternal(w);
296int Backend::open(
const GpgME::Key &key)
302 return openInternal();
306int Backend::openPreHashed(
const QByteArray &passwordHash)
313 if (passwordHash.
size() != 20 && passwordHash.
size() != 40 &&
314 passwordHash.
size() != 56) {
318 _passhash = passwordHash;
319 _newPassHash = passwordHash;
322 return openInternal();
325int Backend::openInternal(WId w)
331 QFile newfile(_path);
348 char magicBuf[KWMAGIC_LEN];
349 db.read(magicBuf, KWMAGIC_LEN);
350 if (memcmp(magicBuf, KWMAGIC, KWMAGIC_LEN) != 0) {
354 db.read(magicBuf, 4);
357 if (magicBuf[0] != KWALLETSTORAGE_VERSION_MAJOR) {
362 if (magicBuf[1] == 1) {
363 qCDebug(KWALLETBACKEND_LOG) <<
"Wallet new enough, using new hash";
365 }
else if (magicBuf[1] != 0) {
366 qCDebug(KWALLETBACKEND_LOG) <<
"Wallet is old, sad panda :(";
370 BackendPersistHandler *phandler = BackendPersistHandler::getPersistHandler(magicBuf);
371 if (
nullptr == phandler) {
374 int result = phandler->read(
this, db, w);
379void Backend::swapToNewHash()
383 qCDebug(KWALLETBACKEND_LOG) <<
"Runtime error on the new hash";
387 _passhash = _newPassHash;
392 QFile saltFile(path);
400 char *randomData = (
char *) gcry_random_bytes(PBKDF2_SHA512_SALTSIZE, GCRY_STRONG_RANDOM);
401 QByteArray salt(randomData, PBKDF2_SHA512_SALTSIZE);
404 if (saltFile.write(salt) != PBKDF2_SHA512_SALTSIZE) {
413int Backend::sync(WId w)
430 if (sf.write(KWMAGIC, KWMAGIC_LEN) != KWMAGIC_LEN) {
437 version[0] = KWALLETSTORAGE_VERSION_MAJOR;
439 version[1] = KWALLETSTORAGE_VERSION_MINOR;
446 BackendPersistHandler *phandler = BackendPersistHandler::getPersistHandler(_cipherType);
447 if (
nullptr == phandler) {
450 int rc = phandler->write(
this, sf, version, w);
455 notification->
setText(
i18n(
"Failed to sync wallet <b>%1</b> to disk. Error codes are:\nRC <b>%2</b>\nSF <b>%3</b>. Please file a BUG report using this information to bugs.kde.org", _name, rc, sf.errorString()));
462int Backend::closeInternal(
bool save)
484int Backend::close(
bool save)
486 int rc = closeInternal(save);
492 _newPassHash.
fill(0);
497const QString &Backend::walletName()
const
502int Backend::renameWallet(
const QString &newName,
bool isPath)
505 const auto saveLocation = getSaveLocation();
510 newPath = saveLocation +
QChar::fromLatin1(
'/') + encodeWalletName(newName) + QStringLiteral(
".kwl");
513 if (newPath == _path) {
521 int rc = closeInternal(
true);
528 saveLocation +
QChar::fromLatin1(
'/') + encodeWalletName(newName) + QStringLiteral(
".salt"));
541bool Backend::isOpen()
const
548 return _entries.
keys();
553 return _entries[_folder].
keys();
556Entry *Backend::readEntry(
const QString &key)
560 if (_open && hasEntry(key)) {
561 rc = _entries[_folder][key];
567#if KWALLET_BUILD_DEPRECATED_SINCE(5, 72)
581 const EntryMap &
map = _entries[_folder];
583 if (re.match(i.key()).hasMatch()) {
596 const EntryMap &
map = _entries[_folder];
602bool Backend::createFolder(
const QString &f)
608 _entries.
insert(f, EntryMap());
611 folderMd5.addData(f.
toUtf8());
617int Backend::renameEntry(
const QString &oldName,
const QString &newName)
619 EntryMap &emap = _entries[_folder];
623 if (oi != emap.end() && ni == emap.end()) {
624 Entry *e = oi.value();
629 folderMd5.addData(_folder.
toUtf8());
631 HashMap::iterator i = _hashes.
find(
MD5Digest(folderMd5.result()));
632 if (i != _hashes.
end()) {
635 oldMd5.addData(oldName.
toUtf8());
636 newMd5.addData(newName.
toUtf8());
637 i.value().removeAll(
MD5Digest(oldMd5.result()));
638 i.value().append(
MD5Digest(newMd5.result()));
646void Backend::writeEntry(Entry *e)
652 if (!hasEntry(e->key())) {
653 _entries[_folder][e->key()] =
new Entry;
655 _entries[_folder][e->key()]->copy(e);
658 folderMd5.addData(_folder.
toUtf8());
660 HashMap::iterator i = _hashes.
find(
MD5Digest(folderMd5.result()));
661 if (i != _hashes.
end()) {
663 md5.addData(e->key().
toUtf8());
664 i.value().append(
MD5Digest(md5.result()));
668bool Backend::hasEntry(
const QString &key)
const
673bool Backend::removeEntry(
const QString &key)
682 if (fi != _entries.
end() && ei != fi.value().end()) {
684 fi.value().erase(ei);
686 folderMd5.addData(_folder.
toUtf8());
688 HashMap::iterator i = _hashes.
find(
MD5Digest(folderMd5.result()));
689 if (i != _hashes.
end()) {
691 md5.addData(key.
toUtf8());
692 i.value().removeAll(
MD5Digest(md5.result()));
700bool Backend::removeFolder(
const QString &f)
708 if (fi != _entries.
end()) {
720 folderMd5.addData(f.
toUtf8());
728bool Backend::folderDoesNotExist(
const QString &folder)
const
731 md5.addData(folder.
toUtf8());
735bool Backend::entryDoesNotExist(
const QString &folder,
const QString &entry)
const
738 md5.addData(folder.
toUtf8());
739 HashMap::const_iterator i = _hashes.
find(
MD5Digest(md5.result()));
740 if (i != _hashes.
end()) {
742 md5.addData(entry.
toUtf8());
743 return !i.value().contains(
MD5Digest(md5.result()));
748void Backend::setPassword(
const QByteArray &password)
752 CipherBlockChain bf(&_bf);
753 _passhash.
resize(bf.keyLen() / 8);
754 _newPassHash.
resize(bf.keyLen() / 8);
755 _newPassHash.
fill(0);
757 password2hash(password, _passhash);
760 QFile saltFile(getSaveLocation() +
'/' + encodeWalletName(_name) +
".salt");
761 if (!saltFile.exists() || saltFile.size() == 0) {
762 salt = createAndSaveSalt(saltFile.fileName());
765 salt = createAndSaveSalt(saltFile.fileName());
767 salt = saltFile.readAll();
771 if (!salt.
isEmpty() && password2PBKDF2_SHA512(password, _newPassHash, salt) == 0) {
772 qCDebug(KWALLETBACKEND_LOG) <<
"Setting useNewHash to true";
778const GpgME::Key &Backend::gpgKey()
const
void setText(const QString &text)
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT unsigned int version()
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString name(StandardAction id)
const char * constData() const const
QByteArray & fill(char ch, qsizetype size)
QByteArray fromPercentEncoding(const QByteArray &input, char percent)
bool isEmpty() const const
void resize(qsizetype newSize, char c)
qsizetype size() const const
QByteArray toPercentEncoding(const QByteArray &exclude, const QByteArray &include, char percent) const const
bool exists() const const
bool rename(const QString &newName)
qint64 size() const const
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const Key &key) const const
iterator erase(const_iterator first, const_iterator last)
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
QList< Key > keys() const const
size_type remove(const Key &key)
QString wildcardToRegularExpression(QStringView pattern, WildcardConversionOptions options)
QString writableLocation(StandardLocation type)
QString fromUtf8(QByteArrayView str)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QByteArray toUtf8() const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)