From cfb55828b651615140ca9163c54a6bda685e7282 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Thu, 13 Jan 2011 22:31:17 +0100 Subject: [PATCH] Add initial support for encrypted database fields. --- src/core/Entry.cpp | 32 ++++++++++++++++++-- src/core/Entry.h | 11 +++++-- src/format/KeePass2.h | 6 ++++ src/format/KeePass2Reader.cpp | 28 +++++++++++++++-- src/format/KeePass2Reader.h | 1 + src/format/KeePass2XmlReader.cpp | 33 +++++++++++++++++--- src/format/KeePass2XmlReader.h | 5 ++- src/format/KeePass2XmlWriter.cpp | 52 +++++++++++++++++++++++++++----- src/format/KeePass2XmlWriter.h | 6 ++-- 9 files changed, 153 insertions(+), 21 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 9512a7f3d..c2c01b6a2 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -114,6 +114,16 @@ const QHash& Entry::attachments() const return m_binaries; } +bool Entry::isAttributeProtected(const QString& key) const +{ + return m_protectedAttributes.contains(key); +} + +bool Entry::isAttachmentProtected(const QString& key) const +{ + return m_protectedAttachments.contains(key); +} + QString Entry::title() const { return m_attributes.value("Title"); @@ -207,9 +217,12 @@ void Entry::addAutoTypeAssociation(const AutoTypeAssociation& assoc) m_autoTypeAssociations << assoc; } -void Entry::addAttribute(const QString& key, const QString& value) +void Entry::addAttribute(const QString& key, const QString& value, bool protect) { m_attributes.insert(key, value); + if (protect) { + m_protectedAttributes.insert(key); + } // TODO add all visible oclumns if (key == "Title") { @@ -217,9 +230,24 @@ void Entry::addAttribute(const QString& key, const QString& value) } } -void Entry::addAttachment(const QString& key, const QByteArray& value) +void Entry::removeAttribute(const QString& key) +{ + m_attributes.remove(key); + m_protectedAttributes.remove(key); +} + +void Entry::addAttachment(const QString& key, const QByteArray& value, bool protect) { m_binaries.insert(key, value); + if (protect) { + m_protectedAttachments.insert(key); + } +} + +void Entry::removeAttachment(const QString& key) +{ + m_binaries.remove(key); + m_protectedAttachments.remove(key); } void Entry::setTitle(const QString& title) diff --git a/src/core/Entry.h b/src/core/Entry.h index 40497e058..b1fc8e77f 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -19,6 +19,7 @@ #define KEEPASSX_ENTRY_H #include +#include #include #include #include @@ -57,6 +58,8 @@ public: const QList& autoTypeAssociations() const; const QHash& attributes() const; const QHash& attachments() const; + bool isAttributeProtected(const QString& key) const; + bool isAttachmentProtected(const QString& key) const; QString title() const; QString url() const; QString username() const; @@ -75,8 +78,10 @@ public: void setAutoTypeObfuscation(int obfuscation); void setDefaultAutoTypeSequence(const QString& sequence); void addAutoTypeAssociation(const AutoTypeAssociation& assoc); - void addAttribute(const QString& key, const QString& value); - void addAttachment(const QString& key, const QByteArray& value); + void addAttribute(const QString& key, const QString& value, bool protect = false); + void removeAttribute(const QString& key); + void addAttachment(const QString& key, const QByteArray& value, bool protect = false); + void removeAttachment(const QString& key); void setTitle(const QString& title); void setUrl(const QString& url); void setUsername(const QString& username); @@ -109,6 +114,8 @@ private: QList m_autoTypeAssociations; QHash m_attributes; QHash m_binaries; + QSet m_protectedAttributes; + QSet m_protectedAttachments; QList m_history; Group* m_group; diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index ecf7921b1..913547623 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -47,6 +47,12 @@ namespace KeePass2 StreamStartBytes = 9, InnerRandomStreamID = 10 }; + + enum ProtectedStreamAlgo + { + ArcFourVariant = 1, + Salsa20 = 2 + }; } #endif // KEEPASSX_KEEPASS2_H diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index f26fa188b..2f827dfe8 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -62,6 +62,8 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke while (readHeaderField() && !error()) { } + // TODO check if all header fields have been parsed + QByteArray transformedMasterKey = key.transform(m_db->transformSeed(), m_db->transformRounds()); m_db->setTransformedMasterKey(transformedMasterKey); @@ -96,8 +98,14 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke xmlDevice = ioCompressor.data(); } + QByteArray protectedStreamKey = CryptoHash::hash(m_protectedStreamKey, CryptoHash::Sha256); + QByteArray protectedStreamIv("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); + + SymmetricCipher protectedStream(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Decrypt, + protectedStreamKey, protectedStreamIv); + KeePass2XmlReader xmlReader; - xmlReader.readDatabase(xmlDevice, m_db); + xmlReader.readDatabase(xmlDevice, m_db, &protectedStream); // TODO forward error messages from xmlReader return m_db; } @@ -279,7 +287,12 @@ void KeePass2Reader::setEncryptionIV(const QByteArray& data) void KeePass2Reader::setProtectedStreamKey(const QByteArray& data) { - // TODO ignore? + if (data.size() != 32) { + raiseError(""); + } + else { + m_protectedStreamKey = data; + } } void KeePass2Reader::setStreamStartBytes(const QByteArray& data) @@ -294,5 +307,14 @@ void KeePass2Reader::setStreamStartBytes(const QByteArray& data) void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data) { - // TODO ignore? + if (data.size() != 4) { + raiseError(""); + } + else { + quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); + + if (id != KeePass2::Salsa20) { + raiseError(""); + } + } } diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index 9730d9538..154097e53 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -60,6 +60,7 @@ private: QByteArray m_masterSeed; QByteArray m_encryptionIV; QByteArray m_streamStartBytes; + QByteArray m_protectedStreamKey; }; #endif // KEEPASSX_KEEPASS2READER_H diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index 62a4e81e7..a38f2f1a2 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -24,17 +24,19 @@ #include "core/Metadata.h" KeePass2XmlReader::KeePass2XmlReader() - : m_db(0) + : m_cipher(0) + , m_db(0) , m_meta(0) { } -void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db) +void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher) { m_xml.setDevice(device); m_db = db; m_meta = m_db->metadata(); + m_cipher = cipher; m_tmpParent = new Group(); m_tmpParent->setParent(m_db); @@ -516,7 +518,21 @@ void KeePass2XmlReader::parseEntryString(Entry *entry) key = readString(); } else if (m_xml.name() == "Value") { - entry->addAttribute(key, readString()); + QXmlStreamAttributes attr = m_xml.attributes(); + QString value = readString(); + + bool isProtected = attr.hasAttribute("Protected") && (attr.value("Protected") == "True"); + + if (isProtected && !value.isEmpty()) { + if (m_cipher) { + value = m_cipher->process(QByteArray::fromBase64(value.toAscii())); + } + else { + raiseError(); + } + } + + entry->addAttribute(key, value, isProtected); } else { skipCurrentElement(); @@ -534,7 +550,16 @@ void KeePass2XmlReader::parseEntryBinary(Entry *entry) key = readString(); } else if (m_xml.name() == "Value") { - entry->addAttachment(key, readBinary()); + QByteArray value = readBinary(); + QXmlStreamAttributes attr = m_xml.attributes(); + + bool isProtected = attr.hasAttribute("Protected") && (attr.value("Protected") == "True"); + + if (isProtected && !value.isEmpty()) { + m_cipher->processInPlace(value); + } + + entry->addAttachment(key, value, isProtected); } else { skipCurrentElement(); diff --git a/src/format/KeePass2XmlReader.h b/src/format/KeePass2XmlReader.h index da6b17c36..575877c37 100644 --- a/src/format/KeePass2XmlReader.h +++ b/src/format/KeePass2XmlReader.h @@ -25,11 +25,13 @@ #include "core/TimeInfo.h" #include "core/Uuid.h" +#include "crypto/SymmetricCipher.h" class Database; class Entry; class Group; class Metadata; +class SymmetricCipher; class KeePass2XmlReader { @@ -38,7 +40,7 @@ class KeePass2XmlReader public: KeePass2XmlReader(); Database* readDatabase(QIODevice* device); - void readDatabase(QIODevice* device, Database* db); + void readDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher = 0); Database* readDatabase(const QString& filename); bool error(); QString errorString(); @@ -77,6 +79,7 @@ private: void skipCurrentElement(); QXmlStreamReader m_xml; + SymmetricCipher* m_cipher; Database* m_db; Metadata* m_meta; Group* m_tmpParent; diff --git a/src/format/KeePass2XmlWriter.cpp b/src/format/KeePass2XmlWriter.cpp index 77a7d8adb..a8a8264f0 100644 --- a/src/format/KeePass2XmlWriter.cpp +++ b/src/format/KeePass2XmlWriter.cpp @@ -21,20 +21,23 @@ #include #include "core/Metadata.h" +#include "crypto/SymmetricCipher.h" KeePass2XmlWriter::KeePass2XmlWriter() : m_db(0) , m_meta(0) + , m_cipher(0) { m_xml.setAutoFormatting(true); m_xml.setAutoFormattingIndent(-1); // 1 tab m_xml.setCodec("UTF-8"); } -void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db) +void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher) { m_db = db; m_meta = db->metadata(); + m_cipher = cipher; m_xml.setDevice(device); @@ -50,11 +53,11 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db) m_xml.writeEndDocument(); } -void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db) +void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db, SymmetricCipher* cipher) { QFile file(filename); file.open(QIODevice::WriteOnly|QIODevice::Truncate); - writeDatabase(&file, db); + writeDatabase(&file, db, cipher); } void KeePass2XmlWriter::writeMetadata() @@ -271,15 +274,50 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry) Q_FOREACH (const QString& key, entry->attributes().keys()) { m_xml.writeStartElement("String"); - writeString("Key", key); - writeString("Value", entry->attributes().value(key)); + + bool protect = ( ((key == "Title") && m_meta->protectTitle()) || + ((key == "UserName") && m_meta->protectUsername()) || + ((key == "Password") && m_meta->protectPassword()) || + ((key == "URL") && m_meta->protectUrl()) || + ((key == "Notes") && m_meta->protectNotes()) || + entry->isAttributeProtected(key) ) && + m_cipher; + + 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())); + } + else { + writeString("Value", entry->attributes().value(key)); + } + m_xml.writeEndElement(); } Q_FOREACH (const QString& key, entry->attachments().keys()) { m_xml.writeStartElement("Binary"); - writeString("Key", key); - writeBinary("Value", entry->attachments().value(key)); + + 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(); + + if (protect) { + writeBinary("Value", m_cipher->process(entry->attachments().value(key))); + } + else { + writeBinary("Value", entry->attachments().value(key)); + } + m_xml.writeEndElement(); } diff --git a/src/format/KeePass2XmlWriter.h b/src/format/KeePass2XmlWriter.h index ba123b80d..afe9876de 100644 --- a/src/format/KeePass2XmlWriter.h +++ b/src/format/KeePass2XmlWriter.h @@ -30,13 +30,14 @@ class Group; class Metadata; +class SymmetricCipher; class KeePass2XmlWriter { public: KeePass2XmlWriter(); - void writeDatabase(QIODevice* device, Database* db); - void writeDatabase(const QString& filename, Database* db); + void writeDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher = 0); + void writeDatabase(const QString& filename, Database* db, SymmetricCipher* cipher = 0); bool error(); QString errorString(); @@ -71,6 +72,7 @@ private: QXmlStreamWriter m_xml; Database* m_db; Metadata* m_meta; + SymmetricCipher* m_cipher; }; #endif // KEEPASSX_KEEPASS2XMLWRITER_H