mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Use PasswordKey for storing transformed secrets.
The transformed secrets were stored in normal QByteArrays, which are at risk of being swapped out. We now use secure PasswordKey objects instead. There are still a few areas where QByteArrays are used for storing secrets, but since they are all temporary, they are less critical. It may be worth hunting those down as well, though.
This commit is contained in:
parent
22af66e3b5
commit
5996ba51c9
@ -316,7 +316,11 @@ bool Database::writeDatabase(QIODevice* device, QString* error)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray oldTransformedKey = m_data.transformedMasterKey;
|
PasswordKey oldTransformedKey;
|
||||||
|
if (m_data.hasKey) {
|
||||||
|
oldTransformedKey.setHash(m_data.transformedMasterKey->rawKey());
|
||||||
|
}
|
||||||
|
|
||||||
KeePass2Writer writer;
|
KeePass2Writer writer;
|
||||||
setEmitModified(false);
|
setEmitModified(false);
|
||||||
writer.writeDatabase(device, this);
|
writer.writeDatabase(device, this);
|
||||||
@ -329,9 +333,10 @@ bool Database::writeDatabase(QIODevice* device, QString* error)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(!m_data.transformedMasterKey.isEmpty());
|
QByteArray newKey = m_data.transformedMasterKey->rawKey();
|
||||||
Q_ASSERT(m_data.transformedMasterKey != oldTransformedKey);
|
Q_ASSERT(!newKey.isEmpty());
|
||||||
if (m_data.transformedMasterKey.isEmpty() || m_data.transformedMasterKey == oldTransformedKey) {
|
Q_ASSERT(newKey != oldTransformedKey.rawKey());
|
||||||
|
if (newKey.isEmpty() || newKey == oldTransformedKey.rawKey()) {
|
||||||
if (error) {
|
if (error) {
|
||||||
*error = tr("Key not transformed. This is a bug, please report it to the developers!");
|
*error = tr("Key not transformed. This is a bug, please report it to the developers!");
|
||||||
}
|
}
|
||||||
@ -382,6 +387,7 @@ bool Database::import(const QString& xmlExportPath, QString* error)
|
|||||||
*
|
*
|
||||||
* A previously reparented root group will not be freed.
|
* A previously reparented root group will not be freed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void Database::releaseData()
|
void Database::releaseData()
|
||||||
{
|
{
|
||||||
s_uuidMap.remove(m_uuid);
|
s_uuidMap.remove(m_uuid);
|
||||||
@ -391,7 +397,7 @@ void Database::releaseData()
|
|||||||
emit databaseDiscarded();
|
emit databaseDiscarded();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_data = DatabaseData();
|
m_data.clear();
|
||||||
|
|
||||||
if (m_rootGroup && m_rootGroup->parent() == this) {
|
if (m_rootGroup && m_rootGroup->parent() == this) {
|
||||||
delete m_rootGroup;
|
delete m_rootGroup;
|
||||||
@ -630,19 +636,24 @@ Database::CompressionAlgorithm Database::compressionAlgorithm() const
|
|||||||
|
|
||||||
QByteArray Database::transformedMasterKey() const
|
QByteArray Database::transformedMasterKey() const
|
||||||
{
|
{
|
||||||
return m_data.transformedMasterKey;
|
return m_data.transformedMasterKey->rawKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Database::challengeResponseKey() const
|
QByteArray Database::challengeResponseKey() const
|
||||||
{
|
{
|
||||||
return m_data.challengeResponseKey;
|
return m_data.challengeResponseKey->rawKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database::challengeMasterSeed(const QByteArray& masterSeed)
|
bool Database::challengeMasterSeed(const QByteArray& masterSeed)
|
||||||
{
|
{
|
||||||
if (m_data.key) {
|
if (m_data.key) {
|
||||||
m_data.masterSeed = masterSeed;
|
m_data.masterSeed->setHash(masterSeed);
|
||||||
return m_data.key->challenge(masterSeed, m_data.challengeResponseKey);
|
QByteArray response;
|
||||||
|
bool ok = m_data.key->challenge(masterSeed, response);
|
||||||
|
if (ok && !response.isEmpty()) {
|
||||||
|
m_data.challengeResponseKey->setHash(response);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -679,7 +690,7 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key,
|
|||||||
|
|
||||||
if (!key) {
|
if (!key) {
|
||||||
m_data.key.reset();
|
m_data.key.reset();
|
||||||
m_data.transformedMasterKey = {};
|
m_data.transformedMasterKey.reset(new PasswordKey());
|
||||||
m_data.hasKey = false;
|
m_data.hasKey = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -689,22 +700,29 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key,
|
|||||||
Q_ASSERT(!m_data.kdf->seed().isEmpty());
|
Q_ASSERT(!m_data.kdf->seed().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray oldTransformedMasterKey = m_data.transformedMasterKey;
|
PasswordKey oldTransformedMasterKey;
|
||||||
|
if (m_data.hasKey) {
|
||||||
|
oldTransformedMasterKey.setHash(m_data.transformedMasterKey->rawKey());
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray transformedMasterKey;
|
QByteArray transformedMasterKey;
|
||||||
|
|
||||||
if (!transformKey) {
|
if (!transformKey) {
|
||||||
transformedMasterKey = oldTransformedMasterKey;
|
transformedMasterKey = QByteArray(oldTransformedMasterKey.rawKey());
|
||||||
} else if (!key->transform(*m_data.kdf, transformedMasterKey)) {
|
} else if (!key->transform(*m_data.kdf, transformedMasterKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_data.key = key;
|
m_data.key = key;
|
||||||
m_data.transformedMasterKey = transformedMasterKey;
|
if (!transformedMasterKey.isEmpty()) {
|
||||||
|
m_data.transformedMasterKey->setHash(transformedMasterKey);
|
||||||
|
}
|
||||||
m_data.hasKey = true;
|
m_data.hasKey = true;
|
||||||
if (updateChangedTime) {
|
if (updateChangedTime) {
|
||||||
m_metadata->setMasterKeyChanged(Clock::currentDateTimeUtc());
|
m_metadata->setMasterKeyChanged(Clock::currentDateTimeUtc());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldTransformedMasterKey != m_data.transformedMasterKey) {
|
if (oldTransformedMasterKey.rawKey() != m_data.transformedMasterKey->rawKey()) {
|
||||||
markAsModified();
|
markAsModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,15 +738,15 @@ bool Database::verifyKey(const QSharedPointer<CompositeKey>& key) const
|
|||||||
{
|
{
|
||||||
Q_ASSERT(hasKey());
|
Q_ASSERT(hasKey());
|
||||||
|
|
||||||
if (!m_data.challengeResponseKey.isEmpty()) {
|
if (!m_data.challengeResponseKey->rawKey().isEmpty()) {
|
||||||
QByteArray result;
|
QByteArray result;
|
||||||
|
|
||||||
if (!key->challenge(m_data.masterSeed, result)) {
|
if (!key->challenge(m_data.masterSeed->rawKey(), result)) {
|
||||||
// challenge failed, (YubiKey?) removed?
|
// challenge failed, (YubiKey?) removed?
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_data.challengeResponseKey != result) {
|
if (m_data.challengeResponseKey->rawKey() != result) {
|
||||||
// wrong response from challenged device(s)
|
// wrong response from challenged device(s)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -893,7 +911,7 @@ bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
|
|||||||
}
|
}
|
||||||
|
|
||||||
setKdf(kdf);
|
setKdf(kdf);
|
||||||
m_data.transformedMasterKey = transformedMasterKey;
|
m_data.transformedMasterKey->setHash(transformedMasterKey);
|
||||||
markAsModified();
|
markAsModified();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -23,12 +23,15 @@
|
|||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
|
||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
#include "crypto/kdf/AesKdf.h"
|
#include "crypto/kdf/AesKdf.h"
|
||||||
#include "crypto/kdf/Kdf.h"
|
#include "crypto/kdf/Kdf.h"
|
||||||
#include "format/KeePass2.h"
|
#include "format/KeePass2.h"
|
||||||
|
#include "keys/PasswordKey.h"
|
||||||
#include "keys/CompositeKey.h"
|
#include "keys/CompositeKey.h"
|
||||||
|
|
||||||
class Entry;
|
class Entry;
|
||||||
enum class EntryReferenceType;
|
enum class EntryReferenceType;
|
||||||
class FileWatcher;
|
class FileWatcher;
|
||||||
@ -162,18 +165,39 @@ private:
|
|||||||
bool isReadOnly = false;
|
bool isReadOnly = false;
|
||||||
QUuid cipher = KeePass2::CIPHER_AES256;
|
QUuid cipher = KeePass2::CIPHER_AES256;
|
||||||
CompressionAlgorithm compressionAlgorithm = CompressionGZip;
|
CompressionAlgorithm compressionAlgorithm = CompressionGZip;
|
||||||
QByteArray transformedMasterKey;
|
|
||||||
QSharedPointer<Kdf> kdf = QSharedPointer<AesKdf>::create(true);
|
QScopedPointer<PasswordKey> masterSeed;
|
||||||
QSharedPointer<const CompositeKey> key;
|
QScopedPointer<PasswordKey> transformedMasterKey;
|
||||||
|
QScopedPointer<PasswordKey> challengeResponseKey;
|
||||||
|
|
||||||
bool hasKey = false;
|
bool hasKey = false;
|
||||||
QByteArray masterSeed;
|
QSharedPointer<const CompositeKey> key;
|
||||||
QByteArray challengeResponseKey;
|
QSharedPointer<Kdf> kdf = QSharedPointer<AesKdf>::create(true);
|
||||||
|
|
||||||
QVariantMap publicCustomData;
|
QVariantMap publicCustomData;
|
||||||
|
|
||||||
DatabaseData()
|
DatabaseData()
|
||||||
|
: masterSeed(new PasswordKey())
|
||||||
|
, transformedMasterKey(new PasswordKey())
|
||||||
|
, challengeResponseKey(new PasswordKey())
|
||||||
{
|
{
|
||||||
kdf->randomizeSeed();
|
kdf->randomizeSeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
filePath.clear();
|
||||||
|
|
||||||
|
masterSeed.reset();
|
||||||
|
transformedMasterKey.reset();
|
||||||
|
challengeResponseKey.reset();
|
||||||
|
|
||||||
|
hasKey = false;
|
||||||
|
key.reset();
|
||||||
|
kdf.reset();
|
||||||
|
|
||||||
|
publicCustomData.clear();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void createRecycleBin();
|
void createRecycleBin();
|
||||||
|
@ -48,19 +48,29 @@ PasswordKey::~PasswordKey()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
|
|
||||||
{
|
|
||||||
auto result = QSharedPointer<PasswordKey>::create();
|
|
||||||
std::memcpy(result->m_key, rawKey.data(), std::min(SHA256_SIZE, rawKey.size()));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray PasswordKey::rawKey() const
|
QByteArray PasswordKey::rawKey() const
|
||||||
{
|
{
|
||||||
|
if (!m_isInitialized) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return QByteArray::fromRawData(m_key, SHA256_SIZE);
|
return QByteArray::fromRawData(m_key, SHA256_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasswordKey::setPassword(const QString& password)
|
void PasswordKey::setPassword(const QString& password)
|
||||||
{
|
{
|
||||||
std::memcpy(m_key, CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256).data(), SHA256_SIZE);
|
setHash(CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PasswordKey::setHash(const QByteArray& hash)
|
||||||
|
{
|
||||||
|
Q_ASSERT(hash.size() == SHA256_SIZE);
|
||||||
|
std::memcpy(m_key, hash.data(), std::min(SHA256_SIZE, hash.size()));
|
||||||
|
m_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
|
||||||
|
{
|
||||||
|
auto result = QSharedPointer<PasswordKey>::create();
|
||||||
|
result->setHash(rawKey);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ public:
|
|||||||
~PasswordKey() override;
|
~PasswordKey() override;
|
||||||
QByteArray rawKey() const override;
|
QByteArray rawKey() const 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);
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ private:
|
|||||||
static constexpr int SHA256_SIZE = 32;
|
static constexpr int SHA256_SIZE = 32;
|
||||||
|
|
||||||
char* m_key = nullptr;
|
char* m_key = nullptr;
|
||||||
|
bool m_isInitialized = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_PASSWORDKEY_H
|
#endif // KEEPASSX_PASSWORDKEY_H
|
||||||
|
Loading…
Reference in New Issue
Block a user