diff --git a/components/password_manager/core/browser/password_reuse_detector_impl_unittest.cc b/components/password_manager/core/browser/password_reuse_detector_impl_unittest.cc index 5b4c81c6e01ef..10fbc31492b56 100644 --- a/components/password_manager/core/browser/password_reuse_detector_impl_unittest.cc +++ b/components/password_manager/core/browser/password_reuse_detector_impl_unittest.cc @@ -231,6 +231,87 @@ TEST_F(PasswordReuseDetectorTest, NoPSLMatchReuseEvent) { &mockConsumer); } +// Demonstrates that an IP-literal saved credential collides with an IP-literal +// phishing destination via the empty eTLD+1, silently suppressing the reuse +// event. +// +// Setup: user has saved "RouterPass123!" for both their home router +// (http://192.168.1.1/) and their bank (https://bank.example.com/). A phishing +// page is served from a public IP literal (http://93.184.216.34/). When the +// user types "RouterPass123!" there, the detector SHOULD report reuse of the +// bank.example.com credential. Because GetRegistryControlledDomain() returns "" +// for both 192.168.1.1 and 93.184.216.34, the "" == "" match short-circuits the +// entire credential set and no reuse is reported. +TEST_F(PasswordReuseDetectorTest, IPLiteralEmptyETLDPlus1CollisionMasksReuse) { + PasswordReuseDetectorImpl reuse_detector; + std::vector test_data = { + {"http://192.168.1.1/", "admin", "RouterPass123!", + PasswordForm::Store::kProfileStore}, + {"https://bank.example.com/", "alice", "RouterPass123!", + PasswordForm::Store::kProfileStore}, + }; + reuse_detector.OnGetPasswordStoreResults(GetForms(test_data)); + MockPasswordReuseDetectorConsumer mockConsumer; + + // CONTROL: domain-based phishing destination — reuse IS detected. + // This proves the detector and the seeded credentials work correctly. + { + const std::vector expected = { + {"http://192.168.1.1/", GURL("http://192.168.1.1/"), u"admin", + PasswordForm::Store::kProfileStore}, + {"https://bank.example.com/", GURL("https://bank.example.com/"), + u"alice", PasswordForm::Store::kProfileStore}}; + EXPECT_CALL( + mockConsumer, + OnReuseCheckDone(true, strlen("RouterPass123!"), + Matches(NO_GAIA_OR_ENTERPRISE_REUSE), + UnorderedElementsAreArray(expected), 2, _, _)); + reuse_detector.CheckReuse(u"RouterPass123!", "https://phishing.test/", + &mockConsumer); + testing::Mock::VerifyAndClearExpectations(&mockConsumer); + } + + // BUG: public-IP-literal phishing destination — reuse is NOT detected. + // 93.184.216.34 is an unrelated, publicly-routable address; this is reuse of + // both the router and bank.example.com passwords and must be reported. The + // empty-string eTLD+1 collision with the 192.168.1.1 credential causes the + // whole set to be skipped. PhishGuard never sees the event. + { + const std::vector expected = { + {"http://192.168.1.1/", GURL("http://192.168.1.1/"), u"admin", + PasswordForm::Store::kProfileStore}, + {"https://bank.example.com/", GURL("https://bank.example.com/"), + u"alice", PasswordForm::Store::kProfileStore}}; + EXPECT_CALL(mockConsumer, + OnReuseCheckDone(true, strlen("RouterPass123!"), + Matches(NO_GAIA_OR_ENTERPRISE_REUSE), + UnorderedElementsAreArray(expected), 2, _, _)); + reuse_detector.CheckReuse(u"RouterPass123!", "http://93.184.216.34/", + &mockConsumer); + testing::Mock::VerifyAndClearExpectations(&mockConsumer); + } +} + +// Minimal variant: a single IP-literal saved credential is enough to suppress +// reuse detection on every IP-literal page, because both sides of the +// comparison are "". +TEST_F(PasswordReuseDetectorTest, IPLiteralSavedCredSuppressesIPLiteralReuse) { + PasswordReuseDetectorImpl reuse_detector; + std::vector test_data = { + {"http://192.168.1.1/", "admin", "RouterPass123!", + PasswordForm::Store::kProfileStore}, + }; + reuse_detector.OnGetPasswordStoreResults(GetForms(test_data)); + MockPasswordReuseDetectorConsumer mockConsumer; + + // 192.168.1.1 and 93.184.216.34 are completely different hosts. This is + // reuse and should be reported. + EXPECT_CALL(mockConsumer, + OnReuseCheckDone(true, strlen("RouterPass123!"), _, _, 1, _, _)); + reuse_detector.CheckReuse(u"RouterPass123!", "http://93.184.216.34/", + &mockConsumer); +} + TEST_F(PasswordReuseDetectorTest, TooShortPasswordNoReuseEvent) { PasswordReuseDetectorImpl reuse_detector; reuse_detector.OnGetPasswordStoreResults(