Add KeePass2Writer.

Support attributes MasterKeyChanged, MasterKeyChangeRec, MasterKeyChangeForce and Tags.
Close streams in the dtor.
This commit is contained in:
Felix Geyer
2010-09-25 12:41:00 +02:00
parent a9ac4bbf41
commit e3da80fcc6
22 changed files with 397 additions and 39 deletions

View File

@@ -20,6 +20,8 @@
#include <QtCore/QtGlobal>
#include "core/Uuid.h"
namespace KeePass2
{
const quint32 SIGNATURE_1 = 0x9AA2D903;
@@ -27,6 +29,10 @@ namespace KeePass2
const quint32 FILE_VERSION = 0x00020000;
const quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000;
const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
const Uuid CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff"));
enum HeaderFieldID
{
EndOfHeader = 0,
@@ -41,13 +47,6 @@ namespace KeePass2
StreamStartBytes = 9,
InnerRandomStreamID = 10
};
enum CompressionAlgorithm
{
CompressionNone = 0,
CompressionGZip = 1,
CompressionCount = 2
};
}
#endif // KEEPASSX_KEEPASS2_H

View File

@@ -21,37 +21,37 @@
#include <QtCore/QFile>
#include <QtCore/QIODevice>
#include "KeePass2XmlReader.h"
#include "core/Database.h"
#include "crypto/CryptoHash.h"
#include "format/KeePass2.h"
#include "format/KeePass2XmlReader.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
const QSysInfo::Endian KeePass2Reader::BYTEORDER = QSysInfo::LittleEndian;
Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key)
{
m_db = new Database();
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);
quint32 signature1 = Endian::readUInt32(m_device, KeePass2::BYTEORDER, &ok);
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
raiseError("1");
return 0;
}
quint32 signature2 = Endian::readUInt32(m_device, BYTEORDER, &ok);
quint32 signature2 = Endian::readUInt32(m_device, KeePass2::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 version = Endian::readUInt32(m_device, KeePass2::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)) {
@@ -62,9 +62,12 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
while (readHeaderField() && !error()) {
}
QByteArray transformedMasterKey = key.transform(m_db->transformSeed(), m_db->transformRounds());
m_db->setTransformedMasterKey(transformedMasterKey);
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(key.transform(m_transformSeed, m_transformRounds));
hash.addData(transformedMasterKey);
QByteArray finalKey = hash.result();
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV);
@@ -83,7 +86,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
QIODevice* xmlDevice;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_compression == KeePass2::CompressionNone) {
if (m_db->compressionAlgo() == Database::CompressionNone) {
xmlDevice = &hashedStream;
}
else {
@@ -94,9 +97,9 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
}
KeePass2XmlReader xmlReader;
Database* db = xmlReader.readDatabase(xmlDevice);
xmlReader.readDatabase(xmlDevice, m_db);
// TODO forward error messages from xmlReader
return db;
return m_db;
}
Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key)
@@ -136,7 +139,7 @@ bool KeePass2Reader::readHeaderField()
quint8 fieldID = fieldIDArray.at(0);
bool ok;
quint16 fieldLen = Endian::readUInt16(m_device, BYTEORDER, &ok);
quint16 fieldLen = Endian::readUInt16(m_device, KeePass2::BYTEORDER, &ok);
if (!ok) {
raiseError("");
return false;
@@ -206,7 +209,14 @@ void KeePass2Reader::setCipher(const QByteArray& data)
raiseError("");
}
else {
m_cipher = Uuid(data);
Uuid uuid(data);
if (uuid != KeePass2::CIPHER_AES) {
raiseError("");
}
else {
m_db->setCipher(uuid);
}
}
}
@@ -216,13 +226,13 @@ void KeePass2Reader::setCompressionFlags(const QByteArray& data)
raiseError("");
}
else {
quint32 id = Endian::bytesToUInt32(data, BYTEORDER);
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
if (id >= KeePass2::CompressionCount) {
if (id > Database::CompressionAlgorithmMax) {
raiseError("");
}
else {
m_compression = static_cast<KeePass2::CompressionAlgorithm>(id);
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
}
}
}
@@ -243,7 +253,7 @@ void KeePass2Reader::setTransformSeed(const QByteArray& data)
raiseError("");
}
else {
m_transformSeed = data;
m_db->setTransformSeed(data);
}
}
@@ -253,7 +263,7 @@ void KeePass2Reader::setTansformRounds(const QByteArray& data)
raiseError("");
}
else {
m_transformRounds = Endian::bytesToUInt64(data, BYTEORDER);
m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER));
}
}

View File

@@ -23,7 +23,6 @@
#include "core/Endian.h"
#include "core/Uuid.h"
#include "keys/CompositeKey.h"
#include "format/KeePass2.h"
class Database;
@@ -52,18 +51,13 @@ private:
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;
KeePass2::CompressionAlgorithm m_compression;
Database* m_db;
QByteArray m_masterSeed;
QByteArray m_transformSeed;
quint64 m_transformRounds;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
};

View File

@@ -0,0 +1,132 @@
/*
* 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 "KeePass2Writer.h"
#include <QtCore/QFile>
#include "core/Database.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
#include "format/KeePass2XmlWriter.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
#define CHECK_RETURN(x) if (!(x)) return;
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
KeePass2Writer::KeePass2Writer()
: m_error(false)
{
}
void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
{
m_error = false;
m_errorStr = QString();
m_device = device;
QByteArray masterSeed = Random::randomArray(32);
QByteArray encryptionIV = Random::randomArray(16);
QByteArray startBytes = Random::randomArray(32);
QByteArray endOfHeader = "\r\n\r\n";
CryptoHash hash(CryptoHash::Sha256);
hash.addData(masterSeed);
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER)));
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER)));
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER)));
CHECK_RETURN(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray()));
CHECK_RETURN(writeHeaderField(KeePass2::CompressionFlags, Endian::int32ToBytes(db->compressionAlgo(), KeePass2::BYTEORDER)));
CHECK_RETURN(writeHeaderField(KeePass2::MasterSeed, masterSeed));
CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, db->transformSeed()));
CHECK_RETURN(writeHeaderField(KeePass2::TransformRounds, Endian::int64ToBytes(db->transformRounds(), KeePass2::BYTEORDER)));
CHECK_RETURN(writeHeaderField(KeePass2::EncryptionIV, encryptionIV));
CHECK_RETURN(writeHeaderField(KeePass2::StreamStartBytes, startBytes));
CHECK_RETURN(writeHeaderField(KeePass2::EndOfHeader, endOfHeader));
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, finalKey, encryptionIV);
cipherStream.open(QIODevice::WriteOnly);
m_device = &cipherStream;
CHECK_RETURN(writeData(startBytes));
HashedBlockStream hashedStream(&cipherStream);
hashedStream.open(QIODevice::WriteOnly);
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
m_device = &hashedStream;
}
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
ioCompressor->open(QIODevice::WriteOnly);
m_device = ioCompressor.data();
}
KeePass2XmlWriter xmlWriter;
xmlWriter.writeDatabase(m_device, db);
}
bool KeePass2Writer::writeData(const QByteArray& data)
{
if (m_device->write(data) != data.size()) {
m_error = true;
m_errorStr = m_device->errorString();
return false;
}
else {
return true;
}
}
bool KeePass2Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data)
{
QByteArray fieldIdArr;
fieldIdArr[0] = fieldId;
CHECK_RETURN_FALSE(writeData(fieldIdArr));
CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(data.size(), KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(data));
return true;
}
void KeePass2Writer::writeDatabase(const QString& filename, Database* db)
{
QFile file(filename);
file.open(QIODevice::WriteOnly|QIODevice::Truncate);
writeDatabase(&file, db);
}
bool KeePass2Writer::error()
{
return m_error;
}
QString KeePass2Writer::errorString()
{
return m_errorStr;
}

View File

@@ -0,0 +1,46 @@
/*
* 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_KEEPASS2WRITER_H
#define KEEPASSX_KEEPASS2WRITER_H
#include <QtCore/QIODevice>
#include "format/KeePass2.h"
#include "keys/CompositeKey.h"
class Database;
class KeePass2Writer
{
public:
KeePass2Writer();
void writeDatabase(QIODevice* device, Database* db);
void writeDatabase(const QString& filename, Database* db);
bool error();
QString errorString();
private:
bool writeData(const QByteArray& data);
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
QIODevice* m_device;
bool m_error;
QString m_errorStr;
};
#endif // KEEPASSX_KEEPASS2WRITER_H

View File

@@ -29,11 +29,11 @@ KeePass2XmlReader::KeePass2XmlReader()
{
}
Database* KeePass2XmlReader::readDatabase(QIODevice* device)
void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db)
{
m_xml.setDevice(device);
m_db = new Database();
m_db = db;
m_meta = m_db->metadata();
m_tmpParent = new Group();
@@ -50,8 +50,13 @@ Database* KeePass2XmlReader::readDatabase(QIODevice* device)
}
delete m_tmpParent;
}
return m_db;
Database* KeePass2XmlReader::readDatabase(QIODevice* device)
{
Database* db = new Database();
readDatabase(device, db);
return db;
}
Database* KeePass2XmlReader::readDatabase(const QString& filename)
@@ -120,6 +125,15 @@ void KeePass2XmlReader::parseMeta()
else if (m_xml.name() == "MaintenanceHistoryDays") {
m_meta->setMaintenanceHistoryDays(readNumber());
}
else if (m_xml.name() == "MasterKeyChanged") {
m_meta->setMasterKeyChanged(readDateTime());
}
else if (m_xml.name() == "MasterKeyChangeRec") {
m_meta->setMasterKeyChangeRec(readNumber());
}
else if (m_xml.name() == "MasterKeyChangeForce") {
m_meta->setMasterKeyChangeForce(readNumber());
}
else if (m_xml.name() == "MemoryProtection") {
parseMemoryProtection();
}
@@ -461,6 +475,9 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
else if (m_xml.name() == "OverrideURL") {
entry->setOverrideUrl(readString());
}
else if (m_xml.name() == "Tags") {
entry->setTags(readString());
}
else if (m_xml.name() == "Times") {
entry->setTimeInfo(parseTimes());
}

View File

@@ -38,6 +38,7 @@ class KeePass2XmlReader
public:
KeePass2XmlReader();
Database* readDatabase(QIODevice* device);
void readDatabase(QIODevice* device, Database* db);
Database* readDatabase(const QString& filename);
bool error();
QString errorString();

View File

@@ -53,7 +53,7 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db)
void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db)
{
QFile file(filename);
file.open(QIODevice::WriteOnly);
file.open(QIODevice::WriteOnly|QIODevice::Truncate);
writeDatabase(&file, db);
}
@@ -69,6 +69,9 @@ void KeePass2XmlWriter::writeMetadata()
writeString("DefaultUserName", m_meta->defaultUserName());
writeDateTime("DefaultUserNameChanged", m_meta->defaultUserNameChanged());
writeNumber("MaintenanceHistoryDays", m_meta->maintenanceHistoryDays());
writeDateTime("MasterKeyChanged", m_meta->masterKeyChanged());
writeNumber("MasterKeyChangeRec", m_meta->masterKeyChangeRec());
writeNumber("MasterKeyChangeForce", m_meta->masterKeyChangeForce());
writeMemoryProtection();
writeCustomIcons();
writeBool("RecycleBinEnabled", m_meta->recycleBinEnabled());
@@ -263,16 +266,17 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
writeColor("ForegroundColor", entry->foregroundColor());
writeColor("BackgroundColor", entry->backgroundColor());
writeString("OverrideURL", entry->overrideUrl());
writeString("Tags", entry->tags());
writeTimes(entry->timeInfo());
Q_FOREACH (const QString& key, entry->attributes()) {
Q_FOREACH (const QString& key, entry->attributes().keys()) {
m_xml.writeStartElement("String");
writeString("Key", key);
writeString("Value", entry->attributes().value(key));
m_xml.writeEndElement();
}
Q_FOREACH (const QString& key, entry->attachments()) {
Q_FOREACH (const QString& key, entry->attachments().keys()) {
m_xml.writeStartElement("Binary");
writeString("Key", key);
writeBinary("Value", entry->attachments().value(key));