Add key serialization to support Quick Unlock

This commit is contained in:
Jonathan White 2022-02-18 10:17:21 -05:00
parent acce1bc5ce
commit a76daeb4c5
11 changed files with 194 additions and 16 deletions

View File

@ -391,7 +391,7 @@ bool Database::writeDatabase(QIODevice* device, QString* error)
{ {
PasswordKey oldTransformedKey; PasswordKey oldTransformedKey;
if (m_data.key->isEmpty()) { if (m_data.key->isEmpty()) {
oldTransformedKey.setHash(m_data.transformedDatabaseKey->rawKey()); oldTransformedKey.setRawKey(m_data.transformedDatabaseKey->rawKey());
} }
KeePass2Writer writer; KeePass2Writer writer;
@ -738,11 +738,11 @@ bool Database::challengeMasterSeed(const QByteArray& masterSeed)
{ {
m_keyError.clear(); m_keyError.clear();
if (m_data.key) { if (m_data.key) {
m_data.masterSeed->setHash(masterSeed); m_data.masterSeed->setRawKey(masterSeed);
QByteArray response; QByteArray response;
bool ok = m_data.key->challenge(masterSeed, response, &m_keyError); bool ok = m_data.key->challenge(masterSeed, response, &m_keyError);
if (ok && !response.isEmpty()) { if (ok && !response.isEmpty()) {
m_data.challengeResponseKey->setHash(response); m_data.challengeResponseKey->setRawKey(response);
} else if (ok && response.isEmpty()) { } else if (ok && response.isEmpty()) {
// no CR key present, make sure buffer is empty // no CR key present, make sure buffer is empty
m_data.challengeResponseKey.reset(new PasswordKey); m_data.challengeResponseKey.reset(new PasswordKey);
@ -795,7 +795,7 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key,
PasswordKey oldTransformedDatabaseKey; PasswordKey oldTransformedDatabaseKey;
if (m_data.key && !m_data.key->isEmpty()) { if (m_data.key && !m_data.key->isEmpty()) {
oldTransformedDatabaseKey.setHash(m_data.transformedDatabaseKey->rawKey()); oldTransformedDatabaseKey.setRawKey(m_data.transformedDatabaseKey->rawKey());
} }
QByteArray transformedDatabaseKey; QByteArray transformedDatabaseKey;
@ -808,7 +808,7 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key,
m_data.key = key; m_data.key = key;
if (!transformedDatabaseKey.isEmpty()) { if (!transformedDatabaseKey.isEmpty()) {
m_data.transformedDatabaseKey->setHash(transformedDatabaseKey); m_data.transformedDatabaseKey->setRawKey(transformedDatabaseKey);
} }
if (updateChangedTime) { if (updateChangedTime) {
m_metadata->setDatabaseKeyChanged(Clock::currentDateTimeUtc()); m_metadata->setDatabaseKeyChanged(Clock::currentDateTimeUtc());
@ -966,7 +966,7 @@ bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
} }
setKdf(kdf); setKdf(kdf);
m_data.transformedDatabaseKey->setHash(transformedDatabaseKey); m_data.transformedDatabaseKey->setRawKey(transformedDatabaseKey);
markAsModified(); markAsModified();
return true; return true;

View File

@ -33,6 +33,16 @@ QByteArray ChallengeResponseKey::rawKey() const
return QByteArray(m_key.data(), m_key.size()); return QByteArray(m_key.data(), m_key.size());
} }
void ChallengeResponseKey::setRawKey(const QByteArray&)
{
// Nothing to do here
}
YubiKeySlot ChallengeResponseKey::slotData() const
{
return m_keySlot;
}
QString ChallengeResponseKey::error() const QString ChallengeResponseKey::error() const
{ {
return m_error; return m_error;
@ -52,3 +62,21 @@ bool ChallengeResponseKey::challenge(const QByteArray& challenge)
return result == YubiKey::ChallengeResult::YCR_SUCCESS; return result == YubiKey::ChallengeResult::YCR_SUCCESS;
} }
QByteArray ChallengeResponseKey::serialize() const
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << uuid().toRfc4122() << m_keySlot;
return data;
}
void ChallengeResponseKey::deserialize(const QByteArray& data)
{
QDataStream stream(data);
QByteArray uuidData;
stream >> uuidData;
if (uuid().toRfc4122() == uuidData) {
stream >> m_keySlot;
}
}

View File

@ -29,10 +29,15 @@ public:
~ChallengeResponseKey() override = default; ~ChallengeResponseKey() override = default;
QByteArray rawKey() const override; QByteArray rawKey() const override;
void setRawKey(const QByteArray&) override;
YubiKeySlot slotData() const;
virtual bool challenge(const QByteArray& challenge); virtual bool challenge(const QByteArray& challenge);
QString error() const; QString error() const;
QByteArray serialize() const override;
void deserialize(const QByteArray& data) override;
static QUuid UUID; static QUuid UUID;
private: private:

View File

@ -17,12 +17,16 @@
*/ */
#include "CompositeKey.h" #include "CompositeKey.h"
#include <QDebug>
#include <format/KeePass2.h>
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "crypto/kdf/Kdf.h" #include "crypto/kdf/Kdf.h"
#include "format/KeePass2.h"
#include "keys/ChallengeResponseKey.h" #include "keys/ChallengeResponseKey.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
#include <QDataStream>
#include <QDebug>
QUuid CompositeKey::UUID("76a7ae25-a542-4add-9849-7c06be945b94"); QUuid CompositeKey::UUID("76a7ae25-a542-4add-9849-7c06be945b94");
@ -62,6 +66,11 @@ QByteArray CompositeKey::rawKey() const
return rawKey(nullptr); return rawKey(nullptr);
} }
void CompositeKey::setRawKey(const QByteArray& data)
{
deserialize(data);
}
/** /**
* Get raw key hash as bytes. * Get raw key hash as bytes.
* *
@ -187,3 +196,62 @@ const QList<QSharedPointer<ChallengeResponseKey>>& CompositeKey::challengeRespon
{ {
return m_challengeResponseKeys; return m_challengeResponseKeys;
} }
QByteArray CompositeKey::serialize() const
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
// Write Composite Key UUID then each sub-key UUID and data
stream << uuid().toRfc4122();
for (auto const& key : m_keys) {
stream << key->uuid().toRfc4122() << key->serialize();
}
for (auto const& key : m_challengeResponseKeys) {
stream << key->uuid().toRfc4122() << key->serialize();
}
return data;
}
void CompositeKey::deserialize(const QByteArray& data)
{
QByteArray uuidData;
QByteArray keyData;
QDataStream stream(data);
// Verify this is a valid composite key data stream
stream >> uuidData;
if (uuid().toRfc4122() != uuidData) {
return;
}
// Clear existing keys
m_keys.clear();
m_challengeResponseKeys.clear();
while (!stream.atEnd()) {
// Read the UUID first to construct the key
stream >> uuidData;
auto uuid = QUuid::fromRfc4122(uuidData);
if (uuid == ChallengeResponseKey::UUID) {
stream >> keyData;
auto key = QSharedPointer<ChallengeResponseKey>::create();
key->deserialize(keyData);
m_challengeResponseKeys.append(key);
} else if (uuid == PasswordKey::UUID) {
stream >> keyData;
auto key = QSharedPointer<PasswordKey>::create();
key->deserialize(keyData);
m_keys << key;
} else if (uuid == FileKey::UUID) {
stream >> keyData;
auto key = QSharedPointer<FileKey>::create();
key->deserialize(keyData);
m_keys << key;
} else {
// Unsupported key type, discard key data
stream >> keyData;
}
}
}

View File

@ -37,6 +37,7 @@ public:
bool isEmpty() const; bool isEmpty() const;
QByteArray rawKey() const override; QByteArray rawKey() const override;
void setRawKey(const QByteArray& data) override;
Q_REQUIRED_RESULT bool transform(const Kdf& kdf, QByteArray& result, QString* error = nullptr) const; Q_REQUIRED_RESULT bool transform(const Kdf& kdf, QByteArray& result, QString* error = nullptr) const;
bool challenge(const QByteArray& seed, QByteArray& result, QString* error = nullptr) const; bool challenge(const QByteArray& seed, QByteArray& result, QString* error = nullptr) const;
@ -47,6 +48,9 @@ public:
void addChallengeResponseKey(const QSharedPointer<ChallengeResponseKey>& key); void addChallengeResponseKey(const QSharedPointer<ChallengeResponseKey>& key);
const QList<QSharedPointer<ChallengeResponseKey>>& challengeResponseKeys() const; const QList<QSharedPointer<ChallengeResponseKey>>& challengeResponseKeys() const;
QByteArray serialize() const override;
void deserialize(const QByteArray& data) override;
private: private:
QByteArray rawKey(const QByteArray* transformSeed, bool* ok = nullptr, QString* error = nullptr) const; QByteArray rawKey(const QByteArray* transformSeed, bool* ok = nullptr, QString* error = nullptr) const;

View File

@ -22,6 +22,7 @@
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "crypto/Random.h" #include "crypto/Random.h"
#include <QDataStream>
#include <QFile> #include <QFile>
#include <QXmlStreamReader> #include <QXmlStreamReader>
@ -147,6 +148,9 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
if (errorMsg) { if (errorMsg) {
*errorMsg = file.errorString(); *errorMsg = file.errorString();
} }
} else {
// Store the file path for serialization
m_file = fileName;
} }
return result; return result;
@ -160,6 +164,35 @@ QByteArray FileKey::rawKey() const
return QByteArray(m_key.data(), m_key.size()); return QByteArray(m_key.data(), m_key.size());
} }
void FileKey::setRawKey(const QByteArray& data)
{
Q_ASSERT(data.size() == SHA256_SIZE);
m_key.assign(data.begin(), data.end());
}
QByteArray FileKey::serialize() const
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << uuid().toRfc4122() << rawKey() << static_cast<qint32>(m_type) << m_file;
return data;
}
void FileKey::deserialize(const QByteArray& data)
{
QDataStream stream(data);
QByteArray uuidData;
stream >> uuidData;
if (uuid().toRfc4122() == uuidData) {
QByteArray key;
qint32 type;
stream >> key >> type >> m_file;
setRawKey(key);
m_type = static_cast<Type>(type);
}
}
/** /**
* Generate a new key file with random bytes. * Generate a new key file with random bytes.
* *

View File

@ -45,11 +45,15 @@ public:
bool load(QIODevice* device, QString* errorMsg = nullptr); bool load(QIODevice* device, QString* errorMsg = nullptr);
bool load(const QString& fileName, QString* errorMsg = nullptr); bool load(const QString& fileName, QString* errorMsg = nullptr);
QByteArray rawKey() const override; QByteArray rawKey() const override;
void setRawKey(const QByteArray& data) override;
Type type() const; Type type() const;
static void createRandom(QIODevice* device, int size = 128); static void createRandom(QIODevice* device, int size = 128);
static void createXMLv2(QIODevice* device, int size = 32); static void createXMLv2(QIODevice* device, int size = 32);
static bool create(const QString& fileName, QString* errorMsg = nullptr); static bool create(const QString& fileName, QString* errorMsg = nullptr);
QByteArray serialize() const override;
void deserialize(const QByteArray& data) override;
private: private:
static constexpr int SHA256_SIZE = 32; static constexpr int SHA256_SIZE = 32;
@ -60,6 +64,7 @@ private:
Botan::secure_vector<char> m_key; Botan::secure_vector<char> m_key;
Type m_type = None; Type m_type = None;
QString m_file;
}; };
#endif // KEEPASSX_FILEKEY_H #endif // KEEPASSX_FILEKEY_H

View File

@ -29,6 +29,9 @@ public:
Q_DISABLE_COPY(Key); Q_DISABLE_COPY(Key);
virtual ~Key() = default; virtual ~Key() = default;
virtual QByteArray rawKey() const = 0; virtual QByteArray rawKey() const = 0;
virtual void setRawKey(const QByteArray& data) = 0;
virtual QByteArray serialize() const = 0;
virtual void deserialize(const QByteArray& data) = 0;
inline virtual QUuid uuid() const inline virtual QUuid uuid() const
{ {
return m_uuid; return m_uuid;

View File

@ -19,6 +19,7 @@
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include <QDataStream>
#include <QSharedPointer> #include <QSharedPointer>
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead"); QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
@ -46,21 +47,44 @@ QByteArray PasswordKey::rawKey() const
return QByteArray(m_key.data(), m_key.size()); return QByteArray(m_key.data(), m_key.size());
} }
void PasswordKey::setPassword(const QString& password) void PasswordKey::setRawKey(const QByteArray& data)
{ {
setHash(CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256)); if (data.isEmpty()) {
m_key.clear();
m_isInitialized = false;
} else {
Q_ASSERT(data.size() == SHA256_SIZE);
m_key.assign(data.begin(), data.end());
m_isInitialized = true;
}
} }
void PasswordKey::setHash(const QByteArray& hash) void PasswordKey::setPassword(const QString& password)
{ {
Q_ASSERT(hash.size() == SHA256_SIZE); setRawKey(CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256));
std::memcpy(m_key.data(), hash.data(), std::min(SHA256_SIZE, hash.size()));
m_isInitialized = true;
} }
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey) QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
{ {
auto result = QSharedPointer<PasswordKey>::create(); auto result = QSharedPointer<PasswordKey>::create();
result->setHash(rawKey); result->setRawKey(rawKey);
return result; return result;
} }
QByteArray PasswordKey::serialize() const
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << uuid().toRfc4122() << rawKey();
return data;
}
void PasswordKey::deserialize(const QByteArray& data)
{
QByteArray uuidData, key;
QDataStream stream(data);
stream >> uuidData >> key;
if (uuid().toRfc4122() == uuidData) {
setRawKey(key);
}
}

View File

@ -33,11 +33,14 @@ public:
explicit PasswordKey(const QString& password); explicit PasswordKey(const QString& password);
~PasswordKey() override = default; ~PasswordKey() override = default;
QByteArray rawKey() const override; QByteArray rawKey() const override;
void setRawKey(const QByteArray& data) override;
void setPassword(const QString& password); void setPassword(const QString& password);
void setHash(const QByteArray& hash);
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey); static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
QByteArray serialize() const override;
void deserialize(const QByteArray& data) override;
private: private:
static constexpr int SHA256_SIZE = 32; static constexpr int SHA256_SIZE = 32;

View File

@ -66,6 +66,11 @@ void TestKeys::testComposite()
compositeKey3->addKey(QSharedPointer<PasswordKey>::create("test")); compositeKey3->addKey(QSharedPointer<PasswordKey>::create("test"));
compositeKey3->clear(); compositeKey3->clear();
QCOMPARE(compositeKey3->rawKey(), compositeKey4->rawKey()); QCOMPARE(compositeKey3->rawKey(), compositeKey4->rawKey());
// Test serialization
auto data = compositeKey1->serialize();
compositeKey3->deserialize(data);
QCOMPARE(compositeKey1->rawKey(), compositeKey3->rawKey());
} }
void TestKeys::testFileKey() void TestKeys::testFileKey()