diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 54c426658..4cc2e99b3 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -24,6 +24,7 @@ #include "core/Database.h" #include "crypto/CryptoHash.h" #include "format/KeePass2.h" +#include "format/KeePass2RandomStream.h" #include "format/KeePass2XmlReader.h" #include "streams/HashedBlockStream.h" #include "streams/QtIOCompressor" @@ -102,10 +103,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke xmlDevice = ioCompressor.data(); } - QByteArray protectedStreamKey = CryptoHash::hash(m_protectedStreamKey, CryptoHash::Sha256); - - SymmetricCipher protectedStream(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Decrypt, - protectedStreamKey, KeePass2::INNER_STREAM_SALSA20_IV); + KeePass2RandomStream randomStream(m_protectedStreamKey); QScopedPointer buffer; @@ -117,7 +115,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke } KeePass2XmlReader xmlReader; - xmlReader.readDatabase(xmlDevice, m_db, &protectedStream); + xmlReader.readDatabase(xmlDevice, m_db, &randomStream); // TODO forward error messages from xmlReader return m_db; } diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index a44b7366f..35a3eecc2 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -23,6 +23,7 @@ #include "core/Endian.h" #include "crypto/CryptoHash.h" #include "crypto/Random.h" +#include "format/KeePass2RandomStream.h" #include "format/KeePass2XmlWriter.h" #include "streams/HashedBlockStream.h" #include "streams/QtIOCompressor" @@ -91,11 +92,10 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) m_device = ioCompressor.data(); } - SymmetricCipher protectedStream(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt, - CryptoHash::hash(protectedStreamKey, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV); + KeePass2RandomStream randomStream(protectedStreamKey); KeePass2XmlWriter xmlWriter; - xmlWriter.writeDatabase(m_device, db, &protectedStream); + xmlWriter.writeDatabase(m_device, db, &randomStream); } bool KeePass2Writer::writeData(const QByteArray& data) diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index a38f2f1a2..2c8989f0e 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -22,21 +22,22 @@ #include "core/Database.h" #include "core/DatabaseIcons.h" #include "core/Metadata.h" +#include "format/KeePass2RandomStream.h" KeePass2XmlReader::KeePass2XmlReader() - : m_cipher(0) + : m_randomStream(0) , m_db(0) , m_meta(0) { } -void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher) +void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) { m_xml.setDevice(device); m_db = db; m_meta = m_db->metadata(); - m_cipher = cipher; + m_randomStream = randomStream; m_tmpParent = new Group(); m_tmpParent->setParent(m_db); @@ -524,8 +525,8 @@ void KeePass2XmlReader::parseEntryString(Entry *entry) bool isProtected = attr.hasAttribute("Protected") && (attr.value("Protected") == "True"); if (isProtected && !value.isEmpty()) { - if (m_cipher) { - value = m_cipher->process(QByteArray::fromBase64(value.toAscii())); + if (m_randomStream) { + value = m_randomStream->process(QByteArray::fromBase64(value.toAscii())); } else { raiseError(); @@ -556,7 +557,7 @@ void KeePass2XmlReader::parseEntryBinary(Entry *entry) bool isProtected = attr.hasAttribute("Protected") && (attr.value("Protected") == "True"); if (isProtected && !value.isEmpty()) { - m_cipher->processInPlace(value); + m_randomStream->processInPlace(value); } entry->addAttachment(key, value, isProtected); diff --git a/src/format/KeePass2XmlReader.h b/src/format/KeePass2XmlReader.h index 575877c37..cd1c6f042 100644 --- a/src/format/KeePass2XmlReader.h +++ b/src/format/KeePass2XmlReader.h @@ -30,8 +30,8 @@ class Database; class Entry; class Group; +class KeePass2RandomStream; class Metadata; -class SymmetricCipher; class KeePass2XmlReader { @@ -40,7 +40,7 @@ class KeePass2XmlReader public: KeePass2XmlReader(); Database* readDatabase(QIODevice* device); - void readDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher = 0); + void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = 0); Database* readDatabase(const QString& filename); bool error(); QString errorString(); @@ -79,7 +79,7 @@ private: void skipCurrentElement(); QXmlStreamReader m_xml; - SymmetricCipher* m_cipher; + KeePass2RandomStream* m_randomStream; Database* m_db; Metadata* m_meta; Group* m_tmpParent; diff --git a/src/format/KeePass2XmlWriter.cpp b/src/format/KeePass2XmlWriter.cpp index d4f8ba445..8244c32fb 100644 --- a/src/format/KeePass2XmlWriter.cpp +++ b/src/format/KeePass2XmlWriter.cpp @@ -21,23 +21,23 @@ #include #include "core/Metadata.h" -#include "crypto/SymmetricCipher.h" +#include "format/KeePass2RandomStream.h" KeePass2XmlWriter::KeePass2XmlWriter() : m_db(0) , m_meta(0) - , m_cipher(0) + , m_randomStream(0) { m_xml.setAutoFormatting(true); m_xml.setAutoFormattingIndent(-1); // 1 tab m_xml.setCodec("UTF-8"); } -void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher) +void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) { m_db = db; m_meta = db->metadata(); - m_cipher = cipher; + m_randomStream = randomStream; m_xml.setDevice(device); @@ -53,11 +53,11 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, Symmetric m_xml.writeEndDocument(); } -void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db, SymmetricCipher* cipher) +void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db, KeePass2RandomStream* randomStream) { QFile file(filename); file.open(QIODevice::WriteOnly|QIODevice::Truncate); - writeDatabase(&file, db, cipher); + writeDatabase(&file, db, randomStream); } void KeePass2XmlWriter::writeMetadata() @@ -281,43 +281,50 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry) ((key == "URL") && m_meta->protectUrl()) || ((key == "Notes") && m_meta->protectNotes()) || entry->isAttributeProtected(key) ) && - m_cipher; + m_randomStream; + + writeString("Key", key); + + m_xml.writeStartElement("Value"); + QString value; - m_xml.writeStartElement("Key"); if (protect) { m_xml.writeAttribute("Protected", "True"); - } - m_xml.writeCharacters(key); - m_xml.writeEndElement(); - - if (protect) { - writeBinary("Value", m_cipher->process(entry->attributes().value(key).toUtf8())); + QByteArray rawData = m_randomStream->process(entry->attributes().value(key).toUtf8()); + value = QString::fromAscii(rawData.toBase64()); } else { - writeString("Value", entry->attributes().value(key)); + value = entry->attributes().value(key); } + m_xml.writeCharacters(value); + m_xml.writeEndElement(); + m_xml.writeEndElement(); } Q_FOREACH (const QString& key, entry->attachments().keys()) { m_xml.writeStartElement("Binary"); - bool protect = entry->isAttachmentProtected(key) && m_cipher; - m_xml.writeStartElement("Key"); - if (protect) { - m_xml.writeAttribute("Protected", "True"); - } - m_xml.writeCharacters(key); - m_xml.writeEndElement(); + bool protect = entry->isAttachmentProtected(key) && m_randomStream; + + writeString("Key", key); + + m_xml.writeStartElement("Value"); + QString value; if (protect) { - writeBinary("Value", m_cipher->process(entry->attachments().value(key))); + m_xml.writeAttribute("Protected", "True"); + QByteArray rawData = m_randomStream->process(entry->attachments().value(key)); + value = QString::fromAscii(rawData.toBase64()); } else { - writeBinary("Value", entry->attachments().value(key)); + value = entry->attachments().value(key); } + m_xml.writeCharacters(value); + m_xml.writeEndElement(); + m_xml.writeEndElement(); } diff --git a/src/format/KeePass2XmlWriter.h b/src/format/KeePass2XmlWriter.h index afe9876de..4b8a39e7c 100644 --- a/src/format/KeePass2XmlWriter.h +++ b/src/format/KeePass2XmlWriter.h @@ -29,15 +29,15 @@ #include "core/Uuid.h" class Group; +class KeePass2RandomStream; class Metadata; -class SymmetricCipher; class KeePass2XmlWriter { public: KeePass2XmlWriter(); - void writeDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher = 0); - void writeDatabase(const QString& filename, Database* db, SymmetricCipher* cipher = 0); + void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = 0); + void writeDatabase(const QString& filename, Database* db, KeePass2RandomStream* randomStream = 0); bool error(); QString errorString(); @@ -72,7 +72,7 @@ private: QXmlStreamWriter m_xml; Database* m_db; Metadata* m_meta; - SymmetricCipher* m_cipher; + KeePass2RandomStream* m_randomStream; }; #endif // KEEPASSX_KEEPASS2XMLWRITER_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2423e4b81..accd015c5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -105,6 +105,8 @@ add_unit_test(NAME testkeepass2xmlreader SOURCES TestKeePass2XmlReader.cpp MOCS add_unit_test(NAME testkeepass2reader SOURCES TestKeePass2Reader.cpp MOCS TestKeePass2Reader.h LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testkeepass2writer SOURCES TestKeePass2Writer.cpp MOCS TestKeePass2Writer.h LIBS ${TEST_LIBRARIES}) + add_unit_test(NAME testgroupmodel SOURCES TestGroupModel.cpp MOCS TestGroupModel.h LIBS ${TEST_LIBRARIES} modeltest) add_unit_test(NAME testentrymodel SOURCES TestEntryModel.cpp MOCS TestEntryModel.h LIBS ${TEST_LIBRARIES} modeltest) diff --git a/tests/ProtectedStrings.kdbx b/tests/ProtectedStrings.kdbx new file mode 100644 index 000000000..9b903f7ae Binary files /dev/null and b/tests/ProtectedStrings.kdbx differ diff --git a/tests/TestKeePass2Reader.cpp b/tests/TestKeePass2Reader.cpp index d94dce852..a35a84d22 100644 --- a/tests/TestKeePass2Reader.cpp +++ b/tests/TestKeePass2Reader.cpp @@ -61,4 +61,33 @@ void TestKeePass2Reader::testCompressed() delete reader; } +void TestKeePass2Reader::testProtectedStrings() +{ + QString filename = QString(KEEPASSX_TEST_DIR).append("/ProtectedStrings.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("masterpw")); + KeePass2Reader* reader = new KeePass2Reader(); + Database* db = reader->readDatabase(filename, key); + QVERIFY(db); + QVERIFY(!reader->error()); + QCOMPARE(db->metadata()->name(), QString("Protected Strings Test")); + + Entry* entry = db->rootGroup()->entries().at(0); + + QCOMPARE(entry->title(), QString("Sample Entry")); + QCOMPARE(entry->username(), QString("Protected User Name")); + QCOMPARE(entry->password(), QString("ProtectedPassword")); + QCOMPARE(entry->attributes().value("TestProtected"), QString("ABC")); + QCOMPARE(entry->attributes().value("TestUnprotected"), QString("DEF")); + + QVERIFY(!db->metadata()->protectTitle()); + QVERIFY(db->metadata()->protectUsername()); + QVERIFY(db->metadata()->protectPassword()); + QVERIFY(entry->isAttributeProtected("TestProtected")); + QVERIFY(!entry->isAttributeProtected("TestUnprotected")); + + delete db; + delete reader; +} + QTEST_MAIN(TestKeePass2Reader); diff --git a/tests/TestKeePass2Reader.h b/tests/TestKeePass2Reader.h index cccaf9171..b2a75f66e 100644 --- a/tests/TestKeePass2Reader.h +++ b/tests/TestKeePass2Reader.h @@ -28,6 +28,7 @@ private Q_SLOTS: void initTestCase(); void testNonAscii(); void testCompressed(); + void testProtectedStrings(); }; #endif // KEEPASSX_TESTKEEPASS2READER_H diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp new file mode 100644 index 000000000..72a351831 --- /dev/null +++ b/tests/TestKeePass2Writer.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * 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 . + */ + +#include "TestKeePass2Writer.h" + +#include +#include + +#include "core/Database.h" +#include "core/Metadata.h" +#include "crypto/Crypto.h" +#include "format/KeePass2Reader.h" +#include "format/KeePass2Writer.h" +#include "keys/PasswordKey.h" + +#include "format/KeePass2XmlWriter.h" +void TestKeePass2Writer::initTestCase() +{ + Crypto::init(); + + CompositeKey key; + key.addKey(PasswordKey("test")); + + m_dbOrg = new Database(); + m_dbOrg->setKey(key); + m_dbOrg->metadata()->setName("TESTDB"); + Group* group = new Group(); + group->setUuid(Uuid::random()); + group->setParent(m_dbOrg); + m_dbOrg->setRootGroup(group); + Entry* entry = new Entry(); + entry->setUuid(Uuid::random()); + entry->addAttribute("test", "protectedTest", true); + QVERIFY(entry->isAttributeProtected("test")); + group->addEntry(entry); + + QBuffer buffer; + buffer.open(QBuffer::ReadWrite); + + KeePass2Writer writer; + writer.writeDatabase(&buffer, m_dbOrg); + QVERIFY(!writer.error()); + buffer.seek(0); + KeePass2Reader reader; + m_dbTest = reader.readDatabase(&buffer, key); + QVERIFY(!reader.error()); + QVERIFY(m_dbTest); +} + +void TestKeePass2Writer::testBasic() +{ + QCOMPARE(m_dbTest->metadata()->name(), m_dbOrg->metadata()->name()); + QVERIFY(m_dbTest->rootGroup()); +} + +void TestKeePass2Writer::testProtectedAttributes() +{ + QCOMPARE(m_dbTest->rootGroup()->entries().size(), 1); + Entry* entry = m_dbTest->rootGroup()->entries().at(0); + QCOMPARE(entry->attributes().value("test"), QString("protectedTest")); + QCOMPARE(entry->isAttributeProtected("test"), true); +} + +QTEST_MAIN(TestKeePass2Writer); diff --git a/tests/TestKeePass2Writer.h b/tests/TestKeePass2Writer.h new file mode 100644 index 000000000..e0e333a89 --- /dev/null +++ b/tests/TestKeePass2Writer.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * 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 . + */ + +#ifndef KEEPASSX_TESTKEEPASS2WRITER_H +#define KEEPASSX_TESTKEEPASS2WRITER_H + +#include + +class Database; + +class TestKeePass2Writer : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testBasic(); + void testProtectedAttributes(); + +private: + Database* m_dbOrg; + Database* m_dbTest; +}; + +#endif // KEEPASSX_TESTKEEPASS2WRITER_H