| // Copyright 2020 The Chromium OS 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 "cryptohome/fingerprint_manager.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/logging.h> |
| #include <brillo/dbus/dbus_object.h> |
| #include <chromeos/dbus/service_constants.h> |
| |
| namespace cryptohome { |
| |
| namespace { |
| |
| struct AuthScanDBusResult { |
| uint32_t scan_result; |
| std::vector<std::string> user_ids; |
| }; |
| |
| // Parses OnAuthScanDone signal from biod. Returns true if parsing succeeds. |
| bool ParseDBusSignal(dbus::Signal* signal, AuthScanDBusResult* result) { |
| dbus::MessageReader signal_reader(signal); |
| |
| if (!signal_reader.PopUint32(&result->scan_result)) |
| return false; |
| |
| // Parsing is completed if the scan result isn't success. |
| if (result->scan_result != biod::ScanResult::SCAN_RESULT_SUCCESS) |
| return true; |
| |
| dbus::MessageReader matches_reader(nullptr); |
| if (!signal_reader.PopArray(&matches_reader)) |
| return false; |
| |
| while (matches_reader.HasMoreData()) { |
| dbus::MessageReader entry_reader(nullptr); |
| if (!matches_reader.PopDictEntry(&entry_reader)) |
| return false; |
| |
| std::string user_id; |
| if (!entry_reader.PopString(&user_id)) |
| return false; |
| |
| result->user_ids.emplace_back(user_id); |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<FingerprintManager> FingerprintManager::Create( |
| const scoped_refptr<dbus::Bus>& bus, const dbus::ObjectPath& path) { |
| auto fingerprint_manager = std::make_unique<FingerprintManager>(); |
| if (!fingerprint_manager->Initialize(bus, path)) |
| return nullptr; |
| return fingerprint_manager; |
| } |
| |
| FingerprintManager::FingerprintManager() |
| : proxy_(nullptr), |
| connected_to_auth_scan_done_signal_(false), |
| weak_factory_(this), |
| mount_thread_id_(base::PlatformThread::CurrentId()) {} |
| |
| FingerprintManager::~FingerprintManager() = default; |
| |
| bool FingerprintManager::Initialize(const scoped_refptr<dbus::Bus>& bus, |
| const dbus::ObjectPath& path) { |
| default_proxy_ = biod::BiometricsManagerProxyBase::Create(bus, path); |
| if (!default_proxy_) |
| return false; |
| default_proxy_->ConnectToAuthScanDoneSignal( |
| base::BindRepeating(&FingerprintManager::OnAuthScanDone, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&FingerprintManager::OnAuthScanDoneSignalConnected, |
| weak_factory_.GetWeakPtr())); |
| if (!proxy_) |
| proxy_ = default_proxy_.get(); |
| return true; |
| } |
| |
| void FingerprintManager::OnAuthScanDoneSignalConnected( |
| const std::string& interface, const std::string& signal, bool success) { |
| if (!success) { |
| LOG(ERROR) << "Failed to connect to signal " << signal << " on interface " |
| << interface; |
| } |
| // If we fail to connect to the AuthScanDone signal, it makes no sense to do |
| // subsequent operations. |
| connected_to_auth_scan_done_signal_ = success; |
| } |
| |
| void FingerprintManager::Reset() { |
| state_ = State::NO_AUTH_SESSION; |
| current_user_.clear(); |
| auth_scan_done_callback_.Reset(); |
| } |
| |
| const std::string& FingerprintManager::GetCurrentUser() { |
| return current_user_; |
| } |
| |
| base::WeakPtr<FingerprintManager> FingerprintManager::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void FingerprintManager::OnAuthScanDone(dbus::Signal* signal) { |
| VLOG(1) << "Received AuthScanDone signal."; |
| |
| // This method is called if any auth scan operation completes, so we validate |
| // that this signal is expected. |
| if (state_ != State::AUTH_SESSION_OPEN) |
| return; |
| |
| AuthScanDoneResourceManager resource_manager(this); |
| |
| AuthScanDBusResult result; |
| if (!ParseDBusSignal(signal, &result)) { |
| if (auth_scan_done_callback_) { |
| auth_scan_done_callback_.Run( |
| FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED); |
| } |
| state_ = State::AUTH_SESSION_LOCKED; |
| return; |
| } |
| |
| if (result.scan_result != biod::ScanResult::SCAN_RESULT_SUCCESS) { |
| VLOG(1) << "Authentication failed: scan result: " |
| << biod::ScanResultToString( |
| static_cast<biod::ScanResult>(result.scan_result)); |
| ProcessRetry(); |
| return; |
| } |
| |
| if (std::find(result.user_ids.begin(), result.user_ids.end(), |
| current_user_) == result.user_ids.end()) { |
| VLOG(1) << "Authentication failed: not matched."; |
| ProcessRetry(); |
| return; |
| } |
| |
| VLOG(1) << "Authentication succeeded."; |
| if (auth_scan_done_callback_) |
| auth_scan_done_callback_.Run(FingerprintScanStatus::SUCCESS); |
| state_ = State::AUTH_SESSION_LOCKED; |
| } |
| |
| void FingerprintManager::ProcessRetry() { |
| retry_left_--; |
| |
| FingerprintScanStatus error; |
| if (retry_left_ <= 0) { |
| state_ = State::AUTH_SESSION_LOCKED; |
| error = FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED; |
| } else { |
| error = FingerprintScanStatus::FAILED_RETRY_ALLOWED; |
| } |
| if (auth_scan_done_callback_) |
| auth_scan_done_callback_.Run(error); |
| } |
| |
| void FingerprintManager::SetAuthScanDoneCallback( |
| ResultCallback auth_scan_done_callback) { |
| DCHECK(base::PlatformThread::CurrentId() == mount_thread_id_); |
| |
| if (!connected_to_auth_scan_done_signal_) |
| return; |
| |
| // Don't allow any operation if we are not in an auth session. |
| if (state_ != State::AUTH_SESSION_OPEN) { |
| auth_scan_done_callback.Run( |
| FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED); |
| return; |
| } |
| |
| auth_scan_done_callback_ = std::move(auth_scan_done_callback); |
| } |
| |
| void FingerprintManager::SetUserAndRunClientCallback( |
| StartSessionCallback auth_session_start_client_callback, |
| const std::string& user, |
| bool success) { |
| // Set |current_user_| to |user| if auth session started successfully. |
| if (success) { |
| current_user_ = user; |
| retry_left_ = kMaxFingerprintRetries; |
| state_ = State::AUTH_SESSION_OPEN; |
| } else { |
| Reset(); |
| } |
| std::move(auth_session_start_client_callback).Run(success); |
| } |
| |
| void FingerprintManager::StartAuthSessionAsyncForUser( |
| const std::string& user, |
| StartSessionCallback auth_session_start_client_callback) { |
| DCHECK(base::PlatformThread::CurrentId() == mount_thread_id_); |
| |
| if (!connected_to_auth_scan_done_signal_) |
| return; |
| |
| // Disallow starting auth session if another session might be pending. |
| if (state_ != State::NO_AUTH_SESSION) { |
| std::move(auth_session_start_client_callback).Run(false); |
| return; |
| } |
| |
| // Wrapper callback around the client's callback for starting auth session, |
| // so that we can set |current_user_| in addition to running the client's |
| // callback. |
| auto auth_session_start_callback = base::BindOnce( |
| &FingerprintManager::SetUserAndRunClientCallback, base::Unretained(this), |
| std::move(auth_session_start_client_callback), user); |
| |
| proxy_->StartAuthSessionAsync(std::move(auth_session_start_callback)); |
| } |
| |
| void FingerprintManager::EndAuthSession() { |
| // Return an error to any pending call. This is for the case where the client |
| // decides to cancel fingerprint auth before receiving a response from us. |
| if (auth_scan_done_callback_) { |
| auth_scan_done_callback_.Run( |
| FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED); |
| } |
| proxy_->EndAuthSession(); |
| Reset(); |
| } |
| |
| bool FingerprintManager::HasAuthSessionForUser(const std::string& user) { |
| DCHECK(base::PlatformThread::CurrentId() == mount_thread_id_); |
| |
| if (!proxy_ || !connected_to_auth_scan_done_signal_) |
| return false; |
| |
| if (state_ != State::AUTH_SESSION_OPEN || current_user_ != user) |
| return false; |
| |
| return true; |
| } |
| |
| void FingerprintManager::SetProxy(biod::BiometricsManagerProxyBase* proxy) { |
| proxy_ = proxy; |
| } |
| |
| } // namespace cryptohome |