Add initial support for encrypted database fields.

This commit is contained in:
Felix Geyer 2011-01-13 22:31:17 +01:00
parent ce834cda46
commit cfb55828b6
9 changed files with 153 additions and 21 deletions

View file

@ -114,6 +114,16 @@ const QHash<QString, QByteArray>& Entry::attachments() const
return m_binaries; 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 QString Entry::title() const
{ {
return m_attributes.value("Title"); return m_attributes.value("Title");
@ -207,9 +217,12 @@ void Entry::addAutoTypeAssociation(const AutoTypeAssociation& assoc)
m_autoTypeAssociations << 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); m_attributes.insert(key, value);
if (protect) {
m_protectedAttributes.insert(key);
}
// TODO add all visible oclumns // TODO add all visible oclumns
if (key == "Title") { 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); 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) void Entry::setTitle(const QString& title)

View file

@ -19,6 +19,7 @@
#define KEEPASSX_ENTRY_H #define KEEPASSX_ENTRY_H
#include <QtCore/QHash> #include <QtCore/QHash>
#include <QtCore/QSet>
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtGui/QColor> #include <QtGui/QColor>
#include <QtGui/QIcon> #include <QtGui/QIcon>
@ -57,6 +58,8 @@ public:
const QList<AutoTypeAssociation>& autoTypeAssociations() const; const QList<AutoTypeAssociation>& autoTypeAssociations() const;
const QHash<QString, QString>& attributes() const; const QHash<QString, QString>& attributes() const;
const QHash<QString, QByteArray>& attachments() const; const QHash<QString, QByteArray>& attachments() const;
bool isAttributeProtected(const QString& key) const;
bool isAttachmentProtected(const QString& key) const;
QString title() const; QString title() const;
QString url() const; QString url() const;
QString username() const; QString username() const;
@ -75,8 +78,10 @@ public:
void setAutoTypeObfuscation(int obfuscation); void setAutoTypeObfuscation(int obfuscation);
void setDefaultAutoTypeSequence(const QString& sequence); void setDefaultAutoTypeSequence(const QString& sequence);
void addAutoTypeAssociation(const AutoTypeAssociation& assoc); void addAutoTypeAssociation(const AutoTypeAssociation& assoc);
void addAttribute(const QString& key, const QString& value); void addAttribute(const QString& key, const QString& value, bool protect = false);
void addAttachment(const QString& key, const QByteArray& value); 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 setTitle(const QString& title);
void setUrl(const QString& url); void setUrl(const QString& url);
void setUsername(const QString& username); void setUsername(const QString& username);
@ -109,6 +114,8 @@ private:
QList<AutoTypeAssociation> m_autoTypeAssociations; QList<AutoTypeAssociation> m_autoTypeAssociations;
QHash<QString, QString> m_attributes; QHash<QString, QString> m_attributes;
QHash<QString, QByteArray> m_binaries; QHash<QString, QByteArray> m_binaries;
QSet<QString> m_protectedAttributes;
QSet<QString> m_protectedAttachments;
QList<Entry*> m_history; QList<Entry*> m_history;
Group* m_group; Group* m_group;

View file

@ -47,6 +47,12 @@ namespace KeePass2
StreamStartBytes = 9, StreamStartBytes = 9,
InnerRandomStreamID = 10 InnerRandomStreamID = 10
}; };
enum ProtectedStreamAlgo
{
ArcFourVariant = 1,
Salsa20 = 2
};
} }
#endif // KEEPASSX_KEEPASS2_H #endif // KEEPASSX_KEEPASS2_H

View file

@ -62,6 +62,8 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
while (readHeaderField() && !error()) { while (readHeaderField() && !error()) {
} }
// TODO check if all header fields have been parsed
QByteArray transformedMasterKey = key.transform(m_db->transformSeed(), m_db->transformRounds()); QByteArray transformedMasterKey = key.transform(m_db->transformSeed(), m_db->transformRounds());
m_db->setTransformedMasterKey(transformedMasterKey); m_db->setTransformedMasterKey(transformedMasterKey);
@ -96,8 +98,14 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
xmlDevice = ioCompressor.data(); 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; KeePass2XmlReader xmlReader;
xmlReader.readDatabase(xmlDevice, m_db); xmlReader.readDatabase(xmlDevice, m_db, &protectedStream);
// TODO forward error messages from xmlReader // TODO forward error messages from xmlReader
return m_db; return m_db;
} }
@ -279,7 +287,12 @@ void KeePass2Reader::setEncryptionIV(const QByteArray& data)
void KeePass2Reader::setProtectedStreamKey(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) void KeePass2Reader::setStreamStartBytes(const QByteArray& data)
@ -294,5 +307,14 @@ void KeePass2Reader::setStreamStartBytes(const QByteArray& data)
void KeePass2Reader::setInnerRandomStreamID(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("");
}
}
} }

View file

@ -60,6 +60,7 @@ private:
QByteArray m_masterSeed; QByteArray m_masterSeed;
QByteArray m_encryptionIV; QByteArray m_encryptionIV;
QByteArray m_streamStartBytes; QByteArray m_streamStartBytes;
QByteArray m_protectedStreamKey;
}; };
#endif // KEEPASSX_KEEPASS2READER_H #endif // KEEPASSX_KEEPASS2READER_H

View file

@ -24,17 +24,19 @@
#include "core/Metadata.h" #include "core/Metadata.h"
KeePass2XmlReader::KeePass2XmlReader() KeePass2XmlReader::KeePass2XmlReader()
: m_db(0) : m_cipher(0)
, m_db(0)
, m_meta(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_xml.setDevice(device);
m_db = db; m_db = db;
m_meta = m_db->metadata(); m_meta = m_db->metadata();
m_cipher = cipher;
m_tmpParent = new Group(); m_tmpParent = new Group();
m_tmpParent->setParent(m_db); m_tmpParent->setParent(m_db);
@ -516,7 +518,21 @@ void KeePass2XmlReader::parseEntryString(Entry *entry)
key = readString(); key = readString();
} }
else if (m_xml.name() == "Value") { 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 { else {
skipCurrentElement(); skipCurrentElement();
@ -534,7 +550,16 @@ void KeePass2XmlReader::parseEntryBinary(Entry *entry)
key = readString(); key = readString();
} }
else if (m_xml.name() == "Value") { 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 { else {
skipCurrentElement(); skipCurrentElement();

View file

@ -25,11 +25,13 @@
#include "core/TimeInfo.h" #include "core/TimeInfo.h"
#include "core/Uuid.h" #include "core/Uuid.h"
#include "crypto/SymmetricCipher.h"
class Database; class Database;
class Entry; class Entry;
class Group; class Group;
class Metadata; class Metadata;
class SymmetricCipher;
class KeePass2XmlReader class KeePass2XmlReader
{ {
@ -38,7 +40,7 @@ class KeePass2XmlReader
public: public:
KeePass2XmlReader(); KeePass2XmlReader();
Database* readDatabase(QIODevice* device); Database* readDatabase(QIODevice* device);
void readDatabase(QIODevice* device, Database* db); void readDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher = 0);
Database* readDatabase(const QString& filename); Database* readDatabase(const QString& filename);
bool error(); bool error();
QString errorString(); QString errorString();
@ -77,6 +79,7 @@ private:
void skipCurrentElement(); void skipCurrentElement();
QXmlStreamReader m_xml; QXmlStreamReader m_xml;
SymmetricCipher* m_cipher;
Database* m_db; Database* m_db;
Metadata* m_meta; Metadata* m_meta;
Group* m_tmpParent; Group* m_tmpParent;

View file

@ -21,20 +21,23 @@
#include <QtCore/QFile> #include <QtCore/QFile>
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/SymmetricCipher.h"
KeePass2XmlWriter::KeePass2XmlWriter() KeePass2XmlWriter::KeePass2XmlWriter()
: m_db(0) : m_db(0)
, m_meta(0) , m_meta(0)
, m_cipher(0)
{ {
m_xml.setAutoFormatting(true); m_xml.setAutoFormatting(true);
m_xml.setAutoFormattingIndent(-1); // 1 tab m_xml.setAutoFormattingIndent(-1); // 1 tab
m_xml.setCodec("UTF-8"); 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_db = db;
m_meta = db->metadata(); m_meta = db->metadata();
m_cipher = cipher;
m_xml.setDevice(device); m_xml.setDevice(device);
@ -50,11 +53,11 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db)
m_xml.writeEndDocument(); m_xml.writeEndDocument();
} }
void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db) void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db, SymmetricCipher* cipher)
{ {
QFile file(filename); QFile file(filename);
file.open(QIODevice::WriteOnly|QIODevice::Truncate); file.open(QIODevice::WriteOnly|QIODevice::Truncate);
writeDatabase(&file, db); writeDatabase(&file, db, cipher);
} }
void KeePass2XmlWriter::writeMetadata() void KeePass2XmlWriter::writeMetadata()
@ -271,15 +274,50 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
Q_FOREACH (const QString& key, entry->attributes().keys()) { Q_FOREACH (const QString& key, entry->attributes().keys()) {
m_xml.writeStartElement("String"); 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(); m_xml.writeEndElement();
} }
Q_FOREACH (const QString& key, entry->attachments().keys()) { Q_FOREACH (const QString& key, entry->attachments().keys()) {
m_xml.writeStartElement("Binary"); 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(); m_xml.writeEndElement();
} }

View file

@ -30,13 +30,14 @@
class Group; class Group;
class Metadata; class Metadata;
class SymmetricCipher;
class KeePass2XmlWriter class KeePass2XmlWriter
{ {
public: public:
KeePass2XmlWriter(); KeePass2XmlWriter();
void writeDatabase(QIODevice* device, Database* db); void writeDatabase(QIODevice* device, Database* db, SymmetricCipher* cipher = 0);
void writeDatabase(const QString& filename, Database* db); void writeDatabase(const QString& filename, Database* db, SymmetricCipher* cipher = 0);
bool error(); bool error();
QString errorString(); QString errorString();
@ -71,6 +72,7 @@ private:
QXmlStreamWriter m_xml; QXmlStreamWriter m_xml;
Database* m_db; Database* m_db;
Metadata* m_meta; Metadata* m_meta;
SymmetricCipher* m_cipher;
}; };
#endif // KEEPASSX_KEEPASS2XMLWRITER_H #endif // KEEPASSX_KEEPASS2XMLWRITER_H