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

View File

@ -33,6 +33,16 @@ QByteArray ChallengeResponseKey::rawKey() const
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
{
return m_error;
@ -52,3 +62,21 @@ bool ChallengeResponseKey::challenge(const QByteArray& challenge)
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;
QByteArray rawKey() const override;
void setRawKey(const QByteArray&) override;
YubiKeySlot slotData() const;
virtual bool challenge(const QByteArray& challenge);
QString error() const;
QByteArray serialize() const override;
void deserialize(const QByteArray& data) override;
static QUuid UUID;
private:

View File

@ -17,12 +17,16 @@
*/
#include "CompositeKey.h"
#include <QDebug>
#include <format/KeePass2.h>
#include "crypto/CryptoHash.h"
#include "crypto/kdf/Kdf.h"
#include "format/KeePass2.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");
@ -62,6 +66,11 @@ QByteArray CompositeKey::rawKey() const
return rawKey(nullptr);
}
void CompositeKey::setRawKey(const QByteArray& data)
{
deserialize(data);
}
/**
* Get raw key hash as bytes.
*
@ -187,3 +196,62 @@ const QList<QSharedPointer<ChallengeResponseKey>>& CompositeKey::challengeRespon
{
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;
QByteArray rawKey() const override;
void setRawKey(const QByteArray& data) override;
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;
@ -47,6 +48,9 @@ public:
void addChallengeResponseKey(const QSharedPointer<ChallengeResponseKey>& key);
const QList<QSharedPointer<ChallengeResponseKey>>& challengeResponseKeys() const;
QByteArray serialize() const override;
void deserialize(const QByteArray& data) override;
private:
QByteArray rawKey(const QByteArray* transformSeed, bool* ok = nullptr, QString* error = nullptr) const;

View File

@ -22,6 +22,7 @@
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
#include <QDataStream>
#include <QFile>
#include <QXmlStreamReader>
@ -147,6 +148,9 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
if (errorMsg) {
*errorMsg = file.errorString();
}
} else {
// Store the file path for serialization
m_file = fileName;
}
return result;
@ -160,6 +164,35 @@ QByteArray FileKey::rawKey() const
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.
*

View File

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

View File

@ -29,6 +29,9 @@ public:
Q_DISABLE_COPY(Key);
virtual ~Key() = default;
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
{
return m_uuid;

View File

@ -19,6 +19,7 @@
#include "crypto/CryptoHash.h"
#include <QDataStream>
#include <QSharedPointer>
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
@ -46,21 +47,44 @@ QByteArray PasswordKey::rawKey() const
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);
std::memcpy(m_key.data(), hash.data(), std::min(SHA256_SIZE, hash.size()));
m_isInitialized = true;
setRawKey(CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256));
}
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
{
auto result = QSharedPointer<PasswordKey>::create();
result->setHash(rawKey);
result->setRawKey(rawKey);
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);
~PasswordKey() override = default;
QByteArray rawKey() const override;
void setRawKey(const QByteArray& data) override;
void setPassword(const QString& password);
void setHash(const QByteArray& hash);
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
QByteArray serialize() const override;
void deserialize(const QByteArray& data) override;
private:
static constexpr int SHA256_SIZE = 32;

View File

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