blob: b600c1bd6aa0e21d6f2a2a735a83fff000b7dd47 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/password_manager/core/browser/password_reuse_detector.h"
#include <algorithm>
#include <utility>
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/hash_password_manager.h"
#include "components/password_manager/core/browser/password_reuse_detector_consumer.h"
#include "components/password_manager/core/browser/psl_matching_helper.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/gaia_urls.h"
#include "url/origin.h"
using url::Origin;
namespace password_manager {
namespace {
// Minimum number of characters in a password for finding it as password reuse.
// It does not make sense to consider short strings for password reuse, since it
// is quite likely that they are parts of common words.
constexpr size_t kMinPasswordLengthToCheck = 8;
// Returns true iff |suffix_candidate| is a suffix of |str|.
bool IsSuffix(const base::string16& str,
const base::string16& suffix_candidate) {
if (str.size() < suffix_candidate.size())
return false;
return std::equal(suffix_candidate.rbegin(), suffix_candidate.rend(),
str.rbegin());
}
} // namespace
bool ReverseStringLess::operator()(const base::string16& lhs,
const base::string16& rhs) const {
return std::lexicographical_compare(lhs.rbegin(), lhs.rend(), rhs.rbegin(),
rhs.rend());
}
PasswordReuseDetector::PasswordReuseDetector() {}
PasswordReuseDetector::~PasswordReuseDetector() {}
void PasswordReuseDetector::OnGetPasswordStoreResults(
std::vector<std::unique_ptr<autofill::PasswordForm>> results) {
for (const auto& form : results)
AddPassword(*form);
}
void PasswordReuseDetector::OnLoginsChanged(
const PasswordStoreChangeList& changes) {
for (const auto& change : changes) {
if (change.type() == PasswordStoreChange::ADD ||
change.type() == PasswordStoreChange::UPDATE)
AddPassword(change.form());
}
}
void PasswordReuseDetector::CheckReuse(
const base::string16& input,
const std::string& domain,
PasswordReuseDetectorConsumer* consumer) {
DCHECK(consumer);
if (input.size() < kMinPasswordLengthToCheck)
return;
size_t sync_reused_password_length = CheckSyncPasswordReuse(input, domain);
std::vector<std::string> matching_domains;
size_t saved_reused_password_length =
CheckSavedPasswordReuse(input, domain, &matching_domains);
size_t max_reused_password_length =
std::max(sync_reused_password_length, saved_reused_password_length);
if (max_reused_password_length != 0) {
consumer->OnReuseFound(
max_reused_password_length,
sync_reused_password_length != 0 /* matches_sync_password */,
matching_domains, saved_passwords_);
}
}
size_t PasswordReuseDetector::CheckSyncPasswordReuse(
const base::string16& input,
const std::string& domain) {
if (!sync_password_data_.has_value())
return 0;
const Origin gaia_origin =
Origin::Create(GaiaUrls::GetInstance()->gaia_url().GetOrigin());
if (Origin::Create(GURL(domain)).IsSameOriginWith(gaia_origin))
return 0;
if (input.size() < sync_password_data_->length)
return 0;
size_t offset = input.size() - sync_password_data_->length;
base::string16 reuse_candidate = input.substr(offset);
if (HashPasswordManager::CalculateSyncPasswordHash(
reuse_candidate, sync_password_data_->salt) ==
sync_password_data_->hash) {
return reuse_candidate.size();
}
return 0;
}
size_t PasswordReuseDetector::CheckSavedPasswordReuse(
const base::string16& input,
const std::string& domain,
std::vector<std::string>* matching_domains_out) {
const std::string registry_controlled_domain =
GetRegistryControlledDomain(GURL(domain));
// More than one password call match |input| if they share a common suffix
// with |input|. Collect the set of domains for all matches.
std::set<std::string> matching_domains_set;
// The longest password match is kept for metrics.
size_t longest_match_len = 0;
for (auto passwords_iterator = FindFirstSavedPassword(input);
passwords_iterator != passwords_.end();
passwords_iterator = FindNextSavedPassword(input, passwords_iterator)) {
const std::set<std::string>& domains = passwords_iterator->second;
DCHECK(!domains.empty());
// If the page's URL matches a saved domain for this password,
// this isn't password-reuse.
if (domains.find(registry_controlled_domain) != domains.end())
continue;
matching_domains_set.insert(domains.begin(), domains.end());
DCHECK(passwords_iterator->first.size());
if (longest_match_len < passwords_iterator->first.size())
longest_match_len = passwords_iterator->first.size();
}
if (matching_domains_set.size() == 0)
return 0;
*matching_domains_out = std::vector<std::string>(matching_domains_set.begin(),
matching_domains_set.end());
return longest_match_len;
}
void PasswordReuseDetector::UseSyncPasswordHash(
base::Optional<SyncPasswordData> sync_password_data) {
sync_password_data_ = std::move(sync_password_data);
}
void PasswordReuseDetector::ClearSyncPasswordHash() {
sync_password_data_.reset();
}
void PasswordReuseDetector::AddPassword(const autofill::PasswordForm& form) {
if (form.password_value.size() < kMinPasswordLengthToCheck)
return;
GURL signon_realm(form.signon_realm);
const std::string domain = GetRegistryControlledDomain(signon_realm);
std::set<std::string>& domains = passwords_[form.password_value];
if (domains.find(domain) == domains.end()) {
++saved_passwords_;
domains.insert(domain);
}
}
PasswordReuseDetector::passwords_iterator
PasswordReuseDetector::FindFirstSavedPassword(const base::string16& input) {
// Keys in |passwords_| are ordered by lexicographical order of reversed
// strings. In order to check a password reuse a key of |passwords_| that is
// a suffix of |input| should be found. The longest such key should be the
// largest key in the |passwords_| keys order that is equal or smaller to
// |input|. There may be more, shorter, matches as well -- call
// FindNextSavedPassword(it) to find the next one.
if (passwords_.empty())
return passwords_.end();
// lower_bound returns the first key that is bigger or equal to input.
passwords_iterator it = passwords_.lower_bound(input);
if (it != passwords_.end() && it->first == input) {
// If the key is equal then a saved password is found.
return it;
}
// Otherwise the previous key is a candidate for password reuse.
return FindNextSavedPassword(input, it);
}
PasswordReuseDetector::passwords_iterator
PasswordReuseDetector::FindNextSavedPassword(
const base::string16& input,
PasswordReuseDetector::passwords_iterator it) {
if (it == passwords_.begin())
return passwords_.end();
--it;
return IsSuffix(input, it->first) ? it : passwords_.end();
}
} // namespace password_manager