| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| #include <variant> |
| |
| #include "absl/status/status.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/time/time.h" |
| #include "cryptohome/proto_bindings/UserDataAuth.pb.h" |
| #include "dbus/bus.h" |
| #include "missive/proto/record_constants.pb.h" |
| #include "secagentd/common.h" |
| #include "secagentd/device_user.h" |
| #include "secagentd/message_sender.h" |
| #include "secagentd/metrics_sender.h" |
| #include "secagentd/plugins.h" |
| #include "secagentd/policies_features_broker.h" |
| #include "secagentd/proto/security_xdr_events.pb.h" |
| #include "user_data_auth/dbus-proxies.h" |
| |
| namespace secagentd { |
| |
| namespace pb = cros_xdr::reporting; |
| |
| namespace { |
| bool UpdateNumFailedAttempts(const int64_t latest_success, |
| const AuthFactorType auth_factor_type, |
| pb::UserEventAtomicVariant* event) { |
| // Check that timestamp is greater than the most recent |
| // success and auth |
| // factor matches. If so increment number of failed attempts |
| // by 1. |
| if ((event->has_common() && |
| event->common().create_timestamp_us() > latest_success) && |
| (event->has_failure() && event->failure().has_authentication() && |
| event->failure().authentication().auth_factor_size() >= 1 && |
| event->failure().authentication().auth_factor()[0] == |
| auth_factor_type)) { |
| event->mutable_failure()->mutable_authentication()->set_num_failed_attempts( |
| event->failure().authentication().num_failed_attempts() + 1); |
| return true; |
| } |
| return false; |
| } |
| |
| std::optional<std::pair<metrics::EnumMetric<metrics::AuthFactor>, int>> |
| GetEventEnumTypeAndAuthFactor(const pb::UserEventAtomicVariant& atomic_event) { |
| int auth_factor = 0; |
| if (atomic_event.has_logon()) { |
| if (atomic_event.logon().has_authentication() && |
| atomic_event.logon().authentication().auth_factor_size() >= 1) { |
| auth_factor = atomic_event.logon().authentication().auth_factor()[0]; |
| } |
| return std::make_pair(metrics::kLogin, auth_factor); |
| } else if (atomic_event.has_unlock()) { |
| if (atomic_event.unlock().has_authentication() && |
| atomic_event.unlock().authentication().auth_factor_size() >= 1) { |
| auth_factor = atomic_event.unlock().authentication().auth_factor()[0]; |
| } |
| return std::make_pair(metrics::kUnlock, auth_factor); |
| } |
| |
| return std::nullopt; |
| } |
| } // namespace |
| |
| AuthenticationPlugin::AuthenticationPlugin( |
| scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker, |
| scoped_refptr<DeviceUserInterface> device_user, |
| uint32_t batch_interval_s) |
| : weak_ptr_factory_(this), |
| policies_features_broker_(policies_features_broker), |
| device_user_(device_user) { |
| batch_sender_ = std::make_unique<BatchSender<std::monostate, pb::XdrUserEvent, |
| pb::UserEventAtomicVariant>>( |
| base::BindRepeating( |
| [](const cros_xdr::reporting::UserEventAtomicVariant& event) |
| -> std::monostate { |
| // Only the most recent event type will need to be visited |
| // so monostate is used. This makes it so for each type of auth |
| // variation only 1 event is tracked. |
| return std::monostate(); |
| }), |
| message_sender, reporting::Destination::CROS_SECURITY_USER, |
| batch_interval_s); |
| CHECK(message_sender != nullptr); |
| } |
| |
| std::string AuthenticationPlugin::GetName() const { |
| return "Authentication"; |
| } |
| |
| absl::Status AuthenticationPlugin::Activate() { |
| if (is_active_) { |
| return absl::OkStatus(); |
| } |
| |
| batch_sender_->Start(); |
| |
| // Register cryptohome proxy for authentication result. |
| if (!cryptohome_proxy_) { |
| cryptohome_proxy_ = |
| std::make_unique<org::chromium::UserDataAuthInterfaceProxy>( |
| common::GetDBus()); |
| } |
| cryptohome_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable( |
| base::BindOnce( |
| [](org::chromium::UserDataAuthInterfaceProxyInterface* |
| cryptohome_proxy, |
| base::RepeatingCallback<void( |
| const user_data_auth::AuthenticateAuthFactorCompleted&)> |
| signal_callback, |
| dbus::ObjectProxy::OnConnectedCallback on_connected_callback, |
| bool available) { |
| if (!available) { |
| LOG(ERROR) << "Failed to register for " |
| "AuthenticateAuthFactorCompleted signal"; |
| return; |
| } |
| cryptohome_proxy |
| ->RegisterAuthenticateAuthFactorCompletedSignalHandler( |
| std::move(signal_callback), |
| std::move(on_connected_callback)); |
| }, |
| cryptohome_proxy_.get(), |
| base::BindRepeating( |
| &AuthenticationPlugin::OnAuthenticateAuthFactorCompleted, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&AuthenticationPlugin::OnRegistrationResult, |
| weak_ptr_factory_.GetWeakPtr()))); |
| |
| // Register for lock/unlock signals. |
| device_user_->RegisterScreenLockedHandler( |
| base::BindRepeating(&AuthenticationPlugin::OnScreenLock, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&AuthenticationPlugin::OnRegistrationResult, |
| weak_ptr_factory_.GetWeakPtr())); |
| device_user_->RegisterScreenUnlockedHandler( |
| base::BindRepeating(&AuthenticationPlugin::OnScreenUnlock, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&AuthenticationPlugin::OnRegistrationResult, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // Register for login/out signal. |
| device_user_->RegisterSessionChangeListener( |
| base::BindRepeating(&AuthenticationPlugin::OnSessionStateChange, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| is_active_ = true; |
| return absl::OkStatus(); |
| } |
| |
| absl::Status AuthenticationPlugin::Deactivate() { |
| return absl::UnimplementedError( |
| "Deactivate not implemented for AuthenticationPlugin."); |
| } |
| |
| bool AuthenticationPlugin::IsActive() const { |
| return is_active_; |
| } |
| |
| void AuthenticationPlugin::OnScreenLock() { |
| auto screen_lock = std::make_unique<pb::UserEventAtomicVariant>(); |
| screen_lock->mutable_lock(); |
| screen_lock->mutable_common()->set_create_timestamp_us( |
| base::Time::Now().InMillisecondsSinceUnixEpoch() * |
| base::Time::kMicrosecondsPerMillisecond); |
| device_user_->GetDeviceUserAsync( |
| base::BindOnce(&AuthenticationPlugin::OnDeviceUserRetrieved, |
| weak_ptr_factory_.GetWeakPtr(), std::move(screen_lock))); |
| } |
| |
| void AuthenticationPlugin::OnScreenUnlock() { |
| auto screen_unlock = std::make_unique<pb::UserEventAtomicVariant>(); |
| screen_unlock->mutable_common()->set_create_timestamp_us( |
| base::Time::Now().InMillisecondsSinceUnixEpoch() * |
| base::Time::kMicrosecondsPerMillisecond); |
| latest_successful_login_timestamp_ = |
| screen_unlock->common().create_timestamp_us(); |
| |
| auto* authentication = |
| screen_unlock->mutable_unlock()->mutable_authentication(); |
| if (!FillAuthFactor(authentication)) { |
| authentication->clear_auth_factor(); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AuthenticationPlugin::DelayedCheckForAuthSignal, |
| weak_ptr_factory_.GetWeakPtr(), std::move(screen_unlock), |
| authentication), |
| kWaitForAuthFactorS); |
| return; |
| } |
| |
| auth_factor_type_ = |
| AuthFactorType::Authentication_AuthenticationType_AUTH_TYPE_UNKNOWN; |
| device_user_->GetDeviceUserAsync( |
| base::BindOnce(&AuthenticationPlugin::OnDeviceUserRetrieved, |
| weak_ptr_factory_.GetWeakPtr(), std::move(screen_unlock))); |
| } |
| |
| void AuthenticationPlugin::OnSessionStateChange(const std::string& state) { |
| auto log_event = std::make_unique<pb::UserEventAtomicVariant>(); |
| log_event->mutable_common()->set_create_timestamp_us( |
| base::Time::Now().InMillisecondsSinceUnixEpoch() * |
| base::Time::kMicrosecondsPerMillisecond); |
| |
| if (state == kStarted) { |
| latest_successful_login_timestamp_ = |
| log_event->common().create_timestamp_us(); |
| auto* authentication = log_event->mutable_logon()->mutable_authentication(); |
| if (!FillAuthFactor(authentication)) { |
| authentication->clear_auth_factor(); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AuthenticationPlugin::DelayedCheckForAuthSignal, |
| weak_ptr_factory_.GetWeakPtr(), std::move(log_event), |
| authentication), |
| kWaitForAuthFactorS); |
| return; |
| } |
| auth_factor_type_ = |
| AuthFactorType::Authentication_AuthenticationType_AUTH_TYPE_UNKNOWN; |
| } else if (state == kStopped) { |
| log_event->mutable_logoff(); |
| if (signed_in_user_ != device_user::kEmpty) { |
| log_event->mutable_common()->set_device_user(signed_in_user_); |
| } else { |
| log_event->mutable_common()->set_device_user(device_user::kUnknown); |
| } |
| } else if (state == kInit) { |
| device_user_->GetDeviceUserAsync( |
| base::BindOnce(&AuthenticationPlugin::OnFirstSessionStart, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } else { |
| return; |
| } |
| |
| device_user_->GetDeviceUserAsync( |
| base::BindOnce(&AuthenticationPlugin::OnDeviceUserRetrieved, |
| weak_ptr_factory_.GetWeakPtr(), std::move(log_event))); |
| } |
| |
| void AuthenticationPlugin::OnRegistrationResult(const std::string& interface, |
| const std::string& signal, |
| bool success) { |
| if (!success) { |
| LOG(ERROR) << "Callback registration failed for dbus signal: " << signal |
| << " on interface: " << interface; |
| } |
| } |
| |
| void AuthenticationPlugin::OnAuthenticateAuthFactorCompleted( |
| const user_data_auth::AuthenticateAuthFactorCompleted& completed) { |
| if (completed.user_creation()) { |
| // If the proto contains an error indicating that creating the user failed, |
| // ignore the error because no user was signed in. |
| if (completed.has_error_info()) { |
| return; |
| } |
| auth_factor_type_ = |
| AuthFactorType::Authentication_AuthenticationType_AUTH_NEW_USER; |
| return; |
| } |
| |
| auto it = auth_factor_map_.find(completed.auth_factor_type()); |
| if (it == auth_factor_map_.end()) { |
| LOG(ERROR) << "Unknown auth factor type " << completed.auth_factor_type(); |
| auth_factor_type_ = |
| AuthFactorType::Authentication_AuthenticationType_AUTH_TYPE_UNKNOWN; |
| } else { |
| if (completed.has_error_info()) { |
| // When a pin is incorrectly entered two Auth signals are sent on the |
| // lockscreen. One trying the pin and one trying the password. In this |
| // case ignore the password and keep the auth_factor as pin. |
| // TODO(b:305093271): Update logic to handle if password is actually used. |
| if (it->second == |
| AuthFactorType::Authentication_AuthenticationType_AUTH_PIN) { |
| latest_pin_failure_ = base::Time::Now().InMillisecondsSinceUnixEpoch() / |
| base::Time::kMillisecondsPerSecond; |
| last_auth_was_password_ = false; |
| } else if (it->second == |
| AuthFactorType:: |
| Authentication_AuthenticationType_AUTH_PASSWORD) { |
| if (auth_factor_type_ == |
| AuthFactorType::Authentication_AuthenticationType_AUTH_PIN && |
| !last_auth_was_password_ && |
| (base::Time::Now().InMillisecondsSinceUnixEpoch() / |
| base::Time::kMillisecondsPerSecond) - |
| latest_pin_failure_ <= |
| kMaxDelayForLockscreenAttemptsS) { |
| last_auth_was_password_ = true; |
| return; |
| } |
| last_auth_was_password_ = true; |
| } else { |
| last_auth_was_password_ = false; |
| } |
| } |
| auth_factor_type_ = it->second; |
| } |
| |
| if (completed.has_error_info()) { |
| // Record auth factor for failure event. |
| MetricsSender::GetInstance().SendEnumMetricToUMA( |
| metrics::kFailure, static_cast<metrics::AuthFactor>(auth_factor_type_)); |
| if (!batch_sender_->Visit(pb::UserEventAtomicVariant::kFailure, |
| std::monostate(), |
| base::BindOnce(&UpdateNumFailedAttempts, |
| latest_successful_login_timestamp_, |
| auth_factor_type_))) { |
| // Create new event if no matching failure event found and updated. |
| auto failure_event = std::make_unique<pb::UserEventAtomicVariant>(); |
| failure_event->mutable_common()->set_create_timestamp_us( |
| base::Time::Now().InMillisecondsSinceUnixEpoch() * |
| base::Time::kMicrosecondsPerMillisecond); |
| |
| auto* authentication = |
| failure_event->mutable_failure()->mutable_authentication(); |
| authentication->set_num_failed_attempts(1); |
| FillAuthFactor(authentication); |
| OnDeviceUserRetrieved( |
| std::move(failure_event), |
| device_user_->GetUsernameBasedOnAffiliation( |
| completed.username(), completed.sanitized_username()), |
| completed.sanitized_username()); |
| } |
| } |
| } |
| |
| bool AuthenticationPlugin::FillAuthFactor(pb::Authentication* proto) { |
| proto->add_auth_factor(auth_factor_type_); |
| |
| return auth_factor_type_ != |
| AuthFactorType::Authentication_AuthenticationType_AUTH_TYPE_UNKNOWN; |
| } |
| |
| void AuthenticationPlugin::DelayedCheckForAuthSignal( |
| std::unique_ptr<cros_xdr::reporting::UserEventAtomicVariant> xdr_proto, |
| cros_xdr::reporting::Authentication* authentication) { |
| if (FillAuthFactor(authentication)) { |
| // Clear auth factor after it has been set. |
| auth_factor_type_ = |
| AuthFactorType::Authentication_AuthenticationType_AUTH_TYPE_UNKNOWN; |
| } |
| device_user_->GetDeviceUserAsync( |
| base::BindOnce(&AuthenticationPlugin::OnDeviceUserRetrieved, |
| weak_ptr_factory_.GetWeakPtr(), std::move(xdr_proto))); |
| } |
| |
| void AuthenticationPlugin::OnDeviceUserRetrieved( |
| std::unique_ptr<pb::UserEventAtomicVariant> atomic_event, |
| const std::string& device_user, |
| const std::string& device_userhash) { |
| // Do not set device user for logoff events because it will be empty. |
| if (!atomic_event->has_logoff()) { |
| atomic_event->mutable_common()->set_device_user(device_user); |
| } |
| |
| if (atomic_event->has_logon()) { |
| if (device_user == device_user::kEmpty) { |
| LOG(ERROR) << "Logon does NOT contain a device user"; |
| signed_in_user_ = device_user::kUnknown; |
| } else { |
| signed_in_user_ = device_user; |
| } |
| } |
| |
| // Send metric for which auth factor is used. |
| auto pair = GetEventEnumTypeAndAuthFactor(*atomic_event.get()); |
| if (pair.has_value()) { |
| MetricsSender::GetInstance().SendEnumMetricToUMA( |
| pair.value().first, |
| static_cast<metrics::AuthFactor>(pair.value().second)); |
| } |
| |
| // 3P needs the device_user to always be filled for authentication events. If |
| // there is no device user fill it with kUnknown. |
| if (atomic_event->common().device_user().empty()) { |
| atomic_event->mutable_common()->set_device_user(device_user::kUnknown); |
| } |
| batch_sender_->Enqueue(std::move(atomic_event)); |
| } |
| |
| void AuthenticationPlugin::OnFirstSessionStart( |
| const std::string& device_user, const std::string& sanitized_username) { |
| // When the device_user is empty no user is signed in so do not send a login |
| // event. |
| // When the device_user is filled there is already a user signed in so a login |
| // will be simulated. |
| if (!device_user.empty()) { |
| OnSessionStateChange(kStarted); |
| signed_in_user_ = device_user; |
| } |
| } |
| |
| } // namespace secagentd |