mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-07-25 15:55:38 -04:00
Merge branch 'develop'
Conflicts: CMakeLists.txt cmake/CLangFormat.cmake snapcraft.yaml src/CMakeLists.txt src/core/Database.cpp src/core/Database.h src/core/Tools.cpp src/crypto/CryptoHash.h src/crypto/ssh/ASN1Key.h src/crypto/ssh/OpenSSHKey.cpp src/format/Kdbx4Reader.cpp src/gui/DatabaseTabWidget.cpp src/gui/DatabaseTabWidget.h src/gui/DatabaseWidget.cpp src/gui/DatabaseWidget.h src/gui/DetailsWidget.cpp src/gui/DetailsWidget.ui src/gui/EditWidgetProperties.cpp src/gui/EntryPreviewWidget.cpp src/gui/EntryPreviewWidget.ui src/gui/FileDialog.cpp src/gui/dbsettings/DatabaseSettingsDialog.cpp src/gui/dbsettings/DatabaseSettingsDialog.h src/gui/group/EditGroupWidget.cpp src/gui/group/EditGroupWidget.h src/sshagent/ASN1Key.h src/sshagent/OpenSSHKey.cpp src/sshagent/SSHAgent.cpp tests/CMakeLists.txt
This commit is contained in:
commit
9e2be34897
421 changed files with 18208 additions and 12907 deletions
|
@ -23,7 +23,7 @@
|
|||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
|
||||
bool CsvExporter::exportDatabase(const QString& filename, const Database* db)
|
||||
bool CsvExporter::exportDatabase(const QString& filename, QSharedPointer<const Database> db)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
|
@ -33,7 +33,7 @@ bool CsvExporter::exportDatabase(const QString& filename, const Database* db)
|
|||
return exportDatabase(&file, db);
|
||||
}
|
||||
|
||||
bool CsvExporter::exportDatabase(QIODevice* device, const Database* db)
|
||||
bool CsvExporter::exportDatabase(QIODevice* device, QSharedPointer<const Database> db)
|
||||
{
|
||||
QString header;
|
||||
addColumn(header, "Group");
|
||||
|
@ -64,7 +64,7 @@ bool CsvExporter::writeGroup(QIODevice* device, const Group* group, QString grou
|
|||
}
|
||||
groupPath.append(group->name());
|
||||
|
||||
const QList<Entry*> entryList = group->entries();
|
||||
const QList<Entry*>& entryList = group->entries();
|
||||
for (const Entry* entry : entryList) {
|
||||
QString line;
|
||||
|
||||
|
@ -83,7 +83,7 @@ bool CsvExporter::writeGroup(QIODevice* device, const Group* group, QString grou
|
|||
}
|
||||
}
|
||||
|
||||
const QList<Group*> children = group->children();
|
||||
const QList<Group*>& children = group->children();
|
||||
for (const Group* child : children) {
|
||||
if (!writeGroup(device, child, groupPath)) {
|
||||
return false;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#define KEEPASSX_CSVEXPORTER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
class Database;
|
||||
class Group;
|
||||
|
@ -28,8 +29,8 @@ class QIODevice;
|
|||
class CsvExporter
|
||||
{
|
||||
public:
|
||||
bool exportDatabase(const QString& filename, const Database* db);
|
||||
bool exportDatabase(QIODevice* device, const Database* db);
|
||||
bool exportDatabase(const QString& filename, QSharedPointer<const Database> db);
|
||||
bool exportDatabase(QIODevice* device, QSharedPointer<const Database> db);
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
|
|
|
@ -29,77 +29,76 @@
|
|||
|
||||
#include <QBuffer>
|
||||
|
||||
Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase)
|
||||
bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db)
|
||||
{
|
||||
Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3_1);
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if all required headers were present
|
||||
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_streamStartBytes.isEmpty()
|
||||
|| m_protectedStreamKey.isEmpty()
|
||||
|| m_db->cipher().isNull()) {
|
||||
|| m_protectedStreamKey.isEmpty() || db->cipher().isNull()) {
|
||||
raiseError(tr("missing database headers"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db->setKey(key, false)) {
|
||||
if (!db->setKey(key, false)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db->challengeMasterSeed(m_masterSeed)) {
|
||||
if (!db->challengeMasterSeed(m_masterSeed)) {
|
||||
raiseError(tr("Unable to issue challenge-response."));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->challengeResponseKey());
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
hash.addData(db->challengeResponseKey());
|
||||
hash.addData(db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher());
|
||||
SymmetricCipherStream cipherStream(
|
||||
device, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray realStart = cipherStream.read(32);
|
||||
|
||||
if (realStart != m_streamStartBytes) {
|
||||
raiseError(tr("Wrong key or database file is corrupt."));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
if (!hashedStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (m_db->compressionAlgo() == Database::CompressionNone) {
|
||||
if (db->compressionAlgorithm() == Database::CompressionNone) {
|
||||
xmlDevice = &hashedStream;
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
|
@ -107,28 +106,17 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
|||
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20);
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QBuffer buffer;
|
||||
if (saveXml()) {
|
||||
m_xmlData = xmlDevice->readAll();
|
||||
buffer.setBuffer(&m_xmlData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
xmlDevice = &buffer;
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(xmlDevice);
|
||||
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1);
|
||||
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
|
||||
xmlReader.readDatabase(xmlDevice, db, &randomStream);
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
raiseError(xmlReader.errorString());
|
||||
if (keepDatabase) {
|
||||
return m_db.take();
|
||||
}
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3_1);
|
||||
|
@ -137,15 +125,17 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
|||
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
|
||||
if (headerHash != xmlReader.headerHash()) {
|
||||
raiseError(tr("Header doesn't match hash"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return m_db.take();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream)
|
||||
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream, Database* db)
|
||||
{
|
||||
Q_UNUSED(db);
|
||||
|
||||
QByteArray fieldIDArray = headerStream.read(1);
|
||||
if (fieldIDArray.size() != 1) {
|
||||
raiseError(tr("Invalid header id size"));
|
||||
|
|
|
@ -29,13 +29,13 @@ class Kdbx3Reader : public KdbxReader
|
|||
Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader)
|
||||
|
||||
public:
|
||||
Database* readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase) override;
|
||||
bool readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db) override;
|
||||
|
||||
protected:
|
||||
bool readHeaderField(StoreDataStream& headerStream) override;
|
||||
bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBX3READER_H
|
||||
|
|
|
@ -66,9 +66,10 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
|
|||
writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_3_1);
|
||||
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags,
|
||||
Endian::sizedIntToBytes<qint32>(db->compressionAlgo(),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(
|
||||
writeHeaderField<quint16>(&header,
|
||||
KeePass2::HeaderFieldID::CompressionFlags,
|
||||
Endian::sizedIntToBytes<qint32>(db->compressionAlgorithm(), KeePass2::BYTEORDER)));
|
||||
auto kdf = db->kdf();
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::TransformSeed, kdf->seed()));
|
||||
|
@ -112,7 +113,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
|
|||
QIODevice* outputDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (db->compressionAlgo() == Database::CompressionNone) {
|
||||
if (db->compressionAlgorithm() == Database::CompressionNone) {
|
||||
outputDevice = &hashedStream;
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
|
|
|
@ -28,83 +28,83 @@
|
|||
#include "streams/QtIOCompressor"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase)
|
||||
bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db)
|
||||
{
|
||||
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
|
||||
|
||||
m_binaryPoolInverse.clear();
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if all required headers were present
|
||||
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_db->cipher().isNull()) {
|
||||
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || db->cipher().isNull()) {
|
||||
raiseError(tr("missing database headers"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db->setKey(key, false, false)) {
|
||||
if (!db->setKey(key, false, false)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
hash.addData(db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
QByteArray headerSha256 = device->read(32);
|
||||
QByteArray headerHmac = device->read(32);
|
||||
if (headerSha256.size() != 32 || headerHmac.size() != 32) {
|
||||
raiseError(tr("Invalid header checksum size"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
if (headerSha256 != CryptoHash::hash(headerData, CryptoHash::Sha256)) {
|
||||
raiseError(tr("Header SHA256 mismatch"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey());
|
||||
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, db->transformedMasterKey());
|
||||
if (headerHmac != CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
|
||||
raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
HmacBlockStream hmacStream(device, hmacKey);
|
||||
if (!hmacStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hmacStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher());
|
||||
if (cipher == SymmetricCipher::InvalidAlgorithm) {
|
||||
raiseError(tr("Unknown cipher"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
SymmetricCipherStream cipherStream(&hmacStream, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (m_db->compressionAlgo() == Database::CompressionNone) {
|
||||
if (db->compressionAlgorithm() == Database::CompressionNone) {
|
||||
xmlDevice = &cipherStream;
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(&cipherStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
|
@ -113,40 +113,29 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
|||
}
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream(m_irsAlgo);
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QBuffer buffer;
|
||||
if (saveXml()) {
|
||||
m_xmlData = xmlDevice->readAll();
|
||||
buffer.setBuffer(&m_xmlData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
xmlDevice = &buffer;
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(xmlDevice);
|
||||
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool());
|
||||
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
|
||||
xmlReader.readDatabase(xmlDevice, db, &randomStream);
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
raiseError(xmlReader.errorString());
|
||||
if (keepDatabase) {
|
||||
return m_db.take();
|
||||
}
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_db.take();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
|
||||
bool Kdbx4Reader::readHeaderField(StoreDataStream& device, Database* db)
|
||||
{
|
||||
QByteArray fieldIDArray = device.read(1);
|
||||
if (fieldIDArray.size() != 1) {
|
||||
|
@ -203,7 +192,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
|
|||
raiseError(tr("Unsupported key derivation function (KDF) or invalid parameters"));
|
||||
return false;
|
||||
}
|
||||
m_db->setKdf(kdf);
|
||||
db->setKdf(kdf);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -212,7 +201,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
|
|||
variantBuffer.setBuffer(&fieldData);
|
||||
variantBuffer.open(QBuffer::ReadOnly);
|
||||
QVariantMap data = readVariantMap(&variantBuffer);
|
||||
m_db->setPublicCustomData(data);
|
||||
db->setPublicCustomData(data);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,15 +30,15 @@ class Kdbx4Reader : public KdbxReader
|
|||
Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader)
|
||||
|
||||
public:
|
||||
Database* readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase) override;
|
||||
bool readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db) override;
|
||||
QHash<QByteArray, QString> binaryPoolInverse() const;
|
||||
QHash<QString, QByteArray> binaryPool() const;
|
||||
|
||||
protected:
|
||||
bool readHeaderField(StoreDataStream& headerStream) override;
|
||||
bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
|
||||
|
||||
private:
|
||||
bool readInnerHeaderField(QIODevice* device);
|
||||
|
|
|
@ -50,7 +50,6 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
|
|||
QByteArray masterSeed = randomGen()->randomArray(32);
|
||||
QByteArray encryptionIV = randomGen()->randomArray(ivSize);
|
||||
QByteArray protectedStreamKey = randomGen()->randomArray(64);
|
||||
QByteArray startBytes;
|
||||
QByteArray endOfHeader = "\r\n\r\n";
|
||||
|
||||
if (!db->setKey(db->key(), false, true)) {
|
||||
|
@ -73,10 +72,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
|
|||
|
||||
writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_4);
|
||||
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CompressionFlags,
|
||||
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgo()),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(
|
||||
writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(
|
||||
&header,
|
||||
KeePass2::HeaderFieldID::CompressionFlags,
|
||||
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgorithm()), KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
|
||||
|
||||
|
@ -138,7 +139,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
|
|||
QIODevice* outputDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (db->compressionAlgo() == Database::CompressionNone) {
|
||||
if (db->compressionAlgorithm() == Database::CompressionNone) {
|
||||
outputDevice = cipherStream.data();
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(cipherStream.data()));
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include <utility>
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
|
@ -18,6 +20,9 @@
|
|||
#include "KdbxReader.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Endian.h"
|
||||
#include "format/KdbxXmlWriter.h"
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#define UUID_LENGTH 16
|
||||
|
||||
|
@ -54,14 +59,14 @@ bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig
|
|||
*
|
||||
* @param device input device
|
||||
* @param key database encryption composite key
|
||||
* @param keepDatabase keep database in case of read failure
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
* @param db database to read into
|
||||
* @return true on success
|
||||
*/
|
||||
Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase)
|
||||
bool KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db)
|
||||
{
|
||||
device->seek(0);
|
||||
|
||||
m_db.reset(new Database());
|
||||
m_db = db;
|
||||
m_xmlData.clear();
|
||||
m_masterSeed.clear();
|
||||
m_encryptionIV.clear();
|
||||
|
@ -74,7 +79,7 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
|
|||
// read KDBX magic numbers
|
||||
quint32 sig1, sig2;
|
||||
if (!readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion)) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
m_kdbxSignature = qMakePair(sig1, sig2);
|
||||
|
||||
|
@ -82,17 +87,24 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
|
|||
m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
|
||||
// read header fields
|
||||
while (readHeaderField(headerStream) && !hasError()) {
|
||||
while (readHeaderField(headerStream, m_db) && !hasError()) {
|
||||
}
|
||||
|
||||
headerStream.close();
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// read payload
|
||||
return readDatabaseImpl(device, headerStream.storedData(), key, keepDatabase);
|
||||
bool ok = readDatabaseImpl(device, headerStream.storedData(), std::move(key), db);
|
||||
|
||||
if (saveXml()) {
|
||||
m_xmlData.clear();
|
||||
decryptXmlInnerStream(m_xmlData, db);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool KdbxReader::hasError() const
|
||||
|
@ -163,7 +175,7 @@ void KdbxReader::setCompressionFlags(const QByteArray& data)
|
|||
raiseError(tr("Unsupported compression algorithm"));
|
||||
return;
|
||||
}
|
||||
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
|
||||
m_db->setCompressionAlgorithm(static_cast<Database::CompressionAlgorithm>(id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,6 +270,23 @@ void KdbxReader::setInnerRandomStreamID(const QByteArray& data)
|
|||
m_irsAlgo = irsAlgo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt protected inner stream fields in XML dump on demand.
|
||||
* Without the stream key from the KDBX header, the values become worthless.
|
||||
*
|
||||
* @param xmlOutput XML dump with decrypted fields
|
||||
* @param db the database object for which to generate the decrypted XML dump
|
||||
*/
|
||||
void KdbxReader::decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const
|
||||
{
|
||||
QBuffer buffer;
|
||||
buffer.setBuffer(&xmlOutput);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
KdbxXmlWriter writer(m_kdbxVersion);
|
||||
writer.disableInnerStreamProtection(true);
|
||||
writer.writeDatabase(&buffer, db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise an error. Use in case of an unexpected read error.
|
||||
*
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
virtual ~KdbxReader() = default;
|
||||
|
||||
static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version);
|
||||
Database* readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase = false);
|
||||
bool readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db);
|
||||
|
||||
bool hasError() const;
|
||||
QString errorString() const;
|
||||
|
@ -57,22 +57,22 @@ protected:
|
|||
* @param device input device at the payload starting position
|
||||
* @param KDBX header data as bytes
|
||||
* @param key database encryption composite key
|
||||
* @param keepDatabase keep database in case of read failure
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
* @param db database to read into
|
||||
* @return true on success
|
||||
*/
|
||||
virtual Database*
|
||||
readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase) = 0;
|
||||
virtual bool readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db) = 0;
|
||||
|
||||
/**
|
||||
* Read next header field from stream.
|
||||
*
|
||||
* @param headerStream input header stream
|
||||
* @param database to read header field for
|
||||
* @return true if there are more header fields
|
||||
*/
|
||||
virtual bool readHeaderField(StoreDataStream& headerStream) = 0;
|
||||
virtual bool readHeaderField(StoreDataStream& headerStream, Database* db) = 0;
|
||||
|
||||
virtual void setCipher(const QByteArray& data);
|
||||
virtual void setCompressionFlags(const QByteArray& data);
|
||||
|
@ -86,9 +86,8 @@ protected:
|
|||
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
QScopedPointer<Database> m_db;
|
||||
void decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const;
|
||||
|
||||
QPair<quint32, quint32> m_kdbxSignature;
|
||||
quint32 m_kdbxVersion = 0;
|
||||
|
||||
QByteArray m_masterSeed;
|
||||
|
@ -100,6 +99,9 @@ protected:
|
|||
QByteArray m_xmlData;
|
||||
|
||||
private:
|
||||
QPair<quint32, quint32> m_kdbxSignature;
|
||||
QPointer<Database> m_db;
|
||||
|
||||
bool m_saveXml = false;
|
||||
bool m_error = false;
|
||||
QString m_errorStr = "";
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
#include <utility>
|
||||
|
||||
#define UUID_LENGTH 16
|
||||
|
||||
|
@ -43,9 +44,9 @@ KdbxXmlReader::KdbxXmlReader(quint32 version)
|
|||
* @param version KDBX version
|
||||
* @param binaryPool binary pool
|
||||
*/
|
||||
KdbxXmlReader::KdbxXmlReader(quint32 version, const QHash<QString, QByteArray>& binaryPool)
|
||||
KdbxXmlReader::KdbxXmlReader(quint32 version, QHash<QString, QByteArray> binaryPool)
|
||||
: m_kdbxVersion(version)
|
||||
, m_binaryPool(binaryPool)
|
||||
, m_binaryPool(std::move(binaryPool))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -55,7 +56,7 @@ KdbxXmlReader::KdbxXmlReader(quint32 version, const QHash<QString, QByteArray>&
|
|||
* @param device input file
|
||||
* @return pointer to the new database
|
||||
*/
|
||||
Database* KdbxXmlReader::readDatabase(const QString& filename)
|
||||
QSharedPointer<Database> KdbxXmlReader::readDatabase(const QString& filename)
|
||||
{
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
|
@ -68,10 +69,10 @@ Database* KdbxXmlReader::readDatabase(const QString& filename)
|
|||
* @param device input device
|
||||
* @return pointer to the new database
|
||||
*/
|
||||
Database* KdbxXmlReader::readDatabase(QIODevice* device)
|
||||
QSharedPointer<Database> KdbxXmlReader::readDatabase(QIODevice* device)
|
||||
{
|
||||
auto db = new Database();
|
||||
readDatabase(device, db);
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
readDatabase(device, db.data());
|
||||
return db;
|
||||
}
|
||||
|
||||
|
@ -82,7 +83,6 @@ Database* KdbxXmlReader::readDatabase(QIODevice* device)
|
|||
* @param db database to read into
|
||||
* @param randomStream random stream to use for decryption
|
||||
*/
|
||||
#include "QDebug"
|
||||
void KdbxXmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream)
|
||||
{
|
||||
m_error = false;
|
||||
|
@ -731,7 +731,7 @@ Entry* KdbxXmlReader::parseEntry(bool history)
|
|||
}
|
||||
if (m_xml.name() == "Binary") {
|
||||
QPair<QString, QString> ref = parseEntryBinary(entry);
|
||||
if (!ref.first.isNull() && !ref.second.isNull()) {
|
||||
if (!ref.first.isEmpty() && !ref.second.isEmpty()) {
|
||||
binaryRefs.append(ref);
|
||||
}
|
||||
continue;
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include "core/Database.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/TimeInfo.h"
|
||||
#include "core/Database.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QPair>
|
||||
|
@ -42,11 +41,11 @@ class KdbxXmlReader
|
|||
|
||||
public:
|
||||
explicit KdbxXmlReader(quint32 version);
|
||||
explicit KdbxXmlReader(quint32 version, const QHash<QString, QByteArray>& binaryPool);
|
||||
explicit KdbxXmlReader(quint32 version, QHash<QString, QByteArray> binaryPool);
|
||||
virtual ~KdbxXmlReader() = default;
|
||||
|
||||
virtual Database* readDatabase(const QString& filename);
|
||||
virtual Database* readDatabase(QIODevice* device);
|
||||
virtual QSharedPointer<Database> readDatabase(const QString& filename);
|
||||
virtual QSharedPointer<Database> readDatabase(QIODevice* device);
|
||||
virtual void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
|
||||
|
||||
bool hasError() const;
|
||||
|
|
|
@ -190,7 +190,7 @@ void KdbxXmlWriter::writeBinaries()
|
|||
m_xml.writeAttribute("ID", QString::number(i.value()));
|
||||
|
||||
QByteArray data;
|
||||
if (m_db->compressionAlgo() == Database::CompressionGZip) {
|
||||
if (m_db->compressionAlgorithm() == Database::CompressionGZip) {
|
||||
m_xml.writeAttribute("Compressed", "True");
|
||||
|
||||
QBuffer buffer;
|
||||
|
@ -356,12 +356,14 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
|
|||
for (const QString& key : attributesKeyList) {
|
||||
m_xml.writeStartElement("String");
|
||||
|
||||
// clang-format off
|
||||
bool protect =
|
||||
(((key == "Title") && m_meta->protectTitle()) || ((key == "UserName") && m_meta->protectUsername())
|
||||
|| ((key == "Password") && m_meta->protectPassword())
|
||||
|| ((key == "URL") && m_meta->protectUrl())
|
||||
|| ((key == "Notes") && m_meta->protectNotes())
|
||||
|| entry->attributes()->isProtected(key));
|
||||
|| ((key == "Password") && m_meta->protectPassword())
|
||||
|| ((key == "URL") && m_meta->protectUrl())
|
||||
|| ((key == "Notes") && m_meta->protectNotes())
|
||||
|| entry->attributes()->isProtected(key));
|
||||
// clang-format on
|
||||
|
||||
writeString("Key", key);
|
||||
|
||||
|
@ -369,7 +371,7 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
|
|||
QString value;
|
||||
|
||||
if (protect) {
|
||||
if (m_randomStream) {
|
||||
if (!m_innerStreamProtectionDisabled && m_randomStream) {
|
||||
m_xml.writeAttribute("Protected", "True");
|
||||
bool ok;
|
||||
QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok);
|
||||
|
@ -596,3 +598,24 @@ void KdbxXmlWriter::raiseError(const QString& errorMessage)
|
|||
m_error = true;
|
||||
m_errorStr = errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable inner stream protection and write protected fields
|
||||
* in plaintext instead. This is useful for plaintext XML exports
|
||||
* where the inner stream key is not available.
|
||||
*
|
||||
* @param disable true to disable protection
|
||||
*/
|
||||
void KdbxXmlWriter::disableInnerStreamProtection(bool disable)
|
||||
{
|
||||
m_innerStreamProtectionDisabled = disable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if inner stream protection is disabled and protected
|
||||
* fields will be saved in plaintext
|
||||
*/
|
||||
bool KdbxXmlWriter::innerStreamProtectionDisabled() const
|
||||
{
|
||||
return m_innerStreamProtectionDisabled;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ public:
|
|||
KeePass2RandomStream* randomStream = nullptr,
|
||||
const QByteArray& headerHash = QByteArray());
|
||||
void writeDatabase(const QString& filename, Database* db);
|
||||
void disableInnerStreamProtection(bool disable);
|
||||
bool innerStreamProtectionDisabled() const;
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
|
||||
|
@ -81,6 +83,8 @@ private:
|
|||
|
||||
const quint32 m_kdbxVersion;
|
||||
|
||||
bool m_innerStreamProtectionDisabled = false;
|
||||
|
||||
QXmlStreamWriter m_xml;
|
||||
QPointer<const Database> m_db;
|
||||
QPointer<const Metadata> m_meta;
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_KEEPASS1_H
|
||||
#define KEEPASSX_KEEPASS1_H
|
||||
|
@ -35,6 +35,6 @@ namespace KeePass1
|
|||
Rijndael = 2,
|
||||
Twofish = 8
|
||||
};
|
||||
}
|
||||
} // namespace KeePass1
|
||||
|
||||
#endif // KEEPASSX_KEEPASS1_H
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
class KeePass1Key : public CompositeKey
|
||||
{
|
||||
public:
|
||||
virtual QByteArray rawKey() const;
|
||||
QByteArray rawKey() const override;
|
||||
virtual void clear();
|
||||
void setPassword(const QByteArray& password);
|
||||
void setKeyfileData(const QByteArray& keyfileData);
|
||||
|
@ -48,8 +48,7 @@ private:
|
|||
};
|
||||
|
||||
KeePass1Reader::KeePass1Reader()
|
||||
: m_db(nullptr)
|
||||
, m_tmpParent(nullptr)
|
||||
: m_tmpParent(nullptr)
|
||||
, m_device(nullptr)
|
||||
, m_encryptionFlags(0)
|
||||
, m_transformRounds(0)
|
||||
|
@ -57,7 +56,7 @@ KeePass1Reader::KeePass1Reader()
|
|||
{
|
||||
}
|
||||
|
||||
Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice)
|
||||
QSharedPointer<Database> KeePass1Reader::readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice)
|
||||
{
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
|
@ -70,22 +69,22 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||
|
||||
if (keyfileData.isEmpty()) {
|
||||
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
if (!keyfileDevice->seek(0)) {
|
||||
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!newFileKey->load(keyfileDevice)) {
|
||||
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QScopedPointer<Database> db(new Database());
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
QScopedPointer<Group> tmpParent(new Group());
|
||||
m_db = db.data();
|
||||
m_db = db;
|
||||
m_tmpParent = tmpParent.data();
|
||||
m_device = device;
|
||||
|
||||
|
@ -94,19 +93,19 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||
auto signature1 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok || signature1 != KeePass1::SIGNATURE_1) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto signature2 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok || signature2 != KeePass1::SIGNATURE_2) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
m_encryptionFlags = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok || !(m_encryptionFlags & KeePass1::Rijndael || m_encryptionFlags & KeePass1::Twofish)) {
|
||||
raiseError(tr("Unsupported encryption algorithm."));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto version = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
|
@ -114,49 +113,49 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||
|| (version & KeePass1::FILE_VERSION_CRITICAL_MASK)
|
||||
!= (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) {
|
||||
raiseError(tr("Unsupported KeePass database version."));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
m_masterSeed = m_device->read(16);
|
||||
if (m_masterSeed.size() != 16) {
|
||||
raiseError("Unable to read master seed");
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
m_encryptionIV = m_device->read(16);
|
||||
if (m_encryptionIV.size() != 16) {
|
||||
raiseError(tr("Unable to read encryption IV", "IV = Initialization Vector for symmetric cipher"));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto numGroups = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError(tr("Invalid number of groups"));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto numEntries = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError(tr("Invalid number of entries"));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
m_contentHashHeader = m_device->read(32);
|
||||
if (m_contentHashHeader.size() != 32) {
|
||||
raiseError(tr("Invalid content hash size"));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
m_transformSeed = m_device->read(32);
|
||||
if (m_transformSeed.size() != 32) {
|
||||
raiseError(tr("Invalid transform seed size"));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
m_transformRounds = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError(tr("Invalid number of transform rounds"));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
auto kdf = QSharedPointer<AesKdf>::create(true);
|
||||
kdf->setRounds(m_transformRounds);
|
||||
|
@ -168,14 +167,14 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||
QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
|
||||
|
||||
if (!cipherStream) {
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<Group*> groups;
|
||||
for (quint32 i = 0; i < numGroups; i++) {
|
||||
Group* group = readGroup(cipherStream.data());
|
||||
if (!group) {
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
groups.append(group);
|
||||
}
|
||||
|
@ -184,14 +183,14 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||
for (quint32 i = 0; i < numEntries; i++) {
|
||||
Entry* entry = readEntry(cipherStream.data());
|
||||
if (!entry) {
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
entries.append(entry);
|
||||
}
|
||||
|
||||
if (!constructGroupTree(groups)) {
|
||||
raiseError(tr("Unable to construct group tree"));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
for (Entry* entry : asConst(entries)) {
|
||||
|
@ -243,41 +242,39 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||
|
||||
if (!db->setKey(key)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
return db.take();
|
||||
return db;
|
||||
}
|
||||
|
||||
Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& password, const QString& keyfileName)
|
||||
QSharedPointer<Database> KeePass1Reader::readDatabase(QIODevice* device, const QString& password, const QString& keyfileName)
|
||||
{
|
||||
QScopedPointer<QFile> keyFile;
|
||||
if (!keyfileName.isEmpty()) {
|
||||
keyFile.reset(new QFile(keyfileName));
|
||||
if (!keyFile->open(QFile::ReadOnly)) {
|
||||
raiseError(keyFile->errorString());
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QScopedPointer<Database> db(readDatabase(device, password, keyFile.data()));
|
||||
|
||||
return db.take();
|
||||
return QSharedPointer<Database>(readDatabase(device, password, keyFile.data()));
|
||||
}
|
||||
|
||||
Database* KeePass1Reader::readDatabase(const QString& filename, const QString& password, const QString& keyfileName)
|
||||
QSharedPointer<Database> KeePass1Reader::readDatabase(const QString& filename, const QString& password, const QString& keyfileName)
|
||||
{
|
||||
QFile dbFile(filename);
|
||||
if (!dbFile.open(QFile::ReadOnly)) {
|
||||
raiseError(dbFile.errorString());
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
Database* db = readDatabase(&dbFile, password, keyfileName);
|
||||
auto db = readDatabase(&dbFile, password, keyfileName);
|
||||
|
||||
if (dbFile.error() != QFile::NoError) {
|
||||
raiseError(dbFile.errorString());
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
return db;
|
||||
|
@ -293,8 +290,7 @@ QString KeePass1Reader::errorString()
|
|||
return m_errorStr;
|
||||
}
|
||||
|
||||
SymmetricCipherStream*
|
||||
KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, qint64 contentPos)
|
||||
SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, qint64 contentPos)
|
||||
{
|
||||
const QList<PasswordEncoding> encodings = {Windows1252, Latin1, UTF8};
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class Database;
|
||||
class Entry;
|
||||
|
@ -34,9 +35,9 @@ class KeePass1Reader
|
|||
|
||||
public:
|
||||
KeePass1Reader();
|
||||
Database* readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice);
|
||||
Database* readDatabase(QIODevice* device, const QString& password, const QString& keyfileName);
|
||||
Database* readDatabase(const QString& filename, const QString& password, const QString& keyfileName);
|
||||
QSharedPointer<Database> readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice);
|
||||
QSharedPointer<Database> readDatabase(QIODevice* device, const QString& password, const QString& keyfileName);
|
||||
QSharedPointer<Database> readDatabase(const QString& filename, const QString& password, const QString& keyfileName);
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
|
||||
|
@ -63,7 +64,7 @@ private:
|
|||
static QDateTime dateFromPackedStruct(const QByteArray& data);
|
||||
static bool isMetaStream(const Entry* entry);
|
||||
|
||||
Database* m_db;
|
||||
QSharedPointer<Database> m_db;
|
||||
Group* m_tmpParent;
|
||||
QIODevice* m_device;
|
||||
quint32 m_encryptionFlags;
|
||||
|
|
|
@ -23,13 +23,14 @@
|
|||
|
||||
#define UUID_LENGTH 16
|
||||
|
||||
const QUuid KeePass2::CIPHER_AES = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff");
|
||||
const QUuid KeePass2::CIPHER_TWOFISH = QUuid("ad68f29f-576f-4bb9-a36a-d47af965346c");
|
||||
const QUuid KeePass2::CIPHER_AES128 = QUuid("61ab05a1-9464-41c3-8d74-3a563df8dd35");
|
||||
const QUuid KeePass2::CIPHER_AES256 = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff");
|
||||
const QUuid KeePass2::CIPHER_TWOFISH = QUuid("ad68f29f-576f-4bb9-a36a-d47af965346c");
|
||||
const QUuid KeePass2::CIPHER_CHACHA20 = QUuid("d6038a2b-8b6f-4cb5-a524-339a31dbb59a");
|
||||
|
||||
const QUuid KeePass2::KDF_AES_KDBX3 = QUuid("c9d9f39a-628a-4460-bf74-0d08c18a4fea");
|
||||
const QUuid KeePass2::KDF_AES_KDBX4 = QUuid("7c02bb82-79a7-4ac0-927d-114a00648238");
|
||||
const QUuid KeePass2::KDF_ARGON2 = QUuid("ef636ddf-8c29-444b-91f7-a9a403e30a0c");
|
||||
const QUuid KeePass2::KDF_AES_KDBX3 = QUuid("c9d9f39a-628a-4460-bf74-0d08c18a4fea");
|
||||
const QUuid KeePass2::KDF_AES_KDBX4 = QUuid("7c02bb82-79a7-4ac0-927d-114a00648238");
|
||||
const QUuid KeePass2::KDF_ARGON2 = QUuid("ef636ddf-8c29-444b-91f7-a9a403e30a0c");
|
||||
|
||||
const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xe8\x30\x09\x4b\x97\x20\x5d\x2a");
|
||||
|
||||
|
@ -47,18 +48,16 @@ const QString KeePass2::KDFPARAM_ARGON2_SECRET("K");
|
|||
const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A");
|
||||
|
||||
const QList<QPair<QUuid, QString>> KeePass2::CIPHERS{
|
||||
qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")),
|
||||
qMakePair(KeePass2::CIPHER_AES256, QObject::tr("AES: 256-bit")),
|
||||
qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")),
|
||||
qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit"))
|
||||
};
|
||||
qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit"))};
|
||||
|
||||
const QList<QPair<QUuid, QString>> KeePass2::KDFS{
|
||||
qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2 (KDBX 4 – recommended)")),
|
||||
qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (KDBX 4)")),
|
||||
qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)"))
|
||||
};
|
||||
qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)"))};
|
||||
|
||||
QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey)
|
||||
QByteArray KeePass2::hmacKey(const QByteArray& masterSeed, const QByteArray& transformedMasterKey)
|
||||
{
|
||||
CryptoHash hmacKeyHash(CryptoHash::Sha512);
|
||||
hmacKeyHash.addData(masterSeed);
|
||||
|
@ -97,7 +96,7 @@ QSharedPointer<Kdf> KeePass2::kdfFromParameters(const QVariantMap& p)
|
|||
return kdf;
|
||||
}
|
||||
|
||||
QVariantMap KeePass2::kdfToParameters(QSharedPointer<Kdf> kdf)
|
||||
QVariantMap KeePass2::kdfToParameters(const QSharedPointer<Kdf>& kdf)
|
||||
{
|
||||
return kdf->writeParameters();
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QSharedPointer>
|
||||
#include <QUuid>
|
||||
#include <QVariantMap>
|
||||
#include <QtGlobal>
|
||||
#include <QUuid>
|
||||
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
#include "crypto/kdf/Kdf.h"
|
||||
|
@ -46,13 +46,14 @@ namespace KeePass2
|
|||
|
||||
const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
|
||||
|
||||
extern const QUuid CIPHER_AES;
|
||||
extern const QUuid CIPHER_TWOFISH;
|
||||
extern const QUuid CIPHER_CHACHA20;
|
||||
extern const QUuid CIPHER_AES128;
|
||||
extern const QUuid CIPHER_AES256;
|
||||
extern const QUuid CIPHER_TWOFISH;
|
||||
extern const QUuid CIPHER_CHACHA20;
|
||||
|
||||
extern const QUuid KDF_AES_KDBX3;
|
||||
extern const QUuid KDF_AES_KDBX4;
|
||||
extern const QUuid KDF_ARGON2;
|
||||
extern const QUuid KDF_AES_KDBX3;
|
||||
extern const QUuid KDF_AES_KDBX4;
|
||||
extern const QUuid KDF_ARGON2;
|
||||
|
||||
extern const QByteArray INNER_STREAM_SALSA20_IV;
|
||||
|
||||
|
@ -67,8 +68,8 @@ extern const QUuid KDF_ARGON2;
|
|||
extern const QString KDFPARAM_ARGON2_SECRET;
|
||||
extern const QString KDFPARAM_ARGON2_ASSOCDATA;
|
||||
|
||||
extern const QList<QPair<QUuid, QString>> CIPHERS;
|
||||
extern const QList<QPair<QUuid, QString>> KDFS;
|
||||
extern const QList<QPair<QUuid, QString>> CIPHERS;
|
||||
extern const QList<QPair<QUuid, QString>> KDFS;
|
||||
|
||||
enum class HeaderFieldID
|
||||
{
|
||||
|
@ -125,11 +126,11 @@ extern const QList<QPair<QUuid, QString>> KDFS;
|
|||
ByteArray = 0x42
|
||||
};
|
||||
|
||||
QByteArray hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey);
|
||||
QSharedPointer<Kdf> kdfFromParameters(const QVariantMap& p);
|
||||
QVariantMap kdfToParameters(QSharedPointer<Kdf> kdf);
|
||||
QSharedPointer<Kdf> uuidToKdf(const QUuid& uuid);
|
||||
ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id);
|
||||
QByteArray hmacKey(const QByteArray& masterSeed, const QByteArray& transformedMasterKey);
|
||||
QSharedPointer<Kdf> kdfFromParameters(const QVariantMap& p);
|
||||
QVariantMap kdfToParameters(const QSharedPointer<Kdf>& kdf);
|
||||
QSharedPointer<Kdf> uuidToKdf(const QUuid& uuid);
|
||||
ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id);
|
||||
|
||||
} // namespace KeePass2
|
||||
|
||||
|
|
|
@ -27,24 +27,25 @@
|
|||
*
|
||||
* @param filename input file
|
||||
* @param key database encryption composite key
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
* @param db Database to read into
|
||||
* @return true on success
|
||||
*/
|
||||
Database* KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key)
|
||||
bool KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key, Database* db)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
raiseError(file.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QScopedPointer<Database> db(readDatabase(&file, key));
|
||||
bool ok = readDatabase(&file, std::move(key), db);
|
||||
|
||||
if (file.error() != QFile::NoError) {
|
||||
raiseError(file.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return db.take();
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,10 +53,10 @@ Database* KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<c
|
|||
*
|
||||
* @param device input device
|
||||
* @param key database encryption composite key
|
||||
* @param keepDatabase keep database in case of read failure
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
* @param db Database to read into
|
||||
* @return true on success
|
||||
*/
|
||||
Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase)
|
||||
bool KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db)
|
||||
{
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
|
@ -63,26 +64,29 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
|
|||
quint32 signature1, signature2;
|
||||
bool ok = KdbxReader::readMagicNumbers(device, signature1, signature2, m_version);
|
||||
|
||||
// mask out minor version
|
||||
m_version &= KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
|
||||
if (!ok || signature1 != KeePass2::SIGNATURE_1 || signature2 != KeePass2::SIGNATURE_2) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return nullptr;
|
||||
if (!ok) {
|
||||
raiseError(tr("Failed to read database file."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signature2 == KeePass1::SIGNATURE_2) {
|
||||
if (signature1 == KeePass1::SIGNATURE_1 && signature2 == KeePass1::SIGNATURE_2) {
|
||||
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
|
||||
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
|
||||
"This is a one-way migration. You won't be able to open the imported "
|
||||
"database with the old KeePassX 0.4 version."));
|
||||
return nullptr;
|
||||
return false;
|
||||
} else if (!(signature1 == KeePass2::SIGNATURE_1 && signature2 == KeePass2::SIGNATURE_2)) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// mask out minor version
|
||||
m_version &= KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
|
||||
quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) {
|
||||
raiseError(tr("Unsupported KeePass 2 database version."));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// determine file format (KDBX 2/3 or 4)
|
||||
|
@ -93,7 +97,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
|
|||
}
|
||||
|
||||
m_reader->setSaveXml(m_saveXml);
|
||||
return m_reader->readDatabase(device, key, keepDatabase);
|
||||
return m_reader->readDatabase(device, std::move(key), db);
|
||||
}
|
||||
|
||||
bool KeePass2Reader::hasError() const
|
||||
|
|
|
@ -35,8 +35,8 @@ class KeePass2Reader
|
|||
Q_DECLARE_TR_FUNCTIONS(KdbxReader)
|
||||
|
||||
public:
|
||||
Database* readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key);
|
||||
Database* readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase = false);
|
||||
bool readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key, Database* db);
|
||||
bool readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db);
|
||||
|
||||
bool hasError() const;
|
||||
QString errorString() const;
|
||||
|
|
|
@ -48,6 +48,10 @@ bool KeePass2Writer::writeDatabase(const QString& filename, Database* db)
|
|||
*/
|
||||
bool KeePass2Writer::implicitUpgradeNeeded(Database const* db) const
|
||||
{
|
||||
if (db->kdf()->uuid() != KeePass2::KDF_AES_KDBX3 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!db->publicCustomData().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue