From 39a7ce58b951798de9c5afdf44531e22dbe61909 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 7 Jan 2020 18:19:02 -0500 Subject: [PATCH 01/13] Prevent crash if Auto-Type performed on new entry * Check that entry's group is not nullptr * Fixes #3967 --- src/autotype/AutoType.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 299299b8c8..80a2268ec3 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -588,12 +588,12 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) { QList sequenceList; + const Group* group = entry->group(); - if (!entry->autoTypeEnabled()) { + if (!group || !entry->autoTypeEnabled()) { return sequenceList; } - const Group* group = entry->group(); do { if (group->autoTypeEnabled() == Group::Disable) { return sequenceList; From cba8947ee8a22e9abd801df78e3ff0e6b58fb77b Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 10 Jan 2020 19:09:46 -0500 Subject: [PATCH 02/13] Prevent unnecessary merge requests on intermittent network shares * Fixes #4118 --- src/core/FileWatcher.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp index fb8e951280..0bc5e34440 100644 --- a/src/core/FileWatcher.cpp +++ b/src/core/FileWatcher.cpp @@ -143,7 +143,9 @@ QByteArray FileWatcher::calculateChecksum() } return hash.result(); } - return {}; + // If we fail to open the file return the last known checksum, this + // prevents unnecessary merge requests on intermittent network shares + return m_fileChecksum; }); } From 247ebf5a35f513f145ea4604ccb76b12cf0e45b3 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Jan 2020 02:11:43 +0100 Subject: [PATCH 03/13] Ensure challenge-response key buffer is properly cleared. The challenge-response key buffer is explicitly cleared before the key transformation if no such key is configured to ensure one is never injected into the hash even if the database had a challenge-response key previously. This patch also adds extensive tests for verifying that a key change will not add any expired key material to the hash. Fixes #4146 --- src/core/Database.cpp | 3 + tests/CMakeLists.txt | 2 +- tests/TestKdbx3.cpp | 2 + tests/TestKdbx4.cpp | 69 ++++++++------ tests/TestKdbx4.h | 10 +- tests/TestKeePass2Format.cpp | 178 +++++++++++++++++++++++++++++++++++ tests/TestKeePass2Format.h | 4 + 7 files changed, 235 insertions(+), 33 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 066abc4e31..fcd48f1e20 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -651,6 +651,9 @@ bool Database::challengeMasterSeed(const QByteArray& masterSeed) bool ok = m_data.key->challenge(masterSeed, response); if (ok && !response.isEmpty()) { m_data.challengeResponseKey->setHash(response); + } else if (ok && response.isEmpty()) { + // no CR key present, make sure buffer is empty + m_data.challengeResponseKey.reset(new PasswordKey); } return ok; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1c0e5f7ed6..fc27f48d33 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -104,7 +104,7 @@ add_unit_test(NAME testgroup SOURCES TestGroup.cpp add_unit_test(NAME testkdbx2 SOURCES TestKdbx2.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testkdbx3 SOURCES TestKeePass2Format.cpp FailDevice.cpp TestKdbx3.cpp +add_unit_test(NAME testkdbx3 SOURCES TestKeePass2Format.cpp FailDevice.cpp mock/MockChallengeResponseKey.cpp TestKdbx3.cpp LIBS testsupport ${TEST_LIBRARIES}) add_unit_test(NAME testkdbx4 SOURCES TestKeePass2Format.cpp FailDevice.cpp mock/MockChallengeResponseKey.cpp TestKdbx4.cpp diff --git a/tests/TestKdbx3.cpp b/tests/TestKdbx3.cpp index bf9a1ec8b4..92fd2c7529 100644 --- a/tests/TestKdbx3.cpp +++ b/tests/TestKdbx3.cpp @@ -31,6 +31,8 @@ QTEST_GUILESS_MAIN(TestKdbx3) void TestKdbx3::initTestCaseImpl() { + m_xmlDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_AES_KDBX3))); + m_kdbxSourceDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_AES_KDBX3))); } QSharedPointer TestKdbx3::readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) diff --git a/tests/TestKdbx4.cpp b/tests/TestKdbx4.cpp index 88352d8252..51784f062e 100644 --- a/tests/TestKdbx4.cpp +++ b/tests/TestKdbx4.cpp @@ -29,15 +29,25 @@ #include "keys/PasswordKey.h" #include "mock/MockChallengeResponseKey.h" -QTEST_GUILESS_MAIN(TestKdbx4) +int main(int argc, char* argv[]) +{ + QCoreApplication app(argc, argv); + QCoreApplication::setAttribute(Qt::AA_Use96Dpi, true); + QTEST_SET_MAIN_SOURCE_PATH + + TestKdbx4Argon2 argon2Test; + TestKdbx4AesKdf aesKdfTest; + return QTest::qExec(&argon2Test, argc, argv) | QTest::qExec(&aesKdfTest, argc, argv); +} -void TestKdbx4::initTestCaseImpl() +void TestKdbx4Argon2::initTestCaseImpl() { m_xmlDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2))); m_kdbxSourceDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2))); } -QSharedPointer TestKdbx4::readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) +QSharedPointer +TestKdbx4Argon2::readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) { KdbxXmlReader reader(KeePass2::FILE_VERSION_4); reader.setStrictMode(strictMode); @@ -47,7 +57,7 @@ QSharedPointer TestKdbx4::readXml(const QString& path, bool strictMode return db; } -QSharedPointer TestKdbx4::readXml(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) +QSharedPointer TestKdbx4Argon2::readXml(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) { KdbxXmlReader reader(KeePass2::FILE_VERSION_4); reader.setStrictMode(strictMode); @@ -57,7 +67,7 @@ QSharedPointer TestKdbx4::readXml(QBuffer* buf, bool strictMode, bool& return db; } -void TestKdbx4::writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) +void TestKdbx4Argon2::writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) { KdbxXmlWriter writer(KeePass2::FILE_VERSION_4); writer.writeDatabase(buf, db); @@ -65,11 +75,11 @@ void TestKdbx4::writeXml(QBuffer* buf, Database* db, bool& hasError, QString& er errorString = writer.errorString(); } -void TestKdbx4::readKdbx(QIODevice* device, - QSharedPointer key, - QSharedPointer db, - bool& hasError, - QString& errorString) +void TestKdbx4Argon2::readKdbx(QIODevice* device, + QSharedPointer key, + QSharedPointer db, + bool& hasError, + QString& errorString) { KeePass2Reader reader; reader.readDatabase(device, key, db.data()); @@ -80,11 +90,11 @@ void TestKdbx4::readKdbx(QIODevice* device, QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4); } -void TestKdbx4::readKdbx(const QString& path, - QSharedPointer key, - QSharedPointer db, - bool& hasError, - QString& errorString) +void TestKdbx4Argon2::readKdbx(const QString& path, + QSharedPointer key, + QSharedPointer db, + bool& hasError, + QString& errorString) { KeePass2Reader reader; reader.readDatabase(path, key, db.data()); @@ -95,7 +105,7 @@ void TestKdbx4::readKdbx(const QString& path, QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4); } -void TestKdbx4::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) +void TestKdbx4Argon2::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) { if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) { db->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2))); @@ -110,7 +120,7 @@ void TestKdbx4::writeKdbx(QIODevice* device, Database* db, bool& hasError, QStri } Q_DECLARE_METATYPE(QUuid) -void TestKdbx4::testFormat400() +void TestKdbx4Argon2::testFormat400() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format400.kdbx"); auto key = QSharedPointer::create(); @@ -135,7 +145,7 @@ void TestKdbx4::testFormat400() QCOMPARE(entry->attachments()->value("Format400"), QByteArray("Format400\n")); } -void TestKdbx4::testFormat400Upgrade() +void TestKdbx4Argon2::testFormat400Upgrade() { QFETCH(QUuid, kdfUuid); QFETCH(QUuid, cipherUuid); @@ -193,7 +203,7 @@ void TestKdbx4::testFormat400Upgrade() } // clang-format off -void TestKdbx4::testFormat400Upgrade_data() +void TestKdbx4Argon2::testFormat400Upgrade_data() { QTest::addColumn("kdfUuid"); QTest::addColumn("cipherUuid"); @@ -226,7 +236,7 @@ void TestKdbx4::testFormat400Upgrade_data() } // clang-format on -void TestKdbx4::testUpgradeMasterKeyIntegrity() +void TestKdbx4Argon2::testUpgradeMasterKeyIntegrity() { QFETCH(QString, upgradeAction); QFETCH(quint32, expectedVersion); @@ -249,6 +259,7 @@ void TestKdbx4::testUpgradeMasterKeyIntegrity() QScopedPointer db(new Database()); db->changeKdf(fastKdf(db->kdf())); + QCOMPARE(db->kdf()->uuid(), KeePass2::KDF_AES_KDBX3); // default is legacy AES-KDF db->setKey(compositeKey); // upgrade the database by a specific method @@ -309,9 +320,12 @@ void TestKdbx4::testUpgradeMasterKeyIntegrity() QFAIL(qPrintable(reader.errorString())); } QCOMPARE(reader.version(), expectedVersion & KeePass2::FILE_VERSION_CRITICAL_MASK); + if (expectedVersion != KeePass2::FILE_VERSION_3) { + QVERIFY(db2->kdf()->uuid() != KeePass2::KDF_AES_KDBX3); + } } -void TestKdbx4::testUpgradeMasterKeyIntegrity_data() +void TestKdbx4Argon2::testUpgradeMasterKeyIntegrity_data() { QTest::addColumn("upgradeAction"); QTest::addColumn("expectedVersion"); @@ -330,7 +344,7 @@ void TestKdbx4::testUpgradeMasterKeyIntegrity_data() QTest::newRow("Upgrade (implicit): entry-customdata") << QString("entry-customdata") << KeePass2::FILE_VERSION_4; } -void TestKdbx4::testCustomData() +void TestKdbx4Argon2::testCustomData() { Database db; @@ -424,13 +438,8 @@ void TestKdbx4::testCustomData() QCOMPARE(newEntry->customData()->value(customDataKey2), customData2); } -QSharedPointer TestKdbx4::fastKdf(QSharedPointer kdf) +void TestKdbx4AesKdf::initTestCaseImpl() { - kdf->setRounds(1); - - if (kdf->uuid() == KeePass2::KDF_ARGON2) { - kdf->processParameters({{KeePass2::KDFPARAM_ARGON2_MEMORY, 1024}, {KeePass2::KDFPARAM_ARGON2_PARALLELISM, 1}}); - } - - return kdf; + m_xmlDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_AES_KDBX4))); + m_kdbxSourceDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_AES_KDBX4))); } diff --git a/tests/TestKdbx4.h b/tests/TestKdbx4.h index e970841483..53f2295e03 100644 --- a/tests/TestKdbx4.h +++ b/tests/TestKdbx4.h @@ -20,7 +20,7 @@ #include "TestKeePass2Format.h" -class TestKdbx4 : public TestKeePass2Format +class TestKdbx4Argon2 : public TestKeePass2Format { Q_OBJECT @@ -51,8 +51,14 @@ private slots: bool& hasError, QString& errorString) override; void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override; +}; + +class TestKdbx4AesKdf : public TestKdbx4Argon2 +{ + Q_OBJECT - QSharedPointer fastKdf(QSharedPointer kdf); +protected: + void initTestCaseImpl() override; }; #endif // KEEPASSXC_TEST_KDBX4_H diff --git a/tests/TestKeePass2Format.cpp b/tests/TestKeePass2Format.cpp index 012a1b11aa..ce4f63fedf 100644 --- a/tests/TestKeePass2Format.cpp +++ b/tests/TestKeePass2Format.cpp @@ -22,7 +22,9 @@ #include "core/Metadata.h" #include "crypto/Crypto.h" #include "format/KdbxXmlReader.h" +#include "keys/FileKey.h" #include "keys/PasswordKey.h" +#include "mock/MockChallengeResponseKey.h" #include "FailDevice.h" #include "config-keepassx-tests.h" @@ -566,6 +568,168 @@ void TestKeePass2Format::testKdbxDeviceFailure() QCOMPARE(errorString, QString("FAILDEVICE")); } +Q_DECLARE_METATYPE(QSharedPointer) + +void TestKeePass2Format::testKdbxKeyChange() +{ + QFETCH(QSharedPointer, key1); + QFETCH(QSharedPointer, key2); + + bool hasError; + QString errorString; + + // write new database + QBuffer buffer; + buffer.open(QBuffer::ReadWrite); + buffer.seek(0); + QSharedPointer db(new Database()); + db->changeKdf(fastKdf(KeePass2::uuidToKdf(m_kdbxSourceDb->kdf()->uuid()))); + db->setRootGroup(m_kdbxSourceDb->rootGroup()->clone(Entry::CloneNoFlags, Group::CloneIncludeEntries)); + + db->setKey(key1); + writeKdbx(&buffer, db.data(), hasError, errorString); + QVERIFY(!hasError); + + // read database + db = QSharedPointer::create(); + buffer.seek(0); + readKdbx(&buffer, key1, db, hasError, errorString); + if (hasError) { + QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString))); + } + QVERIFY(db.data()); + + // change key + db->setKey(key2); + + // write database + buffer.seek(0); + writeKdbx(&buffer, db.data(), hasError, errorString); + QVERIFY(!hasError); + + // read database + db = QSharedPointer::create(); + buffer.seek(0); + readKdbx(&buffer, key2, db, hasError, errorString); + if (hasError) { + QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString))); + } + QVERIFY(db.data()); + QVERIFY(db->rootGroup() != m_kdbxSourceDb->rootGroup()); + QVERIFY(db->rootGroup()->uuid() == m_kdbxSourceDb->rootGroup()->uuid()); +} + +void TestKeePass2Format::testKdbxKeyChange_data() +{ + QTest::addColumn>("key1"); + QTest::addColumn>("key2"); + + auto passwordKey1 = QSharedPointer::create("abc"); + auto passwordKey2 = QSharedPointer::create("def"); + + QByteArray fileKeyBytes1("uvw"); + QBuffer fileKeyBuffer1(&fileKeyBytes1); + fileKeyBuffer1.open(QBuffer::ReadOnly); + auto fileKey1 = QSharedPointer::create(); + fileKey1->load(&fileKeyBuffer1); + + QByteArray fileKeyBytes2("xzy"); + QBuffer fileKeyBuffer2(&fileKeyBytes1); + fileKeyBuffer2.open(QBuffer::ReadOnly); + auto fileKey2 = QSharedPointer::create(); + fileKey2->load(&fileKeyBuffer2); + + auto crKey1 = QSharedPointer::create(QByteArray("123")); + auto crKey2 = QSharedPointer::create(QByteArray("456")); + + // empty key + auto compositeKey0 = QSharedPointer::create(); + + // all in + auto compositeKey1_1 = QSharedPointer::create(); + compositeKey1_1->addKey(passwordKey1); + compositeKey1_1->addKey(fileKey1); + compositeKey1_1->addChallengeResponseKey(crKey1); + auto compositeKey1_2 = QSharedPointer::create(); + compositeKey1_2->addKey(passwordKey2); + compositeKey1_2->addKey(fileKey2); + compositeKey1_2->addChallengeResponseKey(crKey2); + + QTest::newRow("Change: Empty Key -> Full Key") << compositeKey0 << compositeKey1_1; + QTest::newRow("Change: Full Key -> Empty Key") << compositeKey1_1 << compositeKey0; + QTest::newRow("Change: Full Key 1 -> Full Key 2") << compositeKey1_1 << compositeKey1_2; + + // only password + auto compositeKey2_1 = QSharedPointer::create(); + compositeKey2_1->addKey(passwordKey1); + auto compositeKey2_2 = QSharedPointer::create(); + compositeKey2_2->addKey(passwordKey2); + + QTest::newRow("Change: Password -> Empty Key") << compositeKey2_1 << compositeKey0; + QTest::newRow("Change: Empty Key -> Password") << compositeKey0 << compositeKey2_1; + QTest::newRow("Change: Full Key -> Password 1") << compositeKey1_1 << compositeKey2_1; + QTest::newRow("Change: Full Key -> Password 2") << compositeKey1_1 << compositeKey2_2; + QTest::newRow("Change: Password 1 -> Full Key") << compositeKey2_1 << compositeKey1_1; + QTest::newRow("Change: Password 2 -> Full Key") << compositeKey2_2 << compositeKey1_1; + QTest::newRow("Change: Password 1 -> Password 2") << compositeKey2_1 << compositeKey2_2; + + // only key file + auto compositeKey3_1 = QSharedPointer::create(); + compositeKey3_1->addKey(fileKey1); + auto compositeKey3_2 = QSharedPointer::create(); + compositeKey3_2->addKey(fileKey2); + + QTest::newRow("Change: Key File -> Empty Key") << compositeKey3_1 << compositeKey0; + QTest::newRow("Change: Empty Key -> Key File") << compositeKey0 << compositeKey3_1; + QTest::newRow("Change: Full Key -> Key File 1") << compositeKey1_1 << compositeKey3_1; + QTest::newRow("Change: Full Key -> Key File 2") << compositeKey1_1 << compositeKey3_2; + QTest::newRow("Change: Key File 1 -> Full Key") << compositeKey3_1 << compositeKey1_1; + QTest::newRow("Change: Key File 2 -> Full Key") << compositeKey3_2 << compositeKey1_1; + QTest::newRow("Change: Key File 1 -> Key File 2") << compositeKey3_1 << compositeKey3_2; + + // only cr key + auto compositeKey4_1 = QSharedPointer::create(); + compositeKey4_1->addChallengeResponseKey(crKey1); + auto compositeKey4_2 = QSharedPointer::create(); + compositeKey4_2->addChallengeResponseKey(crKey2); + + QTest::newRow("Change: CR Key -> Empty Key") << compositeKey4_1 << compositeKey0; + QTest::newRow("Change: Empty Key -> CR Key") << compositeKey0 << compositeKey4_1; + QTest::newRow("Change: Full Key -> CR Key 1") << compositeKey1_1 << compositeKey4_1; + QTest::newRow("Change: Full Key -> CR Key 2") << compositeKey1_1 << compositeKey4_2; + QTest::newRow("Change: CR Key 1 -> Full Key") << compositeKey4_1 << compositeKey1_1; + QTest::newRow("Change: CR Key 2 -> Full Key") << compositeKey4_2 << compositeKey1_1; + QTest::newRow("Change: CR Key 1 -> CR Key 2") << compositeKey4_1 << compositeKey4_2; + + // rotate + QTest::newRow("Change: Password -> Key File") << compositeKey2_1 << compositeKey3_1; + QTest::newRow("Change: Key File -> Password") << compositeKey3_1 << compositeKey2_1; + QTest::newRow("Change: Password -> Key File") << compositeKey2_1 << compositeKey3_1; + QTest::newRow("Change: Key File -> Password") << compositeKey3_1 << compositeKey2_1; + QTest::newRow("Change: Password -> CR Key") << compositeKey2_1 << compositeKey4_1; + QTest::newRow("Change: CR Key -> Password") << compositeKey4_1 << compositeKey2_1; + QTest::newRow("Change: Key File -> CR Key") << compositeKey3_1 << compositeKey4_1; + QTest::newRow("Change: CR Key -> Key File") << compositeKey4_1 << compositeKey3_1; + + // leave one out + auto compositeKey5_1 = QSharedPointer::create(); + compositeKey5_1->addKey(fileKey1); + compositeKey5_1->addChallengeResponseKey(crKey1); + auto compositeKey5_2 = QSharedPointer::create(); + compositeKey5_2->addKey(passwordKey1); + compositeKey5_2->addChallengeResponseKey(crKey1); + auto compositeKey5_3 = QSharedPointer::create(); + compositeKey5_3->addKey(passwordKey1); + compositeKey5_3->addKey(fileKey1); + + QTest::newRow("Change: Full Key -> No Password") << compositeKey1_1 << compositeKey5_1; + QTest::newRow("Change: No Password -> Full Key") << compositeKey5_1 << compositeKey1_1; + QTest::newRow("Change: Full Key -> No Key File") << compositeKey1_1 << compositeKey5_2; + QTest::newRow("Change: No Key File -> Full Key") << compositeKey5_2 << compositeKey1_1; + QTest::newRow("Change: Full Key -> No CR Key") << compositeKey1_1 << compositeKey5_3; + QTest::newRow("Change: No CR Key -> Full Key") << compositeKey5_3 << compositeKey1_1; +} + /** * Test for catching mapping errors with duplicate attachments. */ @@ -637,3 +801,17 @@ void TestKeePass2Format::testDuplicateAttachments() QCOMPARE(db->rootGroup()->entries()[2]->attachments()->value("c2"), attachment2); QCOMPARE(db->rootGroup()->entries()[2]->attachments()->value("c3"), attachment3); } + +/** + * @return fast "dummy" KDF + */ +QSharedPointer TestKeePass2Format::fastKdf(QSharedPointer kdf) const +{ + kdf->setRounds(1); + + if (kdf->uuid() == KeePass2::KDF_ARGON2) { + kdf->processParameters({{KeePass2::KDFPARAM_ARGON2_MEMORY, 1024}, {KeePass2::KDFPARAM_ARGON2_PARALLELISM, 1}}); + } + + return kdf; +} diff --git a/tests/TestKeePass2Format.h b/tests/TestKeePass2Format.h index 2d4a4b85a7..728d73ba9a 100644 --- a/tests/TestKeePass2Format.h +++ b/tests/TestKeePass2Format.h @@ -62,6 +62,8 @@ private slots: void testKdbxAttachments(); void testKdbxNonAsciiPasswords(); void testKdbxDeviceFailure(); + void testKdbxKeyChange(); + void testKdbxKeyChange_data(); void testDuplicateAttachments(); protected: @@ -84,6 +86,8 @@ private slots: QString& errorString) = 0; virtual void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) = 0; + QSharedPointer fastKdf(QSharedPointer kdf) const; + QSharedPointer m_xmlDb; QSharedPointer m_kdbxSourceDb; QSharedPointer m_kdbxTargetDb; From 460732097ca763ae7cf06c6f7b35415a48feba16 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sat, 11 Jan 2020 12:50:21 +0200 Subject: [PATCH 04/13] Add empty path to URL when needed --- src/browser/BrowserService.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 63860b58da..8c451bf1ea 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -935,6 +935,12 @@ int BrowserService::sortPriority(const Entry* entry, if (url.scheme().isEmpty()) { url.setScheme("https"); } + + // Add the empty path to the URL if it's missing + if (url.path().isEmpty() && !url.hasFragment() && !url.hasQuery()) { + url.setPath("/"); + } + const QString entryURL = url.toString(QUrl::StripTrailingSlash); const QString baseEntryURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment); From 9da07f2f66f1049c90ae86e2f99bfcf58862de7e Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 12 Jan 2020 19:31:53 -0500 Subject: [PATCH 05/13] Don't mark cmd:// urls as invalid * Due to the complexity of cmd:// type url's, avoid trying to parse them for validity once the initial scheme is entered. * Fixes #4138 --- src/core/Tools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 7cfc8a55f3..5d42bc799c 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -262,7 +262,7 @@ namespace Tools bool checkUrlValid(const QString& urlField) { - if (urlField.isEmpty()) { + if (urlField.isEmpty() || urlField.startsWith("cmd://", Qt::CaseInsensitive)) { return true; } From 6cde2b83e8f35d4c402783c4727672176dad414a Mon Sep 17 00:00:00 2001 From: varjolintu Date: Wed, 15 Jan 2020 22:36:22 +0200 Subject: [PATCH 06/13] Add support for Microsoft Edge browser --- README.md | 2 +- src/browser/BrowserOptionDialog.cpp | 11 ++++++++++- src/browser/BrowserOptionDialog.ui | 10 ++++++++++ src/browser/BrowserSettings.cpp | 11 +++++++++++ src/browser/BrowserSettings.h | 2 ++ src/browser/HostInstaller.cpp | 13 ++++++++++--- src/browser/HostInstaller.h | 4 +++- 7 files changed, 47 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a8ca4c4d48..5fad0cc835 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ so please check out your distribution's package list to see if KeePassXC is avai - Using website favicons as entry icons - Merging of databases - Automatic reload when the database changed on disk -- Browser integration with KeePassXC-Browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome, Chromium, Vivaldi, or Brave](https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk) +- Browser integration with KeePassXC-Browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome, Chromium, Vivaldi, or Brave](https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk) and [Microsoft Edge](https://microsoftedge.microsoft.com/addons/detail/pdffhmdngciaglkoonimfcmckehcpafo) - Synchronize passwords using KeeShare. See [Using Sharing](./docs/QUICKSTART.md#using-sharing) for more details. - Many bug fixes diff --git a/src/browser/BrowserOptionDialog.cpp b/src/browser/BrowserOptionDialog.cpp index 1af989e00d..f12c42bb7e 100644 --- a/src/browser/BrowserOptionDialog.cpp +++ b/src/browser/BrowserOptionDialog.cpp @@ -44,10 +44,11 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent) m_ui->extensionLabel->setOpenExternalLinks(true); m_ui->extensionLabel->setText( - tr("KeePassXC-Browser is needed for the browser integration to work.
Download it for %1 and %2. %3") + tr("KeePassXC-Browser is needed for the browser integration to work.
Download it for %1 and %2 and %3. %4") .arg("Firefox", "" "Google Chrome / Chromium / Vivaldi / Brave", + "Microsoft Edge", snapInstructions)); // clang-format on @@ -123,6 +124,7 @@ void BrowserOptionDialog::loadSettings() m_ui->chromeSupport->setChecked(settings->chromeSupport()); m_ui->chromiumSupport->setChecked(settings->chromiumSupport()); m_ui->firefoxSupport->setChecked(settings->firefoxSupport()); + m_ui->edgeSupport->setChecked(settings->edgeSupport()); #ifndef Q_OS_WIN m_ui->braveSupport->setChecked(settings->braveSupport()); m_ui->vivaldiSupport->setChecked(settings->vivaldiSupport()); @@ -132,6 +134,12 @@ void BrowserOptionDialog::loadSettings() m_ui->snapWarningLabel->setVisible(false); #endif +// TODO: Enable when Linux version is released +#ifdef Q_OS_LINUX + m_ui->edgeSupport->setChecked(false); + m_ui->edgeSupport->setEnabled(false); +#endif + #if defined(KEEPASSXC_DIST_APPIMAGE) m_ui->supportBrowserProxy->setChecked(true); m_ui->supportBrowserProxy->setEnabled(false); @@ -194,6 +202,7 @@ void BrowserOptionDialog::saveSettings() settings->setChromeSupport(m_ui->chromeSupport->isChecked()); settings->setChromiumSupport(m_ui->chromiumSupport->isChecked()); settings->setFirefoxSupport(m_ui->firefoxSupport->isChecked()); + settings->setEdgeSupport(m_ui->edgeSupport->isChecked()); #ifndef Q_OS_WIN settings->setBraveSupport(m_ui->braveSupport->isChecked()); settings->setVivaldiSupport(m_ui->vivaldiSupport->isChecked()); diff --git a/src/browser/BrowserOptionDialog.ui b/src/browser/BrowserOptionDialog.ui index 84fc5bdbf6..1c00da148e 100755 --- a/src/browser/BrowserOptionDialog.ui +++ b/src/browser/BrowserOptionDialog.ui @@ -167,6 +167,16 @@ + + + + &Edge + + + false + + + diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index 046734268e..9cb4e0735c 100644 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -280,6 +280,17 @@ void BrowserSettings::setTorBrowserSupport(bool enabled) HostInstaller::SupportedBrowsers::TOR_BROWSER, enabled, supportBrowserProxy(), customProxyLocation()); } +bool BrowserSettings::edgeSupport() +{ + return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::EDGE); +} + +void BrowserSettings::setEdgeSupport(bool enabled) +{ + m_hostInstaller.installBrowser( + HostInstaller::SupportedBrowsers::EDGE, enabled, supportBrowserProxy(), customProxyLocation()); +} + bool BrowserSettings::passwordUseNumbers() { return config()->get("generator/Numbers", PasswordGenerator::DefaultNumbers).toBool(); diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h index 0fa2359eec..395455cbcb 100644 --- a/src/browser/BrowserSettings.h +++ b/src/browser/BrowserSettings.h @@ -80,6 +80,8 @@ class BrowserSettings void setBraveSupport(bool enabled); bool torBrowserSupport(); void setTorBrowserSupport(bool enabled); + bool edgeSupport(); + void setEdgeSupport(bool enabled); bool passwordUseNumbers(); void setPasswordUseNumbers(bool useNumbers); diff --git a/src/browser/HostInstaller.cpp b/src/browser/HostInstaller.cpp index 4928ba64e5..f4ffae3b7a 100644 --- a/src/browser/HostInstaller.cpp +++ b/src/browser/HostInstaller.cpp @@ -31,7 +31,7 @@ HostInstaller::HostInstaller() : HOST_NAME("org.keepassxc.keepassxc_browser") , ALLOWED_EXTENSIONS(QStringList() << "keepassxc-browser@keepassxc.org") - , ALLOWED_ORIGINS(QStringList() << "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/" + , ALLOWED_ORIGINS(QStringList() << "chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/" << "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/") #if defined(Q_OS_MACOS) , TARGET_DIR_CHROME("/Library/Application Support/Google/Chrome/NativeMessagingHosts") @@ -40,6 +40,7 @@ HostInstaller::HostInstaller() , TARGET_DIR_VIVALDI("/Library/Application Support/Vivaldi/NativeMessagingHosts") , TARGET_DIR_TOR_BROWSER("/Library/Application Support/TorBrowser-Data/Browser/Mozilla/NativeMessagingHosts") , TARGET_DIR_BRAVE("/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts") + , TARGET_DIR_EDGE("/Library/Application Support/Microsoft Edge/NativeMessagingHosts") #elif defined(Q_OS_WIN) // clang-format off , TARGET_DIR_CHROME("HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") @@ -49,6 +50,8 @@ HostInstaller::HostInstaller() , TARGET_DIR_VIVALDI(TARGET_DIR_CHROME) , TARGET_DIR_TOR_BROWSER(TARGET_DIR_FIREFOX) , TARGET_DIR_BRAVE(TARGET_DIR_CHROME) + , TARGET_DIR_EDGE( + "HKEY_CURRENT_USER\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") #else , TARGET_DIR_CHROME("/.config/google-chrome/NativeMessagingHosts") , TARGET_DIR_CHROMIUM("/.config/chromium/NativeMessagingHosts") @@ -56,6 +59,7 @@ HostInstaller::HostInstaller() , TARGET_DIR_VIVALDI("/.config/vivaldi/NativeMessagingHosts") , TARGET_DIR_TOR_BROWSER("/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts") , TARGET_DIR_BRAVE("/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts") + , TARGET_DIR_EDGE("/.config/microsoftedge/NativeMessagingHosts") #endif { } @@ -139,8 +143,7 @@ void HostInstaller::installBrowser(SupportedBrowsers browser, */ void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location) { - // Where 6 is the number of entries in the SupportedBrowsers enum declared in HostInstaller.h - for (int i = 0; i < 6; ++i) { + for (int i = 0; i <= SupportedBrowsers::EDGE; ++i) { if (checkIfInstalled(static_cast(i))) { installBrowser(static_cast(i), true, proxy, location); } @@ -168,6 +171,8 @@ QString HostInstaller::getTargetPath(SupportedBrowsers browser) const return TARGET_DIR_TOR_BROWSER; case SupportedBrowsers::BRAVE: return TARGET_DIR_BRAVE; + case SupportedBrowsers::EDGE: + return TARGET_DIR_EDGE; default: return QString(); } @@ -195,6 +200,8 @@ QString HostInstaller::getBrowserName(SupportedBrowsers browser) const return "tor-browser"; case SupportedBrowsers::BRAVE: return "brave"; + case SupportedBrowsers::EDGE: + return "edge"; default: return QString(); } diff --git a/src/browser/HostInstaller.h b/src/browser/HostInstaller.h index 154fe21a93..2136d1c340 100644 --- a/src/browser/HostInstaller.h +++ b/src/browser/HostInstaller.h @@ -35,7 +35,8 @@ class HostInstaller : public QObject FIREFOX = 2, VIVALDI = 3, TOR_BROWSER = 4, - BRAVE = 5 + BRAVE = 5, + EDGE = 6 }; public: @@ -68,6 +69,7 @@ class HostInstaller : public QObject const QString TARGET_DIR_VIVALDI; const QString TARGET_DIR_TOR_BROWSER; const QString TARGET_DIR_BRAVE; + const QString TARGET_DIR_EDGE; }; #endif // HOSTINSTALLER_H From 0d3eb047c7e1e42482b1d37e23e4701b66bd1138 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 10 Jan 2020 22:28:31 -0500 Subject: [PATCH 07/13] Prevent crash when all entries are deleted from a group * Fix #4093 - The first entry in the list is selected after deleting an entry * Prevents crashes due to dangling pointers held by the Entry Preview Widget when entries were deleted. * Improve GUI tests to ensure this new behavior occurs. --- src/gui/DatabaseWidget.cpp | 9 +++++++++ src/gui/EntryPreviewWidget.cpp | 6 ++++-- src/gui/EntryPreviewWidget.h | 4 ++-- tests/gui/TestGui.cpp | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index e7b6179fbc..eb33c09c0b 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -154,6 +154,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer db, QWidget* parent) m_shareLabel->setVisible(false); #endif + m_previewView->setObjectName("previewWidget"); m_previewView->hide(); m_previewSplitter->addWidget(m_entryView); m_previewSplitter->addWidget(m_previewView); @@ -552,6 +553,14 @@ void DatabaseWidget::deleteEntries(QList selectedEntries) } refreshSearch(); + + m_entryView->setFirstEntryActive(); + auto* currentEntry = currentSelectedEntry(); + if (currentEntry) { + m_previewView->setEntry(currentEntry); + } else { + m_previewView->setGroup(groupView()->currentGroup()); + } } bool DatabaseWidget::confirmDeleteEntries(QList entries, bool permanent) diff --git a/src/gui/EntryPreviewWidget.cpp b/src/gui/EntryPreviewWidget.cpp index e46e3a6632..2e2e37dbc3 100644 --- a/src/gui/EntryPreviewWidget.cpp +++ b/src/gui/EntryPreviewWidget.cpp @@ -145,10 +145,12 @@ void EntryPreviewWidget::setDatabaseMode(DatabaseWidget::Mode mode) } if (mode == DatabaseWidget::Mode::ViewMode) { - if (m_ui->stackedWidget->currentWidget() == m_ui->pageGroup) { + if (m_currentGroup && m_ui->stackedWidget->currentWidget() == m_ui->pageGroup) { setGroup(m_currentGroup); - } else { + } else if (m_currentEntry) { setEntry(m_currentEntry); + } else { + hide(); } } } diff --git a/src/gui/EntryPreviewWidget.h b/src/gui/EntryPreviewWidget.h index e8e7d2172b..e1a7aff387 100644 --- a/src/gui/EntryPreviewWidget.h +++ b/src/gui/EntryPreviewWidget.h @@ -77,8 +77,8 @@ private slots: const QScopedPointer m_ui; bool m_locked; - Entry* m_currentEntry; - Group* m_currentGroup; + QPointer m_currentEntry; + QPointer m_currentGroup; QTimer m_totpTimer; quint8 m_selectedTabEntry; quint8 m_selectedTabGroup; diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 48b5c73515..9118d3e214 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -54,6 +54,7 @@ #include "gui/CloneDialog.h" #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" +#include "gui/EntryPreviewWidget.h" #include "gui/FileDialog.h" #include "gui/MessageBox.h" #include "gui/PasswordEdit.h" @@ -967,6 +968,7 @@ void TestGui::testDeleteEntry() QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); entryView->setFocus(); + // Move one entry to the recycling bin QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::ViewMode); clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton); QVERIFY(entryDeleteWidget->isVisible()); @@ -979,6 +981,7 @@ void TestGui::testDeleteEntry() QCOMPARE(entryView->model()->rowCount(), 3); QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 1); + // Select multiple entries and move them to the recycling bin clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton); clickIndex(entryView->model()->index(2, 1), entryView, Qt::LeftButton, Qt::ControlModifier); QCOMPARE(entryView->selectionModel()->selectedRows().size(), 2); @@ -993,6 +996,7 @@ void TestGui::testDeleteEntry() QCOMPARE(entryView->model()->rowCount(), 1); QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3); + // Go to the recycling bin QCOMPARE(groupView->currentGroup(), m_db->rootGroup()); QModelIndex rootGroupIndex = groupView->model()->index(0, 0); clickIndex(groupView->model()->index(groupView->model()->rowCount(rootGroupIndex) - 1, 0, rootGroupIndex), @@ -1000,6 +1004,7 @@ void TestGui::testDeleteEntry() Qt::LeftButton); QCOMPARE(groupView->currentGroup()->name(), m_db->metadata()->recycleBin()->name()); + // Delete one entry from the bin clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton); MessageBox::setNextAnswer(MessageBox::Cancel); QTest::mouseClick(entryDeleteWidget, Qt::LeftButton); @@ -1011,6 +1016,7 @@ void TestGui::testDeleteEntry() QCOMPARE(entryView->model()->rowCount(), 2); QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 2); + // Select the remaining entries and delete them clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton); clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton, Qt::ControlModifier); MessageBox::setNextAnswer(MessageBox::Delete); @@ -1018,6 +1024,16 @@ void TestGui::testDeleteEntry() QCOMPARE(entryView->model()->rowCount(), 0); QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 0); + // Ensure the entry preview widget shows the recycling group since all entries are deleted + auto* previewWidget = m_dbWidget->findChild("previewWidget"); + QVERIFY(previewWidget); + auto* groupTitleLabel = previewWidget->findChild("groupTitleLabel"); + QVERIFY(groupTitleLabel); + + QTRY_VERIFY(groupTitleLabel->isVisible()); + QVERIFY(groupTitleLabel->text().contains(m_db->metadata()->recycleBin()->name())); + + // Go back to the root group clickIndex(groupView->model()->index(0, 0), groupView, Qt::LeftButton); QCOMPARE(groupView->currentGroup(), m_db->rootGroup()); } From 6f9907a3cbf3d2c7119e37ba8b98731590d5ad0e Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 12 Jan 2020 09:15:42 -0500 Subject: [PATCH 08/13] Fix clearing clipboad on Gnome * Prefer clearing clipboard by explicitly setting the clipboard to an empty string. Qt's QClipboard::clear() method is unreliable under X11 environment. * Fixes #4126 --- src/gui/Clipboard.cpp | 28 +++++++++++++--------------- src/gui/Clipboard.h | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/gui/Clipboard.cpp b/src/gui/Clipboard.cpp index a0978bfd4e..10f9595319 100644 --- a/src/gui/Clipboard.cpp +++ b/src/gui/Clipboard.cpp @@ -44,20 +44,22 @@ Clipboard::Clipboard(QObject* parent) connect(qApp, SIGNAL(aboutToQuit()), SLOT(clearCopiedText())); } -void Clipboard::setText(const QString& text) +void Clipboard::setText(const QString& text, bool clear) { - QClipboard* clipboard = QApplication::clipboard(); + auto* clipboard = QApplication::clipboard(); + if (!clipboard) { + qWarning("Unable to access the clipboard."); + return; + } - QMimeData* mime = new QMimeData; + auto* mime = new QMimeData; #ifdef Q_OS_MACOS mime->setText(text); mime->setData("application/x-nspasteboard-concealed-type", text.toUtf8()); clipboard->setMimeData(mime, QClipboard::Clipboard); #else - const QString secretStr = "secret"; - QByteArray secretBa = secretStr.toUtf8(); mime->setText(text); - mime->setData("x-kde-passwordManagerHint", secretBa); + mime->setData("x-kde-passwordManagerHint", QByteArrayLiteral("secret")); clipboard->setMimeData(mime, QClipboard::Clipboard); if (clipboard->supportsSelection()) { @@ -65,7 +67,7 @@ void Clipboard::setText(const QString& text) } #endif - if (config()->get("security/clearclipboard").toBool()) { + if (clear && config()->get("security/clearclipboard").toBool()) { int timeout = config()->get("security/clearclipboardtimeout").toInt(); if (timeout > 0) { m_lastCopied = text; @@ -84,19 +86,15 @@ void Clipboard::clearCopiedText() void Clipboard::clearClipboard() { - QClipboard* clipboard = QApplication::clipboard(); - + auto* clipboard = QApplication::clipboard(); if (!clipboard) { qWarning("Unable to access the clipboard."); return; } - if (clipboard->text(QClipboard::Clipboard) == m_lastCopied) { - clipboard->clear(QClipboard::Clipboard); - } - - if (clipboard->supportsSelection() && (clipboard->text(QClipboard::Selection) == m_lastCopied)) { - clipboard->clear(QClipboard::Selection); + if (m_lastCopied == clipboard->text(QClipboard::Clipboard) + || m_lastCopied == clipboard->text(QClipboard::Selection)) { + setText("", false); } m_lastCopied.clear(); diff --git a/src/gui/Clipboard.h b/src/gui/Clipboard.h index 2748ba6da3..7465f30a54 100644 --- a/src/gui/Clipboard.h +++ b/src/gui/Clipboard.h @@ -32,7 +32,7 @@ class Clipboard : public QObject Q_OBJECT public: - void setText(const QString& text); + void setText(const QString& text, bool clear = true); static Clipboard* instance(); From d2e76058cd6542cc5d69589cb0f2b4de48d404b6 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Mon, 13 Jan 2020 11:26:45 +0200 Subject: [PATCH 09/13] Fix base domain matching --- src/browser/BrowserService.cpp | 2 +- tests/TestBrowser.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 8c451bf1ea..f7ae2858a5 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -1053,7 +1053,7 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& url, cons } // Filter to match hostname in URL field - if (siteQUrl.host().endsWith(entryQUrl.host())) { + if (siteQUrl.host().endsWith(entryQUrl.host()) && baseDomain(siteQUrl.host()) == baseDomain(entryQUrl.host())) { return true; } diff --git a/tests/TestBrowser.cpp b/tests/TestBrowser.cpp index 8da2a28964..5ddb5e8982 100644 --- a/tests/TestBrowser.cpp +++ b/tests/TestBrowser.cpp @@ -298,7 +298,8 @@ void TestBrowser::testSubdomainsAndPaths() "http://login.github.com/pathtonowhere", ".github.com", // Invalid URL "www.github.com/", - "https://github" // Invalid URL + "https://github", // Invalid URL + "https://hub.com" // Should not return }; createEntries(urls, root); From 08a911466e430d2dca31409e2e0a345629408fe5 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Tue, 14 Jan 2020 10:05:24 +0200 Subject: [PATCH 10/13] Simplify the comparison --- src/browser/BrowserService.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index f7ae2858a5..42def203a2 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -1052,8 +1052,13 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& url, cons return false; } - // Filter to match hostname in URL field - if (siteQUrl.host().endsWith(entryQUrl.host()) && baseDomain(siteQUrl.host()) == baseDomain(entryQUrl.host())) { + // Match the base domain + if (baseDomain(siteQUrl.host()) != baseDomain(entryQUrl.host())) { + return false; + } + + // Match the subdomains with the limited wildcard + if (siteQUrl.host().endsWith(entryQUrl.host())) { return true; } From dc37537797096ec34ff1e0d8a3a0cc5ef98bf4a4 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 10 Jan 2020 22:31:56 -0500 Subject: [PATCH 11/13] Prevent proxy host from blocking application shutdown * Fix #4079 - proxy host would get stuck trying to read characters from stdin. Switch to "readsome" which does not block if there are not enough characters to read. Added a longer delay to slow the checking loop down. --- src/browser/NativeMessagingBase.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/NativeMessagingBase.cpp b/src/browser/NativeMessagingBase.cpp index b44875b147..208d28a1ee 100644 --- a/src/browser/NativeMessagingBase.cpp +++ b/src/browser/NativeMessagingBase.cpp @@ -106,9 +106,9 @@ void NativeMessagingBase::readNativeMessages() quint32 length = 0; while (m_running.load() != 0 && !std::cin.eof()) { length = 0; - std::cin.read(reinterpret_cast(&length), 4); + std::cin.readsome(reinterpret_cast(&length), 4); readStdIn(length); - QThread::msleep(1); + QThread::msleep(100); } #endif } From 606661b2717b2839a9f6dc402b593f9909d74918 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 19 Jan 2020 21:41:40 +0100 Subject: [PATCH 12/13] Update CHANGELOG --- CHANGELOG.md | 17 +++++++++++++++++ CMakeLists.txt | 2 +- share/linux/org.keepassxc.KeePassXC.appdata.xml | 14 ++++++++++++++ snap/snapcraft.yaml | 2 +- 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ecd5685b..08b5a51e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 2.5.3 (2020-01-19) + +### Fixed + +- Fix a possible database lockout when removing a YubiKey from a KDBX 3.1 database [#4147] +- Fix crash if Auto-Type is performed on a new entry [#4150] +- Fix crash when all entries are deleted from a group [#4156] +- Improve the reliability of clipboard clearing on Gnome [#4165] +- Do not check cmd:// URLs for valid URL syntax anymore [#4172] +- Prevent unnecessary merges for databases on network shares [#4153] +- Browser: Prevent native messaging proxy from blocking application shutdown [#4155] +- Browser: Improve website URL matching [#4134, #4177] + +### Added + +- Browser: Enable support for Chromium-based Edge Browser [#3359] + ## 2.5.2 (2020-01-04) ### Added diff --git a/CMakeLists.txt b/CMakeLists.txt index c2f9b5bfe8..ac4c8a9ac6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,7 @@ endif() set(KEEPASSXC_VERSION_MAJOR "2") set(KEEPASSXC_VERSION_MINOR "5") -set(KEEPASSXC_VERSION_PATCH "2") +set(KEEPASSXC_VERSION_PATCH "3") set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}") set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds") diff --git a/share/linux/org.keepassxc.KeePassXC.appdata.xml b/share/linux/org.keepassxc.KeePassXC.appdata.xml index e1fe399364..f670ebce93 100644 --- a/share/linux/org.keepassxc.KeePassXC.appdata.xml +++ b/share/linux/org.keepassxc.KeePassXC.appdata.xml @@ -50,6 +50,20 @@ + + +
    +
  • Fix a possible database lockout when removing a YubiKey from a KDBX 3.1 database [#4147]
  • +
  • Fix crash if Auto-Type is performed on a new entry [#4150]
  • +
  • Fix crash when all entries are deleted from a group [#4156]
  • +
  • Improve the reliability of clipboard clearing on Gnome [#4165]
  • +
  • Do not check cmd:// URLs for valid URL syntax anymore [#4172]
  • +
  • Prevent unnecessary merges for databases on network shares [#4153]
  • +
  • Browser: Prevent native messaging proxy from blocking application shutdown [#4155]
  • +
  • Browser: Improve website URL matching [#4134, #4177]
  • +
+
+
    diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 2c22418f2c..d30349ae65 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: keepassxc -version: 2.5.2 +version: 2.5.3 grade: stable summary: Community-driven port of the Windows application “KeePass Password Safe” description: | From 27dbc152f3fe68379a3fb568756b5a025f39ed82 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 19 Jan 2020 21:43:15 +0100 Subject: [PATCH 13/13] Update translations --- share/translations/keepassx_ca.ts | 72 ++++++++++----------- share/translations/keepassx_de.ts | 2 +- share/translations/keepassx_en.ts | 12 ++-- share/translations/keepassx_nl_NL.ts | 2 +- share/translations/keepassx_tr.ts | 96 +++++++++++++++------------- 5 files changed, 96 insertions(+), 88 deletions(-) diff --git a/share/translations/keepassx_ca.ts b/share/translations/keepassx_ca.ts index beadb94e4c..b08f90a0a4 100644 --- a/share/translations/keepassx_ca.ts +++ b/share/translations/keepassx_ca.ts @@ -1112,7 +1112,7 @@ Please consider generating a new key file. Select key file - Seleccioneu el fitxer de clau + Selecciona el fitxer de clau Failed to open key file: %1 @@ -1195,7 +1195,7 @@ To prevent this error from appearing, you must go to "Database Settings / S Enter Additional Credentials (if any): - + Introduïu credencials addionals (si cal): <p>You can use a hardware security key such as a <strong>YubiKey</strong> or <strong>OnlyKey</strong> with slots configured for HMAC-SHA1.</p> @@ -1216,7 +1216,7 @@ To prevent this error from appearing, you must go to "Database Settings / S Select key file... - + Selecciona el fitxer clau... Cannot use database file as key file @@ -2541,7 +2541,7 @@ Voleu deshabilitar el desat segur i provar-ho un altre cop? Toggle password generator - + Commuta el generador de contrasenyes Password field @@ -2829,7 +2829,7 @@ Supported extensions are: %1. Toggle password generator - + Commuta el generador de contrasenyes Clear fields @@ -3468,7 +3468,7 @@ You can enable the DuckDuckGo website icon service in the security section of th Downloading... - + Descarregant... Ok @@ -3480,7 +3480,7 @@ You can enable the DuckDuckGo website icon service in the security section of th Download Failed - + La descàrrega ha fallat Downloading favicons (%1/%2)... @@ -3897,7 +3897,7 @@ Line %2, column %3 Import KeePass1 Database - + Importa una base de dades de KeePass 1 @@ -4093,7 +4093,7 @@ If this reoccurs, then your database file may be corrupt. Imported from - + Importat de Exported to @@ -4195,11 +4195,11 @@ Message: %2 Select a key file - Seleccioneu un arxiu clau + Selecciona un fitxer clau Key file selection - Selecció del fitxer clau + Selecciona el fitxer clau... Browse for key file @@ -4267,7 +4267,7 @@ Are you sure you want to continue with this file? &About - &Sobre + Quant &a &Open database... @@ -4412,7 +4412,7 @@ We recommend you use the AppImage available on our downloads page. &Import - & Importa + &Importa Copy att&ribute... @@ -4480,7 +4480,7 @@ We recommend you use the AppImage available on our downloads page. Open &URL - + Obre la &URL KeePass 1 database... @@ -4488,7 +4488,7 @@ We recommend you use the AppImage available on our downloads page. Import a KeePass 1 database - + Importa una base de dades de KeePass 1 CSV file... @@ -4530,7 +4530,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens &Check for Updates... - + &Comprova si hi ha actualitzacions... Downlo&ad all favicons @@ -4538,19 +4538,19 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens Sort &A-Z - + Ordena &A-Z Sort &Z-A - + Ordena &Z-A &Password Generator - + Generador de contrasenyes Download favicon - Descarregua el favicon + Descarrega el favicon &Export to HTML file... @@ -4566,15 +4566,15 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens &Getting Started - + Primers passos Open Getting Started Guide PDF - + Obre el PDF de la guia Primers passos &Online Help... - + Ajuda &online... Go to online documentation (opens browser) @@ -4582,15 +4582,15 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens &User Guide - + Guia d'&Usuari Open User Guide PDF - + Obre el PDF de la Guia d'usuari &Keyboard Shortcuts - + Dreceres de teclat @@ -4944,7 +4944,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens Toggle password generator - + Commuta la generació de contrasenyes @@ -6151,7 +6151,7 @@ Nucli: %3 %4 Successfully imported database. - + La base de dades s'ha importat correctament. Unknown command %1 @@ -6438,7 +6438,7 @@ Nucli: %3 %4 Enable KeepassXC Freedesktop.org Secret Service integration - + Habilita la integració amb el Freedesktop.org Secret Service General @@ -6446,7 +6446,7 @@ Nucli: %3 %4 Show notification when credentials are requested - + Mostra una notificació quan es demanen credencials <html><head/><body><p>If recycle bin is enabled for the database, entries will be moved to recycle bin directly. Otherwise, they will be deleted without confirmation.</p><p>You will still be prompted if any entries are referenced by others.</p></body></html> @@ -6454,7 +6454,7 @@ Nucli: %3 %4 Don't confirm when entries are deleted by clients. - + No confirmar quan les entrades són esborrades pel clients. Exposed database groups: @@ -6470,19 +6470,19 @@ Nucli: %3 %4 Manage - + Gestiona Authorization - + Autorització These applications are currently connected: - + Aquestes aplicacions estan ara connectades: Application - + Aplicació Disconnect @@ -6650,7 +6650,7 @@ Nucli: %3 %4 Only show warnings and errors - + Mostra només els avisos i els errors Key diff --git a/share/translations/keepassx_de.ts b/share/translations/keepassx_de.ts index 29b765a6ba..4446473c8e 100644 --- a/share/translations/keepassx_de.ts +++ b/share/translations/keepassx_de.ts @@ -7052,7 +7052,7 @@ Example: JBSWY3DPEHPK3PXP You're up-to-date! - Version aktuel + Version aktuell KeePassXC %1 is currently the newest version available diff --git a/share/translations/keepassx_en.ts b/share/translations/keepassx_en.ts index 581eeb27c7..3b9ae00fc6 100644 --- a/share/translations/keepassx_en.ts +++ b/share/translations/keepassx_en.ts @@ -739,10 +739,6 @@ Please select the correct database for saving credentials. Please see special instructions for browser extension use below - - KeePassXC-Browser is needed for the browser integration to work. <br />Download it for %1 and %2. %3 - - &Brave @@ -787,6 +783,14 @@ Please select the correct database for saving credentials. <b>Warning</b>, the keepassxc-proxy application was not found!<br />Please check the KeePassXC installation directory or confirm the custom path in advanced options.<br />Browser integration WILL NOT WORK without the proxy application.<br />Expected Path: %1 + + &Edge + + + + KeePassXC-Browser is needed for the browser integration to work. <br />Download it for %1 and %2 and %3. %4 + + BrowserService diff --git a/share/translations/keepassx_nl_NL.ts b/share/translations/keepassx_nl_NL.ts index a2ed92b0e4..2fac6e4513 100644 --- a/share/translations/keepassx_nl_NL.ts +++ b/share/translations/keepassx_nl_NL.ts @@ -4519,7 +4519,7 @@ Wij raden je aan om de AppImage te gebruiken welke beschikbaar is op onze downlo Perform &Auto-Type - Uitvoeren &Auto-Type + &Auto-type uitvoeren Open &URL diff --git a/share/translations/keepassx_tr.ts b/share/translations/keepassx_tr.ts index 9567ea4e93..78c8592e81 100644 --- a/share/translations/keepassx_tr.ts +++ b/share/translations/keepassx_tr.ts @@ -878,7 +878,11 @@ Mevcut ayarlarınızı şimdi taşımak ister misiniz? Give the connection a unique name or ID, for example: chrome-laptop. - + Aşağıdaki veritabanı için bir ilişkilendirme isteği aldınız: +%1 + +Bağlantıya benzersiz bir ad veya kimlik verin, örneğin: +linux-laptop. @@ -990,7 +994,7 @@ chrome-laptop. Text qualification - + Metin niteliği Field separation @@ -1227,7 +1231,7 @@ To prevent this error from appearing, you must go to "Database Settings / S Cannot use database file as key file - + Veritabanı dosyası anahtar dosyası olarak kullanılamaz You cannot use your database file as a key file. @@ -1389,11 +1393,11 @@ Tarayıcı eklentisiyle uyumluluğu korumak için bu gereklidir. Stored browser keys - + Saklanan tarayıcı tuşları Remove selected key - + Seçili anahtarı kaldır @@ -1539,11 +1543,11 @@ Eğer bu sayı ile devam ederseniz, veritabanınız çok kolay çözülerek kır Change existing decryption time - + Mevcut şifre çözme süresini değiştir Decryption time in seconds - + Saniyede şifre çözme süresi Database format @@ -1551,11 +1555,11 @@ Eğer bu sayı ile devam ederseniz, veritabanınız çok kolay çözülerek kır Encryption algorithm - + Şifreleme algoritması Key derivation function - + Anahtar türetme işlevi Transform rounds @@ -1567,14 +1571,14 @@ Eğer bu sayı ile devam ederseniz, veritabanınız çok kolay çözülerek kır Parallelism - + Benzerlik DatabaseSettingsWidgetFdoSecrets Exposed Entries - + Maruz Kalan Girdiler Don't e&xpose this database @@ -1582,7 +1586,7 @@ Eğer bu sayı ile devam ederseniz, veritabanınız çok kolay çözülerek kır Expose entries &under this group: - + Bu kümenin &altındaki girdileri göster: Enable fd.o Secret Service to access these settings. @@ -1653,11 +1657,11 @@ Eğer bu sayı ile devam ederseniz, veritabanınız çok kolay çözülerek kır Maximum size of history per entry - + Girdi başına azami geçmiş boyutu Delete Recycle Bin - + Geri Dönüşüm Kutusunu Sil Do you want to delete the current recycle bin and all its contents? @@ -1767,7 +1771,7 @@ Parola olmadan devam etmek istediğinize emin misiniz? Hover over lines with error icons for further information. - + Daha fazla bilgi için farenizi hata simgeli satırların üzerine getirin. Name @@ -1803,7 +1807,7 @@ Parola olmadan devam etmek istediğinize emin misiniz? no - + hayır The database was modified, but the changes have not yet been saved to disk. @@ -1811,15 +1815,15 @@ Parola olmadan devam etmek istediğinize emin misiniz? Number of groups - + Küme sayısı Number of entries - + Girdilerin sayısı Number of expired entries - + Süresi dolmuş girdi sayısı The database contains entries that have expired. @@ -1827,35 +1831,35 @@ Parola olmadan devam etmek istediğinize emin misiniz? Unique passwords - + Benzersiz parolalar Non-unique passwords - + Benzersiz olmayan parolalar More than 10% of passwords are reused. Use unique passwords when possible. - + Parolaların %10'undan fazlası yeniden kullanılır. Mümkünse benzersiz parolalar kullanın. Maximum password reuse - + Azami parola kullanımı Some passwords are used more than three times. Use unique passwords when possible. - + Bazı parolalar üç kereden fazla kullanılır. Mümkünse benzersiz parolalar kullanın. Number of short passwords - + Kısa parola sayısı Recommended minimum password length is at least 8 characters. - + Önerilen asgari parola uzunluğu en az 8 karakterdir. Number of weak passwords - + Zayıf parola sayısı Recommend using long, randomized passwords with a rating of 'good' or 'excellent'. @@ -1971,7 +1975,7 @@ Bu kesinlikle bir hatadır, lütfen geliştiricilere bildirin. You are about to export your database to an unencrypted file. This will leave your passwords and sensitive information vulnerable! Are you sure you want to continue? - + Veritabanınızı şifrelenmemiş bir dosyaya vermek üzeresiniz. Bu, parolalarınızı ve hassas bilgilerinizi savunmasız bırakacaktır! Devam etmek istediğine emin misin? @@ -2350,7 +2354,7 @@ Güvenli kaydetme devre dışı bırakılsın ve tekrar denensin mi? Show a protected attribute - + Korumalı bir özelliği göster Foreground color selection @@ -2429,7 +2433,7 @@ Güvenli kaydetme devre dışı bırakılsın ve tekrar denensin mi? Custom Auto-Type sequence for this window - + Bu pencere için özel Otomatik Yazım sırası @@ -2578,7 +2582,7 @@ Güvenli kaydetme devre dışı bırakılsın ve tekrar denensin mi? Expiration Presets - + Son Kullanma Önayarları Expiration presets @@ -2678,11 +2682,11 @@ Güvenli kaydetme devre dışı bırakılsın ve tekrar denensin mi? Remove key from agent after specified seconds - + Belirtilen saniye sonra anahtarı aracıdan kaldır Browser for key file - + Anahtar dosyasına göz at External key file @@ -2690,7 +2694,7 @@ Güvenli kaydetme devre dışı bırakılsın ve tekrar denensin mi? Select attachment file - + Ek dosyasını seç @@ -3070,7 +3074,7 @@ Bu etkilenen eklentilerin bozulmasına neden olabilir. Unique ID - + Benzersiz ID Plugin data @@ -4124,11 +4128,11 @@ If this reoccurs, then your database file may be corrupt. Exported to - + Dışa aktarıldı Synchronized with - + Eşitlendi @@ -4803,7 +4807,7 @@ Bazı hatalar ve küçük sorunlar olabilir, bu sürüm şu an dağıtımda değ OpVaultReader Directory .opvault must exist - + Dizin .opvault mevcut olmalıdır Directory .opvault must be readable @@ -6069,7 +6073,7 @@ MİB mimarisi: %2 Cannot generate a password and prompt at the same time! - + Parola ve komut istemi aynı anda oluşturulamaz! Adds a new group to a database. @@ -6117,7 +6121,7 @@ MİB mimarisi: %2 Display this help. - + Bu yardımı görüntüle. Yubikey slot used to encrypt the database. @@ -6189,7 +6193,7 @@ MİB mimarisi: %2 Successfully imported database. - + Veritabanı başarıyla içe aktarıldı. Unknown command %1 @@ -6245,7 +6249,7 @@ MİB mimarisi: %2 Path of the group to remove. - + Kaldırılacak kümenin yolu. Cannot remove root group from database. @@ -6285,7 +6289,7 @@ MİB mimarisi: %2 Enter password to encrypt database (optional): - + Veritabanını şifrelemek için parola gir (isteğe bağlı): HIBP file, line %1: parse error @@ -6496,7 +6500,7 @@ MİB mimarisi: %2 Exposed database groups: - + Maruz kalan veritabanı kümeleri: File Name @@ -6532,7 +6536,7 @@ MİB mimarisi: %2 Edit database settings - + Veritabanı ayarlarını düzenle Unlock database @@ -6700,7 +6704,7 @@ MİB mimarisi: %2 Generate new certificate - + Yeni sertifika oluştur Import existing certificate