blob: 33767f143424e85510dd415aacb208f2c59a4785 [file] [log] [blame] [edit]
// 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