| // Copyright 2019 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 "u2fd/u2f_daemon.h" |
| |
| #include <functional> |
| #include <string> |
| #include <sysexits.h> |
| #include <utility> |
| #include <vector> |
| |
| #include <attestation/proto_bindings/interface.pb.h> |
| #include <attestation-client/attestation/dbus-constants.h> |
| #include <base/bind.h> |
| #include <base/optional.h> |
| #include <base/synchronization/waitable_event.h> |
| #include <bindings/chrome_device_policy.pb.h> |
| #include <dbus/u2f/dbus-constants.h> |
| #include <policy/device_policy.h> |
| #include <policy/libpolicy.h> |
| #include <trunks/cr50_headers/virtual_nvmem.h> |
| |
| #include "u2fd/u2fhid.h" |
| #include "u2fd/uhid_device.h" |
| |
| namespace u2f { |
| |
| namespace em = enterprise_management; |
| |
| enum class U2fMode : uint8_t { |
| kUnset = em::DeviceSecondFactorAuthenticationProto_U2fMode_UNSET, |
| kDisabled = em::DeviceSecondFactorAuthenticationProto_U2fMode_DISABLED, |
| kU2f = em::DeviceSecondFactorAuthenticationProto_U2fMode_U2F, |
| kU2fExtended = em::DeviceSecondFactorAuthenticationProto_U2fMode_U2F_EXTENDED, |
| }; |
| |
| namespace { |
| |
| constexpr char kDeviceName[] = "Integrated U2F"; |
| constexpr int kWinkSignalMinIntervalMs = 1000; |
| |
| // The U2F counter stored in cr50 is stored in a format resistant to rollbacks, |
| // and that guarantees monotonicity even in the presence of partial writes. |
| // See //platform/ec/include/nvcounter.h |
| // |
| // The counter is stored across 2 pages of flash - a high page and a low page, |
| // with each page containing 512 4-byte words. The counter increments using |
| // 'strikes', with each strike occupying 4 bits. The high page can represent |
| // numbers 0-2048, and the low page can represent numbers 0-4096. |
| // The pages are interpreted as two digits of a base-4097 number, giving us |
| // the maximum value below. |
| // See //platform/ec/common/nvcounter.c for more details. |
| constexpr uint32_t kMaxCr50U2fCounterValue = (2048 * 4097) + 4096; |
| // If we are supporting legacy key handles, we initialize the counter such that |
| // it is always larger than the maximum possible value cr50 could have returned, |
| // and therefore guarantee that we provide a monotonically increasing counter |
| // value for migrated key handles. |
| constexpr uint32_t kLegacyKhCounterMin = kMaxCr50U2fCounterValue + 1; |
| |
| bool U2fPolicyReady() { |
| policy::PolicyProvider policy_provider; |
| |
| return policy_provider.Reload(); |
| } |
| |
| U2fMode ReadU2fPolicy() { |
| policy::PolicyProvider policy_provider; |
| |
| if (!policy_provider.Reload()) { |
| LOG(DFATAL) << "Failed to load device policy"; |
| } |
| |
| int mode = 0; |
| const policy::DevicePolicy* policy = &policy_provider.GetDevicePolicy(); |
| if (!policy->GetSecondFactorAuthenticationMode(&mode)) |
| return U2fMode::kUnset; |
| |
| return static_cast<U2fMode>(mode); |
| } |
| |
| const char* U2fModeToString(U2fMode mode) { |
| switch (mode) { |
| case U2fMode::kUnset: |
| return "unset"; |
| case U2fMode::kDisabled: |
| return "disabled"; |
| case U2fMode::kU2f: |
| return "U2F"; |
| case U2fMode::kU2fExtended: |
| return "U2F+extensions"; |
| } |
| return "unknown"; |
| } |
| |
| U2fMode GetU2fMode(bool force_u2f, bool force_g2f) { |
| U2fMode policy_mode = ReadU2fPolicy(); |
| |
| LOG(INFO) << "Requested Mode: Policy[" << U2fModeToString(policy_mode) |
| << "], force_u2f[" << force_u2f << "], force_g2f[" << force_g2f |
| << "]"; |
| |
| // Always honor the administrator request to disable even if given |
| // contradictory override flags. |
| if (policy_mode == U2fMode::kDisabled) { |
| LOG(INFO) << "Mode: Disabled (explicitly by policy)"; |
| return U2fMode::kDisabled; |
| } |
| |
| if (force_g2f || policy_mode == U2fMode::kU2fExtended) { |
| LOG(INFO) << "Mode: U2F+extensions"; |
| return U2fMode::kU2fExtended; |
| } |
| |
| if (force_u2f || policy_mode == U2fMode::kU2f) { |
| LOG(INFO) << "Mode:U2F"; |
| return U2fMode::kU2f; |
| } |
| |
| LOG(INFO) << "Mode: Disabled"; |
| return U2fMode::kDisabled; |
| } |
| |
| void OnPolicySignalConnected(const std::string& interface, |
| const std::string& signal, |
| bool success) { |
| if (!success) { |
| LOG(FATAL) << "Could not connect to signal " << signal << " on interface " |
| << interface; |
| } |
| } |
| |
| } // namespace |
| |
| U2fDaemon::U2fDaemon(bool force_u2f, |
| bool force_g2f, |
| bool g2f_allowlist_data, |
| bool legacy_kh_fallback, |
| uint32_t vendor_id, |
| uint32_t product_id) |
| : brillo::DBusServiceDaemon(u2f::kU2FServiceName), |
| force_u2f_(force_u2f), |
| force_g2f_(force_g2f), |
| g2f_allowlist_data_(g2f_allowlist_data), |
| legacy_kh_fallback_(legacy_kh_fallback), |
| vendor_id_(vendor_id), |
| product_id_(product_id) {} |
| |
| int U2fDaemon::OnInit() { |
| int rc = brillo::DBusServiceDaemon::OnInit(); |
| if (rc != EX_OK) |
| return rc; |
| |
| if (!InitializeDBusProxies()) { |
| return EX_IOERR; |
| } |
| |
| user_state_ = std::make_unique<u2f::UserState>( |
| sm_proxy_.get(), legacy_kh_fallback_ ? kLegacyKhCounterMin : 0); |
| |
| sm_proxy_->RegisterPropertyChangeCompleteSignalHandler( |
| base::Bind(&U2fDaemon::TryStartService, base::Unretained(this)), |
| base::Bind(&OnPolicySignalConnected)); |
| |
| bool policy_ready = U2fPolicyReady(); |
| |
| if (policy_ready) { |
| int status = StartService(); |
| |
| // If U2F is not currently enabled, we'll wait for policy updates |
| // that may enable it. We don't ever disable U2F on policy updates. |
| // TODO(louiscollard): Fix the above. |
| if (status != EX_CONFIG) |
| return status; |
| } |
| |
| if (policy_ready) { |
| LOG(INFO) << "U2F currently disabled, waiting for policy updates..."; |
| } else { |
| LOG(INFO) << "Policy not available, waiting..."; |
| } |
| |
| return EX_OK; |
| } |
| |
| void U2fDaemon::TryStartService( |
| const std::string& /* unused dbus signal status */) { |
| // If there's u2fhid_ then we have already started service. |
| if (u2fhid_) |
| return; |
| |
| if (!U2fPolicyReady()) |
| return; |
| |
| int status = StartService(); |
| |
| if (status != EX_OK && status != EX_CONFIG) { |
| // Something went wrong. |
| exit(status); |
| } |
| } |
| |
| int U2fDaemon::StartService() { |
| // Start U2fHid service before WebAuthn because WebAuthn initialization can |
| // be slow. |
| int status = StartU2fHidService(); |
| |
| U2fMode u2f_mode = GetU2fMode(force_u2f_, force_g2f_); |
| if (u2f_mode != U2fMode::kU2f && u2f_mode != U2fMode::kU2fExtended) { |
| LOG(INFO) << "Initializing WebAuthn handler."; |
| InitializeWebAuthnHandler(); |
| } |
| |
| return status; |
| } |
| |
| int U2fDaemon::StartU2fHidService() { |
| if (u2fhid_) { |
| // Any failures in previous calls to this function would have caused the |
| // program to terminate, so we can assume we have successfully started. |
| return EX_OK; |
| } |
| |
| U2fMode u2f_mode = GetU2fMode(force_u2f_, force_g2f_); |
| if (u2f_mode == U2fMode::kDisabled) { |
| return EX_CONFIG; |
| } |
| |
| LOG(INFO) << "Starting U2fHid service."; |
| |
| // If g2f is enabled by policy, we always include allowlisting data. |
| bool include_g2f_allowlist_data = |
| g2f_allowlist_data_ || (ReadU2fPolicy() == U2fMode::kU2fExtended); |
| |
| CreateU2fMsgHandler( |
| u2f_mode == U2fMode::kU2fExtended /* Allow G2F Attestation */, |
| include_g2f_allowlist_data); |
| |
| CreateU2fHid(); |
| |
| return u2fhid_->Init() ? EX_OK : EX_PROTOCOL; |
| } |
| |
| bool U2fDaemon::InitializeDBusProxies() { |
| if (!tpm_proxy_.Init()) { |
| LOG(ERROR) << "Failed to initialize trunksd DBus proxy"; |
| return false; |
| } |
| |
| attestation_proxy_ = bus_->GetObjectProxy( |
| attestation::kAttestationServiceName, |
| dbus::ObjectPath(attestation::kAttestationServicePath)); |
| |
| if (!attestation_proxy_) { |
| LOG(ERROR) << "Failed to initialize attestationd DBus proxy"; |
| return false; |
| } |
| |
| pm_proxy_ = std::make_unique<org::chromium::PowerManagerProxy>(bus_.get()); |
| sm_proxy_ = |
| std::make_unique<org::chromium::SessionManagerInterfaceProxy>(bus_.get()); |
| |
| return true; |
| } |
| |
| void U2fDaemon::RegisterDBusObjectsAsync( |
| brillo::dbus_utils::AsyncEventSequencer* sequencer) { |
| dbus_object_.reset(new brillo::dbus_utils::DBusObject( |
| nullptr, bus_, dbus::ObjectPath(u2f::kU2FServicePath))); |
| |
| auto u2f_interface = dbus_object_->AddOrGetInterface(u2f::kU2FInterface); |
| |
| wink_signal_ = u2f_interface->RegisterSignal<u2f::UserNotification>( |
| u2f::kU2FUserNotificationSignal); |
| |
| // Handlers for the WebAuthn DBus API. |
| u2f_interface->AddMethodHandler(kU2FMakeCredential, |
| base::Unretained(&webauthn_handler_), |
| &WebAuthnHandler::MakeCredential); |
| |
| u2f_interface->AddMethodHandler(kU2FGetAssertion, |
| base::Unretained(&webauthn_handler_), |
| &WebAuthnHandler::GetAssertion); |
| |
| u2f_interface->AddSimpleMethodHandler(kU2FHasCredentials, |
| base::Unretained(&webauthn_handler_), |
| &WebAuthnHandler::HasCredentials); |
| |
| u2f_interface->AddSimpleMethodHandler(kU2FCancelWebAuthnFlow, |
| base::Unretained(&webauthn_handler_), |
| &WebAuthnHandler::Cancel); |
| |
| u2f_interface->AddMethodHandler(kU2FIsUvpaa, |
| base::Unretained(&webauthn_handler_), |
| &WebAuthnHandler::IsUvpaa); |
| |
| dbus_object_->RegisterAsync( |
| sequencer->GetHandler("Failed to register DBus Interface.", true)); |
| } |
| |
| void U2fDaemon::CreateU2fMsgHandler(bool allow_g2f_attestation, |
| bool include_g2f_allowlisting_data) { |
| auto user_state = std::make_unique<u2f::UserState>( |
| sm_proxy_.get(), legacy_kh_fallback_ ? kLegacyKhCounterMin : 0); |
| |
| std::function<void()> request_presence = [this]() { |
| IgnorePowerButtonPress(); |
| SendWinkSignal(); |
| }; |
| |
| auto allowlisting_util = |
| include_g2f_allowlisting_data |
| ? std::make_unique<u2f::AllowlistingUtil>([this](int cert_size) { |
| return GetCertifiedG2fCert(cert_size); |
| }) |
| : std::unique_ptr<u2f::AllowlistingUtil>(nullptr); |
| |
| u2f_msg_handler_ = std::make_unique<u2f::U2fMessageHandler>( |
| std::move(allowlisting_util), request_presence, user_state_.get(), |
| &tpm_proxy_, &metrics_library_, legacy_kh_fallback_, |
| allow_g2f_attestation); |
| } |
| |
| void U2fDaemon::CreateU2fHid() { |
| u2fhid_ = std::make_unique<u2f::U2fHid>( |
| std::make_unique<u2f::UHidDevice>(vendor_id_, product_id_, kDeviceName, |
| "u2fd-tpm-cr50"), |
| u2f_msg_handler_.get()); |
| } |
| |
| void U2fDaemon::InitializeWebAuthnHandler() { |
| std::function<void()> request_presence = [this]() { |
| IgnorePowerButtonPress(); |
| SendWinkSignal(); |
| }; |
| |
| webauthn_handler_.Initialize(bus_.get(), &tpm_proxy_, user_state_.get(), |
| request_presence); |
| } |
| |
| void U2fDaemon::SendWinkSignal() { |
| static base::TimeTicks last_sent; |
| base::TimeDelta elapsed = base::TimeTicks::Now() - last_sent; |
| |
| if (elapsed.InMilliseconds() > kWinkSignalMinIntervalMs) { |
| u2f::UserNotification notification; |
| notification.set_event_type(u2f::UserNotification::TOUCH_NEEDED); |
| |
| wink_signal_.lock()->Send(notification); |
| |
| last_sent = base::TimeTicks::Now(); |
| } |
| } |
| |
| void U2fDaemon::IgnorePowerButtonPress() { |
| // Duration of the user presence persistence on the firmware side. |
| const base::TimeDelta kPresenceTimeout = base::TimeDelta::FromSeconds(10); |
| |
| brillo::ErrorPtr err; |
| // Mask the next power button press for the UI |
| pm_proxy_->IgnoreNextPowerButtonPress(kPresenceTimeout.ToInternalValue(), |
| &err, -1); |
| } |
| |
| namespace { |
| |
| constexpr char kKeyLabelEmk[] = "attest-ent-machine"; |
| |
| } // namespace |
| |
| base::Optional<attestation::GetCertifiedNvIndexReply> |
| U2fDaemon::GetCertifiedG2fCert(int g2f_cert_size) { |
| if (g2f_cert_size < 1 || g2f_cert_size > VIRTUAL_NV_INDEX_G2F_CERT_SIZE) { |
| LOG(ERROR) |
| << "Invalid G2F cert size specified for whitelisting data request"; |
| return base::nullopt; |
| } |
| |
| attestation::GetCertifiedNvIndexRequest request; |
| |
| request.set_nv_index(VIRTUAL_NV_INDEX_G2F_CERT); |
| request.set_nv_size(g2f_cert_size); |
| request.set_key_label(kKeyLabelEmk); |
| |
| brillo::ErrorPtr error; |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallMethodAndBlock( |
| attestation_proxy_, attestation::kAttestationInterface, |
| attestation::kGetCertifiedNvIndex, &error, request); |
| |
| if (!dbus_response) { |
| LOG(ERROR) << "Failed to retrieve certified G2F cert from attestationd"; |
| return base::nullopt; |
| } |
| |
| attestation::GetCertifiedNvIndexReply reply; |
| |
| dbus::MessageReader reader(dbus_response.get()); |
| if (!reader.PopArrayOfBytesAsProto(&reply)) { |
| LOG(ERROR) << "Failed to parse GetCertifiedNvIndexReply"; |
| return base::nullopt; |
| } |
| |
| if (reply.status() != attestation::AttestationStatus::STATUS_SUCCESS) { |
| LOG(ERROR) << "Call get GetCertifiedNvIndex failed, status: " |
| << reply.status(); |
| return base::nullopt; |
| } |
| |
| return reply; |
| } |
| |
| } // namespace u2f |