Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFelix Geyer <debfx@fobos.de>2012-05-09 00:03:59 +0400
committerFelix Geyer <debfx@fobos.de>2012-05-09 00:06:12 +0400
commitebce183925d7d22359e48a367eaf31c14677bee3 (patch)
tree4d0d437f428fb8cf270a5639f3bc0e4c1c48b25b /src
parent38e421d9c1ecc41f6161b0121a55aa3a59889c7f (diff)
Start implementing support for reading KeePass 1 databases.
For now only decrypting the database works. Still missing: - Actually parsing the database. - Key files. Refs #1
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/format/KeePass1.h42
-rw-r--r--src/format/KeePass1Reader.cpp321
-rw-r--r--src/format/KeePass1Reader.h67
4 files changed, 432 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 680c8307b..9cde2bf8d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -43,6 +43,8 @@ set(keepassx_SOURCES
crypto/SymmetricCipherBackend.h
crypto/SymmetricCipherGcrypt.cpp
crypto/SymmetricCipherSalsa20.cpp
+ format/KeePass1.h
+ format/KeePass1Reader.cpp
format/KeePass2.h
format/KeePass2RandomStream.cpp
format/KeePass2Reader.cpp
diff --git a/src/format/KeePass1.h b/src/format/KeePass1.h
new file mode 100644
index 000000000..7e656c0cd
--- /dev/null
+++ b/src/format/KeePass1.h
@@ -0,0 +1,42 @@
+/*
+* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 or (at your option)
+* version 3 of the License.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef KEEPASSX_KEEPASS1_H
+#define KEEPASSX_KEEPASS1_H
+
+#include <QtCore/QtGlobal>
+
+#include "core/Uuid.h"
+
+namespace KeePass1
+{
+ const quint32 SIGNATURE_1 = 0x9AA2D903;
+ const quint32 SIGNATURE_2 = 0xB54BFB65;
+ const quint32 PWM_DBVER_DW = 0x00030002;
+ const quint32 FILE_VERSION = 0x00030002;
+ const quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFFFF00;
+
+ const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
+
+ enum EncryptionFlags
+ {
+ Rijndael = 2,
+ Twofish = 8
+ };
+}
+
+#endif // KEEPASSX_KEEPASS1_H
diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp
new file mode 100644
index 000000000..d85f16127
--- /dev/null
+++ b/src/format/KeePass1Reader.cpp
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "KeePass1Reader.h"
+
+#include <QtCore/QFile>
+#include <QtCore/QTextCodec>
+
+#include "core/Database.h"
+#include "core/Endian.h"
+#include "crypto/CryptoHash.h"
+#include "format/KeePass1.h"
+#include "keys/CompositeKey.h"
+#include "streams/SymmetricCipherStream.h"
+
+class KeePass1Key : public CompositeKey
+{
+public:
+ virtual QByteArray rawKey() const;
+ virtual void clear();
+ void setPassword(const QByteArray& password);
+ void setKeyfileData(const QByteArray& keyfileData);
+
+private:
+ QByteArray m_password;
+ QByteArray m_keyfileData;
+};
+
+
+KeePass1Reader::KeePass1Reader()
+ : m_error(false)
+{
+}
+
+Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& password,
+ const QByteArray& keyfileData)
+{
+ QScopedPointer<Database> db(new Database());
+ m_db = db.data();
+ m_device = device;
+ m_error = false;
+ m_errorStr = QString();
+
+ bool ok;
+
+ quint32 signature1 = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
+ if (!ok || signature1 != 0x9AA2D903) {
+ raiseError(tr("Not a KeePass database."));
+ return 0;
+ }
+
+ quint32 signature2 = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
+ if (!ok || signature2 != 0xB54BFB65) {
+ raiseError(tr("Not a KeePass database."));
+ return 0;
+ }
+
+ m_encryptionFlags = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
+ if (!ok || !(m_encryptionFlags & KeePass1::Rijndael || m_encryptionFlags & KeePass1::Twofish)) {
+ raiseError(tr("Unsupported encryption algorithm."));
+ return 0;
+ }
+
+ quint32 version = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
+ if (!ok || (version & KeePass1::FILE_VERSION_CRITICAL_MASK)
+ != (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) {
+ raiseError(tr("Unsupported KeePass database version."));
+ return 0;
+ }
+
+ m_masterSeed = m_device->read(16);
+ if (m_masterSeed.size() != 16) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ m_encryptionIV = m_device->read(16);
+ if (m_encryptionIV.size() != 16) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ quint32 numGroups = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
+ if (!ok) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ quint32 numEntries = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
+ if (!ok) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ m_contentHashHeader = m_device->read(32);
+ if (m_contentHashHeader.size() != 32) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ m_transformSeed = m_device->read(32);
+ if (m_transformSeed.size() != 32) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ m_transformRounds = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
+ if (!ok) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+ m_db->setTransformRounds(m_transformRounds);
+
+ qint64 contentPos = m_device->pos();
+
+ QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
+
+ if (!cipherStream) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ return db.take();
+}
+
+Database* KeePass1Reader::readDatabase(const QString& filename, const QString& password,
+ const QByteArray& keyfileData)
+{
+ QFile file(filename);
+ if (!file.open(QFile::ReadOnly)) {
+ raiseError(file.errorString());
+ return 0;
+ }
+
+ QScopedPointer<Database> db(readDatabase(&file, password, keyfileData));
+
+ if (file.error() != QFile::NoError) {
+ raiseError(file.errorString());
+ return 0;
+ }
+
+ return db.take();
+}
+
+bool KeePass1Reader::hasError()
+{
+ return m_error;
+}
+
+QString KeePass1Reader::errorString()
+{
+ return m_errorStr;
+}
+
+SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData,
+ qint64 contentPos)
+{
+ QList<PasswordEncoding> encodings;
+ encodings << Windows1252 << Latin1 << UTF8;
+
+ QScopedPointer<SymmetricCipherStream> cipherStream;
+ QByteArray passwordData;
+ QTextCodec* codec = QTextCodec::codecForName("Windows-1252");
+ QByteArray passwordDataCorrect = codec->fromUnicode(password);
+
+ Q_FOREACH (PasswordEncoding encoding, encodings) {
+ if (encoding == Windows1252) {
+ passwordData = passwordDataCorrect;
+ }
+ else if (encoding == Latin1) {
+ // KeePassX used Latin-1 encoding for passwords until version 0.3.1
+ // but KeePass/Win32 uses Windows Codepage 1252.
+ passwordData = password.toLatin1();
+
+ if (passwordData == passwordDataCorrect) {
+ continue;
+ }
+ else {
+ qWarning("Testing password encoded as Latin-1.");
+ }
+ }
+ else if (encoding == UTF8) {
+ // KeePassX used UTF-8 encoding for passwords until version 0.2.2
+ // but KeePass/Win32 uses Windows Codepage 1252.
+ passwordData = password.toUtf8();
+
+ if (passwordData == passwordDataCorrect) {
+ continue;
+ }
+ else {
+ qWarning("Testing password encoded as UTF-8.");
+ }
+ }
+
+ QByteArray finalKey = key(passwordData, keyfileData);
+ if (m_encryptionFlags & KeePass1::Rijndael) {
+ cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Aes256,
+ SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV));
+ }
+ else {
+ // TODO twofish
+ }
+
+ cipherStream->open(QIODevice::ReadOnly);
+
+ bool success = verifyKey(cipherStream.data());
+
+ cipherStream->reset();
+ cipherStream->close();
+ if (!m_device->seek(contentPos)) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+ cipherStream->open(QIODevice::ReadOnly);
+
+ if (success) {
+ break;
+ }
+ else {
+ cipherStream.reset();
+ }
+ }
+
+ return cipherStream.take();
+}
+
+QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& keyfileData)
+{
+ KeePass1Key key;
+ key.setPassword(password);
+ key.setKeyfileData(keyfileData);
+
+ CryptoHash hash(CryptoHash::Sha256);
+ hash.addData(m_masterSeed);
+ hash.addData(key.transform(m_transformSeed, m_transformRounds));
+ return hash.result();
+}
+
+bool KeePass1Reader::verifyKey(SymmetricCipherStream* cipherStream)
+{
+ CryptoHash contentHash(CryptoHash::Sha256);
+ QByteArray buffer;
+ buffer.resize(16384);
+ qint64 readResult;
+ do {
+ readResult = cipherStream->read(buffer.data(), buffer.size());
+ if (readResult > 0) {
+ if (readResult != buffer.size()) {
+ buffer.resize(readResult);
+ }
+ qDebug("read %d", buffer.size());
+ contentHash.addData(buffer);
+ }
+ } while (readResult == buffer.size());
+
+ return contentHash.result() == m_contentHashHeader;
+}
+
+void KeePass1Reader::raiseError(const QString& str)
+{
+ m_error = true;
+ m_errorStr = str;
+}
+
+
+QByteArray KeePass1Key::rawKey() const
+{
+ if (m_keyfileData.isEmpty()) {
+ return CryptoHash::hash(m_password, CryptoHash::Sha256);
+ }
+ else if (m_password.isEmpty()) {
+ return m_keyfileData;
+ }
+ else {
+ CryptoHash keyHash(CryptoHash::Sha256);
+ keyHash.addData(m_password);
+ keyHash.addData(m_keyfileData);
+ return keyHash.result();
+ }
+}
+
+void KeePass1Key::clear()
+{
+ CompositeKey::clear();
+
+ m_password.clear();
+ m_keyfileData.clear();
+}
+
+void KeePass1Key::setPassword(const QByteArray& password)
+{
+ m_password = password;
+}
+
+void KeePass1Key::setKeyfileData(const QByteArray& keyfileData)
+{
+ m_keyfileData = keyfileData;
+}
diff --git a/src/format/KeePass1Reader.h b/src/format/KeePass1Reader.h
new file mode 100644
index 000000000..e3d5eafe1
--- /dev/null
+++ b/src/format/KeePass1Reader.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEEPASSX_KEEPASS1READER_H
+#define KEEPASSX_KEEPASS1READER_H
+
+#include <QtCore/QCoreApplication>
+
+class Database;
+class SymmetricCipherStream;
+class QIODevice;
+
+class KeePass1Reader
+{
+ Q_DECLARE_TR_FUNCTIONS(KeePass1Reader)
+
+public:
+ KeePass1Reader();
+ Database* readDatabase(QIODevice* device, const QString& password,
+ const QByteArray& keyfileData);
+ Database* readDatabase(const QString& filename, const QString& password,
+ const QByteArray& keyfileData);
+ bool hasError();
+ QString errorString();
+
+private:
+ enum PasswordEncoding
+ {
+ Windows1252,
+ Latin1,
+ UTF8
+ };
+
+ SymmetricCipherStream* testKeys(const QString& password, const QByteArray& keyfileData,
+ qint64 contentPos);
+ QByteArray key(const QByteArray& password, const QByteArray& keyfileData);
+ bool verifyKey(SymmetricCipherStream* cipherStream);
+ void raiseError(const QString& str);
+
+ Database* m_db;
+ QIODevice* m_device;
+ quint32 m_encryptionFlags;
+ QByteArray m_masterSeed;
+ QByteArray m_encryptionIV;
+ QByteArray m_contentHashHeader;
+ QByteArray m_transformSeed;
+ quint32 m_transformRounds;
+
+ bool m_error;
+ QString m_errorStr;
+};
+
+#endif // KEEPASSX_KEEPASS1READER_H