| // Copyright 2017 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 <memory> |
| #include <string> |
| |
| #include <base/bind.h> |
| #include <base/callback.h> |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/time/time.h> |
| #include <brillo/daemons/daemon.h> |
| #include <brillo/dbus/dbus_object.h> |
| #include <brillo/dbus/dbus_signal.h> |
| #include <brillo/flag_helper.h> |
| #include <brillo/syslog_logging.h> |
| #include <dbus/bus.h> |
| #include <dbus/u2f/dbus-constants.h> |
| #include <policy/device_policy.h> |
| #include <policy/libpolicy.h> |
| #include <session_manager/dbus-proxies.h> |
| #include <sysexits.h> |
| #include <u2f/proto_bindings/interface.pb.h> |
| |
| #include "bindings/chrome_device_policy.pb.h" |
| #include "power_manager-client/power_manager/dbus-proxies.h" |
| #include "u2fd/tpm_vendor_cmd.h" |
| #include "u2fd/u2fhid.h" |
| #include "u2fd/uhid_device.h" |
| #include "u2fd/user_state.h" |
| |
| #ifndef VCSID |
| #define VCSID "<unknown>" |
| #endif |
| |
| 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; |
| |
| 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, |
| }; |
| |
| bool U2fPolicyReady() { |
| policy::PolicyProvider policy_provider; |
| |
| return policy_provider.Reload(); |
| } |
| |
| U2fMode ReadU2fPolicy() { |
| policy::PolicyProvider policy_provider; |
| |
| DCHECK(policy_provider.Reload()); |
| |
| int mode = 0; |
| const policy::DevicePolicy* policy = &policy_provider.GetDevicePolicy(); |
| if (!policy->GetSecondFactorAuthenticationMode(&mode)) |
| return U2fMode::kUnset; |
| |
| return static_cast<U2fMode>(mode); |
| } |
| |
| U2fMode GetU2fMode(bool force_u2f, bool force_g2f) { |
| U2fMode policy_mode = ReadU2fPolicy(); |
| |
| // Always honor the administrator request to disable even if given |
| // contradictory override flags. |
| if (policy_mode == U2fMode::kDisabled) { |
| return U2fMode::kDisabled; |
| } |
| |
| if (force_g2f || policy_mode == U2fMode::kU2fExtended) { |
| return U2fMode::kU2fExtended; |
| } |
| |
| if (force_u2f || policy_mode == U2fMode::kU2f) { |
| return U2fMode::kU2f; |
| } |
| |
| return U2fMode::kDisabled; |
| } |
| |
| const char* U2fModeToString(U2fMode mode) { |
| switch (mode) { |
| case U2fMode::kUnset: |
| return "disabled (no policy)"; |
| case U2fMode::kDisabled: |
| return "disabled"; |
| case U2fMode::kU2f: |
| return "U2F"; |
| case U2fMode::kU2fExtended: |
| return "U2F+extensions"; |
| } |
| return "unknown"; |
| } |
| |
| 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; |
| } |
| } |
| |
| class U2fDaemon : public brillo::Daemon { |
| public: |
| U2fDaemon(bool force_u2f, |
| bool force_g2f, |
| bool user_keys, |
| bool legacy_kh_fallback, |
| uint32_t vendor_id, |
| uint32_t product_id) |
| : force_u2f_(force_u2f), |
| force_g2f_(force_g2f), |
| user_keys_(user_keys), |
| legacy_kh_fallback_(legacy_kh_fallback), |
| vendor_id_(vendor_id), |
| product_id_(product_id) {} |
| |
| private: |
| int OnInit() override { |
| brillo::Daemon::OnInit(); |
| |
| if (!bus_ && !InitializeDBus()) |
| return EX_IOERR; |
| |
| 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 TryStartService(const std::string& /* unused dbus signal status */) { |
| if (!u2fhid_ && U2fPolicyReady()) { |
| int status = StartService(); |
| if (status != EX_OK && status != EX_CONFIG) { |
| exit(status); |
| } |
| } |
| } |
| |
| int StartService() { |
| 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; |
| } |
| |
| u2f_mode_ = GetU2fMode(force_u2f_, force_g2f_); |
| LOG(INFO) << "Mode: " << U2fModeToString(u2f_mode_) |
| << " (force_u2f=" << force_u2f_ << " force_g2f=" << force_g2f_ |
| << ")"; |
| if (u2f_mode_ == U2fMode::kDisabled) { |
| return EX_CONFIG; |
| } |
| |
| // User keys should always be enabled when a U2F policy is set or G2F mode |
| // is enabled, and may additionally be enabled on the command line. |
| // User keys may not be disabled if a policy is defined, as non-user keys |
| // are legacy and should not be used beyond the initial beta launch. |
| if (ReadU2fPolicy() != U2fMode::kUnset || force_g2f_) { |
| user_keys_ = true; |
| } |
| |
| RegisterU2fDBusInterface(); |
| |
| std::string version; |
| if (!tpm_proxy_.Init()) { |
| LOG(ERROR) << "Failed to initialize D-Bus proxy with trunksd."; |
| return EX_IOERR; |
| } |
| int rc = tpm_proxy_.SetU2fVendorMode(static_cast<uint8_t>(u2f_mode_)); |
| if (!rc) |
| rc = tpm_proxy_.GetU2fVersion(&version); |
| if (rc == u2f::kVendorRcNoSuchCommand) { |
| LOG(WARNING) << "U2F Feature not available in firmware."; |
| // Will exit gracefully as we don't want to re-spawn. |
| return EX_UNAVAILABLE; |
| } |
| if (rc != 0) { |
| LOG(ERROR) << "Cannot get U2F version from TPM."; |
| return EX_PROTOCOL; |
| } |
| LOG(INFO) << "version " << version; |
| |
| std::string vendor_sysinfo; |
| if (u2f_mode_ == U2fMode::kU2fExtended) |
| tpm_proxy_.GetVendorSysInfo(&vendor_sysinfo); |
| |
| u2fhid_ = std::make_unique<u2f::U2fHid>( |
| std::make_unique<u2f::UHidDevice>(vendor_id_, product_id_, kDeviceName, |
| "u2fd-tpm-cr50"), |
| vendor_sysinfo, u2f_mode_ == U2fMode::kU2fExtended, user_keys_, |
| legacy_kh_fallback_, |
| base::Bind(&u2f::TpmVendorCommandProxy::SendU2fApdu, |
| base::Unretained(&tpm_proxy_)), |
| base::Bind(&u2f::TpmVendorCommandProxy::SendU2fGenerate, |
| base::Unretained(&tpm_proxy_)), |
| base::Bind(&u2f::TpmVendorCommandProxy::SendU2fSign, |
| base::Unretained(&tpm_proxy_)), |
| base::Bind(&u2f::TpmVendorCommandProxy::SendU2fAttest, |
| base::Unretained(&tpm_proxy_)), |
| base::Bind(&u2f::TpmVendorCommandProxy::GetG2fCertificate, |
| base::Unretained(&tpm_proxy_)), |
| base::Bind( |
| &org::chromium::PowerManagerProxy::IgnoreNextPowerButtonPress, |
| base::Unretained(pm_proxy_.get())), |
| base::Bind(&U2fDaemon::SendWinkSignal, base::Unretained(this)), |
| std::make_unique<u2f::UserState>( |
| sm_proxy_.get(), legacy_kh_fallback_ ? kLegacyKhCounterMin : 0)); |
| |
| return u2fhid_->Init() ? EX_OK : EX_PROTOCOL; |
| } |
| |
| bool InitializeDBus() { |
| dbus::Bus::Options options; |
| options.bus_type = dbus::Bus::SYSTEM; |
| bus_ = new dbus::Bus(options); |
| if (!bus_->Connect()) { |
| LOG(ERROR) << "Cannot connect to D-Bus."; |
| return false; |
| } |
| |
| if (!bus_->RequestOwnershipAndBlock(u2f::kU2FServiceName, |
| dbus::Bus::REQUIRE_PRIMARY)) { |
| LOG(ERROR) << "Cannot acquire dbus ownership for " |
| << u2f::kU2FServiceName; |
| return EX_IOERR; |
| } |
| |
| pm_proxy_ = std::make_unique<org::chromium::PowerManagerProxy>(bus_.get()); |
| sm_proxy_ = std::make_unique<org::chromium::SessionManagerInterfaceProxy>( |
| bus_.get()); |
| |
| return true; |
| } |
| |
| void RegisterU2fDBusInterface() { |
| 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); |
| |
| dbus_object_->RegisterAndBlock(); |
| } |
| |
| void 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(); |
| } |
| } |
| |
| bool force_u2f_; |
| bool force_g2f_; |
| bool user_keys_; |
| bool legacy_kh_fallback_; |
| uint32_t vendor_id_; |
| uint32_t product_id_; |
| U2fMode u2f_mode_; |
| u2f::TpmVendorCommandProxy tpm_proxy_; |
| scoped_refptr<dbus::Bus> bus_; |
| std::unique_ptr<org::chromium::PowerManagerProxy> pm_proxy_; |
| std::unique_ptr<org::chromium::SessionManagerInterfaceProxy> sm_proxy_; |
| std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object_; |
| std::weak_ptr<brillo::dbus_utils::DBusSignal<u2f::UserNotification>> |
| wink_signal_; |
| std::unique_ptr<u2f::U2fHid> u2fhid_; |
| |
| DISALLOW_COPY_AND_ASSIGN(U2fDaemon); |
| }; |
| |
| } // namespace |
| |
| int main(int argc, char* argv[]) { |
| DEFINE_bool(force_u2f, false, "force U2F mode even if disabled by policy"); |
| DEFINE_bool( |
| force_g2f, false, "force U2F mode plus extensions regardless of policy"); |
| DEFINE_int32(product_id, u2f::kDefaultProductId, |
| "Product ID for the HID device"); |
| DEFINE_int32(vendor_id, u2f::kDefaultVendorId, |
| "Vendor ID for the HID device"); |
| DEFINE_bool(verbose, false, "verbose logging"); |
| DEFINE_bool(user_keys, false, "Whether to use user-specific keys"); |
| DEFINE_bool(legacy_kh_fallback, false, |
| "Whether to allow auth with legacy keys when user-specific keys " |
| "are enabled"); |
| |
| brillo::FlagHelper::Init(argc, argv, "u2fd, U2FHID emulation daemon."); |
| |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogHeader | |
| brillo::kLogToStderrIfTty); |
| if (FLAGS_verbose) |
| logging::SetMinLogLevel(-1); |
| |
| LOG(INFO) << "Daemon version " << VCSID; |
| |
| U2fDaemon daemon(FLAGS_force_u2f, FLAGS_force_g2f, FLAGS_user_keys, |
| FLAGS_legacy_kh_fallback, FLAGS_vendor_id, FLAGS_product_id); |
| int rc = daemon.Run(); |
| |
| return rc == EX_UNAVAILABLE ? EX_OK : rc; |
| } |