mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-25 00:50:04 -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;
|
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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user