Implement reading encrypted kdbx files.

This commit is contained in:
Felix Geyer 2010-09-13 23:24:36 +02:00
parent bb6ae3a014
commit 9f282928e8
22 changed files with 1290 additions and 71 deletions

View file

@ -1,34 +0,0 @@
/*
* Copyright (C) 2010 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_DATABASEREADER_H
#define KEEPASSX_DATABASEREADER_H
#include <QtCore/QString>
class Database;
class QIODevice;
class DatabaseReader
{
public:
virtual Database* readDatabase(QIODevice* device) = 0;
virtual bool error() = 0;
virtual QString errorString() = 0;
};
#endif // KEEPASSX_DATABASEREADER_H

View file

@ -1,32 +0,0 @@
/*
* Copyright (C) 2010 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_DATABASEWRITER_H
#define KEEPASSX_DATABASEWRITER_H
#include <QtCore/QString>
class Database;
class QIODevice;
class DatabaseWriter
{
public:
virtual void writeDatabase(QIODevice* device, Database* db) = 0;
};
#endif // KEEPASSX_DATABASEWRITER_H

53
src/format/KeePass2.h Normal file
View file

@ -0,0 +1,53 @@
/*
* Copyright (C) 2010 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_KEEPASS2_H
#define KEEPASSX_KEEPASS2_H
#include <QtCore/QtGlobal>
namespace KeePass2
{
const quint32 SIGNATURE_1 = 0x9AA2D903;
const quint32 SIGNATURE_2 = 0xB54BFB67;
const quint32 FILE_VERSION = 0x00020000;
const quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000;
enum HeaderFieldID
{
EndOfHeader = 0,
Comment = 1,
CipherID = 2,
CompressionFlags = 3,
MasterSeed = 4,
TransformSeed = 5,
TransformRounds = 6,
EncryptionIV = 7,
ProtectedStreamKey = 8,
StreamStartBytes = 9,
InnerRandomStreamID = 10
};
enum CompressionAlgorithm
{
CompressionNone = 0,
CompressionGZip = 1,
CompressionCount = 2
};
}
#endif // KEEPASSX_KEEPASS2_H

View file

@ -0,0 +1,273 @@
/*
* Copyright (C) 2010 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 "KeePass2Reader.h"
#include <QtCore/QBuffer>
#include <QtCore/QFile>
#include <QtCore/QIODevice>
#include "KeePass2.h"
#include "KeePass2XmlReader.h"
#include "crypto/CryptoHash.h"
#include "streams/HashedBlockStream.h"
#include "streams/SymmetricCipherStream.h"
const QSysInfo::Endian KeePass2Reader::BYTEORDER = QSysInfo::LittleEndian;
Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key)
{
m_device = device;
m_error = false;
m_errorStr = QString();
m_headerEnd = false;
m_cipher = Uuid();
bool ok;
quint32 signature1 = Endian::readUInt32(m_device, BYTEORDER, &ok);
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
raiseError("1");
return 0;
}
quint32 signature2 = Endian::readUInt32(m_device, BYTEORDER, &ok);
if (!ok || signature2 != KeePass2::SIGNATURE_2) {
raiseError("2");
return 0;
}
quint32 version = Endian::readUInt32(m_device, BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK;
quint32 expectedVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
// TODO do we support old Kdbx versions?
if (!ok || (version != expectedVersion)) {
raiseError("3");
return 0;
}
while (readHeaderField() && !error()) {
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(key.transform(m_transformSeed, m_transformRounds));
QByteArray finalKey = hash.result();
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV);
cipherStream.open(QIODevice::ReadOnly);
QByteArray realStart = cipherStream.read(32);
if (realStart != m_streamStartBytes) {
raiseError("4");
}
HashedBlockStream hashedStream(&cipherStream);
hashedStream.open(QIODevice::ReadOnly);
KeePass2XmlReader xmlReader;
Database* db = xmlReader.readDatabase(&hashedStream);
return db;
}
Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key)
{
QFile file(filename);
file.open(QFile::ReadOnly);
Database* db = readDatabase(&file, key);
// TODO check for QFile errors
return db;
}
bool KeePass2Reader::error()
{
return m_error;
}
QString KeePass2Reader::errorString()
{
// TODO
return QString();
}
void KeePass2Reader::raiseError(const QString& str)
{
// TODO
qWarning("KeePass2Reader error: %s", qPrintable(str));
m_error = true;
}
bool KeePass2Reader::readHeaderField()
{
QByteArray fieldIDArray = m_device->read(1);
if (fieldIDArray.size() != 1) {
raiseError("");
return false;
}
quint8 fieldID = fieldIDArray.at(0);
bool ok;
quint16 fieldLen = Endian::readUInt16(m_device, BYTEORDER, &ok);
if (!ok) {
raiseError("");
return false;
}
QByteArray fieldData;
if (fieldLen != 0) {
fieldData = m_device->read(fieldLen);
if (fieldData.size() != fieldLen) {
raiseError("");
return false;
}
}
switch (fieldID) {
case KeePass2::EndOfHeader:
m_headerEnd = true;
break;
case KeePass2::CipherID:
setCipher(fieldData);
break;
case KeePass2::CompressionFlags:
setCompressionFlags(fieldData);
break;
case KeePass2::MasterSeed:
setMasterSeed(fieldData);
break;
case KeePass2::TransformSeed:
setTransformSeed(fieldData);
break;
case KeePass2::TransformRounds:
setTansformRounds(fieldData);
break;
case KeePass2::EncryptionIV:
setEncryptionIV(fieldData);
break;
case KeePass2::ProtectedStreamKey:
setProtectedStreamKey(fieldData);
break;
case KeePass2::StreamStartBytes:
setStreamStartBytes(fieldData);
break;
case KeePass2::InnerRandomStreamID:
setInnerRandomStreamID(fieldData);
break;
default:
qWarning("Unknown header field read: id=%d", fieldID);
break;
}
return !m_headerEnd;
}
void KeePass2Reader::setCipher(const QByteArray& data)
{
if (data.size() != Uuid::LENGTH) {
raiseError("");
}
else {
m_cipher = Uuid(data);
}
}
void KeePass2Reader::setCompressionFlags(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("");
}
else {
quint32 id = Endian::bytesToUInt32(data, BYTEORDER);
if (id >= KeePass2::CompressionCount) {
raiseError("");
}
else {
m_compression = id;
}
}
}
void KeePass2Reader::setMasterSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("");
}
else {
m_masterSeed = data;
}
}
void KeePass2Reader::setTransformSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("");
}
else {
m_transformSeed = data;
}
}
void KeePass2Reader::setTansformRounds(const QByteArray& data)
{
if (data.size() != 8) {
raiseError("");
}
else {
m_transformRounds = Endian::bytesToUInt64(data, BYTEORDER);
}
}
void KeePass2Reader::setEncryptionIV(const QByteArray& data)
{
if (data.size() != 16) {
raiseError("");
}
else {
m_encryptionIV = data;
}
}
void KeePass2Reader::setProtectedStreamKey(const QByteArray& data)
{
// TODO ignore?
}
void KeePass2Reader::setStreamStartBytes(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("");
}
else {
m_streamStartBytes = data;
}
}
void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data)
{
// TODO ignore?
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2010 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_KEEPASS2READER_H
#define KEEPASSX_KEEPASS2READER_H
#include <QtCore/QCoreApplication>
#include "core/Endian.h"
#include "core/Uuid.h"
#include "keys/CompositeKey.h"
class Database;
class KeePass2Reader
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Reader);
public:
Database* readDatabase(QIODevice* device, const CompositeKey& key);
Database* readDatabase(const QString& filename, const CompositeKey& key);
bool error();
QString errorString();
private:
void raiseError(const QString& str);
bool readHeaderField();
void setCipher(const QByteArray& data);
void setCompressionFlags(const QByteArray& data);
void setMasterSeed(const QByteArray& data);
void setTransformSeed(const QByteArray& data);
void setTansformRounds(const QByteArray& data);
void setEncryptionIV(const QByteArray& data);
void setProtectedStreamKey(const QByteArray& data);
void setStreamStartBytes(const QByteArray& data);
void setInnerRandomStreamID(const QByteArray& data);
static const QSysInfo::Endian BYTEORDER;
QIODevice* m_device;
bool m_error;
QString m_errorStr;
bool m_headerEnd;
Uuid m_cipher;
int m_compression;
QByteArray m_masterSeed;
QByteArray m_transformSeed;
quint64 m_transformRounds;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
};
#endif // KEEPASSX_KEEPASS2READER_H

View file

@ -57,7 +57,7 @@ Database* KeePass2XmlReader::readDatabase(QIODevice* device)
Database* KeePass2XmlReader::readDatabase(const QString& filename)
{
QFile file(filename);
file.open(QIODevice::ReadOnly | QIODevice::Text);
file.open(QIODevice::ReadOnly);
return readDatabase(&file);
}

View file

@ -23,7 +23,6 @@
#include <QtCore/QXmlStreamReader>
#include <QtGui/QColor>
#include "DatabaseReader.h"
#include "core/TimeInfo.h"
#include "core/Uuid.h"
@ -32,7 +31,7 @@ class Entry;
class Group;
class Metadata;
class KeePass2XmlReader : public DatabaseReader
class KeePass2XmlReader
{
Q_DECLARE_TR_FUNCTIONS(KeePass2XmlReader);

View file

@ -23,7 +23,6 @@
#include <QtGui/QColor>
#include <QtGui/QImage>
#include "DatabaseWriter.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/TimeInfo.h"
@ -32,7 +31,7 @@
class Group;
class Metadata;
class KeePass2XmlWriter : public DatabaseWriter
class KeePass2XmlWriter
{
public:
KeePass2XmlWriter();