delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / kwalletd / backend / kwalletbackend.cc
blob3bd036defda4fab759f60c57072ed6f0888df82b
1 /* This file is part of the KDE project
3 * Copyright (C) 2001-2004 George Staikos <staikos@kde.org>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
21 #include "kwalletbackend.h"
23 #include <stdlib.h>
25 #include <kdebug.h>
26 #include <kglobal.h>
27 #include <klocale.h>
28 #include <kcodecs.h>
29 #include <ksavefile.h>
30 #include <kstandarddirs.h>
32 #include <QtCore/QFile>
33 #include <QtCore/QFileInfo>
34 #include <QtCore/QRegExp>
36 #include "blowfish.h"
37 #include "sha1.h"
38 #include "cbc.h"
40 #include <assert.h>
42 // quick fix to get random numbers on win32
43 #ifdef Q_OS_WIN //krazy:exclude=cpp
44 #include <windows.h>
45 #include <wincrypt.h>
46 #endif
48 #define KWALLET_VERSION_MAJOR 0
49 #define KWALLET_VERSION_MINOR 0
51 #define KWALLET_CIPHER_BLOWFISH_CBC 0
52 #define KWALLET_CIPHER_3DES_CBC 1 // unsupported
54 #define KWALLET_HASH_SHA1 0
55 #define KWALLET_HASH_MD5 1 // unsupported
58 using namespace KWallet;
60 #define KWMAGIC "KWALLET\n\r\0\r\n"
61 #define KWMAGIC_LEN 12
63 class Backend::BackendPrivate
67 static void initKWalletDir()
69 KGlobal::dirs()->addResourceType("kwallet", 0, "share/apps/kwallet");
72 Backend::Backend(const QString& name, bool isPath) : d(0), _name(name), _ref(0) {
73 initKWalletDir();
74 if (isPath) {
75 _path = name;
76 } else {
77 _path = KGlobal::dirs()->saveLocation("kwallet") + _name + ".kwl";
80 _open = false;
84 Backend::~Backend() {
85 if (_open) {
86 close();
88 delete d;
91 static int getRandomBlock(QByteArray& randBlock) {
93 #ifdef Q_OS_WIN //krazy:exclude=cpp
95 // Use windows crypto API to get randomness on win32
96 // HACK: this should be done using qca
97 HCRYPTPROV hProv;
99 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL,
100 CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) return -1; // couldn't get random data
102 if (!CryptGenRandom(hProv, static_cast<DWORD>(randBlock.size()),
103 (BYTE*)randBlock.data())) {
104 return -3; // read error
107 // release the crypto context
108 CryptReleaseContext(hProv, 0);
110 return randBlock.size();
112 #else
114 // First try /dev/urandom
115 if (QFile::exists("/dev/urandom")) {
116 QFile devrand("/dev/urandom");
117 if (devrand.open(QIODevice::ReadOnly)) {
118 int rc = devrand.read(randBlock.data(), randBlock.size());
120 if (rc != randBlock.size()) {
121 return -3; // not enough data read
124 return 0;
128 // If that failed, try /dev/random
129 // FIXME: open in noblocking mode!
130 if (QFile::exists("/dev/random")) {
131 QFile devrand("/dev/random");
132 if (devrand.open(QIODevice::ReadOnly)) {
133 int rc = 0;
134 int cnt = 0;
136 do {
137 int rc2 = devrand.read(randBlock.data() + rc, randBlock.size());
139 if (rc2 < 0) {
140 return -3; // read error
143 rc += rc2;
144 cnt++;
145 if (cnt > randBlock.size()) {
146 return -4; // reading forever?!
148 } while(rc < randBlock.size());
150 return 0;
154 // EGD method
155 QString randFilename = QString::fromLocal8Bit(qgetenv("RANDFILE"));
156 if (!randFilename.isEmpty()) {
157 if (QFile::exists(randFilename)) {
158 QFile devrand(randFilename);
159 if (devrand.open(QIODevice::ReadOnly)) {
160 int rc = devrand.read(randBlock.data(), randBlock.size());
161 if (rc != randBlock.size()) {
162 return -3; // not enough data read
164 return 0;
169 // Couldn't get any random data!!
170 return -1;
172 #endif
176 // this should be SHA-512 for release probably
177 static int password2hash(const QByteArray& password, QByteArray& hash) {
178 SHA1 sha;
179 int shasz = sha.size() / 8;
181 assert(shasz >= 20);
183 QByteArray block1(shasz, 0);
185 sha.process(password.data(), qMin(password.size(), 16));
187 // To make brute force take longer
188 for (int i = 0; i < 2000; i++) {
189 memcpy(block1.data(), sha.hash(), shasz);
190 sha.reset();
191 sha.process(block1.data(), shasz);
194 sha.reset();
196 if (password.size() > 16) {
197 sha.process(password.data() + 16, qMin(password.size() - 16, 16));
198 QByteArray block2(shasz, 0);
199 // To make brute force take longer
200 for (int i = 0; i < 2000; i++) {
201 memcpy(block2.data(), sha.hash(), shasz);
202 sha.reset();
203 sha.process(block2.data(), shasz);
206 sha.reset();
208 if (password.size() > 32) {
209 sha.process(password.data() + 32, qMin(password.size() - 32, 16));
211 QByteArray block3(shasz, 0);
212 // To make brute force take longer
213 for (int i = 0; i < 2000; i++) {
214 memcpy(block3.data(), sha.hash(), shasz);
215 sha.reset();
216 sha.process(block3.data(), shasz);
219 sha.reset();
221 if (password.size() > 48) {
222 sha.process(password.data() + 48, password.size() - 48);
224 QByteArray block4(shasz, 0);
225 // To make brute force take longer
226 for (int i = 0; i < 2000; i++) {
227 memcpy(block4.data(), sha.hash(), shasz);
228 sha.reset();
229 sha.process(block4.data(), shasz);
232 sha.reset();
233 // split 14/14/14/14
234 hash.resize(56);
235 memcpy(hash.data(), block1.data(), 14);
236 memcpy(hash.data() + 14, block2.data(), 14);
237 memcpy(hash.data() + 28, block3.data(), 14);
238 memcpy(hash.data() + 42, block4.data(), 14);
239 block4.fill(0);
240 } else {
241 // split 20/20/16
242 hash.resize(56);
243 memcpy(hash.data(), block1.data(), 20);
244 memcpy(hash.data() + 20, block2.data(), 20);
245 memcpy(hash.data() + 40, block3.data(), 16);
247 block3.fill(0);
248 } else {
249 // split 20/20
250 hash.resize(40);
251 memcpy(hash.data(), block1.data(), 20);
252 memcpy(hash.data() + 20, block2.data(), 20);
254 block2.fill(0);
255 } else {
256 // entirely block1
257 hash.resize(20);
258 memcpy(hash.data(), block1.data(), 20);
261 block1.fill(0);
263 return 0;
267 int Backend::deref() {
268 if (--_ref < 0) {
269 kDebug() << "refCount negative!";
270 _ref = 0;
272 return _ref;
275 bool Backend::exists(const QString& wallet) {
276 initKWalletDir();
277 QString path = KGlobal::dirs()->saveLocation("kwallet") + '/' + wallet + ".kwl";
278 // Note: 60 bytes is presently the minimum size of a wallet file.
279 // Anything smaller is junk.
280 return QFile::exists(path) && QFileInfo(path).size() >= 60;
284 QString Backend::openRCToString(int rc) {
285 switch (rc) {
286 case -255:
287 return i18n("Already open.");
288 case -2:
289 return i18n("Error opening file.");
290 case -3:
291 return i18n("Not a wallet file.");
292 case -4:
293 return i18n("Unsupported file format revision.");
294 case -42:
295 return i18n("Unknown encryption scheme.");
296 case -43:
297 return i18n("Corrupt file?");
298 case -8:
299 return i18n("Error validating wallet integrity. Possibly corrupted.");
300 case -5:
301 case -7:
302 case -9:
303 return i18n("Read error - possibly incorrect password.");
304 case -6:
305 return i18n("Decryption error.");
306 default:
307 return QString();
312 int Backend::open(const QByteArray& password) {
314 if (_open) {
315 return -255; // already open
318 setPassword(password);
320 // No wallet existed. Let's create it.
321 // Note: 60 bytes is presently the minimum size of a wallet file.
322 // Anything smaller is junk and should be deleted.
323 if (!QFile::exists(_path) || QFileInfo(_path).size() < 60) {
324 QFile newfile(_path);
325 if (!newfile.open(QIODevice::ReadWrite)) {
326 return -2; // error opening file
328 newfile.close();
329 _open = true;
330 sync();
331 return 1; // new file opened, but OK
334 QFile db(_path);
336 if (!db.open(QIODevice::ReadOnly)) {
337 return -2; // error opening file
340 char magicBuf[KWMAGIC_LEN];
341 db.read(magicBuf, KWMAGIC_LEN);
342 if (memcmp(magicBuf, KWMAGIC, KWMAGIC_LEN) != 0) {
343 return -3; // bad magic
346 db.read(magicBuf, 4);
348 // First byte is major version, second byte is minor version
349 if (magicBuf[0] != KWALLET_VERSION_MAJOR) {
350 return -4; // unknown version
353 if (magicBuf[1] != KWALLET_VERSION_MINOR) {
354 return -4; // unknown version
357 if (magicBuf[2] != KWALLET_CIPHER_BLOWFISH_CBC) {
358 return -42; // unknown cipher
361 if (magicBuf[3] != KWALLET_HASH_SHA1) {
362 return -42; // unknown hash
365 _hashes.clear();
366 // Read in the hashes
367 QDataStream hds(&db);
368 quint32 n;
369 hds >> n;
370 if (n > 0xffff) { // sanity check
371 return -43;
374 for (size_t i = 0; i < n; ++i) {
375 KMD5::Digest d, d2; // judgment day
376 MD5Digest ba;
377 QMap<MD5Digest,QList<MD5Digest> >::iterator it;
378 quint32 fsz;
379 if (hds.atEnd()) return -43;
380 hds.readRawData(reinterpret_cast<char *>(d), 16);
381 hds >> fsz;
382 ba = MD5Digest(reinterpret_cast<char *>(d));
383 it = _hashes.insert(ba, QList<MD5Digest>());
384 for (size_t j = 0; j < fsz; ++j) {
385 hds.readRawData(reinterpret_cast<char *>(d2), 16);
386 ba = MD5Digest(reinterpret_cast<char *>(d2));
387 (*it).append(ba);
391 // Read in the rest of the file.
392 QByteArray encrypted = db.readAll();
393 assert(encrypted.size() < db.size());
395 BlowFish _bf;
396 CipherBlockChain bf(&_bf);
397 int blksz = bf.blockSize();
398 if ((encrypted.size() % blksz) != 0) {
399 return -5; // invalid file structure
402 bf.setKey((void *)_passhash.data(), _passhash.size()*8);
404 if (!encrypted.data()) {
405 _passhash.fill(0);
406 encrypted.fill(0);
407 return -7; // file structure error
410 int rc = bf.decrypt(encrypted.data(), encrypted.size());
411 if (rc < 0) {
412 _passhash.fill(0);
413 encrypted.fill(0);
414 return -6; // decrypt error
417 const char *t = encrypted.data();
419 // strip the leading data
420 t += blksz; // one block of random data
422 // strip the file size off
423 long fsize = 0;
425 fsize |= (long(*t) << 24) & 0xff000000;
426 t++;
427 fsize |= (long(*t) << 16) & 0x00ff0000;
428 t++;
429 fsize |= (long(*t) << 8) & 0x0000ff00;
430 t++;
431 fsize |= long(*t) & 0x000000ff;
432 t++;
434 if (fsize < 0 || fsize > long(encrypted.size()) - blksz - 4) {
435 //kDebug() << "fsize: " << fsize << " encrypted.size(): " << encrypted.size() << " blksz: " << blksz;
436 encrypted.fill(0);
437 return -9; // file structure error.
440 // compute the hash ourself
441 SHA1 sha;
442 sha.process(t, fsize);
443 const char *testhash = (const char *)sha.hash();
445 // compare hashes
446 int sz = encrypted.size();
447 for (int i = 0; i < 20; i++) {
448 if (testhash[i] != encrypted[sz - 20 + i]) {
449 encrypted.fill(0);
450 sha.reset();
451 return -8; // hash error.
455 sha.reset();
457 // chop off the leading blksz+4 bytes
458 QByteArray tmpenc(encrypted.data()+blksz+4, fsize);
459 encrypted = tmpenc;
460 tmpenc.fill(0);
462 // Load the data structures up
463 QDataStream eStream(encrypted);
465 while (!eStream.atEnd()) {
466 QString folder;
467 quint32 n;
469 eStream >> folder;
470 eStream >> n;
472 // Force initialisation
473 _entries[folder].clear();
475 for (size_t i = 0; i < n; ++i) {
476 QString key;
477 KWallet::Wallet::EntryType et = KWallet::Wallet::Unknown;
478 Entry *e = new Entry;
479 eStream >> key;
480 qint32 x = 0; // necessary to read properly
481 eStream >> x;
482 et = static_cast<KWallet::Wallet::EntryType>(x);
484 switch (et) {
485 case KWallet::Wallet::Password:
486 case KWallet::Wallet::Stream:
487 case KWallet::Wallet::Map:
488 break;
489 default: // Unknown entry
490 delete e;
491 continue;
494 QByteArray a;
495 eStream >> a;
496 e->setValue(a);
497 e->setType(et);
498 e->setKey(key);
499 _entries[folder][key] = e;
503 _open = true;
504 encrypted.fill(0);
505 return 0;
509 int Backend::sync() {
510 if (!_open) {
511 return -255; // not open yet
514 KSaveFile sf(_path);
516 if (!sf.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) {
517 return -1; // error opening file
519 sf.setPermissions(QFile::ReadUser|QFile::WriteUser);
521 if (sf.write(KWMAGIC, KWMAGIC_LEN) != KWMAGIC_LEN) {
522 sf.abort();
523 return -4; // write error
526 // Write the version number
527 QByteArray version(4, 0);
528 version[0] = KWALLET_VERSION_MAJOR;
529 version[1] = KWALLET_VERSION_MINOR;
530 version[2] = KWALLET_CIPHER_BLOWFISH_CBC;
531 version[3] = KWALLET_HASH_SHA1;
532 if (sf.write(version, 4) != 4) {
533 sf.abort();
534 return -4; // write error
537 // Holds the hashes we write out
538 QByteArray hashes;
539 QDataStream hashStream(&hashes, QIODevice::WriteOnly);
540 KMD5 md5;
541 hashStream << static_cast<quint32>(_entries.count());
543 // Holds decrypted data prior to encryption
544 QByteArray decrypted;
546 // FIXME: we should estimate the amount of data we will write in each
547 // buffer and resize them approximately in order to avoid extra
548 // resizes.
550 // populate decrypted
551 QDataStream dStream(&decrypted, QIODevice::WriteOnly);
552 for (FolderMap::ConstIterator i = _entries.constBegin(); i != _entries.constEnd(); ++i) {
553 dStream << i.key();
554 dStream << static_cast<quint32>(i.value().count());
556 md5.reset();
557 md5.update(i.key().toUtf8());
558 hashStream.writeRawData(reinterpret_cast<const char*>(&(md5.rawDigest()[0])), 16);
559 hashStream << static_cast<quint32>(i.value().count());
561 for (EntryMap::ConstIterator j = i.value().constBegin(); j != i.value().constEnd(); ++j) {
562 dStream << j.key();
563 dStream << static_cast<qint32>(j.value()->type());
564 dStream << j.value()->value();
566 md5.reset();
567 md5.update(j.key().toUtf8());
568 hashStream.writeRawData(reinterpret_cast<const char*>(&(md5.rawDigest()[0])), 16);
572 if (sf.write(hashes, hashes.size()) != hashes.size()) {
573 sf.abort();
574 return -4; // write error
577 // calculate the hash of the file
578 SHA1 sha;
579 BlowFish _bf;
580 CipherBlockChain bf(&_bf);
582 sha.process(decrypted.data(), decrypted.size());
584 // prepend and append the random data
585 QByteArray wholeFile;
586 long blksz = bf.blockSize();
587 long newsize = decrypted.size() +
588 blksz + // encrypted block
589 4 + // file size
590 20; // size of the SHA hash
592 int delta = (blksz - (newsize % blksz));
593 newsize += delta;
594 wholeFile.resize(newsize);
596 QByteArray randBlock;
597 randBlock.resize(blksz+delta);
598 if (getRandomBlock(randBlock) < 0) {
599 sha.reset();
600 decrypted.fill(0);
601 sf.abort();
602 return -3; // Fatal error: can't get random
605 for (int i = 0; i < blksz; i++) {
606 wholeFile[i] = randBlock[i];
609 for (int i = 0; i < 4; i++) {
610 wholeFile[(int)(i+blksz)] = (decrypted.size() >> 8*(3-i))&0xff;
613 for (int i = 0; i < decrypted.size(); i++) {
614 wholeFile[(int)(i+blksz+4)] = decrypted[i];
617 for (int i = 0; i < delta; i++) {
618 wholeFile[(int)(i+blksz+4+decrypted.size())] = randBlock[(int)(i+blksz)];
621 const char *hash = (const char *)sha.hash();
622 for (int i = 0; i < 20; i++) {
623 wholeFile[(int)(newsize - 20 + i)] = hash[i];
626 sha.reset();
627 decrypted.fill(0);
629 // encrypt the data
630 if (!bf.setKey(_passhash.data(), _passhash.size() * 8)) {
631 wholeFile.fill(0);
632 sf.abort();
633 return -2; // encrypt error
636 int rc = bf.encrypt(wholeFile.data(), wholeFile.size());
637 if (rc < 0) {
638 wholeFile.fill(0);
639 sf.abort();
640 return -2; // encrypt error
643 // write the file
644 if (sf.write(wholeFile, wholeFile.size()) != wholeFile.size()) {
645 wholeFile.fill(0);
646 sf.abort();
647 return -4; // write error
649 if (!sf.finalize()) {
650 wholeFile.fill(0);
651 return -4; // write error
654 wholeFile.fill(0);
656 return 0;
660 int Backend::close(bool save) {
661 // save if requested
662 if (save) {
663 int rc = sync();
664 if (rc != 0) {
665 return rc;
669 // do the actual close
670 for (FolderMap::ConstIterator i = _entries.constBegin(); i != _entries.constEnd(); ++i) {
671 for (EntryMap::ConstIterator j = i.value().constBegin(); j != i.value().constEnd(); ++j) {
672 delete j.value();
675 _entries.clear();
677 // empty the password hash
678 _passhash.fill(0);
680 _open = false;
682 return 0;
685 const QString& Backend::walletName() const {
686 return _name;
690 bool Backend::isOpen() const {
691 return _open;
695 QStringList Backend::folderList() const {
696 return _entries.keys();
700 QStringList Backend::entryList() const {
701 return _entries[_folder].keys();
705 Entry *Backend::readEntry(const QString& key) {
706 Entry *rc = 0L;
708 if (_open && hasEntry(key)) {
709 rc = _entries[_folder][key];
712 return rc;
716 QList<Entry*> Backend::readEntryList(const QString& key) {
717 QList<Entry*> rc;
719 if (!_open) {
720 return rc;
723 QRegExp re(key, Qt::CaseSensitive, QRegExp::Wildcard);
725 const EntryMap& map = _entries[_folder];
726 for (EntryMap::ConstIterator i = map.begin(); i != map.end(); ++i) {
727 if (re.exactMatch(i.key())) {
728 rc.append(i.value());
731 return rc;
735 bool Backend::createFolder(const QString& f) {
736 if (_entries.contains(f)) {
737 return false;
740 _entries.insert(f, EntryMap());
742 KMD5 folderMd5;
743 folderMd5.update(f.toUtf8());
744 _hashes.insert(MD5Digest(folderMd5.rawDigest()), QList<MD5Digest>());
746 return true;
750 int Backend::renameEntry(const QString& oldName, const QString& newName) {
751 EntryMap& emap = _entries[_folder];
752 EntryMap::Iterator oi = emap.find(oldName);
753 EntryMap::Iterator ni = emap.find(newName);
755 if (oi != emap.end() && ni == emap.end()) {
756 Entry *e = oi.value();
757 emap.erase(oi);
758 emap[newName] = e;
760 KMD5 folderMd5;
761 folderMd5.update(_folder.toUtf8());
763 HashMap::iterator i = _hashes.find(MD5Digest(folderMd5.rawDigest()));
764 if (i != _hashes.end()) {
765 KMD5 oldMd5, newMd5;
766 oldMd5.update(oldName.toUtf8());
767 newMd5.update(newName.toUtf8());
768 i.value().removeAll(MD5Digest(oldMd5.rawDigest()));
769 i.value().append(MD5Digest(newMd5.rawDigest()));
771 return 0;
774 return -1;
778 void Backend::writeEntry(Entry *e) {
779 if (!_open)
780 return;
782 if (!hasEntry(e->key())) {
783 _entries[_folder][e->key()] = new Entry;
785 _entries[_folder][e->key()]->copy(e);
787 KMD5 folderMd5;
788 folderMd5.update(_folder.toUtf8());
790 HashMap::iterator i = _hashes.find(MD5Digest(folderMd5.rawDigest()));
791 if (i != _hashes.end()) {
792 KMD5 md5;
793 md5.update(e->key().toUtf8());
794 i.value().append(MD5Digest(md5.rawDigest()));
799 bool Backend::hasEntry(const QString& key) const {
800 return _entries.contains(_folder) && _entries[_folder].contains(key);
804 bool Backend::removeEntry(const QString& key) {
805 if (!_open) {
806 return false;
809 FolderMap::Iterator fi = _entries.find(_folder);
810 EntryMap::Iterator ei = fi.value().find(key);
812 if (fi != _entries.end() && ei != fi.value().end()) {
813 delete ei.value();
814 fi.value().erase(ei);
815 KMD5 folderMd5;
816 folderMd5.update(_folder.toUtf8());
818 HashMap::iterator i = _hashes.find(MD5Digest(folderMd5.rawDigest()));
819 if (i != _hashes.end()) {
820 KMD5 md5;
821 md5.update(key.toUtf8());
822 i.value().removeAll(MD5Digest(md5.rawDigest()));
824 return true;
827 return false;
831 bool Backend::removeFolder(const QString& f) {
832 if (!_open) {
833 return false;
836 FolderMap::Iterator fi = _entries.find(f);
838 if (fi != _entries.end()) {
839 if (_folder == f) {
840 _folder.clear();
843 for (EntryMap::Iterator ei = fi.value().begin(); ei != fi.value().end(); ++ei) {
844 delete ei.value();
847 _entries.erase(fi);
849 KMD5 folderMd5;
850 folderMd5.update(f.toUtf8());
851 _hashes.remove(MD5Digest(folderMd5.rawDigest()));
852 return true;
855 return false;
859 bool Backend::folderDoesNotExist(const QString& folder) const {
860 KMD5 md5;
861 md5.update(folder.toUtf8());
862 return !_hashes.contains(MD5Digest(md5.rawDigest()));
866 bool Backend::entryDoesNotExist(const QString& folder, const QString& entry) const {
867 KMD5 md5;
868 md5.update(folder.toUtf8());
869 HashMap::const_iterator i = _hashes.find(MD5Digest(md5.rawDigest()));
870 if (i != _hashes.end()) {
871 md5.reset();
872 md5.update(entry.toUtf8());
873 return !i.value().contains(MD5Digest(md5.rawDigest()));
875 return true;
878 void Backend::setPassword(const QByteArray &password) {
879 _passhash.fill(0); // empty just in case
880 BlowFish _bf;
881 CipherBlockChain bf(&_bf);
882 _passhash.resize(bf.keyLen()/8);
883 password2hash(password, _passhash);