mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-23 22:39:43 -05:00
Add key serialization to support Quick Unlock
This commit is contained in:
parent
acce1bc5ce
commit
a76daeb4c5
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user