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;
}
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)

View File

@ -19,6 +19,7 @@
#define KEEPASSX_ENTRY_H
#include <QtCore/QHash>
#include <QtCore/QSet>
#include <QtCore/QUrl>
#include <QtGui/QColor>
#include <QtGui/QIcon>
@ -57,6 +58,8 @@ public:
const QList<AutoTypeAssociation>& autoTypeAssociations() const;
const QHash<QString, QString>& attributes() const;
const QHash<QString, QByteArray>& 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<AutoTypeAssociation> m_autoTypeAssociations;
QHash<QString, QString> m_attributes;
QHash<QString, QByteArray> m_binaries;
QSet<QString> m_protectedAttributes;
QSet<QString> m_protectedAttachments;
QList<Entry*> m_history;
Group* m_group;

View File

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

View File

@ -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("");
}
}
}

View File

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

View File

@ -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();

View File

@ -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;

View File

@ -21,20 +21,23 @@
#include <QtCore/QFile>
#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();
}

View File

@ -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