From cd0084f21cc4eb7c0f2b00bdafb0eeba051fd927 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 10 Dec 2020 01:28:01 +0100 Subject: [PATCH] Add support for version 2 XML key files. As discussed in #4317, the next KeePass2 release will ship with support for a new generation of XML key files which enable hash integrity checks. This patch adds support for reading and generating this new format. By default, KeePass2 now uses the .keyx extension for generated key files, which was added to KeePassXC's key generation file chooser filter. We continue to generate hashed binary key files by default, but the user can explicitly save the file with the new .keyx extension to generate an XML v2 key file (currently undocumented). When opening a database, the key file type is still determined by content negotation, so the file extension has no impact here. As an additional change, the legacy key file warnings have been improved slightly to be less confusing and more helpful. --- src/cli/Utils.cpp | 6 +- src/gui/DatabaseOpenWidget.cpp | 14 +- src/gui/databasekey/KeyFileEditWidget.cpp | 14 +- src/keys/FileKey.cpp | 219 ++++++++++++++-------- src/keys/FileKey.h | 12 +- tests/TestKeys.cpp | 50 +++-- tests/data/FileKeyXmlV2.kdbx | Bin 0 -> 1582 bytes tests/data/FileKeyXmlV2.keyx | 12 ++ tests/data/FileKeyXmlV2BrokenHex.kdbx | Bin 0 -> 1550 bytes tests/data/FileKeyXmlV2BrokenHex.keyx | 12 ++ tests/data/FileKeyXmlV2HashFail.kdbx | Bin 0 -> 1582 bytes tests/data/FileKeyXmlV2HashFail.keyx | 12 ++ 12 files changed, 233 insertions(+), 118 deletions(-) create mode 100644 tests/data/FileKeyXmlV2.kdbx create mode 100644 tests/data/FileKeyXmlV2.keyx create mode 100644 tests/data/FileKeyXmlV2BrokenHex.kdbx create mode 100644 tests/data/FileKeyXmlV2BrokenHex.keyx create mode 100644 tests/data/FileKeyXmlV2HashFail.kdbx create mode 100644 tests/data/FileKeyXmlV2HashFail.keyx diff --git a/src/cli/Utils.cpp b/src/cli/Utils.cpp index bd3f3e2c..ca440a39 100644 --- a/src/cli/Utils.cpp +++ b/src/cli/Utils.cpp @@ -132,9 +132,9 @@ namespace Utils return {}; } - if (fileKey->type() != FileKey::Hashed) { - err << QObject::tr("WARNING: You are using a legacy key file format which may become\n" - "unsupported in the future.\n\n" + if (fileKey->type() != FileKey::KeePass2XMLv2 && fileKey->type() != FileKey::Hashed) { + err << QObject::tr("WARNING: You are using an old key file format which KeePassXC may\n" + "stop supporting in the future.\n\n" "Please consider generating a new key file.") << endl; } diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index b9f77823..78902523 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -301,12 +301,14 @@ QSharedPointer DatabaseOpenWidget::buildDatabaseKey() m_ui->messageWidget->showMessage(tr("Failed to open key file: %1").arg(errorMsg), MessageWidget::Error); return {}; } - if (key->type() != FileKey::Hashed && !config()->get(Config::Messages_NoLegacyKeyFileWarning).toBool()) { + if (key->type() != FileKey::KeePass2XMLv2 && key->type() != FileKey::Hashed + && !config()->get(Config::Messages_NoLegacyKeyFileWarning).toBool()) { QMessageBox legacyWarning; - legacyWarning.setWindowTitle(tr("Legacy key file format")); - legacyWarning.setText(tr("You are using a legacy key file format which may become\n" - "unsupported in the future.\n\n" - "Please consider generating a new key file.")); + legacyWarning.setWindowTitle(tr("Old key file format")); + legacyWarning.setText(tr("You are using an old key file format which KeePassXC may
" + "stop supporting in the future.

" + "Please consider generating a new key file by going to:
" + "Database / Database Security / Change Key File.
")); legacyWarning.setIcon(QMessageBox::Icon::Warning); legacyWarning.addButton(QMessageBox::Ok); legacyWarning.setDefaultButton(QMessageBox::Ok); @@ -355,7 +357,7 @@ void DatabaseOpenWidget::reject() void DatabaseOpenWidget::browseKeyFile() { - QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files")); + QString filters = QString("%1 (*);;%2 (*.keyx; *.key)").arg(tr("All files"), tr("Key files")); if (!config()->get(Config::RememberLastKeyFiles).toBool()) { fileDialog()->setNextForgetDialog(); } diff --git a/src/gui/databasekey/KeyFileEditWidget.cpp b/src/gui/databasekey/KeyFileEditWidget.cpp index e0486ae0..2fb0b3de 100644 --- a/src/gui/databasekey/KeyFileEditWidget.cpp +++ b/src/gui/databasekey/KeyFileEditWidget.cpp @@ -47,12 +47,12 @@ bool KeyFileEditWidget::addToCompositeKey(QSharedPointer key) return false; } - if (fileKey->type() != FileKey::Hashed) { + if (fileKey->type() != FileKey::KeePass2XMLv2 && fileKey->type() != FileKey::Hashed) { QMessageBox::warning(getMainWindow(), - tr("Legacy key file format"), - tr("You are using a legacy key file format which may become\n" - "unsupported in the future.\n\n" - "Generate a new key file in the database security settings."), + tr("Old key file format"), + tr("You selected a key file in an old format which KeePassXC
" + "may stop supporting in the future.

" + "Please consider generating a new key file instead."), QMessageBox::Ok); } @@ -96,7 +96,7 @@ void KeyFileEditWidget::createKeyFile() if (!m_compEditWidget) { return; } - QString filters = QString("%1 (*.key);;%2 (*)").arg(tr("Key files"), tr("All files")); + QString filters = QString("%1 (*.keyx; *.key);;%2 (*)").arg(tr("Key files"), tr("All files")); QString fileName = fileDialog()->getSaveFileName(this, tr("Create Key File..."), QString(), filters); if (!fileName.isEmpty()) { @@ -119,7 +119,7 @@ void KeyFileEditWidget::browseKeyFile() if (!m_compEditWidget) { return; } - QString filters = QString("%1 (*.key);;%2 (*)").arg(tr("Key files"), tr("All files")); + QString filters = QString("%1 (*.keyx; *.key);;%2 (*)").arg(tr("Key files"), tr("All files")); QString fileName = fileDialog()->getOpenFileName(this, tr("Select a key file"), QString(), filters); if (QFileInfo(fileName).canonicalFilePath() == m_parent->getDatabase()->canonicalFilePath()) { diff --git a/src/keys/FileKey.cpp b/src/keys/FileKey.cpp index 6142d58d..2ac52ae6 100644 --- a/src/keys/FileKey.cpp +++ b/src/keys/FileKey.cpp @@ -64,9 +64,10 @@ FileKey::~FileKey() * removed in a future version. * * @param device input device + * @param errorMsg error message in case of fatal failure * @return true if key file was loaded successfully */ -bool FileKey::load(QIODevice* device) +bool FileKey::load(QIODevice* device, QString* errorMsg) { m_type = None; @@ -75,32 +76,33 @@ bool FileKey::load(QIODevice* device) return false; } - if (device->size() == 0) { + if (device->size() == 0 || !device->reset()) { return false; } - // try different legacy key file formats - if (!device->reset()) { - return false; - } - if (loadXml(device)) { - m_type = KeePass2XML; + // load XML key file v1 or v2 + QString xmlError; + if (loadXml(device, &xmlError)) { return true; } - if (!device->reset()) { + if (!device->reset() || !xmlError.isEmpty()) { + if (errorMsg) { + *errorMsg = xmlError; + } return false; } + + // try legacy key file formats if (loadBinary(device)) { - m_type = FixedBinary; return true; } if (!device->reset()) { return false; } + if (loadHex(device)) { - m_type = FixedBinaryHex; return true; } @@ -109,7 +111,6 @@ bool FileKey::load(QIODevice* device) return false; } if (loadHashed(device)) { - m_type = Hashed; return true; } @@ -145,10 +146,14 @@ bool FileKey::load(const QString& fileName, QString* errorMsg) } return false; } - bool result = load(&file); + bool result = load(&file, errorMsg); file.close(); + if (errorMsg && !errorMsg->isEmpty()) { + return false; + } + if (file.error()) { result = false; if (errorMsg) { @@ -171,16 +176,64 @@ QByteArray FileKey::rawKey() const } /** - * Generate a new key file from random bytes. + * Generate a new key file with random bytes. * * @param device output device * @param number of random bytes to generate */ -void FileKey::create(QIODevice* device, int size) +void FileKey::createRandom(QIODevice* device, int size) { device->write(randomGen()->randomArray(size)); } +/** + * Generate a new key file in the KeePass2 XML format v2. + * + * @param device output device + * @param number of random bytes to generate + */ +void FileKey::createXMLv2(QIODevice* device, int size) +{ + QXmlStreamWriter w(device); + w.setAutoFormatting(true); + w.setAutoFormattingIndent(4); + w.writeStartDocument(); + + w.writeStartElement("KeyFile"); + + w.writeStartElement("Meta"); + w.writeTextElement("Version", "2.0"); + w.writeEndElement(); + + w.writeStartElement("Key"); + w.writeStartElement("Data"); + + QByteArray key = randomGen()->randomArray(size); + CryptoHash hash(CryptoHash::Sha256); + hash.addData(key); + QByteArray result = hash.result().left(4); + key = key.toHex().toUpper(); + + w.writeAttribute("Hash", result.toHex().toUpper()); + w.writeCharacters("\n "); + for (int i = 0; i < key.size(); ++i) { + // Pretty-print hex value (not strictly necessary, but nicer to read and KeePass2 does it) + if (i != 0 && i % 32 == 0) { + w.writeCharacters("\n "); + } else if (i != 0 && i % 8 == 0) { + w.writeCharacters(" "); + } + w.writeCharacters(QChar(key[i])); + } + sodium_memzero(key.data(), static_cast(key.capacity())); + w.writeCharacters("\n "); + + w.writeEndElement(); + w.writeEndElement(); + + w.writeEndDocument(); +} + /** * Create a new key file from random bytes. * @@ -189,7 +242,7 @@ void FileKey::create(QIODevice* device, int size) * @param number of random bytes to generate * @return true on successful creation */ -bool FileKey::create(const QString& fileName, QString* errorMsg, int size) +bool FileKey::create(const QString& fileName, QString* errorMsg) { QFile file(fileName); if (!file.open(QFile::WriteOnly)) { @@ -198,7 +251,11 @@ bool FileKey::create(const QString& fileName, QString* errorMsg, int size) } return false; } - create(&file, size); + if (fileName.endsWith(".keyx")) { + createXMLv2(&file); + } else { + createRandom(&file); + } file.close(); file.setPermissions(QFile::ReadUser); @@ -218,87 +275,86 @@ bool FileKey::create(const QString& fileName, QString* errorMsg, int size) * * @param device input device * @return true on success - * @deprecated */ -bool FileKey::loadXml(QIODevice* device) +bool FileKey::loadXml(QIODevice* device, QString* errorMsg) { QXmlStreamReader xmlReader(device); - if (!xmlReader.error() && xmlReader.readNextStartElement()) { - if (xmlReader.name() != "KeyFile") { - return false; - } - } else { + if (xmlReader.error()) { + return false; + } + if (xmlReader.readNextStartElement() && xmlReader.name() != "KeyFile") { return false; } - bool correctMeta = false; - QByteArray data; + struct + { + QString version; + QByteArray hash; + QByteArray data; + } keyFileData; while (!xmlReader.error() && xmlReader.readNextStartElement()) { if (xmlReader.name() == "Meta") { - correctMeta = loadXmlMeta(xmlReader); + while (!xmlReader.error() && xmlReader.readNextStartElement()) { + if (xmlReader.name() == "Version") { + keyFileData.version = xmlReader.readElementText(); + if (keyFileData.version.startsWith("1.0")) { + m_type = KeePass2XML; + } else if (keyFileData.version == "2.0") { + m_type = KeePass2XMLv2; + } else { + if (errorMsg) { + *errorMsg = QObject::tr("Unsupported key file version: %1").arg(keyFileData.version); + } + return false; + } + } + } } else if (xmlReader.name() == "Key") { - data = loadXmlKey(xmlReader); + while (!xmlReader.error() && xmlReader.readNextStartElement()) { + if (xmlReader.name() == "Data") { + keyFileData.hash = QByteArray::fromHex(xmlReader.attributes().value("Hash").toLatin1()); + QByteArray rawData = xmlReader.readElementText().simplified().replace(" ", "").toLatin1(); + + if (keyFileData.version.startsWith("1.0") && Tools::isBase64(rawData)) { + keyFileData.data = QByteArray::fromBase64(rawData); + } else if (keyFileData.version == "2.0" && Tools::isHex(rawData)) { + keyFileData.data = QByteArray::fromHex(rawData); + + CryptoHash hash(CryptoHash::Sha256); + hash.addData(keyFileData.data); + QByteArray result = hash.result().left(4); + if (keyFileData.hash != result) { + if (errorMsg) { + *errorMsg = QObject::tr("Checksum mismatch! Key file may be corrupt."); + } + return false; + } + } else { + if (errorMsg) { + *errorMsg = QObject::tr("Unexpected key file data! Key file may be corrupt."); + } + return false; + } + + sodium_memzero(rawData.data(), static_cast(rawData.capacity())); + } + } } } bool ok = false; - if (!xmlReader.error() && correctMeta && !data.isEmpty()) { - std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size())); + if (!xmlReader.error() && !keyFileData.data.isEmpty()) { + std::memcpy(m_key, keyFileData.data.data(), std::min(SHA256_SIZE, keyFileData.data.size())); ok = true; } - sodium_memzero(data.data(), static_cast(data.capacity())); + sodium_memzero(keyFileData.data.data(), static_cast(keyFileData.data.capacity())); return ok; } -/** - * Load meta data from legacy KeePass 2 XML key file. - * - * @param xmlReader input XML reader - * @return true on success - * @deprecated - */ -bool FileKey::loadXmlMeta(QXmlStreamReader& xmlReader) -{ - bool correctVersion = false; - - while (!xmlReader.error() && xmlReader.readNextStartElement()) { - if (xmlReader.name() == "Version") { - if (xmlReader.readElementText() == "1.00") { - correctVersion = true; - } - } - } - - return correctVersion; -} - -/** - * Load base64 key data from legacy KeePass 2 XML key file. - * - * @param xmlReader input XML reader - * @return true on success - * @deprecated - */ -QByteArray FileKey::loadXmlKey(QXmlStreamReader& xmlReader) -{ - QByteArray data; - - while (!xmlReader.error() && xmlReader.readNextStartElement()) { - if (xmlReader.name() == "Data") { - QByteArray rawData = xmlReader.readElementText().toLatin1(); - if (Tools::isBase64(rawData)) { - data = QByteArray::fromBase64(rawData); - } - } - } - - return data; -} - /** * Load fixed 32-bit binary key file. * @@ -315,11 +371,12 @@ bool FileKey::loadBinary(QIODevice* device) QByteArray data; if (!Tools::readAllFromDevice(device, data) || data.size() != 32) { return false; - } else { - std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size())); - sodium_memzero(data.data(), static_cast(data.capacity())); - return true; } + + std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size())); + sodium_memzero(data.data(), static_cast(data.capacity())); + m_type = FixedBinary; + return true; } /** @@ -354,6 +411,7 @@ bool FileKey::loadHex(QIODevice* device) std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size())); sodium_memzero(key.data(), static_cast(key.capacity())); + m_type = FixedBinaryHex; return true; } @@ -379,6 +437,7 @@ bool FileKey::loadHashed(QIODevice* device) std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size())); sodium_memzero(result.data(), static_cast(result.capacity())); + m_type = Hashed; return true; } diff --git a/src/keys/FileKey.h b/src/keys/FileKey.h index 290a04af..540dde7d 100644 --- a/src/keys/FileKey.h +++ b/src/keys/FileKey.h @@ -35,25 +35,25 @@ public: None, Hashed, KeePass2XML, + KeePass2XMLv2, FixedBinary, FixedBinaryHex }; FileKey(); ~FileKey() override; - bool load(QIODevice* device); + bool load(QIODevice* device, QString* errorMsg = nullptr); bool load(const QString& fileName, QString* errorMsg = nullptr); QByteArray rawKey() const override; Type type() const; - static void create(QIODevice* device, int size = 128); - static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128); + 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); private: static constexpr int SHA256_SIZE = 32; - bool loadXml(QIODevice* device); - bool loadXmlMeta(QXmlStreamReader& xmlReader); - QByteArray loadXmlKey(QXmlStreamReader& xmlReader); + bool loadXml(QIODevice* device, QString* errorMsg = nullptr); bool loadBinary(QIODevice* device); bool loadHex(QIODevice* device); bool loadHashed(QIODevice* device); diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index d25a2bca..cbbaae39 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -69,22 +69,36 @@ void TestKeys::testComposite() void TestKeys::testFileKey() { QFETCH(FileKey::Type, type); - QFETCH(QString, typeString); + QFETCH(QString, keyExt); + QFETCH(bool, fileKeyOk); - QString name = QString("FileKey").append(typeString); + QString name = QString("FileKey").append(QTest::currentDataTag()); KeePass2Reader reader; QString dbFilename = QString("%1/%2.kdbx").arg(QString(KEEPASSX_TEST_DATA_DIR), name); - QString keyFilename = QString("%1/%2.key").arg(QString(KEEPASSX_TEST_DATA_DIR), name); + QString keyFilename = QString("%1/%2.%3").arg(QString(KEEPASSX_TEST_DATA_DIR), name, keyExt); auto compositeKey = QSharedPointer::create(); auto fileKey = QSharedPointer::create(); - QVERIFY(fileKey->load(keyFilename)); - QCOMPARE(fileKey->rawKey().size(), 32); - + QString error; + QVERIFY(fileKey->load(keyFilename, &error) == fileKeyOk); + QVERIFY(error.isEmpty() == fileKeyOk); QCOMPARE(fileKey->type(), type); + // Test for same behaviour on code path without error parameter + auto fileKeyNoErrorParam = QSharedPointer::create(); + QVERIFY(fileKeyNoErrorParam->load(keyFilename) == fileKeyOk); + QCOMPARE(fileKeyNoErrorParam->type(), type); + + QCOMPARE(fileKey->rawKey(), fileKeyNoErrorParam->rawKey()); + + if (!fileKeyOk) { + return; + } + + QCOMPARE(fileKey->rawKey().size(), 32); + compositeKey->addKey(fileKey); auto db = QSharedPointer::create(); @@ -97,12 +111,16 @@ void TestKeys::testFileKey() void TestKeys::testFileKey_data() { QTest::addColumn("type"); - QTest::addColumn("typeString"); - QTest::newRow("Xml") << FileKey::KeePass2XML << QString("Xml"); - QTest::newRow("XmlBrokenBase64") << FileKey::Hashed << QString("XmlBrokenBase64"); - QTest::newRow("Binary") << FileKey::FixedBinary << QString("Binary"); - QTest::newRow("Hex") << FileKey::FixedBinaryHex << QString("Hex"); - QTest::newRow("Hashed") << FileKey::Hashed << QString("Hashed"); + QTest::addColumn("keyExt"); + QTest::addColumn("fileKeyOk"); + QTest::newRow("Xml") << FileKey::KeePass2XML << QString("key") << true; + QTest::newRow("XmlBrokenBase64") << FileKey::KeePass2XML << QString("key") << false; + QTest::newRow("XmlV2") << FileKey::KeePass2XMLv2 << QString("keyx") << true; + QTest::newRow("XmlV2HashFail") << FileKey::KeePass2XMLv2 << QString("keyx") << false; + QTest::newRow("XmlV2BrokenHex") << FileKey::KeePass2XMLv2 << QString("keyx") << false; + QTest::newRow("Binary") << FileKey::FixedBinary << QString("key") << true; + QTest::newRow("Hex") << FileKey::FixedBinaryHex << QString("key") << true; + QTest::newRow("Hashed") << FileKey::Hashed << QString("key") << true; } // clang-format on @@ -111,12 +129,12 @@ void TestKeys::testCreateFileKey() QBuffer keyBuffer1; keyBuffer1.open(QBuffer::ReadWrite); - FileKey::create(&keyBuffer1, 128); + FileKey::createRandom(&keyBuffer1, 128); QCOMPARE(keyBuffer1.size(), 128); QBuffer keyBuffer2; keyBuffer2.open(QBuffer::ReadWrite); - FileKey::create(&keyBuffer2, 64); + FileKey::createRandom(&keyBuffer2, 64); QCOMPARE(keyBuffer2.size(), 64); } @@ -127,7 +145,7 @@ void TestKeys::testCreateAndOpenFileKey() QBuffer keyBuffer; keyBuffer.open(QBuffer::ReadWrite); - FileKey::create(&keyBuffer); + FileKey::createRandom(&keyBuffer); keyBuffer.reset(); auto fileKey = QSharedPointer::create(); @@ -166,7 +184,7 @@ void TestKeys::testFileKeyHash() QBuffer keyBuffer; keyBuffer.open(QBuffer::ReadWrite); - FileKey::create(&keyBuffer); + FileKey::createRandom(&keyBuffer); CryptoHash cryptoHash(CryptoHash::Sha256); cryptoHash.addData(keyBuffer.data()); diff --git a/tests/data/FileKeyXmlV2.kdbx b/tests/data/FileKeyXmlV2.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..4b13c2ec1ed8774d1b3bbe3e7a084d9cc4baa638 GIT binary patch literal 1582 zcmV+}2GRKg*`k_f`%AR}00RI55CAd3^5(yBLr}h01tDtuTK@wC0096100bZaU}u#Z zh<=IX7G_}C769+1GPQ44uPR3Hax^g4fBC0*YfyY^j_yX}DJQgR2`)RnkQN)W3fu5{o zprpE>JHOlHMqy!!DQ;LMB&r>kZrOY3xK-RRHw zk<^@+j;*p*bCgtkADziA40eq4OR!Y4LGb4^`1e*=gA7b=KfCsx&y8NjTuKHA=3SZ2AbSQDE16iUj;y9eW?;R-t zjm#3APVHVA5i5;0>vbjmtHX4d6-gn`#8Uo$s+<5-{W^0|yRidpJiBGAoZ!BZ6~1-& z@0o$HJOJCMlieXMT!59E=>Uu+smaUJ z;2P%jO0R{AhS8^gucYlf5bmXsWRC!?n<_>^VJmy=>x!X}CIxfb9y5r?$a=&i-q2mx zF02Rgoknf>1cgRQ5IX8LJ`afDMZ>pe%Jep?N}6pQ^K+DPu}^Nn!bJDVC1Ef+H2I4q z%ALA`kw$-Rz4a8j>R?A{WDKHM_=I){%~lo>X#tfOZpOqB^(4i!uV+QFeV$QTVh~>f zCHX!FGn6vc51RW5C8Jh8`z^Ll-8E+@p%8Bj?PN&k*JO9ltIsDl7)^%zaWa5{1jQQzeVU8y`( zd(Js*1CfI2MbTcNA9w1~ci$M{5(xk_8`EFVPI{rL%A)qm#;1@r?tF-0&qBQ|hUvX> zSLA`UISJ90$>jWNLvPdJ>R=7_tc_Ab_b%f9diHUYI5!8$kVL6B#>Ly1>9|L5q~r2N zMSwRD2w{~SRlIUsc3!3XH%t77gG#<(-<^FEEwV^S#t}S{4!9$`=EtKQ4^VIKc5g%5 zi^J5Fw5*Q#u|ko|*veAqfZSElA~@BiDO|8s?u3!fVMImP*Q4FDp9(kD!h*j>5IH{F zS{*skPXtr*3~#VS2LkbE>vh6U<^Bx4IpGw6ry + + + 2.0 + + + + A7007945 D07D54BA 28DF6434 1B4500FC + 9750DFB1 D36ADA2D 9C32DC19 4C7AB01B + + + \ No newline at end of file diff --git a/tests/data/FileKeyXmlV2BrokenHex.kdbx b/tests/data/FileKeyXmlV2BrokenHex.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..5a7e7355e31f4f218a72d416dec0bd58dff512a1 GIT binary patch literal 1550 zcmV+p2J!g=*`k_f`%AR}00RI55CAd3^5(yBLr}h01tDtuTK@wC0096100bZaH7zPc znV}`UmbvWL#C+?S7=3K!?0T~`5}!3sIRa@41t0(%Ux+G=5E~pQbY$_m@!YZwYH1Bv zJUCjGV^hayAgWIW2moN}00000000LN058{6yPfQORCdH495D6JvIrmm7#J=Ex9Vb` zpjw9@sGAgadtoW$9)OsY-r=}0teyC}2_OJknL!46)?kFg6L{+(cqGt`WBmtSN=W}*G4F~b6M2z)|AJaYED7Lno ztF}KWPnCCixBCc=8fmmxleALFxF?)Q;ofDC@Y95Rxop)pL3p&eZ&=J}*a=#AK;RV? zh@MzT@44LT%JFStw$bUgS|*mqnd2{WBl+Z*Rjw$gPjr67Ve^}$C(@pSPX;GZ2YT-h zsU~fvS=}d{gck*R8UMwr4Tut#C54MpQ9h|jZR=`q70hf5sJAs0)-$j|tqnrqwCC^T z;G1xPFIR=a-a^JePrE;P4625r*d>h3Dbh^%Uk`8Ko&J~Zu4!m2Vy-Rt0rty`<`}~= zSEsd^^Hcur0`_|EDS=o(cn8*B$n5h)XXtULY2M@m9pKy-t858x;8>O=aP~g$Vk&e-qP1iFcrXz%9G0xs$~lP$BUWo#CRkU){wcBP2h?G6Ot z54WC;_LU1zxwZhSsIO>@ftAU=*qO)W4UV$6N&7!7(Q3|M^)Um(5cMad(BQjeIEv^i z0o9aYUU9khx>*R&41im7NHJ_Be^f>mqK8W5XdXR?I>Hi1pUrLL zC3QX%!uXvoCoXCRmqUWW7T;(2zyTbLcA7X5*wh@HBFk%U@3VdEQkpBfF$}Po;y7uX z*hlmcACn=Uu^JD!wiXsZ#N&Us5Q$5pq0sy-Rgk6Y0%1R*|BIT+VvWe*hJ$uVu+)3J zWE~wG187g(q`=VanX~Q8PVxP8x#Mm_YJrmBu-}KwS5abswc((P!pPPzg|&Uwy)JMvt9@ukQ8#)S^SuV$ruTGcqZq33ela?93tC4CYVaon21qQ0aILVDItW$ zzUcE+hVrW+8+3O>BQy6mDL83W$%-x!>J)H!!xz#a20bAj&&X6c$4E;*tTWcA!M;K0 zq$1VppZa{Nln!Uqh}uV2p4QbT0uhDe>cj-%pq7=Em*EUmM2E0KIJye74>6`ExhZ|% Aod5s; literal 0 HcmV?d00001 diff --git a/tests/data/FileKeyXmlV2BrokenHex.keyx b/tests/data/FileKeyXmlV2BrokenHex.keyx new file mode 100644 index 00000000..a5280383 --- /dev/null +++ b/tests/data/FileKeyXmlV2BrokenHex.keyx @@ -0,0 +1,12 @@ + + + + 2.0 + + + + X7007945 D07D54BA 28DF6434 1B4500FC + 9750DFB1 D36ADA2D 9C32DC19 4C7AB01B + + + \ No newline at end of file diff --git a/tests/data/FileKeyXmlV2HashFail.kdbx b/tests/data/FileKeyXmlV2HashFail.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..9044b1288025346b26466dc97bfc161c245e5eb0 GIT binary patch literal 1582 zcmV+}2GRKg*`k_f`%AR}00RI55CAd3^5(yBLr}h01tDtuTK@wC0096100bZa46y43 zVaCZ8Ay~i#I{v0 zOzqYuC(D)WBz4HT;?w z)OC9c0*jR7VGy3j-$HiJ;`a>~wN*;Aiabmt_wS;btNs=@6u&taF7<&wzj-CUcwS};mtwZQjsQ@)-a5?&xwk> zuml(IO68#vZ;{V&4+p}+1GQ>NkTtu0T#a;*YEfwb-gK3jx$)T7FOw4#b+j@a_I|s_ zZ^I-ve1vyF5Lz_dlSSTNbkTvT?tZ6EzPF<}!aAOJK>wywQZ=6K;O2aT7b#XagQ;Uc`5G#sF}=AX z2^`(^LT7XKgd)uvs)t5)m9}nVPch#Uj&V^d;;8?!;|l`#1@ER5_4ar$@(49M7uW~e zN*28h-83kQIyo17TTNFiD7F@*+r4TG}G={7=O8VZ45sJzz$x)m#{ z4@p14@9sxznd9a5yODN~bDiR*daLl7KgpgB@0*w(Tp9Swx@3r9?yH-C8e!gxIY&W% z)PG`>7l6gTR_j@fZ|;9ILV?}b7OR4YX)?VRE*5!!f~*p3weQy1&l-dbqO4!9y`(FK zu}zPYmvR&ar5g*{5ksE`g2_wz!!v|1bcm2t1r7stOMgo?t^TU3Pg{8f?S};|jziC0 z`ZtmG;PQj#gF|YeT3~Z55KAfzH6;l7nT#fob`*-vXq8ZX?!tHdpYWYu?Tp(53=#SZ zFDr3{>_hr973AlpRfr*VtaeZv(^tk`mBD;0Wh$D{XQaTgMTmpUZ;Ba|g|pQxE9D3n z0Nv{0$P_KL?UN;D4~P|SZf;ptX`#(b!Xo0|%DT+nI3>X<#%RVm^E7bHL9O=Dar1i4 z56MgVw2ZFUJarKn{~B{?)Z{4t?Y0>!tf4rVtvg~@@x6|@MQ_qD7}M9TLUJ{ZJkJHz z?RNc4M+Io8#JmH>DycT`1@<2^B^8uJ+e{@xM6VNSMa`W*6Ig-AX@fqYYJo`J)q2hj z5y=J_%uhwpcK@_d-Q72O*qI31BAHm0q>58C%p(| z&s2tRh>p%`6br3ArL|WI^)G@Wj}f5C2ViQDxGTOB7~Kj5wee<%@agf8Hx!iOGc)C; z>%W}mPml}cjY69Eh;$N<)%$Cn7zM^NmrVC-fLdpoX-nYHGDo1B^j8%zA;9Tu3MzA$ z>9D4o(?z`=%)c7bnxRr)ws?qNCfv932-24(+t6h|LaDLbn-p za1_@SBHs0ul$_iUjY#&l1Er9MU_j=D2H#94>yg2gUE3;nA~=To#*Nyhw?wv}vP}ry zsTdjsLk@)f(zSqkjcg>qn?D&4C_Y$WPe%BHc)nJdDVFqeqh#9n5|lB;)K30iCgQOi g07p-0hFAdY!e@}pkXvYyZ!`-(x5aE@D%${O%IZz=K>z>% literal 0 HcmV?d00001 diff --git a/tests/data/FileKeyXmlV2HashFail.keyx b/tests/data/FileKeyXmlV2HashFail.keyx new file mode 100644 index 00000000..a52e96f1 --- /dev/null +++ b/tests/data/FileKeyXmlV2HashFail.keyx @@ -0,0 +1,12 @@ + + + + 2.0 + + + + A7007945 D07D54BA 28DF6434 1B4500FC + 9750DFB1 D36ADA2D 9C32DC19 4C7AB01B + + + \ No newline at end of file