From a299dd9715443efd2bfb35f3ba155b2e06f4a0a7 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Thu, 7 Jul 2011 00:15:52 +0200 Subject: [PATCH] Integrate KeePass2RandomStream into KeePass2 reader and writer classes. This hopefully completes support for reading/writing kdbx <= 2.14 files. Also fix a bug in KeePass2XmlWriter so it sets Protected="True" in the Value tag instead of Key. --- src/format/KeePass2Reader.cpp | 8 ++-- src/format/KeePass2Writer.cpp | 6 +-- src/format/KeePass2XmlReader.cpp | 13 +++--- src/format/KeePass2XmlReader.h | 6 +-- src/format/KeePass2XmlWriter.cpp | 55 ++++++++++++---------- src/format/KeePass2XmlWriter.h | 8 ++-- tests/CMakeLists.txt | 2 + tests/ProtectedStrings.kdbx | Bin 0 -> 1950 bytes tests/TestKeePass2Reader.cpp | 29 ++++++++++++ tests/TestKeePass2Reader.h | 1 + tests/TestKeePass2Writer.cpp | 78 +++++++++++++++++++++++++++++++ tests/TestKeePass2Writer.h | 39 ++++++++++++++++ 12 files changed, 200 insertions(+), 45 deletions(-) create mode 100644 tests/ProtectedStrings.kdbx create mode 100644 tests/TestKeePass2Writer.cpp create mode 100644 tests/TestKeePass2Writer.h diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 54c42665..4cc2e99b 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 a44b7366..35a3eecc 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 a38f2f1a..2c8989f0 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 575877c3..cd1c6f04 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 d4f8ba44..8244c32f 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 afe9876d..4b8a39e7 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 2423e4b8..accd015c 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 0000000000000000000000000000000000000000..9b903f7aeb2852a6111451768dd7ebb04f0a335e GIT binary patch literal 1950 zcmV;P2VwXF*`k_f`%AS100IC45CAd3^5(yBLr}h01tDtuTK@wC0096100bZaU7}Q# zmrA@5cx-ZmbaR-bv%@ts>0urRWQo7V%pgb61t0(e{6mh#j+`+6lJd!k+P{p-bT8{m z#^QAJRc435ZYv`O2mo*w00000000LN08?FE%qGBMrBsxO{97?(57APIW9xqKk2e8$*od>RxLHdRY1ONg60000401XNa3avViaaKV$eqmt{a45*#SpAi&OmSzzf7n3}yhvhD zKD@tx8u3>QrWc+Okn)8fg;ILOY|__f9mS~m$w7!N82+uz3)Y(Yy2u4Yp$Om_DZUZ=ZLZAdJmNnEsr zKGVu|t(&4RvpQypJ7Gp5tb^DFJ_txa4(sj|yp-N`Rug6^Crg+(^Yw7W@^LO$ieZ7P zW+M)TuLO3YBs=p3v({=O4N))kQEY8M&v=w$fF9mK%i5k)N|b4#Az3@h+l^x^Mg zjQM(3tOzf*=J1THfb${-7|rEXY!T|rB0gj-oYXLbD*0dfJj2rMsJQuk{+V}2a2JPr zDa;)hcO^As9)>02|DR`1?D^^2FK(7ADVPVd)qNZW`}wuT{J}lnkE_*Pd?va#6DJW$ zM84n|aouEIhI#ei{2oG0NFeIiVx&*#U{*0{0=%&8n|4krsDDnny^2!M<$Ha)0{bpn z1R(7oLscltK>oOE8h+`|&wr3i$Zd-(lK#z-D??W-yCeWikwhHkr-tvF0qW4K{m~%< zdvnZy6WN71UZREL@OvZ+ePVd~R~48_9vC5kdwm!SFImZk=5poK1Hy#6GVJ*iPf8Z^OyFITexFSMXP!|HGCL9cE=bRYv^8%3bIEULrY!U^rStJoi&X`5=H~#VaKjAn z@2XojNA@;D@W?&!mtnU8^Bs%ViR^s9v9vLZo+K?@+B`e`ehYDILRdvcJ*nx)f2=CW z%Y4G8UraRdh*BoaU!G3MQzTEr%e-*HHMNG!QPr0xc*AL0cp=M^v;VBpF^^!Rj56ez zXkZy%c#n%(io9BIg@IjP9vh*?7cxR2Z{(LeRz5(Qw*mmo7-bB6ET$`a$_=G7O;rD;I$%7!49Sg%{qC!=_a(hB4L#X14#Vb)}_7Fd15h3s>pM zcBw;9-H6-wKt_%?z)D^L^I~G3#KPANk%f?sJR<{Q8DQCFU0Hg<=Xm4J*pG4u7FKsB zwD4H(rkHQn$~mu-VUTaJK?h{Vqj|@S24Ue88-@Wof1OM;_8`pq$a_fvpR>^P+DuHI zTqM~;=e#`(37V@=6ON;3SIt6t^rH8QRJon)+f}d-VWWj5q_+=Pzkq)47%{k99UsQs zTf#b?79o!L4k+e>!)#%l?xlIMDd9rT&xp8|GZAG6F1qKM!PJXfqHn0%&zhEf4ny=n zW7)3m0~8?m92oD6 zbx@-8^MeVjb&JeIFZfb&KkMxY1Vpp zc{|We^CD@DIny922`@IxTnfSk!Ak`usECB?V@^D_q7I*dOSYu46EU$ku76$Q02PzH z8aPjT^|~j^Xv9K?;wizv;d_Suhqyj#9>71|_8q@Tu#eYm)$N|jWz~RHQKg8}z(n?@ z>N9pm_^WW!fSyc4oUR9-JUX2cG9x=QjR(*1&(ZSbV&AjbZF5I+b!urusFY@pfRP>m zT|3_?D5O8`X=sI!zZxxB{{MjVMP!+JmIqj5X|36h0t75I+N_s6nTrzUyjS@KDk7uM z98(;x^fxy7?NR|wp5pQ|e-VXq=q*G~dfiRL058tkl2S6WEzy5zL1JzSsM23cn&U!ZvZlNDMt1F?r&;HHJLzo>!YYiXbpQYW literal 0 HcmV?d00001 diff --git a/tests/TestKeePass2Reader.cpp b/tests/TestKeePass2Reader.cpp index d94dce85..a35a84d2 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 cccaf917..b2a75f66 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 00000000..72a35183 --- /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 00000000..e0e333a8 --- /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