diff options
Diffstat (limited to 'chromium/device')
207 files changed, 7124 insertions, 4220 deletions
diff --git a/chromium/device/BUILD.gn b/chromium/device/BUILD.gn index 6982c5b52fa..e0df5c725c0 100644 --- a/chromium/device/BUILD.gn +++ b/chromium/device/BUILD.gn @@ -70,15 +70,20 @@ test("device_unittests") { "fido/fido_ble_connection_unittest.cc", "fido/fido_ble_device_unittest.cc", "fido/fido_ble_frames_unittest.cc", + "fido/fido_cable_device_unittest.cc", + "fido/fido_cable_discovery_unittest.cc", "fido/fido_discovery_unittest.cc", "fido/fido_hid_message_unittest.cc", + "fido/fido_parsing_utils_unittest.cc", "fido/fido_request_handler_unittest.cc", "fido/get_assertion_handler_unittest.cc", "fido/get_assertion_task_unittest.cc", + "fido/mac/get_assertion_operation_unittest_mac.mm", + "fido/mac/make_credential_operation_unittest_mac.mm", "fido/make_credential_handler_unittest.cc", "fido/make_credential_task_unittest.cc", "fido/test_callback_receiver_unittest.cc", - "fido/u2f_parsing_utils_unittest.cc", + "fido/u2f_command_constructor_unittest.cc", "fido/u2f_register_unittest.cc", "fido/u2f_request_unittest.cc", "fido/u2f_sign_unittest.cc", @@ -108,7 +113,6 @@ test("device_unittests") { "//device/gamepad/public/mojom", "//device/gamepad/public/mojom:gamepad_mojom_traits_test", "//device/geolocation:unittests", - "//mojo/common", "//mojo/edk", "//mojo/public/cpp/bindings", "//net", @@ -137,8 +141,11 @@ test("device_unittests") { # Serial is supported in below platforms. See //device/servial/BUILD.gn if (is_win || is_linux || is_mac) { - sources += [ "serial/serial_io_handler_posix_unittest.cc" ] + sources += [ "serial/serial_device_enumerator_unittest.cc" ] deps += [ "//device/serial" ] + if (is_linux || is_mac) { + sources += [ "serial/serial_io_handler_posix_unittest.cc" ] + } } } @@ -241,6 +248,7 @@ test("device_unittests") { deps += [ "//chromecast/device/bluetooth:util", "//chromecast/device/bluetooth/le", + "//chromecast/device/bluetooth/le:test_support", "//chromecast/device/bluetooth/shlib:mock_shlib", ] } else { @@ -275,10 +283,14 @@ test("device_unittests") { "bluetooth/bluetooth_classic_win_fake.h", "bluetooth/bluetooth_low_energy_win_fake.cc", "bluetooth/bluetooth_low_energy_win_fake.h", + "bluetooth/test/fake_bluetooth_adapter_winrt.cc", + "bluetooth/test/fake_bluetooth_adapter_winrt.h", + "bluetooth/test/fake_device_information_winrt.cc", + "bluetooth/test/fake_device_information_winrt.h", ] } - if (enable_vr && is_android) { + if (enable_vr) { sources += [ "vr/orientation/orientation_device_provider_unittest.cc", "vr/orientation/orientation_device_unittest.cc", @@ -286,10 +298,15 @@ test("device_unittests") { "vr/vr_display_impl_unittest.cc", ] + if (is_android) { + deps += [ "//device/vr:java" ] + } + + defines = [ "DEVICE_VR_IMPLEMENTATION" ] + deps += [ "//device/vr", "//device/vr:fakes", - "//device/vr:java", "//device/vr/public/mojom", "//services/device/public/cpp/generic_sensor", "//ui/display", diff --git a/chromium/device/OWNERS b/chromium/device/OWNERS index d3db08f5d0a..2d212568dac 100644 --- a/chromium/device/OWNERS +++ b/chromium/device/OWNERS @@ -1,7 +1,6 @@ reillyg@chromium.org rockot@chromium.org -per-file *.gyp*=* per-file BUILD.gn=* # TEAM: device-dev@chromium.org diff --git a/chromium/device/base/features.cc b/chromium/device/base/features.cc index 0cb9b248adb..b38d28dbae4 100644 --- a/chromium/device/base/features.cc +++ b/chromium/device/base/features.cc @@ -11,6 +11,8 @@ namespace device { #if defined(OS_WIN) const base::Feature kNewUsbBackend{"NewUsbBackend", base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kNewBLEWinImplementation{"NewBLEWinImplementation", + base::FEATURE_DISABLED_BY_DEFAULT}; #endif // defined(OS_WIN) #if defined(OS_LINUX) || defined(OS_CHROMEOS) diff --git a/chromium/device/base/features.h b/chromium/device/base/features.h index 062f03f2494..c25697a021e 100644 --- a/chromium/device/base/features.h +++ b/chromium/device/base/features.h @@ -13,6 +13,7 @@ namespace device { #if defined(OS_WIN) DEVICE_BASE_EXPORT extern const base::Feature kNewUsbBackend; +DEVICE_BASE_EXPORT extern const base::Feature kNewBLEWinImplementation; #endif // defined(OS_WIN) #if defined(OS_LINUX) || defined(OS_CHROMEOS) diff --git a/chromium/device/base/synchronization/one_writer_seqlock.cc b/chromium/device/base/synchronization/one_writer_seqlock.cc index b0eb3810dd4..7b855cfd1cb 100644 --- a/chromium/device/base/synchronization/one_writer_seqlock.cc +++ b/chromium/device/base/synchronization/one_writer_seqlock.cc @@ -24,17 +24,6 @@ base::subtle::Atomic32 OneWriterSeqLock::ReadBegin() const { return version; } -void OneWriterSeqLock::TryRead(bool* can_read, - base::subtle::Atomic32* version) const { - DCHECK(can_read); - DCHECK(version); - - *version = base::subtle::NoBarrier_Load(&sequence_); - // If the counter is even, then the associated data might be in a - // consistent state, so we can try to read. - *can_read = (*version & 1) == 0; -} - bool OneWriterSeqLock::ReadRetry(base::subtle::Atomic32 version) const { // If the sequence number was updated then a read should be re-attempted. // -- Load fence, read membarrier diff --git a/chromium/device/base/synchronization/one_writer_seqlock.h b/chromium/device/base/synchronization/one_writer_seqlock.h index 7be84056015..867fb82ba7a 100644 --- a/chromium/device/base/synchronization/one_writer_seqlock.h +++ b/chromium/device/base/synchronization/one_writer_seqlock.h @@ -32,7 +32,6 @@ class OneWriterSeqLock { public: OneWriterSeqLock(); base::subtle::Atomic32 ReadBegin() const; - void TryRead(bool* can_read, base::subtle::Atomic32* version) const; bool ReadRetry(base::subtle::Atomic32 version) const; void WriteBegin(); void WriteEnd(); diff --git a/chromium/device/base/synchronization/shared_memory_seqlock_buffer.h b/chromium/device/base/synchronization/shared_memory_seqlock_buffer.h index 1ad5a8a6a60..4835b097bc1 100644 --- a/chromium/device/base/synchronization/shared_memory_seqlock_buffer.h +++ b/chromium/device/base/synchronization/shared_memory_seqlock_buffer.h @@ -21,8 +21,6 @@ namespace device { template <class Data> class SharedMemorySeqLockBuffer { public: - SharedMemorySeqLockBuffer() {} - explicit SharedMemorySeqLockBuffer(const Data& data) : data(data) {} OneWriterSeqLock seqlock; Data data; }; diff --git a/chromium/device/bluetooth/BUILD.gn b/chromium/device/bluetooth/BUILD.gn index 5cb4e7c2791..56b36d78949 100644 --- a/chromium/device/bluetooth/BUILD.gn +++ b/chromium/device/bluetooth/BUILD.gn @@ -237,6 +237,13 @@ component("bluetooth") { } if (is_win) { + sources += [ + "bluetooth_adapter_winrt.cc", + "bluetooth_adapter_winrt.h", + "bluetooth_device_winrt.cc", + "bluetooth_device_winrt.h", + ] + libs = [ # Bthprops must be listed before BluetoothApis or else delay loading # crashes. diff --git a/chromium/device/bluetooth/DEPS b/chromium/device/bluetooth/DEPS index d098881abd8..1eab7930bbc 100644 --- a/chromium/device/bluetooth/DEPS +++ b/chromium/device/bluetooth/DEPS @@ -1,5 +1,6 @@ include_rules = [ "+chromecast/device/bluetooth", + "+chromecast/public/bluetooth", "+chromeos/dbus", "+crypto", "+dbus", diff --git a/chromium/device/bluetooth/OWNERS b/chromium/device/bluetooth/OWNERS index 7e02776acd4..8ac8597828e 100644 --- a/chromium/device/bluetooth/OWNERS +++ b/chromium/device/bluetooth/OWNERS @@ -3,7 +3,6 @@ ortuno@chromium.org # For changes related to Chrome OS. rkc@chromium.org -per-file *.gyp*=* per-file BUILD.gn=* # TEAM: web-bluetooth@chromium.org diff --git a/chromium/device/bluetooth/bluetooth_adapter.cc b/chromium/device/bluetooth/bluetooth_adapter.cc index 092b3adca7f..64fd5c8fe4b 100644 --- a/chromium/device/bluetooth/bluetooth_adapter.cc +++ b/chromium/device/bluetooth/bluetooth_adapter.cc @@ -28,7 +28,7 @@ BluetoothAdapter::ServiceOptions::~ServiceOptions() = default; !defined(OS_WIN) && !defined(OS_LINUX) // static base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( - const InitCallback& init_callback) { + InitCallback init_callback) { return base::WeakPtr<BluetoothAdapter>(); } #endif // !defined(OS_CHROMEOS) && !defined(OS_WIN) && !defined(OS_MACOSX) diff --git a/chromium/device/bluetooth/bluetooth_adapter.h b/chromium/device/bluetooth/bluetooth_adapter.h index 0923745b065..389dae23bf2 100644 --- a/chromium/device/bluetooth/bluetooth_adapter.h +++ b/chromium/device/bluetooth/bluetooth_adapter.h @@ -130,6 +130,14 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter virtual void DevicePairedChanged(BluetoothAdapter* adapter, BluetoothDevice* device, bool new_paired_status) {} + + // This function is implemented for ChromeOS only. + // Called when the MTU |mtu| (Bluetooth Spec Vol 3, Part F, 3.4.2) used in + // ATT communication with device |device| known to the adapter |adapter| + // changed. + virtual void DeviceMTUChanged(BluetoothAdapter* adapter, + BluetoothDevice* device, + uint16_t mtu) {} #endif // Called when the device |device| is removed from the adapter |adapter|, @@ -271,7 +279,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter // The InitCallback is used to trigger a callback after asynchronous // initialization, if initialization is asynchronous on the platform. - using InitCallback = base::Callback<void()>; + using InitCallback = base::OnceClosure; using DiscoverySessionCallback = base::Callback<void(std::unique_ptr<BluetoothDiscoverySession>)>; @@ -294,7 +302,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter // caller is expected to call |AddRef()| on the returned pointer, typically by // storing it into a |scoped_refptr|. static base::WeakPtr<BluetoothAdapter> CreateAdapter( - const InitCallback& init_callback); + InitCallback init_callback); // Returns a weak pointer to an existing adapter for testing purposes only. base::WeakPtr<BluetoothAdapter> GetWeakPtrForTesting(); @@ -556,7 +564,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter using PairingDelegatePair = std::pair<BluetoothDevice::PairingDelegate*, PairingDelegatePriority>; using DiscoverySessionErrorCallback = - base::Callback<void(UMABluetoothDiscoverySessionOutcome)>; + base::OnceCallback<void(UMABluetoothDiscoverySessionOutcome)>; // Implementations on Android and macOS need to store pending SetPowered() // callbacks until an appropriate event is received, due to a lack of blocking @@ -620,18 +628,18 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter virtual void AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) = 0; + DiscoverySessionErrorCallback error_callback) = 0; virtual void RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) = 0; + DiscoverySessionErrorCallback error_callback) = 0; // Used to set and update the discovery filter used by the underlying // Bluetooth controller. virtual void SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) = 0; + DiscoverySessionErrorCallback error_callback) = 0; // Called by RemovePairingDelegate() in order to perform any class-specific // internal functionality necessary to remove the pairing delegate, such as diff --git a/chromium/device/bluetooth/bluetooth_adapter_android.cc b/chromium/device/bluetooth/bluetooth_adapter_android.cc index 910ca657f03..3d2b667cd5f 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_android.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_android.cc @@ -44,7 +44,7 @@ namespace device { // static base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( - const InitCallback& init_callback) { + InitCallback init_callback) { return BluetoothAdapterAndroid::Create( BluetoothAdapterWrapper_CreateWithDefaultAdapter()); } @@ -281,7 +281,7 @@ bool BluetoothAdapterAndroid::SetPoweredImpl(bool powered) { void BluetoothAdapterAndroid::AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { // TODO(scheib): Support filters crbug.com/490401 bool session_added = false; if (IsPowered()) { @@ -309,14 +309,14 @@ void BluetoothAdapterAndroid::AddDiscoverySession( callback.Run(); } else { // TODO(scheib): Eventually wire the SCAN_FAILED result through to here. - error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); + std::move(error_callback).Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); } } void BluetoothAdapterAndroid::RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { bool session_removed = false; if (num_discovery_sessions_ == 0) { VLOG(1) << "RemoveDiscoverySession: No scan in progress."; @@ -341,17 +341,18 @@ void BluetoothAdapterAndroid::RemoveDiscoverySession( callback.Run(); } else { // TODO(scheib): Eventually wire the SCAN_FAILED result through to here. - error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); + std::move(error_callback).Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); } } void BluetoothAdapterAndroid::SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { // TODO(scheib): Support filters crbug.com/490401 NOTIMPLEMENTED(); - error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_IMPLEMENTED); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::NOT_IMPLEMENTED); } void BluetoothAdapterAndroid::RemovePairingDelegateInternal( diff --git a/chromium/device/bluetooth/bluetooth_adapter_android.h b/chromium/device/bluetooth/bluetooth_adapter_android.h index 483c634311e..113f9c985cb 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_android.h +++ b/chromium/device/bluetooth/bluetooth_adapter_android.h @@ -115,15 +115,15 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterAndroid final void AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void RemovePairingDelegateInternal( BluetoothDevice::PairingDelegate* pairing_delegate) override; diff --git a/chromium/device/bluetooth/bluetooth_adapter_mac.h b/chromium/device/bluetooth/bluetooth_adapter_mac.h index fe22655676f..a31b33b1b54 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_mac.h +++ b/chromium/device/bluetooth/bluetooth_adapter_mac.h @@ -17,6 +17,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" +#include "base/single_thread_task_runner.h" #include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_discovery_manager_mac.h" #include "device/bluetooth/bluetooth_export.h" @@ -174,15 +175,15 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterMac void AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; // Start classic and/or low energy discovery sessions, according to the // filter. If a discovery session is already running the filter is updated. diff --git a/chromium/device/bluetooth/bluetooth_adapter_mac.mm b/chromium/device/bluetooth/bluetooth_adapter_mac.mm index 4a025982dcd..bb996bfd087 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_mac.mm +++ b/chromium/device/bluetooth/bluetooth_adapter_mac.mm @@ -65,7 +65,7 @@ CBCentralManagerState GetCBManagerState(CBCentralManager* manager) { // static base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( - const InitCallback& init_callback) { + InitCallback init_callback) { return BluetoothAdapterMac::CreateAdapter(); } @@ -350,7 +350,7 @@ void BluetoothAdapterMac::SetPowerStateFunctionForTesting( void BluetoothAdapterMac::AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { DVLOG(1) << __func__; if (num_discovery_sessions_ > 0) { DCHECK(IsDiscovering()); @@ -361,7 +361,7 @@ void BluetoothAdapterMac::AddDiscoverySession( // TODO: Provide a more precise error here. ui_task_runner_->PostTask( FROM_HERE, - base::BindOnce(error_callback, + base::BindOnce(std::move(error_callback), UMABluetoothDiscoverySessionOutcome::UNKNOWN)); return; } @@ -375,7 +375,7 @@ void BluetoothAdapterMac::AddDiscoverySession( // TODO: Provide a more precise error here. ui_task_runner_->PostTask( FROM_HERE, - base::BindOnce(error_callback, + base::BindOnce(std::move(error_callback), UMABluetoothDiscoverySessionOutcome::UNKNOWN)); return; } @@ -390,7 +390,7 @@ void BluetoothAdapterMac::AddDiscoverySession( void BluetoothAdapterMac::RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { DVLOG(1) << __func__; if (num_discovery_sessions_ > 1) { @@ -403,7 +403,8 @@ void BluetoothAdapterMac::RemoveDiscoverySession( if (num_discovery_sessions_ == 0) { DVLOG(1) << "No active discovery sessions. Returning error."; - error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_ACTIVE); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::NOT_ACTIVE); return; } @@ -416,7 +417,8 @@ void BluetoothAdapterMac::RemoveDiscoverySession( if (!classic_discovery_manager_->StopDiscovery()) { DVLOG(1) << "Failed to stop classic discovery"; // TODO: Provide a more precise error here. - error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN); return; } } @@ -437,9 +439,10 @@ void BluetoothAdapterMac::RemoveDiscoverySession( void BluetoothAdapterMac::SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { NOTIMPLEMENTED(); - error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_IMPLEMENTED); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::NOT_IMPLEMENTED); } bool BluetoothAdapterMac::StartDiscovery( diff --git a/chromium/device/bluetooth/bluetooth_adapter_stub.cc b/chromium/device/bluetooth/bluetooth_adapter_stub.cc index fc62f95a54a..e32d07e0171 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_stub.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_stub.cc @@ -11,7 +11,7 @@ namespace device { // a particular platform. // static base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( - const InitCallback& init_callback) { + InitCallback init_callback) { return base::WeakPtr<BluetoothAdapter>(); } diff --git a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc index 0bc7ba99ede..292c6734ba7 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc @@ -147,17 +147,17 @@ class TestBluetoothAdapter : public BluetoothAdapter { void AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override {} + DiscoverySessionErrorCallback error_callback) override {} void RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override {} + DiscoverySessionErrorCallback error_callback) override {} void SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override {} + DiscoverySessionErrorCallback error_callback) override {} void RemovePairingDelegateInternal( BluetoothDevice::PairingDelegate* pairing_delegate) override {} @@ -434,12 +434,17 @@ TEST(BluetoothAdapterTest, GetMergedDiscoveryFilterAllFields) { } // TODO(scheib): Enable BluetoothTest fixture tests on all platforms. -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ConstructDefaultAdapter ConstructDefaultAdapter #else #define MAYBE_ConstructDefaultAdapter DISABLED_ConstructDefaultAdapter #endif + +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrt, ConstructDefaultAdapter) { +#else TEST_F(BluetoothTest, MAYBE_ConstructDefaultAdapter) { +#endif InitWithDefaultAdapter(); if (!adapter_->IsPresent()) { LOG(WARNING) << "Bluetooth adapter not present; skipping unit test."; @@ -464,13 +469,18 @@ TEST_F(BluetoothTest, MAYBE_ConstructDefaultAdapter) { } // TODO(scheib): Enable BluetoothTest fixture tests on all platforms. -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ConstructWithoutDefaultAdapter ConstructWithoutDefaultAdapter #else #define MAYBE_ConstructWithoutDefaultAdapter \ DISABLED_ConstructWithoutDefaultAdapter #endif + +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrt, ConstructWithoutDefaultAdapter) { +#else TEST_F(BluetoothTest, MAYBE_ConstructWithoutDefaultAdapter) { +#endif // defined(OS_WIN) InitWithoutDefaultAdapter(); EXPECT_EQ(adapter_->GetAddress(), ""); EXPECT_EQ(adapter_->GetName(), ""); @@ -481,12 +491,17 @@ TEST_F(BluetoothTest, MAYBE_ConstructWithoutDefaultAdapter) { } // TODO(scheib): Enable BluetoothTest fixture tests on all platforms. -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ConstructFakeAdapter ConstructFakeAdapter #else #define MAYBE_ConstructFakeAdapter DISABLED_ConstructFakeAdapter #endif + +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrt, ConstructFakeAdapter) { +#else TEST_F(BluetoothTest, MAYBE_ConstructFakeAdapter) { +#endif // defined(OS_WIN) if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1325,4 +1340,11 @@ TEST_F(BluetoothTest, DiscoverConnectedLowEnergyDeviceTwice) { } #endif // defined(OS_MACOSX) +#if defined(OS_WIN) +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + BluetoothTestWinrt, + ::testing::Bool()); +#endif // defined(OS_WIN) + } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter_win.cc b/chromium/device/bluetooth/bluetooth_adapter_win.cc index 24f9d177df2..32e7fc89f50 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_win.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_win.cc @@ -8,6 +8,7 @@ #include <string> #include <utility> +#include "base/feature_list.h" #include "base/location.h" #include "base/logging.h" #include "base/memory/ptr_util.h" @@ -15,6 +16,9 @@ #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/win/windows_version.h" +#include "device/base/features.h" +#include "device/bluetooth/bluetooth_adapter_winrt.h" #include "device/bluetooth/bluetooth_device_win.h" #include "device/bluetooth/bluetooth_discovery_session_outcome.h" #include "device/bluetooth/bluetooth_socket_thread.h" @@ -26,21 +30,33 @@ namespace device { // static base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( - const InitCallback& init_callback) { - return BluetoothAdapterWin::CreateAdapter(init_callback); + InitCallback init_callback) { + return BluetoothAdapterWin::CreateAdapter(std::move(init_callback)); } // static base::WeakPtr<BluetoothAdapter> BluetoothAdapterWin::CreateAdapter( - const InitCallback& init_callback) { - BluetoothAdapterWin* adapter = new BluetoothAdapterWin(init_callback); + InitCallback init_callback) { + if (UseNewBLEWinImplementation()) { + auto* adapter = new BluetoothAdapterWinrt(); + adapter->Init(std::move(init_callback)); + return adapter->weak_ptr_factory_.GetWeakPtr(); + } + + auto* adapter = new BluetoothAdapterWin(std::move(init_callback)); adapter->Init(); return adapter->weak_ptr_factory_.GetWeakPtr(); } -BluetoothAdapterWin::BluetoothAdapterWin(const InitCallback& init_callback) +// static +bool BluetoothAdapterWin::UseNewBLEWinImplementation() { + return base::FeatureList::IsEnabled(kNewBLEWinImplementation) && + base::win::GetVersion() >= base::win::VERSION_WIN10; +} + +BluetoothAdapterWin::BluetoothAdapterWin(InitCallback init_callback) : BluetoothAdapter(), - init_callback_(init_callback), + init_callback_(std::move(init_callback)), initialized_(false), powered_(false), discovery_status_(NOT_DISCOVERING), @@ -106,21 +122,23 @@ bool BluetoothAdapterWin::IsDiscovering() const { } static void RunDiscoverySessionErrorCallback( - const base::Callback<void(UMABluetoothDiscoverySessionOutcome)>& callback, + base::OnceCallback<void(UMABluetoothDiscoverySessionOutcome)> + error_callback, UMABluetoothDiscoverySessionOutcome outcome) { - callback.Run(outcome); + std::move(error_callback).Run(outcome); } void BluetoothAdapterWin::DiscoveryStarted(bool success) { discovery_status_ = success ? DISCOVERING : NOT_DISCOVERING; - for (const auto& callbacks : on_start_discovery_callbacks_) { + for (auto& callbacks : on_start_discovery_callbacks_) { if (success) ui_task_runner_->PostTask(FROM_HERE, callbacks.first); else ui_task_runner_->PostTask( FROM_HERE, - base::Bind(&RunDiscoverySessionErrorCallback, callbacks.second, - UMABluetoothDiscoverySessionOutcome::UNKNOWN)); + base::BindOnce(&RunDiscoverySessionErrorCallback, + std::move(callbacks.second), + UMABluetoothDiscoverySessionOutcome::UNKNOWN)); } num_discovery_listeners_ = on_start_discovery_callbacks_.size(); on_start_discovery_callbacks_.clear(); @@ -220,7 +238,7 @@ void BluetoothAdapterWin::AdapterStateChanged( } if (!initialized_) { initialized_ = true; - init_callback_.Run(); + std::move(init_callback_).Run(); } } @@ -299,23 +317,24 @@ bool BluetoothAdapterWin::SetPoweredImpl(bool powered) { void BluetoothAdapterWin::AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { if (discovery_status_ == DISCOVERING) { num_discovery_listeners_++; callback.Run(); return; } on_start_discovery_callbacks_.push_back( - std::make_pair(callback, error_callback)); + std::make_pair(callback, std::move(error_callback))); MaybePostStartDiscoveryTask(); } void BluetoothAdapterWin::RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { if (discovery_status_ == NOT_DISCOVERING) { - error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_ACTIVE); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::NOT_ACTIVE); return; } on_stop_discovery_callbacks_.push_back(callback); @@ -325,9 +344,10 @@ void BluetoothAdapterWin::RemoveDiscoverySession( void BluetoothAdapterWin::SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { NOTIMPLEMENTED(); - error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_IMPLEMENTED); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::NOT_IMPLEMENTED); } void BluetoothAdapterWin::Init() { diff --git a/chromium/device/bluetooth/bluetooth_adapter_win.h b/chromium/device/bluetooth/bluetooth_adapter_win.h index 124ba6aa8b1..88ea310b7f0 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_win.h +++ b/chromium/device/bluetooth/bluetooth_adapter_win.h @@ -34,7 +34,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWin public BluetoothTaskManagerWin::Observer { public: static base::WeakPtr<BluetoothAdapter> CreateAdapter( - const InitCallback& init_callback); + InitCallback init_callback); + + static bool UseNewBLEWinImplementation(); // BluetoothAdapter: std::string GetAddress() const override; @@ -107,7 +109,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWin DISCOVERY_STOPPING }; - explicit BluetoothAdapterWin(const InitCallback& init_callback); + explicit BluetoothAdapterWin(InitCallback init_callback); ~BluetoothAdapterWin() override; // BluetoothAdapter: @@ -115,15 +117,15 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWin void AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void Init(); void InitForTest( diff --git a/chromium/device/bluetooth/bluetooth_adapter_win_unittest.cc b/chromium/device/bluetooth/bluetooth_adapter_win_unittest.cc index 0537efb078a..7a991c94481 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_win_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_win_unittest.cc @@ -89,19 +89,20 @@ class BluetoothAdapterWinTest : public testing::Test { num_stop_discovery_error_callbacks_++; } - typedef base::Callback<void(UMABluetoothDiscoverySessionOutcome)> + typedef base::OnceCallback<void(UMABluetoothDiscoverySessionOutcome)> DiscoverySessionErrorCallback; - void CallAddDiscoverySession( - const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { - adapter_win_->AddDiscoverySession(nullptr, callback, error_callback); + void CallAddDiscoverySession(const base::Closure& callback, + DiscoverySessionErrorCallback error_callback) { + adapter_win_->AddDiscoverySession(nullptr, callback, + std::move(error_callback)); } void CallRemoveDiscoverySession( const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { - adapter_win_->RemoveDiscoverySession(nullptr, callback, error_callback); + DiscoverySessionErrorCallback error_callback) { + adapter_win_->RemoveDiscoverySession(nullptr, callback, + std::move(error_callback)); } protected: diff --git a/chromium/device/bluetooth/bluetooth_adapter_winrt.cc b/chromium/device/bluetooth/bluetooth_adapter_winrt.cc new file mode 100644 index 00000000000..94cde0ca5a6 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_adapter_winrt.cc @@ -0,0 +1,399 @@ +// Copyright 2018 The Chromium 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 "device/bluetooth/bluetooth_adapter_winrt.h" + +#include <windows.foundation.h> +#include <wrl/event.h> + +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/win/core_winrt_util.h" +#include "device/bluetooth/bluetooth_discovery_filter.h" + +namespace device { + +namespace { + +// In order to avoid a name clash with device::BluetoothAdapter we need this +// auxiliary namespace. +namespace uwp { +using ABI::Windows::Devices::Bluetooth::BluetoothAdapter; +} // namespace uwp +using ABI::Windows::Devices::Bluetooth::IBluetoothAdapter; +using ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics; +using ABI::Windows::Devices::Enumeration::DeviceInformation; +using ABI::Windows::Devices::Enumeration::IDeviceInformation; +using ABI::Windows::Devices::Enumeration::IDeviceInformationStatics; +using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Foundation::IAsyncOperationCompletedHandler; +using Microsoft::WRL::Callback; +using Microsoft::WRL::ComPtr; + +bool ResolveCoreWinRT() { + return base::win::ResolveCoreWinRTDelayload() && + base::win::ScopedHString::ResolveCoreWinRTStringDelayload(); +} + +// Utility functions to pretty print enum values. +constexpr const char* ToCString(AsyncStatus async_status) { + switch (async_status) { + case AsyncStatus::Started: + return "AsyncStatus::Started"; + case AsyncStatus::Completed: + return "AsyncStatus::Completed"; + case AsyncStatus::Canceled: + return "AsyncStatus::Canceled"; + case AsyncStatus::Error: + return "AsyncStatus::Error"; + } + + NOTREACHED(); + return ""; +} + +template <typename T> +using AsyncAbiT = typename ABI::Windows::Foundation::Internal::GetAbiType< + typename IAsyncOperation<T>::TResult_complex>::type; + +// Compile time switch to decide what container to use for the async results for +// |T|. Depends on whether the underlying Abi type is a pointer to IUnknown or +// not. It queries the internals of Windows::Foundation to obtain this +// information. +template <typename T> +using AsyncResultsT = + std::conditional_t<std::is_convertible<AsyncAbiT<T>, IUnknown*>::value, + ComPtr<std::remove_pointer_t<AsyncAbiT<T>>>, + AsyncAbiT<T>>; + +// Obtains the results of the provided async operation. Returns it if the +// operation completed successfully. +template <typename T> +AsyncResultsT<T> GetAsyncResults(IAsyncOperation<T>* async_op, + AsyncStatus async_status) { + if (async_status != AsyncStatus::Completed) { + VLOG(2) << "Got unexpected AsyncStatus: " << ToCString(async_status); + return {}; + } + + AsyncResultsT<T> results; + HRESULT hr = async_op->GetResults(&results); + if (FAILED(hr)) { + VLOG(2) << "GotAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + } + + return results; +} + +// This method registers a completion handler for |async_op| and will post the +// results to |callback| on |task_runner|. While a Callback can be constructed +// from callable types such as a lambda or std::function objects, it cannot be +// directly constructed from a base::OnceCallback. Thus the callback is moved +// into a capturing lambda, which then posts the callback once it is run. +// Posting the results to the TaskRunner is required, since the completion +// callback might be invoked on an arbitrary thread. Lastly, the lambda takes +// ownership of |async_op|, as this needs to be kept alive until GetAsyncResults +// can be invoked. Once that is done it is safe to release the |async_op|. +template <typename T> +HRESULT PostAsyncResults(ComPtr<IAsyncOperation<T>> async_op, + scoped_refptr<base::TaskRunner> task_runner, + base::OnceCallback<void(AsyncResultsT<T>)> callback) { + auto async_op_raw = async_op.Get(); + return async_op_raw->put_Completed( + Callback<IAsyncOperationCompletedHandler<T>>([ + async_op(std::move(async_op)), task_runner(std::move(task_runner)), + callback(std::move(callback)) + ](IAsyncOperation<T> * async_op_raw, AsyncStatus async_status) mutable { + // Note: We are using |async_op_raw| instead of async_op.Get(), as this + // could be executed on any thread and only |async_op_raw| is guaranteed + // to be in the correct COM apartment. + task_runner->PostTask( + FROM_HERE, + base::BindOnce(std::move(callback), + GetAsyncResults(async_op_raw, async_status))); + async_op.Reset(); + return S_OK; + }) + .Get()); +} + +} // namespace + +std::string BluetoothAdapterWinrt::GetAddress() const { + return address_; +} + +std::string BluetoothAdapterWinrt::GetName() const { + return name_; +} + +void BluetoothAdapterWinrt::SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +bool BluetoothAdapterWinrt::IsInitialized() const { + return is_initialized_; +} + +bool BluetoothAdapterWinrt::IsPresent() const { + return is_present_; +} + +bool BluetoothAdapterWinrt::IsPowered() const { + // Note: While the UWP APIs can provide access to the underlying radio [1] and + // its power state [2], this feature is not available for x86 Apps [3]. Thus + // we simply assume the adapter is powered if it is present. + // + // [1] + // https://docs.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothadapter.getradioasync + // [2] + // https://docs.microsoft.com/en-us/uwp/api/windows.devices.radios.radiostate + // [3] https://github.com/Microsoft/cppwinrt/issues/47#issuecomment-335181782 + if (IsPresent()) { + LOG(WARNING) << "Optimistically assuming the adapter is powered since it " + "is present. This might not actually be true."; + } + + return IsPresent(); +} + +bool BluetoothAdapterWinrt::IsDiscoverable() const { + NOTIMPLEMENTED(); + return false; +} + +void BluetoothAdapterWinrt::SetDiscoverable( + bool discoverable, + const base::Closure& callback, + const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +bool BluetoothAdapterWinrt::IsDiscovering() const { + NOTIMPLEMENTED(); + return false; +} + +BluetoothAdapter::UUIDList BluetoothAdapterWinrt::GetUUIDs() const { + NOTIMPLEMENTED(); + return UUIDList(); +} + +void BluetoothAdapterWinrt::CreateRfcommService( + const BluetoothUUID& uuid, + const ServiceOptions& options, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothAdapterWinrt::CreateL2capService( + const BluetoothUUID& uuid, + const ServiceOptions& options, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothAdapterWinrt::RegisterAdvertisement( + std::unique_ptr<BluetoothAdvertisement::Data> advertisement_data, + const CreateAdvertisementCallback& callback, + const AdvertisementErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +BluetoothLocalGattService* BluetoothAdapterWinrt::GetGattService( + const std::string& identifier) const { + NOTIMPLEMENTED(); + return nullptr; +} + +BluetoothAdapterWinrt::BluetoothAdapterWinrt() : weak_ptr_factory_(this) { + ui_task_runner_ = base::ThreadTaskRunnerHandle::Get(); +} + +BluetoothAdapterWinrt::~BluetoothAdapterWinrt() = default; + +void BluetoothAdapterWinrt::Init(InitCallback init_cb) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // We are wrapping |init_cb| in a ScopedClosureRunner to ensure it gets run no + // matter how the function exits. Furthermore, we set |is_initialized_| to + // true if adapter is still active when the callback gets run. + base::ScopedClosureRunner on_init(base::BindOnce( + [](base::WeakPtr<BluetoothAdapterWinrt> adapter, InitCallback init_cb) { + if (adapter) + adapter->is_initialized_ = true; + std::move(init_cb).Run(); + }, + weak_ptr_factory_.GetWeakPtr(), std::move(init_cb))); + + if (!ResolveCoreWinRT()) + return; + + ComPtr<IBluetoothAdapterStatics> adapter_statics; + HRESULT hr = GetBluetoothAdapterStaticsActivationFactory(&adapter_statics); + if (FAILED(hr)) { + VLOG(2) << "GetBluetoothAdapterStaticsActivationFactory failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + ComPtr<IAsyncOperation<uwp::BluetoothAdapter*>> get_default_adapter_op; + hr = adapter_statics->GetDefaultAsync(&get_default_adapter_op); + if (FAILED(hr)) { + VLOG(2) << "BluetoothAdapter::GetDefaultAsync failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + hr = PostAsyncResults( + std::move(get_default_adapter_op), ui_task_runner_, + base::BindOnce(&BluetoothAdapterWinrt::OnGetDefaultAdapter, + weak_ptr_factory_.GetWeakPtr(), std::move(on_init))); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + } +} + +bool BluetoothAdapterWinrt::SetPoweredImpl(bool powered) { + NOTIMPLEMENTED(); + return false; +} + +void BluetoothAdapterWinrt::AddDiscoverySession( + BluetoothDiscoveryFilter* discovery_filter, + const base::Closure& callback, + DiscoverySessionErrorCallback error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothAdapterWinrt::RemoveDiscoverySession( + BluetoothDiscoveryFilter* discovery_filter, + const base::Closure& callback, + DiscoverySessionErrorCallback error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothAdapterWinrt::SetDiscoveryFilter( + std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, + const base::Closure& callback, + DiscoverySessionErrorCallback error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothAdapterWinrt::RemovePairingDelegateInternal( + BluetoothDevice::PairingDelegate* pairing_delegate) { + NOTIMPLEMENTED(); +} + +HRESULT BluetoothAdapterWinrt::GetBluetoothAdapterStaticsActivationFactory( + IBluetoothAdapterStatics** statics) const { + return base::win::GetActivationFactory< + IBluetoothAdapterStatics, + RuntimeClass_Windows_Devices_Bluetooth_BluetoothAdapter>(statics); +} + +HRESULT BluetoothAdapterWinrt::GetDeviceInformationStaticsActivationFactory( + IDeviceInformationStatics** statics) const { + return base::win::GetActivationFactory< + IDeviceInformationStatics, + RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>(statics); +} + +void BluetoothAdapterWinrt::OnGetDefaultAdapter( + base::ScopedClosureRunner on_init, + ComPtr<IBluetoothAdapter> adapter) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + if (!adapter) { + VLOG(2) << "Getting Default Adapter failed."; + return; + } + + // Obtaining the default adapter will fail if no physical adapter is present. + // Thus a non-zero |adapter| implies that a physical adapter is present. + is_present_ = true; + uint64_t raw_address; + HRESULT hr = adapter->get_BluetoothAddress(&raw_address); + if (FAILED(hr)) { + VLOG(2) << "Getting BluetoothAddress failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + address_ = BluetoothDevice::CanonicalizeAddress( + base::StringPrintf("%012llX", raw_address)); + DCHECK(!address_.empty()); + + HSTRING device_id; + hr = adapter->get_DeviceId(&device_id); + if (FAILED(hr)) { + VLOG(2) << "Getting DeviceId failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + ComPtr<IDeviceInformationStatics> device_information_statics; + hr = + GetDeviceInformationStaticsActivationFactory(&device_information_statics); + if (FAILED(hr)) { + VLOG(2) << "GetDeviceInformationStaticsActivationFactory failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + ComPtr<IAsyncOperation<DeviceInformation*>> create_from_id_op; + hr = device_information_statics->CreateFromIdAsync(device_id, + &create_from_id_op); + if (FAILED(hr)) { + VLOG(2) << "CreateFromIdAsync failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + hr = PostAsyncResults( + std::move(create_from_id_op), ui_task_runner_, + base::BindOnce(&BluetoothAdapterWinrt::OnCreateFromIdAsync, + weak_ptr_factory_.GetWeakPtr(), std::move(on_init))); + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + } +} + +void BluetoothAdapterWinrt::OnCreateFromIdAsync( + base::ScopedClosureRunner on_init, + ComPtr<IDeviceInformation> device_information) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + if (!device_information) { + VLOG(2) << "Getting Device Information failed."; + return; + } + + HSTRING name; + HRESULT hr = device_information->get_Name(&name); + if (FAILED(hr)) { + VLOG(2) << "Getting Name failed: " << logging::SystemErrorCodeToString(hr); + return; + } + + name_ = base::win::ScopedHString(name).GetAsUTF8(); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter_winrt.h b/chromium/device/bluetooth/bluetooth_adapter_winrt.h new file mode 100644 index 00000000000..a607e31927d --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_adapter_winrt.h @@ -0,0 +1,123 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_WINRT_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_WINRT_H_ + +#include <windows.devices.bluetooth.h> +#include <windows.devices.enumeration.h> +#include <wrl/client.h> + +#include <memory> +#include <string> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/threading/thread_checker.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_export.h" + +namespace base { +class ScopedClosureRunner; +} + +namespace device { + +class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { + public: + // BluetoothAdapter: + std::string GetAddress() const override; + std::string GetName() const override; + void SetName(const std::string& name, + const base::Closure& callback, + const ErrorCallback& error_callback) override; + bool IsInitialized() const override; + bool IsPresent() const override; + bool IsPowered() const override; + bool IsDiscoverable() const override; + void SetDiscoverable(bool discoverable, + const base::Closure& callback, + const ErrorCallback& error_callback) override; + bool IsDiscovering() const override; + UUIDList GetUUIDs() const override; + void CreateRfcommService( + const BluetoothUUID& uuid, + const ServiceOptions& options, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) override; + void CreateL2capService( + const BluetoothUUID& uuid, + const ServiceOptions& options, + const CreateServiceCallback& callback, + const CreateServiceErrorCallback& error_callback) override; + void RegisterAdvertisement( + std::unique_ptr<BluetoothAdvertisement::Data> advertisement_data, + const CreateAdvertisementCallback& callback, + const AdvertisementErrorCallback& error_callback) override; + BluetoothLocalGattService* GetGattService( + const std::string& identifier) const override; + + protected: + friend class BluetoothAdapterWin; + friend class BluetoothTestWin; + + BluetoothAdapterWinrt(); + ~BluetoothAdapterWinrt() override; + + void Init(InitCallback init_cb); + + // BluetoothAdapter: + bool SetPoweredImpl(bool powered) override; + void AddDiscoverySession( + BluetoothDiscoveryFilter* discovery_filter, + const base::Closure& callback, + DiscoverySessionErrorCallback error_callback) override; + void RemoveDiscoverySession( + BluetoothDiscoveryFilter* discovery_filter, + const base::Closure& callback, + DiscoverySessionErrorCallback error_callback) override; + void SetDiscoveryFilter( + std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, + const base::Closure& callback, + DiscoverySessionErrorCallback error_callback) override; + void RemovePairingDelegateInternal( + BluetoothDevice::PairingDelegate* pairing_delegate) override; + + // These are declared virtual so that they can be overridden by tests. + virtual HRESULT GetBluetoothAdapterStaticsActivationFactory( + ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics** statics) + const; + virtual HRESULT GetDeviceInformationStaticsActivationFactory( + ABI::Windows::Devices::Enumeration::IDeviceInformationStatics** statics) + const; + + private: + void OnGetDefaultAdapter( + base::ScopedClosureRunner on_init, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::IBluetoothAdapter> adapter); + + void OnCreateFromIdAsync( + base::ScopedClosureRunner on_init, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Enumeration::IDeviceInformation> + device_information); + + bool is_initialized_ = false; + bool is_present_ = false; + std::string address_; + std::string name_; + + THREAD_CHECKER(thread_checker_); + + // Note: This should remain the last member so it'll be destroyed and + // invalidate its weak pointers before any other members are destroyed. + base::WeakPtrFactory<BluetoothAdapterWinrt> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothAdapterWinrt); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_WINRT_H_ diff --git a/chromium/device/bluetooth/bluetooth_device_win.h b/chromium/device/bluetooth/bluetooth_device_win.h index 0468b67e0c8..8ab8302987f 100644 --- a/chromium/device/bluetooth/bluetooth_device_win.h +++ b/chromium/device/bluetooth/bluetooth_device_win.h @@ -15,6 +15,7 @@ #include "base/macros.h" #include "base/observer_list.h" +#include "base/sequenced_task_runner.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_export.h" #include "device/bluetooth/bluetooth_task_manager_win.h" diff --git a/chromium/device/bluetooth/bluetooth_device_winrt.cc b/chromium/device/bluetooth/bluetooth_device_winrt.cc new file mode 100644 index 00000000000..932f48c65ef --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_device_winrt.cc @@ -0,0 +1,168 @@ +// Copyright 2018 The Chromium 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 "device/bluetooth/bluetooth_device_winrt.h" + +#include "base/logging.h" +#include "device/bluetooth/bluetooth_adapter_winrt.h" + +namespace device { + +BluetoothDeviceWinrt::BluetoothDeviceWinrt(BluetoothAdapterWinrt* adapter) + : BluetoothDevice(adapter) {} + +BluetoothDeviceWinrt::~BluetoothDeviceWinrt() = default; + +uint32_t BluetoothDeviceWinrt::GetBluetoothClass() const { + NOTIMPLEMENTED(); + return 0; +} + +std::string BluetoothDeviceWinrt::GetAddress() const { + NOTIMPLEMENTED(); + return std::string(); +} + +BluetoothDevice::VendorIDSource BluetoothDeviceWinrt::GetVendorIDSource() + const { + NOTIMPLEMENTED(); + return VendorIDSource(); +} + +uint16_t BluetoothDeviceWinrt::GetVendorID() const { + NOTIMPLEMENTED(); + return 0; +} + +uint16_t BluetoothDeviceWinrt::GetProductID() const { + NOTIMPLEMENTED(); + return 0; +} + +uint16_t BluetoothDeviceWinrt::GetDeviceID() const { + NOTIMPLEMENTED(); + return 0; +} + +uint16_t BluetoothDeviceWinrt::GetAppearance() const { + NOTIMPLEMENTED(); + return 0; +} + +base::Optional<std::string> BluetoothDeviceWinrt::GetName() const { + NOTIMPLEMENTED(); + return base::nullopt; +} + +bool BluetoothDeviceWinrt::IsPaired() const { + NOTIMPLEMENTED(); + return false; +} + +bool BluetoothDeviceWinrt::IsConnected() const { + NOTIMPLEMENTED(); + return false; +} + +bool BluetoothDeviceWinrt::IsGattConnected() const { + NOTIMPLEMENTED(); + return false; +} + +bool BluetoothDeviceWinrt::IsConnectable() const { + NOTIMPLEMENTED(); + return false; +} + +bool BluetoothDeviceWinrt::IsConnecting() const { + NOTIMPLEMENTED(); + return false; +} + +bool BluetoothDeviceWinrt::ExpectingPinCode() const { + NOTIMPLEMENTED(); + return false; +} + +bool BluetoothDeviceWinrt::ExpectingPasskey() const { + NOTIMPLEMENTED(); + return false; +} + +bool BluetoothDeviceWinrt::ExpectingConfirmation() const { + NOTIMPLEMENTED(); + return false; +} + +void BluetoothDeviceWinrt::GetConnectionInfo( + const ConnectionInfoCallback& callback) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::SetConnectionLatency( + ConnectionLatency connection_latency, + const base::Closure& callback, + const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::Connect(PairingDelegate* pairing_delegate, + const base::Closure& callback, + const ConnectErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::SetPinCode(const std::string& pincode) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::SetPasskey(uint32_t passkey) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::ConfirmPairing() { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::RejectPairing() { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::CancelPairing() { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::Disconnect(const base::Closure& callback, + const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::Forget(const base::Closure& callback, + const ErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::ConnectToService( + const BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::ConnectToServiceInsecurely( + const device::BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::CreateGattConnectionImpl() { + NOTIMPLEMENTED(); +} + +void BluetoothDeviceWinrt::DisconnectGatt() { + NOTIMPLEMENTED(); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_device_winrt.h b/chromium/device/bluetooth/bluetooth_device_winrt.h new file mode 100644 index 00000000000..df656cbd7e4 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_device_winrt.h @@ -0,0 +1,77 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_WINRT_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_WINRT_H_ + +#include <string> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/optional.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_export.h" + +namespace device { + +class BluetoothAdapterWinrt; + +class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWinrt : public BluetoothDevice { + public: + explicit BluetoothDeviceWinrt(BluetoothAdapterWinrt* adapter); + ~BluetoothDeviceWinrt() override; + + // BluetoothDevice: + uint32_t GetBluetoothClass() const override; + std::string GetAddress() const override; + VendorIDSource GetVendorIDSource() const override; + uint16_t GetVendorID() const override; + uint16_t GetProductID() const override; + uint16_t GetDeviceID() const override; + uint16_t GetAppearance() const override; + base::Optional<std::string> GetName() const override; + bool IsPaired() const override; + bool IsConnected() const override; + bool IsGattConnected() const override; + bool IsConnectable() const override; + bool IsConnecting() const override; + bool ExpectingPinCode() const override; + bool ExpectingPasskey() const override; + bool ExpectingConfirmation() const override; + void GetConnectionInfo(const ConnectionInfoCallback& callback) override; + void SetConnectionLatency(ConnectionLatency connection_latency, + const base::Closure& callback, + const ErrorCallback& error_callback) override; + void Connect(PairingDelegate* pairing_delegate, + const base::Closure& callback, + const ConnectErrorCallback& error_callback) override; + void SetPinCode(const std::string& pincode) override; + void SetPasskey(uint32_t passkey) override; + void ConfirmPairing() override; + void RejectPairing() override; + void CancelPairing() override; + void Disconnect(const base::Closure& callback, + const ErrorCallback& error_callback) override; + void Forget(const base::Closure& callback, + const ErrorCallback& error_callback) override; + void ConnectToService( + const BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) override; + void ConnectToServiceInsecurely( + const device::BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback) override; + + protected: + // BluetoothDevice: + void CreateGattConnectionImpl() override; + void DisconnectGatt() override; + + DISALLOW_COPY_AND_ASSIGN(BluetoothDeviceWinrt); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_WINRT_H_ diff --git a/chromium/device/bluetooth/bluetooth_socket_net.cc b/chromium/device/bluetooth/bluetooth_socket_net.cc index 31ce8af8c85..422be8c0c4d 100644 --- a/chromium/device/bluetooth/bluetooth_socket_net.cc +++ b/chromium/device/bluetooth/bluetooth_socket_net.cc @@ -17,6 +17,7 @@ #include "base/threading/thread_restrictions.h" #include "device/bluetooth/bluetooth_socket.h" #include "device/bluetooth/bluetooth_socket_thread.h" +#include "net/base/completion_callback.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/log/net_log_source.h" diff --git a/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.cc b/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.cc index 8f92a3c3077..a5c5c498535 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.cc @@ -14,6 +14,7 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/callback_helpers.h" #include "base/location.h" #include "base/logging.h" #include "base/memory/ptr_util.h" @@ -103,8 +104,8 @@ namespace device { // static base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( - const InitCallback& init_callback) { - return bluez::BluetoothAdapterBlueZ::CreateAdapter(init_callback); + InitCallback init_callback) { + return bluez::BluetoothAdapterBlueZ::CreateAdapter(std::move(init_callback)); } } // namespace device @@ -160,8 +161,9 @@ void ResetAdvertisingErrorCallbackConnector( // static base::WeakPtr<BluetoothAdapter> BluetoothAdapterBlueZ::CreateAdapter( - const InitCallback& init_callback) { - BluetoothAdapterBlueZ* adapter = new BluetoothAdapterBlueZ(init_callback); + InitCallback init_callback) { + BluetoothAdapterBlueZ* adapter = + new BluetoothAdapterBlueZ(std::move(init_callback)); return adapter->weak_ptr_factory_.GetWeakPtr(); } @@ -227,8 +229,8 @@ void BluetoothAdapterBlueZ::Shutdown() { dbus_is_shutdown_ = true; } -BluetoothAdapterBlueZ::BluetoothAdapterBlueZ(const InitCallback& init_callback) - : init_callback_(init_callback), +BluetoothAdapterBlueZ::BluetoothAdapterBlueZ(InitCallback init_callback) + : init_callback_(std::move(init_callback)), initialized_(false), dbus_is_shutdown_(false), num_discovery_sessions_(0), @@ -256,7 +258,7 @@ void BluetoothAdapterBlueZ::Init() { if (dbus_is_shutdown_ || !bluez::BluezDBusManager::Get()->IsObjectManagerSupported()) { initialized_ = true; - init_callback_.Run(); + std::move(init_callback_).Run(); return; } @@ -281,7 +283,7 @@ void BluetoothAdapterBlueZ::Init() { SetAdapter(object_paths[0]); } initialized_ = true; - init_callback_.Run(); + std::move(init_callback_).Run(); } BluetoothAdapterBlueZ::~BluetoothAdapterBlueZ() { @@ -678,6 +680,9 @@ void BluetoothAdapterBlueZ::DevicePropertyChanged( NotifyDeviceChanged(device_bluez); } + if (property_name == properties->mtu.name()) + NotifyDeviceMTUChanged(device_bluez, properties->mtu.value()); + if (property_name == properties->services_resolved.name() && properties->services_resolved.value()) { device_bluez->UpdateGattServices(object_path); @@ -1057,8 +1062,14 @@ void BluetoothAdapterBlueZ::RemoveAdapter() { NotifyAdapterPoweredChanged(false); if (properties->discoverable.value()) DiscoverableChanged(false); - if (properties->discovering.value()) - DiscoveringChanged(false); + + // The properties->discovering.value() may not be up to date with the real + // discovering state (BlueZ bug: http://crbug.com/822104). + // When the adapter is removed, make sure to clear all discovery sessions no + // matter what the current properties->discovering.value() is. + // DiscoveringChanged() properly handles the case where there is no discovery + // sessions currently. + DiscoveringChanged(false); // Move all elements of the original devices list to a new list here, // leaving the original list empty so that when we send DeviceRemoved(), @@ -1117,6 +1128,14 @@ void BluetoothAdapterBlueZ::NotifyDeviceAddressChanged( observer.DeviceAddressChanged(this, device, old_address); } +void BluetoothAdapterBlueZ::NotifyDeviceMTUChanged(BluetoothDeviceBlueZ* device, + uint16_t mtu) { + DCHECK(device->adapter_ == this); + + for (auto& observer : observers_) + observer.DeviceMTUChanged(this, device, mtu); +} + void BluetoothAdapterBlueZ::UseProfile( const BluetoothUUID& uuid, const dbus::ObjectPath& device_path, @@ -1362,10 +1381,10 @@ bool BluetoothAdapterBlueZ::SetPoweredImpl(bool powered) { void BluetoothAdapterBlueZ::AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { if (!IsPresent()) { - error_callback.Run( - UMABluetoothDiscoverySessionOutcome::ADAPTER_NOT_PRESENT); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_NOT_PRESENT); return; } BLUETOOTH_LOG(EVENT) << __func__; @@ -1377,7 +1396,7 @@ void BluetoothAdapterBlueZ::AddDiscoverySession( << "Pending request to start/stop device discovery. Queueing " << "request to start a new discovery session."; discovery_request_queue_.push( - std::make_tuple(discovery_filter, callback, error_callback)); + std::make_tuple(discovery_filter, callback, std::move(error_callback))); return; } @@ -1390,7 +1409,7 @@ void BluetoothAdapterBlueZ::AddDiscoverySession( num_discovery_sessions_++; SetDiscoveryFilter(BluetoothDiscoveryFilter::Merge( GetMergedDiscoveryFilter().get(), discovery_filter), - callback, error_callback); + callback, std::move(error_callback)); return; } @@ -1403,12 +1422,16 @@ void BluetoothAdapterBlueZ::AddDiscoverySession( std::unique_ptr<BluetoothDiscoveryFilter> df( new BluetoothDiscoveryFilter(device::BLUETOOTH_TRANSPORT_DUAL)); df->CopyFrom(*discovery_filter); + auto copyable_error_callback = + base::AdaptCallbackForRepeating(std::move(error_callback)); SetDiscoveryFilter( std::move(df), base::Bind(&BluetoothAdapterBlueZ::OnPreSetDiscoveryFilter, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback), + weak_ptr_factory_.GetWeakPtr(), callback, + copyable_error_callback), base::Bind(&BluetoothAdapterBlueZ::OnPreSetDiscoveryFilterError, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); + weak_ptr_factory_.GetWeakPtr(), callback, + copyable_error_callback)); return; } else { current_filter_.reset(); @@ -1416,33 +1439,39 @@ void BluetoothAdapterBlueZ::AddDiscoverySession( // This is the first request to start device discovery. discovery_request_pending_ = true; + auto copyable_error_callback = + base::AdaptCallbackForRepeating(std::move(error_callback)); bluez::BluezDBusManager::Get()->GetBluetoothAdapterClient()->StartDiscovery( object_path_, base::Bind(&BluetoothAdapterBlueZ::OnStartDiscovery, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback), + weak_ptr_factory_.GetWeakPtr(), callback, + copyable_error_callback), base::Bind(&BluetoothAdapterBlueZ::OnStartDiscoveryError, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); + weak_ptr_factory_.GetWeakPtr(), callback, + copyable_error_callback)); } void BluetoothAdapterBlueZ::RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { if (!IsPresent()) { - error_callback.Run( - UMABluetoothDiscoverySessionOutcome::ADAPTER_NOT_PRESENT); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_NOT_PRESENT); return; } BLUETOOTH_LOG(EVENT) << __func__; // There are active sessions other than the one currently being removed. if (num_discovery_sessions_ > 1) { - DCHECK(IsDiscovering()); + // DCHECK(IsDiscovering()) is removed due to BlueZ bug + // (https://crbug.com/822104). + // TODO(sonnysasaka): Put it back here when BlueZ bug is fixed. DCHECK(!discovery_request_pending_); num_discovery_sessions_--; SetDiscoveryFilter(GetMergedDiscoveryFilterMasked(discovery_filter), - callback, error_callback); + callback, std::move(error_callback)); return; } @@ -1451,8 +1480,8 @@ void BluetoothAdapterBlueZ::RemoveDiscoverySession( BLUETOOTH_LOG(DEBUG) << "Pending request to start/stop device discovery. Queueing " << "request to stop discovery session."; - error_callback.Run( - UMABluetoothDiscoverySessionOutcome::REMOVE_WITH_PENDING_REQUEST); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::REMOVE_WITH_PENDING_REQUEST); return; } @@ -1462,8 +1491,9 @@ void BluetoothAdapterBlueZ::RemoveDiscoverySession( // DiscoverySession API. Replace this case with an assert once it's // the deprecated methods have been removed. (See crbug.com/3445008). BLUETOOTH_LOG(DEBUG) << "No active discovery sessions. Returning error."; - error_callback.Run( - UMABluetoothDiscoverySessionOutcome::ACTIVE_SESSION_NOT_IN_ADAPTER); + std::move(error_callback) + .Run( + UMABluetoothDiscoverySessionOutcome::ACTIVE_SESSION_NOT_IN_ADAPTER); return; } @@ -1472,18 +1502,21 @@ void BluetoothAdapterBlueZ::RemoveDiscoverySession( DCHECK_EQ(num_discovery_sessions_, 1); discovery_request_pending_ = true; bluez::BluezDBusManager::Get()->GetBluetoothAdapterClient()->StopDiscovery( - object_path_, base::Bind(&BluetoothAdapterBlueZ::OnStopDiscovery, - weak_ptr_factory_.GetWeakPtr(), callback), - base::Bind(&BluetoothAdapterBlueZ::OnStopDiscoveryError, - weak_ptr_factory_.GetWeakPtr(), error_callback)); + object_path_, + base::Bind(&BluetoothAdapterBlueZ::OnStopDiscovery, + weak_ptr_factory_.GetWeakPtr(), callback), + base::BindOnce(&BluetoothAdapterBlueZ::OnStopDiscoveryError, + weak_ptr_factory_.GetWeakPtr(), + std::move(error_callback))); } void BluetoothAdapterBlueZ::SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { if (!IsPresent()) { - error_callback.Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_REMOVED); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_REMOVED); return; } @@ -1532,19 +1565,23 @@ void BluetoothAdapterBlueZ::SetDiscoveryFilter( } } + auto copyable_error_callback = + base::AdaptCallbackForRepeating(std::move(error_callback)); bluez::BluezDBusManager::Get() ->GetBluetoothAdapterClient() ->SetDiscoveryFilter( object_path_, dbus_discovery_filter, base::Bind(&BluetoothAdapterBlueZ::OnSetDiscoveryFilter, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback), + weak_ptr_factory_.GetWeakPtr(), callback, + copyable_error_callback), base::Bind(&BluetoothAdapterBlueZ::OnSetDiscoveryFilterError, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); + weak_ptr_factory_.GetWeakPtr(), callback, + copyable_error_callback)); } void BluetoothAdapterBlueZ::OnStartDiscovery( const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { // Report success on the original request and increment the count. BLUETOOTH_LOG(EVENT) << __func__; DCHECK(discovery_request_pending_); @@ -1554,7 +1591,8 @@ void BluetoothAdapterBlueZ::OnStartDiscovery( if (IsPresent()) { callback.Run(); } else { - error_callback.Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_REMOVED); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_REMOVED); } // Try to add a new discovery session for each queued request. @@ -1563,7 +1601,7 @@ void BluetoothAdapterBlueZ::OnStartDiscovery( void BluetoothAdapterBlueZ::OnStartDiscoveryError( const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback, + DiscoverySessionErrorCallback error_callback, const std::string& error_name, const std::string& error_message) { BLUETOOTH_LOG(ERROR) << object_path_.value() @@ -1575,7 +1613,7 @@ void BluetoothAdapterBlueZ::OnStartDiscoveryError( DCHECK(discovery_request_pending_); discovery_request_pending_ = false; - error_callback.Run(TranslateDiscoveryErrorToUMA(error_name)); + std::move(error_callback).Run(TranslateDiscoveryErrorToUMA(error_name)); // Try to add a new discovery session for each queued request. ProcessQueuedDiscoveryRequests(); @@ -1599,7 +1637,7 @@ void BluetoothAdapterBlueZ::OnStopDiscovery(const base::Closure& callback) { } void BluetoothAdapterBlueZ::OnStopDiscoveryError( - const DiscoverySessionErrorCallback& error_callback, + DiscoverySessionErrorCallback error_callback, const std::string& error_name, const std::string& error_message) { // Failed to stop discovery. This can only happen if the count is at 1. @@ -1623,7 +1661,7 @@ void BluetoothAdapterBlueZ::OnStopDiscoveryError( << error_message; } - error_callback.Run(TranslateDiscoveryErrorToUMA(error_name)); + std::move(error_callback).Run(TranslateDiscoveryErrorToUMA(error_name)); // Try to add a new discovery session for each queued request. ProcessQueuedDiscoveryRequests(); @@ -1631,22 +1669,26 @@ void BluetoothAdapterBlueZ::OnStopDiscoveryError( void BluetoothAdapterBlueZ::OnPreSetDiscoveryFilter( const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { // This is the first request to start device discovery. DCHECK(discovery_request_pending_); DCHECK_EQ(num_discovery_sessions_, 0); + auto copyable_error_callback = + base::AdaptCallbackForRepeating(std::move(error_callback)); bluez::BluezDBusManager::Get()->GetBluetoothAdapterClient()->StartDiscovery( object_path_, base::Bind(&BluetoothAdapterBlueZ::OnStartDiscovery, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback), + weak_ptr_factory_.GetWeakPtr(), callback, + copyable_error_callback), base::Bind(&BluetoothAdapterBlueZ::OnStartDiscoveryError, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); + weak_ptr_factory_.GetWeakPtr(), callback, + copyable_error_callback)); } void BluetoothAdapterBlueZ::OnPreSetDiscoveryFilterError( const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback, + DiscoverySessionErrorCallback error_callback, UMABluetoothDiscoverySessionOutcome outcome) { BLUETOOTH_LOG(ERROR) << object_path_.value() << ": Failed to pre set discovery filter."; @@ -1656,7 +1698,7 @@ void BluetoothAdapterBlueZ::OnPreSetDiscoveryFilterError( DCHECK(discovery_request_pending_); discovery_request_pending_ = false; - error_callback.Run(outcome); + std::move(error_callback).Run(outcome); // Try to add a new discovery session for each queued request. ProcessQueuedDiscoveryRequests(); @@ -1664,19 +1706,20 @@ void BluetoothAdapterBlueZ::OnPreSetDiscoveryFilterError( void BluetoothAdapterBlueZ::OnSetDiscoveryFilter( const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { // Report success on the original request and increment the count. BLUETOOTH_LOG(EVENT) << __func__; if (IsPresent()) { callback.Run(); } else { - error_callback.Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_REMOVED); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_REMOVED); } } void BluetoothAdapterBlueZ::OnSetDiscoveryFilterError( const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback, + DiscoverySessionErrorCallback error_callback, const std::string& error_name, const std::string& error_message) { BLUETOOTH_LOG(ERROR) << object_path_.value() @@ -1692,7 +1735,7 @@ void BluetoothAdapterBlueZ::OnSetDiscoveryFilterError( outcome = UMABluetoothDiscoverySessionOutcome:: BLUEZ_DBUS_FAILED_MAYBE_UNSUPPORTED_TRANSPORT; } - error_callback.Run(outcome); + std::move(error_callback).Run(outcome); // Try to add a new discovery session for each queued request. ProcessQueuedDiscoveryRequests(); @@ -1701,10 +1744,10 @@ void BluetoothAdapterBlueZ::OnSetDiscoveryFilterError( void BluetoothAdapterBlueZ::ProcessQueuedDiscoveryRequests() { while (!discovery_request_queue_.empty()) { BLUETOOTH_LOG(EVENT) << "Process queued discovery request."; - DiscoveryParamTuple params = discovery_request_queue_.front(); + DiscoveryParamTuple params = std::move(discovery_request_queue_.front()); discovery_request_queue_.pop(); AddDiscoverySession(std::get<0>(params), std::get<1>(params), - std::get<2>(params)); + std::move(std::get<2>(params))); // If the queued request resulted in a pending call, then let it // asynchonously process the remaining queued requests once the pending diff --git a/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.h b/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.h index 384c62c349b..093ed7825a7 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.h +++ b/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.h @@ -84,7 +84,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterBlueZ // Calls |init_callback| after a BluetoothAdapter is fully initialized. static base::WeakPtr<BluetoothAdapter> CreateAdapter( - const InitCallback& init_callback); + InitCallback init_callback); // BluetoothAdapter: void Shutdown() override; @@ -164,6 +164,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterBlueZ void NotifyDeviceAddressChanged(BluetoothDeviceBlueZ* device, const std::string& old_address); + // Announce to observers MTU change in ATT communication to |device|. + void NotifyDeviceMTUChanged(BluetoothDeviceBlueZ* device, uint16_t mtu); + // Returns the object path of the adapter. const dbus::ObjectPath& object_path() const { return object_path_; } @@ -243,7 +246,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterBlueZ using RegisterProfileCompletionPair = std::pair<base::Closure, ErrorCompletionCallback>; - explicit BluetoothAdapterBlueZ(const InitCallback& init_callback); + explicit BluetoothAdapterBlueZ(InitCallback init_callback); ~BluetoothAdapterBlueZ() override; // Init will get asynchronouly called once we know if Object Manager is @@ -339,46 +342,42 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterBlueZ void AddDiscoverySession( device::BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void RemoveDiscoverySession( device::BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void SetDiscoveryFilter( std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; // Called by dbus:: on completion of the D-Bus method call to start discovery. void OnStartDiscovery(const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback); - void OnStartDiscoveryError( - const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback, - const std::string& error_name, - const std::string& error_message); + DiscoverySessionErrorCallback error_callback); + void OnStartDiscoveryError(const base::Closure& callback, + DiscoverySessionErrorCallback error_callback, + const std::string& error_name, + const std::string& error_message); // Called by dbus:: on completion of the D-Bus method call to stop discovery. void OnStopDiscovery(const base::Closure& callback); - void OnStopDiscoveryError(const DiscoverySessionErrorCallback& error_callback, + void OnStopDiscoveryError(DiscoverySessionErrorCallback error_callback, const std::string& error_name, const std::string& error_message); - void OnPreSetDiscoveryFilter( - const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback); + void OnPreSetDiscoveryFilter(const base::Closure& callback, + DiscoverySessionErrorCallback error_callback); void OnPreSetDiscoveryFilterError( const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback, + DiscoverySessionErrorCallback error_callback, device::UMABluetoothDiscoverySessionOutcome outcome); - void OnSetDiscoveryFilter( - const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback); - void OnSetDiscoveryFilterError( - const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback, - const std::string& error_name, - const std::string& error_message); + void OnSetDiscoveryFilter(const base::Closure& callback, + DiscoverySessionErrorCallback error_callback); + void OnSetDiscoveryFilterError(const base::Closure& callback, + DiscoverySessionErrorCallback error_callback, + const std::string& error_name, + const std::string& error_message); // Called by dbus:: on completion of the D-Bus method to register a profile. void OnRegisterProfile(const device::BluetoothUUID& uuid, diff --git a/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc b/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc index b3ceaa6f728..0d5201a17e1 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc @@ -452,9 +452,12 @@ TEST_F(BluetoothBlueZTest, BecomeNotPresent) { address.compare( bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress) == 0); + // DiscoveringChanged() should be triggered regardless of the current value + // of Discovering property. + EXPECT_EQ(1, observer.discovering_changed_count()); + // Other callbacks shouldn't be called since the values are false. EXPECT_EQ(0, observer.powered_changed_count()); - EXPECT_EQ(0, observer.discovering_changed_count()); EXPECT_FALSE(adapter_->IsPowered()); EXPECT_FALSE(adapter_->IsDiscovering()); } @@ -494,9 +497,12 @@ TEST_F(BluetoothBlueZTest, SecondAdapter) { address.compare( bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress) == 0); + // DiscoveringChanged() should be triggered regardless of the current value + // of Discovering property. + EXPECT_EQ(1, observer.discovering_changed_count()); + // Other callbacks shouldn't be called since the values are false. EXPECT_EQ(0, observer.powered_changed_count()); - EXPECT_EQ(0, observer.discovering_changed_count()); EXPECT_FALSE(adapter_->IsPowered()); EXPECT_FALSE(adapter_->IsDiscovering()); @@ -2440,6 +2446,35 @@ TEST_F(BluetoothBlueZTest, DevicePairedChanged) { EXPECT_TRUE(observer.device_new_paired_status()); EXPECT_EQ(devices[idx], observer.last_device()); } + +TEST_F(BluetoothBlueZTest, DeviceMTUChanged) { + // Simulate a change of MTU of a device. + GetAdapter(); + + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(bluez::FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + // Install an observer; expect DeviceMTUChanged method to be called with the + // updated MTU values. + TestBluetoothAdapterObserver observer(adapter_); + + bluez::FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(0, observer.device_mtu_changed_count()); + + properties->mtu.ReplaceValue(258); + EXPECT_EQ(1, observer.device_mtu_changed_count()); + EXPECT_EQ(258, observer.last_mtu_value()); + + properties->mtu.ReplaceValue(128); + EXPECT_EQ(2, observer.device_mtu_changed_count()); + EXPECT_EQ(128, observer.last_mtu_value()); +} #endif TEST_F(BluetoothBlueZTest, DeviceUuidsChanged) { diff --git a/chromium/device/bluetooth/bluez/bluetooth_gatt_service_bluez.cc b/chromium/device/bluetooth/bluez/bluetooth_gatt_service_bluez.cc index 9d88b747391..aace302a83e 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_gatt_service_bluez.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_gatt_service_bluez.cc @@ -34,8 +34,7 @@ BluetoothGattServiceBlueZ::DBusErrorToServiceError(std::string error_name) { code = GATT_ERROR_IN_PROGRESS; } else if (error_name == bluetooth_gatt_service::kErrorInvalidValueLength) { code = GATT_ERROR_INVALID_LENGTH; - } else if (error_name == bluetooth_gatt_service::kErrorReadNotPermitted || - error_name == bluetooth_gatt_service::kErrorWriteNotPermitted) { + } else if (error_name == bluetooth_gatt_service::kErrorNotPermitted) { code = GATT_ERROR_NOT_PERMITTED; } else if (error_name == bluetooth_gatt_service::kErrorNotAuthorized) { code = GATT_ERROR_NOT_AUTHORIZED; diff --git a/chromium/device/bluetooth/bluez/bluetooth_service_record_bluez_unittest.cc b/chromium/device/bluetooth/bluez/bluetooth_service_record_bluez_unittest.cc index af35997b09e..024761df4ec 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_service_record_bluez_unittest.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_service_record_bluez_unittest.cc @@ -10,7 +10,6 @@ #include "base/bind_helpers.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluez/bluetooth_adapter_bluez.h" diff --git a/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc b/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc index ba185a232df..af5df4cbd04 100644 --- a/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc +++ b/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc @@ -38,10 +38,12 @@ BluetoothAdapterCast::DiscoveryParams::DiscoveryParams( DiscoverySessionErrorCallback error_callback) : filter(filter), success_callback(success_callback), - error_callback(error_callback) {} + error_callback(std::move(error_callback)) {} -BluetoothAdapterCast::DiscoveryParams::DiscoveryParams(const DiscoveryParams&) = - default; +BluetoothAdapterCast::DiscoveryParams::DiscoveryParams( + DiscoveryParams&& params) noexcept = default; +BluetoothAdapterCast::DiscoveryParams& BluetoothAdapterCast::DiscoveryParams:: +operator=(DiscoveryParams&& params) = default; BluetoothAdapterCast::DiscoveryParams::~DiscoveryParams() = default; BluetoothAdapterCast::BluetoothAdapterCast( @@ -184,7 +186,7 @@ bool BluetoothAdapterCast::SetPoweredImpl(bool powered) { void BluetoothAdapterCast::AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { // The discovery filter is unused for now, as the Cast bluetooth stack does // not expose scan filters yet. However, implementation of filtering would // save numerous UI<->IO threadhops by eliminating uneccessary calls to @@ -200,8 +202,8 @@ void BluetoothAdapterCast::AddDiscoverySession( } // Add this request to the queue. - pending_discovery_requests_.push(DiscoveryParams( - discovery_filter, std::move(callback), std::move(error_callback))); + pending_discovery_requests_.emplace(discovery_filter, std::move(callback), + std::move(error_callback)); // If the queue length is greater than 1 (i.e. there was a pending request // when this method was called), exit early. This request will be processed @@ -219,7 +221,7 @@ void BluetoothAdapterCast::AddDiscoverySession( void BluetoothAdapterCast::RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { // The discovery filter is unused for now, as the Cast bluetooth stack does // not expose scan filters yet. However, implementation of filtering would // save numerous UI<->IO threadhops by eliminating uneccessary calls to @@ -230,8 +232,8 @@ void BluetoothAdapterCast::RemoveDiscoverySession( // If there are pending requests, run the error call immediately. if (pending_discovery_requests_.size() > 0u || pending_disable_discovery_request_) { - error_callback.Run( - UMABluetoothDiscoverySessionOutcome::REMOVE_WITH_PENDING_REQUEST); + std::move(error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::REMOVE_WITH_PENDING_REQUEST); return; } @@ -242,8 +244,8 @@ void BluetoothAdapterCast::RemoveDiscoverySession( } // This was the last active discovery session. Disable scanning. - pending_disable_discovery_request_ = - DiscoveryParams(discovery_filter, callback, error_callback); + pending_disable_discovery_request_.emplace(discovery_filter, callback, + std::move(error_callback)); le_scan_manager_->SetScanEnable( false, base::BindOnce(&BluetoothAdapterCast::OnScanDisabled, weak_factory_.GetWeakPtr())); @@ -252,7 +254,7 @@ void BluetoothAdapterCast::RemoveDiscoverySession( void BluetoothAdapterCast::SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) { + DiscoverySessionErrorCallback error_callback) { // The discovery filter is unused for now, as the Cast bluetooth stack does // not expose scan filters yet. However, implementation of filtering would // save numerous UI<->IO threadhops by eliminating unnecessary calls to @@ -298,24 +300,6 @@ void BluetoothAdapterCast::OnMtuChanged( << " mtu: " << mtu; } -void BluetoothAdapterCast::OnServicesUpdated( - scoped_refptr<chromecast::bluetooth::RemoteDevice> device, - std::vector<scoped_refptr<chromecast::bluetooth::RemoteService>> services) { - DVLOG(1) << __func__; - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - std::string address = GetCanonicalBluetoothAddress(device->addr()); - BluetoothDeviceCast* cast_device = GetCastDevice(address); - if (!cast_device) - return; - - if (!cast_device->UpdateServices(services)) - LOG(WARNING) << "The services were not updated. Alerting anyway."; - - cast_device->SetGattServicesDiscoveryComplete(true); - NotifyGattServicesDiscovered(cast_device); -} - void BluetoothAdapterCast::OnCharacteristicNotification( scoped_refptr<chromecast::bluetooth::RemoteDevice> device, scoped_refptr<chromecast::bluetooth::RemoteCharacteristic> characteristic, @@ -345,17 +329,19 @@ void BluetoothAdapterCast::OnNewScanResult( // to send an async request to |gatt_client_manager_| for a handle to the // device. if (devices_.find(address) == devices_.end()) { + bool first_time_seen = + pending_scan_results_.find(address) == pending_scan_results_.end(); + // These results will be used to construct the BluetoothDeviceCast. + pending_scan_results_[address].push_back(result); + // Only send a request if this is the first time we've seen this |address| // in a scan. This may happen if we pick up additional GAP advertisements // while the first request is in-flight. - if (pending_scan_results_.find(address) == pending_scan_results_.end()) { + if (first_time_seen) { gatt_client_manager_->GetDevice( result.addr, base::BindOnce(&BluetoothAdapterCast::OnGetDevice, weak_factory_.GetWeakPtr())); } - - // These results will be used to construct the BluetoothDeviceCast. - pending_scan_results_[address].push_back(result); return; } @@ -416,8 +402,8 @@ void BluetoothAdapterCast::OnScanEnabled(bool enabled) { // Run the error callback. DCHECK(!pending_discovery_requests_.empty()); - pending_discovery_requests_.front().error_callback.Run( - UMABluetoothDiscoverySessionOutcome::FAILED); + std::move(pending_discovery_requests_.front().error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::FAILED); pending_discovery_requests_.pop(); // If there is another pending request, try again. @@ -450,8 +436,8 @@ void BluetoothAdapterCast::OnScanDisabled(bool success) { LOG(WARNING) << "Failed to stop scan."; // Run the error callback. - pending_disable_discovery_request_->error_callback.Run( - UMABluetoothDiscoverySessionOutcome::FAILED); + std::move(pending_disable_discovery_request_->error_callback) + .Run(UMABluetoothDiscoverySessionOutcome::FAILED); pending_disable_discovery_request_ = base::nullopt; return; } @@ -527,7 +513,7 @@ base::WeakPtr<BluetoothAdapter> BluetoothAdapterCast::Create( // static base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( - const InitCallback& init_callback) { + InitCallback init_callback) { return BluetoothAdapterCast::Create(std::move(init_callback)); } diff --git a/chromium/device/bluetooth/cast/bluetooth_adapter_cast.h b/chromium/device/bluetooth/cast/bluetooth_adapter_cast.h index bf09fc72eba..7dca46652a5 100644 --- a/chromium/device/bluetooth/cast/bluetooth_adapter_cast.h +++ b/chromium/device/bluetooth/cast/bluetooth_adapter_cast.h @@ -93,15 +93,15 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterCast void AddDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void RemoveDiscoverySession( BluetoothDiscoveryFilter* discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, - const DiscoverySessionErrorCallback& error_callback) override; + DiscoverySessionErrorCallback error_callback) override; void RemovePairingDelegateInternal( BluetoothDevice::PairingDelegate* pairing_delegate) override; @@ -137,10 +137,6 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterCast bool connected) override; void OnMtuChanged(scoped_refptr<chromecast::bluetooth::RemoteDevice> device, int mtu) override; - void OnServicesUpdated( - scoped_refptr<chromecast::bluetooth::RemoteDevice> device, - std::vector<scoped_refptr<chromecast::bluetooth::RemoteService>> services) - override; void OnCharacteristicNotification( scoped_refptr<chromecast::bluetooth::RemoteDevice> device, scoped_refptr<chromecast::bluetooth::RemoteCharacteristic> characteristic, @@ -173,7 +169,8 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterCast DiscoveryParams(device::BluetoothDiscoveryFilter* filter, base::Closure success_callback, DiscoverySessionErrorCallback error_callback); - DiscoveryParams(const DiscoveryParams&); + DiscoveryParams(DiscoveryParams&& params) noexcept; + DiscoveryParams& operator=(DiscoveryParams&& params); ~DiscoveryParams(); device::BluetoothDiscoveryFilter* filter = nullptr; base::Closure success_callback; diff --git a/chromium/device/bluetooth/cast/bluetooth_device_cast.cc b/chromium/device/bluetooth/cast/bluetooth_device_cast.cc index 1dfeba95197..32ccb2a546c 100644 --- a/chromium/device/bluetooth/cast/bluetooth_device_cast.cc +++ b/chromium/device/bluetooth/cast/bluetooth_device_cast.cc @@ -14,6 +14,7 @@ #include "chromecast/device/bluetooth/bluetooth_util.h" #include "chromecast/device/bluetooth/le/remote_characteristic.h" #include "chromecast/device/bluetooth/le/remote_service.h" +#include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.h" #include "device/bluetooth/cast/bluetooth_remote_gatt_service_cast.h" #include "device/bluetooth/cast/bluetooth_utils.h" @@ -57,9 +58,15 @@ BluetoothDeviceCast::BluetoothDeviceCast( BluetoothAdapter* adapter, scoped_refptr<chromecast::bluetooth::RemoteDevice> device) : BluetoothDevice(adapter), + connected_(device->IsConnected()), remote_device_(std::move(device)), address_(GetCanonicalBluetoothAddress(remote_device_->addr())), - weak_factory_(this) {} + weak_factory_(this) { + if (connected_) { + remote_device_->GetServices(base::BindOnce( + &BluetoothDeviceCast::OnGetServices, weak_factory_.GetWeakPtr())); + } +} BluetoothDeviceCast::~BluetoothDeviceCast() {} @@ -285,6 +292,8 @@ bool BluetoothDeviceCast::SetConnected(bool connected) { // fired. if (!was_connected && connected) { DidConnectGatt(); + remote_device_->GetServices(base::BindOnce( + &BluetoothDeviceCast::OnGetServices, weak_factory_.GetWeakPtr())); } else if (was_connected && !connected) { DidDisconnectGatt(); } @@ -293,43 +302,23 @@ bool BluetoothDeviceCast::SetConnected(bool connected) { return was_connected != connected; } -bool BluetoothDeviceCast::UpdateServices( +void BluetoothDeviceCast::OnGetServices( std::vector<scoped_refptr<chromecast::bluetooth::RemoteService>> services) { DVLOG(2) << __func__; - bool changed = false; - - // Create a look-up for the updated list of services. - std::unordered_set<std::string> new_service_uuids; - for (const auto& service : services) - new_service_uuids.insert(GetCanonicalBluetoothUuid(service->uuid())); - - // Remove any services in |gatt_services_| that are not present in |services|. - for (auto it = gatt_services_.cbegin(); it != gatt_services_.cend();) { - if (new_service_uuids.find(it->first) == new_service_uuids.end()) { - gatt_services_.erase(it++); - changed = true; - } else { - ++it; - } - } + gatt_services_.clear(); // Add new services. for (auto& service : services) { auto key = GetCanonicalBluetoothUuid(service->uuid()); - - if (gatt_services_.find(key) != gatt_services_.end()) - continue; - auto cast_service = std::make_unique<BluetoothRemoteGattServiceCast>( this, std::move(service)); DCHECK_EQ(key, cast_service->GetIdentifier()); gatt_services_[key] = std::move(cast_service); - changed = true; } - if (changed) - device_uuids_.ReplaceServiceUUIDs(gatt_services_); - return changed; + device_uuids_.ReplaceServiceUUIDs(gatt_services_); + SetGattServicesDiscoveryComplete(true); + adapter_->NotifyGattServicesDiscovered(this); } bool BluetoothDeviceCast::UpdateCharacteristicValue( @@ -374,18 +363,14 @@ void BluetoothDeviceCast::DisconnectGatt() { void BluetoothDeviceCast::OnConnect(bool success) { DVLOG(2) << __func__ << " success:" << success; pending_connect_ = false; - if (success) - SetConnected(true); - else + if (!success) DidFailToConnectGatt(ERROR_FAILED); } void BluetoothDeviceCast::OnDisconnect(bool success) { DVLOG(2) << __func__ << " success:" << success; pending_disconnect_ = false; - if (success) - SetConnected(false); - else + if (!success) LOG(ERROR) << "Request to DisconnectGatt() failed!"; } diff --git a/chromium/device/bluetooth/cast/bluetooth_device_cast.h b/chromium/device/bluetooth/cast/bluetooth_device_cast.h index 562ba724dfa..0f212b1f357 100644 --- a/chromium/device/bluetooth/cast/bluetooth_device_cast.h +++ b/chromium/device/bluetooth/cast/bluetooth_device_cast.h @@ -95,13 +95,6 @@ class BluetoothDeviceCast : public BluetoothDevice { // connection state changed as a result. bool SetConnected(bool connected); - // Called by BluetoothAdapterCast when the GATT services for this device are - // updated. Updates the services in this devices to reflect |services|. - // Returns true if a service was added or removed. - bool UpdateServices( - std::vector<scoped_refptr<chromecast::bluetooth::RemoteService>> - services); - // Called by BluetoothAdapterCast when the value of a characteristic in one of // this device's services has changed, resulting in a notification to the // device. Locate the characteristc and update the underluing value. If the @@ -132,7 +125,12 @@ class BluetoothDeviceCast : public BluetoothDevice { // Called back from disconnect requests. void OnDisconnect(bool success); - bool connected_ = false; + // Called in response to GetServices + void OnGetServices( + std::vector<scoped_refptr<chromecast::bluetooth::RemoteService>> + services); + + bool connected_; bool pending_connect_ = false; bool pending_disconnect_ = false; diff --git a/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.cc b/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.cc index b0ae0d4b06c..3876ee9d587 100644 --- a/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.cc +++ b/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.cc @@ -164,7 +164,6 @@ void BluetoothRemoteGattCharacteristicCast::WriteRemoteCharacteristic( const base::Closure& callback, const ErrorCallback& error_callback) { remote_characteristic_->Write( - chromecast::bluetooth_v2_shlib::Gatt::WriteType::WRITE_TYPE_DEFAULT, value, base::BindOnce( &BluetoothRemoteGattCharacteristicCast::OnWriteRemoteCharacteristic, diff --git a/chromium/device/bluetooth/dbus/bluetooth_adapter_client.cc b/chromium/device/bluetooth/dbus/bluetooth_adapter_client.cc index a707cc78670..11d9ca5d255 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_adapter_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_adapter_client.cc @@ -203,14 +203,14 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, // BluetoothAdapterClient override. void StartDiscovery(const dbus::ObjectPath& object_path, const base::Closure& callback, - const ErrorCallback& error_callback) override { + ErrorCallback error_callback) override { dbus::MethodCall method_call(bluetooth_adapter::kBluetoothAdapterInterface, bluetooth_adapter::kStartDiscovery); dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy(object_path); if (!object_proxy) { - error_callback.Run(kUnknownAdapterError, ""); + std::move(error_callback).Run(kUnknownAdapterError, ""); return; } @@ -219,20 +219,21 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, base::BindOnce(&BluetoothAdapterClientImpl::OnSuccess, weak_ptr_factory_.GetWeakPtr(), callback), base::BindOnce(&BluetoothAdapterClientImpl::OnError, - weak_ptr_factory_.GetWeakPtr(), error_callback)); + weak_ptr_factory_.GetWeakPtr(), + std::move(error_callback))); } // BluetoothAdapterClient override. void StopDiscovery(const dbus::ObjectPath& object_path, const base::Closure& callback, - const ErrorCallback& error_callback) override { + ErrorCallback error_callback) override { dbus::MethodCall method_call(bluetooth_adapter::kBluetoothAdapterInterface, bluetooth_adapter::kStopDiscovery); dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy(object_path); if (!object_proxy) { - error_callback.Run(kUnknownAdapterError, ""); + std::move(error_callback).Run(kUnknownAdapterError, ""); return; } @@ -241,14 +242,15 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, base::BindOnce(&BluetoothAdapterClientImpl::OnSuccess, weak_ptr_factory_.GetWeakPtr(), callback), base::BindOnce(&BluetoothAdapterClientImpl::OnError, - weak_ptr_factory_.GetWeakPtr(), error_callback)); + weak_ptr_factory_.GetWeakPtr(), + std::move(error_callback))); } // BluetoothAdapterClient override. void RemoveDevice(const dbus::ObjectPath& object_path, const dbus::ObjectPath& device_path, const base::Closure& callback, - const ErrorCallback& error_callback) override { + ErrorCallback error_callback) override { dbus::MethodCall method_call(bluetooth_adapter::kBluetoothAdapterInterface, bluetooth_adapter::kRemoveDevice); @@ -258,7 +260,7 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy(object_path); if (!object_proxy) { - error_callback.Run(kUnknownAdapterError, ""); + std::move(error_callback).Run(kUnknownAdapterError, ""); return; } @@ -267,14 +269,15 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, base::BindOnce(&BluetoothAdapterClientImpl::OnSuccess, weak_ptr_factory_.GetWeakPtr(), callback), base::BindOnce(&BluetoothAdapterClientImpl::OnError, - weak_ptr_factory_.GetWeakPtr(), error_callback)); + weak_ptr_factory_.GetWeakPtr(), + std::move(error_callback))); } // BluetoothAdapterClient override. void SetDiscoveryFilter(const dbus::ObjectPath& object_path, const DiscoveryFilter& discovery_filter, const base::Closure& callback, - const ErrorCallback& error_callback) override { + ErrorCallback error_callback) override { dbus::MethodCall method_call(bluetooth_adapter::kBluetoothAdapterInterface, bluetooth_adapter::kSetDiscoveryFilter); @@ -284,7 +287,7 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy(object_path); if (!object_proxy) { - error_callback.Run(kUnknownAdapterError, ""); + std::move(error_callback).Run(kUnknownAdapterError, ""); return; } @@ -346,14 +349,15 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, base::BindOnce(&BluetoothAdapterClientImpl::OnSuccess, weak_ptr_factory_.GetWeakPtr(), callback), base::BindOnce(&BluetoothAdapterClientImpl::OnError, - weak_ptr_factory_.GetWeakPtr(), error_callback)); + weak_ptr_factory_.GetWeakPtr(), + std::move(error_callback))); } // BluetoothAdapterClient override. void CreateServiceRecord(const dbus::ObjectPath& object_path, const bluez::BluetoothServiceRecordBlueZ& record, const ServiceRecordCallback& callback, - const ErrorCallback& error_callback) override { + ErrorCallback error_callback) override { dbus::MethodCall method_call(bluetooth_adapter::kBluetoothAdapterInterface, bluetooth_adapter::kCreateServiceRecord); @@ -374,7 +378,7 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy(object_path); if (!object_proxy) { - error_callback.Run(kUnknownAdapterError, ""); + std::move(error_callback).Run(kUnknownAdapterError, ""); return; } @@ -383,14 +387,15 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, base::BindOnce(&BluetoothAdapterClientImpl::OnCreateServiceRecord, weak_ptr_factory_.GetWeakPtr(), callback), base::BindOnce(&BluetoothAdapterClientImpl::OnError, - weak_ptr_factory_.GetWeakPtr(), error_callback)); + weak_ptr_factory_.GetWeakPtr(), + std::move(error_callback))); } // BluetoothAdapterClient override. void RemoveServiceRecord(const dbus::ObjectPath& object_path, uint32_t handle, const base::Closure& callback, - const ErrorCallback& error_callback) override { + ErrorCallback error_callback) override { dbus::MethodCall method_call(bluetooth_adapter::kBluetoothAdapterInterface, bluetooth_adapter::kRemoveServiceRecord); @@ -399,7 +404,7 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy(object_path); if (!object_proxy) { - error_callback.Run(kUnknownAdapterError, ""); + std::move(error_callback).Run(kUnknownAdapterError, ""); return; } @@ -408,7 +413,8 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, base::BindOnce(&BluetoothAdapterClientImpl::OnSuccess, weak_ptr_factory_.GetWeakPtr(), callback), base::BindOnce(&BluetoothAdapterClientImpl::OnError, - weak_ptr_factory_.GetWeakPtr(), error_callback)); + weak_ptr_factory_.GetWeakPtr(), + std::move(error_callback))); } protected: @@ -466,8 +472,7 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, } // Called when a response for a failed method call is received. - void OnError(const ErrorCallback& error_callback, - dbus::ErrorResponse* response) { + void OnError(ErrorCallback error_callback, dbus::ErrorResponse* response) { // Error response has optional error message argument. std::string error_name; std::string error_message; @@ -479,7 +484,7 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, error_name = kNoResponseError; error_message = ""; } - error_callback.Run(error_name, error_message); + std::move(error_callback).Run(error_name, error_message); } dbus::ObjectManager* object_manager_; diff --git a/chromium/device/bluetooth/dbus/bluetooth_adapter_client.h b/chromium/device/bluetooth/dbus/bluetooth_adapter_client.h index ba625268a86..0288b76627a 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_adapter_client.h +++ b/chromium/device/bluetooth/dbus/bluetooth_adapter_client.h @@ -143,19 +143,20 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterClient : public BluezDBusClient { // The ErrorCallback is used by adapter methods to indicate failure. // It receives two arguments: the name of the error in |error_name| and // an optional message in |error_message|. - typedef base::Callback<void(const std::string& error_name, - const std::string& error_message)> ErrorCallback; + typedef base::OnceCallback<void(const std::string& error_name, + const std::string& error_message)> + ErrorCallback; // Starts a device discovery on the adapter with object path |object_path|. virtual void StartDiscovery(const dbus::ObjectPath& object_path, const base::Closure& callback, - const ErrorCallback& error_callback) = 0; + ErrorCallback error_callback) = 0; // Cancels any previous device discovery on the adapter with object path // |object_path|. virtual void StopDiscovery(const dbus::ObjectPath& object_path, const base::Closure& callback, - const ErrorCallback& error_callback) = 0; + ErrorCallback error_callback) = 0; // Removes from the adapter with object path |object_path| the remote // device with object path |object_path| from the list of known devices @@ -163,7 +164,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterClient : public BluezDBusClient { virtual void RemoveDevice(const dbus::ObjectPath& object_path, const dbus::ObjectPath& device_path, const base::Closure& callback, - const ErrorCallback& error_callback) = 0; + ErrorCallback error_callback) = 0; // Sets the device discovery filter on the adapter with object path // |object_path|. When this method is called with no filter parameter, filter @@ -174,21 +175,21 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterClient : public BluezDBusClient { virtual void SetDiscoveryFilter(const dbus::ObjectPath& object_path, const DiscoveryFilter& discovery_filter, const base::Closure& callback, - const ErrorCallback& error_callback) = 0; + ErrorCallback error_callback) = 0; // Creates the service record |record| on the adapter with the object path // |object_path|. virtual void CreateServiceRecord(const dbus::ObjectPath& object_path, const BluetoothServiceRecordBlueZ& record, const ServiceRecordCallback& callback, - const ErrorCallback& error_callback) = 0; + ErrorCallback error_callback) = 0; // Removes the service record with the uuid |uuid| on the adapter with the // object path |object_path|. virtual void RemoveServiceRecord(const dbus::ObjectPath& object_path, uint32_t handle, const base::Closure& callback, - const ErrorCallback& error_callback) = 0; + ErrorCallback error_callback) = 0; // Creates the instance. static BluetoothAdapterClient* Create(); diff --git a/chromium/device/bluetooth/dbus/bluetooth_device_client.cc b/chromium/device/bluetooth/dbus/bluetooth_device_client.cc index df13ba34189..9a69b3ef415 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_device_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_device_client.cc @@ -208,6 +208,7 @@ BluetoothDeviceClient::Properties::Properties( &services_resolved); RegisterProperty(bluetooth_device::kAdvertisingDataFlagsProperty, &advertising_data_flags); + RegisterProperty(bluetooth_device::kMTUProperty, &mtu); } BluetoothDeviceClient::Properties::~Properties() = default; diff --git a/chromium/device/bluetooth/dbus/bluetooth_device_client.h b/chromium/device/bluetooth/dbus/bluetooth_device_client.h index a7313bad404..d670b360f67 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_device_client.h +++ b/chromium/device/bluetooth/dbus/bluetooth_device_client.h @@ -123,6 +123,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceClient : public BluezDBusClient { // The Advertising Data Flags of the remote device. Read-only. dbus::Property<std::vector<uint8_t>> advertising_data_flags; + // The MTU used in ATT communication with the remote device. Read-only. + dbus::Property<uint16_t> mtu; + Properties(dbus::ObjectProxy* object_proxy, const std::string& interface_name, const PropertyChangedCallback& callback); diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.cc index 2f24e8cda68..0577108ff3a 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.cc @@ -133,9 +133,10 @@ FakeBluetoothAdapterClient::GetProperties(const dbus::ObjectPath& object_path) { void FakeBluetoothAdapterClient::StartDiscovery( const dbus::ObjectPath& object_path, const base::Closure& callback, - const ErrorCallback& error_callback) { + ErrorCallback error_callback) { if (object_path != dbus::ObjectPath(kAdapterPath)) { - PostDelayedTask(base::Bind(error_callback, kNoResponseError, "")); + PostDelayedTask( + base::BindOnce(std::move(error_callback), kNoResponseError, "")); return; } @@ -157,15 +158,17 @@ void FakeBluetoothAdapterClient::StartDiscovery( void FakeBluetoothAdapterClient::StopDiscovery( const dbus::ObjectPath& object_path, const base::Closure& callback, - const ErrorCallback& error_callback) { + ErrorCallback error_callback) { if (object_path != dbus::ObjectPath(kAdapterPath)) { - PostDelayedTask(base::Bind(error_callback, kNoResponseError, "")); + PostDelayedTask( + base::BindOnce(std::move(error_callback), kNoResponseError, "")); return; } if (!discovering_count_) { LOG(WARNING) << "StopDiscovery called when not discovering"; - PostDelayedTask(base::Bind(error_callback, kNoResponseError, "")); + PostDelayedTask( + base::BindOnce(std::move(error_callback), kNoResponseError, "")); return; } @@ -194,9 +197,9 @@ void FakeBluetoothAdapterClient::RemoveDevice( const dbus::ObjectPath& object_path, const dbus::ObjectPath& device_path, const base::Closure& callback, - const ErrorCallback& error_callback) { + ErrorCallback error_callback) { if (object_path != dbus::ObjectPath(kAdapterPath)) { - error_callback.Run(kNoResponseError, ""); + std::move(error_callback).Run(kNoResponseError, ""); return; } @@ -218,15 +221,17 @@ void FakeBluetoothAdapterClient::SetDiscoveryFilter( const dbus::ObjectPath& object_path, const DiscoveryFilter& discovery_filter, const base::Closure& callback, - const ErrorCallback& error_callback) { + ErrorCallback error_callback) { if (object_path != dbus::ObjectPath(kAdapterPath)) { - PostDelayedTask(base::Bind(error_callback, kNoResponseError, "")); + PostDelayedTask( + base::BindOnce(std::move(error_callback), kNoResponseError, "")); return; } VLOG(1) << "SetDiscoveryFilter: " << object_path.value(); if (set_discovery_filter_should_fail_) { - PostDelayedTask(base::Bind(error_callback, kNoResponseError, "")); + PostDelayedTask( + base::BindOnce(std::move(error_callback), kNoResponseError, "")); set_discovery_filter_should_fail_ = false; return; } @@ -240,7 +245,7 @@ void FakeBluetoothAdapterClient::CreateServiceRecord( const dbus::ObjectPath& object_path, const bluez::BluetoothServiceRecordBlueZ& record, const ServiceRecordCallback& callback, - const ErrorCallback& error_callback) { + ErrorCallback error_callback) { ++last_handle_; records_.insert( std::pair<uint32_t, BluetoothServiceRecordBlueZ>(last_handle_, record)); @@ -251,11 +256,12 @@ void FakeBluetoothAdapterClient::RemoveServiceRecord( const dbus::ObjectPath& object_path, uint32_t handle, const base::Closure& callback, - const ErrorCallback& error_callback) { + ErrorCallback error_callback) { auto it = records_.find(handle); if (it == records_.end()) { - error_callback.Run(bluetooth_adapter::kErrorDoesNotExist, - "Service record does not exist."); + std::move(error_callback) + .Run(bluetooth_adapter::kErrorDoesNotExist, + "Service record does not exist."); return; } records_.erase(it); @@ -337,10 +343,9 @@ void FakeBluetoothAdapterClient::OnPropertyChanged( } } -void FakeBluetoothAdapterClient::PostDelayedTask( - const base::Closure& callback) { +void FakeBluetoothAdapterClient::PostDelayedTask(base::OnceClosure callback) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( - FROM_HERE, callback, + FROM_HERE, std::move(callback), base::TimeDelta::FromMilliseconds(simulation_interval_ms_)); } diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.h index f43bddd4ca7..7e8b234dc16 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.h @@ -48,26 +48,26 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothAdapterClient Properties* GetProperties(const dbus::ObjectPath& object_path) override; void StartDiscovery(const dbus::ObjectPath& object_path, const base::Closure& callback, - const ErrorCallback& error_callback) override; + ErrorCallback error_callback) override; void StopDiscovery(const dbus::ObjectPath& object_path, const base::Closure& callback, - const ErrorCallback& error_callback) override; + ErrorCallback error_callback) override; void RemoveDevice(const dbus::ObjectPath& object_path, const dbus::ObjectPath& device_path, const base::Closure& callback, - const ErrorCallback& error_callback) override; + ErrorCallback error_callback) override; void SetDiscoveryFilter(const dbus::ObjectPath& object_path, const DiscoveryFilter& discovery_filter, const base::Closure& callback, - const ErrorCallback& error_callback) override; + ErrorCallback error_callback) override; void CreateServiceRecord(const dbus::ObjectPath& object_path, const bluez::BluetoothServiceRecordBlueZ& record, const ServiceRecordCallback& callback, - const ErrorCallback& error_callback) override; + ErrorCallback error_callback) override; void RemoveServiceRecord(const dbus::ObjectPath& object_path, uint32_t handle, const base::Closure& callback, - const ErrorCallback& error_callback) override; + ErrorCallback error_callback) override; // Sets the current simulation timeout interval. void SetSimulationIntervalMs(int interval_ms); @@ -104,7 +104,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothAdapterClient // Posts the delayed task represented by |callback| onto the current // message loop to be executed after |simulation_interval_ms_| milliseconds. - void PostDelayedTask(const base::Closure& callback); + void PostDelayedTask(base::OnceClosure callback); // List of observers interested in event notifications from us. base::ObserverList<Observer> observers_; diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc index 8973c603915..66fc0f11abe 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc @@ -148,7 +148,7 @@ void FakeBluetoothGattCharacteristicClient::ReadValue( } if (object_path.value() == heart_rate_control_point_path_) { - error_callback.Run(bluetooth_gatt_service::kErrorReadNotPermitted, + error_callback.Run(bluetooth_gatt_service::kErrorNotPermitted, "Reads of this value are not allowed"); return; } @@ -226,7 +226,7 @@ void FakeBluetoothGattCharacteristicClient::WriteValue( } if (object_path.value() != heart_rate_control_point_path_) { - error_callback.Run(bluetooth_gatt_service::kErrorWriteNotPermitted, + error_callback.Run(bluetooth_gatt_service::kErrorNotPermitted, "Writes of this value are not allowed"); return; } diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.cc index 1d4783a3c0b..974571534a8 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.cc @@ -138,7 +138,7 @@ void FakeBluetoothGattDescriptorClient::WriteValue( // Since the only fake descriptor is "Client Characteristic Configuration" // and BlueZ doesn't allow writing to it, return failure. - error_callback.Run(bluetooth_gatt_service::kErrorWriteNotPermitted, + error_callback.Run(bluetooth_gatt_service::kErrorNotPermitted, "Writing to the Client Characteristic Configuration " "descriptor not allowed"); } diff --git a/chromium/device/bluetooth/public/mojom/BUILD.gn b/chromium/device/bluetooth/public/mojom/BUILD.gn index b4c1b1893af..0adcd734d1e 100644 --- a/chromium/device/bluetooth/public/mojom/BUILD.gn +++ b/chromium/device/bluetooth/public/mojom/BUILD.gn @@ -24,8 +24,6 @@ mojom("fake_bluetooth_interfaces") { ":mojom", ] - use_once_callback = true - # This mojom interface is exposed publicly to layout tests which use # prepackaged redistributable JS bindings. It is therefore not desirable to # scramble these messages. diff --git a/chromium/device/bluetooth/strings/OWNERS b/chromium/device/bluetooth/strings/OWNERS index b1598b50912..3d41590efa9 100644 --- a/chromium/device/bluetooth/strings/OWNERS +++ b/chromium/device/bluetooth/strings/OWNERS @@ -1,2 +1 @@ -per-file *.gyp*=* per-file BUILD.gn=* diff --git a/chromium/device/fido/BUILD.gn b/chromium/device/fido/BUILD.gn index ea09dff5dfb..9ac467aa604 100644 --- a/chromium/device/fido/BUILD.gn +++ b/chromium/device/fido/BUILD.gn @@ -31,34 +31,48 @@ component("fido") { "ctap_get_assertion_request.h", "ctap_make_credential_request.cc", "ctap_make_credential_request.h", + "ctap_register_operation.cc", + "ctap_register_operation.h", + "device_operation.h", "device_response_converter.cc", "device_response_converter.h", "ec_public_key.cc", "ec_public_key.h", "fido_attestation_statement.cc", "fido_attestation_statement.h", + "fido_authenticator.h", "fido_ble_connection.cc", "fido_ble_connection.h", "fido_ble_device.cc", "fido_ble_device.h", "fido_ble_discovery.cc", "fido_ble_discovery.h", + "fido_ble_discovery_base.cc", + "fido_ble_discovery_base.h", "fido_ble_frames.cc", "fido_ble_frames.h", "fido_ble_transaction.cc", "fido_ble_transaction.h", "fido_ble_uuids.cc", "fido_ble_uuids.h", + "fido_cable_device.cc", + "fido_cable_device.h", + "fido_cable_discovery.cc", + "fido_cable_discovery.h", "fido_constants.cc", "fido_constants.h", "fido_device.cc", "fido_device.h", + "fido_device_authenticator.cc", + "fido_device_authenticator.h", "fido_discovery.cc", "fido_discovery.h", "fido_hid_message.cc", "fido_hid_message.h", "fido_hid_packet.cc", "fido_hid_packet.h", + "fido_parsing_utils.cc", + "fido_parsing_utils.h", "fido_request_handler.h", "fido_request_handler_base.cc", "fido_request_handler_base.h", @@ -89,8 +103,8 @@ component("fido") { "public_key_credential_user_entity.h", "response_data.cc", "response_data.h", - "u2f_parsing_utils.cc", - "u2f_parsing_utils.h", + "u2f_command_constructor.cc", + "u2f_command_constructor.h", "u2f_register.cc", "u2f_register.h", "u2f_request.cc", @@ -99,6 +113,8 @@ component("fido") { "u2f_sign.h", "virtual_fido_device.cc", "virtual_fido_device.h", + "virtual_u2f_device.cc", + "virtual_u2f_device.h", ] defines = [ "IS_DEVICE_FIDO_IMPL" ] @@ -119,6 +135,8 @@ component("fido") { "//services/device/public/mojom", ] + libs = [] # Extended for mac. + # HID is not supported on Android. if (!is_android) { sources += [ @@ -133,6 +151,31 @@ component("fido") { "//services/device/public/mojom", ] } + + if (is_mac) { + sources += [ + "mac/authenticator.h", + "mac/authenticator.mm", + "mac/get_assertion_operation.h", + "mac/get_assertion_operation.mm", + "mac/keychain.h", + "mac/keychain.mm", + "mac/make_credential_operation.h", + "mac/make_credential_operation.mm", + "mac/operation.h", + "mac/operation_base.h", + "mac/touch_id_context.h", + "mac/touch_id_context.mm", + "mac/util.h", + "mac/util.mm", + ] + + libs += [ + "Foundation.framework", + "LocalAuthentication.framework", + "Security.framework", + ] + } } source_set("mocks") { diff --git a/chromium/device/fido/attested_credential_data.cc b/chromium/device/fido/attested_credential_data.cc index 77ae6606840..90bc165a558 100644 --- a/chromium/device/fido/attested_credential_data.cc +++ b/chromium/device/fido/attested_credential_data.cc @@ -8,9 +8,9 @@ #include <utility> #include "base/numerics/safe_math.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/opaque_public_key.h" #include "device/fido/public_key.h" -#include "device/fido/u2f_parsing_utils.h" namespace device { @@ -22,12 +22,12 @@ AttestedCredentialData::DecodeFromCtapResponse( return base::nullopt; std::array<uint8_t, kAaguidLength> aaguid; - if (!u2f_parsing_utils::ExtractArray(buffer, 0, &aaguid)) + if (!fido_parsing_utils::ExtractArray(buffer, 0, &aaguid)) return base::nullopt; std::array<uint8_t, kCredentialIdLengthLength> credential_id_length_array; - if (!u2f_parsing_utils::ExtractArray(buffer, kAaguidLength, - &credential_id_length_array)) { + if (!fido_parsing_utils::ExtractArray(buffer, kAaguidLength, + &credential_id_length_array)) { return base::nullopt; } @@ -36,7 +36,7 @@ AttestedCredentialData::DecodeFromCtapResponse( (base::strict_cast<size_t>(credential_id_length_array[0]) << 8) | base::strict_cast<size_t>(credential_id_length_array[1]); - auto credential_id = u2f_parsing_utils::Extract( + auto credential_id = fido_parsing_utils::Extract( buffer, kAaguidLength + kCredentialIdLengthLength, credential_id_length); if (credential_id.empty()) return base::nullopt; @@ -60,8 +60,8 @@ AttestedCredentialData::CreateFromU2fRegisterResponse( // TODO(crbug/799075): Introduce a CredentialID class to do this extraction. // Extract the length of the credential (i.e. of the U2FResponse key // handle). Length is big endian. - std::vector<uint8_t> extracted_length = u2f_parsing_utils::Extract( - u2f_data, u2f_parsing_utils::kU2fResponseKeyHandleLengthPos, 1); + std::vector<uint8_t> extracted_length = fido_parsing_utils::Extract( + u2f_data, fido_parsing_utils::kU2fResponseKeyHandleLengthPos, 1); if (extracted_length.empty()) { return base::nullopt; @@ -76,8 +76,8 @@ AttestedCredentialData::CreateFromU2fRegisterResponse( 0, extracted_length[0]}; // Extract the credential id (i.e. key handle). - std::vector<uint8_t> credential_id = u2f_parsing_utils::Extract( - u2f_data, u2f_parsing_utils::kU2fResponseKeyHandleStartPos, + std::vector<uint8_t> credential_id = fido_parsing_utils::Extract( + u2f_data, fido_parsing_utils::kU2fResponseKeyHandleStartPos, base::strict_cast<size_t>(credential_id_length[1])); if (credential_id.empty()) { @@ -103,13 +103,13 @@ void AttestedCredentialData::DeleteAaguid() { std::vector<uint8_t> AttestedCredentialData::SerializeAsBytes() const { std::vector<uint8_t> attestation_data; - u2f_parsing_utils::Append(&attestation_data, - base::make_span(aaguid_.data(), kAaguidLength)); - u2f_parsing_utils::Append( + fido_parsing_utils::Append(&attestation_data, + base::make_span(aaguid_.data(), kAaguidLength)); + fido_parsing_utils::Append( &attestation_data, base::make_span(credential_id_length_.data(), kCredentialIdLengthLength)); - u2f_parsing_utils::Append(&attestation_data, credential_id_); - u2f_parsing_utils::Append(&attestation_data, public_key_->EncodeAsCOSEKey()); + fido_parsing_utils::Append(&attestation_data, credential_id_); + fido_parsing_utils::Append(&attestation_data, public_key_->EncodeAsCOSEKey()); return attestation_data; } diff --git a/chromium/device/fido/attested_credential_data.h b/chromium/device/fido/attested_credential_data.h index c33dccbaea2..7e4f02e132d 100644 --- a/chromium/device/fido/attested_credential_data.h +++ b/chromium/device/fido/attested_credential_data.h @@ -48,7 +48,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData { // * Credential Public Key. std::vector<uint8_t> SerializeAsBytes() const; - private: static constexpr size_t kAaguidLength = 16; // Number of bytes used to represent length of credential ID. static constexpr size_t kCredentialIdLengthLength = 2; @@ -59,6 +58,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData { std::vector<uint8_t> credential_id, std::unique_ptr<PublicKey> public_key); + private: // The 16-byte AAGUID of the authenticator. std::array<uint8_t, kAaguidLength> aaguid_; diff --git a/chromium/device/fido/authenticator_data.cc b/chromium/device/fido/authenticator_data.cc index cfd67692240..e979b0a31be 100644 --- a/chromium/device/fido/authenticator_data.cc +++ b/chromium/device/fido/authenticator_data.cc @@ -7,7 +7,7 @@ #include <utility> #include "device/fido/attested_credential_data.h" -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" namespace device { @@ -69,14 +69,14 @@ void AuthenticatorData::DeleteDeviceAaguid() { std::vector<uint8_t> AuthenticatorData::SerializeToByteArray() const { std::vector<uint8_t> authenticator_data; - u2f_parsing_utils::Append(&authenticator_data, application_parameter_); + fido_parsing_utils::Append(&authenticator_data, application_parameter_); authenticator_data.insert(authenticator_data.end(), flags_); - u2f_parsing_utils::Append(&authenticator_data, counter_); + fido_parsing_utils::Append(&authenticator_data, counter_); if (attested_data_) { // Attestations are returned in registration responses but not in assertion // responses. - u2f_parsing_utils::Append(&authenticator_data, - attested_data_->SerializeAsBytes()); + fido_parsing_utils::Append(&authenticator_data, + attested_data_->SerializeAsBytes()); } return authenticator_data; } diff --git a/chromium/device/fido/authenticator_get_assertion_response.cc b/chromium/device/fido/authenticator_get_assertion_response.cc index 7d801c317ea..dc83b75b500 100644 --- a/chromium/device/fido/authenticator_get_assertion_response.cc +++ b/chromium/device/fido/authenticator_get_assertion_response.cc @@ -8,7 +8,7 @@ #include "base/optional.h" #include "device/fido/authenticator_data.h" -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" namespace device { @@ -31,27 +31,27 @@ AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( if (key_handle.empty()) return base::nullopt; - auto flags = u2f_parsing_utils::Extract(u2f_data, kFlagIndex, kFlagLength); + auto flags = fido_parsing_utils::Extract(u2f_data, kFlagIndex, kFlagLength); if (flags.empty()) return base::nullopt; auto counter = - u2f_parsing_utils::Extract(u2f_data, kCounterIndex, kCounterLength); + fido_parsing_utils::Extract(u2f_data, kCounterIndex, kCounterLength); if (counter.empty()) return base::nullopt; AuthenticatorData authenticator_data(relying_party_id_hash, flags[0], std::move(counter), base::nullopt); - auto signature = u2f_parsing_utils::Extract( + auto signature = fido_parsing_utils::Extract( u2f_data, kSignatureIndex, u2f_data.size() - kSignatureIndex); if (signature.empty()) return base::nullopt; AuthenticatorGetAssertionResponse response(std::move(authenticator_data), std::move(signature)); - response.SetCredential(PublicKeyCredentialDescriptor( - to_string(CredentialType::kPublicKey), key_handle)); + response.SetCredential( + PublicKeyCredentialDescriptor(CredentialType::kPublicKey, key_handle)); return std::move(response); } diff --git a/chromium/device/fido/authenticator_make_credential_response.cc b/chromium/device/fido/authenticator_make_credential_response.cc index addc6f1e16f..e916787ef60 100644 --- a/chromium/device/fido/authenticator_make_credential_response.cc +++ b/chromium/device/fido/authenticator_make_credential_response.cc @@ -11,7 +11,7 @@ #include "device/fido/authenticator_data.h" #include "device/fido/ec_public_key.h" #include "device/fido/fido_attestation_statement.h" -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" namespace device { @@ -21,7 +21,7 @@ AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse( const std::vector<uint8_t>& relying_party_id_hash, base::span<const uint8_t> u2f_data) { auto public_key = ECPublicKey::ExtractFromU2fRegistrationResponse( - u2f_parsing_utils::kEs256, u2f_data); + fido_parsing_utils::kEs256, u2f_data); if (!public_key) return base::nullopt; diff --git a/chromium/device/fido/ctap_get_assertion_request.cc b/chromium/device/fido/ctap_get_assertion_request.cc index 9e3dfc171da..8fb876ad766 100644 --- a/chromium/device/fido/ctap_get_assertion_request.cc +++ b/chromium/device/fido/ctap_get_assertion_request.cc @@ -112,4 +112,10 @@ CtapGetAssertionRequest& CtapGetAssertionRequest::SetPinProtocol( return *this; } +CtapGetAssertionRequest& CtapGetAssertionRequest::SetCableExtension( + std::vector<FidoCableDiscovery::CableDiscoveryData> cable_extension) { + cable_extension_ = std::move(cable_extension); + return *this; +} + } // namespace device diff --git a/chromium/device/fido/ctap_get_assertion_request.h b/chromium/device/fido/ctap_get_assertion_request.h index 3983ceb9a36..25ee8a6a186 100644 --- a/chromium/device/fido/ctap_get_assertion_request.h +++ b/chromium/device/fido/ctap_get_assertion_request.h @@ -13,6 +13,7 @@ #include "base/component_export.h" #include "base/macros.h" #include "base/optional.h" +#include "device/fido/fido_cable_discovery.h" #include "device/fido/fido_constants.h" #include "device/fido/public_key_credential_descriptor.h" @@ -43,6 +44,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { std::vector<PublicKeyCredentialDescriptor> allow_list); CtapGetAssertionRequest& SetPinAuth(std::vector<uint8_t> pin_auth); CtapGetAssertionRequest& SetPinProtocol(uint8_t pin_protocol); + CtapGetAssertionRequest& SetCableExtension( + std::vector<FidoCableDiscovery::CableDiscoveryData> cable_extension); const std::string& rp_id() const { return rp_id_; } const std::vector<uint8_t>& client_data_hash() const { @@ -64,6 +67,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { } const base::Optional<uint8_t>& pin_protocol() const { return pin_protocol_; } + const base::Optional<std::vector<FidoCableDiscovery::CableDiscoveryData>>& + cable_extension() const { + return cable_extension_; + } private: std::string rp_id_; @@ -75,6 +82,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { base::Optional<std::vector<PublicKeyCredentialDescriptor>> allow_list_; base::Optional<std::vector<uint8_t>> pin_auth_; base::Optional<uint8_t> pin_protocol_; + base::Optional<std::vector<FidoCableDiscovery::CableDiscoveryData>> + cable_extension_; }; } // namespace device diff --git a/chromium/device/fido/ctap_make_credential_request.h b/chromium/device/fido/ctap_make_credential_request.h index 574bd48aea9..2e47e855ef5 100644 --- a/chromium/device/fido/ctap_make_credential_request.h +++ b/chromium/device/fido/ctap_make_credential_request.h @@ -61,6 +61,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapMakeCredentialRequest { return user_verification_required_; } bool resident_key_supported() const { return resident_key_supported_; } + const base::Optional<std::vector<PublicKeyCredentialDescriptor>>& + exclude_list() const { + return exclude_list_; + } private: std::vector<uint8_t> client_data_hash_; diff --git a/chromium/device/fido/ctap_register_operation.cc b/chromium/device/fido/ctap_register_operation.cc new file mode 100644 index 00000000000..c2776cffd6f --- /dev/null +++ b/chromium/device/fido/ctap_register_operation.cc @@ -0,0 +1,46 @@ +// Copyright 2018 The Chromium 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 "device/fido/ctap_register_operation.h" + +#include <utility> + +#include "base/bind.h" +#include "device/fido/authenticator_make_credential_response.h" +#include "device/fido/ctap_make_credential_request.h" +#include "device/fido/device_response_converter.h" +#include "device/fido/fido_device.h" + +namespace device { + +CtapRegisterOperation::CtapRegisterOperation( + FidoDevice* device, + const CtapMakeCredentialRequest* request, + DeviceResponseCallback callback) + : DeviceOperation(device, std::move(callback)), + request_(request), + weak_factory_(this) {} + +CtapRegisterOperation::~CtapRegisterOperation() = default; + +void CtapRegisterOperation::Start() { + device_->DeviceTransact( + request_->EncodeAsCBOR(), + base::BindOnce(&CtapRegisterOperation::OnResponseReceived, + weak_factory_.GetWeakPtr())); +} + +void CtapRegisterOperation::OnResponseReceived( + base::Optional<std::vector<uint8_t>> device_response) { + if (!device_response) { + std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther, + base::nullopt); + return; + } + + std::move(callback_).Run(GetResponseCode(*device_response), + ReadCTAPMakeCredentialResponse(*device_response)); +} + +} // namespace device diff --git a/chromium/device/fido/ctap_register_operation.h b/chromium/device/fido/ctap_register_operation.h new file mode 100644 index 00000000000..b188d18b5e2 --- /dev/null +++ b/chromium/device/fido/ctap_register_operation.h @@ -0,0 +1,56 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_CTAP_REGISTER_OPERATION_H_ +#define DEVICE_FIDO_CTAP_REGISTER_OPERATION_H_ + +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "base/callback.h" +#include "base/component_export.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "device/fido/device_operation.h" +#include "device/fido/fido_constants.h" + +namespace device { + +class FidoDevice; +class CtapMakeCredentialRequest; +class AuthenticatorMakeCredentialResponse; + +// Represents per device registration logic for CTAP device. +// CtapRegisterOperation is owned by MakeCredentialTask, and the lifetime of +// CtapRegisterOperation does not exceed that of MakeCredentialTask. As so, +// |request_| member variable is dependency injected from MakeCredentialTask. +class COMPONENT_EXPORT(DEVICE_FIDO) CtapRegisterOperation + : public DeviceOperation<CtapMakeCredentialRequest, + AuthenticatorMakeCredentialResponse> { + public: + CtapRegisterOperation(FidoDevice* device, + const CtapMakeCredentialRequest* request, + DeviceResponseCallback callback); + + ~CtapRegisterOperation() override; + + // DeviceOperation: + void Start() override; + + private: + void OnResponseReceived(base::Optional<std::vector<uint8_t>> device_response); + + const CtapMakeCredentialRequest* const request_; + + base::WeakPtrFactory<CtapRegisterOperation> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CtapRegisterOperation); +}; + +} // namespace device + +#endif // DEVICE_FIDO_CTAP_REGISTER_OPERATION_H_ diff --git a/chromium/device/fido/ctap_request_unittest.cc b/chromium/device/fido/ctap_request_unittest.cc index 40b6f1df0dc..725d0514ff7 100644 --- a/chromium/device/fido/ctap_request_unittest.cc +++ b/chromium/device/fido/ctap_request_unittest.cc @@ -6,6 +6,8 @@ #include "device/fido/ctap_get_assertion_request.h" #include "device/fido/ctap_make_credential_request.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/fido_test_data.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -14,16 +16,6 @@ namespace device { // Leveraging example 4 of section 6.1 of the spec // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html TEST(CTAPRequestTest, TestConstructMakeCredentialRequestParam) { - static constexpr uint8_t kClientDataHash[] = { - 0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, 0x42, - 0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05, - 0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41}; - - static constexpr uint8_t kUserId[] = { - 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, - 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, - 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82}; - static constexpr uint8_t kSerializedRequest[] = { // clang-format off 0x01, // authenticatorMakeCredential command @@ -50,10 +42,8 @@ TEST(CTAPRequestTest, TestConstructMakeCredentialRequestParam) { 0xa4, // map(4) 0x62, // text(2) 0x69, 0x64, // "id" - 0x58, 0x20, // bytes(32) - user id - 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, - 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, + 0x48, // bytes(8) - user id + 0x10, 0x98, 0x23, 0x72, 0x35, 0x40, 0x98, 0x72, 0x64, // text(4) 0x69, 0x63, 0x6f, 0x6e, // "icon" 0x78, 0x28, // text(40) @@ -112,13 +102,13 @@ TEST(CTAPRequestTest, TestConstructMakeCredentialRequestParam) { rp.SetRpName("Acme"); PublicKeyCredentialUserEntity user( - std::vector<uint8_t>(kUserId, std::end(kUserId))); + fido_parsing_utils::Materialize(test_data::kUserId)); user.SetUserName("johnpsmith@example.com") .SetDisplayName("John P. Smith") .SetIconUrl(GURL("https://pics.acme.com/00/p/aBjjjpqPb.png")); CtapMakeCredentialRequest make_credential_param( - std::vector<uint8_t>(kClientDataHash, std::end(kClientDataHash)), + fido_parsing_utils::Materialize(test_data::kClientDataHash), std::move(rp), std::move(user), PublicKeyCredentialParams({{CredentialType::kPublicKey, 7}, {CredentialType::kPublicKey, 257}})); @@ -129,11 +119,6 @@ TEST(CTAPRequestTest, TestConstructMakeCredentialRequestParam) { } TEST(CTAPRequestTest, TestConstructGetAssertionRequest) { - static constexpr uint8_t kClientDataHash[] = { - 0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, 0x42, - 0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05, - 0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41}; - static constexpr uint8_t kSerializedRequest[] = { // clang-format off 0x02, // authenticatorGetAssertion command @@ -198,12 +183,11 @@ TEST(CTAPRequestTest, TestConstructGetAssertionRequest) { }; CtapGetAssertionRequest get_assertion_req( - "acme.com", - std::vector<uint8_t>(kClientDataHash, std::end(kClientDataHash))); + "acme.com", fido_parsing_utils::Materialize(test_data::kClientDataHash)); std::vector<PublicKeyCredentialDescriptor> allowed_list; allowed_list.push_back(PublicKeyCredentialDescriptor( - "public-key", + CredentialType::kPublicKey, {0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43, 0x94, 0x2f, 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b, 0x3d, 0xf8, 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0, @@ -211,7 +195,7 @@ TEST(CTAPRequestTest, TestConstructGetAssertionRequest) { 0x08, 0xd9, 0x4f, 0xcb, 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77, 0xaf, 0x0a, 0xdc, 0xc3, 0x58, 0x52, 0xea, 0x6b, 0x9e})); allowed_list.push_back(PublicKeyCredentialDescriptor( - "public-key", + CredentialType::kPublicKey, {0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, diff --git a/chromium/device/fido/ctap_response_unittest.cc b/chromium/device/fido/ctap_response_unittest.cc index dcafb902223..45c44c3e9a3 100644 --- a/chromium/device/fido/ctap_response_unittest.cc +++ b/chromium/device/fido/ctap_response_unittest.cc @@ -4,10 +4,15 @@ #include "components/cbor/cbor_reader.h" #include "components/cbor/cbor_values.h" +#include "components/cbor/cbor_writer.h" #include "device/fido/authenticator_get_assertion_response.h" #include "device/fido/authenticator_make_credential_response.h" #include "device/fido/device_response_converter.h" +#include "device/fido/ec_public_key.h" +#include "device/fido/fido_attestation_statement.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/fido_test_data.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -15,162 +20,125 @@ namespace device { namespace { -const uint8_t kDeviceMakeCredentialResponse[] = { - // clang-format off - 0x00, // Success response code - 0xa3, // map(3) - 0x01, // unsigned(1) - 0x66, // text(6) - // "packed" - 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, - - 0x02, // unsigned(2) - 0x58, 0x9a, // bytes(154) - // auth data - 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, - 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, - 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0x41, 0x00, 0x00, 0x00, - 0x0b, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, - 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, - 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa3, - 0x63, 0x61, 0x6c, 0x67, 0x65, 0x45, 0x53, 0x32, 0x35, 0x36, 0x61, 0x78, - 0x58, 0x20, 0xf7, 0xc4, 0xf4, 0xa6, 0xf1, 0xd7, 0x95, 0x38, 0xdf, 0xa4, - 0xc9, 0xac, 0x50, 0x84, 0x8d, 0xf7, 0x08, 0xbc, 0x1c, 0x99, 0xf5, 0xe6, - 0x0e, 0x51, 0xb4, 0x2a, 0x52, 0x1b, 0x35, 0xd3, 0xb6, 0x9a, 0x61, 0x79, - 0x58, 0x20, 0xde, 0x7b, 0x7d, 0x6c, 0xa5, 0x64, 0xe7, 0x0e, 0xa3, 0x21, - 0xa4, 0xd5, 0xd9, 0x6e, 0xa0, 0x0e, 0xf0, 0xe2, 0xdb, 0x89, 0xdd, 0x61, - 0xd4, 0x89, 0x4c, 0x15, 0xac, 0x58, 0x5b, 0xd2, 0x36, 0x84, - - 0x03, // unsigned(3) - 0xa3, // map(3) - 0x63, // text(3) - 0x61, 0x6c, 0x67, // "alg" - 0x07, // 7 - 0x63, // text(3) - 0x73, 0x69, 0x67, // "sig" - 0x58, 0x47, // bytes(71) - // signature - 0x30, 0x45, 0x02, 0x20, 0x13, 0xf7, 0x3c, 0x5d, 0x9d, 0x53, 0x0e, 0x8c, - 0xc1, 0x5c, 0xc9, 0xbd, 0x96, 0xad, 0x58, 0x6d, 0x39, 0x36, 0x64, 0xe4, - 0x62, 0xd5, 0xf0, 0x56, 0x12, 0x35, 0xe6, 0x35, 0x0f, 0x2b, 0x72, 0x89, - 0x02, 0x21, 0x00, 0x90, 0x35, 0x7f, 0xf9, 0x10, 0xcc, 0xb5, 0x6a, 0xc5, - 0xb5, 0x96, 0x51, 0x19, 0x48, 0x58, 0x1c, 0x8f, 0xdd, 0xb4, 0xa2, 0xb7, - 0x99, 0x59, 0x94, 0x80, 0x78, 0xb0, 0x9f, 0x4b, 0xdc, 0x62, 0x29, - - 0x63, // text(3) - 0x78, 0x35, 0x63, // "x5c" - 0x81, // array(1) - 0x59, 0x01, 0x97, // bytes(407) - // certificate - 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x09, 0x00, 0x85, 0x9b, 0x72, 0x6c, 0xb2, 0x4b, 0x4c, 0x29, - 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, - 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x0c, 0x0b, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, - 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, - 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, - 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x32, 0x30, 0x34, 0x31, - 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x31, 0x32, - 0x30, 0x32, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x30, 0x47, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, - 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, - 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, - 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x59, - 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, - 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, - 0x04, 0xad, 0x11, 0xeb, 0x0e, 0x88, 0x52, 0xe5, 0x3a, 0xd5, 0xdf, 0xed, - 0x86, 0xb4, 0x1e, 0x61, 0x34, 0xa1, 0x8e, 0xc4, 0xe1, 0xaf, 0x8f, 0x22, - 0x1a, 0x3c, 0x7d, 0x6e, 0x63, 0x6c, 0x80, 0xea, 0x13, 0xc3, 0xd5, 0x04, - 0xff, 0x2e, 0x76, 0x21, 0x1b, 0xb4, 0x45, 0x25, 0xb1, 0x96, 0xc4, 0x4c, - 0xb4, 0x84, 0x99, 0x79, 0xcf, 0x6f, 0x89, 0x6e, 0xcd, 0x2b, 0xb8, 0x60, - 0xde, 0x1b, 0xf4, 0x37, 0x6b, 0xa3, 0x0d, 0x30, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0a, 0x06, 0x08, - 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, - 0x46, 0x02, 0x21, 0x00, 0xe9, 0xa3, 0x9f, 0x1b, 0x03, 0x19, 0x75, 0x25, - 0xf7, 0x37, 0x3e, 0x10, 0xce, 0x77, 0xe7, 0x80, 0x21, 0x73, 0x1b, 0x94, - 0xd0, 0xc0, 0x3f, 0x3f, 0xda, 0x1f, 0xd2, 0x2d, 0xb3, 0xd0, 0x30, 0xe7, - 0x02, 0x21, 0x00, 0xc4, 0xfa, 0xec, 0x34, 0x45, 0xa8, 0x20, 0xcf, 0x43, - 0x12, 0x9c, 0xdb, 0x00, 0xaa, 0xbe, 0xfd, 0x9a, 0xe2, 0xd8, 0x74, 0xf9, - 0xc5, 0xd3, 0x43, 0xcb, 0x2f, 0x11, 0x3d, 0xa2, 0x37, 0x23, 0xf3 - // clang-format on +// The attested credential data, excluding the public key bytes. Append +// with kTestECPublicKeyCOSE to get the complete attestation data. +constexpr uint8_t kTestAttestedCredentialDataPrefix[] = { + // 16-byte aaguid + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // 2-byte length + 0x00, 0x40, + // 64-byte key handle + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, + 0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, + 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, + 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64, + 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, + 0xFE, 0x42, 0x00, 0x38, }; +// The authenticator data, excluding the attested credential data bytes. Append +// with attested credential data to get the complete authenticator data. +constexpr uint8_t kTestAuthenticatorDataPrefix[] = { + // sha256 hash of rp id. + 0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7, + 0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF, + 0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE, + // flags (TUP and AT bits set) + 0x41, + // counter + 0x00, 0x00, 0x00, 0x00}; + +// Components of the CBOR needed to form an authenticator object. +// Combined diagnostic notation: +// {"fmt": "fido-u2f", "attStmt": {"sig": h'30...}, "authData": h'D4C9D9...'} +constexpr uint8_t kFormatFidoU2fCBOR[] = { + // map(3) + 0xA3, + // text(3) + 0x63, + // "fmt" + 0x66, 0x6D, 0x74, + // text(8) + 0x68, + // "fido-u2f" + 0x66, 0x69, 0x64, 0x6F, 0x2D, 0x75, 0x32, 0x66}; + +constexpr uint8_t kAttStmtCBOR[] = { + // text(7) + 0x67, + // "attStmt" + 0x61, 0x74, 0x74, 0x53, 0x74, 0x6D, 0x74}; + +constexpr uint8_t kAuthDataCBOR[] = { + // text(8) + 0x68, + // "authData" + 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, + // bytes(196). i.e., the authenticator_data byte array corresponding to + // kTestAuthenticatorDataPrefix|, |kTestAttestedCredentialDataPrefix|, + // and test_data::kTestECPublicKeyCOSE. + 0x58, 0xC4}; + +std::vector<uint8_t> GetTestAttestedCredentialDataBytes() { + // Combine kTestAttestedCredentialDataPrefix and kTestECPublicKeyCOSE. + auto test_attested_data = + fido_parsing_utils::Materialize(kTestAttestedCredentialDataPrefix); + fido_parsing_utils::Append(&test_attested_data, + test_data::kTestECPublicKeyCOSE); + return test_attested_data; +} + +std::vector<uint8_t> GetTestAuthenticatorDataBytes() { + // Build the test authenticator data. + auto test_authenticator_data = + fido_parsing_utils::Materialize(kTestAuthenticatorDataPrefix); + auto test_attested_data = GetTestAttestedCredentialDataBytes(); + fido_parsing_utils::Append(&test_authenticator_data, test_attested_data); + return test_authenticator_data; +} + +std::vector<uint8_t> GetTestAttestationObjectBytes() { + auto test_authenticator_object = + fido_parsing_utils::Materialize(kFormatFidoU2fCBOR); + fido_parsing_utils::Append(&test_authenticator_object, kAttStmtCBOR); + fido_parsing_utils::Append(&test_authenticator_object, + test_data::kU2fAttestationStatementCBOR); + fido_parsing_utils::Append(&test_authenticator_object, kAuthDataCBOR); + auto test_authenticator_data = GetTestAuthenticatorDataBytes(); + fido_parsing_utils::Append(&test_authenticator_object, + test_authenticator_data); + return test_authenticator_object; +} + +std::vector<uint8_t> GetTestSignResponse() { + return fido_parsing_utils::Materialize(test_data::kTestU2fSignResponse); +} + +std::vector<uint8_t> GetTestSignatureCounter() { + return fido_parsing_utils::Materialize(test_data::kTestSignatureCounter); +} + +// Get a subset of the response for testing error handling. +std::vector<uint8_t> GetTestCorruptedSignResponse(size_t length) { + DCHECK_LE(length, arraysize(test_data::kTestU2fSignResponse)); + return fido_parsing_utils::Materialize(fido_parsing_utils::ExtractSpan( + test_data::kTestU2fSignResponse, 0, length)); +} + +// Return a key handle used for GetAssertion request. +std::vector<uint8_t> GetTestCredentialRawIdBytes() { + return fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle); +} + } // namespace // Leveraging example 4 of section 6.1 of the spec https://fidoalliance.org // /specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd- // 20170927.html TEST(CTAPResponseTest, TestReadMakeCredentialResponse) { - constexpr uint8_t kCertificate[] = { - 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x09, 0x00, 0x85, 0x9b, 0x72, 0x6c, 0xb2, 0x4b, 0x4c, 0x29, - 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, - 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x0c, 0x0b, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, - 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, - 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, - 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x32, 0x30, 0x34, 0x31, - 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x31, 0x32, - 0x30, 0x32, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x30, 0x47, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, - 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, - 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, - 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x59, - 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, - 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, - 0x04, 0xad, 0x11, 0xeb, 0x0e, 0x88, 0x52, 0xe5, 0x3a, 0xd5, 0xdf, 0xed, - 0x86, 0xb4, 0x1e, 0x61, 0x34, 0xa1, 0x8e, 0xc4, 0xe1, 0xaf, 0x8f, 0x22, - 0x1a, 0x3c, 0x7d, 0x6e, 0x63, 0x6c, 0x80, 0xea, 0x13, 0xc3, 0xd5, 0x04, - 0xff, 0x2e, 0x76, 0x21, 0x1b, 0xb4, 0x45, 0x25, 0xb1, 0x96, 0xc4, 0x4c, - 0xb4, 0x84, 0x99, 0x79, 0xcf, 0x6f, 0x89, 0x6e, 0xcd, 0x2b, 0xb8, 0x60, - 0xde, 0x1b, 0xf4, 0x37, 0x6b, 0xa3, 0x0d, 0x30, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0a, 0x06, 0x08, - 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, - 0x46, 0x02, 0x21, 0x00, 0xe9, 0xa3, 0x9f, 0x1b, 0x03, 0x19, 0x75, 0x25, - 0xf7, 0x37, 0x3e, 0x10, 0xce, 0x77, 0xe7, 0x80, 0x21, 0x73, 0x1b, 0x94, - 0xd0, 0xc0, 0x3f, 0x3f, 0xda, 0x1f, 0xd2, 0x2d, 0xb3, 0xd0, 0x30, 0xe7, - 0x02, 0x21, 0x00, 0xc4, 0xfa, 0xec, 0x34, 0x45, 0xa8, 0x20, 0xcf, 0x43, - 0x12, 0x9c, 0xdb, 0x00, 0xaa, 0xbe, 0xfd, 0x9a, 0xe2, 0xd8, 0x74, 0xf9, - 0xc5, 0xd3, 0x43, 0xcb, 0x2f, 0x11, 0x3d, 0xa2, 0x37, 0x23, 0xf3}; - - constexpr uint8_t kAuthData[] = { - 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, - 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, - 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0x41, 0x00, 0x00, 0x00, - 0x0b, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, - 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, - 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa3, - 0x63, 0x61, 0x6c, 0x67, 0x65, 0x45, 0x53, 0x32, 0x35, 0x36, 0x61, 0x78, - 0x58, 0x20, 0xf7, 0xc4, 0xf4, 0xa6, 0xf1, 0xd7, 0x95, 0x38, 0xdf, 0xa4, - 0xc9, 0xac, 0x50, 0x84, 0x8d, 0xf7, 0x08, 0xbc, 0x1c, 0x99, 0xf5, 0xe6, - 0x0e, 0x51, 0xb4, 0x2a, 0x52, 0x1b, 0x35, 0xd3, 0xb6, 0x9a, 0x61, 0x79, - 0x58, 0x20, 0xde, 0x7b, 0x7d, 0x6c, 0xa5, 0x64, 0xe7, 0x0e, 0xa3, 0x21, - 0xa4, 0xd5, 0xd9, 0x6e, 0xa0, 0x0e, 0xf0, 0xe2, 0xdb, 0x89, 0xdd, 0x61, - 0xd4, 0x89, 0x4c, 0x15, 0xac, 0x58, 0x5b, 0xd2, 0x36, 0x84}; - - constexpr uint8_t kSignature[] = { - 0x30, 0x45, 0x02, 0x20, 0x13, 0xf7, 0x3c, 0x5d, 0x9d, 0x53, 0x0e, 0x8c, - 0xc1, 0x5c, 0xc9, 0xbd, 0x96, 0xad, 0x58, 0x6d, 0x39, 0x36, 0x64, 0xe4, - 0x62, 0xd5, 0xf0, 0x56, 0x12, 0x35, 0xe6, 0x35, 0x0f, 0x2b, 0x72, 0x89, - 0x02, 0x21, 0x00, 0x90, 0x35, 0x7f, 0xf9, 0x10, 0xcc, 0xb5, 0x6a, 0xc5, - 0xb5, 0x96, 0x51, 0x19, 0x48, 0x58, 0x1c, 0x8f, 0xdd, 0xb4, 0xa2, 0xb7, - 0x99, 0x59, 0x94, 0x80, 0x78, 0xb0, 0x9f, 0x4b, 0xdc, 0x62, 0x29}; - - constexpr uint8_t kCredentialId[] = { - 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, - 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, - }; - auto make_credential_response = - ReadCTAPMakeCredentialResponse(kDeviceMakeCredentialResponse); + ReadCTAPMakeCredentialResponse(test_data::kDeviceMakeCredentialResponse); ASSERT_TRUE(make_credential_response); auto cbor_attestation_object = cbor::CBORReader::Read( make_credential_response->GetCBOREncodedAttestationObject()); @@ -186,8 +154,9 @@ TEST(CTAPResponseTest, TestReadMakeCredentialResponse) { it = attestation_object_map.find(cbor::CBORValue(kAuthDataKey)); ASSERT_TRUE(it != attestation_object_map.end()); ASSERT_TRUE(it->second.is_bytestring()); - EXPECT_THAT(it->second.GetBytestring(), - ::testing::ElementsAreArray(kAuthData)); + EXPECT_THAT( + it->second.GetBytestring(), + ::testing::ElementsAreArray(test_data::kCtap2MakeCredentialAuthData)); it = attestation_object_map.find(cbor::CBORValue(kAttestationStatementKey)); ASSERT_TRUE(it != attestation_object_map.end()); @@ -203,8 +172,9 @@ TEST(CTAPResponseTest, TestReadMakeCredentialResponse) { attStmt_it = attestation_statement_map.find(cbor::CBORValue("sig")); ASSERT_TRUE(attStmt_it != attestation_statement_map.end()); ASSERT_TRUE(attStmt_it->second.is_bytestring()); - EXPECT_THAT(attStmt_it->second.GetBytestring(), - ::testing::ElementsAreArray(kSignature)); + EXPECT_THAT( + attStmt_it->second.GetBytestring(), + ::testing::ElementsAreArray(test_data::kCtap2MakeCredentialSignature)); attStmt_it = attestation_statement_map.find(cbor::CBORValue("x5c")); ASSERT_TRUE(attStmt_it != attestation_statement_map.end()); @@ -212,162 +182,188 @@ TEST(CTAPResponseTest, TestReadMakeCredentialResponse) { ASSERT_TRUE(certificate.is_array()); ASSERT_EQ(certificate.GetArray().size(), 1u); ASSERT_TRUE(certificate.GetArray()[0].is_bytestring()); - EXPECT_THAT(certificate.GetArray()[0].GetBytestring(), - ::testing::ElementsAreArray(kCertificate)); - EXPECT_THAT(make_credential_response->raw_credential_id(), - ::testing::ElementsAreArray(kCredentialId)); + EXPECT_THAT( + certificate.GetArray()[0].GetBytestring(), + ::testing::ElementsAreArray(test_data::kCtap2MakeCredentialCertificate)); + EXPECT_THAT( + make_credential_response->raw_credential_id(), + ::testing::ElementsAreArray(test_data::kCtap2MakeCredentialCredentialId)); } TEST(CTAPResponseTest, TestMakeCredentialNoneAttestationResponse) { - constexpr uint8_t kNoneAttestationResponse[] = { - // clang-format off - 0xa3, // map(3) - // Format - 0x63, // text(3) - 0x66, 0x6D, 0x74, // "fmt" - 0x64, // text(6) - // "none" - 0x6E, 0x6F, 0x6E, 0x65, - // Attestation statement - 0x67, // text(7) - 0x61, 0x74, 0x74, 0x53, 0x74, 0x6D, 0x74, // "attStmt" - 0xa0, // Empty CBOR Map - // Authenticator data - 0x68, // text(8) - 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, - 0x58, 0x9a, // bytes(154) - 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, - 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, - 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0x41, 0x00, 0x00, 0x00, - 0x0b, - // Replaced device AAGUID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - // Credential information - 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, - 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa3, - 0x63, 0x61, 0x6c, 0x67, 0x65, 0x45, 0x53, 0x32, 0x35, 0x36, 0x61, 0x78, - 0x58, 0x20, 0xf7, 0xc4, 0xf4, 0xa6, 0xf1, 0xd7, 0x95, 0x38, 0xdf, 0xa4, - 0xc9, 0xac, 0x50, 0x84, 0x8d, 0xf7, 0x08, 0xbc, 0x1c, 0x99, 0xf5, 0xe6, - 0x0e, 0x51, 0xb4, 0x2a, 0x52, 0x1b, 0x35, 0xd3, 0xb6, 0x9a, 0x61, 0x79, - 0x58, 0x20, 0xde, 0x7b, 0x7d, 0x6c, 0xa5, 0x64, 0xe7, 0x0e, 0xa3, 0x21, - 0xa4, 0xd5, 0xd9, 0x6e, 0xa0, 0x0e, 0xf0, 0xe2, 0xdb, 0x89, 0xdd, 0x61, - 0xd4, 0x89, 0x4c, 0x15, 0xac, 0x58, 0x5b, 0xd2, 0x36, 0x84, - // clang-format on - }; - auto make_credential_response = - ReadCTAPMakeCredentialResponse(kDeviceMakeCredentialResponse); + ReadCTAPMakeCredentialResponse(test_data::kDeviceMakeCredentialResponse); ASSERT_TRUE(make_credential_response); make_credential_response->EraseAttestationStatement(); EXPECT_THAT(make_credential_response->GetCBOREncodedAttestationObject(), - ::testing::ElementsAreArray(kNoneAttestationResponse)); + ::testing::ElementsAreArray(test_data::kNoneAttestationResponse)); } -// Leveraging example 5 of section 6.1 of the spec https://fidoalliance.org -// /specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd- -// 20170927.html +// Leveraging example 5 of section 6.1 of the CTAP spec. +// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html TEST(CTAPResponseTest, TestReadGetAssertionResponse) { - constexpr uint8_t kAuthData[] = { - 0x62, 0x5d, 0xda, 0xdf, 0x74, 0x3f, 0x57, 0x27, 0xe6, 0x6b, - 0xba, 0x8c, 0x2e, 0x38, 0x79, 0x22, 0xd1, 0xaf, 0x43, 0xc5, - 0x03, 0xd9, 0x11, 0x4a, 0x8f, 0xba, 0x10, 0x4d, 0x84, 0xd0, - 0x2b, 0xfa, 0x01, 0x00, 0x00, 0x00, 0x11}; - - constexpr uint8_t kSignature[] = { - 0x30, 0x45, 0x02, 0x20, 0x4a, 0x5a, 0x9d, 0xd3, 0x92, 0x98, 0x14, 0x9d, - 0x90, 0x47, 0x69, 0xb5, 0x1a, 0x45, 0x14, 0x33, 0x00, 0x6f, 0x18, 0x2a, - 0x34, 0xfb, 0xdf, 0x66, 0xde, 0x5f, 0xc7, 0x17, 0xd7, 0x5f, 0xb3, 0x50, - 0x02, 0x21, 0x00, 0xa4, 0x6b, 0x8e, 0xa3, 0xc3, 0xb9, 0x33, 0x82, 0x1c, - 0x6e, 0x7f, 0x5e, 0xf9, 0xda, 0xae, 0x94, 0xab, 0x47, 0xf1, 0x8d, 0xb4, - 0x74, 0xc7, 0x47, 0x90, 0xea, 0xab, 0xb1, 0x44, 0x11, 0xe7, 0xa0, - }; - - constexpr uint8_t kDeviceGetAssertionResponse[] = { - // clang-format off - 0x00, // Success response code - 0xa5, // map(5) - 0x01, // unsigned(1) - Credential - 0xa2, // map(2) - 0x62, // text(2) - 0x69, 0x64, // "id" - 0x58, 0x40, // bytes(64) - // credential id - 0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43, 0x94, 0x2f, - 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b, 0x3d, 0xf8, - 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0, 0x34, 0x85, 0x8a, - 0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98, 0x08, 0xd9, 0x4f, 0xcb, - 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77, 0xaf, 0x0a, 0xdc, 0xc3, 0x58, - 0x52, 0xea, 0x6b, 0x9e, - 0x64, // text(4) - 0x74, 0x79, 0x70, 0x65, // "type" - 0x6a, // text(10) - // "public-key" - 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, - - 0x02, // unsigned(2) - Auth data - 0x58, 0x25, // bytes(37) - // auth data - 0x62, 0x5d, 0xda, 0xdf, 0x74, 0x3f, 0x57, 0x27, 0xe6, 0x6b, 0xba, 0x8c, - 0x2e, 0x38, 0x79, 0x22, 0xd1, 0xaf, 0x43, 0xc5, 0x03, 0xd9, 0x11, 0x4a, - 0x8f, 0xba, 0x10, 0x4d, 0x84, 0xd0, 0x2b, 0xfa, 0x01, 0x00, 0x00, 0x00, - 0x11, - - 0x03, // unsigned(3) - signature - 0x58, 0x47, // bytes(71) - // signature - 0x30, 0x45, 0x02, 0x20, 0x4a, 0x5a, 0x9d, 0xd3, 0x92, 0x98, 0x14, 0x9d, - 0x90, 0x47, 0x69, 0xb5, 0x1a, 0x45, 0x14, 0x33, 0x00, 0x6f, 0x18, 0x2a, - 0x34, 0xfb, 0xdf, 0x66, 0xde, 0x5f, 0xc7, 0x17, 0xd7, 0x5f, 0xb3, 0x50, - 0x02, 0x21, 0x00, 0xa4, 0x6b, 0x8e, 0xa3, 0xc3, 0xb9, 0x33, 0x82, 0x1c, - 0x6e, 0x7f, 0x5e, 0xf9, 0xda, 0xae, 0x94, 0xab, 0x47, 0xf1, 0x8d, 0xb4, - 0x74, 0xc7, 0x47, 0x90, 0xea, 0xab, 0xb1, 0x44, 0x11, 0xe7, 0xa0, - - 0x04, // unsigned(4) - publicKeyCredentialUserEntity - 0xa4, // map(4) - 0x62, // text(2) - 0x69, 0x64, // "id" - 0x58, 0x20, // bytes(32) - user id - 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, - 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, - 0x64, // text(4) - 0x69, 0x63, 0x6f, 0x6e, // "icon" - 0x78, 0x28, // text(40) - // "https://pics.acme.com/00/p/aBjjjpqPb.png" - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x70, 0x69, 0x63, 0x73, - 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x30, - 0x2f, 0x70, 0x2f, 0x61, 0x42, 0x6a, 0x6a, 0x6a, 0x70, 0x71, 0x50, 0x62, - 0x2e, 0x70, 0x6e, 0x67, - 0x64, // text(4) - 0x6e, 0x61, 0x6d, 0x65, // "name" - 0x76, // text(22) - // "johnpsmith@example.com" - 0x6a, 0x6f, 0x68, 0x6e, 0x70, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x40, 0x65, - 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x6b, // text(11) - // "displayName" - 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, - 0x6d, // text(13) - // "John P. Smith" - 0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x50, 0x2e, 0x20, 0x53, 0x6d, 0x69, 0x74, - 0x68, - - 0x05, // unsigned(5) - number of credentials - 0x01, // 1 - // clang-format on - }; - auto get_assertion_response = - ReadCTAPGetAssertionResponse(kDeviceGetAssertionResponse); + ReadCTAPGetAssertionResponse(test_data::kDeviceGetAssertionResponse); ASSERT_TRUE(get_assertion_response); ASSERT_TRUE(get_assertion_response->num_credentials()); EXPECT_EQ(*get_assertion_response->num_credentials(), 1u); - EXPECT_THAT(get_assertion_response->auth_data().SerializeToByteArray(), - ::testing::ElementsAreArray(kAuthData)); - EXPECT_THAT(get_assertion_response->signature(), - ::testing::ElementsAreArray(kSignature)); + EXPECT_THAT( + get_assertion_response->auth_data().SerializeToByteArray(), + ::testing::ElementsAreArray(test_data::kCtap2GetAssertionAuthData)); + EXPECT_THAT( + get_assertion_response->signature(), + ::testing::ElementsAreArray(test_data::kCtap2GetAssertionSignature)); +} + +// Test that U2F register response is properly parsed. +TEST(CTAPResponseTest, TestParseRegisterResponseData) { + auto response = + AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + test_data::kTestU2fRegisterResponse); + ASSERT_TRUE(response); + EXPECT_THAT(response->raw_credential_id(), + ::testing::ElementsAreArray(test_data::kU2fSignKeyHandle)); + EXPECT_EQ(GetTestAttestationObjectBytes(), + response->GetCBOREncodedAttestationObject()); +} + +// These test the parsing of the U2F raw bytes of the registration response. +// Test that an EC public key serializes to CBOR properly. +TEST(CTAPResponseTest, TestSerializedPublicKey) { + auto public_key = ECPublicKey::ExtractFromU2fRegistrationResponse( + fido_parsing_utils::kEs256, test_data::kTestU2fRegisterResponse); + ASSERT_TRUE(public_key); + EXPECT_THAT(public_key->EncodeAsCOSEKey(), + ::testing::ElementsAreArray(test_data::kTestECPublicKeyCOSE)); +} + +// Test that the attestation statement cbor map is constructed properly. +TEST(CTAPResponseTest, TestParseU2fAttestationStatementCBOR) { + auto fido_attestation_statement = + FidoAttestationStatement::CreateFromU2fRegisterResponse( + test_data::kTestU2fRegisterResponse); + ASSERT_TRUE(fido_attestation_statement); + auto cbor = cbor::CBORWriter::Write( + cbor::CBORValue(fido_attestation_statement->GetAsCBORMap())); + ASSERT_TRUE(cbor); + EXPECT_THAT(*cbor, ::testing::ElementsAreArray( + test_data::kU2fAttestationStatementCBOR)); +} + +// Tests that well-formed attested credential data serializes properly. +TEST(CTAPResponseTest, TestSerializeAttestedCredentialData) { + auto public_key = ECPublicKey::ExtractFromU2fRegistrationResponse( + fido_parsing_utils::kEs256, test_data::kTestU2fRegisterResponse); + auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( + test_data::kTestU2fRegisterResponse, std::move(public_key)); + ASSERT_TRUE(attested_data); + EXPECT_EQ(GetTestAttestedCredentialDataBytes(), + attested_data->SerializeAsBytes()); +} + +// Tests that well-formed authenticator data serializes properly. +TEST(CTAPResponseTest, TestSerializeAuthenticatorData) { + auto public_key = ECPublicKey::ExtractFromU2fRegistrationResponse( + fido_parsing_utils::kEs256, test_data::kTestU2fRegisterResponse); + auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( + test_data::kTestU2fRegisterResponse, std::move(public_key)); + + constexpr uint8_t flags = + static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence) | + static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); + + AuthenticatorData authenticator_data( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), flags, + std::vector<uint8_t>(4) /* counter */, std::move(attested_data)); + + EXPECT_EQ(GetTestAuthenticatorDataBytes(), + authenticator_data.SerializeToByteArray()); +} + +// Tests that a U2F attestation object serializes properly. +TEST(CTAPResponseTest, TestSerializeU2fAttestationObject) { + auto public_key = ECPublicKey::ExtractFromU2fRegistrationResponse( + fido_parsing_utils::kEs256, test_data::kTestU2fRegisterResponse); + auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( + test_data::kTestU2fRegisterResponse, std::move(public_key)); + + constexpr uint8_t flags = + static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence) | + static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); + AuthenticatorData authenticator_data( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), flags, + std::vector<uint8_t>(4) /* counter */, std::move(attested_data)); + + // Construct the attestation statement. + auto fido_attestation_statement = + FidoAttestationStatement::CreateFromU2fRegisterResponse( + test_data::kTestU2fRegisterResponse); + + // Construct the attestation object. + auto attestation_object = std::make_unique<AttestationObject>( + std::move(authenticator_data), std::move(fido_attestation_statement)); + + ASSERT_TRUE(attestation_object); + EXPECT_EQ(GetTestAttestationObjectBytes(), + attestation_object->SerializeToCBOREncodedBytes()); +} + +// Tests that U2F authenticator data is properly serialized. +TEST(CTAPResponseTest, TestSerializeAuthenticatorDataForSign) { + constexpr uint8_t flags = + static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence); + + EXPECT_THAT( + AuthenticatorData( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + flags, GetTestSignatureCounter(), base::nullopt) + .SerializeToByteArray(), + ::testing::ElementsAreArray(test_data::kTestSignAuthenticatorData)); +} + +TEST(CTAPResponseTest, TestParseSignResponseData) { + auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + GetTestSignResponse(), GetTestCredentialRawIdBytes()); + ASSERT_TRUE(response); + EXPECT_EQ(GetTestCredentialRawIdBytes(), response->raw_credential_id()); + EXPECT_THAT( + response->auth_data().SerializeToByteArray(), + ::testing::ElementsAreArray(test_data::kTestSignAuthenticatorData)); + EXPECT_THAT(response->signature(), + ::testing::ElementsAreArray(test_data::kU2fSignature)); +} + +TEST(CTAPResponseTest, TestParseU2fSignWithNullNullKeyHandle) { + auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + GetTestSignResponse(), std::vector<uint8_t>()); + EXPECT_FALSE(response); +} + +TEST(CTAPResponseTest, TestParseU2fSignWithNullResponse) { + auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + std::vector<uint8_t>(), GetTestCredentialRawIdBytes()); + EXPECT_FALSE(response); +} + +TEST(CTAPResponseTest, TestParseU2fSignWithNullCorruptedCounter) { + // A sign response of less than 5 bytes. + auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + GetTestCorruptedSignResponse(3), GetTestCredentialRawIdBytes()); + EXPECT_FALSE(response); +} + +TEST(CTAPResponseTest, TestParseU2fSignWithNullCorruptedSignature) { + // A sign response no more than 5 bytes. + auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + GetTestCorruptedSignResponse(5), GetTestCredentialRawIdBytes()); + EXPECT_FALSE(response); } } // namespace device diff --git a/chromium/device/fido/device_operation.h b/chromium/device/fido/device_operation.h new file mode 100644 index 00000000000..8d4b57a4ffb --- /dev/null +++ b/chromium/device/fido/device_operation.h @@ -0,0 +1,46 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_DEVICE_OPERATION_H_ +#define DEVICE_FIDO_DEVICE_OPERATION_H_ + +#include <stdint.h> + +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/optional.h" +#include "device/fido/authenticator_get_assertion_response.h" +#include "device/fido/authenticator_make_credential_response.h" +#include "device/fido/ctap_get_assertion_request.h" +#include "device/fido/ctap_make_credential_request.h" +#include "device/fido/fido_constants.h" +#include "device/fido/fido_device.h" + +namespace device { + +template <class Request, class Response> +class DeviceOperation { + public: + using DeviceResponseCallback = + base::OnceCallback<void(CtapDeviceResponseCode, + base::Optional<Response>)>; + + DeviceOperation(FidoDevice* device, DeviceResponseCallback callback) + : device_(device), callback_(std::move(callback)) {} + virtual ~DeviceOperation() = default; + + virtual void Start() = 0; + + protected: + FidoDevice* const device_ = nullptr; + DeviceResponseCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(DeviceOperation); +}; + +} // namespace device + +#endif // DEVICE_FIDO_DEVICE_OPERATION_H_ diff --git a/chromium/device/fido/ec_public_key.cc b/chromium/device/fido/ec_public_key.cc index 9d922b4e979..effded9a490 100644 --- a/chromium/device/fido/ec_public_key.cc +++ b/chromium/device/fido/ec_public_key.cc @@ -7,39 +7,44 @@ #include <utility> #include "components/cbor/cbor_writer.h" -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" namespace device { namespace { -// The key is located after the first byte of the response -// (which is a reserved byte). It's in X9.62 format: +// In a U2F registration response, the key is located after the first byte of +// the response (which is a reserved byte). It's in X9.62 format: // - a constant 0x04 prefix to indicate an uncompressed key // - the 32-byte x coordinate // - the 32-byte y coordinate. -constexpr size_t kKeyCompressionTypeOffset = 1; -constexpr size_t kUncompressedKey = 0x04; -constexpr size_t kHeaderLength = 2; // Account for reserved byte and prefix. -constexpr size_t kKeyLength = 32; +constexpr size_t kReservedLength = 1; +constexpr uint8_t kUncompressedKey = 0x04; +constexpr size_t kFieldElementLength = 32; } // namespace // static std::unique_ptr<ECPublicKey> ECPublicKey::ExtractFromU2fRegistrationResponse( std::string algorithm, base::span<const uint8_t> u2f_data) { - if (u2f_data.size() < kHeaderLength || - u2f_data[kKeyCompressionTypeOffset] != kUncompressedKey) - return nullptr; + return ParseX962Uncompressed( + std::move(algorithm), + fido_parsing_utils::ExtractSuffixSpan(u2f_data, kReservedLength)); +} - std::vector<uint8_t> x = - u2f_parsing_utils::Extract(u2f_data, kHeaderLength, kKeyLength); +// static +std::unique_ptr<ECPublicKey> ECPublicKey::ParseX962Uncompressed( + std::string algorithm, + base::span<const uint8_t> input) { + if (input.empty() || input[0] != kUncompressedKey) + return nullptr; + const std::vector<uint8_t> x = + fido_parsing_utils::Extract(input, 1, kFieldElementLength); if (x.empty()) return nullptr; - std::vector<uint8_t> y = u2f_parsing_utils::Extract( - u2f_data, kHeaderLength + kKeyLength, kKeyLength); - + const std::vector<uint8_t> y = fido_parsing_utils::Extract( + input, 1 + kFieldElementLength, kFieldElementLength); if (y.empty()) return nullptr; @@ -53,8 +58,8 @@ ECPublicKey::ECPublicKey(std::string algorithm, : PublicKey(std::move(algorithm)), x_coordinate_(std::move(x)), y_coordinate_(std::move(y)) { - DCHECK_EQ(x_coordinate_.size(), kKeyLength); - DCHECK_EQ(y_coordinate_.size(), kKeyLength); + DCHECK_EQ(x_coordinate_.size(), kFieldElementLength); + DCHECK_EQ(y_coordinate_.size(), kFieldElementLength); } ECPublicKey::~ECPublicKey() = default; diff --git a/chromium/device/fido/ec_public_key.h b/chromium/device/fido/ec_public_key.h index dfd66ad3fdb..b49e4d2776d 100644 --- a/chromium/device/fido/ec_public_key.h +++ b/chromium/device/fido/ec_public_key.h @@ -26,6 +26,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) ECPublicKey : public PublicKey { std::string algorithm, base::span<const uint8_t> u2f_data); + // Parse a public key encoded in ANSI X9.62 uncompressed format. + static std::unique_ptr<ECPublicKey> ParseX962Uncompressed( + std::string algorithm, + base::span<const uint8_t> input); + ECPublicKey(std::string algorithm, std::vector<uint8_t> x, std::vector<uint8_t> y); diff --git a/chromium/device/fido/fake_fido_discovery.cc b/chromium/device/fido/fake_fido_discovery.cc index e7e53351abd..5a73d3a5d9b 100644 --- a/chromium/device/fido/fake_fido_discovery.cc +++ b/chromium/device/fido/fake_fido_discovery.cc @@ -73,6 +73,13 @@ FakeFidoDiscovery* ScopedFakeFidoDiscoveryFactory::ForgeNextBleDiscovery( return next_ble_discovery_.get(); } +FakeFidoDiscovery* ScopedFakeFidoDiscoveryFactory::ForgeNextCableDiscovery( + FakeFidoDiscovery::StartMode mode) { + next_cable_discovery_ = std::make_unique<FakeFidoDiscovery>( + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy, mode); + return next_cable_discovery_.get(); +} + std::unique_ptr<FidoDiscovery> ScopedFakeFidoDiscoveryFactory::CreateFidoDiscovery( FidoTransportProtocol transport, @@ -84,6 +91,11 @@ ScopedFakeFidoDiscoveryFactory::CreateFidoDiscovery( return std::move(next_nfc_discovery_); case FidoTransportProtocol::kBluetoothLowEnergy: return std::move(next_ble_discovery_); + case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy: + return std::move(next_cable_discovery_); + case FidoTransportProtocol::kInternal: + NOTREACHED() << "Internal authenticators should be handled separately."; + return nullptr; } NOTREACHED(); return nullptr; diff --git a/chromium/device/fido/fake_fido_discovery.h b/chromium/device/fido/fake_fido_discovery.h index de4428eb399..15f55bd152a 100644 --- a/chromium/device/fido/fake_fido_discovery.h +++ b/chromium/device/fido/fake_fido_discovery.h @@ -103,7 +103,7 @@ class ScopedFakeFidoDiscoveryFactory ScopedFakeFidoDiscoveryFactory(); ~ScopedFakeFidoDiscoveryFactory() override; - // Constructs a fake BLE/HID discovery to be returned from the next call to + // Constructs a fake discovery to be returned from the next call to // FidoDiscovery::Create. Returns a raw pointer to the fake so that tests can // set it up according to taste. // @@ -112,6 +112,8 @@ class ScopedFakeFidoDiscoveryFactory FakeFidoDiscovery* ForgeNextHidDiscovery(StartMode mode = StartMode::kManual); FakeFidoDiscovery* ForgeNextNfcDiscovery(StartMode mode = StartMode::kManual); FakeFidoDiscovery* ForgeNextBleDiscovery(StartMode mode = StartMode::kManual); + FakeFidoDiscovery* ForgeNextCableDiscovery( + StartMode mode = StartMode::kManual); protected: std::unique_ptr<FidoDiscovery> CreateFidoDiscovery( @@ -122,6 +124,7 @@ class ScopedFakeFidoDiscoveryFactory std::unique_ptr<FakeFidoDiscovery> next_hid_discovery_; std::unique_ptr<FakeFidoDiscovery> next_nfc_discovery_; std::unique_ptr<FakeFidoDiscovery> next_ble_discovery_; + std::unique_ptr<FakeFidoDiscovery> next_cable_discovery_; DISALLOW_COPY_AND_ASSIGN(ScopedFakeFidoDiscoveryFactory); }; diff --git a/chromium/device/fido/fake_hid_impl_for_testing.cc b/chromium/device/fido/fake_hid_impl_for_testing.cc index 24041b74dc3..dc2a5d56db8 100644 --- a/chromium/device/fido/fake_hid_impl_for_testing.cc +++ b/chromium/device/fido/fake_hid_impl_for_testing.cc @@ -6,7 +6,7 @@ #include <utility> -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" namespace device { diff --git a/chromium/device/fido/fido_attestation_statement.cc b/chromium/device/fido/fido_attestation_statement.cc index 77a30b24d77..b4f80574124 100644 --- a/chromium/device/fido/fido_attestation_statement.cc +++ b/chromium/device/fido/fido_attestation_statement.cc @@ -7,7 +7,7 @@ #include <utility> #include "base/logging.h" -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" #include "third_party/boringssl/src/include/openssl/bytestring.h" namespace device { @@ -88,7 +88,8 @@ FidoAttestationStatement::CreateFromU2fRegisterResponse( // The format of |u2f_data| is specified here: // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-response-message-success uint8_t credential_length; - if (!CBS_skip(&response, u2f_parsing_utils::kU2fResponseKeyHandleLengthPos) || + if (!CBS_skip(&response, + fido_parsing_utils::kU2fResponseKeyHandleLengthPos) || !CBS_get_u8(&response, &credential_length) || !CBS_skip(&response, credential_length) || !CBS_get_asn1_element(&response, &cert, CBS_ASN1_SEQUENCE)) { diff --git a/chromium/device/fido/fido_authenticator.h b/chromium/device/fido/fido_authenticator.h new file mode 100644 index 00000000000..1eddfe5038f --- /dev/null +++ b/chromium/device/fido/fido_authenticator.h @@ -0,0 +1,53 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_FIDO_AUTHENTICATOR_H_ +#define DEVICE_FIDO_FIDO_AUTHENTICATOR_H_ + +#include <string> + +#include "base/callback_forward.h" +#include "base/component_export.h" +#include "base/macros.h" +#include "base/optional.h" +#include "device/fido/authenticator_get_assertion_response.h" +#include "device/fido/authenticator_make_credential_response.h" + +namespace device { + +class AuthenticatorSelectionCriteria; +class CtapGetAssertionRequest; +class CtapMakeCredentialRequest; + +// FidoAuthenticator is an authenticator from the WebAuthn Authenticator model +// (https://www.w3.org/TR/webauthn/#sctn-authenticator-model). It may be a +// physical device, or a built-in (platform) authenticator. +class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { + public: + using MakeCredentialCallback = base::OnceCallback<void( + CtapDeviceResponseCode, + base::Optional<AuthenticatorMakeCredentialResponse>)>; + using GetAssertionCallback = base::OnceCallback<void( + CtapDeviceResponseCode, + base::Optional<AuthenticatorGetAssertionResponse>)>; + + FidoAuthenticator() = default; + virtual ~FidoAuthenticator() = default; + + virtual void MakeCredential( + AuthenticatorSelectionCriteria authenticator_selection_criteria, + CtapMakeCredentialRequest request, + MakeCredentialCallback callback) = 0; + virtual void GetAssertion(CtapGetAssertionRequest request, + GetAssertionCallback callback) = 0; + virtual void Cancel() = 0; + virtual std::string GetId() const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FidoAuthenticator); +}; + +} // namespace device + +#endif // DEVICE_FIDO_FIDO_AUTHENTICATOR_H_ diff --git a/chromium/device/fido/fido_ble_connection_unittest.cc b/chromium/device/fido/fido_ble_connection_unittest.cc index 93b1df2b8c3..d7cf1a24156 100644 --- a/chromium/device/fido/fido_ble_connection_unittest.cc +++ b/chromium/device/fido/fido_ble_connection_unittest.cc @@ -20,6 +20,7 @@ #include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h" #include "device/bluetooth/test/mock_bluetooth_gatt_service.h" #include "device/fido/fido_ble_uuids.h" +#include "device/fido/test_callback_receiver.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -114,77 +115,13 @@ class TestReadCallback { base::Optional<base::RunLoop> run_loop_{base::in_place}; }; -class TestReadControlPointLengthCallback { - public: - void OnReadControlPointLength(base::Optional<uint16_t> value) { - value_ = std::move(value); - run_loop_->Quit(); - } - - const base::Optional<uint16_t>& WaitForResult() { - run_loop_->Run(); - run_loop_.emplace(); - return value_; - } - - FidoBleConnection::ControlPointLengthCallback GetCallback() { - return base::BindOnce( - &TestReadControlPointLengthCallback::OnReadControlPointLength, - base::Unretained(this)); - } +using TestReadControlPointLengthCallback = + test::ValueCallbackReceiver<base::Optional<uint16_t>>; - private: - base::Optional<uint16_t> value_; - base::Optional<base::RunLoop> run_loop_{base::in_place}; -}; - -class TestReadServiceRevisionsCallback { - public: - void OnReadServiceRevisions( - std::set<FidoBleConnection::ServiceRevision> revisions) { - revisions_ = std::move(revisions); - run_loop_->Quit(); - } - - const std::set<FidoBleConnection::ServiceRevision>& WaitForResult() { - run_loop_->Run(); - run_loop_.emplace(); - return revisions_; - } - - FidoBleConnection::ServiceRevisionsCallback GetCallback() { - return base::BindOnce( - &TestReadServiceRevisionsCallback::OnReadServiceRevisions, - base::Unretained(this)); - } - - private: - std::set<FidoBleConnection::ServiceRevision> revisions_; - base::Optional<base::RunLoop> run_loop_{base::in_place}; -}; - -class TestWriteCallback { - public: - void OnWrite(bool success) { - success_ = success; - run_loop_->Quit(); - } - - bool WaitForResult() { - run_loop_->Run(); - run_loop_.emplace(); - return success_; - } - - FidoBleConnection::WriteCallback GetCallback() { - return base::BindOnce(&TestWriteCallback::OnWrite, base::Unretained(this)); - } - - private: - bool success_ = false; - base::Optional<base::RunLoop> run_loop_{base::in_place}; -}; +using TestReadServiceRevisionsCallback = + test::ValueCallbackReceiver<std::set<FidoBleConnection::ServiceRevision>>; +using TestWriteCallback = test::ValueCallbackReceiver<bool>; } // namespace class FidoBleConnectionTest : public ::testing::Test { @@ -540,26 +477,26 @@ TEST_F(FidoBleConnectionTest, ReadControlPointLength) { TestReadControlPointLengthCallback length_callback; SetNextReadControlPointLengthReponse(false, {}); - connection.ReadControlPointLength(length_callback.GetCallback()); - EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + connection.ReadControlPointLength(length_callback.callback()); + EXPECT_EQ(base::nullopt, length_callback.value()); // The Control Point Length should consist of exactly two bytes, hence we // EXPECT_EQ(base::nullopt) for payloads of size 0, 1 and 3. SetNextReadControlPointLengthReponse(true, {}); - connection.ReadControlPointLength(length_callback.GetCallback()); - EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + connection.ReadControlPointLength(length_callback.callback()); + EXPECT_EQ(base::nullopt, length_callback.value()); SetNextReadControlPointLengthReponse(true, {0xAB}); - connection.ReadControlPointLength(length_callback.GetCallback()); - EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + connection.ReadControlPointLength(length_callback.callback()); + EXPECT_EQ(base::nullopt, length_callback.value()); SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD}); - connection.ReadControlPointLength(length_callback.GetCallback()); - EXPECT_EQ(0xABCD, *length_callback.WaitForResult()); + connection.ReadControlPointLength(length_callback.callback()); + EXPECT_EQ(0xABCD, *length_callback.value()); SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD, 0xEF}); - connection.ReadControlPointLength(length_callback.GetCallback()); - EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + connection.ReadControlPointLength(length_callback.callback()); + EXPECT_EQ(base::nullopt, length_callback.value()); } TEST_F(FidoBleConnectionTest, ReadServiceRevisions) { @@ -578,76 +515,76 @@ TEST_F(FidoBleConnectionTest, ReadServiceRevisions) { TestReadServiceRevisionsCallback revisions_callback; SetNextReadServiceRevisionResponse(false, {}); SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), IsEmpty()); SetNextReadServiceRevisionResponse(true, ToByteVector("bogus")); SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), IsEmpty()); SetNextReadServiceRevisionResponse(true, ToByteVector("1.0")); SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_0)); SetNextReadServiceRevisionResponse(true, ToByteVector("1.1")); SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1)); SetNextReadServiceRevisionResponse(true, ToByteVector("1.2")); SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_2)); // Version 1.3 currently does not exist, so this should be treated as an // error. SetNextReadServiceRevisionResponse(true, ToByteVector("1.3")); SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), IsEmpty()); SetNextReadServiceRevisionResponse(false, {}); SetNextReadServiceRevisionBitfieldResponse(true, {0x00}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), IsEmpty()); SetNextReadServiceRevisionResponse(false, {}); SetNextReadServiceRevisionBitfieldResponse(true, {0x80}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1)); SetNextReadServiceRevisionResponse(false, {}); SetNextReadServiceRevisionBitfieldResponse(true, {0x40}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_2)); SetNextReadServiceRevisionResponse(false, {}); SetNextReadServiceRevisionBitfieldResponse(true, {0xC0}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1, FidoBleConnection::ServiceRevision::VERSION_1_2)); // All bits except the first two should be ignored. SetNextReadServiceRevisionResponse(false, {}); SetNextReadServiceRevisionBitfieldResponse(true, {0xFF}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1, FidoBleConnection::ServiceRevision::VERSION_1_2)); // All bytes except the first one should be ignored. SetNextReadServiceRevisionResponse(false, {}); SetNextReadServiceRevisionBitfieldResponse(true, {0xC0, 0xFF}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1, FidoBleConnection::ServiceRevision::VERSION_1_2)); @@ -655,8 +592,8 @@ TEST_F(FidoBleConnectionTest, ReadServiceRevisions) { // supported as well. SetNextReadServiceRevisionResponse(true, ToByteVector("1.0")); SetNextReadServiceRevisionBitfieldResponse(true, {0xC0}); - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_0, FidoBleConnection::ServiceRevision::VERSION_1_1, FidoBleConnection::ServiceRevision::VERSION_1_2)); @@ -678,13 +615,13 @@ TEST_F(FidoBleConnectionTest, WriteControlPoint) { TestWriteCallback write_callback; SetNextWriteControlPointResponse(false); - connection.WriteControlPoint({}, write_callback.GetCallback()); - result = write_callback.WaitForResult(); + connection.WriteControlPoint({}, write_callback.callback()); + result = write_callback.value(); EXPECT_FALSE(result); SetNextWriteControlPointResponse(true); - connection.WriteControlPoint({}, write_callback.GetCallback()); - result = write_callback.WaitForResult(); + connection.WriteControlPoint({}, write_callback.callback()); + result = write_callback.value(); EXPECT_TRUE(result); } @@ -707,31 +644,31 @@ TEST_F(FidoBleConnectionTest, WriteServiceRevision) { SetNextWriteServiceRevisionResponse(false); connection.WriteServiceRevision( FidoBleConnection::ServiceRevision::VERSION_1_1, - write_callback.GetCallback()); - result = write_callback.WaitForResult(); + write_callback.callback()); + result = write_callback.value(); EXPECT_FALSE(result); // Expect a successful write of version 1.1. SetNextWriteServiceRevisionResponse(true); connection.WriteServiceRevision( FidoBleConnection::ServiceRevision::VERSION_1_1, - write_callback.GetCallback()); - result = write_callback.WaitForResult(); + write_callback.callback()); + result = write_callback.value(); EXPECT_TRUE(result); // Expect a successful write of version 1.2. SetNextWriteServiceRevisionResponse(true); connection.WriteServiceRevision( FidoBleConnection::ServiceRevision::VERSION_1_2, - write_callback.GetCallback()); - result = write_callback.WaitForResult(); + write_callback.callback()); + result = write_callback.value(); EXPECT_TRUE(result); // Writing version 1.0 to the bitfield is not intended, so this should fail. connection.WriteServiceRevision( FidoBleConnection::ServiceRevision::VERSION_1_0, - write_callback.GetCallback()); - result = write_callback.WaitForResult(); + write_callback.callback()); + result = write_callback.value(); EXPECT_FALSE(result); } @@ -755,23 +692,23 @@ TEST_F(FidoBleConnectionTest, ReadsAndWriteFailWhenDisconnected) { // Reads should always fail on a disconnected device. TestReadControlPointLengthCallback length_callback; - connection.ReadControlPointLength(length_callback.GetCallback()); - EXPECT_EQ(base::nullopt, length_callback.WaitForResult()); + connection.ReadControlPointLength(length_callback.callback()); + EXPECT_EQ(base::nullopt, length_callback.value()); TestReadServiceRevisionsCallback revisions_callback; - connection.ReadServiceRevisions(revisions_callback.GetCallback()); - EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty()); + connection.ReadServiceRevisions(revisions_callback.callback()); + EXPECT_THAT(revisions_callback.value(), IsEmpty()); // Writes should always fail on a disconnected device. TestWriteCallback write_callback; connection.WriteServiceRevision( FidoBleConnection::ServiceRevision::VERSION_1_1, - write_callback.GetCallback()); - result = write_callback.WaitForResult(); + write_callback.callback()); + result = write_callback.value(); EXPECT_FALSE(result); - connection.WriteControlPoint({}, write_callback.GetCallback()); - result = write_callback.WaitForResult(); + connection.WriteControlPoint({}, write_callback.callback()); + result = write_callback.value(); EXPECT_FALSE(result); } diff --git a/chromium/device/fido/fido_ble_device.cc b/chromium/device/fido/fido_ble_device.cc index c4ee5cc0619..ae49580fd68 100644 --- a/chromium/device/fido/fido_ble_device.cc +++ b/chromium/device/fido/fido_ble_device.cc @@ -84,6 +84,23 @@ base::WeakPtr<FidoDevice> FidoBleDevice::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } +void FidoBleDevice::OnResponseFrame(FrameCallback callback, + base::Optional<FidoBleFrame> frame) { + // The request is done, time to reset |transaction_|. + ResetTransaction(); + + state_ = frame ? State::kReady : State::kDeviceError; + auto self = GetWeakPtr(); + std::move(callback).Run(std::move(frame)); + // Executing callbacks may free |this|. Check |self| first. + if (self) + Transition(); +} + +void FidoBleDevice::ResetTransaction() { + transaction_.reset(); +} + void FidoBleDevice::Transition() { switch (state_) { case State::kInit: @@ -165,19 +182,6 @@ void FidoBleDevice::SendRequestFrame(FidoBleFrame frame, std::move(callback))); } -void FidoBleDevice::OnResponseFrame(FrameCallback callback, - base::Optional<FidoBleFrame> frame) { - // The request is done, time to reset |transaction_|. - transaction_.reset(); - - state_ = frame ? State::kReady : State::kDeviceError; - auto self = GetWeakPtr(); - std::move(callback).Run(std::move(frame)); - // Executing callbacks may free |this|. Check |self| first. - if (self) - Transition(); -} - void FidoBleDevice::StartTimeout() { timer_.Start(FROM_HERE, kDeviceTimeout, this, &FidoBleDevice::OnTimeout); } diff --git a/chromium/device/fido/fido_ble_device.h b/chromium/device/fido/fido_ble_device.h index 419d823994b..58b11218840 100644 --- a/chromium/device/fido/fido_ble_device.h +++ b/chromium/device/fido/fido_ble_device.h @@ -52,12 +52,15 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice { DeviceCallback callback) override; base::WeakPtr<FidoDevice> GetWeakPtr() override; - private: + virtual void OnResponseFrame(FrameCallback callback, + base::Optional<FidoBleFrame> frame); void Transition(); void AddToPendingFrames(FidoBleDeviceCommand cmd, std::vector<uint8_t> request, DeviceCallback callback); + void ResetTransaction(); + private: void OnConnectionStatus(bool success); void OnStatusMessage(std::vector<uint8_t> data); @@ -66,8 +69,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice { void SendPendingRequestFrame(); void SendRequestFrame(FidoBleFrame frame, FrameCallback callback); - void OnResponseFrame(FrameCallback callback, - base::Optional<FidoBleFrame> frame); void StartTimeout(); void StopTimeout(); diff --git a/chromium/device/fido/fido_ble_device_unittest.cc b/chromium/device/fido/fido_ble_device_unittest.cc index fac9b88bc3c..39916f831ec 100644 --- a/chromium/device/fido/fido_ble_device_unittest.cc +++ b/chromium/device/fido/fido_ble_device_unittest.cc @@ -9,9 +9,9 @@ #include "base/test/scoped_task_environment.h" #include "device/bluetooth/test/bluetooth_test.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/mock_fido_ble_connection.h" #include "device/fido/test_callback_receiver.h" -#include "device/fido/u2f_parsing_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -22,7 +22,7 @@ using ::testing::_; using ::testing::Invoke; using ::testing::Test; using TestDeviceCallbackReceiver = - test::TestCallbackReceiver<base::Optional<std::vector<uint8_t>>>; + test::ValueCallbackReceiver<base::Optional<std::vector<uint8_t>>>; constexpr uint16_t kControlPointLength = 20; constexpr uint8_t kTestData[] = {'T', 'E', 'S', 'T'}; @@ -100,11 +100,11 @@ TEST_F(FidoBleDeviceTest, SendPingTest_Failure_WriteFailed) { })); TestDeviceCallbackReceiver callback_receiver; - auto payload = u2f_parsing_utils::Materialize(kTestData); + auto payload = fido_parsing_utils::Materialize(kTestData); device()->SendPing(std::move(payload), callback_receiver.callback()); callback_receiver.WaitForCallback(); - EXPECT_FALSE(std::get<0>(*callback_receiver.result())); + EXPECT_FALSE(callback_receiver.value()); } TEST_F(FidoBleDeviceTest, SendPingTest_Failure_NoResponse) { @@ -116,11 +116,11 @@ TEST_F(FidoBleDeviceTest, SendPingTest_Failure_NoResponse) { })); TestDeviceCallbackReceiver callback_receiver; - const auto payload = u2f_parsing_utils::Materialize(kTestData); + const auto payload = fido_parsing_utils::Materialize(kTestData); device()->SendPing(payload, callback_receiver.callback()); callback_receiver.WaitForCallback(); - EXPECT_FALSE(std::get<0>(*callback_receiver.result())); + EXPECT_FALSE(callback_receiver.value().has_value()); } TEST_F(FidoBleDeviceTest, SendPingTest_Failure_SlowResponse) { @@ -132,10 +132,10 @@ TEST_F(FidoBleDeviceTest, SendPingTest_Failure_SlowResponse) { })); TestDeviceCallbackReceiver callback_receiver; - auto payload = u2f_parsing_utils::Materialize(kTestData); + auto payload = fido_parsing_utils::Materialize(kTestData); device()->SendPing(payload, callback_receiver.callback()); callback_receiver.WaitForCallback(); - EXPECT_FALSE(std::get<0>(*callback_receiver.result())); + EXPECT_FALSE(callback_receiver.value()); // Imitate a ping response from the device after the timeout has passed. for (auto&& fragment : @@ -158,13 +158,13 @@ TEST_F(FidoBleDeviceTest, SendPingTest) { })); TestDeviceCallbackReceiver callback_receiver; - const auto payload = u2f_parsing_utils::Materialize(kTestData); + const auto payload = fido_parsing_utils::Materialize(kTestData); device()->SendPing(payload, callback_receiver.callback()); callback_receiver.WaitForCallback(); - const auto& result = std::get<0>(*callback_receiver.result()); - ASSERT_TRUE(result); - EXPECT_EQ(payload, *result); + const auto& value = callback_receiver.value(); + ASSERT_TRUE(value); + EXPECT_EQ(payload, *value); } TEST_F(FidoBleDeviceTest, SendCancelTest) { @@ -174,7 +174,7 @@ TEST_F(FidoBleDeviceTest, SendCancelTest) { ConnectWithLength(kControlPointLength); EXPECT_CALL(*connection(), WriteControlPointPtr( - u2f_parsing_utils::Materialize(kBleCancelCommand), _)); + fido_parsing_utils::Materialize(kBleCancelCommand), _)); device()->Cancel(); scoped_task_environment_.FastForwardUntilNoTasksRemain(); diff --git a/chromium/device/fido/fido_ble_discovery.cc b/chromium/device/fido/fido_ble_discovery.cc index ef8b2c53d88..16de4ad732f 100644 --- a/chromium/device/fido/fido_ble_discovery.cc +++ b/chromium/device/fido/fido_ble_discovery.cc @@ -4,18 +4,12 @@ #include "device/fido/fido_ble_discovery.h" -#include <string> #include <utility> #include "base/bind.h" -#include "base/location.h" -#include "base/stl_util.h" -#include "base/strings/string_piece.h" -#include "base/threading/thread_task_runner_handle.h" -#include "build/build_config.h" -#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "base/callback_helpers.h" +#include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_common.h" -#include "device/bluetooth/bluetooth_discovery_filter.h" #include "device/bluetooth/bluetooth_discovery_session.h" #include "device/bluetooth/bluetooth_uuid.h" #include "device/fido/fido_ble_device.h" @@ -23,15 +17,9 @@ namespace device { -FidoBleDiscovery::FidoBleDiscovery() - : FidoDiscovery(FidoTransportProtocol::kBluetoothLowEnergy), - weak_factory_(this) {} -FidoBleDiscovery::~FidoBleDiscovery() { - if (adapter_) - adapter_->RemoveObserver(this); +FidoBleDiscovery::FidoBleDiscovery() : weak_factory_(this) {} - // Destroying |discovery_session_| will best-effort-stop discovering. -} +FidoBleDiscovery::~FidoBleDiscovery() = default; // static const BluetoothUUID& FidoBleDiscovery::FidoServiceUUID() { @@ -39,29 +27,11 @@ const BluetoothUUID& FidoBleDiscovery::FidoServiceUUID() { return service_uuid; } -void FidoBleDiscovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { - DCHECK(!adapter_); - adapter_ = std::move(adapter); - DCHECK(adapter_); - VLOG(2) << "Got adapter " << adapter_->GetAddress(); - - adapter_->AddObserver(this); - if (adapter_->IsPowered()) { - OnSetPowered(); - } else { - adapter_->SetPowered( - true, - base::Bind(&FidoBleDiscovery::OnSetPowered, weak_factory_.GetWeakPtr()), - base::Bind(&FidoBleDiscovery::OnSetPoweredError, - weak_factory_.GetWeakPtr())); - } -} - void FidoBleDiscovery::OnSetPowered() { - DCHECK(adapter_); - VLOG(2) << "Adapter " << adapter_->GetAddress() << " is powered on."; + DCHECK(adapter()); + VLOG(2) << "Adapter " << adapter()->GetAddress() << " is powered on."; - for (BluetoothDevice* device : adapter_->GetDevices()) { + for (BluetoothDevice* device : adapter()->GetDevices()) { if (base::ContainsKey(device->GetUUIDs(), FidoServiceUUID())) { VLOG(2) << "U2F BLE device: " << device->GetAddress(); AddDevice(std::make_unique<FidoBleDevice>(device->GetAddress())); @@ -72,49 +42,14 @@ void FidoBleDiscovery::OnSetPowered() { BluetoothTransport::BLUETOOTH_TRANSPORT_LE); filter->AddUUID(FidoServiceUUID()); - adapter_->StartDiscoverySessionWithFilter( + adapter()->StartDiscoverySessionWithFilter( std::move(filter), - base::Bind(&FidoBleDiscovery::OnStartDiscoverySessionWithFilter, - weak_factory_.GetWeakPtr()), - base::Bind(&FidoBleDiscovery::OnStartDiscoverySessionWithFilterError, - weak_factory_.GetWeakPtr())); -} - -void FidoBleDiscovery::OnSetPoweredError() { - DLOG(ERROR) << "Failed to power on the adapter."; - NotifyDiscoveryStarted(false); -} - -void FidoBleDiscovery::OnStartDiscoverySessionWithFilter( - std::unique_ptr<BluetoothDiscoverySession> session) { - discovery_session_ = std::move(session); - DVLOG(2) << "Discovery session started."; - NotifyDiscoveryStarted(true); -} - -void FidoBleDiscovery::OnStartDiscoverySessionWithFilterError() { - DLOG(ERROR) << "Discovery session not started."; - NotifyDiscoveryStarted(false); -} - -void FidoBleDiscovery::StartInternal() { - auto& factory = BluetoothAdapterFactory::Get(); - auto callback = base::BindRepeating(&FidoBleDiscovery::OnGetAdapter, - weak_factory_.GetWeakPtr()); -#if defined(OS_MACOSX) - // BluetoothAdapter may invoke the callback synchronously on Mac, but - // StartInternal() never wants to invoke to NotifyDiscoveryStarted() - // immediately, so ensure there is at least post-task at this bottleneck. - // See: https://crbug.com/823686. - callback = base::BindRepeating( - [](BluetoothAdapterFactory::AdapterCallback callback, - scoped_refptr<BluetoothAdapter> adapter) { - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindRepeating(callback, adapter)); - }, - std::move(callback)); -#endif // defined(OS_MACOSX) - factory.GetAdapter(std::move(callback)); + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoBleDiscovery::OnStartDiscoverySessionWithFilter, + weak_factory_.GetWeakPtr())), + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoBleDiscovery::OnStartDiscoverySessionError, + weak_factory_.GetWeakPtr()))); } void FidoBleDiscovery::DeviceAdded(BluetoothAdapter* adapter, diff --git a/chromium/device/fido/fido_ble_discovery.h b/chromium/device/fido/fido_ble_discovery.h index 6e560b97513..abf6f27476f 100644 --- a/chromium/device/fido/fido_ble_discovery.h +++ b/chromium/device/fido/fido_ble_discovery.h @@ -9,20 +9,16 @@ #include "base/component_export.h" #include "base/macros.h" -#include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" -#include "device/bluetooth/bluetooth_adapter.h" -#include "device/fido/fido_discovery.h" +#include "device/fido/fido_ble_discovery_base.h" namespace device { class BluetoothDevice; -class BluetoothDiscoverySession; class BluetoothUUID; class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscovery - : public FidoDiscovery, - BluetoothAdapter::Observer { + : public FidoBleDiscoveryBase { public: FidoBleDiscovery(); ~FidoBleDiscovery() override; @@ -30,15 +26,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscovery private: static const BluetoothUUID& FidoServiceUUID(); - void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter); - void OnSetPowered(); - void OnSetPoweredError(); - void OnStartDiscoverySessionWithFilter( - std::unique_ptr<BluetoothDiscoverySession>); - void OnStartDiscoverySessionWithFilterError(); - - // FidoDiscovery: - void StartInternal() override; + // FidoBleDiscoveryBase: + void OnSetPowered() override; // BluetoothAdapter::Observer: void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override; @@ -47,9 +36,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscovery void DeviceRemoved(BluetoothAdapter* adapter, BluetoothDevice* device) override; - scoped_refptr<BluetoothAdapter> adapter_; - std::unique_ptr<BluetoothDiscoverySession> discovery_session_; - base::WeakPtrFactory<FidoBleDiscovery> weak_factory_; DISALLOW_COPY_AND_ASSIGN(FidoBleDiscovery); diff --git a/chromium/device/fido/fido_ble_discovery_base.cc b/chromium/device/fido/fido_ble_discovery_base.cc new file mode 100644 index 00000000000..b118bc1e1e7 --- /dev/null +++ b/chromium/device/fido/fido_ble_discovery_base.cc @@ -0,0 +1,94 @@ +// Copyright 2018 The Chromium 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 "device/fido/fido_ble_discovery_base.h" + +#include <utility> + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/location.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_common.h" +#include "device/bluetooth/bluetooth_discovery_session.h" + +namespace device { + +FidoBleDiscoveryBase::FidoBleDiscoveryBase() + : FidoDiscovery(FidoTransportProtocol::kBluetoothLowEnergy), + weak_factory_(this) {} + +FidoBleDiscoveryBase::~FidoBleDiscoveryBase() { + if (adapter_) + adapter_->RemoveObserver(this); + + // Destroying |discovery_session_| will best-effort-stop discovering. +} + +void FidoBleDiscoveryBase::OnStartDiscoverySessionWithFilter( + std::unique_ptr<BluetoothDiscoverySession> session) { + SetDiscoverySession(std::move(session)); + DVLOG(2) << "Discovery session started."; + NotifyDiscoveryStarted(true); +} + +void FidoBleDiscoveryBase::OnSetPoweredError() { + DLOG(ERROR) << "Failed to power on the adapter."; + NotifyDiscoveryStarted(false); +} + +void FidoBleDiscoveryBase::OnStartDiscoverySessionError() { + DLOG(ERROR) << "Discovery session not started."; + NotifyDiscoveryStarted(false); +} + +void FidoBleDiscoveryBase::SetDiscoverySession( + std::unique_ptr<BluetoothDiscoverySession> discovery_session) { + discovery_session_ = std::move(discovery_session); +} + +void FidoBleDiscoveryBase::OnGetAdapter( + scoped_refptr<BluetoothAdapter> adapter) { + DCHECK(!adapter_); + adapter_ = std::move(adapter); + DCHECK(adapter_); + VLOG(2) << "Got adapter " << adapter_->GetAddress(); + + adapter_->AddObserver(this); + if (adapter_->IsPowered()) { + OnSetPowered(); + } else { + adapter_->SetPowered( + true, + base::AdaptCallbackForRepeating(base::BindOnce( + &FidoBleDiscoveryBase::OnSetPowered, weak_factory_.GetWeakPtr())), + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoBleDiscoveryBase::OnSetPoweredError, + weak_factory_.GetWeakPtr()))); + } +} + +void FidoBleDiscoveryBase::StartInternal() { + auto& factory = BluetoothAdapterFactory::Get(); + auto callback = base::BindOnce(&FidoBleDiscoveryBase::OnGetAdapter, + weak_factory_.GetWeakPtr()); +#if defined(OS_MACOSX) + // BluetoothAdapter may invoke the callback synchronously on Mac, but + // StartInternal() never wants to invoke to NotifyDiscoveryStarted() + // immediately, so ensure there is at least post-task at this bottleneck. + // See: https://crbug.com/823686. + callback = base::BindOnce( + [](BluetoothAdapterFactory::AdapterCallback callback, + scoped_refptr<BluetoothAdapter> adapter) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), adapter)); + }, + base::AdaptCallbackForRepeating(std::move(callback))); +#endif // defined(OS_MACOSX) + factory.GetAdapter(base::AdaptCallbackForRepeating(std::move(callback))); +} + +} // namespace device diff --git a/chromium/device/fido/fido_ble_discovery_base.h b/chromium/device/fido/fido_ble_discovery_base.h new file mode 100644 index 00000000000..dd4a0f14e54 --- /dev/null +++ b/chromium/device/fido/fido_ble_discovery_base.h @@ -0,0 +1,56 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_FIDO_BLE_DISCOVERY_BASE_H_ +#define DEVICE_FIDO_FIDO_BLE_DISCOVERY_BASE_H_ + +#include <memory> + +#include "base/component_export.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/fido/fido_discovery.h" + +namespace device { + +class BluetoothDiscoverySession; + +class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscoveryBase + : public FidoDiscovery, + public BluetoothAdapter::Observer { + public: + FidoBleDiscoveryBase(); + ~FidoBleDiscoveryBase() override; + + protected: + virtual void OnSetPowered() = 0; + virtual void OnStartDiscoverySessionWithFilter( + std::unique_ptr<BluetoothDiscoverySession>); + + void OnSetPoweredError(); + void OnStartDiscoverySessionError(); + void SetDiscoverySession( + std::unique_ptr<BluetoothDiscoverySession> discovery_session); + + BluetoothAdapter* adapter() { return adapter_.get(); } + + private: + void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter); + + // FidoDiscovery: + void StartInternal() override; + + scoped_refptr<BluetoothAdapter> adapter_; + std::unique_ptr<BluetoothDiscoverySession> discovery_session_; + + base::WeakPtrFactory<FidoBleDiscoveryBase> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FidoBleDiscoveryBase); +}; + +} // namespace device + +#endif // DEVICE_FIDO_FIDO_BLE_DISCOVERY_BASE_H_ diff --git a/chromium/device/fido/fido_ble_frames.cc b/chromium/device/fido/fido_ble_frames.cc index 1f0425319ad..314519c0726 100644 --- a/chromium/device/fido/fido_ble_frames.cc +++ b/chromium/device/fido/fido_ble_frames.cc @@ -10,7 +10,7 @@ #include "base/logging.h" #include "base/numerics/safe_conversions.h" #include "device/fido/fido_constants.h" -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" namespace device { @@ -73,7 +73,7 @@ FidoBleFrame::ToFragments(size_t max_fragment_size) const { // Subtract 1 to account for SEQ byte. for (auto cont_data : - u2f_parsing_utils::SplitSpan(data_view, max_fragment_size - 1)) { + fido_parsing_utils::SplitSpan(data_view, max_fragment_size - 1)) { // High bit must stay cleared. other_fragments.emplace(cont_data, other_fragments.size() & 0x7F); } diff --git a/chromium/device/fido/fido_ble_uuids.cc b/chromium/device/fido/fido_ble_uuids.cc index 8fe32d093c4..2f96f75d4dc 100644 --- a/chromium/device/fido/fido_ble_uuids.cc +++ b/chromium/device/fido/fido_ble_uuids.cc @@ -4,6 +4,8 @@ #include "device/fido/fido_ble_uuids.h" +#include "build/build_config.h" + namespace device { const char kFidoServiceUUID[] = "0000fffd-0000-1000-8000-00805f9b34fb"; @@ -15,4 +17,10 @@ const char kFidoServiceRevisionUUID[] = "00002a28-0000-1000-8000-00805f9b34fb"; const char kFidoServiceRevisionBitfieldUUID[] = "f1d0fff4-deaa-ecee-b42f-c9ba7ed623bb"; +#if defined(OS_MACOSX) +const char kCableAdvertisementUUID[] = "fde2"; +#else +const char kCableAdvertisementUUID[] = "0000fde2-0000-1000-8000-00805f9b34fb"; +#endif + } // namespace device diff --git a/chromium/device/fido/fido_ble_uuids.h b/chromium/device/fido/fido_ble_uuids.h index 61c4a559f72..a2ead64b534 100644 --- a/chromium/device/fido/fido_ble_uuids.h +++ b/chromium/device/fido/fido_ble_uuids.h @@ -22,6 +22,9 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFidoControlPointLengthUUID[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFidoServiceRevisionUUID[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFidoServiceRevisionBitfieldUUID[]; +// TODO(hongjunchoi): Add URL to the specification once CaBLE protocol is +// standardized. +COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableAdvertisementUUID[]; } // namespace device diff --git a/chromium/device/fido/fido_cable_device.cc b/chromium/device/fido/fido_cable_device.cc new file mode 100644 index 00000000000..02deea187df --- /dev/null +++ b/chromium/device/fido/fido_cable_device.cc @@ -0,0 +1,178 @@ +// Copyright 2018 The Chromium 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 "device/fido/fido_cable_device.h" + +#include <utility> + +#include "base/command_line.h" +#include "base/strings/string_piece.h" +#include "device/fido/fido_ble_connection.h" +#include "device/fido/fido_ble_frames.h" +#include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" + +namespace device { + +namespace switches { +constexpr char kEnableCableEncryption[] = "enable-cable-encryption"; +} // namespace switches + +namespace { + +// Maximum size of EncryptionData::read_sequence_num or +// EncryptionData::write_sequence_num allowed. If we encounter +// counter larger than |kMaxCounter| FidoCableDevice should error out. +constexpr size_t kMaxCounter = (1 << 24) - 1; + +base::StringPiece ConvertToStringPiece(const std::vector<uint8_t>& data) { + return base::StringPiece(reinterpret_cast<const char*>(data.data()), + data.size()); +} + +// static +bool IsEncryptionEnabled() { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + return command_line->HasSwitch(switches::kEnableCableEncryption); +} + +base::Optional<std::vector<uint8_t>> ConstructEncryptionNonce( + base::span<const uint8_t> nonce, + bool is_sender_client, + uint32_t counter) { + if (counter > kMaxCounter) + return base::nullopt; + + auto constructed_nonce = fido_parsing_utils::Materialize(nonce); + constructed_nonce.push_back(is_sender_client ? 0x00 : 0x01); + constructed_nonce.push_back(counter >> 16 & 0xFF); + constructed_nonce.push_back(counter >> 8 & 0xFF); + constructed_nonce.push_back(counter & 0xFF); + return constructed_nonce; +} + +bool EncryptOutgoingMessage( + const FidoCableDevice::EncryptionData& encryption_data, + std::vector<uint8_t>* message_to_encrypt) { + const auto nonce = ConstructEncryptionNonce( + encryption_data.nonce, true /* is_sender_client */, + encryption_data.write_sequence_num); + if (!nonce) + return false; + + DCHECK_EQ(nonce->size(), encryption_data.aes_key.NonceLength()); + std::string ciphertext; + bool encryption_success = encryption_data.aes_key.Seal( + ConvertToStringPiece(*message_to_encrypt), ConvertToStringPiece(*nonce), + nullptr /* additional_data */, &ciphertext); + if (!encryption_success) + return false; + + message_to_encrypt->assign(ciphertext.begin(), ciphertext.end()); + return true; +} + +bool DecryptIncomingMessage( + const FidoCableDevice::EncryptionData& encryption_data, + FidoBleFrame* incoming_frame) { + const auto nonce = ConstructEncryptionNonce( + encryption_data.nonce, false /* is_sender_client */, + encryption_data.read_sequence_num); + if (!nonce) + return false; + + DCHECK_EQ(nonce->size(), encryption_data.aes_key.NonceLength()); + std::string ciphertext; + + bool decryption_success = encryption_data.aes_key.Open( + ConvertToStringPiece(incoming_frame->data()), + ConvertToStringPiece(*nonce), nullptr /* additional_data */, &ciphertext); + if (!decryption_success) + return false; + + incoming_frame->data().assign(ciphertext.begin(), ciphertext.end()); + return true; +} + +} // namespace + +// FidoCableDevice::EncryptionData ---------------------------------------- + +FidoCableDevice::EncryptionData::EncryptionData( + std::string session_key, + const std::array<uint8_t, 8>& nonce) + : encryption_key(std::move(session_key)), nonce(nonce) { + DCHECK_EQ(encryption_key.size(), aes_key.KeyLength()); + aes_key.Init(&encryption_key); +} + +FidoCableDevice::EncryptionData::EncryptionData(EncryptionData&& data) = + default; + +FidoCableDevice::EncryptionData& FidoCableDevice::EncryptionData::operator=( + EncryptionData&& other) = default; + +FidoCableDevice::EncryptionData::~EncryptionData() = default; + +// FidoCableDevice::EncryptionData ---------------------------------------- + +FidoCableDevice::FidoCableDevice(std::string address, + std::string session_key, + const std::array<uint8_t, 8>& nonce) + : FidoBleDevice(std::move(address)), + encryption_data_(std::move(session_key), nonce), + weak_factory_(this) {} + +FidoCableDevice::FidoCableDevice(std::unique_ptr<FidoBleConnection> connection, + std::string session_key, + const std::array<uint8_t, 8>& nonce) + : FidoBleDevice(std::move(connection)), + encryption_data_(std::move(session_key), nonce), + weak_factory_(this) {} + +FidoCableDevice::~FidoCableDevice() = default; + +void FidoCableDevice::DeviceTransact(std::vector<uint8_t> command, + DeviceCallback callback) { + if (IsEncryptionEnabled()) { + if (!EncryptOutgoingMessage(encryption_data_, &command)) { + state_ = State::kDeviceError; + return; + } + + ++encryption_data_.write_sequence_num; + } + + AddToPendingFrames(FidoBleDeviceCommand::kMsg, std::move(command), + std::move(callback)); +} + +void FidoCableDevice::OnResponseFrame(FrameCallback callback, + base::Optional<FidoBleFrame> frame) { + // The request is done, time to reset |transaction_|. + ResetTransaction(); + state_ = frame ? State::kReady : State::kDeviceError; + + if (frame && IsEncryptionEnabled()) { + if (!DecryptIncomingMessage(encryption_data_, &frame.value())) { + state_ = State::kDeviceError; + frame = base::nullopt; + } + + ++encryption_data_.read_sequence_num; + } + + auto self = GetWeakPtr(); + std::move(callback).Run(std::move(frame)); + + // Executing callbacks may free |this|. Check |self| first. + if (self) + Transition(); +} + +base::WeakPtr<FidoDevice> FidoCableDevice::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + +} // namespace device diff --git a/chromium/device/fido/fido_cable_device.h b/chromium/device/fido/fido_cable_device.h new file mode 100644 index 00000000000..e8c76fe0c6a --- /dev/null +++ b/chromium/device/fido/fido_cable_device.h @@ -0,0 +1,78 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_FIDO_CABLE_DEVICE_H_ +#define DEVICE_FIDO_FIDO_CABLE_DEVICE_H_ + +#include <array> +#include <memory> +#include <string> +#include <vector> + +#include "base/component_export.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "crypto/aead.h" +#include "device/fido/fido_ble_device.h" + +namespace device { + +class FidoBleFrame; +class FidoBleConnection; + +class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDevice : public FidoBleDevice { + public: + // Encapsulates state FidoCableDevice maintains to encrypt and decrypt + // data within FidoBleFrame. + struct COMPONENT_EXPORT(DEVICE_FIDO) EncryptionData { + EncryptionData() = delete; + EncryptionData(std::string session_key, + const std::array<uint8_t, 8>& nonce); + EncryptionData(EncryptionData&& data); + EncryptionData& operator=(EncryptionData&& other); + ~EncryptionData(); + + std::string encryption_key; + std::array<uint8_t, 8> nonce; + crypto::Aead aes_key{crypto::Aead::AES_256_GCM}; + uint32_t write_sequence_num = 0; + uint32_t read_sequence_num = 0; + + DISALLOW_COPY_AND_ASSIGN(EncryptionData); + }; + + using FrameCallback = FidoBleTransaction::FrameCallback; + + FidoCableDevice(std::string address, + std::string session_key, + const std::array<uint8_t, 8>& nonce); + // Constructor used for testing purposes. + FidoCableDevice(std::unique_ptr<FidoBleConnection> connection, + std::string session_key, + const std::array<uint8_t, 8>& nonce); + ~FidoCableDevice() override; + + // FidoBleDevice: + void DeviceTransact(std::vector<uint8_t> command, + DeviceCallback callback) override; + void OnResponseFrame(FrameCallback callback, + base::Optional<FidoBleFrame> frame) override; + base::WeakPtr<FidoDevice> GetWeakPtr() override; + + private: + FRIEND_TEST_ALL_PREFIXES(FidoCableDeviceTest, + TestCableDeviceSendMultipleRequests); + FRIEND_TEST_ALL_PREFIXES(FidoCableDeviceTest, + TestCableDeviceErrorOnMaxCounter); + + EncryptionData encryption_data_; + base::WeakPtrFactory<FidoCableDevice> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FidoCableDevice); +}; + +} // namespace device + +#endif // DEVICE_FIDO_FIDO_CABLE_DEVICE_H_ diff --git a/chromium/device/fido/fido_cable_device_unittest.cc b/chromium/device/fido/fido_cable_device_unittest.cc new file mode 100644 index 00000000000..75ad4acbb6f --- /dev/null +++ b/chromium/device/fido/fido_cable_device_unittest.cc @@ -0,0 +1,360 @@ +// Copyright 2018 The Chromium 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 "device/fido/fido_cable_device.h" + +#include <array> +#include <limits> +#include <memory> +#include <string> +#include <utility> + +#include "base/command_line.h" +#include "base/optional.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "crypto/aead.h" +#include "device/bluetooth/test/bluetooth_test.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/mock_fido_ble_connection.h" +#include "device/fido/test_callback_receiver.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +namespace { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Test; +using TestDeviceCallbackReceiver = + test::ValueCallbackReceiver<base::Optional<std::vector<uint8_t>>>; + +// Sufficiently large test control point length as we are not interested +// in testing fragmentations of BLE messages. All Cable messages are encrypted +// and decrypted per request frame, not fragment. +constexpr auto kControlPointLength = std::numeric_limits<uint16_t>::max(); +// Counter value that is larger than FidoCableDevice::kMaxCounter. +constexpr uint32_t kInvalidCounter = 1 << 24; +constexpr char kTestSessionKey[] = "00000000000000000000000000000000"; +constexpr std::array<uint8_t, 8> kTestEncryptionNonce = { + {1, 1, 1, 1, 1, 1, 1, 1}}; +constexpr uint8_t kTestData[] = {'T', 'E', 'S', 'T'}; + +std::vector<uint8_t> ConstructSerializedOutgoingFragment( + base::span<const uint8_t> data) { + FidoBleFrame response_frame(FidoBleDeviceCommand::kMsg, + fido_parsing_utils::Materialize(data)); + const auto response_fragment = + std::get<0>(response_frame.ToFragments(kControlPointLength)); + + std::vector<uint8_t> outgoing_message; + response_fragment.Serialize(&outgoing_message); + return outgoing_message; +} + +class FakeCableAuthenticator { + public: + // Returns encrypted message of the ciphertext received from the client. + std::vector<uint8_t> ReplyWithSameMessage(base::span<const uint8_t> message) { + auto decrypted_message = DecryptMessage(message); + auto message_to_send = EncryptMessage(std::move(decrypted_message)); + return std::vector<uint8_t>(message_to_send.begin(), message_to_send.end()); + } + + void SetSessionKey(const std::string& session_key) { + session_key_ = session_key; + } + + void SetAuthenticatorCounter(uint32_t authenticator_counter) { + authenticator_counter_ = authenticator_counter; + } + + private: + std::string EncryptMessage(std::string message) { + crypto::Aead aead(crypto::Aead::AES_256_GCM); + DCHECK_EQ(session_key_.size(), aead.KeyLength()); + aead.Init(&session_key_); + + auto encryption_nonce = fido_parsing_utils::Materialize(nonce_); + encryption_nonce.push_back(0x01); + encryption_nonce.push_back(authenticator_counter_ >> 16 & 0xFF); + encryption_nonce.push_back(authenticator_counter_ >> 8 & 0xFF); + encryption_nonce.push_back(authenticator_counter_ & 0xFF); + DCHECK(encryption_nonce.size() == aead.NonceLength()); + + std::string ciphertext; + aead.Seal(message, + base::StringPiece( + reinterpret_cast<const char*>(encryption_nonce.data()), + encryption_nonce.size()), + nullptr /* additional_data */, &ciphertext); + authenticator_counter_++; + return ciphertext; + } + + std::string DecryptMessage(base::span<const uint8_t> message) { + crypto::Aead aead(crypto::Aead::AES_256_GCM); + DCHECK_EQ(session_key_.size(), aead.KeyLength()); + aead.Init(&session_key_); + + auto encryption_nonce = fido_parsing_utils::Materialize(nonce_); + encryption_nonce.push_back(0x00); + encryption_nonce.push_back(expected_client_counter_ >> 16 & 0xFF); + encryption_nonce.push_back(expected_client_counter_ >> 8 & 0xFF); + encryption_nonce.push_back(expected_client_counter_ & 0xFF); + DCHECK(encryption_nonce.size() == aead.NonceLength()); + + std::string ciphertext; + aead.Open(base::StringPiece(reinterpret_cast<const char*>(message.data()), + message.size()), + base::StringPiece( + reinterpret_cast<const char*>(encryption_nonce.data()), + encryption_nonce.size()), + nullptr /* additional_data */, &ciphertext); + expected_client_counter_++; + return ciphertext; + } + + std::array<uint8_t, 8> nonce_ = kTestEncryptionNonce; + std::string session_key_ = kTestSessionKey; + uint32_t expected_client_counter_ = 0; + uint32_t authenticator_counter_ = 0; +}; + +} // namespace + +class FidoCableDeviceTest : public Test { + public: + FidoCableDeviceTest() { + auto connection = std::make_unique<MockFidoBleConnection>( + BluetoothTestBase::kTestDeviceAddress1); + connection_ = connection.get(); + device_ = std::make_unique<FidoCableDevice>( + std::move(connection), kTestSessionKey, kTestEncryptionNonce); + + connection_->connection_status_callback() = + device_->GetConnectionStatusCallbackForTesting(); + connection_->read_callback() = device_->GetReadCallbackForTesting(); + } + + void ConnectWithLength(uint16_t length) { + EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] { + connection()->connection_status_callback().Run(true); + })); + + EXPECT_CALL(*connection(), ReadControlPointLengthPtr(_)) + .WillOnce(Invoke([length](auto* cb) { std::move(*cb).Run(length); })); + + device()->Connect(); + } + + void SetUpEncryptionSwitch() { + base::CommandLine::ForCurrentProcess()->AppendSwitch( + "enable-cable-encryption"); + } + + FidoCableDevice* device() { return device_.get(); } + MockFidoBleConnection* connection() { return connection_; } + FakeCableAuthenticator* authenticator() { return &authenticator_; } + + protected: + base::test::ScopedTaskEnvironment scoped_task_environment_; + + private: + FakeCableAuthenticator authenticator_; + MockFidoBleConnection* connection_; + std::unique_ptr<FidoCableDevice> device_; +}; + +TEST_F(FidoCableDeviceTest, TestCaBleDeviceSendData) { + SetUpEncryptionSwitch(); + ConnectWithLength(kControlPointLength); + + EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) + .WillOnce(Invoke([this](const auto& data, auto* cb) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(*cb), true)); + + const auto authenticator_reply = authenticator()->ReplyWithSameMessage( + base::make_span(data).subspan(3)); + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(connection()->read_callback(), + ConstructSerializedOutgoingFragment( + authenticator_reply))); + })); + + TestDeviceCallbackReceiver callback_receiver; + device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), + callback_receiver.callback()); + + callback_receiver.WaitForCallback(); + const auto& value = callback_receiver.value(); + ASSERT_TRUE(value); + EXPECT_THAT(*value, ::testing::ElementsAreArray(kTestData)); +} + +// Test that FidoCableDevice properly updates counters when sending/receiving +// multiple requests. +TEST_F(FidoCableDeviceTest, TestCableDeviceSendMultipleRequests) { + SetUpEncryptionSwitch(); + ConnectWithLength(kControlPointLength); + EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) + .Times(2) + .WillRepeatedly(Invoke([this](const auto& data, auto* cb) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(*cb), true)); + + const auto authenticator_reply = authenticator()->ReplyWithSameMessage( + base::make_span(data).subspan(3)); + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(connection()->read_callback(), + ConstructSerializedOutgoingFragment( + authenticator_reply))); + })); + + EXPECT_EQ(0u, device()->encryption_data_.write_sequence_num); + EXPECT_EQ(0u, device()->encryption_data_.read_sequence_num); + + TestDeviceCallbackReceiver callback_receiver1; + device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), + callback_receiver1.callback()); + callback_receiver1.WaitForCallback(); + const auto& value1 = callback_receiver1.value(); + ASSERT_TRUE(value1); + EXPECT_THAT(*value1, ::testing::ElementsAreArray(kTestData)); + EXPECT_EQ(1u, device()->encryption_data_.write_sequence_num); + EXPECT_EQ(1u, device()->encryption_data_.read_sequence_num); + + constexpr uint8_t kTestData2[] = {'T', 'E', 'S', 'T', '2'}; + TestDeviceCallbackReceiver callback_receiver2; + device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData2), + callback_receiver2.callback()); + callback_receiver2.WaitForCallback(); + const auto& value2 = callback_receiver2.value(); + ASSERT_TRUE(value2); + EXPECT_THAT(*value2, ::testing::ElementsAreArray(kTestData2)); + EXPECT_EQ(2u, device()->encryption_data_.write_sequence_num); + EXPECT_EQ(2u, device()->encryption_data_.read_sequence_num); +} + +TEST_F(FidoCableDeviceTest, TestCableDeviceFailOnIncorrectSessionKey) { + constexpr char kIncorrectSessionKey[] = "11111111111111111111111111111111"; + SetUpEncryptionSwitch(); + ConnectWithLength(kControlPointLength); + + EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) + .WillOnce(Invoke([this, &kIncorrectSessionKey](const auto& data, + auto* cb) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(*cb), true)); + + authenticator()->SetSessionKey(kIncorrectSessionKey); + const auto authenticator_reply = authenticator()->ReplyWithSameMessage( + base::make_span(data).subspan(3)); + + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(connection()->read_callback(), + ConstructSerializedOutgoingFragment( + authenticator_reply))); + })); + + TestDeviceCallbackReceiver callback_receiver; + device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), + callback_receiver.callback()); + + callback_receiver.WaitForCallback(); + const auto& value = callback_receiver.value(); + EXPECT_FALSE(value); +} + +TEST_F(FidoCableDeviceTest, TestCableDeviceFailOnUnexpectedCounter) { + constexpr uint32_t kIncorrectAuthenticatorCounter = 1; + SetUpEncryptionSwitch(); + ConnectWithLength(kControlPointLength); + + EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) + .WillOnce(Invoke([this, kIncorrectAuthenticatorCounter](const auto& data, + auto* cb) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(*cb), true)); + + authenticator()->SetAuthenticatorCounter( + kIncorrectAuthenticatorCounter); + const auto authenticator_reply = authenticator()->ReplyWithSameMessage( + base::make_span(data).subspan(3)); + + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(connection()->read_callback(), + ConstructSerializedOutgoingFragment( + authenticator_reply))); + })); + + TestDeviceCallbackReceiver callback_receiver; + device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), + callback_receiver.callback()); + + callback_receiver.WaitForCallback(); + const auto& value = callback_receiver.value(); + EXPECT_FALSE(value); +} + +// Test the unlikely event that the authenticator and client has sent/received +// requests more than FidoCableDevice::kMaxCounter amount of times. As we are +// only using 3 bytes to encapsulate counter during encryption, any counter +// value that is above FidoCableDevice::kMaxCounter -- even though it may be +// the expected counter value -- should return an error. +TEST_F(FidoCableDeviceTest, TestCableDeviceErrorOnMaxCounter) { + ConnectWithLength(kControlPointLength); + SetUpEncryptionSwitch(); + + EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) + .WillOnce(Invoke([this](const auto& data, auto* cb) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(*cb), true)); + + authenticator()->SetAuthenticatorCounter(kInvalidCounter); + const auto authenticator_reply = authenticator()->ReplyWithSameMessage( + base::make_span(data).subspan(3)); + + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(connection()->read_callback(), + ConstructSerializedOutgoingFragment( + authenticator_reply))); + })); + + TestDeviceCallbackReceiver callback_receiver; + device()->encryption_data_.read_sequence_num = kInvalidCounter; + device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), + callback_receiver.callback()); + + callback_receiver.WaitForCallback(); + const auto& value = callback_receiver.value(); + EXPECT_FALSE(value); +} + +TEST_F(FidoCableDeviceTest, TestEncryptionDisabledWithoutCommandLineSwitch) { + ConnectWithLength(kControlPointLength); + + EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) + .WillOnce(Invoke([this](const auto& data, auto* cb) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(*cb), true)); + + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(connection()->read_callback(), data)); + })); + + TestDeviceCallbackReceiver callback_receiver; + device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), + callback_receiver.callback()); + + callback_receiver.WaitForCallback(); + const auto& value = callback_receiver.value(); + ASSERT_TRUE(value); + EXPECT_THAT(*value, ::testing::ElementsAreArray(kTestData)); +} + +} // namespace device diff --git a/chromium/device/fido/fido_cable_discovery.cc b/chromium/device/fido/fido_cable_discovery.cc new file mode 100644 index 00000000000..1468db3d37d --- /dev/null +++ b/chromium/device/fido/fido_cable_discovery.cc @@ -0,0 +1,286 @@ +// Copyright 2018 The Chromium 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 "device/fido/fido_cable_discovery.h" + +#include <algorithm> +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback_helpers.h" +#include "base/strings/stringprintf.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "build/build_config.h" +#include "device/bluetooth/bluetooth_advertisement.h" +#include "device/bluetooth/bluetooth_discovery_session.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "device/fido/fido_ble_uuids.h" +#include "device/fido/fido_cable_device.h" +#include "device/fido/fido_parsing_utils.h" + +namespace device { + +namespace { + +#if defined(OS_MACOSX) + +// Convert byte array into GUID formatted string as defined by RFC 4122. +// As we are converting 128 bit UUID, |bytes| must be have length of 16. +// https://tools.ietf.org/html/rfc4122 +std::string ConvertBytesToUuid(base::span<const uint8_t, 16> bytes) { + uint64_t most_significant_bytes = 0; + for (size_t i = 0; i < sizeof(uint64_t); i++) { + most_significant_bytes |= base::strict_cast<uint64_t>(bytes[i]) + << 8 * (7 - i); + } + + uint64_t least_significant_bytes = 0; + for (size_t i = 0; i < sizeof(uint64_t); i++) { + least_significant_bytes |= base::strict_cast<uint64_t>(bytes[i + 8]) + << 8 * (7 - i); + } + + return base::StringPrintf( + "%08x-%04x-%04x-%04x-%012llx", + static_cast<unsigned int>(most_significant_bytes >> 32), + static_cast<unsigned int>((most_significant_bytes >> 16) & 0x0000ffff), + static_cast<unsigned int>(most_significant_bytes & 0x0000ffff), + static_cast<unsigned int>(least_significant_bytes >> 48), + least_significant_bytes & 0x0000ffff'ffffffffULL); +} + +#endif + +const BluetoothUUID& CableAdvertisementUUID() { + static const BluetoothUUID service_uuid(kCableAdvertisementUUID); + return service_uuid; +} + +bool IsCableDevice(const BluetoothDevice* device) { + return base::ContainsKey(device->GetServiceData(), CableAdvertisementUUID()); +} + +// Construct advertisement data with different formats depending on client's +// operating system. Ideally, we advertise EIDs as part of Service Data, but +// this isn't available on all platforms. On Windows we use Manufacturer Data +// instead, and on Mac our only option is to advertise an additional service +// with the EID as its UUID. +std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData( + uint8_t version_number, + base::span<const uint8_t, FidoCableDiscovery::kEphemeralIdSize> + client_eid) { + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::AdvertisementType::ADVERTISEMENT_TYPE_BROADCAST); + +#if defined(OS_MACOSX) + auto list = std::make_unique<BluetoothAdvertisement::UUIDList>(); + list->emplace_back(kCableAdvertisementUUID); + list->emplace_back(ConvertBytesToUuid(client_eid)); + advertisement_data->set_service_uuids(std::move(list)); + +#elif defined(OS_WIN) + constexpr uint16_t kFidoManufacturerId = 0xFFFD; + constexpr std::array<uint8_t, 2> kFidoManufacturerDataHeader = {0x51, 0xFE}; + + auto manufacturer_data = + std::make_unique<BluetoothAdvertisement::ManufacturerData>(); + std::vector<uint8_t> manufacturer_data_value; + fido_parsing_utils::Append(&manufacturer_data_value, + kFidoManufacturerDataHeader); + fido_parsing_utils::Append(&manufacturer_data_value, client_eid); + manufacturer_data->emplace(kFidoManufacturerId, + std::move(manufacturer_data_value)); + advertisement_data->set_manufacturer_data(std::move(manufacturer_data)); + +#elif defined(OS_LINUX) || defined(OS_CHROMEOS) + // Service data for ChromeOS and Linux is 1 byte corresponding to Cable + // version number, followed by 7 empty(0x00) bytes, followed by 16 bytes + // corresponding to client EID. + auto service_data = std::make_unique<BluetoothAdvertisement::ServiceData>(); + std::vector<uint8_t> service_data_value(24, 0); + service_data_value[0] = version_number; + std::copy(client_eid.begin(), client_eid.end(), + service_data_value.begin() + 8); + service_data->emplace(kCableAdvertisementUUID, std::move(service_data_value)); + advertisement_data->set_service_data(std::move(service_data)); +#endif + + return advertisement_data; +} + +} // namespace + +// FidoCableDiscovery::CableDiscoveryData ------------------------------------- + +FidoCableDiscovery::CableDiscoveryData::CableDiscoveryData( + uint8_t version, + const EidArray& client_eid, + const EidArray& authenticator_eid, + const SessionKeyArray& session_key) + : version(version), + client_eid(client_eid), + authenticator_eid(authenticator_eid), + session_key(session_key) {} + +FidoCableDiscovery::CableDiscoveryData::CableDiscoveryData( + const CableDiscoveryData& data) = default; + +FidoCableDiscovery::CableDiscoveryData& FidoCableDiscovery::CableDiscoveryData:: +operator=(const CableDiscoveryData& other) = default; + +FidoCableDiscovery::CableDiscoveryData::~CableDiscoveryData() = default; + +// FidoCableDiscovery --------------------------------------------------------- + +FidoCableDiscovery::FidoCableDiscovery( + std::vector<CableDiscoveryData> discovery_data) + : discovery_data_(std::move(discovery_data)), weak_factory_(this) {} + +// This is a workaround for https://crbug.com/846522 +FidoCableDiscovery::~FidoCableDiscovery() { + for (auto advertisement : advertisements_) + advertisement.second->Unregister(base::DoNothing(), base::DoNothing()); +} + +void FidoCableDiscovery::DeviceAdded(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (!IsCableDevice(device)) + return; + + DVLOG(2) << "Discovered Cable device: " << device->GetAddress(); + CableDeviceFound(adapter, device); +} + +void FidoCableDiscovery::DeviceChanged(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (!IsCableDevice(device)) + return; + + DVLOG(2) << "Device changed for Cable device: " << device->GetAddress(); + CableDeviceFound(adapter, device); +} + +void FidoCableDiscovery::DeviceRemoved(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (IsCableDevice(device) && GetFoundCableDiscoveryData(device)) { + const auto& device_address = device->GetAddress(); + VLOG(2) << "Cable device removed: " << device_address; + RemoveDevice(FidoBleDevice::GetId(device_address)); + } +} + +void FidoCableDiscovery::OnSetPowered() { + DCHECK(adapter()); + + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&FidoCableDiscovery::StartAdvertisement, + weak_factory_.GetWeakPtr())); +} + +void FidoCableDiscovery::StartAdvertisement() { + DCHECK(adapter()); + + for (const auto& data : discovery_data_) { + adapter()->RegisterAdvertisement( + ConstructAdvertisementData(data.version, data.client_eid), + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoCableDiscovery::OnAdvertisementRegistered, + weak_factory_.GetWeakPtr(), data.client_eid)), + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoCableDiscovery::OnAdvertisementRegisterError, + weak_factory_.GetWeakPtr()))); + } +} + +void FidoCableDiscovery::OnAdvertisementRegistered( + const EidArray& client_eid, + scoped_refptr<BluetoothAdvertisement> advertisement) { + DVLOG(2) << "Advertisement registered."; + advertisements_.emplace(client_eid, std::move(advertisement)); + RecordAdvertisementResult(true /* is_success */); +} + +void FidoCableDiscovery::OnAdvertisementRegisterError( + BluetoothAdvertisement::ErrorCode error_code) { + DLOG(ERROR) << "Failed to register advertisement: " << error_code; + RecordAdvertisementResult(false /* is_success */); +} + +void FidoCableDiscovery::RecordAdvertisementResult(bool is_success) { + is_success ? ++advertisement_success_counter_ + : ++advertisement_failure_counter_; + + // Wait until all advertisements are sent out. + if (advertisement_success_counter_ + advertisement_failure_counter_ != + discovery_data_.size()) { + return; + } + + // No advertisements succeeded, no point in starting scanning. + if (!advertisement_success_counter_) { + NotifyDiscoveryStarted(false); + return; + } + + // At least one advertisement succeeded and all advertisement has been + // processed. Start scanning. + adapter()->StartDiscoverySessionWithFilter( + std::make_unique<BluetoothDiscoveryFilter>( + BluetoothTransport::BLUETOOTH_TRANSPORT_LE), + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionWithFilter, + weak_factory_.GetWeakPtr())), + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionError, + weak_factory_.GetWeakPtr()))); +} + +void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter, + BluetoothDevice* device) { + const auto* found_cable_device_data = GetFoundCableDiscoveryData(device); + if (!found_cable_device_data) + return; + + DVLOG(2) << "Found new Cable device."; + // Nonce is embedded as first 8 bytes of client EID. + std::array<uint8_t, 8> nonce; + bool extract_success = fido_parsing_utils::ExtractArray( + found_cable_device_data->client_eid, 0, &nonce); + if (!extract_success) + return; + + AddDevice(std::make_unique<FidoCableDevice>( + device->GetAddress(), + std::string(found_cable_device_data->session_key.begin(), + found_cable_device_data->session_key.end()), + nonce)); +} + +const FidoCableDiscovery::CableDiscoveryData* +FidoCableDiscovery::GetFoundCableDiscoveryData( + const BluetoothDevice* device) const { + const auto* service_data = + device->GetServiceDataForUUID(CableAdvertisementUUID()); + DCHECK(service_data); + + EidArray received_authenticator_eid; + bool extract_success = fido_parsing_utils::ExtractArray( + *service_data, 8, &received_authenticator_eid); + if (!extract_success) + return nullptr; + + auto discovery_data_iterator = std::find_if( + discovery_data_.begin(), discovery_data_.end(), + [&received_authenticator_eid](const auto& data) { + return received_authenticator_eid == data.authenticator_eid; + }); + + return discovery_data_iterator != discovery_data_.end() + ? &(*discovery_data_iterator) + : nullptr; +} + +} // namespace device diff --git a/chromium/device/fido/fido_cable_discovery.h b/chromium/device/fido/fido_cable_discovery.h new file mode 100644 index 00000000000..a5f9f1749be --- /dev/null +++ b/chromium/device/fido/fido_cable_discovery.h @@ -0,0 +1,100 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_FIDO_CABLE_DISCOVERY_H_ +#define DEVICE_FIDO_FIDO_CABLE_DISCOVERY_H_ + +#include <stdint.h> + +#include <array> +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/component_export.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "device/fido/fido_ble_discovery_base.h" + +namespace device { + +class BluetoothDevice; +class BluetoothAdvertisement; + +class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery + : public FidoBleDiscoveryBase { + public: + static constexpr size_t kEphemeralIdSize = 16; + static constexpr size_t kSessionKeySize = 32; + using EidArray = std::array<uint8_t, kEphemeralIdSize>; + using SessionKeyArray = std::array<uint8_t, kSessionKeySize>; + + // Encapsulates information required to discover Cable device per single + // credential. When multiple credentials are enrolled to a single account + // (i.e. more than one phone has been enrolled to an user account as a + // security key), then FidoCableDiscovery must advertise for all of the client + // EID received from the relying party. + // TODO(hongjunchoi): Add discovery data required for MakeCredential request. + // See: https://crbug.com/837088 + struct COMPONENT_EXPORT(DEVICE_FIDO) CableDiscoveryData { + CableDiscoveryData(uint8_t version, + const EidArray& client_eid, + const EidArray& authenticator_eid, + const SessionKeyArray& session_key); + CableDiscoveryData(const CableDiscoveryData& data); + CableDiscoveryData& operator=(const CableDiscoveryData& other); + ~CableDiscoveryData(); + + uint8_t version; + EidArray client_eid; + EidArray authenticator_eid; + SessionKeyArray session_key; + }; + + FidoCableDiscovery(std::vector<CableDiscoveryData> discovery_data); + ~FidoCableDiscovery() override; + + private: + FRIEND_TEST_ALL_PREFIXES(FidoCableDiscoveryTest, + TestUnregisterAdvertisementUponDestruction); + + // BluetoothAdapter::Observer: + void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override; + void DeviceChanged(BluetoothAdapter* adapter, + BluetoothDevice* device) override; + void DeviceRemoved(BluetoothAdapter* adapter, + BluetoothDevice* device) override; + + // FidoBleDiscoveryBase: + void OnSetPowered() override; + + void StartAdvertisement(); + void OnAdvertisementRegistered( + const EidArray& client_eid, + scoped_refptr<BluetoothAdvertisement> advertisement); + void OnAdvertisementRegisterError( + BluetoothAdvertisement::ErrorCode error_code); + // Keeps a counter of success/failure of advertisements done by the client. + // If all advertisements fail, then immediately stop discovery process and + // invoke NotifyDiscoveryStarted(false). Otherwise kick off discovery session + // once all advertisements has been processed. + void RecordAdvertisementResult(bool is_success); + void CableDeviceFound(BluetoothAdapter* adapter, BluetoothDevice* device); + const CableDiscoveryData* GetFoundCableDiscoveryData( + const BluetoothDevice* device) const; + + std::vector<CableDiscoveryData> discovery_data_; + size_t advertisement_success_counter_ = 0; + size_t advertisement_failure_counter_ = 0; + std::map<EidArray, scoped_refptr<BluetoothAdvertisement>> advertisements_; + base::WeakPtrFactory<FidoCableDiscovery> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FidoCableDiscovery); +}; + +} // namespace device + +#endif // DEVICE_FIDO_FIDO_CABLE_DISCOVERY_H_ diff --git a/chromium/device/fido/fido_cable_discovery_unittest.cc b/chromium/device/fido/fido_cable_discovery_unittest.cc new file mode 100644 index 00000000000..920f7d8e289 --- /dev/null +++ b/chromium/device/fido/fido_cable_discovery_unittest.cc @@ -0,0 +1,390 @@ +// Copyright 2018 The Chromium 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 "device/fido/fido_cable_discovery.h" + +#include <algorithm> +#include <memory> +#include <utility> + +#include "base/stl_util.h" +#include "build/build_config.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_advertisement.h" +#include "device/bluetooth/test/bluetooth_test.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/fido_ble_device.h" +#include "device/fido/fido_ble_uuids.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/mock_fido_discovery_observer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::NiceMock; + +namespace device { + +namespace { + +constexpr uint8_t kTestCableVersionNumber = 0x01; + +// Constants required for discovering and constructing a Cable device that +// are given by the relying party via an extension. +constexpr FidoCableDiscovery::EidArray kClientEid = { + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15}}; + +constexpr char kUuidFormattedClientEid[] = + "00010203-0405-0607-0809-101112131415"; + +constexpr FidoCableDiscovery::EidArray kAuthenticatorEid = { + {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01}}; + +constexpr FidoCableDiscovery::EidArray kInvalidAuthenticatorEid = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}}; + +constexpr FidoCableDiscovery::SessionKeyArray kTestSessionKey = { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; + +constexpr FidoCableDiscovery::EidArray kSecondaryClientEid = { + {0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, + 0x03, 0x02, 0x01, 0x00}}; + +constexpr char kUuidFormattedSecondaryClientEid[] = + "15141312-1110-0908-0706-050403020100"; + +constexpr FidoCableDiscovery::EidArray kSecondaryAuthenticatorEid = { + {0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee}}; + +constexpr FidoCableDiscovery::SessionKeyArray kSecondarySessionKey = { + {0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd}}; + +// Below constants are used to construct MockBluetoothDevice for testing. +constexpr char kTestBleDeviceAddress[] = "11:12:13:14:15:16"; + +constexpr char kTestBleDeviceName[] = "test_cable_device"; + +std::unique_ptr<MockBluetoothDevice> CreateTestBluetoothDevice() { + return std::make_unique<testing::NiceMock<MockBluetoothDevice>>( + nullptr /* adapter */, 0 /* bluetooth_class */, kTestBleDeviceName, + kTestBleDeviceAddress, true /* paired */, true /* connected */); +} + +// Matcher to compare the content of advertisement data received from the +// client. +MATCHER_P2(IsAdvertisementContent, + expected_client_eid, + expected_uuid_formatted_client_eid, + "") { +#if defined(OS_MACOSX) + const auto uuid_list = arg->service_uuids(); + return std::any_of(uuid_list->begin(), uuid_list->end(), + [this](const auto& uuid) { + return uuid == expected_uuid_formatted_client_eid; + }); + +#elif defined(OS_WIN) + const auto manufacturer_data = arg->manufacturer_data(); + const auto manufacturer_data_value = manufacturer_data->find(0xFFFD); + + if (manufacturer_data_value == manufacturer_data->end()) + return false; + + return fido_parsing_utils::ExtractSuffixSpan(manufacturer_data_value->second, + 2) == expected_client_eid; + +#elif defined(OS_LINUX) || defined(OS_CHROMEOS) + const auto service_data = arg->service_data(); + const auto service_data_with_uuid = + service_data->find(kCableAdvertisementUUID); + + if (service_data_with_uuid == service_data->end()) + return false; + + const auto& service_data_value = service_data_with_uuid->second; + return service_data_value[0] == kTestCableVersionNumber && + service_data_value.size() == 24u && + base::make_span(service_data_value).subspan(8) == expected_client_eid; + +#endif + + return true; +} + +class CableMockBluetoothAdvertisement : public BluetoothAdvertisement { + public: + MOCK_METHOD2(Unregister, + void(const SuccessCallback& success_callback, + const ErrorCallback& error_callback)); + + private: + ~CableMockBluetoothAdvertisement() override = default; +}; + +// Mock BLE adapter that abstracts out authenticator logic with the following +// logic: +// - Responds to BluetoothAdapter::RegisterAdvertisement() by always invoking +// success callback. +// - Responds to BluetoothAdapter::StartDiscoverySessionWithFilter() by +// invoking BluetoothAdapter::Observer::DeviceAdded() on a test bluetooth +// device that includes service data containing authenticator EID. +class CableMockAdapter : public MockBluetoothAdapter { + public: + MOCK_METHOD3(RegisterAdvertisement, + void(std::unique_ptr<BluetoothAdvertisement::Data>, + const CreateAdvertisementCallback&, + const AdvertisementErrorCallback&)); + + void AddNewTestBluetoothDevice( + base::span<const uint8_t, FidoCableDiscovery::kEphemeralIdSize> + authenticator_eid) { + auto mock_device = CreateTestBluetoothDevice(); + + std::vector<uint8_t> service_data(8); + fido_parsing_utils::Append(&service_data, authenticator_eid); + BluetoothDevice::ServiceDataMap service_data_map; + service_data_map.emplace(kCableAdvertisementUUID, std::move(service_data)); + + mock_device->UpdateAdvertisementData( + 1 /* rssi */, BluetoothDevice::UUIDList(), std::move(service_data_map), + BluetoothDevice::ManufacturerDataMap(), nullptr /* tx_power*/); + + auto* mock_device_ptr = mock_device.get(); + AddMockDevice(std::move(mock_device)); + + for (auto& observer : GetObservers()) + observer.DeviceAdded(this, mock_device_ptr); + } + + void ExpectRegisterAdvertisementWithResponse( + bool simulate_success, + base::span<const uint8_t> expected_client_eid, + base::StringPiece expected_uuid_formatted_client_eid, + scoped_refptr<CableMockBluetoothAdvertisement> advertisement_ptr = + nullptr) { + if (!advertisement_ptr) + advertisement_ptr = + base::MakeRefCounted<CableMockBluetoothAdvertisement>(); + + EXPECT_CALL(*this, + RegisterAdvertisement( + IsAdvertisementContent(expected_client_eid, + expected_uuid_formatted_client_eid), + _, _)) + .WillOnce(::testing::WithArgs<1, 2>( + [simulate_success, advertisement_ptr]( + const auto& success_callback, const auto& failure_callback) { + simulate_success + ? success_callback.Run(advertisement_ptr) + : failure_callback.Run(BluetoothAdvertisement::ErrorCode:: + INVALID_ADVERTISEMENT_ERROR_CODE); + })); + } + + void ExpectSuccessCallbackToSetPowered() { + EXPECT_CALL(*this, SetPowered(true, _, _)) + .WillOnce(::testing::WithArg<1>( + [](const auto& callback) { callback.Run(); })); + } + + protected: + ~CableMockAdapter() override = default; +}; + +} // namespace + +class FidoCableDiscoveryTest : public ::testing::Test { + public: + std::unique_ptr<FidoCableDiscovery> CreateDiscovery() { + std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data; + discovery_data.emplace_back(kTestCableVersionNumber, kClientEid, + kAuthenticatorEid, kTestSessionKey); + return std::make_unique<FidoCableDiscovery>(std::move(discovery_data)); + } + + base::test::ScopedTaskEnvironment scoped_task_environment_; +}; + +// Tests regular successful discovery flow for Cable device. +TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewDevice) { + auto cable_discovery = CreateDiscovery(); + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL(mock_observer, DeviceAdded(_, _)); + cable_discovery->set_observer(&mock_observer); + + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + ::testing::InSequence testing_sequence; + mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + true /* simulate_success */, kClientEid, kUuidFormattedClientEid); + EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) + .WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) { + mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid); + callback.Run(nullptr); + })); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + cable_discovery->Start(); + scoped_task_environment_.RunUntilIdle(); +} + +// Tests a scenario where upon broadcasting advertisement and scanning, client +// discovers a device with an incorrect authenticator EID. Observer::AddDevice() +// must not be called. +TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsIncorrectDevice) { + auto cable_discovery = CreateDiscovery(); + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL(mock_observer, DeviceAdded(_, _)).Times(0); + cable_discovery->set_observer(&mock_observer); + + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + ::testing::InSequence testing_sequence; + mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + true /* simulate_success */, kClientEid, kUuidFormattedClientEid); + EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) + .WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) { + mock_adapter->AddNewTestBluetoothDevice(kInvalidAuthenticatorEid); + callback.Run(nullptr); + })); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + cable_discovery->Start(); + scoped_task_environment_.RunUntilIdle(); +} + +// Tests Cable discovery flow when multiple(2) sets of client/authenticator EIDs +// are passed on from the relying party. We should expect 2 invocations of +// BluetoothAdapter::RegisterAdvertisement(). +TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithMultipleEids) { + std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data; + discovery_data.emplace_back(kTestCableVersionNumber, kClientEid, + kAuthenticatorEid, kTestSessionKey); + discovery_data.emplace_back(kTestCableVersionNumber, kSecondaryClientEid, + kSecondaryAuthenticatorEid, kSecondarySessionKey); + auto cable_discovery = + std::make_unique<FidoCableDiscovery>(std::move(discovery_data)); + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL(mock_observer, DeviceAdded(_, _)); + cable_discovery->set_observer(&mock_observer); + + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + ::testing::InSequence testing_sequence; + mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + true /* simulate_success */, kClientEid, kUuidFormattedClientEid); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + true /* simulate_success */, kSecondaryClientEid, + kUuidFormattedSecondaryClientEid); + EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) + .WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) { + mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid); + callback.Run(nullptr); + })); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + cable_discovery->Start(); + scoped_task_environment_.RunUntilIdle(); +} + +// Tests a scenario where only one of the two client EID's are advertised +// successfully. Since at least one advertisement are successfully processed, +// scanning process should be invoked. +TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithPartialAdvertisementSuccess) { + std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data; + discovery_data.emplace_back(kTestCableVersionNumber, kClientEid, + kAuthenticatorEid, kTestSessionKey); + discovery_data.emplace_back(kTestCableVersionNumber, kSecondaryClientEid, + kSecondaryAuthenticatorEid, kSecondarySessionKey); + auto cable_discovery = + std::make_unique<FidoCableDiscovery>(std::move(discovery_data)); + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL(mock_observer, DeviceAdded(_, _)); + cable_discovery->set_observer(&mock_observer); + + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + ::testing::InSequence testing_sequence; + mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + true /* simulate_success */, kClientEid, kUuidFormattedClientEid); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + false /* simulate_success */, kSecondaryClientEid, + kUuidFormattedSecondaryClientEid); + EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) + .WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) { + mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid); + callback.Run(nullptr); + })); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + cable_discovery->Start(); + scoped_task_environment_.RunUntilIdle(); +} + +// Test the scenario when all advertisement for client EID's fails. +TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithAdvertisementFailures) { + std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data; + discovery_data.emplace_back(kTestCableVersionNumber, kClientEid, + kAuthenticatorEid, kTestSessionKey); + discovery_data.emplace_back(kTestCableVersionNumber, kSecondaryClientEid, + kSecondaryAuthenticatorEid, kSecondarySessionKey); + auto cable_discovery = + std::make_unique<FidoCableDiscovery>(std::move(discovery_data)); + + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL(mock_observer, DeviceAdded(_, _)).Times(0); + cable_discovery->set_observer(&mock_observer); + + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + ::testing::InSequence testing_sequence; + mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + false /* simulate_success */, kClientEid, kUuidFormattedClientEid); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + false /* simulate_success */, kSecondaryClientEid, + kUuidFormattedSecondaryClientEid); + EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) + .Times(0); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + cable_discovery->Start(); + scoped_task_environment_.RunUntilIdle(); +} + +TEST_F(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponDestruction) { + auto cable_discovery = CreateDiscovery(); + CableMockBluetoothAdvertisement* advertisement = + new CableMockBluetoothAdvertisement(); + EXPECT_CALL(*advertisement, Unregister(_, _)).Times(1); + + ::testing::InSequence testing_sequence; + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + true /* simulate_success */, kClientEid, kUuidFormattedClientEid, + base::WrapRefCounted(advertisement)); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + cable_discovery->Start(); + scoped_task_environment_.RunUntilIdle(); + + EXPECT_EQ(1u, cable_discovery->advertisements_.size()); + cable_discovery.reset(); +} + +} // namespace device diff --git a/chromium/device/fido/fido_constants.cc b/chromium/device/fido/fido_constants.cc index 20de24e51a8..4c61c7e8abb 100644 --- a/chromium/device/fido/fido_constants.cc +++ b/chromium/device/fido/fido_constants.cc @@ -43,11 +43,6 @@ const uint8_t kP1IndividualAttestation = 0x80; const size_t kMaxKeyHandleLength = 255; const size_t kU2fParameterLength = 32; -const std::array<uint8_t, 2> kLegacyVersionSuffix = {0x00, 0x00}; - -const std::array<uint8_t, 6> kU2fVersionResponse = {'U', '2', 'F', - '_', 'V', '2'}; - const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(3); const base::TimeDelta kHidKeepAliveDelay = base::TimeDelta::FromMilliseconds(100); @@ -59,4 +54,13 @@ const char kNoneAttestationValue[] = "none"; const char kPublicKey[] = "public-key"; +const char* CredentialTypeToString(CredentialType type) { + switch (type) { + case CredentialType::kPublicKey: + return kPublicKey; + } + NOTREACHED(); + return kPublicKey; +} + } // namespace device diff --git a/chromium/device/fido/fido_constants.h b/chromium/device/fido/fido_constants.h index 3ec3d1dd52b..2d4040651f2 100644 --- a/chromium/device/fido/fido_constants.h +++ b/chromium/device/fido/fido_constants.h @@ -212,6 +212,19 @@ enum class UserVerificationRequirement { kDiscouraged, }; +// Enumerates the two types of application parameter values used: the +// "primary" value is the hash of the relying party ID[1] and is always +// provided. The "alternative" value is the hash of a U2F AppID, specified in +// an extension[2], for compatibility with keys that were registered with the +// old API. +// +// [1] https://w3c.github.io/webauthn/#rp-id +// [2] https://w3c.github.io/webauthn/#sctn-appid-extension +enum class ApplicationParameterType { + kPrimary, + kAlternative, +}; + // Parameters for fake U2F registration used to check for user presence. COMPONENT_EXPORT(DEVICE_FIDO) extern const std::array<uint8_t, 32> kBogusAppParam; @@ -261,15 +274,6 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const uint8_t kP1IndividualAttestation; COMPONENT_EXPORT(DEVICE_FIDO) extern const size_t kMaxKeyHandleLength; COMPONENT_EXPORT(DEVICE_FIDO) extern const size_t kU2fParameterLength; -// Suffix added to APDU encoded command for legacy version request. -COMPONENT_EXPORT(DEVICE_FIDO) -extern const std::array<uint8_t, 2> kLegacyVersionSuffix; - -// Expected response data for version request from U2F device. -// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#getversion-request-and-response---u2f_version -COMPONENT_EXPORT(DEVICE_FIDO) -extern const std::array<uint8_t, 6> kU2fVersionResponse; - // Maximum wait time before client error outs on device. COMPONENT_EXPORT(DEVICE_FIDO) extern const base::TimeDelta kDeviceTimeout; @@ -290,14 +294,7 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kNoneAttestationValue[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kPublicKey[]; -constexpr const char* to_string(CredentialType type) { - switch (type) { - case CredentialType::kPublicKey: - return kPublicKey; - } - NOTREACHED(); - return kPublicKey; -} +const char* CredentialTypeToString(CredentialType type); } // namespace device diff --git a/chromium/device/fido/fido_device.h b/chromium/device/fido/fido_device.h index b22900d5e69..28ffef7f692 100644 --- a/chromium/device/fido/fido_device.h +++ b/chromium/device/fido/fido_device.h @@ -25,7 +25,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDevice { public: using WinkCallback = base::OnceClosure; using DeviceCallback = - base::OnceCallback<void(base::Optional<std::vector<uint8_t>> response)>; + base::OnceCallback<void(base::Optional<std::vector<uint8_t>>)>; // Internal state machine states. enum class State { kInit, kConnected, kBusy, kReady, kDeviceError }; diff --git a/chromium/device/fido/fido_device_authenticator.cc b/chromium/device/fido/fido_device_authenticator.cc new file mode 100644 index 00000000000..c8aa469918e --- /dev/null +++ b/chromium/device/fido/fido_device_authenticator.cc @@ -0,0 +1,56 @@ +// Copyright 2018 The Chromium 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 "device/fido/fido_device_authenticator.h" + +#include <utility> + +#include "device/fido/authenticator_selection_criteria.h" +#include "device/fido/ctap_get_assertion_request.h" +#include "device/fido/ctap_make_credential_request.h" +#include "device/fido/fido_device.h" +#include "device/fido/get_assertion_task.h" +#include "device/fido/make_credential_task.h" + +namespace device { + +FidoDeviceAuthenticator::FidoDeviceAuthenticator(FidoDevice* device) + : device_(device) {} +FidoDeviceAuthenticator::~FidoDeviceAuthenticator() = default; + +void FidoDeviceAuthenticator::MakeCredential( + AuthenticatorSelectionCriteria authenticator_selection_criteria, + CtapMakeCredentialRequest request, + MakeCredentialCallback callback) { + DCHECK(!task_); + // TODO(martinkr): Change FidoTasks to take all request parameters by const + // reference, so we can avoid copying these from the RequestHandler. + task_ = std::make_unique<MakeCredentialTask>( + device_, std::move(request), std::move(authenticator_selection_criteria), + std::move(callback)); +} + +void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request, + GetAssertionCallback callback) { + task_ = std::make_unique<GetAssertionTask>(device_, std::move(request), + std::move(callback)); +} + +void FidoDeviceAuthenticator::Cancel() { + if (!task_) + return; + + task_->CancelTask(); +} + +std::string FidoDeviceAuthenticator::GetId() const { + return device_->GetId(); +} + +void FidoDeviceAuthenticator::SetTaskForTesting( + std::unique_ptr<FidoTask> task) { + task_ = std::move(task); +} + +} // namespace device diff --git a/chromium/device/fido/fido_device_authenticator.h b/chromium/device/fido/fido_device_authenticator.h new file mode 100644 index 00000000000..29db9d814f5 --- /dev/null +++ b/chromium/device/fido/fido_device_authenticator.h @@ -0,0 +1,63 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_FIDO_DEVICE_AUTHENTICATOR_H_ +#define DEVICE_FIDO_FIDO_DEVICE_AUTHENTICATOR_H_ + +#include <string> +#include <vector> + +#include "base/component_export.h" +#include "base/macros.h" +#include "base/optional.h" +#include "device/fido/fido_authenticator.h" + +namespace device { + +class AuthenticatorSelectionCriteria; +class CtapGetAssertionRequest; +class CtapMakeCredentialRequest; +class FidoDevice; +class FidoTask; + +// Adaptor class from a |FidoDevice| to the |FidoAuthenticator| interface. +// Responsible for translating WebAuthn-level requests into serializations that +// can be passed to the device for transport. +class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator + : public FidoAuthenticator { + public: + FidoDeviceAuthenticator(FidoDevice* device); + ~FidoDeviceAuthenticator() override; + + // FidoAuthenticator: + void MakeCredential( + AuthenticatorSelectionCriteria authenticator_selection_criteria, + CtapMakeCredentialRequest request, + MakeCredentialCallback callback) override; + void GetAssertion(CtapGetAssertionRequest request, + GetAssertionCallback callback) override; + void Cancel() override; + std::string GetId() const override; + + protected: + void OnCtapMakeCredentialResponseReceived( + MakeCredentialCallback callback, + base::Optional<std::vector<uint8_t>> response_data); + void OnCtapGetAssertionResponseReceived( + GetAssertionCallback callback, + base::Optional<std::vector<uint8_t>> response_data); + + FidoDevice* device() { return device_; } + void SetTaskForTesting(std::unique_ptr<FidoTask> task); + + private: + FidoDevice* const device_; + std::unique_ptr<FidoTask> task_; + + DISALLOW_COPY_AND_ASSIGN(FidoDeviceAuthenticator); +}; + +} // namespace device + +#endif // DEVICE_FIDO_FIDO_DEVICE_AUTHENTICATOR_H_ diff --git a/chromium/device/fido/fido_discovery.cc b/chromium/device/fido/fido_discovery.cc index 8e941b65032..299bbf058c7 100644 --- a/chromium/device/fido/fido_discovery.cc +++ b/chromium/device/fido/fido_discovery.cc @@ -33,11 +33,21 @@ std::unique_ptr<FidoDiscovery> CreateFidoDiscoveryImpl( #endif // !defined(OS_ANDROID) case FidoTransportProtocol::kBluetoothLowEnergy: return std::make_unique<FidoBleDiscovery>(); + // FidoCaBleDiscovery is not constructed using FidoDiscovery factory + // function and instead constructed in FidoGetAssertionRequestHandler as it + // requires extensions passed on from the relying party. + case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy: + NOTREACHED() + << "Cable discovery is not constructed using factory method."; + return nullptr; case FidoTransportProtocol::kNearFieldCommunication: // TODO(https://crbug.com/825949): Add NFC support. return nullptr; + case FidoTransportProtocol::kInternal: + NOTREACHED() << "Internal authenticators should be handled separately."; + return nullptr; } - NOTREACHED(); + NOTREACHED() << "Unhandled transport type"; return nullptr; } @@ -64,9 +74,10 @@ FidoDiscovery::~FidoDiscovery() = default; void FidoDiscovery::Start() { DCHECK_EQ(state_, State::kIdle); state_ = State::kStarting; + // TODO(hongjunchoi): Fix so that NotifiyStarted() is never called + // synchronously after StartInternal(). + // See: https://crbug.com/823686 StartInternal(); - // StartInternal should never synchronously call NotifyStarted(). - DCHECK_EQ(state_, State::kStarting); } void FidoDiscovery::NotifyDiscoveryStarted(bool success) { diff --git a/chromium/device/fido/fido_hid_device.cc b/chromium/device/fido/fido_hid_device.cc index 89652ed41c4..d2104f564b9 100644 --- a/chromium/device/fido/fido_hid_device.cc +++ b/chromium/device/fido/fido_hid_device.cc @@ -148,13 +148,13 @@ void FidoHidDevice::AllocateChannel(std::vector<uint8_t> command, void FidoHidDevice::OnAllocateChannel(std::vector<uint8_t> nonce, std::vector<uint8_t> command, DeviceCallback callback, - bool success, - std::unique_ptr<FidoHidMessage> message) { + base::Optional<FidoHidMessage> message) { if (state_ == State::kDeviceError) return; + timeout_callback_.Cancel(); - if (!success || !message) { + if (!message) { state_ = State::kDeviceError; Transition(std::vector<uint8_t>(), std::move(callback)); return; @@ -198,11 +198,11 @@ void FidoHidDevice::OnAllocateChannel(std::vector<uint8_t> nonce, Transition(std::move(command), std::move(callback)); } -void FidoHidDevice::WriteMessage(std::unique_ptr<FidoHidMessage> message, +void FidoHidDevice::WriteMessage(base::Optional<FidoHidMessage> message, bool response_expected, HidMessageCallback callback) { if (!connection_ || !message || message->NumPackets() == 0) { - std::move(callback).Run(false, nullptr); + std::move(callback).Run(base::nullopt); return; } const auto& packet = message->PopNextPacket(); @@ -213,7 +213,7 @@ void FidoHidDevice::WriteMessage(std::unique_ptr<FidoHidMessage> message, std::move(callback))); } -void FidoHidDevice::PacketWritten(std::unique_ptr<FidoHidMessage> message, +void FidoHidDevice::PacketWritten(base::Optional<FidoHidMessage> message, bool response_expected, HidMessageCallback callback, bool success) { @@ -222,13 +222,13 @@ void FidoHidDevice::PacketWritten(std::unique_ptr<FidoHidMessage> message, } else if (success && response_expected) { ReadMessage(std::move(callback)); } else { - std::move(callback).Run(success, nullptr); + std::move(callback).Run(base::nullopt); } } void FidoHidDevice::ReadMessage(HidMessageCallback callback) { if (!connection_) { - std::move(callback).Run(false, nullptr); + std::move(callback).Run(base::nullopt); return; } @@ -241,14 +241,14 @@ void FidoHidDevice::OnRead(HidMessageCallback callback, uint8_t report_id, const base::Optional<std::vector<uint8_t>>& buf) { if (!success) { - std::move(callback).Run(success, nullptr); + std::move(callback).Run(base::nullopt); return; } DCHECK(buf); auto read_message = FidoHidMessage::CreateFromSerializedData(*buf); if (!read_message) { - std::move(callback).Run(false, nullptr); + std::move(callback).Run(base::nullopt); return; } @@ -261,7 +261,7 @@ void FidoHidDevice::OnRead(HidMessageCallback callback, } if (read_message->MessageComplete()) { - std::move(callback).Run(success, std::move(read_message)); + std::move(callback).Run(std::move(read_message)); return; } @@ -272,20 +272,20 @@ void FidoHidDevice::OnRead(HidMessageCallback callback, } void FidoHidDevice::OnReadContinuation( - std::unique_ptr<FidoHidMessage> message, + base::Optional<FidoHidMessage> message, HidMessageCallback callback, bool success, uint8_t report_id, const base::Optional<std::vector<uint8_t>>& buf) { if (!success) { - std::move(callback).Run(success, nullptr); + std::move(callback).Run(base::nullopt); return; } DCHECK(buf); message->AddContinuationPacket(*buf); if (message->MessageComplete()) { - std::move(callback).Run(success, std::move(message)); + std::move(callback).Run(std::move(message)); return; } connection_->Read(base::BindOnce(&FidoHidDevice::OnReadContinuation, @@ -294,13 +294,12 @@ void FidoHidDevice::OnReadContinuation( } void FidoHidDevice::MessageReceived(DeviceCallback callback, - bool success, - std::unique_ptr<FidoHidMessage> message) { + base::Optional<FidoHidMessage> message) { if (state_ == State::kDeviceError) return; timeout_callback_.Cancel(); - if (!success || !message) { + if (!message) { state_ = State::kDeviceError; Transition(std::vector<uint8_t>(), std::move(callback)); return; @@ -329,8 +328,8 @@ void FidoHidDevice::MessageReceived(DeviceCallback callback, state_ = State::kReady; base::WeakPtr<FidoHidDevice> self = weak_factory_.GetWeakPtr(); std::move(callback).Run( - (success && message) ? base::make_optional(message->GetMessagePayload()) - : base::nullopt); + message ? base::make_optional(message->GetMessagePayload()) + : base::nullopt); // Executing |callback| may have freed |this|. Check |self| first. if (self && !pending_transactions_.empty()) { @@ -366,8 +365,7 @@ void FidoHidDevice::OnKeepAlive(DeviceCallback callback) { } void FidoHidDevice::OnWink(WinkCallback callback, - bool success, - std::unique_ptr<FidoHidMessage> response) { + base::Optional<FidoHidMessage> response) { std::move(callback).Run(); } diff --git a/chromium/device/fido/fido_hid_device.h b/chromium/device/fido/fido_hid_device.h index 57dd5f65185..f1e4865b50e 100644 --- a/chromium/device/fido/fido_hid_device.h +++ b/chromium/device/fido/fido_hid_device.h @@ -5,7 +5,6 @@ #ifndef DEVICE_FIDO_FIDO_HID_DEVICE_H_ #define DEVICE_FIDO_FIDO_HID_DEVICE_H_ -#include <memory> #include <queue> #include <string> #include <utility> @@ -15,6 +14,7 @@ #include "base/cancelable_callback.h" #include "base/component_export.h" #include "base/macros.h" +#include "base/optional.h" #include "components/apdu/apdu_command.h" #include "components/apdu/apdu_response.h" #include "device/fido/fido_device.h" @@ -58,7 +58,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice { static constexpr uint32_t kBroadcastChannel = 0xffffffff; using HidMessageCallback = - base::OnceCallback<void(bool, std::unique_ptr<FidoHidMessage>)>; + base::OnceCallback<void(base::Optional<FidoHidMessage>)>; using ConnectCallback = device::mojom::HidManager::ConnectCallback; // Open a connection to this device. @@ -71,35 +71,31 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice { void OnAllocateChannel(std::vector<uint8_t> nonce, std::vector<uint8_t> command, DeviceCallback callback, - bool success, - std::unique_ptr<FidoHidMessage> message); + base::Optional<FidoHidMessage> message); void Transition(std::vector<uint8_t> command, DeviceCallback callback); // Write all message packets to device, and read response if expected. - void WriteMessage(std::unique_ptr<FidoHidMessage> message, + void WriteMessage(base::Optional<FidoHidMessage> message, bool response_expected, HidMessageCallback callback); - void PacketWritten(std::unique_ptr<FidoHidMessage> message, + void PacketWritten(base::Optional<FidoHidMessage> message, bool response_expected, HidMessageCallback callback, bool success); // Read all response message packets from device. void ReadMessage(HidMessageCallback callback); void MessageReceived(DeviceCallback callback, - bool success, - std::unique_ptr<FidoHidMessage> message); + base::Optional<FidoHidMessage> message); void OnRead(HidMessageCallback callback, bool success, uint8_t report_id, const base::Optional<std::vector<uint8_t>>& buf); - void OnReadContinuation(std::unique_ptr<FidoHidMessage> message, + void OnReadContinuation(base::Optional<FidoHidMessage> message, HidMessageCallback callback, bool success, uint8_t report_id, const base::Optional<std::vector<uint8_t>>& buf); void OnKeepAlive(DeviceCallback callback); - void OnWink(WinkCallback callback, - bool success, - std::unique_ptr<FidoHidMessage> response); + void OnWink(WinkCallback callback, base::Optional<FidoHidMessage> response); void ArmTimeout(DeviceCallback callback); void OnTimeout(DeviceCallback callback); diff --git a/chromium/device/fido/fido_hid_device_unittest.cc b/chromium/device/fido/fido_hid_device_unittest.cc index 0b95f705219..e52e1b5382a 100644 --- a/chromium/device/fido/fido_hid_device_unittest.cc +++ b/chromium/device/fido/fido_hid_device_unittest.cc @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "device/fido/fido_hid_device.h" + +#include <memory> #include <tuple> #include "base/bind.h" @@ -11,14 +14,10 @@ #include "base/strings/string_number_conversions.h" #include "base/test/scoped_task_environment.h" #include "base/threading/thread_task_runner_handle.h" -#include "components/apdu/apdu_command.h" -#include "components/apdu/apdu_response.h" #include "device/fido/fake_hid_impl_for_testing.h" #include "device/fido/fido_constants.h" -#include "device/fido/fido_hid_device.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/test_callback_receiver.h" -#include "device/fido/u2f_parsing_utils.h" -#include "device/fido/u2f_request.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/interface_request.h" #include "services/device/public/cpp/hid/hid_device_filter.h" @@ -33,10 +32,17 @@ using ::testing::Invoke; namespace { -// HID_MSG(83), followed by payload length(0008), followed by 'U2F_V2', followed -// by APDU response code(9000). -constexpr uint8_t kMockVersionResponseSuffix[] = { - 0x83, 0x00, 0x08, 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x90, 0x00}; +// HID_MSG(83), followed by payload length(000b), followed by response data +// "MOCK_DATA", followed by APDU SW_NO_ERROR response code(9000). +constexpr uint8_t kU2fMockResponseMessage[] = { + 0x83, 0x00, 0x0b, 0x4d, 0x4f, 0x43, 0x4b, + 0x5f, 0x44, 0x41, 0x54, 0x41, 0x90, 0x00, +}; + +// APDU encoded success response with data "MOCK_DATA" followed by a SW_NO_ERROR +// APDU response code(9000). +constexpr uint8_t kU2fMockResponseData[] = {0x4d, 0x4f, 0x43, 0x4b, 0x5f, 0x44, + 0x41, 0x54, 0x41, 0x90, 0x00}; // HID_KEEP_ALIVE(bb), followed by payload length(0001), followed by // status processing(01) byte. @@ -49,13 +55,17 @@ constexpr uint8_t kInitResponsePrefix[] = { 0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x11, }; +// Mock APDU encoded U2F request with empty data and mock P1 parameter(0x04). +constexpr uint8_t kMockU2fRequest[] = {0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00}; + // Returns HID_INIT request to send to device with mock connection. std::vector<uint8_t> CreateMockInitResponse( base::span<const uint8_t> nonce, base::span<const uint8_t> channel_id) { - auto init_response = u2f_parsing_utils::Materialize(kInitResponsePrefix); - u2f_parsing_utils::Append(&init_response, nonce); - u2f_parsing_utils::Append(&init_response, channel_id); + auto init_response = fido_parsing_utils::Materialize(kInitResponsePrefix); + fido_parsing_utils::Append(&init_response, nonce); + fido_parsing_utils::Append(&init_response, channel_id); init_response.resize(64); return init_response; } @@ -63,28 +73,25 @@ std::vector<uint8_t> CreateMockInitResponse( // Returns HID keep alive message encoded into HID packet format. std::vector<uint8_t> GetKeepAliveHidMessage( base::span<const uint8_t> channel_id) { - auto response = u2f_parsing_utils::Materialize(channel_id); - u2f_parsing_utils::Append(&response, kMockKeepAliveResponseSuffix); + auto response = fido_parsing_utils::Materialize(channel_id); + fido_parsing_utils::Append(&response, kMockKeepAliveResponseSuffix); response.resize(64); return response; } // Returns "U2F_v2" as a mock response to version request with given channel id. -std::vector<uint8_t> CreateMockResponse( +std::vector<uint8_t> CreateMockResponseWithChannelId( base::span<const uint8_t> channel_id, base::span<const uint8_t> response_buffer) { - auto response = u2f_parsing_utils::Materialize(channel_id); - u2f_parsing_utils::Append(&response, response_buffer); + auto response = fido_parsing_utils::Materialize(channel_id); + fido_parsing_utils::Append(&response, response_buffer); response.resize(64); return response; } -// Returns U2F_V2 version response formatted in APDU response encoding. -std::vector<uint8_t> GetValidU2fVersionResponse() { - return apdu::ApduResponse(std::vector<uint8_t>(kU2fVersionResponse.begin(), - kU2fVersionResponse.end()), - apdu::ApduResponse::Status::SW_NO_ERROR) - .GetEncodedResponse(); +// Returns a APDU encoded U2F version request for testing. +std::vector<uint8_t> GetMockDeviceRequest() { + return fido_parsing_utils::Materialize(kMockU2fRequest); } device::mojom::HidDeviceInfoPtr TestHidDevice() { @@ -131,7 +138,7 @@ class FidoDeviceEnumerateCallbackReceiver }; using TestDeviceCallbackReceiver = - ::device::test::TestCallbackReceiver<base::Optional<std::vector<uint8_t>>>; + ::device::test::ValueCallbackReceiver<base::Optional<std::vector<uint8_t>>>; } // namespace @@ -170,20 +177,19 @@ TEST_F(FidoHidDeviceTest, TestConnectionFailure) { // Add pending transactions manually and ensure they are processed. TestDeviceCallbackReceiver receiver_1; - device->pending_transactions_.emplace(U2fRequest::GetU2fVersionApduCommand(), + device->pending_transactions_.emplace(GetMockDeviceRequest(), receiver_1.callback()); TestDeviceCallbackReceiver receiver_2; - device->pending_transactions_.emplace(U2fRequest::GetU2fVersionApduCommand(), + device->pending_transactions_.emplace(GetMockDeviceRequest(), receiver_2.callback()); TestDeviceCallbackReceiver receiver_3; - device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(), - receiver_3.callback()); + device->DeviceTransact(GetMockDeviceRequest(), receiver_3.callback()); EXPECT_EQ(FidoDevice::State::kDeviceError, device->state_); - EXPECT_FALSE(std::get<0>(*receiver_1.result())); - EXPECT_FALSE(std::get<0>(*receiver_2.result())); - EXPECT_FALSE(std::get<0>(*receiver_3.result())); + EXPECT_FALSE(receiver_1.value()); + EXPECT_FALSE(receiver_2.value()); + EXPECT_FALSE(receiver_3.value()); } TEST_F(FidoHidDeviceTest, TestDeviceError) { @@ -206,27 +212,25 @@ TEST_F(FidoHidDeviceTest, TestDeviceError) { device->state_ = FidoDevice::State::kReady; TestDeviceCallbackReceiver receiver_0; - device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(), - receiver_0.callback()); - EXPECT_FALSE(std::get<0>(*receiver_0.result())); + device->DeviceTransact(GetMockDeviceRequest(), receiver_0.callback()); + EXPECT_FALSE(receiver_0.value()); EXPECT_EQ(FidoDevice::State::kDeviceError, device->state_); // Add pending transactions manually and ensure they are processed. TestDeviceCallbackReceiver receiver_1; - device->pending_transactions_.emplace(U2fRequest::GetU2fVersionApduCommand(), + device->pending_transactions_.emplace(GetMockDeviceRequest(), receiver_1.callback()); TestDeviceCallbackReceiver receiver_2; - device->pending_transactions_.emplace(U2fRequest::GetU2fVersionApduCommand(), + device->pending_transactions_.emplace(GetMockDeviceRequest(), receiver_2.callback()); TestDeviceCallbackReceiver receiver_3; - device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(), - receiver_3.callback()); + device->DeviceTransact(GetMockDeviceRequest(), receiver_3.callback()); FakeHidConnection::mock_connection_error_ = false; EXPECT_EQ(FidoDevice::State::kDeviceError, device->state_); - EXPECT_FALSE(std::get<0>(*receiver_1.result())); - EXPECT_FALSE(std::get<0>(*receiver_2.result())); - EXPECT_FALSE(std::get<0>(*receiver_3.result())); + EXPECT_FALSE(receiver_1.value()); + EXPECT_FALSE(receiver_2.value()); + EXPECT_FALSE(receiver_3.value()); } TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) { @@ -240,9 +244,9 @@ TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) { // Replace device HID connection with custom client connection bound to mock // server-side mojo connection. device::mojom::HidConnectionPtr connection_client; - MockHidConnection mock_connection(hid_device.Clone(), - mojo::MakeRequest(&connection_client), - u2f_parsing_utils::Materialize(kChannelId)); + MockHidConnection mock_connection( + hid_device.Clone(), mojo::MakeRequest(&connection_client), + fido_parsing_utils::Materialize(kChannelId)); // Initial write for establishing a channel ID. mock_connection.ExpectWriteHidInit(); @@ -270,10 +274,10 @@ TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) { // Version response from the authenticator. .WillOnce(Invoke( [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) { - std::move(*cb).Run( - true, 0, - CreateMockResponse(mock_connection.connection_channel_id(), - kMockVersionResponseSuffix)); + std::move(*cb).Run(true, 0, + CreateMockResponseWithChannelId( + mock_connection.connection_channel_id(), + kU2fMockResponseMessage)); })); // Add device and set mock connection to fake hid manager. @@ -289,13 +293,12 @@ TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) { auto& device = u2f_devices.front(); TestDeviceCallbackReceiver cb; - device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(false), - cb.callback()); + device->DeviceTransact(GetMockDeviceRequest(), cb.callback()); cb.WaitForCallback(); - const auto& result = std::get<0>(*cb.result()); - ASSERT_TRUE(result); - EXPECT_THAT(*result, testing::ElementsAreArray(GetValidU2fVersionResponse())); + const auto& value = cb.value(); + ASSERT_TRUE(value); + EXPECT_THAT(*value, testing::ElementsAreArray(kU2fMockResponseData)); } TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) { @@ -306,9 +309,9 @@ TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) { // Replace device HID connection with custom client connection bound to mock // server-side mojo connection. device::mojom::HidConnectionPtr connection_client; - MockHidConnection mock_connection(hid_device.Clone(), - mojo::MakeRequest(&connection_client), - u2f_parsing_utils::Materialize(kChannelId)); + MockHidConnection mock_connection( + hid_device.Clone(), mojo::MakeRequest(&connection_client), + fido_parsing_utils::Materialize(kChannelId)); // Initial write for establishing channel ID. mock_connection.ExpectWriteHidInit(); @@ -337,10 +340,10 @@ TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) { kDeviceTimeout - base::TimeDelta::FromMicroseconds(1); scoped_task_environment_.FastForwardBy(almost_time_out); - std::move(*cb).Run( - true, 0, - CreateMockResponse(mock_connection.connection_channel_id(), - kMockVersionResponseSuffix)); + std::move(*cb).Run(true, 0, + CreateMockResponseWithChannelId( + mock_connection.connection_channel_id(), + kU2fMockResponseMessage)); })); // Add device and set mock connection to fake hid manager. @@ -359,12 +362,11 @@ TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) { // Keep alive message handling is only supported for CTAP HID device. device->set_supported_protocol(ProtocolVersion::kCtap); TestDeviceCallbackReceiver cb; - device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(false), - cb.callback()); + device->DeviceTransact(GetMockDeviceRequest(), cb.callback()); cb.WaitForCallback(); - const auto result = std::get<0>(*cb.result()); - ASSERT_TRUE(result); - EXPECT_THAT(*result, testing::ElementsAreArray(GetValidU2fVersionResponse())); + const auto& value = cb.value(); + ASSERT_TRUE(value); + EXPECT_THAT(*value, testing::ElementsAreArray(kU2fMockResponseData)); } TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) { @@ -375,9 +377,9 @@ TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) { // Replace device HID connection with custom client connection bound to mock // server-side mojo connection. device::mojom::HidConnectionPtr connection_client; - MockHidConnection mock_connection(hid_device.Clone(), - mojo::MakeRequest(&connection_client), - u2f_parsing_utils::Materialize(kChannelId)); + MockHidConnection mock_connection( + hid_device.Clone(), mojo::MakeRequest(&connection_client), + fido_parsing_utils::Materialize(kChannelId)); // Initial write for establishing channel ID. mock_connection.ExpectWriteHidInit(); @@ -403,10 +405,10 @@ TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) { // is invoked only after 3 seconds, which should cause device to timeout. .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { scoped_task_environment_.FastForwardBy(kDeviceTimeout); - std::move(*cb).Run( - true, 0, - CreateMockResponse(mock_connection.connection_channel_id(), - kMockVersionResponseSuffix)); + std::move(*cb).Run(true, 0, + CreateMockResponseWithChannelId( + mock_connection.connection_channel_id(), + kU2fMockResponseMessage)); })); // Add device and set mock connection to fake hid manager. @@ -425,11 +427,10 @@ TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) { // Keep alive message handling is only supported for CTAP HID device. device->set_supported_protocol(ProtocolVersion::kCtap); TestDeviceCallbackReceiver cb; - device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(false), - cb.callback()); + device->DeviceTransact(GetMockDeviceRequest(), cb.callback()); cb.WaitForCallback(); - const auto result = std::get<0>(*cb.result()); - EXPECT_FALSE(result); + const auto& value = cb.value(); + EXPECT_FALSE(value); EXPECT_EQ(FidoDevice::State::kDeviceError, device->state()); } @@ -441,9 +442,9 @@ TEST_F(FidoHidDeviceTest, TestCancel) { // Replace device HID connection with custom client connection bound to mock // server-side mojo connection. device::mojom::HidConnectionPtr connection_client; - MockHidConnection mock_connection(hid_device.Clone(), - mojo::MakeRequest(&connection_client), - u2f_parsing_utils::Materialize(kChannelId)); + MockHidConnection mock_connection( + hid_device.Clone(), mojo::MakeRequest(&connection_client), + fido_parsing_utils::Materialize(kChannelId)); // Initial write for establishing channel ID. mock_connection.ExpectWriteHidInit(); @@ -467,10 +468,10 @@ TEST_F(FidoHidDeviceTest, TestCancel) { auto delay = base::TimeDelta::FromSeconds(2); base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, - base::BindOnce( - std::move(*cb), true, 0, - CreateMockResponse(mock_connection.connection_channel_id(), - kMockVersionResponseSuffix)), + base::BindOnce(std::move(*cb), true, 0, + CreateMockResponseWithChannelId( + mock_connection.connection_channel_id(), + kU2fMockResponseMessage)), delay); })); @@ -490,8 +491,7 @@ TEST_F(FidoHidDeviceTest, TestCancel) { // Keep alive message handling is only supported for CTAP HID device. device->set_supported_protocol(ProtocolVersion::kCtap); TestDeviceCallbackReceiver cb; - device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(false), - cb.callback()); + device->DeviceTransact(GetMockDeviceRequest(), cb.callback()); device->Cancel(); scoped_task_environment_.FastForwardUntilNoTasksRemain(); } diff --git a/chromium/device/fido/fido_hid_message.cc b/chromium/device/fido/fido_hid_message.cc index b72f86feba1..d95598c4a62 100644 --- a/chromium/device/fido/fido_hid_message.cc +++ b/chromium/device/fido/fido_hid_message.cc @@ -10,17 +10,17 @@ #include "base/memory/ptr_util.h" #include "base/numerics/safe_conversions.h" -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" namespace device { // static -std::unique_ptr<FidoHidMessage> FidoHidMessage::Create( +base::Optional<FidoHidMessage> FidoHidMessage::Create( uint32_t channel_id, FidoHidDeviceCommand type, base::span<const uint8_t> data) { if (data.size() > kHidMaxMessageSize) - return nullptr; + return base::nullopt; switch (type) { case FidoHidDeviceCommand::kPing: @@ -28,53 +28,56 @@ std::unique_ptr<FidoHidMessage> FidoHidMessage::Create( case FidoHidDeviceCommand::kMsg: case FidoHidDeviceCommand::kCbor: { if (data.empty()) - return nullptr; + return base::nullopt; break; } case FidoHidDeviceCommand::kCancel: case FidoHidDeviceCommand::kWink: { if (!data.empty()) - return nullptr; + return base::nullopt; break; } case FidoHidDeviceCommand::kLock: { if (data.size() != 1 || data[0] > kHidMaxLockSeconds) - return nullptr; + return base::nullopt; break; } case FidoHidDeviceCommand::kInit: { if (data.size() != 8) - return nullptr; + return base::nullopt; break; } case FidoHidDeviceCommand::kKeepAlive: case FidoHidDeviceCommand::kError: if (data.size() != 1) - return nullptr; + return base::nullopt; } - return base::WrapUnique(new FidoHidMessage(channel_id, type, data)); + return FidoHidMessage(channel_id, type, data); } // static -std::unique_ptr<FidoHidMessage> FidoHidMessage::CreateFromSerializedData( +base::Optional<FidoHidMessage> FidoHidMessage::CreateFromSerializedData( base::span<const uint8_t> serialized_data) { size_t remaining_size = 0; if (serialized_data.size() > kHidPacketSize || serialized_data.size() < kHidInitPacketHeaderSize) - return nullptr; + return base::nullopt; auto init_packet = FidoHidInitPacket::CreateFromSerializedData( serialized_data, &remaining_size); if (init_packet == nullptr) - return nullptr; + return base::nullopt; - return base::WrapUnique( - new FidoHidMessage(std::move(init_packet), remaining_size)); + return FidoHidMessage(std::move(init_packet), remaining_size); } +FidoHidMessage::FidoHidMessage(FidoHidMessage&& that) = default; + +FidoHidMessage& FidoHidMessage::operator=(FidoHidMessage&& other) = default; + FidoHidMessage::~FidoHidMessage() = default; bool FidoHidMessage::MessageComplete() const { @@ -137,9 +140,9 @@ FidoHidMessage::FidoHidMessage(uint32_t channel_id, data = data.subspan(init_data.size()); for (auto cont_data : - u2f_parsing_utils::SplitSpan(data, kHidContinuationPacketDataSize)) { + fido_parsing_utils::SplitSpan(data, kHidContinuationPacketDataSize)) { packets_.push_back(std::make_unique<FidoHidContinuationPacket>( - channel_id, sequence++, u2f_parsing_utils::Materialize(cont_data))); + channel_id, sequence++, fido_parsing_utils::Materialize(cont_data))); } } diff --git a/chromium/device/fido/fido_hid_message.h b/chromium/device/fido/fido_hid_message.h index 529aa00634e..ecf386cd39e 100644 --- a/chromium/device/fido/fido_hid_message.h +++ b/chromium/device/fido/fido_hid_message.h @@ -17,6 +17,7 @@ #include "base/containers/queue.h" #include "base/containers/span.h" #include "base/macros.h" +#include "base/optional.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_hid_packet.h" @@ -27,14 +28,16 @@ namespace device { class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidMessage { public: // Static functions to create CTAP/U2F HID commands. - static std::unique_ptr<FidoHidMessage> Create(uint32_t channel_id, - FidoHidDeviceCommand cmd, - base::span<const uint8_t> data); + static base::Optional<FidoHidMessage> Create(uint32_t channel_id, + FidoHidDeviceCommand cmd, + base::span<const uint8_t> data); // Reconstruct a message from serialized message data. - static std::unique_ptr<FidoHidMessage> CreateFromSerializedData( + static base::Optional<FidoHidMessage> CreateFromSerializedData( base::span<const uint8_t> serialized_data); + FidoHidMessage(FidoHidMessage&& that); + FidoHidMessage& operator=(FidoHidMessage&& other); ~FidoHidMessage(); bool MessageComplete() const; diff --git a/chromium/device/fido/fido_hid_message_unittest.cc b/chromium/device/fido/fido_hid_message_unittest.cc index cc7ab4fef97..b819454dbe5 100644 --- a/chromium/device/fido/fido_hid_message_unittest.cc +++ b/chromium/device/fido/fido_hid_message_unittest.cc @@ -160,7 +160,7 @@ TEST(FidoHidMessageTest, TestMaxSize) { std::vector<uint8_t> data(kHidMaxMessageSize + 1); auto oversize_message = FidoHidMessage::Create(channel_id, FidoHidDeviceCommand::kPing, data); - EXPECT_EQ(nullptr, oversize_message); + EXPECT_FALSE(oversize_message); } TEST(FidoHidMessageTest, TestDeconstruct) { diff --git a/chromium/device/fido/u2f_parsing_utils.cc b/chromium/device/fido/fido_parsing_utils.cc index 2da8ba0b628..54daf0940e4 100644 --- a/chromium/device/fido/u2f_parsing_utils.cc +++ b/chromium/device/fido/fido_parsing_utils.cc @@ -2,12 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" #include "base/logging.h" +#include "crypto/sha2.h" namespace device { -namespace u2f_parsing_utils { +namespace fido_parsing_utils { namespace { @@ -79,5 +80,11 @@ std::vector<base::span<const uint8_t>> SplitSpan(base::span<const uint8_t> span, return chunks; } -} // namespace u2f_parsing_utils +std::vector<uint8_t> CreateSHA256Hash(base::StringPiece data) { + std::vector<uint8_t> hashed_data(crypto::kSHA256Length); + crypto::SHA256HashString(data, hashed_data.data(), hashed_data.size()); + return hashed_data; +} + +} // namespace fido_parsing_utils } // namespace device diff --git a/chromium/device/fido/u2f_parsing_utils.h b/chromium/device/fido/fido_parsing_utils.h index 15ca8c7f95f..ace50c5cd7f 100644 --- a/chromium/device/fido/u2f_parsing_utils.h +++ b/chromium/device/fido/fido_parsing_utils.h @@ -2,21 +2,37 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_U2F_PARSING_UTILS_H_ -#define DEVICE_FIDO_U2F_PARSING_UTILS_H_ +#ifndef DEVICE_FIDO_FIDO_PARSING_UTILS_H_ +#define DEVICE_FIDO_FIDO_PARSING_UTILS_H_ #include <stddef.h> #include <stdint.h> + #include <algorithm> #include <array> +#include <utility> #include <vector> #include "base/component_export.h" #include "base/containers/span.h" #include "base/optional.h" +#include "base/strings/string_piece.h" namespace device { -namespace u2f_parsing_utils { +namespace fido_parsing_utils { + +// Comparator object that calls base::make_span on its arguments before +// comparing them with operator<. Useful when comparing sequence containers that +// are of different types, but have similar semantics. +struct SpanLess { + template <typename T, typename U> + constexpr bool operator()(T&& lhs, U&& rhs) const { + return base::make_span(std::forward<T>(lhs)) < + base::make_span(std::forward<U>(rhs)); + } + + using is_transparent = void; +}; // U2FResponse offsets. The format of a U2F response is defined in // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-response-message-success @@ -81,7 +97,10 @@ COMPONENT_EXPORT(DEVICE_FIDO) std::vector<base::span<const uint8_t>> SplitSpan(base::span<const uint8_t> span, size_t max_chunk_size); -} // namespace u2f_parsing_utils +COMPONENT_EXPORT(DEVICE_FIDO) +std::vector<uint8_t> CreateSHA256Hash(base::StringPiece data); + +} // namespace fido_parsing_utils } // namespace device -#endif // DEVICE_FIDO_U2F_PARSING_UTILS_H_ +#endif // DEVICE_FIDO_FIDO_PARSING_UTILS_H_ diff --git a/chromium/device/fido/u2f_parsing_utils_unittest.cc b/chromium/device/fido/fido_parsing_utils_unittest.cc index 7dffb524f72..b84cb6d9265 100644 --- a/chromium/device/fido/u2f_parsing_utils_unittest.cc +++ b/chromium/device/fido/fido_parsing_utils_unittest.cc @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/u2f_parsing_utils.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/fido_test_data.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace device { -namespace u2f_parsing_utils { +namespace fido_parsing_utils { namespace { constexpr uint8_t kOne[] = {0x01}; @@ -19,6 +20,66 @@ constexpr uint8_t kThree[] = {0x03}; constexpr uint8_t kOneTwoThree[] = {0x01, 0x02, 0x03}; } // namespace +TEST(U2fParsingUtils, SpanLess) { + const std::array<int, 4> kOneTwoThreeFour = {1, 2, 3, 4}; + + EXPECT_FALSE(SpanLess()(kOne, kOne)); + EXPECT_TRUE(SpanLess()(kOne, kOneTwo)); + EXPECT_TRUE(SpanLess()(kOne, kTwo)); + EXPECT_TRUE(SpanLess()(kOne, kTwoThree)); + EXPECT_TRUE(SpanLess()(kOne, kThree)); + EXPECT_TRUE(SpanLess()(kOne, kOneTwoThree)); + EXPECT_TRUE(SpanLess()(kOne, kOneTwoThreeFour)); + + EXPECT_FALSE(SpanLess()(kOneTwo, kOne)); + EXPECT_FALSE(SpanLess()(kOneTwo, kOneTwo)); + EXPECT_TRUE(SpanLess()(kOneTwo, kTwo)); + EXPECT_TRUE(SpanLess()(kOneTwo, kTwoThree)); + EXPECT_TRUE(SpanLess()(kOneTwo, kThree)); + EXPECT_TRUE(SpanLess()(kOneTwo, kOneTwoThree)); + EXPECT_TRUE(SpanLess()(kOneTwo, kOneTwoThreeFour)); + + EXPECT_FALSE(SpanLess()(kTwo, kOne)); + EXPECT_FALSE(SpanLess()(kTwo, kOneTwo)); + EXPECT_FALSE(SpanLess()(kTwo, kTwo)); + EXPECT_TRUE(SpanLess()(kTwo, kTwoThree)); + EXPECT_TRUE(SpanLess()(kTwo, kThree)); + EXPECT_FALSE(SpanLess()(kTwo, kOneTwoThree)); + EXPECT_FALSE(SpanLess()(kTwo, kOneTwoThreeFour)); + + EXPECT_FALSE(SpanLess()(kTwoThree, kOne)); + EXPECT_FALSE(SpanLess()(kTwoThree, kOneTwo)); + EXPECT_FALSE(SpanLess()(kTwoThree, kTwo)); + EXPECT_FALSE(SpanLess()(kTwoThree, kTwoThree)); + EXPECT_TRUE(SpanLess()(kTwoThree, kThree)); + EXPECT_FALSE(SpanLess()(kTwoThree, kOneTwoThree)); + EXPECT_FALSE(SpanLess()(kTwoThree, kOneTwoThreeFour)); + + EXPECT_FALSE(SpanLess()(kThree, kOne)); + EXPECT_FALSE(SpanLess()(kThree, kOneTwo)); + EXPECT_FALSE(SpanLess()(kThree, kTwo)); + EXPECT_FALSE(SpanLess()(kThree, kTwoThree)); + EXPECT_FALSE(SpanLess()(kThree, kThree)); + EXPECT_FALSE(SpanLess()(kThree, kOneTwoThree)); + EXPECT_FALSE(SpanLess()(kThree, kOneTwoThreeFour)); + + EXPECT_FALSE(SpanLess()(kOneTwoThree, kOne)); + EXPECT_FALSE(SpanLess()(kOneTwoThree, kOneTwo)); + EXPECT_TRUE(SpanLess()(kOneTwoThree, kTwo)); + EXPECT_TRUE(SpanLess()(kOneTwoThree, kTwoThree)); + EXPECT_TRUE(SpanLess()(kOneTwoThree, kThree)); + EXPECT_FALSE(SpanLess()(kOneTwoThree, kOneTwoThree)); + EXPECT_TRUE(SpanLess()(kOneTwoThree, kOneTwoThreeFour)); + + EXPECT_FALSE(SpanLess()(kOneTwoThreeFour, kOne)); + EXPECT_FALSE(SpanLess()(kOneTwoThreeFour, kOneTwo)); + EXPECT_TRUE(SpanLess()(kOneTwoThreeFour, kTwo)); + EXPECT_TRUE(SpanLess()(kOneTwoThreeFour, kTwoThree)); + EXPECT_TRUE(SpanLess()(kOneTwoThreeFour, kThree)); + EXPECT_FALSE(SpanLess()(kOneTwoThreeFour, kOneTwoThree)); + EXPECT_FALSE(SpanLess()(kOneTwoThreeFour, kOneTwoThreeFour)); +} + TEST(U2fParsingUtils, Materialize) { const std::vector<uint8_t> empty; EXPECT_THAT(Materialize(empty), ::testing::IsEmpty()); @@ -188,5 +249,10 @@ TEST(U2fParsingUtils, SplitSpan) { ::testing::ElementsAre(::testing::ElementsAreArray(kOneTwoThree))); } -} // namespace u2f_parsing_utils +TEST(U2fParsingUtils, CreateSHA256Hash) { + EXPECT_THAT(CreateSHA256Hash("acme.com"), + ::testing::ElementsAreArray(test_data::kApplicationParameter)); +} + +} // namespace fido_parsing_utils } // namespace device diff --git a/chromium/device/fido/fido_request_handler.h b/chromium/device/fido/fido_request_handler.h index 3b46f9039aa..a81327cdd2f 100644 --- a/chromium/device/fido/fido_request_handler.h +++ b/chromium/device/fido/fido_request_handler.h @@ -13,6 +13,7 @@ #include "base/containers/flat_set.h" #include "base/macros.h" #include "base/optional.h" +#include "device/fido/fido_authenticator.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_device.h" #include "device/fido/fido_transport_protocol.h" @@ -41,11 +42,11 @@ class FidoRequestHandler : public FidoRequestHandlerBase { bool is_complete() const { return completion_callback_.is_null(); } protected: - // Converts device response code received from CTAP1/CTAP2 device into + // Converts authenticator response code received from CTAP1/CTAP2 device into // FidoReturnCode and passes response data to webauth::mojom::Authenticator. - void OnDeviceResponse(FidoDevice* device, - CtapDeviceResponseCode device_response_code, - base::Optional<Response> response_data) { + void OnAuthenticatorResponse(FidoAuthenticator* authenticator, + CtapDeviceResponseCode device_response_code, + base::Optional<Response> response_data) { if (is_complete()) { DVLOG(2) << "Response from authenticator received after request is complete."; @@ -54,14 +55,18 @@ class FidoRequestHandler : public FidoRequestHandlerBase { const auto return_code = ConvertDeviceResponseCodeToFidoReturnCode( device_response_code, response_data.has_value()); + + // Any authenticator response codes that do not result from user consent + // imply that the authenticator should be dropped and that other on-going + // requests should continue until timeout is reached. if (!return_code) { - ongoing_tasks().erase(device->GetId()); + active_authenticators().erase(authenticator->GetId()); return; } // Once response has been passed to the relying party, cancel all other on // going requests. - CancelOngoingTasks(device->GetId()); + CancelOngoingTasks(authenticator->GetId()); std::move(completion_callback_).Run(*return_code, std::move(response_data)); } @@ -77,7 +82,7 @@ class FidoRequestHandler : public FidoRequestHandlerBase { : FidoReturnCode::kAuthenticatorResponseInvalid; // These errors are only returned after the user interacted with the - // device. + // authenticator. case CtapDeviceResponseCode::kCtap2ErrCredentialExcluded: return FidoReturnCode::kUserConsentButCredentialExcluded; case CtapDeviceResponseCode::kCtap2ErrNoCredentials: diff --git a/chromium/device/fido/fido_request_handler_base.cc b/chromium/device/fido/fido_request_handler_base.cc index 156f61c3b13..f3b5b7bf93b 100644 --- a/chromium/device/fido/fido_request_handler_base.cc +++ b/chromium/device/fido/fido_request_handler_base.cc @@ -7,16 +7,31 @@ #include <utility> #include "base/strings/string_piece.h" +#include "build/build_config.h" #include "device/fido/fido_device.h" #include "device/fido/fido_task.h" #include "services/service_manager/public/cpp/connector.h" +#if defined(OS_MACOSX) +#include "device/fido/mac/authenticator.h" +#endif + namespace device { FidoRequestHandlerBase::FidoRequestHandlerBase( service_manager::Connector* connector, const base::flat_set<FidoTransportProtocol>& transports) { for (const auto transport : transports) { + // Construction of CaBleDiscovery is handled by the implementing class as it + // requires an extension passed on from the relying party. + if (transport == FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy) + continue; + + if (transport == FidoTransportProtocol::kInternal) { + use_platform_authenticator_ = true; + continue; + } + auto discovery = FidoDiscovery::Create(transport, connector); if (discovery == nullptr) { // This can occur in tests when a ScopedVirtualU2fDevice is in effect and @@ -24,7 +39,6 @@ FidoRequestHandlerBase::FidoRequestHandlerBase( continue; } discovery->set_observer(this); - discovery->Start(); discoveries_.push_back(std::move(discovery)); } } @@ -33,30 +47,56 @@ FidoRequestHandlerBase::~FidoRequestHandlerBase() = default; void FidoRequestHandlerBase::CancelOngoingTasks( base::StringPiece exclude_device_id) { - for (auto task_it = ongoing_tasks_.begin(); - task_it != ongoing_tasks_.end();) { + for (auto task_it = active_authenticators_.begin(); + task_it != active_authenticators_.end();) { DCHECK(!task_it->first.empty()); if (task_it->first != exclude_device_id) { DCHECK(task_it->second); - task_it->second->CancelTask(); - task_it = ongoing_tasks_.erase(task_it); + task_it->second->Cancel(); + task_it = active_authenticators_.erase(task_it); } else { ++task_it; } } } +void FidoRequestHandlerBase::Start() { + for (const auto& discovery : discoveries_) { + discovery->Start(); + } + if (use_platform_authenticator_) { + MaybeAddPlatformAuthenticator(); + } +} + +void FidoRequestHandlerBase::MaybeAddPlatformAuthenticator() { +#if defined(OS_MACOSX) + if (__builtin_available(macOS 10.12.2, *)) { + auto authenticator = fido::mac::TouchIdAuthenticator::CreateIfAvailable(); + if (!authenticator) { + return; + } + AddAuthenticator(std::move(authenticator)); + } +#endif +} + void FidoRequestHandlerBase::DiscoveryStarted(FidoDiscovery* discovery, bool success) {} void FidoRequestHandlerBase::DeviceAdded(FidoDiscovery* discovery, FidoDevice* device) { - DCHECK(!base::ContainsKey(ongoing_tasks(), device->GetId())); + DCHECK(!base::ContainsKey(active_authenticators(), device->GetId())); // All devices are initially assumed to support CTAP protocol and thus // AuthenticatorGetInfo command is sent to all connected devices. If device // errors out, then it is assumed to support U2F protocol. device->set_supported_protocol(ProtocolVersion::kCtap); - ongoing_tasks_.emplace(device->GetId(), CreateTaskForNewDevice(device)); + AddAuthenticator(CreateAuthenticatorFromDevice(device)); +} + +std::unique_ptr<FidoDeviceAuthenticator> +FidoRequestHandlerBase::CreateAuthenticatorFromDevice(FidoDevice* device) { + return std::make_unique<FidoDeviceAuthenticator>(device); } void FidoRequestHandlerBase::DeviceRemoved(FidoDiscovery* discovery, @@ -66,7 +106,16 @@ void FidoRequestHandlerBase::DeviceRemoved(FidoDiscovery* discovery, // ongoing_tasks_.erase() will have no effect for the devices that have been // already removed due to processing error or due to invocation of // CancelOngoingTasks(). - ongoing_tasks_.erase(device->GetId()); + active_authenticators_.erase(device->GetId()); +} + +void FidoRequestHandlerBase::AddAuthenticator( + std::unique_ptr<FidoAuthenticator> authenticator) { + DCHECK(!base::ContainsKey(active_authenticators(), authenticator->GetId())); + FidoAuthenticator* authenticator_ptr = authenticator.get(); + active_authenticators_.emplace(authenticator->GetId(), + std::move(authenticator)); + DispatchRequest(authenticator_ptr); } } // namespace device diff --git a/chromium/device/fido/fido_request_handler_base.h b/chromium/device/fido/fido_request_handler_base.h index 8c010f01c78..65e494a7225 100644 --- a/chromium/device/fido/fido_request_handler_base.h +++ b/chromium/device/fido/fido_request_handler_base.h @@ -15,6 +15,7 @@ #include "base/containers/flat_set.h" #include "base/macros.h" #include "base/strings/string_piece_forward.h" +#include "device/fido/fido_device_authenticator.h" #include "device/fido/fido_discovery.h" #include "device/fido/fido_transport_protocol.h" @@ -24,6 +25,7 @@ class Connector; namespace device { +class FidoAuthenticator; class FidoDevice; class FidoTask; @@ -35,7 +37,8 @@ class FidoTask; class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase : public FidoDiscovery::Observer { public: - using TaskMap = std::map<std::string, std::unique_ptr<FidoTask>, std::less<>>; + using AuthenticatorMap = + std::map<std::string, std::unique_ptr<FidoAuthenticator>, std::less<>>; FidoRequestHandlerBase( service_manager::Connector* connector, @@ -55,9 +58,21 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase void CancelOngoingTasks(base::StringPiece exclude_device_id = nullptr); protected: - virtual std::unique_ptr<FidoTask> CreateTaskForNewDevice(FidoDevice*) = 0; + // Subclasses implement this method to dispatch their request onto the given + // FidoAuthenticator. The FidoAuthenticator is owned by this + // FidoRequestHandler and stored in active_authenticators(). + virtual void DispatchRequest(FidoAuthenticator*) = 0; - TaskMap& ongoing_tasks() { return ongoing_tasks_; } + void Start(); + + // Testing seam to allow unit tests to inject a fake authenticator. + virtual std::unique_ptr<FidoDeviceAuthenticator> + CreateAuthenticatorFromDevice(FidoDevice* device); + + AuthenticatorMap& active_authenticators() { return active_authenticators_; } + std::vector<std::unique_ptr<FidoDiscovery>>& discoveries() { + return discoveries_; + } private: // FidoDiscovery::Observer @@ -65,9 +80,18 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase void DeviceAdded(FidoDiscovery* discovery, FidoDevice* device) final; void DeviceRemoved(FidoDiscovery* discovery, FidoDevice* device) final; - TaskMap ongoing_tasks_; + void AddAuthenticator(std::unique_ptr<FidoAuthenticator> authenticator); + + void MaybeAddPlatformAuthenticator(); + + AuthenticatorMap active_authenticators_; std::vector<std::unique_ptr<FidoDiscovery>> discoveries_; + // If set to true at any point before calling Start(), the request handler + // will try to create a platform authenticator to handle the request + // (currently only TouchIdAuthenticator on macOS). + bool use_platform_authenticator_ = false; + DISALLOW_COPY_AND_ASSIGN(FidoRequestHandlerBase); }; diff --git a/chromium/device/fido/fido_request_handler_unittest.cc b/chromium/device/fido/fido_request_handler_unittest.cc index f96a7bfe281..2d5dce3822b 100644 --- a/chromium/device/fido/fido_request_handler_unittest.cc +++ b/chromium/device/fido/fido_request_handler_unittest.cc @@ -6,6 +6,7 @@ #include <utility> #include <vector> +#include "base/bind.h" #include "base/numerics/safe_conversions.h" #include "base/test/scoped_task_environment.h" #include "device/fido/fake_fido_discovery.h" @@ -83,6 +84,16 @@ class FakeFidoTask : public FidoTask { base::WeakPtrFactory<FakeFidoTask> weak_factory_; }; +class FakeFidoAuthenticator : public FidoDeviceAuthenticator { + public: + FakeFidoAuthenticator(FidoDevice* device) : FidoDeviceAuthenticator(device) {} + + void RunFakeTask(FakeTaskCallback callback) { + SetTaskForTesting( + std::make_unique<FakeFidoTask>(device(), std::move(callback))); + } +}; + class FakeFidoRequestHandler : public FidoRequestHandler<std::vector<uint8_t>> { public: FakeFidoRequestHandler(const base::flat_set<FidoTransportProtocol>& protocols, @@ -90,18 +101,24 @@ class FakeFidoRequestHandler : public FidoRequestHandler<std::vector<uint8_t>> { : FidoRequestHandler(nullptr /* connector */, protocols, std::move(callback)), - weak_factory_(this) {} + weak_factory_(this) { + Start(); + } ~FakeFidoRequestHandler() override = default; - std::unique_ptr<FidoTask> CreateTaskForNewDevice( + void DispatchRequest(FidoAuthenticator* authenticator) override { + static_cast<FakeFidoAuthenticator*>(authenticator) + ->RunFakeTask( + base::BindOnce(&FakeFidoRequestHandler::OnAuthenticatorResponse, + weak_factory_.GetWeakPtr(), authenticator)); + } + + std::unique_ptr<FidoDeviceAuthenticator> CreateAuthenticatorFromDevice( FidoDevice* device) override { - return std::make_unique<FakeFidoTask>( - device, base::BindOnce(&FakeFidoRequestHandler::OnDeviceResponse, - weak_factory_.GetWeakPtr(), device)); + return std::make_unique<FakeFidoAuthenticator>(device); } private: - FakeHandlerCallback callback_; base::WeakPtrFactory<FakeFidoRequestHandler> weak_factory_; }; diff --git a/chromium/device/fido/fido_test_data.h b/chromium/device/fido/fido_test_data.h index 88838d45826..ba23b8ed792 100644 --- a/chromium/device/fido/fido_test_data.h +++ b/chromium/device/fido/fido_test_data.h @@ -28,6 +28,13 @@ constexpr uint8_t kApplicationParameter[] = { 0x7B, 0xCF, 0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE, }; +constexpr uint8_t kClientDataHash[] = { + 0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, 0x42, + 0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05, + 0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41}; + +constexpr uint8_t kUserId[] = {0x10, 0x98, 0x23, 0x72, 0x35, 0x40, 0x98, 0x72}; + constexpr char kRelyingPartyId[] = "example.com"; constexpr uint8_t kU2fRegisterCommandApduWithIndividualAttestation[] = { @@ -299,6 +306,8 @@ constexpr uint8_t kU2fFakeRegisterCommand[] = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + // Maximum response length + 0x00, 0x00, }; // U2F response blob produced by a U2F registration request used in example 6 @@ -788,6 +797,326 @@ constexpr uint8_t kTestGetAssertionResponseWithIncorrectRpIdHash[] = { 0x30, 0x36, 0x59, 0xC9, 0xCD, 0x92, }; +// Below |kCtap2MakeCredentialCertificate|, |kCtap2MakeCredentialAuthData|, and +// |kCtap2MakeCredentialSignature| leverage example 4 of the CTAP spec. +// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html +constexpr uint8_t kCtap2MakeCredentialCertificate[] = { + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x09, 0x00, 0x85, 0x9b, 0x72, 0x6c, 0xb2, 0x4b, 0x4c, 0x29, + 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, + 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x0b, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, + 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, + 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, + 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x32, 0x30, 0x34, 0x31, + 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x31, 0x32, + 0x30, 0x32, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x30, 0x47, 0x31, + 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, + 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, + 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, + 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x59, + 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, + 0x04, 0xad, 0x11, 0xeb, 0x0e, 0x88, 0x52, 0xe5, 0x3a, 0xd5, 0xdf, 0xed, + 0x86, 0xb4, 0x1e, 0x61, 0x34, 0xa1, 0x8e, 0xc4, 0xe1, 0xaf, 0x8f, 0x22, + 0x1a, 0x3c, 0x7d, 0x6e, 0x63, 0x6c, 0x80, 0xea, 0x13, 0xc3, 0xd5, 0x04, + 0xff, 0x2e, 0x76, 0x21, 0x1b, 0xb4, 0x45, 0x25, 0xb1, 0x96, 0xc4, 0x4c, + 0xb4, 0x84, 0x99, 0x79, 0xcf, 0x6f, 0x89, 0x6e, 0xcd, 0x2b, 0xb8, 0x60, + 0xde, 0x1b, 0xf4, 0x37, 0x6b, 0xa3, 0x0d, 0x30, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0a, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, + 0x46, 0x02, 0x21, 0x00, 0xe9, 0xa3, 0x9f, 0x1b, 0x03, 0x19, 0x75, 0x25, + 0xf7, 0x37, 0x3e, 0x10, 0xce, 0x77, 0xe7, 0x80, 0x21, 0x73, 0x1b, 0x94, + 0xd0, 0xc0, 0x3f, 0x3f, 0xda, 0x1f, 0xd2, 0x2d, 0xb3, 0xd0, 0x30, 0xe7, + 0x02, 0x21, 0x00, 0xc4, 0xfa, 0xec, 0x34, 0x45, 0xa8, 0x20, 0xcf, 0x43, + 0x12, 0x9c, 0xdb, 0x00, 0xaa, 0xbe, 0xfd, 0x9a, 0xe2, 0xd8, 0x74, 0xf9, + 0xc5, 0xd3, 0x43, 0xcb, 0x2f, 0x11, 0x3d, 0xa2, 0x37, 0x23, 0xf3}; + +constexpr uint8_t kCtap2MakeCredentialAuthData[] = { + 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, + 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, + 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0x41, 0x00, 0x00, 0x00, + 0x0b, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, + 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, + 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa3, + 0x63, 0x61, 0x6c, 0x67, 0x65, 0x45, 0x53, 0x32, 0x35, 0x36, 0x61, 0x78, + 0x58, 0x20, 0xf7, 0xc4, 0xf4, 0xa6, 0xf1, 0xd7, 0x95, 0x38, 0xdf, 0xa4, + 0xc9, 0xac, 0x50, 0x84, 0x8d, 0xf7, 0x08, 0xbc, 0x1c, 0x99, 0xf5, 0xe6, + 0x0e, 0x51, 0xb4, 0x2a, 0x52, 0x1b, 0x35, 0xd3, 0xb6, 0x9a, 0x61, 0x79, + 0x58, 0x20, 0xde, 0x7b, 0x7d, 0x6c, 0xa5, 0x64, 0xe7, 0x0e, 0xa3, 0x21, + 0xa4, 0xd5, 0xd9, 0x6e, 0xa0, 0x0e, 0xf0, 0xe2, 0xdb, 0x89, 0xdd, 0x61, + 0xd4, 0x89, 0x4c, 0x15, 0xac, 0x58, 0x5b, 0xd2, 0x36, 0x84}; + +constexpr uint8_t kCtap2MakeCredentialSignature[] = { + 0x30, 0x45, 0x02, 0x20, 0x13, 0xf7, 0x3c, 0x5d, 0x9d, 0x53, 0x0e, 0x8c, + 0xc1, 0x5c, 0xc9, 0xbd, 0x96, 0xad, 0x58, 0x6d, 0x39, 0x36, 0x64, 0xe4, + 0x62, 0xd5, 0xf0, 0x56, 0x12, 0x35, 0xe6, 0x35, 0x0f, 0x2b, 0x72, 0x89, + 0x02, 0x21, 0x00, 0x90, 0x35, 0x7f, 0xf9, 0x10, 0xcc, 0xb5, 0x6a, 0xc5, + 0xb5, 0x96, 0x51, 0x19, 0x48, 0x58, 0x1c, 0x8f, 0xdd, 0xb4, 0xa2, 0xb7, + 0x99, 0x59, 0x94, 0x80, 0x78, 0xb0, 0x9f, 0x4b, 0xdc, 0x62, 0x29}; + +constexpr uint8_t kCtap2MakeCredentialCredentialId[] = { + 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, + 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, +}; + +constexpr uint8_t kNoneAttestationResponse[] = { + // map(3) + 0xa3, + // Format - text(3) + 0x63, + // "fmt" + 0x66, 0x6D, 0x74, + // text(4) + 0x64, + // "none" + 0x6E, 0x6F, 0x6E, 0x65, + // Attestation statement + 0x67, + // text(7) - "attStmt" + 0x61, 0x74, 0x74, 0x53, 0x74, 0x6D, 0x74, + // Empty CBOR Map + 0xa0, + // Authenticator data + 0x68, + // text(8) - "authData" + 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, + // bytes(154) + 0x58, 0x9a, + // byte data + 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, + 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, + 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0x41, 0x00, 0x00, 0x00, + 0x0b, + // Replaced device AAGUID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Credential information + 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, + 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa3, 0x63, 0x61, 0x6c, 0x67, 0x65, + 0x45, 0x53, 0x32, 0x35, 0x36, 0x61, 0x78, 0x58, 0x20, 0xf7, 0xc4, 0xf4, + 0xa6, 0xf1, 0xd7, 0x95, 0x38, 0xdf, 0xa4, 0xc9, 0xac, 0x50, 0x84, 0x8d, + 0xf7, 0x08, 0xbc, 0x1c, 0x99, 0xf5, 0xe6, 0x0e, 0x51, 0xb4, 0x2a, 0x52, + 0x1b, 0x35, 0xd3, 0xb6, 0x9a, 0x61, 0x79, 0x58, 0x20, 0xde, 0x7b, 0x7d, + 0x6c, 0xa5, 0x64, 0xe7, 0x0e, 0xa3, 0x21, 0xa4, 0xd5, 0xd9, 0x6e, 0xa0, + 0x0e, 0xf0, 0xe2, 0xdb, 0x89, 0xdd, 0x61, 0xd4, 0x89, 0x4c, 0x15, 0xac, + 0x58, 0x5b, 0xd2, 0x36, 0x84, +}; + +const uint8_t kDeviceMakeCredentialResponse[] = { + // Success response code + 0x00, + // map(3) + 0xa3, + // unsigned(1) + 0x01, + // text(6) + 0x66, + // "packed" + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, + // unsigned(2) + 0x02, + // bytes(154) + 0x58, 0x9a, + // auth data + 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, + 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, + 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0x41, 0x00, 0x00, 0x00, + 0x0b, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, + 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, + 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa3, + 0x63, 0x61, 0x6c, 0x67, 0x65, 0x45, 0x53, 0x32, 0x35, 0x36, 0x61, 0x78, + 0x58, 0x20, 0xf7, 0xc4, 0xf4, 0xa6, 0xf1, 0xd7, 0x95, 0x38, 0xdf, 0xa4, + 0xc9, 0xac, 0x50, 0x84, 0x8d, 0xf7, 0x08, 0xbc, 0x1c, 0x99, 0xf5, 0xe6, + 0x0e, 0x51, 0xb4, 0x2a, 0x52, 0x1b, 0x35, 0xd3, 0xb6, 0x9a, 0x61, 0x79, + 0x58, 0x20, 0xde, 0x7b, 0x7d, 0x6c, 0xa5, 0x64, 0xe7, 0x0e, 0xa3, 0x21, + 0xa4, 0xd5, 0xd9, 0x6e, 0xa0, 0x0e, 0xf0, 0xe2, 0xdb, 0x89, 0xdd, 0x61, + 0xd4, 0x89, 0x4c, 0x15, 0xac, 0x58, 0x5b, 0xd2, 0x36, 0x84, + // unsigned(3) + 0x03, + // map(3) + 0xa3, + // text(3) + 0x63, + // "alg" + 0x61, 0x6c, 0x67, + // 7 + 0x07, + // text(3) + 0x63, + // "sig" + 0x73, 0x69, 0x67, + // bytes(71) + 0x58, 0x47, + // signature + 0x30, 0x45, 0x02, 0x20, 0x13, 0xf7, 0x3c, 0x5d, 0x9d, 0x53, 0x0e, 0x8c, + 0xc1, 0x5c, 0xc9, 0xbd, 0x96, 0xad, 0x58, 0x6d, 0x39, 0x36, 0x64, 0xe4, + 0x62, 0xd5, 0xf0, 0x56, 0x12, 0x35, 0xe6, 0x35, 0x0f, 0x2b, 0x72, 0x89, + 0x02, 0x21, 0x00, 0x90, 0x35, 0x7f, 0xf9, 0x10, 0xcc, 0xb5, 0x6a, 0xc5, + 0xb5, 0x96, 0x51, 0x19, 0x48, 0x58, 0x1c, 0x8f, 0xdd, 0xb4, 0xa2, 0xb7, + 0x99, 0x59, 0x94, 0x80, 0x78, 0xb0, 0x9f, 0x4b, 0xdc, 0x62, 0x29, + // text(3) + 0x63, + // "x5c" + 0x78, 0x35, 0x63, + // array(1) + 0x81, + // bytes(407) + 0x59, 0x01, 0x97, + // certificate + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x09, 0x00, 0x85, 0x9b, 0x72, 0x6c, 0xb2, 0x4b, 0x4c, 0x29, + 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, + 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x0b, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, + 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, + 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, + 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x32, 0x30, 0x34, 0x31, + 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x31, 0x32, + 0x30, 0x32, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x30, 0x47, 0x31, + 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, + 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, + 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, + 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x59, + 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, + 0x04, 0xad, 0x11, 0xeb, 0x0e, 0x88, 0x52, 0xe5, 0x3a, 0xd5, 0xdf, 0xed, + 0x86, 0xb4, 0x1e, 0x61, 0x34, 0xa1, 0x8e, 0xc4, 0xe1, 0xaf, 0x8f, 0x22, + 0x1a, 0x3c, 0x7d, 0x6e, 0x63, 0x6c, 0x80, 0xea, 0x13, 0xc3, 0xd5, 0x04, + 0xff, 0x2e, 0x76, 0x21, 0x1b, 0xb4, 0x45, 0x25, 0xb1, 0x96, 0xc4, 0x4c, + 0xb4, 0x84, 0x99, 0x79, 0xcf, 0x6f, 0x89, 0x6e, 0xcd, 0x2b, 0xb8, 0x60, + 0xde, 0x1b, 0xf4, 0x37, 0x6b, 0xa3, 0x0d, 0x30, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0a, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, + 0x46, 0x02, 0x21, 0x00, 0xe9, 0xa3, 0x9f, 0x1b, 0x03, 0x19, 0x75, 0x25, + 0xf7, 0x37, 0x3e, 0x10, 0xce, 0x77, 0xe7, 0x80, 0x21, 0x73, 0x1b, 0x94, + 0xd0, 0xc0, 0x3f, 0x3f, 0xda, 0x1f, 0xd2, 0x2d, 0xb3, 0xd0, 0x30, 0xe7, + 0x02, 0x21, 0x00, 0xc4, 0xfa, 0xec, 0x34, 0x45, 0xa8, 0x20, 0xcf, 0x43, + 0x12, 0x9c, 0xdb, 0x00, 0xaa, 0xbe, 0xfd, 0x9a, 0xe2, 0xd8, 0x74, 0xf9, + 0xc5, 0xd3, 0x43, 0xcb, 0x2f, 0x11, 0x3d, 0xa2, 0x37, 0x23, 0xf3}; + +constexpr uint8_t kCtap2GetAssertionAuthData[] = { + 0x62, 0x5d, 0xda, 0xdf, 0x74, 0x3f, 0x57, 0x27, 0xe6, 0x6b, + 0xba, 0x8c, 0x2e, 0x38, 0x79, 0x22, 0xd1, 0xaf, 0x43, 0xc5, + 0x03, 0xd9, 0x11, 0x4a, 0x8f, 0xba, 0x10, 0x4d, 0x84, 0xd0, + 0x2b, 0xfa, 0x01, 0x00, 0x00, 0x00, 0x11}; + +constexpr uint8_t kCtap2GetAssertionSignature[] = { + 0x30, 0x45, 0x02, 0x20, 0x4a, 0x5a, 0x9d, 0xd3, 0x92, 0x98, 0x14, 0x9d, + 0x90, 0x47, 0x69, 0xb5, 0x1a, 0x45, 0x14, 0x33, 0x00, 0x6f, 0x18, 0x2a, + 0x34, 0xfb, 0xdf, 0x66, 0xde, 0x5f, 0xc7, 0x17, 0xd7, 0x5f, 0xb3, 0x50, + 0x02, 0x21, 0x00, 0xa4, 0x6b, 0x8e, 0xa3, 0xc3, 0xb9, 0x33, 0x82, 0x1c, + 0x6e, 0x7f, 0x5e, 0xf9, 0xda, 0xae, 0x94, 0xab, 0x47, 0xf1, 0x8d, 0xb4, + 0x74, 0xc7, 0x47, 0x90, 0xea, 0xab, 0xb1, 0x44, 0x11, 0xe7, 0xa0, +}; + +constexpr uint8_t kDeviceGetAssertionResponse[] = { + // Success response code + 0x00, + // map(5) + 0xa5, + // unsigned(1) - Credential + 0x01, + // map(2) + 0xa2, + // text(2) + 0x62, + // "id" + 0x69, 0x64, + // bytes(64) + 0x58, 0x40, + // credential id + 0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43, 0x94, 0x2f, + 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b, 0x3d, 0xf8, + 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0, 0x34, 0x85, 0x8a, + 0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98, 0x08, 0xd9, 0x4f, 0xcb, + 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77, 0xaf, 0x0a, 0xdc, 0xc3, 0x58, + 0x52, 0xea, 0x6b, 0x9e, + // text(4) + 0x64, + // "type" + 0x74, 0x79, 0x70, 0x65, + // text(10) + 0x6a, + // "public-key" + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, + // unsigned(2) - Auth data + 0x02, + // bytes(37) + 0x58, 0x25, + // auth data + 0x62, 0x5d, 0xda, 0xdf, 0x74, 0x3f, 0x57, 0x27, 0xe6, 0x6b, 0xba, 0x8c, + 0x2e, 0x38, 0x79, 0x22, 0xd1, 0xaf, 0x43, 0xc5, 0x03, 0xd9, 0x11, 0x4a, + 0x8f, 0xba, 0x10, 0x4d, 0x84, 0xd0, 0x2b, 0xfa, 0x01, 0x00, 0x00, 0x00, + 0x11, + // unsigned(3) - signature + 0x03, + // bytes(71) + 0x58, 0x47, + // signature + 0x30, 0x45, 0x02, 0x20, 0x4a, 0x5a, 0x9d, 0xd3, 0x92, 0x98, 0x14, 0x9d, + 0x90, 0x47, 0x69, 0xb5, 0x1a, 0x45, 0x14, 0x33, 0x00, 0x6f, 0x18, 0x2a, + 0x34, 0xfb, 0xdf, 0x66, 0xde, 0x5f, 0xc7, 0x17, 0xd7, 0x5f, 0xb3, 0x50, + 0x02, 0x21, 0x00, 0xa4, 0x6b, 0x8e, 0xa3, 0xc3, 0xb9, 0x33, 0x82, 0x1c, + 0x6e, 0x7f, 0x5e, 0xf9, 0xda, 0xae, 0x94, 0xab, 0x47, 0xf1, 0x8d, 0xb4, + 0x74, 0xc7, 0x47, 0x90, 0xea, 0xab, 0xb1, 0x44, 0x11, 0xe7, 0xa0, + // unsigned(4) - publicKeyCredentialUserEntity + 0x04, + // map(4) + 0xa4, + // text(2) + 0x62, + // "id" + 0x69, 0x64, + // bytes(32) - user id + 0x58, 0x20, + // user id + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, + 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, + // text(4) + 0x64, + // "icon" + 0x69, 0x63, 0x6f, 0x6e, + // text(40) + 0x78, 0x28, + // "https://pics.acme.com/00/p/aBjjjpqPb.png" + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x70, 0x69, 0x63, 0x73, + 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x30, + 0x2f, 0x70, 0x2f, 0x61, 0x42, 0x6a, 0x6a, 0x6a, 0x70, 0x71, 0x50, 0x62, + 0x2e, 0x70, 0x6e, 0x67, + // text(4) + 0x64, + // "name" + 0x6e, 0x61, 0x6d, 0x65, + // text(22) + 0x76, + // "johnpsmith@example.com" + 0x6a, 0x6f, 0x68, 0x6e, 0x70, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x40, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + // text(11) + 0x6b, + // "displayName" + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, + // text(13) + 0x6d, + // "John P. Smith" + 0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x50, 0x2e, 0x20, 0x53, 0x6d, 0x69, 0x74, + 0x68, + // unsigned(5) - number of credentials + 0x05, + // 1 + 0x01, +}; + } // namespace test_data } // namespace device diff --git a/chromium/device/fido/fido_transport_protocol.h b/chromium/device/fido/fido_transport_protocol.h index 87bae5792e0..71b09d95902 100644 --- a/chromium/device/fido/fido_transport_protocol.h +++ b/chromium/device/fido/fido_transport_protocol.h @@ -13,6 +13,8 @@ enum class FidoTransportProtocol { kUsbHumanInterfaceDevice, kNearFieldCommunication, kBluetoothLowEnergy, + kCloudAssistedBluetoothLowEnergy, + kInternal, }; } // namespace device diff --git a/chromium/device/fido/get_assertion_handler_unittest.cc b/chromium/device/fido/get_assertion_handler_unittest.cc index e8a2d7b63cc..7dba2d677be 100644 --- a/chromium/device/fido/get_assertion_handler_unittest.cc +++ b/chromium/device/fido/get_assertion_handler_unittest.cc @@ -10,12 +10,12 @@ #include "device/fido/ctap_get_assertion_request.h" #include "device/fido/fake_fido_discovery.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/fido_transport_protocol.h" #include "device/fido/get_assertion_request_handler.h" #include "device/fido/mock_fido_device.h" #include "device/fido/test_callback_receiver.h" -#include "device/fido/u2f_parsing_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -42,10 +42,10 @@ class FidoGetAssertionHandlerTest : public ::testing::Test { ForgeNextHidDiscovery(); CtapGetAssertionRequest request_param( - kRpId, u2f_parsing_utils::Materialize(kClientDataHash)); + kRpId, fido_parsing_utils::Materialize(kClientDataHash)); request_param.SetAllowList( - {{to_string(CredentialType::kPublicKey), - u2f_parsing_utils::Materialize( + {{CredentialType::kPublicKey, + fido_parsing_utils::Materialize( test_data::kTestGetAssertionCredentialId)}}); return std::make_unique<GetAssertionRequestHandler>( diff --git a/chromium/device/fido/get_assertion_request_handler.cc b/chromium/device/fido/get_assertion_request_handler.cc index 119a417b70b..722deacae64 100644 --- a/chromium/device/fido/get_assertion_request_handler.cc +++ b/chromium/device/fido/get_assertion_request_handler.cc @@ -8,7 +8,8 @@ #include "base/bind.h" #include "device/fido/authenticator_get_assertion_response.h" -#include "device/fido/fido_device.h" +#include "device/fido/fido_authenticator.h" +#include "device/fido/fido_cable_discovery.h" #include "device/fido/get_assertion_task.h" namespace device { @@ -20,16 +21,27 @@ GetAssertionRequestHandler::GetAssertionRequestHandler( SignResponseCallback completion_callback) : FidoRequestHandler(connector, protocols, std::move(completion_callback)), request_(std::move(request)), - weak_factory_(this) {} + weak_factory_(this) { + if (base::ContainsKey( + protocols, FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy) && + request_.cable_extension()) { + auto discovery = + std::make_unique<FidoCableDiscovery>(*request_.cable_extension()); + discovery->set_observer(this); + discoveries().push_back(std::move(discovery)); + } + + Start(); +} GetAssertionRequestHandler::~GetAssertionRequestHandler() = default; -std::unique_ptr<FidoTask> GetAssertionRequestHandler::CreateTaskForNewDevice( - FidoDevice* device) { - return std::make_unique<GetAssertionTask>( - device, request_, - base::BindOnce(&GetAssertionRequestHandler::OnDeviceResponse, - weak_factory_.GetWeakPtr(), device)); +void GetAssertionRequestHandler::DispatchRequest( + FidoAuthenticator* authenticator) { + authenticator->GetAssertion( + request_, + base::BindOnce(&GetAssertionRequestHandler::OnAuthenticatorResponse, + weak_factory_.GetWeakPtr(), authenticator)); } } // namespace device diff --git a/chromium/device/fido/get_assertion_request_handler.h b/chromium/device/fido/get_assertion_request_handler.h index 191a6ad6ce2..4f318334895 100644 --- a/chromium/device/fido/get_assertion_request_handler.h +++ b/chromium/device/fido/get_assertion_request_handler.h @@ -22,13 +22,12 @@ class Connector; namespace device { -class FidoDevice; -class FidoTask; +class FidoAuthenticator; class AuthenticatorGetAssertionResponse; -using SignResponseCallback = base::OnceCallback<void( - FidoReturnCode status_code, - base::Optional<AuthenticatorGetAssertionResponse> response_data)>; +using SignResponseCallback = + base::OnceCallback<void(FidoReturnCode, + base::Optional<AuthenticatorGetAssertionResponse>)>; class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler : public FidoRequestHandler<AuthenticatorGetAssertionResponse> { @@ -42,7 +41,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler private: // FidoRequestHandlerBase: - std::unique_ptr<FidoTask> CreateTaskForNewDevice(FidoDevice* device) override; + void DispatchRequest(FidoAuthenticator* authenticator) override; CtapGetAssertionRequest request_; base::WeakPtrFactory<GetAssertionRequestHandler> weak_factory_; diff --git a/chromium/device/fido/get_assertion_task.h b/chromium/device/fido/get_assertion_task.h index 22ef34ed8d8..3f90a21b8e7 100644 --- a/chromium/device/fido/get_assertion_task.h +++ b/chromium/device/fido/get_assertion_task.h @@ -27,8 +27,8 @@ class AuthenticatorGetAssertionResponse; class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionTask : public FidoTask { public: using GetAssertionTaskCallback = base::OnceCallback<void( - CtapDeviceResponseCode return_code, - base::Optional<AuthenticatorGetAssertionResponse> response_data)>; + CtapDeviceResponseCode, + base::Optional<AuthenticatorGetAssertionResponse>)>; GetAssertionTask(FidoDevice* device, CtapGetAssertionRequest request, diff --git a/chromium/device/fido/get_assertion_task_unittest.cc b/chromium/device/fido/get_assertion_task_unittest.cc index 311f0b89c24..d126d035b7c 100644 --- a/chromium/device/fido/get_assertion_task_unittest.cc +++ b/chromium/device/fido/get_assertion_task_unittest.cc @@ -14,10 +14,10 @@ #include "device/fido/authenticator_get_assertion_response.h" #include "device/fido/ctap_get_assertion_request.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/mock_fido_device.h" #include "device/fido/test_callback_receiver.h" -#include "device/fido/u2f_parsing_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -58,9 +58,9 @@ TEST_F(FidoGetAssertionTaskTest, TestGetAssertionSuccess) { test_data::kTestGetAssertionResponse); CtapGetAssertionRequest request_param( - kRpId, u2f_parsing_utils::Materialize(kClientDataHash)); - request_param.SetAllowList({{to_string(CredentialType::kPublicKey), - u2f_parsing_utils::Materialize( + kRpId, fido_parsing_utils::Materialize(kClientDataHash)); + request_param.SetAllowList({{CredentialType::kPublicKey, + fido_parsing_utils::Materialize( test_data::kTestGetAssertionCredentialId)}}); auto task = std::make_unique<GetAssertionTask>( @@ -90,7 +90,7 @@ TEST_F(FidoGetAssertionTaskTest, TestGetAssertionInvalidCredential) { auto task = std::make_unique<GetAssertionTask>( device.get(), CtapGetAssertionRequest(kRpId, - u2f_parsing_utils::Materialize(kClientDataHash)), + fido_parsing_utils::Materialize(kClientDataHash)), get_assertion_callback_receiver().callback()); get_assertion_callback_receiver().WaitForCallback(); @@ -116,7 +116,7 @@ TEST_F(FidoGetAssertionTaskTest, TestGetAsserionIncorrectUserEntity) { auto task = std::make_unique<GetAssertionTask>( device.get(), CtapGetAssertionRequest(kRpId, - u2f_parsing_utils::Materialize(kClientDataHash)), + fido_parsing_utils::Materialize(kClientDataHash)), get_assertion_callback_receiver().callback()); get_assertion_callback_receiver().WaitForCallback(); @@ -140,7 +140,7 @@ TEST_F(FidoGetAssertionTaskTest, TestGetAsserionIncorrectRpIdHash) { auto task = std::make_unique<GetAssertionTask>( device.get(), CtapGetAssertionRequest(kRpId, - u2f_parsing_utils::Materialize(kClientDataHash)), + fido_parsing_utils::Materialize(kClientDataHash)), get_assertion_callback_receiver().callback()); get_assertion_callback_receiver().WaitForCallback(); @@ -163,7 +163,7 @@ TEST_F(FidoGetAssertionTaskTest, TestIncorrectGetAssertionResponse) { auto task = std::make_unique<GetAssertionTask>( device.get(), CtapGetAssertionRequest(kRpId, - u2f_parsing_utils::Materialize(kClientDataHash)), + fido_parsing_utils::Materialize(kClientDataHash)), get_assertion_callback_receiver().callback()); get_assertion_callback_receiver().WaitForCallback(); @@ -182,7 +182,7 @@ TEST_F(FidoGetAssertionTaskTest, TestIncompatibleUserVerificationSetting) { test_data::kTestGetInfoResponseWithoutUvSupport); auto request = CtapGetAssertionRequest( - kRpId, u2f_parsing_utils::Materialize(kClientDataHash)); + kRpId, fido_parsing_utils::Materialize(kClientDataHash)); request.SetUserVerification(UserVerificationRequirement::kRequired); auto task = std::make_unique<GetAssertionTask>( diff --git a/chromium/device/fido/mac/authenticator.h b/chromium/device/fido/mac/authenticator.h new file mode 100644 index 00000000000..bedc1e08373 --- /dev/null +++ b/chromium/device/fido/mac/authenticator.h @@ -0,0 +1,68 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_MAC_AUTHENTICATOR_H_ +#define DEVICE_FIDO_MAC_AUTHENTICATOR_H_ + +#include "base/mac/availability.h" +#include "base/macros.h" +#include "base/strings/string_piece_forward.h" +#include "device/fido/fido_authenticator.h" +#include "device/fido/mac/operation.h" + +namespace device { +namespace fido { +namespace mac { + +class API_AVAILABLE(macosx(10.12.2)) TouchIdAuthenticator + : public FidoAuthenticator { + public: + // IsAvailable returns true iff Touch ID is enabled and enrolled on the + // current device. + static bool IsAvailable(); + + // CreateIfAvailable returns a TouchIdAuthenticator if IsAvailable() returns + // true and nullptr otherwise. + static std::unique_ptr<TouchIdAuthenticator> CreateIfAvailable(); + + ~TouchIdAuthenticator() override; + + // TouchIdAuthenticator + void MakeCredential( + AuthenticatorSelectionCriteria authenticator_selection_criteria, + CtapMakeCredentialRequest request, + MakeCredentialCallback callback) override; + void GetAssertion(CtapGetAssertionRequest request, + GetAssertionCallback callback) override; + void Cancel() override; + + std::string GetId() const override; + + private: + TouchIdAuthenticator(); + + // The profile ID identifies the user profile from which the request + // originates. It is used to scope credentials to the profile under which they + // were created. + base::StringPiece GetOrInitializeProfileId(); + + // The keychain access group is a string value related to the Apple developer + // ID under which the binary gets signed that the Keychain Services API use + // for access control. See + // https://developer.apple.com/documentation/security/ksecattraccessgroup?language=objc. + base::StringPiece keychain_access_group() { + return "EQHXZ8M8AV.com.google.chrome.webauthn"; + } + + std::unique_ptr<Operation> operation_; + + private: + DISALLOW_COPY_AND_ASSIGN(TouchIdAuthenticator); +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_AUTHENTICATOR_H_ diff --git a/chromium/device/fido/mac/authenticator.mm b/chromium/device/fido/mac/authenticator.mm new file mode 100644 index 00000000000..351e3e94c49 --- /dev/null +++ b/chromium/device/fido/mac/authenticator.mm @@ -0,0 +1,84 @@ +// Copyright (c) 2018 The Chromium 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 "device/fido/mac/authenticator.h" + +#import <LocalAuthentication/LocalAuthentication.h> + +#include "base/memory/ptr_util.h" +#include "base/optional.h" +#include "base/strings/string_piece.h" +#include "device/fido/authenticator_selection_criteria.h" +#include "device/fido/ctap_get_assertion_request.h" +#include "device/fido/ctap_make_credential_request.h" +#include "device/fido/mac/get_assertion_operation.h" +#include "device/fido/mac/make_credential_operation.h" +#include "device/fido/mac/util.h" + +namespace device { +namespace fido { +namespace mac { + +// static +// NOTE(martinkr): This is currently only called from |CreateIfAvailable| but +// will also be needed for the implementation of +// IsUserVerifyingPlatformAuthenticatorAvailable() (see +// https://www.w3.org/TR/webauthn/#isUserVerifyingPlatformAuthenticatorAvailable). +bool TouchIdAuthenticator::IsAvailable() { + base::scoped_nsobject<LAContext> context([[LAContext alloc] init]); + return + [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics + error:nil]; +} + +// static +std::unique_ptr<TouchIdAuthenticator> +TouchIdAuthenticator::CreateIfAvailable() { + return IsAvailable() ? base::WrapUnique(new TouchIdAuthenticator()) : nullptr; +} + +TouchIdAuthenticator::~TouchIdAuthenticator() = default; + +void TouchIdAuthenticator::MakeCredential( + AuthenticatorSelectionCriteria authenticator_selection_criteria, + CtapMakeCredentialRequest request, + MakeCredentialCallback callback) { + DCHECK(!operation_); + operation_ = std::make_unique<MakeCredentialOperation>( + std::move(request), GetOrInitializeProfileId().as_string(), + keychain_access_group().as_string(), std::move(callback)); + operation_->Run(); +} + +void TouchIdAuthenticator::GetAssertion(CtapGetAssertionRequest request, + GetAssertionCallback callback) { + DCHECK(!operation_); + operation_ = std::make_unique<GetAssertionOperation>( + std::move(request), GetOrInitializeProfileId().as_string(), + keychain_access_group().as_string(), std::move(callback)); + operation_->Run(); +} + +void TouchIdAuthenticator::Cancel() { + // If there is an operation pending, delete it, which will clean up any + // pending callbacks, e.g. if the operation is waiting for a response from + // the Touch ID prompt. Note that we cannot cancel the actual prompt once it + // has been shown. + operation_.reset(); +} + +std::string TouchIdAuthenticator::GetId() const { + return "TouchIdAuthenticator"; +} + +TouchIdAuthenticator::TouchIdAuthenticator() = default; + +base::StringPiece TouchIdAuthenticator::GetOrInitializeProfileId() { + // TODO(martinkr): Implement. + return "TODO"; +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/get_assertion_operation.h b/chromium/device/fido/mac/get_assertion_operation.h new file mode 100644 index 00000000000..a36e2dd09d8 --- /dev/null +++ b/chromium/device/fido/mac/get_assertion_operation.h @@ -0,0 +1,52 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_MAC_GET_ASSERTION_OPERATION_H_ +#define DEVICE_FIDO_MAC_GET_ASSERTION_OPERATION_H_ + +#include "base/component_export.h" +#include "base/mac/availability.h" +#include "base/macros.h" +#include "device/fido/authenticator_get_assertion_response.h" +#include "device/fido/ctap_get_assertion_request.h" +#include "device/fido/mac/operation_base.h" + +namespace device { +namespace fido { +namespace mac { + +// GetAssertionOperation implements the authenticatorGetAssertion operation. The +// operation can be invoked via its |Run| method, which must only be called +// once. +// +// It prompts the user for consent via Touch ID, then looks up a key pair +// matching the request in the keychain and generates an assertion. +// +// For documentation on the keychain item metadata, see +// |MakeCredentialOperation|. +class API_AVAILABLE(macosx(10.12.2)) + COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionOperation + : public OperationBase<CtapGetAssertionRequest, + AuthenticatorGetAssertionResponse> { + public: + GetAssertionOperation(CtapGetAssertionRequest request, + std::string profile_id, + std::string keychain_access_group, + Callback callback); + ~GetAssertionOperation() override; + + void Run() override; + + private: + const std::string& RpId() const override; + void PromptTouchIdDone(bool success, NSError* err) override; + + DISALLOW_COPY_AND_ASSIGN(GetAssertionOperation); +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_GET_ASSERTION_OPERATION_H_ diff --git a/chromium/device/fido/mac/get_assertion_operation.mm b/chromium/device/fido/mac/get_assertion_operation.mm new file mode 100644 index 00000000000..a7a5572c0a0 --- /dev/null +++ b/chromium/device/fido/mac/get_assertion_operation.mm @@ -0,0 +1,157 @@ +// Copyright 2018 The Chromium 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 "device/fido/mac/get_assertion_operation.h" + +#include <set> +#include <string> + +#import <Foundation/Foundation.h> + +#include "base/mac/foundation_util.h" +#include "base/mac/mac_logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/string_number_conversions.h" +#include "device/fido/fido_constants.h" +#include "device/fido/mac/keychain.h" +#include "device/fido/mac/util.h" + +namespace device { +namespace fido { +namespace mac { + +using base::ScopedCFTypeRef; + +GetAssertionOperation::GetAssertionOperation(CtapGetAssertionRequest request, + std::string profile_id, + std::string keychain_access_group, + Callback callback) + : OperationBase<CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>( + std::move(request), + std::move(profile_id), + std::move(keychain_access_group), + std::move(callback)) {} +GetAssertionOperation::~GetAssertionOperation() = default; + +const std::string& GetAssertionOperation::RpId() const { + return request().rp_id(); +} + +void GetAssertionOperation::Run() { + // Prompt the user for consent. + // TODO(martinkr): Localize reason strings. + PromptTouchId("sign in to " + RpId()); +} + +void GetAssertionOperation::PromptTouchIdDone(bool success, NSError* err) { + if (!success) { + // err is autoreleased. + CHECK(err != nil); + DVLOG(1) << "Touch ID prompt failed: " << base::mac::NSToCFCast(err); + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt); + return; + } + + // Collect the credential ids from allowList. If allowList is absent, we will + // just pick the first available credential for the RP below. + std::set<std::vector<uint8_t>> allowed_credential_ids; + if (request().allow_list()) { + for (const PublicKeyCredentialDescriptor& desc : *request().allow_list()) { + if (desc.credential_type() != CredentialType::kPublicKey) { + continue; + } + allowed_credential_ids.insert(desc.id()); + } + } + + // Fetch credentials for RP from the request and current user profile. + ScopedCFTypeRef<CFArrayRef> keychain_items; + base::ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery(); + CFDictionarySetValue(query, kSecUseAuthenticationContext, + authentication_context()); + CFDictionarySetValue(query, kSecReturnRef, @YES); + CFDictionarySetValue(query, kSecReturnAttributes, @YES); + CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); + + OSStatus status = Keychain::GetInstance().ItemCopyMatching( + query, reinterpret_cast<CFTypeRef*>(keychain_items.InitializeInto())); + if (status == errSecItemNotFound) { + DVLOG(1) << "no credentials found for RP"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt); + return; + } + if (status != errSecSuccess) { + OSSTATUS_DLOG(ERROR, status) << "SecItemCopyMatching failed"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + SecKeyRef private_key = nil; // Owned by |keychain_items|. + std::vector<uint8_t> credential_id; + for (CFIndex i = 0; i < CFArrayGetCount(keychain_items); ++i) { + CFDictionaryRef attributes = base::mac::CFCast<CFDictionaryRef>( + CFArrayGetValueAtIndex(keychain_items, i)); + CFDataRef application_label = base::mac::GetValueFromDictionary<CFDataRef>( + attributes, kSecAttrApplicationLabel); + SecKeyRef key = + base::mac::GetValueFromDictionary<SecKeyRef>(attributes, kSecValueRef); + if (!application_label || !key) { + // Corrupted keychain? + DLOG(ERROR) << "could not find application label or key ref: " + << attributes; + continue; + } + std::vector<uint8_t> cid(CFDataGetBytePtr(application_label), + CFDataGetBytePtr(application_label) + + CFDataGetLength(application_label)); + if (allowed_credential_ids.empty() || + allowed_credential_ids.find(cid) != allowed_credential_ids.end()) { + private_key = key; + credential_id = std::move(cid); + break; + } + } + if (!private_key) { + DVLOG(1) << "no allowed credential found"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt); + return; + } + base::ScopedCFTypeRef<SecKeyRef> public_key( + Keychain::GetInstance().KeyCopyPublicKey(private_key)); + if (!public_key) { + DLOG(ERROR) << "failed to get public key for credential id " + << base::HexEncode(credential_id.data(), credential_id.size()); + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + + base::Optional<AuthenticatorData> authenticator_data = + MakeAuthenticatorData(RpId(), std::move(credential_id), public_key); + if (!authenticator_data) { + DLOG(ERROR) << "MakeAuthenticatorData failed"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + base::Optional<std::vector<uint8_t>> signature = GenerateSignature( + *authenticator_data, request().client_data_hash(), private_key); + if (!signature) { + DLOG(ERROR) << "GenerateSignature failed"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + std::move(callback()) + .Run(CtapDeviceResponseCode::kSuccess, + AuthenticatorGetAssertionResponse(std::move(*authenticator_data), + std::move(*signature))); +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/get_assertion_operation_unittest_mac.mm b/chromium/device/fido/mac/get_assertion_operation_unittest_mac.mm new file mode 100644 index 00000000000..d0bcc7fbcce --- /dev/null +++ b/chromium/device/fido/mac/get_assertion_operation_unittest_mac.mm @@ -0,0 +1,82 @@ +// Copyright 2018 The Chromium 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 "device/fido/mac/get_assertion_operation.h" + +#include <Foundation/Foundation.h> +#include <Security/Security.h> + +#include "base/strings/string_number_conversions.h" + +#include "base/test/scoped_task_environment.h" +#include "device/fido/mac/make_credential_operation.h" +#include "device/fido/test_callback_receiver.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { +namespace fido { +namespace mac { +namespace { + +using test::TestCallbackReceiver; + +const std::vector<uint8_t> kClientDataHash = {1, 2, 3, 4, 5}; +const std::string kRpId = "rp.example.com"; +const std::vector<uint8_t> kUserId = {10, 11, 12, 13, 14, 15}; +const char kKeychainAccessGroup[] = + "EQHXZ8M8AV.com.google.chrome.webauthn.test"; + +CtapGetAssertionRequest MakeTestRequest() { + return CtapGetAssertionRequest(kRpId, kClientDataHash); +} + +bool MakeCredential() API_AVAILABLE(macos(10.12.2)) { + TestCallbackReceiver<CtapDeviceResponseCode, + base::Optional<AuthenticatorMakeCredentialResponse>> + callback_receiver; + auto request = CtapMakeCredentialRequest( + kClientDataHash, PublicKeyCredentialRpEntity(kRpId), + PublicKeyCredentialUserEntity(kUserId), + PublicKeyCredentialParams( + {{PublicKeyCredentialParams:: + CredentialInfo() /* defaults to ES-256 */}})); + MakeCredentialOperation op(request, "test-profile", kKeychainAccessGroup, + callback_receiver.callback()); + + op.Run(); + callback_receiver.WaitForCallback(); + auto result = callback_receiver.TakeResult(); + CtapDeviceResponseCode error = std::get<0>(result); + auto opt_response = std::move(std::get<1>(result)); + return error == CtapDeviceResponseCode::kSuccess && opt_response; +} + +// For demo purposes only. This test does a Touch ID user prompt. It will fail +// on incompatible hardware and crash if not code signed or lacking the +// keychain-access-group entitlement. +TEST(GetAssertionOperationTest, DISABLED_TestRun) +API_AVAILABLE(macos(10.12.2)) { + base::test::ScopedTaskEnvironment scoped_task_environment; + ASSERT_TRUE(MakeCredential()); + + TestCallbackReceiver<CtapDeviceResponseCode, + base::Optional<AuthenticatorGetAssertionResponse>> + callback_receiver; + auto request = MakeTestRequest(); + GetAssertionOperation op(request, "test-profile", kKeychainAccessGroup, + callback_receiver.callback()); + + op.Run(); + callback_receiver.WaitForCallback(); + auto result = callback_receiver.TakeResult(); + CtapDeviceResponseCode error = std::get<0>(result); + EXPECT_EQ(CtapDeviceResponseCode::kSuccess, error); + auto opt_response = std::move(std::get<1>(result)); + ASSERT_TRUE(opt_response); +}; +} +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/keychain.h b/chromium/device/fido/mac/keychain.h new file mode 100644 index 00000000000..0b627798868 --- /dev/null +++ b/chromium/device/fido/mac/keychain.h @@ -0,0 +1,62 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_MAC_KEYCHAIN_H_ +#define DEVICE_FIDO_MAC_KEYCHAIN_H_ + +#import <Foundation/Foundation.h> +#import <Security/Security.h> + +#include "base/mac/scoped_cftyperef.h" +#include "base/macros.h" +#include "base/no_destructor.h" + +namespace device { +namespace fido { +namespace mac { + +// Keychain wraps some operations from the macOS Security framework to work with +// keys and keychain items. +// +// The Touch ID authenticator creates keychain items in the "iOS-style" +// keychain, which scopes item access based on the application-identifer or +// keychain-access-group entitlements, and therefore requires code signing with +// a real Apple developer ID. We therefore group these function here, so they +// can be mocked out in testing. +class API_AVAILABLE(macosx(10.12.2)) Keychain { + public: + static const Keychain& GetInstance(); + + // KeyCreateRandomKey wraps the |SecKeyCreateRandomKey| function. + virtual base::ScopedCFTypeRef<SecKeyRef> KeyCreateRandomKey( + CFDictionaryRef params, + CFErrorRef* error) const; + // KeyCreateSignature wraps the |SecKeyCreateSignature| function. + virtual base::ScopedCFTypeRef<CFDataRef> KeyCreateSignature( + SecKeyRef key, + SecKeyAlgorithm algorithm, + CFDataRef data, + CFErrorRef* error) const; + // KeyCopyPublicKey wraps the |SecKeyCopyPublicKey| function. + virtual base::ScopedCFTypeRef<SecKeyRef> KeyCopyPublicKey( + SecKeyRef key) const; + + // ItemCopyMatching wraps the |SecItemCopyMatching| function. + virtual OSStatus ItemCopyMatching(CFDictionaryRef query, + CFTypeRef* result) const; + // ItemDelete wraps the |SecItemDelete| function. + virtual OSStatus ItemDelete(CFDictionaryRef query) const; + + private: + friend class base::NoDestructor<Keychain>; + Keychain(); + + DISALLOW_COPY_AND_ASSIGN(Keychain); +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_KEYCHAIN_H_ diff --git a/chromium/device/fido/mac/keychain.mm b/chromium/device/fido/mac/keychain.mm new file mode 100644 index 00000000000..692b7c8a776 --- /dev/null +++ b/chromium/device/fido/mac/keychain.mm @@ -0,0 +1,50 @@ +// Copyright 2018 The Chromium 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 "device/fido/mac/keychain.h" + +namespace device { +namespace fido { +namespace mac { + +// static +const Keychain& Keychain::GetInstance() { + static const base::NoDestructor<Keychain> k; + return *k; +} + +Keychain::Keychain() = default; + +base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCreateRandomKey( + CFDictionaryRef params, + CFErrorRef* error) const { + return base::ScopedCFTypeRef<SecKeyRef>(SecKeyCreateRandomKey(params, error)); +} + +base::ScopedCFTypeRef<CFDataRef> Keychain::KeyCreateSignature( + SecKeyRef key, + SecKeyAlgorithm algorithm, + CFDataRef data, + CFErrorRef* error) const { + return base::ScopedCFTypeRef<CFDataRef>( + SecKeyCreateSignature(key, algorithm, data, error)); +} + +base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCopyPublicKey( + SecKeyRef key) const { + return base::ScopedCFTypeRef<SecKeyRef>(SecKeyCopyPublicKey(key)); +} + +OSStatus Keychain::ItemCopyMatching(CFDictionaryRef query, + CFTypeRef* result) const { + return SecItemCopyMatching(query, result); +} + +OSStatus Keychain::ItemDelete(CFDictionaryRef query) const { + return SecItemDelete(query); +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/make_credential_operation.h b/chromium/device/fido/mac/make_credential_operation.h new file mode 100644 index 00000000000..f8d59652cb6 --- /dev/null +++ b/chromium/device/fido/mac/make_credential_operation.h @@ -0,0 +1,67 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_MAC_MAKE_CREDENTIAL_OPERATION_H_ +#define DEVICE_FIDO_MAC_MAKE_CREDENTIAL_OPERATION_H_ + +#include "base/component_export.h" +#include "base/mac/availability.h" +#include "base/macros.h" +#include "device/fido/authenticator_make_credential_response.h" +#include "device/fido/ctap_make_credential_request.h" +#include "device/fido/mac/operation_base.h" + +namespace device { +namespace fido { +namespace mac { + +// MakeCredentialOperation implements the authenticatorMakeCredential operation. +// The operation can be invoked via its |Run| method, which must only be called +// once. +// +// It prompts the user for consent via Touch ID and then generates a key pair +// in the secure enclave. A reference to the private key is stored as a +// keychain item in the macOS keychain for later lookup. The actual private key +// cannot be extracted from the secure enclave. Each keychain item stores the +// following metadata: +// +// - The item's application label (kSecAttrApplicationLabel), which must be +// unique, contains the credential identifier, which is computed as the CBOR +// encoding of (rp_id, user_id). +// +// - The application tag (kSecAttrApplicationTag) holds an identifier for the +// associated Chrome user profile, in order to separate credentials from +// different profiles. +// +// - The label (kSecAttrLabel) stores the RP ID, to allow iteration over all +// keys by a given RP. +// +// Keychain items are stored with the access group (kSecAttrAccessGroup) set +// to a value that identifies them as Chrome WebAuthn credentials +// (keychain_access_group_), so that they are logically +// separate from any other data that Chrome may store in the keychain in +// the future. +class API_AVAILABLE(macosx(10.12.2)) + COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialOperation + : public OperationBase<CtapMakeCredentialRequest, + AuthenticatorMakeCredentialResponse> { + public: + MakeCredentialOperation(CtapMakeCredentialRequest request, + std::string profile_id, + std::string keychain_access_group, + Callback callback); + ~MakeCredentialOperation() override; + + void Run() override; + + private: + const std::string& RpId() const override; + void PromptTouchIdDone(bool success, NSError* err) override; +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_MAKE_CREDENTIAL_OPERATION_H_ diff --git a/chromium/device/fido/mac/make_credential_operation.mm b/chromium/device/fido/mac/make_credential_operation.mm new file mode 100644 index 00000000000..36e11ff5429 --- /dev/null +++ b/chromium/device/fido/mac/make_credential_operation.mm @@ -0,0 +1,192 @@ +// Copyright 2018 The Chromium 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 "device/fido/mac/make_credential_operation.h" + +#include <string> + +#import <Foundation/Foundation.h> + +#include "base/mac/foundation_util.h" +#include "base/mac/mac_logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "device/fido/fido_attestation_statement.h" +#include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/mac/keychain.h" +#include "device/fido/mac/util.h" + +namespace device { +namespace fido { +namespace mac { + +using base::ScopedCFTypeRef; + +MakeCredentialOperation::MakeCredentialOperation( + CtapMakeCredentialRequest request, + std::string profile_id, + std::string keychain_access_group, + Callback callback) + : OperationBase<CtapMakeCredentialRequest, + AuthenticatorMakeCredentialResponse>( + std::move(request), + std::move(profile_id), + std::move(keychain_access_group), + std::move(callback)) {} +MakeCredentialOperation::~MakeCredentialOperation() = default; + +const std::string& MakeCredentialOperation::RpId() const { + return request().rp().rp_id(); +} + +void MakeCredentialOperation::Run() { + // Verify pubKeyCredParams contains ES-256, which is the only algorithm we + // support. + auto is_es256 = + [](const PublicKeyCredentialParams::CredentialInfo& cred_info) { + return cred_info.algorithm == + static_cast<int>(CoseAlgorithmIdentifier::kCoseEs256); + }; + const auto& key_params = + request().public_key_credential_params().public_key_credential_params(); + if (!std::any_of(key_params.begin(), key_params.end(), is_es256)) { + DVLOG(1) << "No supported algorithm found."; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrUnsupportedAlgorithms, + base::nullopt); + return; + } + + // Prompt the user for consent. + // TODO(martinkr): Localize reason strings. + PromptTouchId("register with " + RpId()); +} + +void MakeCredentialOperation::PromptTouchIdDone(bool success, NSError* err) { + if (!success) { + // err is autoreleased. + CHECK(err != nil); + DVLOG(1) << "Touch ID prompt failed: " << base::mac::NSToCFCast(err); + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt); + return; + } + + // Evaluate that excludeList does not contain any credentials stored by this + // authenticator. + if (request().exclude_list()) { + for (auto& credential : *request().exclude_list()) { + ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery(); + CFDictionarySetValue(query, kSecAttrApplicationLabel, + [NSData dataWithBytes:credential.id().data() + length:credential.id().size()]); + OSStatus status = SecItemCopyMatching(query, nullptr); + if (status == errSecSuccess) { + // Excluded item found. + DVLOG(1) << "credential from excludeList found"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrCredentialExcluded, + base::nullopt); + return; + } + if (status != errSecItemNotFound) { + // Unexpected keychain error. + OSSTATUS_DLOG(ERROR, status) + << "failed to check for excluded credential"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + } + } + + // Delete the key pair for this RP + user handle if one already exists. + const std::vector<uint8_t> keychain_item_id = + KeychainItemIdentifier(RpId(), request().user().user_id()); + { + ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery(); + CFDictionarySetValue(query, kSecAttrApplicationLabel, + [NSData dataWithBytes:keychain_item_id.data() + length:keychain_item_id.size()]); + OSStatus status = Keychain::GetInstance().ItemDelete(query); + if (status != errSecSuccess && status != errSecItemNotFound) { + // Internal keychain error. + OSSTATUS_DLOG(ERROR, status) << "SecItemDelete failed"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + } + + // Generate the new key pair. + ScopedCFTypeRef<CFMutableDictionaryRef> params( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr)); + CFDictionarySetValue(params, kSecAttrKeyType, + kSecAttrKeyTypeECSECPrimeRandom); + CFDictionarySetValue(params, kSecAttrKeySizeInBits, @256); + CFDictionarySetValue(params, kSecAttrSynchronizable, @NO); + CFDictionarySetValue(params, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave); + + ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params = + DefaultKeychainQuery(); + CFDictionarySetValue(params, kSecPrivateKeyAttrs, private_key_params); + CFDictionarySetValue(private_key_params, kSecAttrIsPermanent, @YES); + CFDictionarySetValue(private_key_params, kSecAttrAccessControl, + access_control()); + CFDictionarySetValue(private_key_params, kSecUseAuthenticationContext, + authentication_context()); + CFDictionarySetValue(private_key_params, kSecAttrApplicationLabel, + [NSData dataWithBytes:keychain_item_id.data() + length:keychain_item_id.size()]); + + ScopedCFTypeRef<CFErrorRef> cferr; + ScopedCFTypeRef<SecKeyRef> private_key( + Keychain::GetInstance().KeyCreateRandomKey(params, + cferr.InitializeInto())); + if (!private_key) { + DLOG(ERROR) << "SecKeyCreateRandomKey failed: " << cferr; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + ScopedCFTypeRef<SecKeyRef> public_key( + Keychain::GetInstance().KeyCopyPublicKey(private_key)); + if (!public_key) { + DLOG(ERROR) << "SecKeyCopyPublicKey failed"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + + // Create attestation object. There is no separate attestation key pair, so + // we perform self-attestation. + base::Optional<AuthenticatorData> authenticator_data = + MakeAuthenticatorData(RpId(), keychain_item_id, public_key); + if (!authenticator_data) { + DLOG(ERROR) << "MakeAuthenticatorData failed"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + base::Optional<std::vector<uint8_t>> signature = GenerateSignature( + *authenticator_data, request().client_data_hash(), private_key); + if (!signature) { + DLOG(ERROR) << "MakeSignature failed"; + std::move(callback()) + .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + std::vector<std::vector<uint8_t>> no_certificates; + AuthenticatorMakeCredentialResponse response(AttestationObject( + std::move(*authenticator_data), + // TODO(martinkr): Add a PackedAttestationStatement for self-attestation. + std::make_unique<FidoAttestationStatement>(std::move(*signature), + std::move(no_certificates)))); + std::move(callback()) + .Run(CtapDeviceResponseCode::kSuccess, std::move(response)); +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/make_credential_operation_unittest_mac.mm b/chromium/device/fido/mac/make_credential_operation_unittest_mac.mm new file mode 100644 index 00000000000..39d7aa9b65e --- /dev/null +++ b/chromium/device/fido/mac/make_credential_operation_unittest_mac.mm @@ -0,0 +1,64 @@ +// Copyright 2018 The Chromium 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 "device/fido/mac/make_credential_operation.h" + +#include <Foundation/Foundation.h> +#include <Security/Security.h> + +#include "base/strings/string_number_conversions.h" + +#include "base/test/scoped_task_environment.h" +#include "device/fido/test_callback_receiver.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { +namespace fido { +namespace mac { +namespace { + +using test::TestCallbackReceiver; + +const std::vector<uint8_t> kClientDataHash = {1, 2, 3, 4, 5}; +const std::string kRpId = "rp.example.com"; +const std::vector<uint8_t> kUserId = {10, 11, 12, 13, 14, 15}; +const char kKeychainAccessGroup[] = + "EQHXZ8M8AV.com.google.chrome.webauthn.test"; + +CtapMakeCredentialRequest MakeTestRequest() { + return CtapMakeCredentialRequest( + kClientDataHash, PublicKeyCredentialRpEntity(kRpId), + PublicKeyCredentialUserEntity(kUserId), + PublicKeyCredentialParams( + {{PublicKeyCredentialParams:: + CredentialInfo() /* defaults to ES-256 */}})); +} + +// For demo purposes only. This test does a Touch ID user prompt. It will fail +// on incompatible hardware and crash if not code signed or lacking the +// keychain-access-group entitlement. +TEST(MakeCredentialOperationTest, DISABLED_TestRun) +API_AVAILABLE(macosx(10.12.2)) { + base::test::ScopedTaskEnvironment scoped_task_environment; + TestCallbackReceiver<CtapDeviceResponseCode, + base::Optional<AuthenticatorMakeCredentialResponse>> + callback_receiver; + auto request = MakeTestRequest(); + MakeCredentialOperation op(request, "test-profile", kKeychainAccessGroup, + callback_receiver.callback()); + + op.Run(); + callback_receiver.WaitForCallback(); + auto result = callback_receiver.TakeResult(); + CtapDeviceResponseCode error = std::get<0>(result); + EXPECT_EQ(CtapDeviceResponseCode::kSuccess, error); + auto opt_response = std::move(std::get<1>(result)); + ASSERT_TRUE(opt_response); +}; + +} // namespace +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/operation.h b/chromium/device/fido/mac/operation.h new file mode 100644 index 00000000000..191a2dbe192 --- /dev/null +++ b/chromium/device/fido/mac/operation.h @@ -0,0 +1,31 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_MAC_OPERATION_H_ +#define DEVICE_FIDO_MAC_OPERATION_H_ + +#include "base/macros.h" + +namespace device { +namespace fido { +namespace mac { + +// Operation is the interface to OperationBase. +class Operation { + public: + virtual ~Operation() = default; + virtual void Run() = 0; + + protected: + Operation() = default; + + private: + DISALLOW_COPY_AND_ASSIGN(Operation); +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_OPERATION_H_ diff --git a/chromium/device/fido/mac/operation_base.h b/chromium/device/fido/mac/operation_base.h new file mode 100644 index 00000000000..5112cbf8017 --- /dev/null +++ b/chromium/device/fido/mac/operation_base.h @@ -0,0 +1,101 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_MAC_OPERATION_BASE_H_ +#define DEVICE_FIDO_MAC_OPERATION_BASE_H_ + +#import <Foundation/Foundation.h> +#import <Security/Security.h> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/macros.h" +#include "base/strings/sys_string_conversions.h" +#include "device/fido/mac/operation.h" +#include "device/fido/mac/touch_id_context.h" + +namespace device { +namespace fido { +namespace mac { + +// OperationBase abstracts behavior common to both concrete Operations, +// |MakeCredentialOperation| and |GetAssertionOperation|. +template <class Request, class Response> +class API_AVAILABLE(macosx(10.12.2)) OperationBase : public Operation { + public: + using Callback = base::OnceCallback<void(CtapDeviceResponseCode, + base::Optional<Response>)>; + + OperationBase(Request request, + std::string profile_id, + std::string keychain_access_group, + Callback callback) + : request_(std::move(request)), + profile_id_(std::move(profile_id)), + keychain_access_group_(std::move(keychain_access_group)), + callback_(std::move(callback)), + touch_id_context_(std::make_unique<TouchIdContext>()) {} + ~OperationBase() override = default; + + protected: + // PromptTouchId triggers a Touch ID consent dialog with the given reason + // string. Subclasses implement the PromptTouchIdDone callback to receive the + // result. + void PromptTouchId(std::string reason) { + // The callback passed to TouchIdContext::Prompt will not fire if the + // TouchIdContext itself has been deleted. Since that it is owned by this + // class, there is no need to bind the callback to a weak ref here. + touch_id_context_->PromptTouchId( + std::move(reason), base::BindOnce(&OperationBase::PromptTouchIdDone, + base::Unretained(this))); + } + + // Callback for |PromptTouchId|. Any NSError that gets passed is autoreleased. + virtual void PromptTouchIdDone(bool success, NSError* err) = 0; + + // Subclasses override RpId to return the RP ID from the type-specific + // request. + virtual const std::string& RpId() const = 0; + + LAContext* authentication_context() const { + return touch_id_context_->authentication_context(); + } + SecAccessControlRef access_control() const { + return touch_id_context_->access_control(); + } + + // DefaultKeychainQuery returns a default keychain query dictionary that has + // the keychain item class, profile ID and RP ID filled out (but not the + // credential ID). More fields can be set on the return value to refine the + // query. + base::ScopedCFTypeRef<CFMutableDictionaryRef> DefaultKeychainQuery() const { + base::ScopedCFTypeRef<CFMutableDictionaryRef> query( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr)); + CFDictionarySetValue(query, kSecClass, kSecClassKey); + CFDictionarySetValue(query, kSecAttrAccessGroup, + base::SysUTF8ToNSString(keychain_access_group_)); + CFDictionarySetValue(query, kSecAttrLabel, base::SysUTF8ToNSString(RpId())); + CFDictionarySetValue(query, kSecAttrApplicationTag, + base::SysUTF8ToNSString(profile_id_)); + return query; + } + + const Request& request() const { return request_; } + Callback& callback() { return callback_; } + + private: + Request request_; + std::string profile_id_; + std::string keychain_access_group_; + Callback callback_; + + std::unique_ptr<TouchIdContext> touch_id_context_; +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_OPERATION_BASE_H_ diff --git a/chromium/device/fido/mac/touch_id_context.h b/chromium/device/fido/mac/touch_id_context.h new file mode 100644 index 00000000000..29133b482d2 --- /dev/null +++ b/chromium/device/fido/mac/touch_id_context.h @@ -0,0 +1,59 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_MAC_TOUCH_ID_CONTEXT_H_ +#define DEVICE_FIDO_MAC_TOUCH_ID_CONTEXT_H_ + +#import <LocalAuthentication/LocalAuthentication.h> +#import <Security/Security.h> + +#include "base/callback.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsobject.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" + +namespace device { +namespace fido { +namespace mac { + +// TouchIdContext wraps a macOS Touch ID consent prompt for signing with a +// secure enclave key. +class API_AVAILABLE(macosx(10.12.2)) TouchIdContext { + public: + // The callback is invoked when the Touch ID prompt completes. It receives a + // boolean indicating success and an autoreleased NSError if the prompt was + // denied or failed. + using Callback = base::OnceCallback<void(bool, NSError*)>; + + TouchIdContext(); + ~TouchIdContext(); + + // PromptTouchId displays a Touch ID consent prompt with the provided reason + // string to the user. On completion or error, the provided callback is + // invoked, unless the TouchIdContext instance has been destroyed in the + // meantime (in which case nothing happens). + void PromptTouchId(std::string reason, Callback callback); + + // authentication_context returns the LAContext used for the Touch ID prompt. + LAContext* authentication_context() const { return context_; } + + // access_control returns a reference to the SecAccessControl object that was + // evaluated/authorized in the Touch ID prompt. + SecAccessControlRef access_control() const { return access_control_; } + + private: + base::scoped_nsobject<LAContext> context_; + base::ScopedCFTypeRef<SecAccessControlRef> access_control_; + Callback callback_; + base::WeakPtrFactory<TouchIdContext> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(TouchIdContext); +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_TOUCH_ID_CONTEXT_H_ diff --git a/chromium/device/fido/mac/touch_id_context.mm b/chromium/device/fido/mac/touch_id_context.mm new file mode 100644 index 00000000000..8a31f2bca26 --- /dev/null +++ b/chromium/device/fido/mac/touch_id_context.mm @@ -0,0 +1,54 @@ +// Copyright 2018 The Chromium 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 "device/fido/mac/touch_id_context.h" + +#import <Foundation/Foundation.h> + +#include "base/strings/sys_string_conversions.h" + +namespace device { +namespace fido { +namespace mac { + +namespace { +API_AVAILABLE(macosx(10.12.2)) +base::ScopedCFTypeRef<SecAccessControlRef> DefaultAccessControl() { + return base::ScopedCFTypeRef<SecAccessControlRef>( + SecAccessControlCreateWithFlags( + kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + kSecAccessControlPrivateKeyUsage | kSecAccessControlTouchIDAny, + nullptr)); +} +} // namespace + +TouchIdContext::TouchIdContext() + : context_([[LAContext alloc] init]), + access_control_(DefaultAccessControl()), + callback_(), + weak_ptr_factory_(this) {} + +TouchIdContext::~TouchIdContext() = default; + +void TouchIdContext::PromptTouchId(std::string reason, Callback callback) { + callback_ = std::move(callback); + auto weak_self = weak_ptr_factory_.GetWeakPtr(); + // If evaluation succeeds (i.e. user provides a fingerprint), |context_| can + // be used for one signing operation. N.B. even in |MakeCredentialOperation|, + // we need to perform a signature for the attestation statement, so we need + // the sign bit there. + [context_ evaluateAccessControl:access_control_ + operation:LAAccessControlOperationUseKeySign + localizedReason:base::SysUTF8ToNSString(reason) + reply:^(BOOL success, NSError* error) { + if (!weak_self) { + return; + } + std::move(callback_).Run(success, error); + }]; +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/util.h b/chromium/device/fido/mac/util.h new file mode 100644 index 00000000000..fe0935ccc35 --- /dev/null +++ b/chromium/device/fido/mac/util.h @@ -0,0 +1,51 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_MAC_UTIL_H_ +#define DEVICE_FIDO_MAC_UTIL_H_ + +#include <string> +#include <vector> + +#import <Security/Security.h> + +#include "base/callback.h" +#include "base/mac/availability.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsobject.h" + +namespace device { +namespace fido { +namespace mac { + +// KeychainItemIdentifier returns the unique identifier for a key pair, derived +// from an RP ID and user handle. It is stored in the keychain items +// kSecAttrApplicationLabel attribute and can be used for lookup. +std::vector<uint8_t> KeychainItemIdentifier(std::string rp_id, + std::vector<uint8_t> user_id); + +// MakeAuthenticatorData returns an AuthenticatorData instance for the Touch ID +// authenticator with the given Relying Party ID, credential ID and public key. +// It returns |base::nullopt| on failure. +base::Optional<AuthenticatorData> MakeAuthenticatorData( + const std::string& rp_id, + std::vector<uint8_t> credential_id, + SecKeyRef public_key) API_AVAILABLE(macosx(10.12.2)); + +// GenerateSignature signs the concatenation of the serialization of the given +// authenticator data and the given client data hash, as required for +// (self-)attestation and assertion. Returns |base::nullopt| if the operation +// fails. +base::Optional<std::vector<uint8_t>> GenerateSignature( + const AuthenticatorData& authenticator_data, + const std::vector<uint8_t>& client_data_hash, + SecKeyRef private_key) API_AVAILABLE(macosx(10.12.2)); + +std::vector<uint8_t> TouchIdAaguid(); + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_UTIL_H_ diff --git a/chromium/device/fido/mac/util.mm b/chromium/device/fido/mac/util.mm new file mode 100644 index 00000000000..38b71a5556d --- /dev/null +++ b/chromium/device/fido/mac/util.mm @@ -0,0 +1,142 @@ +// Copyright 2018 The Chromium 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 "device/fido/mac/get_assertion_operation.h" + +#include <array> +#include <set> +#include <string> + +#import <Foundation/Foundation.h> + +#include "base/bind.h" +#include "base/mac/foundation_util.h" +#include "base/mac/mac_logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsobject.h" +#include "base/strings/string_number_conversions.h" +#include "components/cbor/cbor_writer.h" +#include "device/fido/ec_public_key.h" +#include "device/fido/fido_attestation_statement.h" +#include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/mac/keychain.h" + +namespace device { +namespace fido { +namespace mac { + +using base::ScopedCFTypeRef; +using base::scoped_nsobject; +using cbor::CBORWriter; +using cbor::CBORValue; + +// The authenticator AAGUID value. +constexpr std::array<uint8_t, 16> kAaguid = {0xad, 0xce, 0x00, 0x02, 0x35, 0xbc, + 0xc6, 0x0a, 0x64, 0x8b, 0x0b, 0x25, + 0xf1, 0xf0, 0x55, 0x03}; + +std::vector<uint8_t> TouchIdAaguid() { + return std::vector<uint8_t>(kAaguid.begin(), kAaguid.end()); +} + +namespace { + +// MakeECPublicKey converts a SecKeyRef for a public key into an equivalent +// |ECPublicKey| instance. It returns |nullptr| if the key cannot be converted. +std::unique_ptr<ECPublicKey> MakeECPublicKey(SecKeyRef public_key_ref) + API_AVAILABLE(macosx(10.12.2)) { + CHECK(public_key_ref); + ScopedCFTypeRef<CFErrorRef> err; + ScopedCFTypeRef<CFDataRef> data_ref( + SecKeyCopyExternalRepresentation(public_key_ref, err.InitializeInto())); + if (!data_ref) { + LOG(ERROR) << "SecCopyExternalRepresentation failed: " << err; + return nullptr; + } + base::span<const uint8_t> key_data = + base::make_span(CFDataGetBytePtr(data_ref), CFDataGetLength(data_ref)); + auto key = + ECPublicKey::ParseX962Uncompressed(fido_parsing_utils::kEs256, key_data); + if (!key) { + LOG(ERROR) << "Unexpected public key format: " + << base::HexEncode(key_data.data(), key_data.size()); + return nullptr; + } + return key; +} + +} // namespace + +// KeychainItemIdentifier returns the unique identifier for a given RP ID +// and user handle. It is stored in the keychain items Application Label and +// used for later lookup. +std::vector<uint8_t> KeychainItemIdentifier(std::string rp_id, + std::vector<uint8_t> user_id) { + std::vector<CBORValue> array; + array.emplace_back(CBORValue(rp_id)); + array.emplace_back(CBORValue(user_id)); + auto value = CBORWriter::Write(CBORValue(std::move(array))); + CHECK(value); + return *value; +} + +base::Optional<AuthenticatorData> MakeAuthenticatorData( + const std::string& rp_id, + std::vector<uint8_t> credential_id, + SecKeyRef public_key) API_AVAILABLE(macosx(10.12.2)) { + if (credential_id.size() > 255) { + LOG(ERROR) << "credential id too long: " + << base::HexEncode(credential_id.data(), credential_id.size()); + return base::nullopt; + } + std::array<uint8_t, 2> encoded_credential_id_length = { + 0, static_cast<uint8_t>(credential_id.size())}; + constexpr uint8_t flags = + static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserVerification) | + static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); + std::vector<uint8_t> counter = {0, 0, 0, 0}; // implement + auto ec_public_key = MakeECPublicKey(public_key); + if (!ec_public_key) { + LOG(ERROR) << "MakeECPublicKey failed"; + return base::nullopt; + } + return AuthenticatorData( + fido_parsing_utils::CreateSHA256Hash(rp_id), flags, counter, + AttestedCredentialData(kAaguid, encoded_credential_id_length, + std::move(credential_id), + std::move(ec_public_key))); +} + +base::Optional<std::vector<uint8_t>> GenerateSignature( + const AuthenticatorData& authenticator_data, + const std::vector<uint8_t>& client_data_hash, + SecKeyRef private_key) API_AVAILABLE(macosx(10.12.2)) { + const std::vector<uint8_t> serialized_authenticator_data = + authenticator_data.SerializeToByteArray(); + size_t capacity = + serialized_authenticator_data.size() + client_data_hash.size(); + ScopedCFTypeRef<CFMutableDataRef> sig_input( + CFDataCreateMutable(kCFAllocatorDefault, capacity)); + CFDataAppendBytes(sig_input, serialized_authenticator_data.data(), + serialized_authenticator_data.size()); + CFDataAppendBytes(sig_input, client_data_hash.data(), + client_data_hash.size()); + ScopedCFTypeRef<CFErrorRef> err; + ScopedCFTypeRef<CFDataRef> sig_data( + Keychain::GetInstance().KeyCreateSignature( + private_key, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, + sig_input, err.InitializeInto())); + if (!sig_data) { + LOG(ERROR) << "SecKeyCreateSignature failed: " << err; + return base::nullopt; + } + return std::vector<uint8_t>( + CFDataGetBytePtr(sig_data), + CFDataGetBytePtr(sig_data) + CFDataGetLength(sig_data)); +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/make_credential_handler_unittest.cc b/chromium/device/fido/make_credential_handler_unittest.cc index 736408b47e2..7f8bee04a48 100644 --- a/chromium/device/fido/make_credential_handler_unittest.cc +++ b/chromium/device/fido/make_credential_handler_unittest.cc @@ -12,12 +12,12 @@ #include "device/fido/fake_fido_discovery.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_device.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/fido_transport_protocol.h" #include "device/fido/make_credential_request_handler.h" #include "device/fido/mock_fido_device.h" #include "device/fido/test_callback_receiver.h" -#include "device/fido/u2f_parsing_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -46,12 +46,13 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test { std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandler() { ForgeNextHidDiscovery(); PublicKeyCredentialRpEntity rp(kRpId); - PublicKeyCredentialUserEntity user(u2f_parsing_utils::Materialize(kUserId)); + PublicKeyCredentialUserEntity user( + fido_parsing_utils::Materialize(kUserId)); PublicKeyCredentialParams credential_params( std::vector<PublicKeyCredentialParams::CredentialInfo>(1)); auto request_parameter = CtapMakeCredentialRequest( - u2f_parsing_utils::Materialize(kClientDataHash), std::move(rp), + fido_parsing_utils::Materialize(kClientDataHash), std::move(rp), std::move(user), std::move(credential_params)); return std::make_unique<MakeCredentialRequestHandler>( diff --git a/chromium/device/fido/make_credential_request_handler.cc b/chromium/device/fido/make_credential_request_handler.cc index fe72f26a1a8..f9c16af664c 100644 --- a/chromium/device/fido/make_credential_request_handler.cc +++ b/chromium/device/fido/make_credential_request_handler.cc @@ -8,7 +8,7 @@ #include "base/bind.h" #include "device/fido/authenticator_make_credential_response.h" -#include "device/fido/fido_device.h" +#include "device/fido/fido_authenticator.h" #include "device/fido/make_credential_task.h" #include "services/service_manager/public/cpp/connector.h" @@ -24,16 +24,18 @@ MakeCredentialRequestHandler::MakeCredentialRequestHandler( request_parameter_(std::move(request_parameter)), authenticator_selection_criteria_( std::move(authenticator_selection_criteria)), - weak_factory_(this) {} + weak_factory_(this) { + Start(); +} MakeCredentialRequestHandler::~MakeCredentialRequestHandler() = default; -std::unique_ptr<FidoTask> MakeCredentialRequestHandler::CreateTaskForNewDevice( - FidoDevice* device) { - return std::make_unique<MakeCredentialTask>( - device, request_parameter_, authenticator_selection_criteria_, - base::BindOnce(&MakeCredentialRequestHandler::OnDeviceResponse, - weak_factory_.GetWeakPtr(), device)); +void MakeCredentialRequestHandler::DispatchRequest( + FidoAuthenticator* authenticator) { + return authenticator->MakeCredential( + authenticator_selection_criteria_, request_parameter_, + base::BindOnce(&MakeCredentialRequestHandler::OnAuthenticatorResponse, + weak_factory_.GetWeakPtr(), authenticator)); } } // namespace device diff --git a/chromium/device/fido/make_credential_request_handler.h b/chromium/device/fido/make_credential_request_handler.h index f0ac0d6e99a..23cc5b6178f 100644 --- a/chromium/device/fido/make_credential_request_handler.h +++ b/chromium/device/fido/make_credential_request_handler.h @@ -24,13 +24,11 @@ class Connector; namespace device { -class FidoDevice; -class FidoTask; +class FidoAuthenticator; class AuthenticatorMakeCredentialResponse; -using RegisterResponseCallback = base::OnceCallback<void( - FidoReturnCode status_code, - base::Optional<AuthenticatorMakeCredentialResponse> response_data)>; +using RegisterResponseCallback = base::OnceCallback< + void(FidoReturnCode, base::Optional<AuthenticatorMakeCredentialResponse>)>; class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler : public FidoRequestHandler<AuthenticatorMakeCredentialResponse> { @@ -45,7 +43,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler private: // FidoRequestHandlerBase: - std::unique_ptr<FidoTask> CreateTaskForNewDevice(FidoDevice* device) final; + void DispatchRequest(FidoAuthenticator* authenticator) final; CtapMakeCredentialRequest request_parameter_; AuthenticatorSelectionCriteria authenticator_selection_criteria_; diff --git a/chromium/device/fido/make_credential_task.cc b/chromium/device/fido/make_credential_task.cc index acefe073c02..4c966ebd331 100644 --- a/chromium/device/fido/make_credential_task.cc +++ b/chromium/device/fido/make_credential_task.cc @@ -8,6 +8,7 @@ #include "base/bind.h" #include "device/fido/ctap_empty_authenticator_request.h" +#include "device/fido/ctap_register_operation.h" #include "device/fido/device_response_converter.h" namespace device { @@ -40,10 +41,11 @@ void MakeCredentialTask::MakeCredential() { return; } - device()->DeviceTransact( - request_parameter_.EncodeAsCBOR(), + register_operation_ = std::make_unique<CtapRegisterOperation>( + device(), &request_parameter_, base::BindOnce(&MakeCredentialTask::OnCtapMakeCredentialResponseReceived, weak_factory_.GetWeakPtr())); + register_operation_->Start(); } void MakeCredentialTask::U2fRegister() { @@ -56,28 +58,21 @@ void MakeCredentialTask::U2fRegister() { } void MakeCredentialTask::OnCtapMakeCredentialResponseReceived( - base::Optional<std::vector<uint8_t>> device_response) { - if (!device_response) { - std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther, - base::nullopt); - return; - } - - auto response_code = GetResponseCode(*device_response); - if (response_code != CtapDeviceResponseCode::kSuccess) { - std::move(callback_).Run(response_code, base::nullopt); + CtapDeviceResponseCode return_code, + base::Optional<AuthenticatorMakeCredentialResponse> response_data) { + if (return_code != CtapDeviceResponseCode::kSuccess) { + std::move(callback_).Run(return_code, base::nullopt); return; } - auto parsed_response = ReadCTAPMakeCredentialResponse(*device_response); - if (!parsed_response || - !parsed_response->CheckRpIdHash(request_parameter_.rp().rp_id())) { + if (!response_data || + !response_data->CheckRpIdHash(request_parameter_.rp().rp_id())) { std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); return; } - std::move(callback_).Run(response_code, std::move(parsed_response)); + std::move(callback_).Run(return_code, std::move(response_data)); } bool MakeCredentialTask::CheckIfAuthenticatorSelectionCriteriaAreSatisfied() { diff --git a/chromium/device/fido/make_credential_task.h b/chromium/device/fido/make_credential_task.h index 8e87f975d15..25025892edf 100644 --- a/chromium/device/fido/make_credential_task.h +++ b/chromium/device/fido/make_credential_task.h @@ -7,6 +7,7 @@ #include <stdint.h> +#include <memory> #include <vector> #include "base/callback.h" @@ -17,6 +18,7 @@ #include "device/fido/authenticator_make_credential_response.h" #include "device/fido/authenticator_selection_criteria.h" #include "device/fido/ctap_make_credential_request.h" +#include "device/fido/ctap_register_operation.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_task.h" @@ -27,8 +29,8 @@ namespace device { class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialTask : public FidoTask { public: using MakeCredentialTaskCallback = base::OnceCallback<void( - CtapDeviceResponseCode return_code, - base::Optional<AuthenticatorMakeCredentialResponse> response_data)>; + CtapDeviceResponseCode, + base::Optional<AuthenticatorMakeCredentialResponse>)>; MakeCredentialTask(FidoDevice* device, CtapMakeCredentialRequest request_parameter, @@ -43,7 +45,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialTask : public FidoTask { void MakeCredential(); void U2fRegister(); void OnCtapMakeCredentialResponseReceived( - base::Optional<std::vector<uint8_t>> device_response); + CtapDeviceResponseCode return_code, + base::Optional<AuthenticatorMakeCredentialResponse> response_data); // Invoked after retrieving response to AuthenticatorGetInfo request. Filters // out authenticators based on |authenticator_selection_criteria_| constraints @@ -53,8 +56,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialTask : public FidoTask { CtapMakeCredentialRequest request_parameter_; AuthenticatorSelectionCriteria authenticator_selection_criteria_; + std::unique_ptr<CtapRegisterOperation> register_operation_; MakeCredentialTaskCallback callback_; - base::WeakPtrFactory<MakeCredentialTask> weak_factory_; DISALLOW_COPY_AND_ASSIGN(MakeCredentialTask); diff --git a/chromium/device/fido/make_credential_task_unittest.cc b/chromium/device/fido/make_credential_task_unittest.cc index 2ce00b9f5f3..24cc98701ae 100644 --- a/chromium/device/fido/make_credential_task_unittest.cc +++ b/chromium/device/fido/make_credential_task_unittest.cc @@ -13,11 +13,11 @@ #include "device/fido/authenticator_make_credential_response.h" #include "device/fido/ctap_make_credential_request.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/make_credential_task.h" #include "device/fido/mock_fido_device.h" #include "device/fido/test_callback_receiver.h" -#include "device/fido/u2f_parsing_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -47,11 +47,12 @@ class FidoMakeCredentialTaskTest : public testing::Test { std::unique_ptr<MakeCredentialTask> CreateMakeCredentialTask( FidoDevice* device) { PublicKeyCredentialRpEntity rp(kRpId); - PublicKeyCredentialUserEntity user(u2f_parsing_utils::Materialize(kUserId)); + PublicKeyCredentialUserEntity user( + fido_parsing_utils::Materialize(kUserId)); return std::make_unique<MakeCredentialTask>( device, CtapMakeCredentialRequest( - u2f_parsing_utils::Materialize(kClientDataHash), std::move(rp), + fido_parsing_utils::Materialize(kClientDataHash), std::move(rp), std::move(user), PublicKeyCredentialParams( std::vector<PublicKeyCredentialParams::CredentialInfo>(1))), @@ -63,11 +64,12 @@ class FidoMakeCredentialTaskTest : public testing::Test { FidoDevice* device, AuthenticatorSelectionCriteria criteria) { PublicKeyCredentialRpEntity rp(kRpId); - PublicKeyCredentialUserEntity user(u2f_parsing_utils::Materialize(kUserId)); + PublicKeyCredentialUserEntity user( + fido_parsing_utils::Materialize(kUserId)); return std::make_unique<MakeCredentialTask>( device, CtapMakeCredentialRequest( - u2f_parsing_utils::Materialize(kClientDataHash), std::move(rp), + fido_parsing_utils::Materialize(kClientDataHash), std::move(rp), std::move(user), PublicKeyCredentialParams( std::vector<PublicKeyCredentialParams::CredentialInfo>(1))), diff --git a/chromium/device/fido/mock_fido_device.cc b/chromium/device/fido/mock_fido_device.cc index 4b91df55b47..42d61e8c835 100644 --- a/chromium/device/fido/mock_fido_device.cc +++ b/chromium/device/fido/mock_fido_device.cc @@ -11,8 +11,8 @@ #include "base/threading/thread_task_runner_handle.h" #include "components/apdu/apdu_response.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" -#include "device/fido/u2f_parsing_utils.h" namespace device { @@ -74,16 +74,6 @@ void MockFidoDevice::NoErrorRegister(const std::vector<uint8_t>& command, } // static -void MockFidoDevice::NoErrorVersion(const std::vector<uint8_t>& command, - DeviceCallback& cb) { - std::move(cb).Run( - apdu::ApduResponse(std::vector<uint8_t>(kU2fVersionResponse.cbegin(), - kU2fVersionResponse.cend()), - apdu::ApduResponse::Status::SW_NO_ERROR) - .GetEncodedResponse()); -} - -// static void MockFidoDevice::SignWithCorruptedResponse( const std::vector<uint8_t>& command, DeviceCallback& cb) { @@ -109,7 +99,7 @@ void MockFidoDevice::ExpectCtap2CommandAndRespondWith( CtapRequestCommand command, base::Optional<base::span<const uint8_t>> response, base::TimeDelta delay) { - auto data = u2f_parsing_utils::MaterializeOrNull(response); + auto data = fido_parsing_utils::MaterializeOrNull(response); auto send_response = [ data(std::move(data)), delay ](DeviceCallback & cb) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(std::move(cb), std::move(data)), delay); @@ -123,13 +113,13 @@ void MockFidoDevice::ExpectRequestAndRespondWith( base::span<const uint8_t> request, base::Optional<base::span<const uint8_t>> response, base::TimeDelta delay) { - auto data = u2f_parsing_utils::MaterializeOrNull(response); + auto data = fido_parsing_utils::MaterializeOrNull(response); auto send_response = [ data(std::move(data)), delay ](DeviceCallback & cb) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(std::move(cb), std::move(data)), delay); }; - auto request_as_vector = u2f_parsing_utils::Materialize(request); + auto request_as_vector = fido_parsing_utils::Materialize(request); EXPECT_CALL(*this, DeviceTransactPtr(std::move(request_as_vector), ::testing::_)) .WillOnce(::testing::WithArg<1>(::testing::Invoke(send_response))); @@ -142,7 +132,7 @@ void MockFidoDevice::ExpectCtap2CommandAndDoNotRespond( void MockFidoDevice::ExpectRequestAndDoNotRespond( base::span<const uint8_t> request) { - auto request_as_vector = u2f_parsing_utils::Materialize(request); + auto request_as_vector = fido_parsing_utils::Materialize(request); EXPECT_CALL(*this, DeviceTransactPtr(std::move(request_as_vector), ::testing::_)); } diff --git a/chromium/device/fido/mock_fido_device.h b/chromium/device/fido/mock_fido_device.h index 9e17e2e2ed4..e8afa2c4e47 100644 --- a/chromium/device/fido/mock_fido_device.h +++ b/chromium/device/fido/mock_fido_device.h @@ -51,8 +51,6 @@ class MockFidoDevice : public FidoDevice { DeviceCallback& cb); static void NoErrorRegister(const std::vector<uint8_t>& command, DeviceCallback& cb); - static void NoErrorVersion(const std::vector<uint8_t>& command, - DeviceCallback& cb); static void SignWithCorruptedResponse(const std::vector<uint8_t>& command, DeviceCallback& cb); static void WinkDoNothing(WinkCallback& cb); diff --git a/chromium/device/fido/public_key_credential_descriptor.cc b/chromium/device/fido/public_key_credential_descriptor.cc index 007897f6ee1..af48eebcf34 100644 --- a/chromium/device/fido/public_key_credential_descriptor.cc +++ b/chromium/device/fido/public_key_credential_descriptor.cc @@ -26,21 +26,22 @@ PublicKeyCredentialDescriptor::CreateFromCBORValue( const cbor::CBORValue::MapValue& map = cbor.GetMap(); auto type = map.find(cbor::CBORValue(kCredentialTypeKey)); - if (type == map.end() || !type->second.is_string()) + if (type == map.end() || !type->second.is_string() || + type->second.GetString() != kPublicKey) return base::nullopt; auto id = map.find(cbor::CBORValue(kCredentialIdKey)); if (id == map.end() || !id->second.is_bytestring()) return base::nullopt; - return PublicKeyCredentialDescriptor(type->second.GetString(), + return PublicKeyCredentialDescriptor(CredentialType::kPublicKey, id->second.GetBytestring()); } PublicKeyCredentialDescriptor::PublicKeyCredentialDescriptor( - std::string credential_type, + CredentialType credential_type, std::vector<uint8_t> id) - : credential_type_(std::move(credential_type)), id_(std::move(id)) {} + : credential_type_(credential_type), id_(std::move(id)) {} PublicKeyCredentialDescriptor::PublicKeyCredentialDescriptor( const PublicKeyCredentialDescriptor& other) = default; @@ -60,7 +61,7 @@ cbor::CBORValue PublicKeyCredentialDescriptor::ConvertToCBOR() const { cbor::CBORValue::MapValue cbor_descriptor_map; cbor_descriptor_map[cbor::CBORValue(kCredentialIdKey)] = cbor::CBORValue(id_); cbor_descriptor_map[cbor::CBORValue(kCredentialTypeKey)] = - cbor::CBORValue(credential_type_); + cbor::CBORValue(CredentialTypeToString(credential_type_)); return cbor::CBORValue(std::move(cbor_descriptor_map)); } diff --git a/chromium/device/fido/public_key_credential_descriptor.h b/chromium/device/fido/public_key_credential_descriptor.h index bcd369fa2b0..02bc4389a37 100644 --- a/chromium/device/fido/public_key_credential_descriptor.h +++ b/chromium/device/fido/public_key_credential_descriptor.h @@ -12,6 +12,7 @@ #include "base/component_export.h" #include "base/optional.h" #include "components/cbor/cbor_values.h" +#include "device/fido/fido_constants.h" namespace device { @@ -24,7 +25,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) PublicKeyCredentialDescriptor { static base::Optional<PublicKeyCredentialDescriptor> CreateFromCBORValue( const cbor::CBORValue& cbor); - PublicKeyCredentialDescriptor(std::string credential_type, + PublicKeyCredentialDescriptor(CredentialType credential_type, std::vector<uint8_t> id); PublicKeyCredentialDescriptor(const PublicKeyCredentialDescriptor& other); PublicKeyCredentialDescriptor(PublicKeyCredentialDescriptor&& other); @@ -36,11 +37,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) PublicKeyCredentialDescriptor { cbor::CBORValue ConvertToCBOR() const; - const std::string& credential_type() const { return credential_type_; } + CredentialType credential_type() const { return credential_type_; } const std::vector<uint8_t>& id() const { return id_; } private: - std::string credential_type_; + CredentialType credential_type_; std::vector<uint8_t> id_; }; diff --git a/chromium/device/fido/public_key_credential_params.cc b/chromium/device/fido/public_key_credential_params.cc index d0d575d557c..4b7e53f2bb5 100644 --- a/chromium/device/fido/public_key_credential_params.cc +++ b/chromium/device/fido/public_key_credential_params.cc @@ -33,7 +33,7 @@ cbor::CBORValue PublicKeyCredentialParams::ConvertToCBOR() const { for (const auto& credential : public_key_credential_params_) { cbor::CBORValue::MapValue cbor_credential_map; cbor_credential_map[cbor::CBORValue("type")] = - cbor::CBORValue(to_string(credential.type)); + cbor::CBORValue(CredentialTypeToString(credential.type)); cbor_credential_map[cbor::CBORValue("alg")] = cbor::CBORValue(credential.algorithm); credential_param_array.emplace_back(std::move(cbor_credential_map)); diff --git a/chromium/device/fido/response_data.cc b/chromium/device/fido/response_data.cc index 829adcef82e..09f3e4e4632 100644 --- a/chromium/device/fido/response_data.cc +++ b/chromium/device/fido/response_data.cc @@ -8,7 +8,7 @@ #include "base/base64url.h" #include "base/strings/string_piece.h" -#include "crypto/sha2.h" +#include "device/fido/fido_parsing_utils.h" namespace device { @@ -33,11 +33,7 @@ std::string ResponseData::GetId() const { } bool ResponseData::CheckRpIdHash(const std::string& rp_id) const { - const auto& response_rp_id_hash = GetRpIdHash(); - std::vector<uint8_t> request_rp_id_hash(crypto::kSHA256Length); - crypto::SHA256HashString(rp_id, request_rp_id_hash.data(), - request_rp_id_hash.size()); - return response_rp_id_hash == request_rp_id_hash; + return GetRpIdHash() == fido_parsing_utils::CreateSHA256Hash(rp_id); } } // namespace device diff --git a/chromium/device/fido/scoped_virtual_fido_device.cc b/chromium/device/fido/scoped_virtual_fido_device.cc index db7fc515882..d5afaf87a4a 100644 --- a/chromium/device/fido/scoped_virtual_fido_device.cc +++ b/chromium/device/fido/scoped_virtual_fido_device.cc @@ -11,6 +11,7 @@ #include "base/location.h" #include "base/macros.h" #include "base/threading/sequenced_task_runner_handle.h" +#include "device/fido/virtual_u2f_device.h" namespace device { namespace test { @@ -26,7 +27,7 @@ class VirtualFidoDeviceDiscovery : public FidoDiscovery { protected: void StartInternal() override { - auto device = std::make_unique<VirtualFidoDevice>(state_); + auto device = std::make_unique<VirtualU2fDevice>(state_); AddDevice(std::move(device)); base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, diff --git a/chromium/device/fido/test_callback_receiver.h b/chromium/device/fido/test_callback_receiver.h index 1a93bc5c22a..90363783a03 100644 --- a/chromium/device/fido/test_callback_receiver.h +++ b/chromium/device/fido/test_callback_receiver.h @@ -40,8 +40,7 @@ namespace test { template <class... CallbackArgs> class TestCallbackReceiver { public: - using TupleOfNonReferenceArgs = - std::tuple<typename std::decay_t<CallbackArgs>...>; + using TupleOfNonReferenceArgs = std::tuple<std::decay_t<CallbackArgs>...>; TestCallbackReceiver() = default; ~TestCallbackReceiver() = default; @@ -82,7 +81,7 @@ class TestCallbackReceiver { private: void ReceiverMethod(CallbackArgs... args) { - result_.emplace(std::move(args)...); + result_.emplace(std::forward<CallbackArgs>(args)...); was_called_ = true; wait_for_callback_loop_.Quit(); } @@ -94,11 +93,11 @@ class TestCallbackReceiver { DISALLOW_COPY_AND_ASSIGN(TestCallbackReceiver); }; -template <class Status> -class StatusCallbackReceiver : public TestCallbackReceiver<Status> { +template <class Value> +class ValueCallbackReceiver : public TestCallbackReceiver<Value> { public: - const Status& status() const { - return std::get<0>(*TestCallbackReceiver<Status>::result()); + const Value& value() const { + return std::get<0>(*TestCallbackReceiver<Value>::result()); } }; diff --git a/chromium/device/fido/test_callback_receiver_unittest.cc b/chromium/device/fido/test_callback_receiver_unittest.cc index dc31f78fb41..4197776840c 100644 --- a/chromium/device/fido/test_callback_receiver_unittest.cc +++ b/chromium/device/fido/test_callback_receiver_unittest.cc @@ -4,6 +4,10 @@ #include "device/fido/test_callback_receiver.h" +#include <memory> +#include <tuple> +#include <utility> + #include "base/test/bind_test_util.h" #include "base/test/scoped_task_environment.h" #include "base/threading/thread_task_runner_handle.h" @@ -12,6 +16,31 @@ namespace device { namespace test { +namespace { + +// Simple class that resets a boolean flag when moved out from. +class MoveResets { + public: + MoveResets() = default; + MoveResets(bool value) : value_(value) {} // NOLINT(runtime/explicit) + MoveResets(const MoveResets&) = default; + MoveResets(MoveResets&& other) : value_(std::exchange(other.value_, false)) {} + MoveResets& operator=(const MoveResets&) = default; + MoveResets& operator=(MoveResets&& other) { + value_ = std::exchange(other.value_, false); + return *this; + } + + ~MoveResets() = default; + + bool value() const { return value_; } + + private: + bool value_ = false; +}; + +} // namespace + TEST(TestCallbackReceiver, BasicClosure) { base::test::ScopedTaskEnvironment task_environment; TestCallbackReceiver<> closure_receiver; @@ -64,22 +93,24 @@ TEST(TestCallbackReceiver, MoveOnlyArgumentIsMoved) { TEST(TestCallbackReceiver, ReferenceArgumentIsCopied) { base::test::ScopedTaskEnvironment task_environment; - TestCallbackReceiver<int&> callback_receiver; + TestCallbackReceiver<MoveResets&> callback_receiver; - int passed_in_value = 42; + MoveResets passed_in_value = true; auto callback = callback_receiver.callback(); EXPECT_FALSE(callback_receiver.result().has_value()); + // Make sure |passed_in_value| is not moved from. std::move(callback).Run(passed_in_value); - + EXPECT_TRUE(passed_in_value.value()); EXPECT_TRUE(callback_receiver.result().has_value()); - const int& received_value = std::get<0>(*callback_receiver.result()); - EXPECT_EQ(passed_in_value, received_value); + const MoveResets& received_value = std::get<0>(*callback_receiver.result()); + EXPECT_EQ(passed_in_value.value(), received_value.value()); - passed_in_value = 43; - EXPECT_NE(passed_in_value, received_value); + // Make sure |received_value| is not a reference to |passed_in_value|. + passed_in_value = false; + EXPECT_NE(passed_in_value.value(), received_value.value()); callback_receiver.TakeResult(); EXPECT_FALSE(callback_receiver.result().has_value()); diff --git a/chromium/device/fido/u2f_command_constructor.cc b/chromium/device/fido/u2f_command_constructor.cc new file mode 100644 index 00000000000..2b4fb1172ec --- /dev/null +++ b/chromium/device/fido/u2f_command_constructor.cc @@ -0,0 +1,130 @@ +// Copyright 2018 The Chromium 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 "device/fido/u2f_command_constructor.h" + +#include <string> +#include <utility> + +#include "components/apdu/apdu_command.h" +#include "device/fido/fido_parsing_utils.h" + +namespace device { + +bool IsConvertibleToU2fRegisterCommand( + const CtapMakeCredentialRequest& request) { + if (request.user_verification_required() || request.resident_key_supported()) + return false; + + const auto& public_key_credential_info = + request.public_key_credential_params().public_key_credential_params(); + return std::any_of( + public_key_credential_info.begin(), public_key_credential_info.end(), + [](const auto& credential_info) { + return credential_info.algorithm == + base::strict_cast<int>(CoseAlgorithmIdentifier::kCoseEs256); + }); +} + +bool IsConvertibleToU2fSignCommand(const CtapGetAssertionRequest& request) { + const auto& allow_list = request.allow_list(); + return request.user_verification() != + UserVerificationRequirement::kRequired && + allow_list && !allow_list->empty(); +} + +base::Optional<std::vector<uint8_t>> ConvertToU2fRegisterCommand( + const CtapMakeCredentialRequest& request) { + if (!IsConvertibleToU2fRegisterCommand(request)) + return base::nullopt; + + return ConstructU2fRegisterCommand( + fido_parsing_utils::CreateSHA256Hash(request.rp().rp_id()), + request.client_data_hash()); +} + +base::Optional<std::vector<uint8_t>> ConvertToU2fCheckOnlySignCommand( + const CtapMakeCredentialRequest& request, + const PublicKeyCredentialDescriptor& key_handle) { + if (key_handle.credential_type() != CredentialType::kPublicKey) + return base::nullopt; + + return ConstructU2fSignCommand( + fido_parsing_utils::CreateSHA256Hash(request.rp().rp_id()), + request.client_data_hash(), key_handle.id(), true /* check_only */); +} + +base::Optional<std::vector<uint8_t>> ConvertToU2fSignCommand( + const CtapGetAssertionRequest& request, + ApplicationParameterType application_parameter_type, + base::span<const uint8_t> key_handle, + bool check_only) { + if (!IsConvertibleToU2fSignCommand(request)) + return base::nullopt; + + auto application_parameter = + application_parameter_type == ApplicationParameterType::kPrimary + ? fido_parsing_utils::CreateSHA256Hash(request.rp_id()) + : std::vector<uint8_t>(); + + return ConstructU2fSignCommand(std::move(application_parameter), + request.client_data_hash(), key_handle, + check_only); +} + +base::Optional<std::vector<uint8_t>> ConstructU2fRegisterCommand( + base::span<const uint8_t> application_parameter, + base::span<const uint8_t> challenge_parameter, + bool is_individual_attestation) { + if (application_parameter.size() != kU2fParameterLength || + challenge_parameter.size() != kU2fParameterLength) { + return base::nullopt; + } + + std::vector<uint8_t> data; + data.reserve(challenge_parameter.size() + application_parameter.size()); + fido_parsing_utils::Append(&data, challenge_parameter); + fido_parsing_utils::Append(&data, application_parameter); + + apdu::ApduCommand command; + command.set_ins(base::strict_cast<uint8_t>(U2fApduInstruction::kRegister)); + command.set_p1(kP1TupRequiredConsumed | + (is_individual_attestation ? kP1IndividualAttestation : 0)); + command.set_data(std::move(data)); + command.set_response_length(apdu::ApduCommand::kApduMaxResponseLength); + return command.GetEncodedCommand(); +} + +base::Optional<std::vector<uint8_t>> ConstructU2fSignCommand( + base::span<const uint8_t> application_parameter, + base::span<const uint8_t> challenge_parameter, + base::span<const uint8_t> key_handle, + bool check_only) { + if (application_parameter.size() != kU2fParameterLength || + challenge_parameter.size() != kU2fParameterLength || + key_handle.size() > kMaxKeyHandleLength) { + return base::nullopt; + } + + std::vector<uint8_t> data; + data.reserve(challenge_parameter.size() + application_parameter.size() + 1 + + key_handle.size()); + fido_parsing_utils::Append(&data, challenge_parameter); + fido_parsing_utils::Append(&data, application_parameter); + data.push_back(static_cast<uint8_t>(key_handle.size())); + fido_parsing_utils::Append(&data, key_handle); + + apdu::ApduCommand command; + command.set_ins(base::strict_cast<uint8_t>(U2fApduInstruction::kSign)); + command.set_p1(check_only ? kP1CheckOnly : kP1TupRequiredConsumed); + command.set_data(std::move(data)); + command.set_response_length(apdu::ApduCommand::kApduMaxResponseLength); + return command.GetEncodedCommand(); +} + +base::Optional<std::vector<uint8_t>> ConstructBogusU2fRegistrationCommand() { + return ConstructU2fRegisterCommand(kBogusAppParam, kBogusChallenge); +} + +} // namespace device diff --git a/chromium/device/fido/u2f_command_constructor.h b/chromium/device/fido/u2f_command_constructor.h new file mode 100644 index 00000000000..69161d110cc --- /dev/null +++ b/chromium/device/fido/u2f_command_constructor.h @@ -0,0 +1,79 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_U2F_COMMAND_CONSTRUCTOR_H_ +#define DEVICE_FIDO_U2F_COMMAND_CONSTRUCTOR_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/component_export.h" +#include "base/containers/span.h" +#include "base/optional.h" +#include "device/fido/ctap_get_assertion_request.h" +#include "device/fido/ctap_make_credential_request.h" +#include "device/fido/fido_constants.h" + +namespace device { + +// Checks whether the request can be translated to valid U2F request +// parameter. Namely, U2F request does not support resident key and +// user verification, and ES256 algorithm must be used for public key +// credential. +// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#using-the-ctap2-authenticatormakecredential-command-with-ctap1-u2f-authenticators +COMPONENT_EXPORT(DEVICE_FIDO) +bool IsConvertibleToU2fRegisterCommand( + const CtapMakeCredentialRequest& request); + +// Checks whether user verification is not required and that allow list is +// not empty. +// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#using-the-ctap2-authenticatorgetassertion-command-with-ctap1-u2f-authenticators +COMPONENT_EXPORT(DEVICE_FIDO) +bool IsConvertibleToU2fSignCommand(const CtapGetAssertionRequest& request); + +// Extracts APDU encoded U2F register command from CtapMakeCredentialRequest. +COMPONENT_EXPORT(DEVICE_FIDO) +base::Optional<std::vector<uint8_t>> ConvertToU2fRegisterCommand( + const CtapMakeCredentialRequest& request); + +// Extracts APDU encoded U2F check only sign command from +// CtapMakeCredentialRequest. Invoked when U2F register operation includes key +// handles in exclude list. +COMPONENT_EXPORT(DEVICE_FIDO) +base::Optional<std::vector<uint8_t>> ConvertToU2fCheckOnlySignCommand( + const CtapMakeCredentialRequest& request, + const PublicKeyCredentialDescriptor& key_handle); + +// Extracts APDU encoded U2F sign command from CtapGetAssertionRequest. +COMPONENT_EXPORT(DEVICE_FIDO) +base::Optional<std::vector<uint8_t>> ConvertToU2fSignCommand( + const CtapGetAssertionRequest& request, + ApplicationParameterType application_parameter_type, + base::span<const uint8_t> key_handle, + bool check_only = false); + +// TODO(hongjunchoi): Move this logic inside ConvertToU2fRegisterCommand() +// once U2fRegister is removed. +COMPONENT_EXPORT(DEVICE_FIDO) +base::Optional<std::vector<uint8_t>> ConstructU2fRegisterCommand( + base::span<const uint8_t> application_parameter, + base::span<const uint8_t> challenge_parameter, + bool is_individual_attestation = false); + +// TODO(hongjunchoi): Move this logic inside ConvertToU2fSignCommand() once +// U2fSign is deleted. +COMPONENT_EXPORT(DEVICE_FIDO) +base::Optional<std::vector<uint8_t>> ConstructU2fSignCommand( + base::span<const uint8_t> application_parameter, + base::span<const uint8_t> challenge_parameter, + base::span<const uint8_t> key_handle, + bool check_only = false); + +COMPONENT_EXPORT(DEVICE_FIDO) +base::Optional<std::vector<uint8_t>> ConstructBogusU2fRegistrationCommand(); + +} // namespace device + +#endif // DEVICE_FIDO_U2F_COMMAND_CONSTRUCTOR_H_ diff --git a/chromium/device/fido/u2f_command_constructor_unittest.cc b/chromium/device/fido/u2f_command_constructor_unittest.cc new file mode 100644 index 00000000000..2824ba44fc8 --- /dev/null +++ b/chromium/device/fido/u2f_command_constructor_unittest.cc @@ -0,0 +1,240 @@ +// Copyright 2018 The Chromium 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 "device/fido/u2f_command_constructor.h" + +#include <utility> + +#include "device/fido/ctap_get_assertion_request.h" +#include "device/fido/ctap_make_credential_request.h" +#include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/fido_test_data.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +namespace { + +CtapMakeCredentialRequest ConstructMakeCredentialRequest() { + PublicKeyCredentialRpEntity rp("acme.com"); + rp.SetRpName("acme.com"); + + PublicKeyCredentialUserEntity user( + fido_parsing_utils::Materialize(test_data::kUserId)); + user.SetUserName("johnpsmith@example.com") + .SetDisplayName("John P. Smith") + .SetIconUrl(GURL("https://pics.acme.com/00/p/aBjjjpqPb.png")); + + return CtapMakeCredentialRequest( + fido_parsing_utils::Materialize(test_data::kClientDataHash), + std::move(rp), std::move(user), + PublicKeyCredentialParams(PublicKeyCredentialParams( + std::vector<PublicKeyCredentialParams::CredentialInfo>(1)))); +} + +CtapGetAssertionRequest ConstructGetAssertionRequest() { + return CtapGetAssertionRequest( + "acme.com", fido_parsing_utils::Materialize(test_data::kClientDataHash)); +} + +} // namespace + +TEST(U2fCommandConstructorTest, TestCreateU2fRegisterCommand) { + const auto& register_command_without_individual_attestation = + ConstructU2fRegisterCommand(test_data::kApplicationParameter, + test_data::kChallengeParameter, + false /* is_individual_attestation */); + + ASSERT_TRUE(register_command_without_individual_attestation); + EXPECT_THAT(*register_command_without_individual_attestation, + ::testing::ElementsAreArray(test_data::kU2fRegisterCommandApdu)); + + const auto& register_command_with_individual_attestation = + ConstructU2fRegisterCommand(test_data::kApplicationParameter, + test_data::kChallengeParameter, + true /* is_individual_attestation */); + + ASSERT_TRUE(register_command_with_individual_attestation); + EXPECT_THAT(*register_command_with_individual_attestation, + ::testing::ElementsAreArray( + test_data::kU2fRegisterCommandApduWithIndividualAttestation)); +} + +TEST(U2fCommandConstructorTest, TestCreateRegisterWithIncorrectParameters) { + std::vector<uint8_t> application_parameter(kU2fParameterLength, 0x01); + std::vector<uint8_t> challenge_parameter(kU2fParameterLength, 0xff); + + const auto& register_command_without_individual_attestation = + ConstructU2fRegisterCommand(application_parameter, challenge_parameter, + false /* is_individual_attestation */); + + ASSERT_TRUE(register_command_without_individual_attestation); + ASSERT_LE(3u, register_command_without_individual_attestation->size()); + // Individual attestation bit should be cleared. + EXPECT_EQ(0, (*register_command_without_individual_attestation)[2] & 0x80); + + const auto register_request_with_individual_attestation = + ConstructU2fRegisterCommand(application_parameter, challenge_parameter, + true /* is_individual_attestation */); + + ASSERT_TRUE(register_request_with_individual_attestation); + ASSERT_LE(3u, register_request_with_individual_attestation->size()); + // Individual attestation bit should be set. + EXPECT_EQ(0x80, (*register_request_with_individual_attestation)[2] & 0x80); + + // Expect null result with incorrectly sized application_parameter. + application_parameter.push_back(0xff); + auto incorrect_register_cmd = + ConstructU2fRegisterCommand(application_parameter, challenge_parameter, + false /* is_individual_attestation */); + + EXPECT_FALSE(incorrect_register_cmd); + application_parameter.pop_back(); + + // Expect null result with incorrectly sized challenge. + challenge_parameter.push_back(0xff); + incorrect_register_cmd = + ConstructU2fRegisterCommand(application_parameter, challenge_parameter, + false /* is_individual_attestation */); + + EXPECT_FALSE(incorrect_register_cmd); +} + +TEST(U2fCommandConstructorTest, TestConvertCtapMakeCredentialToU2fRegister) { + const auto make_credential_param = ConstructMakeCredentialRequest(); + + EXPECT_TRUE(IsConvertibleToU2fRegisterCommand(make_credential_param)); + + const auto u2f_register_command = + ConvertToU2fRegisterCommand(make_credential_param); + ASSERT_TRUE(u2f_register_command); + EXPECT_THAT(*u2f_register_command, + ::testing::ElementsAreArray(test_data::kU2fRegisterCommandApdu)); +} + +TEST(U2fCommandConstructorTest, + TestConvertCtapMakeCredentialToU2fCheckOnlySign) { + auto make_credential_param = ConstructMakeCredentialRequest(); + PublicKeyCredentialDescriptor credential_descriptor( + CredentialType::kPublicKey, + fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)); + std::vector<PublicKeyCredentialDescriptor> exclude_list; + exclude_list.push_back(credential_descriptor); + make_credential_param.SetExcludeList(std::move(exclude_list)); + EXPECT_TRUE(IsConvertibleToU2fRegisterCommand(make_credential_param)); + + const auto u2f_check_only_sign = ConvertToU2fCheckOnlySignCommand( + make_credential_param, credential_descriptor); + + ASSERT_TRUE(u2f_check_only_sign); + EXPECT_THAT( + *u2f_check_only_sign, + ::testing::ElementsAreArray(test_data::kU2fCheckOnlySignCommandApdu)); +} + +TEST(U2fCommandConstructorTest, + TestConvertCtapMakeCredentialToU2fCheckOnlySignWithInvalidCredentialType) { + auto make_credential_param = ConstructMakeCredentialRequest(); + PublicKeyCredentialDescriptor credential_descriptor( + // Purposefully construct an invalid CredentialType. + static_cast<CredentialType>(-1), + fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)); + std::vector<PublicKeyCredentialDescriptor> exclude_list; + exclude_list.push_back(credential_descriptor); + make_credential_param.SetExcludeList(std::move(exclude_list)); + EXPECT_TRUE(IsConvertibleToU2fRegisterCommand(make_credential_param)); + + const auto u2f_check_only_sign = ConvertToU2fCheckOnlySignCommand( + make_credential_param, credential_descriptor); + + EXPECT_FALSE(u2f_check_only_sign); +} + +TEST(U2fCommandConstructorTest, TestU2fRegisterCredentialAlgorithmRequirement) { + PublicKeyCredentialRpEntity rp("acme.com"); + rp.SetRpName("acme.com"); + + PublicKeyCredentialUserEntity user( + fido_parsing_utils::Materialize(test_data::kUserId)); + user.SetUserName("johnpsmith@example.com") + .SetDisplayName("John P. Smith") + .SetIconUrl(GURL("https://pics.acme.com/00/p/aBjjjpqPb.png")); + + CtapMakeCredentialRequest make_credential_param( + fido_parsing_utils::Materialize(test_data::kClientDataHash), + std::move(rp), std::move(user), + PublicKeyCredentialParams({{CredentialType::kPublicKey, -257}})); + + EXPECT_FALSE(IsConvertibleToU2fRegisterCommand(make_credential_param)); +} + +TEST(U2fCommandConstructorTest, TestU2fRegisterUserVerificationRequirement) { + auto make_credential_param = ConstructMakeCredentialRequest(); + make_credential_param.SetUserVerificationRequired(true); + + EXPECT_FALSE(IsConvertibleToU2fRegisterCommand(make_credential_param)); +} + +TEST(U2fCommandConstructorTest, TestU2fRegisterResidentKeyRequirement) { + auto make_credential_param = ConstructMakeCredentialRequest(); + make_credential_param.SetResidentKeySupported(true); + + EXPECT_FALSE(IsConvertibleToU2fRegisterCommand(make_credential_param)); +} + +TEST(U2fCommandConstructorTest, TestCreateSignApduCommand) { + const auto& encoded_sign = ConstructU2fSignCommand( + test_data::kApplicationParameter, test_data::kChallengeParameter, + test_data::kU2fSignKeyHandle); + ASSERT_TRUE(encoded_sign); + EXPECT_THAT(*encoded_sign, + ::testing::ElementsAreArray(test_data::kU2fSignCommandApdu)); + + const auto encoded_sign_check_only = ConstructU2fSignCommand( + test_data::kApplicationParameter, test_data::kChallengeParameter, + test_data::kU2fSignKeyHandle, true /* check_only */); + ASSERT_TRUE(encoded_sign_check_only); + EXPECT_THAT( + *encoded_sign_check_only, + ::testing::ElementsAreArray(test_data::kU2fCheckOnlySignCommandApdu)); +} + +TEST(U2fCommandConstructorTest, TestConvertCtapGetAssertionToU2fSignRequest) { + auto get_assertion_req = ConstructGetAssertionRequest(); + std::vector<PublicKeyCredentialDescriptor> allowed_list; + allowed_list.push_back(PublicKeyCredentialDescriptor( + CredentialType::kPublicKey, + fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle))); + get_assertion_req.SetAllowList(std::move(allowed_list)); + + const auto u2f_sign_command = ConvertToU2fSignCommand( + get_assertion_req, ApplicationParameterType::kPrimary, + fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)); + + EXPECT_TRUE(IsConvertibleToU2fSignCommand(get_assertion_req)); + ASSERT_TRUE(u2f_sign_command); + EXPECT_THAT(*u2f_sign_command, + ::testing::ElementsAreArray(test_data::kU2fSignCommandApdu)); +} + +TEST(U2fCommandConstructorTest, TestU2fSignAllowListRequirement) { + auto get_assertion_req = ConstructGetAssertionRequest(); + EXPECT_FALSE(IsConvertibleToU2fSignCommand(get_assertion_req)); +} + +TEST(U2fCommandConstructorTest, TestU2fSignUserVerificationRequirement) { + auto get_assertion_req = ConstructGetAssertionRequest(); + std::vector<PublicKeyCredentialDescriptor> allowed_list; + allowed_list.push_back(PublicKeyCredentialDescriptor( + CredentialType::kPublicKey, + fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle))); + get_assertion_req.SetAllowList(std::move(allowed_list)); + get_assertion_req.SetUserVerification(UserVerificationRequirement::kRequired); + + EXPECT_FALSE(IsConvertibleToU2fSignCommand(get_assertion_req)); +} + +} // namespace device diff --git a/chromium/device/fido/u2f_register.cc b/chromium/device/fido/u2f_register.cc index 4f6557cff85..a05818eaffc 100644 --- a/chromium/device/fido/u2f_register.cc +++ b/chromium/device/fido/u2f_register.cc @@ -10,6 +10,7 @@ #include "components/apdu/apdu_command.h" #include "components/apdu/apdu_response.h" #include "device/fido/authenticator_make_credential_response.h" +#include "device/fido/u2f_command_constructor.h" #include "services/service_manager/public/cpp/connector.h" namespace device { @@ -82,7 +83,7 @@ void U2fRegister::OnTryCheckRegistration( // Duplicate registration found. Call bogus registration to check for // user presence (touch) and terminate the registration process. InitiateDeviceTransaction( - U2fRequest::GetBogusRegisterCommand(), + ConstructBogusU2fRegistrationCommand(), base::BindOnce(&U2fRegister::OnTryDevice, weak_factory_.GetWeakPtr(), true /* is_duplicate_registration */)); break; diff --git a/chromium/device/fido/u2f_register_unittest.cc b/chromium/device/fido/u2f_register_unittest.cc index af6edcaac50..b29834525d9 100644 --- a/chromium/device/fido/u2f_register_unittest.cc +++ b/chromium/device/fido/u2f_register_unittest.cc @@ -9,21 +9,15 @@ #include "base/run_loop.h" #include "base/test/scoped_task_environment.h" -#include "components/cbor/cbor_writer.h" -#include "device/fido/attestation_object.h" -#include "device/fido/attested_credential_data.h" -#include "device/fido/authenticator_data.h" #include "device/fido/authenticator_make_credential_response.h" -#include "device/fido/ec_public_key.h" #include "device/fido/fake_fido_discovery.h" -#include "device/fido/fido_attestation_statement.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/fido_transport_protocol.h" #include "device/fido/mock_fido_device.h" #include "device/fido/test_callback_receiver.h" -#include "device/fido/u2f_parsing_utils.h" -#include "device/fido/virtual_fido_device.h" +#include "device/fido/virtual_u2f_device.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -33,129 +27,8 @@ using ::testing::_; namespace { -constexpr bool kNoIndividualAttestation = false; -constexpr bool kIndividualAttestation = true; - -// The attested credential data, excluding the public key bytes. Append -// with kTestECPublicKeyCOSE to get the complete attestation data. -constexpr uint8_t kTestAttestedCredentialDataPrefix[] = { - // clang-format off - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, // 16-byte aaguid - 0x00, 0x40, // 2-byte length - 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, - 0x26, 0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, - 0x36, 0xC3, 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, - 0xD7, 0xFF, 0x94, 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, - 0xDD, 0x39, 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, - 0xCC, 0xD4, 0x15, 0xCD, 0x08, 0xFE, 0x42, 0x00, 0x38, // 64-byte key handle - // clang-format on -}; - -// The authenticator data, excluding the attested credential data bytes. Append -// with attested credential data to get the complete authenticator data. -constexpr uint8_t kTestAuthenticatorDataPrefix[] = { - // clang-format off - // sha256 hash of rp id. - 0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, - 0xD7, 0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, - 0x7B, 0xCF, 0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE, - 0x41, // flags (TUP and AT bits set) - 0x00, 0x00, 0x00, 0x00 // counter - // clang-format on -}; - -// Components of the CBOR needed to form an authenticator object. -// Combined diagnostic notation: -// {"fmt": "fido-u2f", "attStmt": {"sig": h'30...}, "authData": h'D4C9D9...'} -constexpr uint8_t kFormatFidoU2fCBOR[] = { - // clang-format off - 0xA3, // map(3) - 0x63, // text(3) - 0x66, 0x6D, 0x74, // "fmt" - 0x68, // text(8) - 0x66, 0x69, 0x64, 0x6F, 0x2D, 0x75, 0x32, 0x66 // "fido-u2f" - // clang-format on -}; - -constexpr uint8_t kAttStmtCBOR[] = { - // clang-format off - 0x67, // text(7) - 0x61, 0x74, 0x74, 0x53, 0x74, 0x6D, 0x74 // "attStmt" - // clang-format on -}; - -constexpr uint8_t kAuthDataCBOR[] = { - // clang-format off - 0x68, // text(8) - 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, // "authData" - 0x58, 0xC4 // bytes(196). i.e.,the authenticator_data bytearray - // clang-format on -}; - -// Helpers for testing U2f register responses. -std::vector<uint8_t> GetTestECPublicKeyCOSE() { - return u2f_parsing_utils::Materialize(test_data::kTestECPublicKeyCOSE); -} - -std::vector<uint8_t> GetTestRegisterResponse() { - return u2f_parsing_utils::Materialize(test_data::kTestU2fRegisterResponse); -} - std::vector<uint8_t> GetTestRegisterRequest() { - return u2f_parsing_utils::Materialize(test_data::kU2fRegisterCommandApdu); -} - -std::vector<uint8_t> GetTestCredentialRawIdBytes() { - return u2f_parsing_utils::Materialize(test_data::kU2fSignKeyHandle); -} - -std::vector<uint8_t> GetU2fAttestationStatementCBOR() { - return u2f_parsing_utils::Materialize( - test_data::kU2fAttestationStatementCBOR); -} - -std::vector<uint8_t> GetTestAttestedCredentialDataBytes() { - // Combine kTestAttestedCredentialDataPrefix and kTestECPublicKeyCOSE. - auto test_attested_data = - u2f_parsing_utils::Materialize(kTestAttestedCredentialDataPrefix); - test_attested_data.insert(test_attested_data.end(), - std::begin(test_data::kTestECPublicKeyCOSE), - std::end(test_data::kTestECPublicKeyCOSE)); - return test_attested_data; -} - -std::vector<uint8_t> GetTestAuthenticatorDataBytes() { - // Build the test authenticator data. - auto test_authenticator_data = - u2f_parsing_utils::Materialize(kTestAuthenticatorDataPrefix); - std::vector<uint8_t> test_attested_data = - GetTestAttestedCredentialDataBytes(); - test_authenticator_data.insert(test_authenticator_data.end(), - test_attested_data.begin(), - test_attested_data.end()); - return test_authenticator_data; -} - -std::vector<uint8_t> GetTestAttestationObjectBytes() { - auto test_authenticator_object = - u2f_parsing_utils::Materialize(kFormatFidoU2fCBOR); - test_authenticator_object.insert(test_authenticator_object.end(), - std::begin(kAttStmtCBOR), - std::end(kAttStmtCBOR)); - test_authenticator_object.insert( - test_authenticator_object.end(), - std::begin(test_data::kU2fAttestationStatementCBOR), - std::end(test_data::kU2fAttestationStatementCBOR)); - test_authenticator_object.insert(test_authenticator_object.end(), - std::begin(kAuthDataCBOR), - std::end(kAuthDataCBOR)); - std::vector<uint8_t> test_authenticator_data = - GetTestAuthenticatorDataBytes(); - test_authenticator_object.insert(test_authenticator_object.end(), - test_authenticator_data.begin(), - test_authenticator_data.end()); - return test_authenticator_object; + return fido_parsing_utils::Materialize(test_data::kU2fRegisterCommandApdu); } using TestRegisterCallback = ::device::test::StatusAndValueCallbackReceiver< @@ -185,22 +58,20 @@ class U2fRegisterTest : public ::testing::Test { base::flat_set<FidoTransportProtocol>( {FidoTransportProtocol::kUsbHumanInterfaceDevice}), registered_keys, - u2f_parsing_utils::Materialize(test_data::kChallengeParameter), - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), - kNoIndividualAttestation, register_callback_receiver_.callback()); + fido_parsing_utils::Materialize(test_data::kChallengeParameter), + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + false /* is_individual_attestation */, + register_callback_receiver_.callback()); } test::FakeFidoDiscovery* discovery() const { return discovery_; } + TestRegisterCallback& register_callback_receiver() { return register_callback_receiver_; } protected: base::test::ScopedTaskEnvironment scoped_task_environment_; - std::vector<uint8_t> application_parameter_ = - u2f_parsing_utils::Materialize(test_data::kApplicationParameter); - std::vector<uint8_t> challenge_parameter_ = - u2f_parsing_utils::Materialize(test_data::kChallengeParameter); std::vector<std::vector<uint8_t>> key_handles_; base::flat_set<FidoTransportProtocol> protocols_; test::ScopedFakeFidoDiscoveryFactory scoped_fake_discovery_factory_; @@ -208,69 +79,6 @@ class U2fRegisterTest : public ::testing::Test { TestRegisterCallback register_callback_receiver_; }; -TEST_F(U2fRegisterTest, TestCreateU2fRegisterCommand) { - U2fRegister register_request(nullptr /* connector */, protocols_, - key_handles_, challenge_parameter_, - application_parameter_, kNoIndividualAttestation, - register_callback_receiver().callback()); - - const auto register_command_without_individual_attestation = - register_request.GetU2fRegisterApduCommand(kNoIndividualAttestation); - - ASSERT_TRUE(register_command_without_individual_attestation); - EXPECT_THAT(*register_command_without_individual_attestation, - ::testing::ElementsAreArray(test_data::kU2fRegisterCommandApdu)); - - const auto register_command_with_individual_attestation = - register_request.GetU2fRegisterApduCommand(kIndividualAttestation); - ASSERT_TRUE(register_command_with_individual_attestation); - EXPECT_THAT(*register_command_with_individual_attestation, - ::testing::ElementsAreArray( - test_data::kU2fRegisterCommandApduWithIndividualAttestation)); -} - -TEST_F(U2fRegisterTest, TestCreateRegisterWithIncorrectParameters) { - std::vector<uint8_t> application_parameter(kU2fParameterLength, 0x01); - std::vector<uint8_t> challenge_parameter(kU2fParameterLength, 0xff); - - U2fRegister register_request(nullptr, protocols_, key_handles_, - challenge_parameter, application_parameter, - kNoIndividualAttestation, - register_callback_receiver().callback()); - const auto register_without_individual_attestation = - register_request.GetU2fRegisterApduCommand(kNoIndividualAttestation); - - ASSERT_TRUE(register_without_individual_attestation); - ASSERT_LE(3u, register_without_individual_attestation->size()); - // Individual attestation bit should be cleared. - EXPECT_EQ(0, (*register_without_individual_attestation)[2] & 0x80); - - const auto register_request_with_individual_attestation = - register_request.GetU2fRegisterApduCommand(kIndividualAttestation); - ASSERT_TRUE(register_request_with_individual_attestation); - ASSERT_LE(3u, register_request_with_individual_attestation->size()); - // Individual attestation bit should be set. - EXPECT_EQ(0x80, (*register_request_with_individual_attestation)[2] & 0x80); - - // Expect null result with incorrectly sized application_parameter. - application_parameter.push_back(0xff); - auto incorrect_register_cmd = - U2fRegister(nullptr, protocols_, key_handles_, challenge_parameter, - application_parameter, kNoIndividualAttestation, - register_callback_receiver().callback()) - .GetU2fRegisterApduCommand(kNoIndividualAttestation); - EXPECT_FALSE(incorrect_register_cmd); - application_parameter.pop_back(); - - // Expect null result with incorrectly sized challenge. - challenge_parameter.push_back(0xff); - incorrect_register_cmd = - U2fRegister(nullptr, protocols_, key_handles_, challenge_parameter, - application_parameter, kNoIndividualAttestation, - register_callback_receiver().callback()) - .GetU2fRegisterApduCommand(kNoIndividualAttestation); - EXPECT_FALSE(incorrect_register_cmd); -} TEST_F(U2fRegisterTest, TestRegisterSuccess) { auto request = CreateRegisterRequest(); @@ -288,8 +96,8 @@ TEST_F(U2fRegisterTest, TestRegisterSuccess) { register_callback_receiver().WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, register_callback_receiver().status()); ASSERT_TRUE(register_callback_receiver().value()); - EXPECT_EQ(GetTestCredentialRawIdBytes(), - register_callback_receiver().value()->raw_credential_id()); + EXPECT_THAT(register_callback_receiver().value()->raw_credential_id(), + ::testing::ElementsAreArray(test_data::kU2fSignKeyHandle)); } TEST_F(U2fRegisterTest, TestRegisterSuccessWithFake) { @@ -297,7 +105,7 @@ TEST_F(U2fRegisterTest, TestRegisterSuccessWithFake) { request->Start(); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<VirtualFidoDevice>(); + auto device = std::make_unique<VirtualU2fDevice>(); discovery()->AddDevice(std::move(device)); register_callback_receiver().WaitForCallback(); @@ -328,8 +136,8 @@ TEST_F(U2fRegisterTest, TestDelayedSuccess) { register_callback_receiver().WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, register_callback_receiver().status()); ASSERT_TRUE(register_callback_receiver().value()); - EXPECT_EQ(GetTestCredentialRawIdBytes(), - register_callback_receiver().value()->raw_credential_id()); + EXPECT_THAT(register_callback_receiver().value()->raw_credential_id(), + ::testing::ElementsAreArray(test_data::kU2fSignKeyHandle)); } TEST_F(U2fRegisterTest, TestMultipleDevices) { @@ -359,8 +167,8 @@ TEST_F(U2fRegisterTest, TestMultipleDevices) { register_callback_receiver().WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, register_callback_receiver().status()); ASSERT_TRUE(register_callback_receiver().value()); - EXPECT_EQ(GetTestCredentialRawIdBytes(), - register_callback_receiver().value()->raw_credential_id()); + EXPECT_THAT(register_callback_receiver().value()->raw_credential_id(), + ::testing::ElementsAreArray(test_data::kU2fSignKeyHandle)); } // Tests a scenario where a single device is connected and registration call @@ -369,8 +177,8 @@ TEST_F(U2fRegisterTest, TestMultipleDevices) { TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithExclusionList) { // Simulate three unknown key handles. auto request = CreateRegisterRequestWithRegisteredKeys( - {u2f_parsing_utils::Materialize(test_data::kKeyHandleAlpha), - u2f_parsing_utils::Materialize(test_data::kKeyHandleBeta)}); + {fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha), + fido_parsing_utils::Materialize(test_data::kKeyHandleBeta)}); request->Start(); discovery()->WaitForCallToStartAndSimulateSuccess(); @@ -383,14 +191,14 @@ TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithExclusionList) { // call, MockFidoDevice::NoErrorRegister will be invoked after registration. EXPECT_CALL(*device.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL( *device.get(), - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); @@ -409,8 +217,8 @@ TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithExclusionList) { register_callback_receiver().WaitForCallback(); ASSERT_TRUE(register_callback_receiver().value()); EXPECT_EQ(FidoReturnCode::kSuccess, register_callback_receiver().status()); - EXPECT_EQ(GetTestCredentialRawIdBytes(), - register_callback_receiver().value()->raw_credential_id()); + EXPECT_THAT(register_callback_receiver().value()->raw_credential_id(), + ::testing::ElementsAreArray(test_data::kU2fSignKeyHandle)); } // Tests a scenario where two devices are connected and registration call is @@ -419,9 +227,9 @@ TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithExclusionList) { TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithExclusionList) { // Simulate three unknown key handles. auto request = CreateRegisterRequestWithRegisteredKeys( - {u2f_parsing_utils::Materialize(test_data::kKeyHandleAlpha), - u2f_parsing_utils::Materialize(test_data::kKeyHandleBeta), - u2f_parsing_utils::Materialize(test_data::kKeyHandleGamma)}); + {fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha), + fido_parsing_utils::Materialize(test_data::kKeyHandleBeta), + fido_parsing_utils::Materialize(test_data::kKeyHandleGamma)}); request->Start(); auto device0 = std::make_unique<MockFidoDevice>(); @@ -432,21 +240,21 @@ TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithExclusionList) { // MockFidoDevice::NotSatisfied. EXPECT_CALL(*device0.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL( *device0.get(), - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device0.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyGamma), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); @@ -468,21 +276,21 @@ TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithExclusionList) { // successful registration. EXPECT_CALL(*device1.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL( *device1.get(), - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device1.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyGamma), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); @@ -501,8 +309,8 @@ TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithExclusionList) { register_callback_receiver().WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, register_callback_receiver().status()); ASSERT_TRUE(register_callback_receiver().value()); - EXPECT_EQ(GetTestCredentialRawIdBytes(), - register_callback_receiver().value()->raw_credential_id()); + EXPECT_THAT(register_callback_receiver().value()->raw_credential_id(), + ::testing::ElementsAreArray(test_data::kU2fSignKeyHandle)); } // Tests a scenario where single device is connected and registration is called @@ -513,9 +321,9 @@ TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithExclusionList) { TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithDuplicateHandle) { // Simulate three unknown key handles followed by a duplicate key. auto request = CreateRegisterRequestWithRegisteredKeys( - {u2f_parsing_utils::Materialize(test_data::kKeyHandleAlpha), - u2f_parsing_utils::Materialize(test_data::kKeyHandleBeta), - u2f_parsing_utils::Materialize(test_data::kKeyHandleGamma)}); + {fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha), + fido_parsing_utils::Materialize(test_data::kKeyHandleBeta), + fido_parsing_utils::Materialize(test_data::kKeyHandleGamma)}); request->Start(); discovery()->WaitForCallToStartAndSimulateSuccess(); @@ -528,27 +336,27 @@ TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithDuplicateHandle) { // MockFidoDevice::NoErrorRegister. EXPECT_CALL(*device.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL( *device.get(), - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyGamma), _)) .WillOnce(::testing::Invoke(MockFidoDevice::NoErrorSign)); EXPECT_CALL(*device.get(), - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fFakeRegisterCommand), _)) .WillOnce(::testing::Invoke(MockFidoDevice::NoErrorRegister)); @@ -572,9 +380,9 @@ TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithDuplicateHandle) { TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithDuplicateHandle) { // Simulate two unknown key handles followed by a duplicate key. auto request = CreateRegisterRequestWithRegisteredKeys( - {u2f_parsing_utils::Materialize(test_data::kKeyHandleAlpha), - u2f_parsing_utils::Materialize(test_data::kKeyHandleBeta), - u2f_parsing_utils::Materialize(test_data::kKeyHandleGamma)}); + {fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha), + fido_parsing_utils::Materialize(test_data::kKeyHandleBeta), + fido_parsing_utils::Materialize(test_data::kKeyHandleGamma)}); request->Start(); // Since the first device did not create any of the key handles provided in @@ -585,21 +393,21 @@ TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithDuplicateHandle) { EXPECT_CALL(*device0, GetId()).WillRepeatedly(testing::Return("device0")); EXPECT_CALL(*device0.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL( *device0.get(), - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device0.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyGamma), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); @@ -618,27 +426,27 @@ TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithDuplicateHandle) { EXPECT_CALL(*device1, GetId()).WillRepeatedly(::testing::Return("device1")); EXPECT_CALL(*device1.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL( *device1.get(), - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device1.get(), DeviceTransactPtr( - u2f_parsing_utils::Materialize( + fido_parsing_utils::Materialize( test_data::kU2fCheckOnlySignCommandApduWithKeyGamma), _)) .WillOnce(::testing::Invoke(MockFidoDevice::NoErrorSign)); EXPECT_CALL(*device1.get(), - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fFakeRegisterCommand), _)) .WillOnce(::testing::Invoke(MockFidoDevice::NoErrorRegister)); @@ -654,100 +462,6 @@ TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithDuplicateHandle) { EXPECT_FALSE(register_callback_receiver().value()); } -// These test the parsing of the U2F raw bytes of the registration response. -// Test that an EC public key serializes to CBOR properly. -TEST_F(U2fRegisterTest, TestSerializedPublicKey) { - std::unique_ptr<ECPublicKey> public_key = - ECPublicKey::ExtractFromU2fRegistrationResponse( - u2f_parsing_utils::kEs256, GetTestRegisterResponse()); - EXPECT_EQ(GetTestECPublicKeyCOSE(), public_key->EncodeAsCOSEKey()); -} - -// Test that the attestation statement cbor map is constructed properly. -TEST_F(U2fRegisterTest, TestU2fAttestationStatementCBOR) { - std::unique_ptr<FidoAttestationStatement> fido_attestation_statement = - FidoAttestationStatement::CreateFromU2fRegisterResponse( - GetTestRegisterResponse()); - auto cbor = cbor::CBORWriter::Write( - cbor::CBORValue(fido_attestation_statement->GetAsCBORMap())); - ASSERT_TRUE(cbor); - EXPECT_EQ(GetU2fAttestationStatementCBOR(), *cbor); -} - -// Tests that well-formed attested credential data serializes properly. -TEST_F(U2fRegisterTest, TestAttestedCredentialData) { - std::unique_ptr<ECPublicKey> public_key = - ECPublicKey::ExtractFromU2fRegistrationResponse( - u2f_parsing_utils::kEs256, GetTestRegisterResponse()); - base::Optional<AttestedCredentialData> attested_data = - AttestedCredentialData::CreateFromU2fRegisterResponse( - GetTestRegisterResponse(), std::move(public_key)); - - EXPECT_EQ(GetTestAttestedCredentialDataBytes(), - attested_data->SerializeAsBytes()); -} - -// Tests that well-formed authenticator data serializes properly. -TEST_F(U2fRegisterTest, TestAuthenticatorData) { - std::unique_ptr<ECPublicKey> public_key = - ECPublicKey::ExtractFromU2fRegistrationResponse( - u2f_parsing_utils::kEs256, GetTestRegisterResponse()); - base::Optional<AttestedCredentialData> attested_data = - AttestedCredentialData::CreateFromU2fRegisterResponse( - GetTestRegisterResponse(), std::move(public_key)); - - constexpr uint8_t flags = - static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence) | - static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); - - AuthenticatorData authenticator_data( - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), flags, - std::vector<uint8_t>(4) /* counter */, std::move(attested_data)); - - EXPECT_EQ(GetTestAuthenticatorDataBytes(), - authenticator_data.SerializeToByteArray()); -} - -// Tests that a U2F attestation object serializes properly. -TEST_F(U2fRegisterTest, TestU2fAttestationObject) { - std::unique_ptr<ECPublicKey> public_key = - ECPublicKey::ExtractFromU2fRegistrationResponse( - u2f_parsing_utils::kEs256, GetTestRegisterResponse()); - base::Optional<AttestedCredentialData> attested_data = - AttestedCredentialData::CreateFromU2fRegisterResponse( - GetTestRegisterResponse(), std::move(public_key)); - - constexpr uint8_t flags = - static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence) | - static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); - AuthenticatorData authenticator_data( - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), flags, - std::vector<uint8_t>(4) /* counter */, std::move(attested_data)); - - // Construct the attestation statement. - std::unique_ptr<FidoAttestationStatement> fido_attestation_statement = - FidoAttestationStatement::CreateFromU2fRegisterResponse( - GetTestRegisterResponse()); - - // Construct the attestation object. - auto attestation_object = std::make_unique<AttestationObject>( - std::move(authenticator_data), std::move(fido_attestation_statement)); - - EXPECT_EQ(GetTestAttestationObjectBytes(), - attestation_object->SerializeToCBOREncodedBytes()); -} - -// Test that a U2F register response is properly parsed. -TEST_F(U2fRegisterTest, TestRegisterResponseData) { - base::Optional<AuthenticatorMakeCredentialResponse> response = - AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse( - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), - GetTestRegisterResponse()); - EXPECT_EQ(GetTestCredentialRawIdBytes(), response->raw_credential_id()); - EXPECT_EQ(GetTestAttestationObjectBytes(), - response->GetCBOREncodedAttestationObject()); -} - MATCHER_P(IndicatesIndividualAttestation, expected, "") { return arg.size() > 2 && ((arg[2] & 0x80) == 0x80) == expected; } @@ -764,7 +478,9 @@ TEST_F(U2fRegisterTest, TestIndividualAttestation) { nullptr /* connector */, base::flat_set<FidoTransportProtocol>( {FidoTransportProtocol::kUsbHumanInterfaceDevice}) /* transports */, - key_handles_, challenge_parameter_, application_parameter_, + key_handles_, + fido_parsing_utils::Materialize(test_data::kChallengeParameter), + fido_parsing_utils::Materialize(test_data::kApplicationParameter), individual_attestation, cb.callback()); request->Start(); discovery()->WaitForCallToStartAndSimulateSuccess(); @@ -782,7 +498,8 @@ TEST_F(U2fRegisterTest, TestIndividualAttestation) { cb.WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, cb.status()); ASSERT_TRUE(cb.value()); - EXPECT_EQ(GetTestCredentialRawIdBytes(), cb.value()->raw_credential_id()); + EXPECT_THAT(cb.value()->raw_credential_id(), + ::testing::ElementsAreArray(test_data::kU2fSignKeyHandle)); } } diff --git a/chromium/device/fido/u2f_request.cc b/chromium/device/fido/u2f_request.cc index f5fb0f26d2e..fd81c6cc2fa 100644 --- a/chromium/device/fido/u2f_request.cc +++ b/chromium/device/fido/u2f_request.cc @@ -13,7 +13,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "components/apdu/apdu_command.h" -#include "components/apdu/apdu_response.h" +#include "device/fido/u2f_command_constructor.h" #include "services/service_manager/public/cpp/connector.h" namespace device { @@ -50,73 +50,18 @@ void U2fRequest::Start() { } } -// static -std::vector<uint8_t> U2fRequest::GetBogusRegisterCommand() { - apdu::ApduCommand command; - std::vector<uint8_t> data(kBogusChallenge.cbegin(), kBogusChallenge.cend()); - data.insert(data.end(), kBogusAppParam.cbegin(), kBogusAppParam.cend()); - command.set_ins(base::strict_cast<uint8_t>(U2fApduInstruction::kRegister)); - command.set_p1(kP1TupRequiredConsumed); - command.set_data(data); - return command.GetEncodedCommand(); -} - -// static -std::vector<uint8_t> U2fRequest::GetU2fVersionApduCommand( - bool is_legacy_version) { - apdu::ApduCommand command; - command.set_ins(base::strict_cast<uint8_t>(U2fApduInstruction::kVersion)); - // Set maximum expected response length to maximum length possible. - command.set_response_length(kU2fMaxResponseSize); - // Early U2F drafts defined the U2F version command a format - // incompatible with ISO 7816-4, so 2 additional 0x0 bytes are necessary. - // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#implementation-considerations - auto version_cmd = command.GetEncodedCommand(); - if (is_legacy_version) - version_cmd.insert(version_cmd.end(), kLegacyVersionSuffix.cbegin(), - kLegacyVersionSuffix.cend()); - - return version_cmd; -} - base::Optional<std::vector<uint8_t>> U2fRequest::GetU2fSignApduCommand( const std::vector<uint8_t>& application_parameter, const std::vector<uint8_t>& key_handle, bool is_check_only_sign) const { - if (application_parameter.size() != kU2fParameterLength || - challenge_digest_.size() != kU2fParameterLength || - key_handle.size() > kMaxKeyHandleLength) { - return base::nullopt; - } - apdu::ApduCommand command; - std::vector<uint8_t> data(challenge_digest_.begin(), challenge_digest_.end()); - data.insert(data.end(), application_parameter.begin(), - application_parameter.end()); - data.push_back(static_cast<uint8_t>(key_handle.size())); - data.insert(data.end(), key_handle.begin(), key_handle.end()); - command.set_ins(base::strict_cast<uint8_t>(U2fApduInstruction::kSign)); - command.set_p1(is_check_only_sign ? kP1CheckOnly : kP1TupRequiredConsumed); - command.set_data(data); - command.set_response_length(apdu::ApduCommand::kApduMaxResponseLength); - return command.GetEncodedCommand(); + return ConstructU2fSignCommand(application_parameter, challenge_digest_, + key_handle, is_check_only_sign); } base::Optional<std::vector<uint8_t>> U2fRequest::GetU2fRegisterApduCommand( bool is_individual_attestation) const { - if (application_parameter_.size() != kU2fParameterLength || - challenge_digest_.size() != kU2fParameterLength) { - return base::nullopt; - } - apdu::ApduCommand command; - std::vector<uint8_t> data(challenge_digest_.begin(), challenge_digest_.end()); - data.insert(data.end(), application_parameter_.begin(), - application_parameter_.end()); - command.set_ins(base::strict_cast<uint8_t>(U2fApduInstruction::kRegister)); - command.set_p1(kP1TupRequiredConsumed | - (is_individual_attestation ? kP1IndividualAttestation : 0)); - command.set_data(data); - command.set_response_length(apdu::ApduCommand::kApduMaxResponseLength); - return command.GetEncodedCommand(); + return ConstructU2fRegisterCommand(application_parameter_, challenge_digest_, + is_individual_attestation); } void U2fRequest::Transition() { @@ -151,31 +96,6 @@ void U2fRequest::InitiateDeviceTransaction( current_device_->DeviceTransact(std::move(*cmd), std::move(callback)); } -void U2fRequest::OnDeviceVersionRequest( - VersionCallback callback, - base::WeakPtr<FidoDevice> device, - bool legacy, - base::Optional<std::vector<uint8_t>> response) { - const auto apdu_response = - response ? apdu::ApduResponse::CreateFromMessage(std::move(*response)) - : base::nullopt; - if (apdu_response && - apdu_response->status() == apdu::ApduResponse::Status::SW_NO_ERROR && - std::equal(apdu_response->data().cbegin(), apdu_response->data().cend(), - kU2fVersionResponse.cbegin(), kU2fVersionResponse.cend())) { - std::move(callback).Run(ProtocolVersion::kU2f); - } else if (!legacy) { - // Standard GetVersion failed, attempt legacy GetVersion command. - device->DeviceTransact( - GetU2fVersionApduCommand(true), - base::BindOnce(&U2fRequest::OnDeviceVersionRequest, - weak_factory_.GetWeakPtr(), std::move(callback), device, - true /* legacy */)); - } else { - std::move(callback).Run(ProtocolVersion::kUnknown); - } -} - void U2fRequest::AbandonCurrentDeviceAndTransition() { DCHECK_NE(nullptr, current_device_); abandoned_devices_.emplace_back(std::exchange(current_device_, nullptr)); diff --git a/chromium/device/fido/u2f_request.h b/chromium/device/fido/u2f_request.h index 2ed0157a5f4..e8717f0fa7f 100644 --- a/chromium/device/fido/u2f_request.h +++ b/chromium/device/fido/u2f_request.h @@ -52,12 +52,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) U2fRequest // registration process. That is, check-only sign command is sent during // registration to prevent duplicate registration. // - // Returns bogus register command to be used to verify user presence. - static std::vector<uint8_t> GetBogusRegisterCommand(); - // Returns APDU formatted U2F version request command. If |is_legacy_version| - // is set to true, suffix {0x00, 0x00} is added at the end. - static std::vector<uint8_t> GetU2fVersionApduCommand( - bool is_legacy_version = false); // Returns APDU U2F request commands. Null optional is returned for // incorrectly formatted parameter. base::Optional<std::vector<uint8_t>> GetU2fSignApduCommand( @@ -83,12 +77,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) U2fRequest // |current_device_|. void InitiateDeviceTransaction(base::Optional<std::vector<uint8_t>> cmd, FidoDevice::DeviceCallback callback); - // Callback function to U2F version request. If non-legacy version request - // fails, retry with legacy version request. - void OnDeviceVersionRequest(VersionCallback callback, - base::WeakPtr<FidoDevice> device, - bool legacy, - base::Optional<std::vector<uint8_t>> response); virtual void TryDevice() = 0; diff --git a/chromium/device/fido/u2f_request_unittest.cc b/chromium/device/fido/u2f_request_unittest.cc index b4b9d930ca7..33124f01557 100644 --- a/chromium/device/fido/u2f_request_unittest.cc +++ b/chromium/device/fido/u2f_request_unittest.cc @@ -37,7 +37,7 @@ class FakeU2fRequest : public U2fRequest { }; using TestVersionCallback = - ::device::test::TestCallbackReceiver<ProtocolVersion>; + ::device::test::ValueCallbackReceiver<ProtocolVersion>; } // namespace @@ -361,48 +361,4 @@ TEST_F(U2fRequestTest, TestMultipleDiscoveriesWithFailures) { } } -TEST_F(U2fRequestTest, TestEncodeVersionRequest) { - constexpr uint8_t kEncodedU2fVersionRequest[] = {0x00, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00}; - EXPECT_THAT(U2fRequest::GetU2fVersionApduCommand(), - ::testing::ElementsAreArray(kEncodedU2fVersionRequest)); - - // Legacy version command contains 2 extra null bytes compared to ISO 7816-4 - // format. - constexpr uint8_t kEncodedU2fLegacyVersionRequest[] = { - 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - EXPECT_THAT(U2fRequest::GetU2fVersionApduCommand(true), - ::testing::ElementsAreArray(kEncodedU2fLegacyVersionRequest)); -} - -// Test a scenario when version request is sent to legacy U2F token. -// After non-legacy version requests fails, legacy version request should be -// sent to device as a retry. -TEST_F(U2fRequestTest, TestLegacyVersionRequest) { - auto* discovery = discovery_factory().ForgeNextHidDiscovery(); - FakeU2fRequest request({FidoTransportProtocol::kUsbHumanInterfaceDevice}); - request.Start(); - - auto device0 = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device0, GetId()).WillRepeatedly(::testing::Return("device0")); - EXPECT_CALL(*device0, - DeviceTransactPtr(U2fRequest::GetU2fVersionApduCommand(true), _)) - // Success response for legacy version request after retry. - .WillOnce(testing::Invoke(MockFidoDevice::NoErrorVersion)); - - auto* device_ptr = device0.get(); - discovery->AddDevice(std::move(device0)); - - // Represents version callback received from legacy U2F token on initial - // version request. Device responses with invalid protocol version (in this - // case, empty byte array). Retry version request with legacy bit is expected - // to be issued afterwards. - request.OnDeviceVersionRequest(version_callback_receiver().callback(), - device_ptr->GetWeakPtr(), false /* legacy */, - std::vector<uint8_t>()); - - EXPECT_EQ(ProtocolVersion::kU2f, - std::get<0>(*version_callback_receiver().result())); -} - } // namespace device diff --git a/chromium/device/fido/u2f_sign.cc b/chromium/device/fido/u2f_sign.cc index fd7b44df8e3..1cad2c8c8fa 100644 --- a/chromium/device/fido/u2f_sign.cc +++ b/chromium/device/fido/u2f_sign.cc @@ -8,6 +8,7 @@ #include "components/apdu/apdu_command.h" #include "components/apdu/apdu_response.h" +#include "device/fido/u2f_command_constructor.h" #include "services/service_manager/public/cpp/connector.h" namespace device { @@ -137,7 +138,7 @@ void U2fSign::OnTryDevice(std::vector<std::vector<uint8_t>>::const_iterator it, // it's not registered. Once the user consents to use the device, // the relying party can inform them that it hasn't been registered. InitiateDeviceTransaction( - U2fRequest::GetBogusRegisterCommand(), + ConstructBogusU2fRegistrationCommand(), base::BindOnce(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(), registered_keys_.cend(), ApplicationParameterType::kPrimary)); diff --git a/chromium/device/fido/u2f_sign.h b/chromium/device/fido/u2f_sign.h index 6dccad0afaa..10cad3e122b 100644 --- a/chromium/device/fido/u2f_sign.h +++ b/chromium/device/fido/u2f_sign.h @@ -49,19 +49,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) U2fSign : public U2fRequest { ~U2fSign() override; private: - // Enumerates the two types of |application_parameter| values used: the - // "primary" value is the hash of the relying party ID[1] and is always - // provided. The "alternative" value is the hash of a U2F AppID, specified in - // an extension[2], for compatibility with keys that were registered with the - // old API. - // - // [1] https://w3c.github.io/webauthn/#rp-id - // [2] https://w3c.github.io/webauthn/#sctn-appid-extension - enum class ApplicationParameterType { - kPrimary, - kAlternative, - }; - void TryDevice() override; void OnTryDevice(std::vector<std::vector<uint8_t>>::const_iterator it, ApplicationParameterType application_parameter_type, diff --git a/chromium/device/fido/u2f_sign_unittest.cc b/chromium/device/fido/u2f_sign_unittest.cc index 44c51211fda..23b972ddcc0 100644 --- a/chromium/device/fido/u2f_sign_unittest.cc +++ b/chromium/device/fido/u2f_sign_unittest.cc @@ -9,17 +9,15 @@ #include "base/run_loop.h" #include "base/test/scoped_task_environment.h" #include "crypto/ec_private_key.h" -#include "crypto/sha2.h" -#include "device/fido/authenticator_data.h" #include "device/fido/authenticator_get_assertion_response.h" #include "device/fido/fake_fido_discovery.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/fido_transport_protocol.h" #include "device/fido/mock_fido_device.h" #include "device/fido/test_callback_receiver.h" -#include "device/fido/u2f_parsing_utils.h" -#include "device/fido/virtual_fido_device.h" +#include "device/fido/virtual_u2f_device.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -30,34 +28,11 @@ namespace device { namespace { std::vector<uint8_t> GetTestCredentialRawIdBytes() { - return u2f_parsing_utils::Materialize(test_data::kU2fSignKeyHandle); + return fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle); } std::vector<uint8_t> GetU2fSignCommandWithCorrectCredential() { - return u2f_parsing_utils::Materialize(test_data::kU2fSignCommandApdu); -} - -std::vector<uint8_t> GetTestSignResponse() { - return u2f_parsing_utils::Materialize(test_data::kTestU2fSignResponse); -} - -std::vector<uint8_t> GetTestAuthenticatorData() { - return u2f_parsing_utils::Materialize(test_data::kTestSignAuthenticatorData); -} - -std::vector<uint8_t> GetTestAssertionSignature() { - return u2f_parsing_utils::Materialize(test_data::kU2fSignature); -} - -std::vector<uint8_t> GetTestSignatureCounter() { - return u2f_parsing_utils::Materialize(test_data::kTestSignatureCounter); -} - -// Get a subset of the response for testing error handling. -std::vector<uint8_t> GetTestCorruptedSignResponse(size_t length) { - DCHECK_LE(length, arraysize(test_data::kTestU2fSignResponse)); - return u2f_parsing_utils::Materialize(u2f_parsing_utils::ExtractSpan( - test_data::kTestU2fSignResponse, 0, length)); + return fido_parsing_utils::Materialize(test_data::kU2fSignCommandApdu); } using TestSignCallback = ::device::test::StatusAndValueCallbackReceiver< @@ -87,8 +62,10 @@ class U2fSignTest : public ::testing::Test { nullptr /* connector */, base::flat_set<FidoTransportProtocol>( {FidoTransportProtocol::kUsbHumanInterfaceDevice}), - std::move(registered_keys), challenge_parameter_, - application_parameter_, base::nullopt, + std::move(registered_keys), + fido_parsing_utils::Materialize(test_data::kChallengeParameter), + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + base::nullopt /* alt_application_parameter*/, sign_callback_receiver_.callback()); } @@ -97,32 +74,12 @@ class U2fSignTest : public ::testing::Test { protected: base::test::ScopedTaskEnvironment scoped_task_environment_; - std::vector<uint8_t> application_parameter_ = - u2f_parsing_utils::Materialize(test_data::kApplicationParameter); - std::vector<uint8_t> challenge_parameter_ = - u2f_parsing_utils::Materialize(test_data::kChallengeParameter); test::ScopedFakeFidoDiscoveryFactory scoped_fake_discovery_factory_; test::FakeFidoDiscovery* discovery_; TestSignCallback sign_callback_receiver_; base::flat_set<FidoTransportProtocol> protocols_; }; -TEST_F(U2fSignTest, TestCreateSignApduCommand) { - const auto u2f_sign = CreateSignRequest(); - const auto encoded_sign = u2f_sign->GetU2fSignApduCommand( - application_parameter_, GetTestCredentialRawIdBytes()); - ASSERT_TRUE(encoded_sign); - EXPECT_THAT(*encoded_sign, - ::testing::ElementsAreArray(test_data::kU2fSignCommandApdu)); - - const auto encoded_sign_check_only = u2f_sign->GetU2fSignApduCommand( - application_parameter_, GetTestCredentialRawIdBytes(), true); - ASSERT_TRUE(encoded_sign_check_only); - EXPECT_THAT( - *encoded_sign_check_only, - ::testing::ElementsAreArray(test_data::kU2fCheckOnlySignCommandApdu)); -} - TEST_F(U2fSignTest, TestSignSuccess) { auto request = CreateSignRequest(); request->Start(); @@ -141,8 +98,8 @@ TEST_F(U2fSignTest, TestSignSuccess) { EXPECT_EQ(FidoReturnCode::kSuccess, sign_callback_receiver().status()); // Correct key was sent so a sign response is expected. - EXPECT_EQ(GetTestAssertionSignature(), - sign_callback_receiver().value()->signature()); + EXPECT_THAT(sign_callback_receiver().value()->signature(), + ::testing::ElementsAreArray(test_data::kU2fSignature)); // Verify that we get the key handle used for signing. EXPECT_THAT(GetTestCredentialRawIdBytes(), @@ -154,18 +111,20 @@ TEST_F(U2fSignTest, TestSignSuccessWithFake) { auto private_key = crypto::ECPrivateKey::Create(); std::string public_key; private_key->ExportRawPublicKey(&public_key); - std::vector<uint8_t> key_handle(32); - crypto::SHA256HashString(public_key, key_handle.data(), key_handle.size()); + auto key_handle = fido_parsing_utils::CreateSHA256Hash(public_key); std::vector<std::vector<uint8_t>> handles{key_handle}; auto request = CreateSignRequestWithKeys(handles); request->Start(); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<VirtualFidoDevice>(); + auto device = std::make_unique<VirtualU2fDevice>(); device->mutable_state()->registrations.emplace( - key_handle, VirtualFidoDevice::RegistrationData( - std::move(private_key), application_parameter_, 42)); + key_handle, + VirtualFidoDevice::RegistrationData( + std::move(private_key), + fido_parsing_utils::Materialize(test_data::kApplicationParameter), + 42)); discovery()->AddDevice(std::move(device)); sign_callback_receiver().WaitForCallback(); @@ -210,8 +169,8 @@ TEST_F(U2fSignTest, TestDelayedSuccess) { EXPECT_EQ(FidoReturnCode::kSuccess, sign_callback_receiver().status()); // Correct key was sent so a sign response is expected. - EXPECT_EQ(GetTestAssertionSignature(), - sign_callback_receiver().value()->signature()); + EXPECT_THAT(sign_callback_receiver().value()->signature(), + ::testing::ElementsAreArray(test_data::kU2fSignature)); // Verify that we get the key handle used for signing. EXPECT_THAT(GetTestCredentialRawIdBytes(), @@ -224,8 +183,8 @@ TEST_F(U2fSignTest, TestMultipleHandles) { // tested first. const auto correct_key_handle = GetTestCredentialRawIdBytes(); auto request = CreateSignRequestWithKeys( - {u2f_parsing_utils::Materialize(test_data::kKeyHandleAlpha), - u2f_parsing_utils::Materialize(test_data::kKeyHandleBeta), + {fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha), + fido_parsing_utils::Materialize(test_data::kKeyHandleBeta), correct_key_handle}); request->Start(); discovery()->WaitForCallToStartAndSimulateSuccess(); @@ -234,12 +193,12 @@ TEST_F(U2fSignTest, TestMultipleHandles) { // Wrong key would respond with SW_WRONG_DATA. EXPECT_CALL(*device, GetId()).WillRepeatedly(::testing::Return("device")); EXPECT_CALL(*device, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fSignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fSignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); @@ -257,9 +216,8 @@ TEST_F(U2fSignTest, TestMultipleHandles) { EXPECT_EQ(FidoReturnCode::kSuccess, sign_callback_receiver().status()); // Correct key was sent so a sign response is expected. - EXPECT_EQ(GetTestAssertionSignature(), - sign_callback_receiver().value()->signature()); - + EXPECT_THAT(sign_callback_receiver().value()->signature(), + ::testing::ElementsAreArray(test_data::kU2fSignature)); // Verify that we get the key handle used for signing. EXPECT_EQ(correct_key_handle, sign_callback_receiver().value()->raw_credential_id()); @@ -269,7 +227,7 @@ TEST_F(U2fSignTest, TestMultipleDevices) { const auto correct_key_handle = GetTestCredentialRawIdBytes(); auto request = CreateSignRequestWithKeys( {GetTestCredentialRawIdBytes(), - u2f_parsing_utils::Materialize(test_data::kKeyHandleAlpha)}); + fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha)}); request->Start(); auto device0 = std::make_unique<MockFidoDevice>(); @@ -278,7 +236,7 @@ TEST_F(U2fSignTest, TestMultipleDevices) { DeviceTransactPtr(GetU2fSignCommandWithCorrectCredential(), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device0, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fSignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::NotSatisfied)); @@ -304,8 +262,8 @@ TEST_F(U2fSignTest, TestMultipleDevices) { EXPECT_EQ(FidoReturnCode::kSuccess, sign_callback_receiver().status()); // Correct key was sent so a sign response is expected. - EXPECT_EQ(GetTestAssertionSignature(), - sign_callback_receiver().value()->signature()); + EXPECT_THAT(sign_callback_receiver().value()->signature(), + ::testing::ElementsAreArray(test_data::kU2fSignature)); // Verify that we get the key handle used for signing. EXPECT_EQ(correct_key_handle, @@ -314,19 +272,19 @@ TEST_F(U2fSignTest, TestMultipleDevices) { TEST_F(U2fSignTest, TestFakeEnroll) { auto request = CreateSignRequestWithKeys( - {u2f_parsing_utils::Materialize(test_data::kKeyHandleAlpha), - u2f_parsing_utils::Materialize(test_data::kKeyHandleBeta)}); + {fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha), + fido_parsing_utils::Materialize(test_data::kKeyHandleBeta)}); request->Start(); auto device0 = std::make_unique<MockFidoDevice>(); EXPECT_CALL(*device0, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fSignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device0, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fSignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::NotSatisfied)); @@ -340,17 +298,17 @@ TEST_F(U2fSignTest, TestFakeEnroll) { EXPECT_CALL(*device1, GetId()).WillRepeatedly(::testing::Return("device1")); // Both keys will be tried, when both fail, register is tried on that device. EXPECT_CALL(*device1, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fSignCommandApduWithKeyAlpha), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device1, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fSignCommandApduWithKeyBeta), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device1, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fFakeRegisterCommand), _)) .WillOnce(::testing::Invoke(MockFidoDevice::NoErrorRegister)); @@ -380,7 +338,7 @@ TEST_F(U2fSignTest, TestFakeEnrollErroringOut) { DeviceTransactPtr(GetU2fSignCommandWithCorrectCredential(), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); EXPECT_CALL(*device0, - DeviceTransactPtr(u2f_parsing_utils::Materialize( + DeviceTransactPtr(fido_parsing_utils::Materialize( test_data::kU2fFakeRegisterCommand), _)) .WillOnce(::testing::Invoke(MockFidoDevice::WrongData)); @@ -403,65 +361,8 @@ TEST_F(U2fSignTest, TestFakeEnrollErroringOut) { // Correct key was sent so a sign response is expected. sign_callback_receiver().WaitForCallback(); - EXPECT_EQ(GetTestAssertionSignature(), - sign_callback_receiver().value()->signature()); -} - -TEST_F(U2fSignTest, TestAuthenticatorDataForSign) { - constexpr uint8_t flags = - static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence); - - EXPECT_EQ(GetTestAuthenticatorData(), - AuthenticatorData(u2f_parsing_utils::Materialize( - test_data::kApplicationParameter), - flags, GetTestSignatureCounter(), base::nullopt) - .SerializeToByteArray()); -} - -TEST_F(U2fSignTest, TestSignResponseData) { - base::Optional<AuthenticatorGetAssertionResponse> response = - AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), - GetTestSignResponse(), GetTestCredentialRawIdBytes()); - ASSERT_TRUE(response.has_value()); - EXPECT_EQ(GetTestCredentialRawIdBytes(), response->raw_credential_id()); - EXPECT_EQ(GetTestAuthenticatorData(), - response->auth_data().SerializeToByteArray()); - EXPECT_EQ(GetTestAssertionSignature(), response->signature()); -} - -TEST_F(U2fSignTest, TestNullKeyHandle) { - base::Optional<AuthenticatorGetAssertionResponse> response = - AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), - GetTestSignResponse(), std::vector<uint8_t>()); - EXPECT_FALSE(response); -} - -TEST_F(U2fSignTest, TestNullResponse) { - base::Optional<AuthenticatorGetAssertionResponse> response = - AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), - std::vector<uint8_t>(), GetTestCredentialRawIdBytes()); - EXPECT_FALSE(response); -} - -TEST_F(U2fSignTest, TestCorruptedCounter) { - // A sign response of less than 5 bytes. - base::Optional<AuthenticatorGetAssertionResponse> response = - AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), - GetTestCorruptedSignResponse(3), GetTestCredentialRawIdBytes()); - EXPECT_FALSE(response); -} - -TEST_F(U2fSignTest, TestCorruptedSignature) { - // A sign response no more than 5 bytes. - base::Optional<AuthenticatorGetAssertionResponse> response = - AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( - u2f_parsing_utils::Materialize(test_data::kApplicationParameter), - GetTestCorruptedSignResponse(5), GetTestCredentialRawIdBytes()); - EXPECT_FALSE(response); + EXPECT_THAT(sign_callback_receiver().value()->signature(), + ::testing::ElementsAreArray(test_data::kU2fSignature)); } // Device returns success, but the response is unparse-able. @@ -538,8 +439,8 @@ TEST_F(U2fSignTest, TestAlternativeApplicationParameter) { sign_callback_receiver().WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, sign_callback_receiver().status()); - EXPECT_EQ(GetTestAssertionSignature(), - sign_callback_receiver().value()->signature()); + EXPECT_THAT(sign_callback_receiver().value()->signature(), + ::testing::ElementsAreArray(test_data::kU2fSignature)); EXPECT_EQ(signing_key_handle, sign_callback_receiver().value()->raw_credential_id()); } diff --git a/chromium/device/fido/virtual_fido_device.cc b/chromium/device/fido/virtual_fido_device.cc index aef7dc80d4e..38a6112ef44 100644 --- a/chromium/device/fido/virtual_fido_device.cc +++ b/chromium/device/fido/virtual_fido_device.cc @@ -7,30 +7,14 @@ #include <tuple> #include <utility> -#include "base/bind.h" -#include "base/location.h" -#include "base/logging.h" -#include "base/numerics/safe_conversions.h" -#include "base/threading/thread_task_runner_handle.h" -#include "base/time/time.h" -#include "components/apdu/apdu_command.h" -#include "components/apdu/apdu_response.h" #include "crypto/ec_private_key.h" #include "crypto/ec_signature_creator.h" -#include "crypto/sha2.h" -#include "device/fido/fido_constants.h" -#include "device/fido/u2f_parsing_utils.h" -#include "net/cert/x509_util.h" +#include "device/fido/fido_parsing_utils.h" namespace device { - namespace { -// First byte of registration response is 0x05 for historical reasons -// not detailed in the spec. -constexpr uint8_t kU2fRegistrationResponseHeader = 0x05; - // The example attestation private key from the U2F spec at // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-example // @@ -49,24 +33,6 @@ constexpr uint8_t kAttestationKey[]{ 0xd7, 0x86, 0x2f, 0x23, 0xab, 0xaf, 0x02, 0x03, 0xb4, 0xb8, 0x91, 0x1b, 0xa0, 0x56, 0x99, 0x94, 0xe1, 0x01}; -std::vector<uint8_t> GetAttestationKey() { - return std::vector<uint8_t>(std::begin(kAttestationKey), - std::end(kAttestationKey)); -} - -// Small helper to make the code below more readable. -template <class T> -void AppendTo(std::vector<uint8_t>* dst, const T& src) { - dst->insert(dst->end(), std::begin(src), std::end(src)); -} - -// Returns an error response with the given status. -base::Optional<std::vector<uint8_t>> ErrorStatus( - apdu::ApduResponse::Status status) { - return apdu::ApduResponse(std::vector<uint8_t>(), status) - .GetEncodedResponse(); -} - } // namespace // VirtualFidoDevice::RegistrationData ---------------------------------------- @@ -96,14 +62,12 @@ VirtualFidoDevice::State::~State() = default; bool VirtualFidoDevice::State::InjectRegistration( const std::vector<uint8_t>& credential_id, const std::string& relying_party_id) { - std::vector<uint8_t> application_parameter(crypto::kSHA256Length); - crypto::SHA256HashString(relying_party_id, application_parameter.data(), - application_parameter.size()); - + auto application_parameter = + fido_parsing_utils::CreateSHA256Hash(relying_party_id); auto private_key = crypto::ECPrivateKey::Create(); - if (!private_key) { + if (!private_key) return false; - } + RegistrationData registration(std::move(private_key), std::move(application_parameter), 0 /* signature counter */); @@ -114,126 +78,33 @@ bool VirtualFidoDevice::State::InjectRegistration( return was_inserted; } -VirtualFidoDevice::VirtualFidoDevice() - : state_(new State), weak_factory_(this) {} +VirtualFidoDevice::VirtualFidoDevice() = default; // VirtualFidoDevice ---------------------------------------------------------- VirtualFidoDevice::VirtualFidoDevice(scoped_refptr<State> state) - : state_(std::move(state)), weak_factory_(this) {} + : state_(std::move(state)) {} VirtualFidoDevice::~VirtualFidoDevice() = default; -void VirtualFidoDevice::TryWink(WinkCallback cb) { - std::move(cb).Run(); +// static +std::vector<uint8_t> VirtualFidoDevice::GetAttestationKey() { + return fido_parsing_utils::Materialize(kAttestationKey); } -// Cancel operation is not supported on U2F devices. -void VirtualFidoDevice::Cancel() {} - -std::string VirtualFidoDevice::GetId() const { - // Use our heap address to get a unique-ish number. (0xffe1 is a prime). - return "VirtualFidoDevice-" + std::to_string((size_t)this % 0xffe1); +// static +bool VirtualFidoDevice::Sign(crypto::ECPrivateKey* private_key, + base::span<const uint8_t> sign_buffer, + std::vector<uint8_t>* signature) { + auto signer = crypto::ECSignatureCreator::Create(private_key); + return signer->Sign(sign_buffer.data(), sign_buffer.size(), signature); } -void VirtualFidoDevice::DeviceTransact(std::vector<uint8_t> command, - DeviceCallback cb) { - // Note, here we are using the code-under-test in this fake. - auto parsed_command = apdu::ApduCommand::CreateFromMessage(command); - - // If malformed U2F request is received, respond with error immediately. - if (!parsed_command) { - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce( - std::move(cb), - ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED))); - return; - } - - base::Optional<std::vector<uint8_t>> response; - - switch (parsed_command->ins()) { - case base::strict_cast<uint8_t>(U2fApduInstruction::kVersion): - break; - case base::strict_cast<uint8_t>(U2fApduInstruction::kRegister): - response = DoRegister(parsed_command->ins(), parsed_command->p1(), - parsed_command->p2(), parsed_command->data()); - break; - case base::strict_cast<uint8_t>(U2fApduInstruction::kSign): - response = DoSign(parsed_command->ins(), parsed_command->p1(), - parsed_command->p2(), parsed_command->data()); - break; - default: - response = ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED); - } - - // Call |callback| via the |MessageLoop| because |AuthenticatorImpl| doesn't - // support callback hairpinning. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(std::move(cb), std::move(response))); -} - -base::WeakPtr<FidoDevice> VirtualFidoDevice::GetWeakPtr() { - return weak_factory_.GetWeakPtr(); -} - -base::Optional<std::vector<uint8_t>> VirtualFidoDevice::DoRegister( - uint8_t ins, - uint8_t p1, - uint8_t p2, - base::span<const uint8_t> data) { - if (data.size() != 64) { - return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_LENGTH); - } - - if (mutable_state()->simulate_press_callback) { - mutable_state()->simulate_press_callback.Run(); - } - - const bool individual_attestation_requested = p1 & kP1IndividualAttestation; - // The spec says that the other bits of P1 should be zero. However, Chrome - // sends Test User Presence (0x03) so we ignore those bits. - - auto challenge_param = data.first(32); - auto application_parameter = data.last(32); - - // Create key to register. - // Note: Non-deterministic, you need to mock this out if you rely on - // deterministic behavior. - auto private_key = crypto::ECPrivateKey::Create(); - std::string public_key; - bool status = private_key->ExportRawPublicKey(&public_key); - DCHECK(status); - public_key.insert(0, 1, 0x04); - DCHECK_EQ(public_key.size(), 65ul); - - // Our key handles are simple hashes of the public key. - std::vector<uint8_t> key_handle(32); - crypto::SHA256HashString(public_key, key_handle.data(), key_handle.size()); - - // Data to be signed. - std::vector<uint8_t> sign_buffer; - sign_buffer.reserve(1 + application_parameter.size() + - challenge_param.size() + key_handle.size() + - public_key.size()); - sign_buffer.push_back(0x00); - AppendTo(&sign_buffer, application_parameter); - AppendTo(&sign_buffer, challenge_param); - AppendTo(&sign_buffer, key_handle); - AppendTo(&sign_buffer, public_key); - - // Sign with attestation key. - // Note: Non-deterministic, you need to mock this out if you rely on - // deterministic behavior. - std::vector<uint8_t> sig; +base::Optional<std::vector<uint8_t>> +VirtualFidoDevice::GenerateAttestationCertificate( + bool individual_attestation_requested) const { std::unique_ptr<crypto::ECPrivateKey> attestation_private_key = crypto::ECPrivateKey::CreateFromPrivateKeyInfo(GetAttestationKey()); - auto signer = - crypto::ECSignatureCreator::Create(attestation_private_key.get()); - status = signer->Sign(sign_buffer.data(), sign_buffer.size(), &sig); - DCHECK(status); - constexpr uint32_t kAttestationCertSerialNumber = 1; std::string attestation_cert; if (!net::x509_util::CreateSelfSignedCert( @@ -244,105 +115,19 @@ base::Optional<std::vector<uint8_t>> VirtualFidoDevice::DoRegister( kAttestationCertSerialNumber, base::Time::FromTimeT(1500000000), base::Time::FromTimeT(1500000000), &attestation_cert)) { DVLOG(2) << "Failed to create attestation certificate"; - return ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED); + return base::nullopt; } - // U2F response data. - std::vector<uint8_t> response; - response.reserve(1 + public_key.size() + 1 + key_handle.size() + - attestation_cert.size() + sig.size()); - response.push_back(kU2fRegistrationResponseHeader); - AppendTo(&response, public_key); - response.push_back(key_handle.size()); - AppendTo(&response, key_handle); - AppendTo(&response, attestation_cert); - AppendTo(&response, sig); - - // Store the registration. Because the key handle is the hashed public key we - // just generated, no way this should already be registered. - bool did_insert = false; - std::tie(std::ignore, did_insert) = state_->registrations.emplace( - std::move(key_handle), - RegistrationData(std::move(private_key), - u2f_parsing_utils::Materialize(application_parameter), - 1)); - DCHECK(did_insert); - - return apdu::ApduResponse(std::move(response), - apdu::ApduResponse::Status::SW_NO_ERROR) - .GetEncodedResponse(); + return std::vector<uint8_t>(attestation_cert.begin(), attestation_cert.end()); } -base::Optional<std::vector<uint8_t>> VirtualFidoDevice::DoSign( - uint8_t ins, - uint8_t p1, - uint8_t p2, - base::span<const uint8_t> data) { - if (!(p1 == kP1CheckOnly || p1 == kP1TupRequiredConsumed || - p1 == kP1IndividualAttestation) || - p2 != 0) { - return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_DATA); - } - - if (mutable_state()->simulate_press_callback) { - mutable_state()->simulate_press_callback.Run(); - } - - auto challenge_param = data.first(32); - auto application_parameter = data.subspan(32, 32); - size_t key_handle_length = data[64]; - if (data.size() != 32 + 32 + 1 + key_handle_length) { - return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_LENGTH); - } - auto key_handle = data.last(key_handle_length); - - // Check if this is our key_handle and it's for this appId. - auto it = state_->registrations.find( - std::vector<uint8_t>(key_handle.cbegin(), key_handle.cend())); - - if (it == state_->registrations.end()) { - return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_DATA); - } - - base::span<const uint8_t> registered_app_id_hash = - base::make_span(it->second.application_parameter); - if (application_parameter != registered_app_id_hash) { - // It's important this error looks identical to the previous one, as - // tokens should not reveal the existence of keyHandles to unrelated appIds. - return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_DATA); - } - - auto& registration = it->second; - ++registration.counter; - - // First create the part of the response that gets signed over. - std::vector<uint8_t> response; - response.push_back(0x01); // Always pretend we got a touch. - response.push_back(registration.counter >> 24); - response.push_back(registration.counter >> 16); - response.push_back(registration.counter >> 8); - response.push_back(registration.counter); - - std::vector<uint8_t> sign_buffer; - sign_buffer.reserve(application_parameter.size() + response.size() + - challenge_param.size()); - AppendTo(&sign_buffer, application_parameter); - AppendTo(&sign_buffer, response); - AppendTo(&sign_buffer, challenge_param); - - // Sign with credential key. - std::vector<uint8_t> sig; - auto signer = - crypto::ECSignatureCreator::Create(registration.private_key.get()); - bool status = signer->Sign(sign_buffer.data(), sign_buffer.size(), &sig); - DCHECK(status); - - // Add signature for full response. - AppendTo(&response, sig); +void VirtualFidoDevice::TryWink(WinkCallback cb) { + std::move(cb).Run(); +} - return apdu::ApduResponse(std::move(response), - apdu::ApduResponse::Status::SW_NO_ERROR) - .GetEncodedResponse(); +std::string VirtualFidoDevice::GetId() const { + // Use our heap address to get a unique-ish number. (0xffe1 is a prime). + return "VirtualFidoDevice-" + std::to_string((size_t)this % 0xffe1); } } // namespace device diff --git a/chromium/device/fido/virtual_fido_device.h b/chromium/device/fido/virtual_fido_device.h index f10e7dbc80c..66bc161ffb0 100644 --- a/chromium/device/fido/virtual_fido_device.h +++ b/chromium/device/fido/virtual_fido_device.h @@ -17,7 +17,10 @@ #include "base/containers/span.h" #include "base/macros.h" #include "base/memory/scoped_refptr.h" +#include "base/optional.h" #include "device/fido/fido_device.h" +#include "device/fido/fido_parsing_utils.h" +#include "net/cert/x509_util.h" namespace crypto { class ECPrivateKey; @@ -49,7 +52,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { // Stores the state of the device. Since |U2fDevice| objects only persist for // the lifetime of a single request, keeping state in an external object is - // neccessary in order to provide continuity between requests. + // necessary in order to provide continuity between requests. class COMPONENT_EXPORT(DEVICE_FIDO) State : public base::RefCounted<State> { public: State(); @@ -62,12 +65,18 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { std::string individual_attestation_cert_common_name; // Registered keys. Keyed on key handle (a.k.a. "credential ID"). - std::map<std::vector<uint8_t>, RegistrationData> registrations; + std::map<std::vector<uint8_t>, + RegistrationData, + fido_parsing_utils::SpanLess> + registrations; // If set, this callback is called whenever a "press" is required. It allows // tests to change the state of the world during processing. base::RepeatingCallback<void(void)> simulate_press_callback; + // If true, causes the response from the device to be invalid. + bool simulate_invalid_response = false; + // Adds a registration for the specified credential ID with the application // parameter set to be valid for the given relying party ID (which would // typically be a domain, e.g. "example.com"). @@ -97,27 +106,24 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { State* mutable_state() { return state_.get(); } protected: + static std::vector<uint8_t> GetAttestationKey(); + + static bool Sign(crypto::ECPrivateKey* private_key, + base::span<const uint8_t> sign_buffer, + std::vector<uint8_t>* signature); + + // Constructs certificate encoded in X.509 format to be used for packed + // attestation statement and FIDO-U2F attestation statement. + // https://w3c.github.io/webauthn/#defined-attestation-formats + base::Optional<std::vector<uint8_t>> GenerateAttestationCertificate( + bool individual_attestation_requested) const; + // FidoDevice: void TryWink(WinkCallback cb) override; - void Cancel() override; std::string GetId() const override; - void DeviceTransact(std::vector<uint8_t> command, DeviceCallback cb) override; - base::WeakPtr<FidoDevice> GetWeakPtr() override; private: - base::Optional<std::vector<uint8_t>> DoRegister( - uint8_t ins, - uint8_t p1, - uint8_t p2, - base::span<const uint8_t> data); - - base::Optional<std::vector<uint8_t>> DoSign(uint8_t ins, - uint8_t p1, - uint8_t p2, - base::span<const uint8_t> data); - - scoped_refptr<State> state_; - base::WeakPtrFactory<FidoDevice> weak_factory_; + scoped_refptr<State> state_ = base::MakeRefCounted<State>(); DISALLOW_COPY_AND_ASSIGN(VirtualFidoDevice); }; diff --git a/chromium/device/fido/virtual_u2f_device.cc b/chromium/device/fido/virtual_u2f_device.cc new file mode 100644 index 00000000000..fa38b41b215 --- /dev/null +++ b/chromium/device/fido/virtual_u2f_device.cc @@ -0,0 +1,261 @@ +// Copyright 2018 The Chromium 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 "device/fido/virtual_u2f_device.h" + +#include <memory> +#include <string> +#include <tuple> +#include <utility> + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "components/apdu/apdu_command.h" +#include "components/apdu/apdu_response.h" +#include "crypto/ec_private_key.h" +#include "device/fido/fido_constants.h" +#include "device/fido/fido_parsing_utils.h" + +namespace device { + +using fido_parsing_utils::Append; + +namespace { + +// First byte of registration response is 0x05 for historical reasons +// not detailed in the spec. +constexpr uint8_t kU2fRegistrationResponseHeader = 0x05; + +// Returns an error response with the given status. +base::Optional<std::vector<uint8_t>> ErrorStatus( + apdu::ApduResponse::Status status) { + return apdu::ApduResponse(std::vector<uint8_t>(), status) + .GetEncodedResponse(); +} + +} // namespace + +VirtualU2fDevice::VirtualU2fDevice() + : VirtualFidoDevice(), weak_factory_(this) {} + +// VirtualU2fDevice ---------------------------------------------------------- + +VirtualU2fDevice::VirtualU2fDevice(scoped_refptr<State> state) + : VirtualFidoDevice(std::move(state)), weak_factory_(this) {} + +VirtualU2fDevice::~VirtualU2fDevice() = default; + +// Cancel operation is not supported on U2F devices. +void VirtualU2fDevice::Cancel() {} + +void VirtualU2fDevice::DeviceTransact(std::vector<uint8_t> command, + DeviceCallback cb) { + // Note, here we are using the code-under-test in this fake. + auto parsed_command = apdu::ApduCommand::CreateFromMessage(command); + + // If malformed U2F request is received, respond with error immediately. + if (!parsed_command) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce( + std::move(cb), + ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED))); + return; + } + + if (mutable_state()->simulate_invalid_response) { + std::vector<uint8_t> nonsense = {1, 2, 3}; + auto response = apdu::ApduResponse(std::move(nonsense), + apdu::ApduResponse::Status::SW_NO_ERROR) + .GetEncodedResponse(); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(cb), std::move(response))); + return; + } + + base::Optional<std::vector<uint8_t>> response; + + switch (parsed_command->ins()) { + // Version request is defined by the U2F spec, but is never used in + // production code. + case base::strict_cast<uint8_t>(U2fApduInstruction::kVersion): + break; + case base::strict_cast<uint8_t>(U2fApduInstruction::kRegister): + response = DoRegister(parsed_command->ins(), parsed_command->p1(), + parsed_command->p2(), parsed_command->data()); + break; + case base::strict_cast<uint8_t>(U2fApduInstruction::kSign): + response = DoSign(parsed_command->ins(), parsed_command->p1(), + parsed_command->p2(), parsed_command->data()); + break; + default: + response = ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED); + } + + // Call |callback| via the |MessageLoop| because |AuthenticatorImpl| doesn't + // support callback hairpinning. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(cb), std::move(response))); +} + +base::WeakPtr<FidoDevice> VirtualU2fDevice::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + +base::Optional<std::vector<uint8_t>> VirtualU2fDevice::DoRegister( + uint8_t ins, + uint8_t p1, + uint8_t p2, + base::span<const uint8_t> data) { + if (data.size() != 64) { + return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_LENGTH); + } + + if (mutable_state()->simulate_press_callback) { + mutable_state()->simulate_press_callback.Run(); + } + + auto challenge_param = data.first(32); + auto application_parameter = data.last(32); + + // Create key to register. + // Note: Non-deterministic, you need to mock this out if you rely on + // deterministic behavior. + auto private_key = crypto::ECPrivateKey::Create(); + std::string public_key; + bool status = private_key->ExportRawPublicKey(&public_key); + DCHECK(status); + public_key.insert(0, 1, 0x04); + DCHECK_EQ(public_key.size(), 65ul); + + // Our key handles are simple hashes of the public key. + auto key_handle = fido_parsing_utils::CreateSHA256Hash(public_key); + + // Data to be signed. + std::vector<uint8_t> sign_buffer; + sign_buffer.reserve(1 + application_parameter.size() + + challenge_param.size() + key_handle.size() + + public_key.size()); + sign_buffer.push_back(0x00); + Append(&sign_buffer, application_parameter); + Append(&sign_buffer, challenge_param); + Append(&sign_buffer, key_handle); + Append(&sign_buffer, base::as_bytes(base::make_span(public_key))); + + // Sign with attestation key. + // Note: Non-deterministic, you need to mock this out if you rely on + // deterministic behavior. + std::vector<uint8_t> sig; + std::unique_ptr<crypto::ECPrivateKey> attestation_private_key = + crypto::ECPrivateKey::CreateFromPrivateKeyInfo(GetAttestationKey()); + status = Sign(attestation_private_key.get(), sign_buffer, &sig); + DCHECK(status); + + // The spec says that the other bits of P1 should be zero. However, Chrome + // sends Test User Presence (0x03) so we ignore those bits. + bool individual_attestation_requested = p1 & kP1IndividualAttestation; + const auto attestation_cert = + GenerateAttestationCertificate(individual_attestation_requested); + if (!attestation_cert) + return ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED); + + // U2F response data. + std::vector<uint8_t> response; + response.reserve(1 + public_key.size() + 1 + key_handle.size() + + attestation_cert->size() + sig.size()); + response.push_back(kU2fRegistrationResponseHeader); + Append(&response, base::as_bytes(base::make_span(public_key))); + response.push_back(key_handle.size()); + Append(&response, key_handle); + Append(&response, *attestation_cert); + Append(&response, sig); + + // Store the registration. Because the key handle is the hashed public key we + // just generated, no way this should already be registered. + bool did_insert = false; + std::tie(std::ignore, did_insert) = mutable_state()->registrations.emplace( + std::move(key_handle), + RegistrationData(std::move(private_key), + fido_parsing_utils::Materialize(application_parameter), + 1)); + DCHECK(did_insert); + + return apdu::ApduResponse(std::move(response), + apdu::ApduResponse::Status::SW_NO_ERROR) + .GetEncodedResponse(); +} + +base::Optional<std::vector<uint8_t>> VirtualU2fDevice::DoSign( + uint8_t ins, + uint8_t p1, + uint8_t p2, + base::span<const uint8_t> data) { + if (!(p1 == kP1CheckOnly || p1 == kP1TupRequiredConsumed || + p1 == kP1IndividualAttestation) || + p2 != 0) { + return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_DATA); + } + + if (mutable_state()->simulate_press_callback) { + mutable_state()->simulate_press_callback.Run(); + } + + auto challenge_param = data.first(32); + auto application_parameter = data.subspan(32, 32); + size_t key_handle_length = data[64]; + if (data.size() != 32 + 32 + 1 + key_handle_length) { + return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_LENGTH); + } + auto key_handle = data.last(key_handle_length); + + // Check if this is our key_handle and it's for this appId. + auto it = mutable_state()->registrations.find(key_handle); + if (it == mutable_state()->registrations.end()) { + return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_DATA); + } + + base::span<const uint8_t> registered_app_id_hash = + base::make_span(it->second.application_parameter); + if (application_parameter != registered_app_id_hash) { + // It's important this error looks identical to the previous one, as + // tokens should not reveal the existence of keyHandles to unrelated appIds. + return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_DATA); + } + + auto& registration = it->second; + ++registration.counter; + + // First create the part of the response that gets signed over. + std::vector<uint8_t> response; + response.push_back(0x01); // Always pretend we got a touch. + response.push_back(registration.counter >> 24); + response.push_back(registration.counter >> 16); + response.push_back(registration.counter >> 8); + response.push_back(registration.counter); + + std::vector<uint8_t> sign_buffer; + sign_buffer.reserve(application_parameter.size() + response.size() + + challenge_param.size()); + Append(&sign_buffer, application_parameter); + Append(&sign_buffer, response); + Append(&sign_buffer, challenge_param); + + // Sign with credential key. + std::vector<uint8_t> sig; + bool status = Sign(registration.private_key.get(), sign_buffer, &sig); + DCHECK(status); + + // Add signature for full response. + Append(&response, sig); + + return apdu::ApduResponse(std::move(response), + apdu::ApduResponse::Status::SW_NO_ERROR) + .GetEncodedResponse(); +} + +} // namespace device diff --git a/chromium/device/fido/virtual_u2f_device.h b/chromium/device/fido/virtual_u2f_device.h new file mode 100644 index 00000000000..aa47d4c9ada --- /dev/null +++ b/chromium/device/fido/virtual_u2f_device.h @@ -0,0 +1,51 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_VIRTUAL_U2F_DEVICE_H_ +#define DEVICE_FIDO_VIRTUAL_U2F_DEVICE_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/component_export.h" +#include "base/containers/span.h" +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "device/fido/virtual_fido_device.h" + +namespace device { + +class COMPONENT_EXPORT(DEVICE_FIDO) VirtualU2fDevice + : public VirtualFidoDevice { + public: + VirtualU2fDevice(); + explicit VirtualU2fDevice(scoped_refptr<State> state); + ~VirtualU2fDevice() override; + + // FidoDevice: + void Cancel() override; + void DeviceTransact(std::vector<uint8_t> command, DeviceCallback cb) override; + base::WeakPtr<FidoDevice> GetWeakPtr() override; + + private: + base::Optional<std::vector<uint8_t>> DoRegister( + uint8_t ins, + uint8_t p1, + uint8_t p2, + base::span<const uint8_t> data); + + base::Optional<std::vector<uint8_t>> DoSign(uint8_t ins, + uint8_t p1, + uint8_t p2, + base::span<const uint8_t> data); + + base::WeakPtrFactory<FidoDevice> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(VirtualU2fDevice); +}; + +} // namespace device + +#endif // DEVICE_FIDO_VIRTUAL_U2F_DEVICE_H_ diff --git a/chromium/device/gamepad/gamepad_platform_data_fetcher_win.h b/chromium/device/gamepad/gamepad_platform_data_fetcher_win.h index 9925ca3dcec..fbf9afbd9c2 100644 --- a/chromium/device/gamepad/gamepad_platform_data_fetcher_win.h +++ b/chromium/device/gamepad/gamepad_platform_data_fetcher_win.h @@ -21,7 +21,6 @@ #include "base/compiler_specific.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" -#include "base/message_loop/message_loop.h" #include "base/scoped_native_library.h" #include "device/gamepad/gamepad_data_fetcher.h" #include "device/gamepad/gamepad_standard_mappings.h" diff --git a/chromium/device/geolocation/geolocation_provider_impl_unittest.cc b/chromium/device/geolocation/geolocation_provider_impl_unittest.cc index ce9524ba581..9f5f44a4458 100644 --- a/chromium/device/geolocation/geolocation_provider_impl_unittest.cc +++ b/chromium/device/geolocation/geolocation_provider_impl_unittest.cc @@ -13,7 +13,6 @@ #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/strings/string16.h" @@ -151,7 +150,7 @@ bool GeolocationProviderTest::ProvidersStarted() { FROM_HERE, base::BindOnce(&GeolocationProviderTest::GetProvidersStarted, base::Unretained(this)), - base::MessageLoop::QuitWhenIdleClosure()); + base::RunLoop::QuitCurrentWhenIdleClosureDeprecated()); base::RunLoop().Run(); return is_started_; } diff --git a/chromium/device/media_transfer_protocol/BUILD.gn b/chromium/device/media_transfer_protocol/BUILD.gn deleted file mode 100644 index 1cee23d7eca..00000000000 --- a/chromium/device/media_transfer_protocol/BUILD.gn +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import("//build/config/features.gni") -import("//third_party/protobuf/proto_library.gni") - -assert(is_chromeos) -assert(use_dbus) - -proto_library("mtp_file_entry_proto") { - sources = [ - "//third_party/cros_system_api/dbus/mtp_file_entry.proto", - ] - proto_out_dir = "device/media_transfer_protocol" -} - -proto_library("mtp_storage_info_proto") { - sources = [ - "//third_party/cros_system_api/dbus/mtp_storage_info.proto", - ] - proto_out_dir = "device/media_transfer_protocol" -} - -static_library("media_transfer_protocol") { - sources = [ - "media_transfer_protocol_daemon_client.cc", - "media_transfer_protocol_daemon_client.h", - "media_transfer_protocol_manager.cc", - "media_transfer_protocol_manager.h", - ] - - public_deps = [ - ":mtp_file_entry_proto", - ":mtp_storage_info_proto", - "//base", - "//device/media_transfer_protocol/public/mojom", - ] - deps = [ - "//chromeos", - "//dbus", - ] -} diff --git a/chromium/device/media_transfer_protocol/DEPS b/chromium/device/media_transfer_protocol/DEPS deleted file mode 100644 index b9845a38d93..00000000000 --- a/chromium/device/media_transfer_protocol/DEPS +++ /dev/null @@ -1,6 +0,0 @@ -include_rules = [ - # The media transfer protocol daemon uses D-Bus. - "+dbus", - "+chromeos/dbus", - "+third_party/cros_system_api/dbus", -] diff --git a/chromium/device/media_transfer_protocol/OWNERS b/chromium/device/media_transfer_protocol/OWNERS deleted file mode 100644 index 7f803eb8544..00000000000 --- a/chromium/device/media_transfer_protocol/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -satorux@chromium.org -thestig@chromium.org - -# COMPONENT: Platform>Apps>FileManager diff --git a/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc b/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc deleted file mode 100644 index 097fa5e67c1..00000000000 --- a/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.cc +++ /dev/null @@ -1,554 +0,0 @@ -// Copyright (c) 2012 The Chromium 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 "device/media_transfer_protocol/media_transfer_protocol_daemon_client.h" - -#include <algorithm> - -#include "base/bind.h" -#include "base/macros.h" -#include "base/memory/weak_ptr.h" -#include "dbus/bus.h" -#include "dbus/message.h" -#include "dbus/object_path.h" -#include "dbus/object_proxy.h" -#include "device/media_transfer_protocol/mtp_storage_info.pb.h" -#include "device/media_transfer_protocol/mtp_file_entry.pb.h" -#include "third_party/cros_system_api/dbus/service_constants.h" - -namespace device { - -namespace { - -const char kInvalidResponseMsg[] = "Invalid Response: "; -uint32_t kMaxChunkSize = 1024 * 1024; // D-Bus has message size limits. - -mojom::MtpFileEntry GetMojoMtpFileEntryFromProtobuf( - const MtpFileEntry& entry) { - return mojom::MtpFileEntry( - entry.item_id(), - entry.parent_id(), - entry.file_name(), - entry.file_size(), - entry.modification_time(), - static_cast<mojom::MtpFileEntry::FileType>(entry.file_type())); -} - -mojom::MtpStorageInfo GetMojoMtpStorageInfoFromProtobuf( - const MtpStorageInfo& protobuf) { - return mojom::MtpStorageInfo( - protobuf.storage_name(), - protobuf.vendor(), - protobuf.vendor_id(), - protobuf.product(), - protobuf.product_id(), - protobuf.device_flags(), - protobuf.storage_type(), - protobuf.filesystem_type(), - protobuf.access_capability(), - protobuf.max_capacity(), - protobuf.free_space_in_bytes(), - protobuf.free_space_in_objects(), - protobuf.storage_description(), - protobuf.volume_identifier()); -} - -// The MediaTransferProtocolDaemonClient implementation. -class MediaTransferProtocolDaemonClientImpl - : public MediaTransferProtocolDaemonClient { - public: - explicit MediaTransferProtocolDaemonClientImpl(dbus::Bus* bus) - : proxy_(bus->GetObjectProxy( - mtpd::kMtpdServiceName, - dbus::ObjectPath(mtpd::kMtpdServicePath))), - listen_for_changes_called_(false), - weak_ptr_factory_(this) { - } - - // MediaTransferProtocolDaemonClient override. - void EnumerateStorages(const EnumerateStoragesCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, - mtpd::kEnumerateStorages); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnEnumerateStorages, - weak_ptr_factory_.GetWeakPtr(), - callback, - error_callback)); - } - - // MediaTransferProtocolDaemonClient override. - void GetStorageInfo(const std::string& storage_name, - const GetStorageInfoCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kGetStorageInfo); - dbus::MessageWriter writer(&method_call); - writer.AppendString(storage_name); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnGetStorageInfo, - weak_ptr_factory_.GetWeakPtr(), - storage_name, - callback, - error_callback)); - } - - void GetStorageInfoFromDevice(const std::string& storage_name, - const GetStorageInfoCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, - mtpd::kGetStorageInfoFromDevice); - dbus::MessageWriter writer(&method_call); - writer.AppendString(storage_name); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnGetStorageInfo, - weak_ptr_factory_.GetWeakPtr(), storage_name, callback, - error_callback)); - } - - // MediaTransferProtocolDaemonClient override. - void OpenStorage(const std::string& storage_name, - const std::string& mode, - const OpenStorageCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kOpenStorage); - dbus::MessageWriter writer(&method_call); - writer.AppendString(storage_name); - DCHECK(mode == mtpd::kReadOnlyMode || mode == mtpd::kReadWriteMode); - writer.AppendString(mode); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnOpenStorage, - weak_ptr_factory_.GetWeakPtr(), - callback, - error_callback)); - } - - // MediaTransferProtocolDaemonClient override. - void CloseStorage(const std::string& handle, - const CloseStorageCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kCloseStorage); - dbus::MessageWriter writer(&method_call); - writer.AppendString(handle); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnCloseStorage, - weak_ptr_factory_.GetWeakPtr(), - callback, - error_callback)); - } - - void CreateDirectory(const std::string& handle, - const uint32_t parent_id, - const std::string& directory_name, - const CreateDirectoryCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kCreateDirectory); - dbus::MessageWriter writer(&method_call); - writer.AppendString(handle); - writer.AppendUint32(parent_id); - writer.AppendString(directory_name); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnCreateDirectory, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); - } - - // MediaTransferProtocolDaemonClient override. - void ReadDirectoryEntryIds(const std::string& handle, - uint32_t file_id, - const ReadDirectoryEntryIdsCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, - mtpd::kReadDirectoryEntryIds); - dbus::MessageWriter writer(&method_call); - writer.AppendString(handle); - writer.AppendUint32(file_id); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnReadDirectoryIds, - weak_ptr_factory_.GetWeakPtr(), - callback, - error_callback)); - } - - void GetFileInfo(const std::string& handle, - const std::vector<uint32_t>& file_ids, - size_t offset, - size_t entries_to_read, - const GetFileInfoCallback& callback, - const ErrorCallback& error_callback) override { - if (offset >= file_ids.size()) { - error_callback.Run(); - return; - } - - dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kGetFileInfo); - dbus::MessageWriter writer(&method_call); - writer.AppendString(handle); - { - dbus::MessageWriter array_writer(nullptr); - writer.OpenArray("u", &array_writer); - - size_t end_offset = file_ids.size(); - if (offset <= SIZE_MAX - entries_to_read) - end_offset = std::min(end_offset, offset + entries_to_read); - for (size_t i = offset; i < end_offset; ++i) - array_writer.AppendUint32(file_ids[i]); - - writer.CloseContainer(&array_writer); - } - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnGetFileInfo, - weak_ptr_factory_.GetWeakPtr(), - callback, - error_callback)); - } - - // MediaTransferProtocolDaemonClient override. - void ReadFileChunk(const std::string& handle, - uint32_t file_id, - uint32_t offset, - uint32_t bytes_to_read, - const ReadFileCallback& callback, - const ErrorCallback& error_callback) override { - DCHECK_LE(bytes_to_read, kMaxChunkSize); - dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kReadFileChunk); - dbus::MessageWriter writer(&method_call); - writer.AppendString(handle); - writer.AppendUint32(file_id); - writer.AppendUint32(offset); - writer.AppendUint32(bytes_to_read); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnReadFile, - weak_ptr_factory_.GetWeakPtr(), - callback, - error_callback)); - } - - void RenameObject(const std::string& handle, - const uint32_t object_id, - const std::string& new_name, - const RenameObjectCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kRenameObject); - dbus::MessageWriter writer(&method_call); - writer.AppendString(handle); - writer.AppendUint32(object_id); - writer.AppendString(new_name); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnRenameObject, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); - } - - void CopyFileFromLocal(const std::string& handle, - const int source_file_descriptor, - const uint32_t parent_id, - const std::string& file_name, - const CopyFileFromLocalCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, - mtpd::kCopyFileFromLocal); - dbus::MessageWriter writer(&method_call); - writer.AppendString(handle); - writer.AppendFileDescriptor(source_file_descriptor); - writer.AppendUint32(parent_id); - writer.AppendString(file_name); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_INFINITE, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnCopyFileFromLocal, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); - } - - void DeleteObject(const std::string& handle, - const uint32_t object_id, - const DeleteObjectCallback& callback, - const ErrorCallback& error_callback) override { - dbus::MethodCall method_call(mtpd::kMtpdInterface, mtpd::kDeleteObject); - dbus::MessageWriter writer(&method_call); - writer.AppendString(handle); - writer.AppendUint32(object_id); - proxy_->CallMethod( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnDeleteObject, - weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); - } - - // MediaTransferProtocolDaemonClient override. - void ListenForChanges(const MTPStorageEventHandler& handler) override { - DCHECK(!listen_for_changes_called_); - listen_for_changes_called_ = true; - - static const SignalEventTuple kSignalEventTuples[] = { - { mtpd::kMTPStorageAttached, true }, - { mtpd::kMTPStorageDetached, false }, - }; - for (const auto& event : kSignalEventTuples) { - proxy_->ConnectToSignal( - mtpd::kMtpdInterface, event.signal_name, - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnMTPStorageSignal, - weak_ptr_factory_.GetWeakPtr(), handler, event.is_attach), - base::Bind(&MediaTransferProtocolDaemonClientImpl::OnSignalConnected, - weak_ptr_factory_.GetWeakPtr())); - } - } - - private: - // A struct to contain a pair of signal name and attachment event type. - // Used by SetUpConnections. - struct SignalEventTuple { - const char* signal_name; - bool is_attach; - }; - - // Handles the result of EnumerateStorages and calls |callback| or - // |error_callback|. - void OnEnumerateStorages(const EnumerateStoragesCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - dbus::MessageReader reader(response); - std::vector<std::string> storage_names; - if (!reader.PopArrayOfStrings(&storage_names)) { - LOG(ERROR) << kInvalidResponseMsg << response->ToString(); - error_callback.Run(); - return; - } - callback.Run(storage_names); - } - - // Handles the result of GetStorageInfo and calls |callback| or - // |error_callback|. - void OnGetStorageInfo(const std::string& storage_name, - const GetStorageInfoCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - - dbus::MessageReader reader(response); - MtpStorageInfo protobuf; - if (!reader.PopArrayOfBytesAsProto(&protobuf)) { - LOG(ERROR) << kInvalidResponseMsg << response->ToString(); - error_callback.Run(); - return; - } - callback.Run(GetMojoMtpStorageInfoFromProtobuf(protobuf)); - } - - // Handles the result of OpenStorage and calls |callback| or |error_callback|. - void OnOpenStorage(const OpenStorageCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - dbus::MessageReader reader(response); - std::string handle; - if (!reader.PopString(&handle)) { - LOG(ERROR) << kInvalidResponseMsg << response->ToString(); - error_callback.Run(); - return; - } - callback.Run(handle); - } - - // Handles the result of CloseStorage and calls |callback| or - // |error_callback|. - void OnCloseStorage(const CloseStorageCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - callback.Run(); - } - - void OnCreateDirectory(const CreateDirectoryCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - callback.Run(); - } - - // Handles the result of ReadDirectoryEntryIds and calls |callback| or - // |error_callback|. - void OnReadDirectoryIds(const ReadDirectoryEntryIdsCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - - dbus::MessageReader reader(response); - dbus::MessageReader array_reader(nullptr); - if (!reader.PopArray(&array_reader) || reader.HasMoreData()) { - LOG(ERROR) << kInvalidResponseMsg << response->ToString(); - error_callback.Run(); - return; - } - - std::vector<uint32_t> file_ids; - while (array_reader.HasMoreData()) { - uint32_t file_id; - if (!array_reader.PopUint32(&file_id)) { - LOG(ERROR) << kInvalidResponseMsg << response->ToString(); - error_callback.Run(); - return; - } - file_ids.push_back(file_id); - } - callback.Run(file_ids); - } - - // Handles the result of GetFileInfo and calls |callback| or |error_callback|. - void OnGetFileInfo(const GetFileInfoCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - - dbus::MessageReader reader(response); - MtpFileEntries entries_protobuf; - if (!reader.PopArrayOfBytesAsProto(&entries_protobuf)) { - LOG(ERROR) << kInvalidResponseMsg << response->ToString(); - error_callback.Run(); - return; - } - - std::vector<mojom::MtpFileEntry> file_entries; - file_entries.reserve(entries_protobuf.file_entries_size()); - for (int i = 0; i < entries_protobuf.file_entries_size(); ++i) { - const auto& entry = entries_protobuf.file_entries(i); - file_entries.push_back( - GetMojoMtpFileEntryFromProtobuf(entry)); - } - callback.Run(file_entries); - } - - // Handles the result of ReadFileChunk and calls |callback| or - // |error_callback|. - void OnReadFile(const ReadFileCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - - const uint8_t* data_bytes = nullptr; - size_t data_length = 0; - dbus::MessageReader reader(response); - if (!reader.PopArrayOfBytes(&data_bytes, &data_length)) { - error_callback.Run(); - return; - } - std::string data(reinterpret_cast<const char*>(data_bytes), data_length); - callback.Run(data); - } - - void OnRenameObject(const RenameObjectCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - - callback.Run(); - } - - void OnCopyFileFromLocal(const CopyFileFromLocalCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - - callback.Run(); - } - - void OnDeleteObject(const DeleteObjectCallback& callback, - const ErrorCallback& error_callback, - dbus::Response* response) { - if (!response) { - error_callback.Run(); - return; - } - - callback.Run(); - } - - // Handles MTPStorageAttached/Dettached signals and calls |handler|. - void OnMTPStorageSignal(MTPStorageEventHandler handler, - bool is_attach, - dbus::Signal* signal) { - dbus::MessageReader reader(signal); - std::string storage_name; - if (!reader.PopString(&storage_name)) { - LOG(ERROR) << "Invalid signal: " << signal->ToString(); - return; - } - DCHECK(!storage_name.empty()); - handler.Run(is_attach, storage_name); - } - - - // Handles the result of signal connection setup. - void OnSignalConnected(const std::string& interface, - const std::string& signal, - bool succeeded) { - LOG_IF(ERROR, !succeeded) << "Connect to " << interface << " " - << signal << " failed."; - } - - dbus::ObjectProxy* const proxy_; - - bool listen_for_changes_called_; - - // Note: This should remain the last member so it'll be destroyed and - // invalidate its weak pointers before any other members are destroyed. - base::WeakPtrFactory<MediaTransferProtocolDaemonClientImpl> weak_ptr_factory_; - - DISALLOW_COPY_AND_ASSIGN(MediaTransferProtocolDaemonClientImpl); -}; - -} // namespace - -//////////////////////////////////////////////////////////////////////////////// -// MediaTransferProtocolDaemonClient - -MediaTransferProtocolDaemonClient::MediaTransferProtocolDaemonClient() = - default; - -MediaTransferProtocolDaemonClient::~MediaTransferProtocolDaemonClient() = - default; - -// static -std::unique_ptr<MediaTransferProtocolDaemonClient> -MediaTransferProtocolDaemonClient::Create(dbus::Bus* bus) { - return std::make_unique<MediaTransferProtocolDaemonClientImpl>(bus); -} - -} // namespace device diff --git a/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.h b/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.h deleted file mode 100644 index e9ac3aadc52..00000000000 --- a/chromium/device/media_transfer_protocol/media_transfer_protocol_daemon_client.h +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Client code to talk to the Media Transfer Protocol daemon. The MTP daemon is -// responsible for communicating with PTP / MTP capable devices like cameras -// and smartphones. - -#ifndef DEVICE_MEDIA_TRANSFER_PROTOCOL_MEDIA_TRANSFER_PROTOCOL_DAEMON_CLIENT_H_ -#define DEVICE_MEDIA_TRANSFER_PROTOCOL_MEDIA_TRANSFER_PROTOCOL_DAEMON_CLIENT_H_ - -#include <stddef.h> -#include <stdint.h> - -#include <memory> -#include <string> -#include <vector> - -#include "base/callback.h" -#include "base/macros.h" -#include "build/build_config.h" -#include "device/media_transfer_protocol/public/mojom/mtp_file_entry.mojom.h" -#include "device/media_transfer_protocol/public/mojom/mtp_storage_info.mojom.h" - -#if !defined(OS_CHROMEOS) -#error "Only used on ChromeOS" -#endif - -namespace dbus { -class Bus; -} - -namespace device { - -// A class to make the actual DBus calls for mtpd service. -// This class only makes calls, result/error handling should be done -// by callbacks. -class MediaTransferProtocolDaemonClient { - public: - // A callback to be called when DBus method call fails. - using ErrorCallback = base::Closure; - - // A callback to handle the result of EnumerateAutoMountableDevices. - // The argument is the enumerated storage names. - using EnumerateStoragesCallback = - base::Callback<void(const std::vector<std::string>& storage_names)>; - - // A callback to handle the result of GetStorageInfo. - // The argument is the information about the specified storage. - using GetStorageInfoCallback = - base::Callback<void(const mojom::MtpStorageInfo& storage_info)>; - - // A callback to handle the result of OpenStorage. - // The argument is the returned handle. - using OpenStorageCallback = base::Callback<void(const std::string& handle)>; - - // A callback to handle the result of CloseStorage. - using CloseStorageCallback = base::Closure; - - // A callback to handle the result of CreateDirectory. - using CreateDirectoryCallback = base::Closure; - - // A callback to handle the result of ReadDirectoryEntryIds. - // The argument is a vector of file ids. - using ReadDirectoryEntryIdsCallback = - base::Callback<void(const std::vector<uint32_t>& file_ids)>; - - // A callback to handle the result of GetFileInfo. - // The argument is a vector of file entries. - using GetFileInfoCallback = - base::Callback<void(const std::vector<mojom::MtpFileEntry>& file_entries)>; - - // A callback to handle the result of ReadFileChunkById. - // The argument is a string containing the file data. - using ReadFileCallback = base::Callback<void(const std::string& data)>; - - // A callback to handle the result of RenameObject. - using RenameObjectCallback = base::Closure; - - // A callback to handle the result of CopyFileFromLocal. - using CopyFileFromLocalCallback = base::Closure; - - // A callback to handle the result of DeleteObject. - using DeleteObjectCallback = base::Closure; - - // A callback to handle storage attach/detach events. - // The first argument is true for attach, false for detach. - // The second argument is the storage name. - using MTPStorageEventHandler = - base::Callback<void(bool is_attach, const std::string& storage_name)>; - - virtual ~MediaTransferProtocolDaemonClient(); - - // Calls EnumerateStorages method. |callback| is called after the - // method call succeeds, otherwise, |error_callback| is called. - virtual void EnumerateStorages( - const EnumerateStoragesCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls GetStorageInfo method. |callback| is called after the method call - // succeeds, otherwise, |error_callback| is called. - virtual void GetStorageInfo(const std::string& storage_name, - const GetStorageInfoCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls GetStorageInfoFromDevice method. |callback| is called after the - // method call succeeds, otherwise, |error_callback| is called. - virtual void GetStorageInfoFromDevice( - const std::string& storage_name, - const GetStorageInfoCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls OpenStorage method. |callback| is called after the method call - // succeeds, otherwise, |error_callback| is called. - // OpenStorage returns a handle in |callback|. - virtual void OpenStorage(const std::string& storage_name, - const std::string& mode, - const OpenStorageCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls CloseStorage method. |callback| is called after the method call - // succeeds, otherwise, |error_callback| is called. - // |handle| comes from a OpenStorageCallback. - virtual void CloseStorage(const std::string& handle, - const CloseStorageCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls CreateDirectory method. |callback| is called after the method call - // succeeds, otherwise, |error_callback| is called. - // |parent_id| is an id of the parent directory. - // |directory_name| is name of new directory. - virtual void CreateDirectory(const std::string& handle, - const uint32_t parent_id, - const std::string& directory_name, - const CreateDirectoryCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls ReadDirectoryEntryIds method. |callback| is called after the method - // call succeeds, otherwise, |error_callback| is called. - // |file_id| is a MTP-device specific id for a file. - virtual void ReadDirectoryEntryIds( - const std::string& handle, - uint32_t file_id, - const ReadDirectoryEntryIdsCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls GetFileInfo method. |callback| is called after the method - // call succeeds, otherwise, |error_callback| is called. - // |file_ids| is a list of MTP-device specific file ids. - // |offset| is the index into |file_ids| to read from. - // |entries_to_read| is the maximum number of file entries to read. - virtual void GetFileInfo(const std::string& handle, - const std::vector<uint32_t>& file_ids, - size_t offset, - size_t entries_to_read, - const GetFileInfoCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls ReadFileChunk method. |callback| is called after the method call - // succeeds, otherwise, |error_callback| is called. - // |file_id| is a MTP-device specific id for a file. - // |offset| is the offset into the file. - // |bytes_to_read| cannot exceed 1 MiB. - virtual void ReadFileChunk(const std::string& handle, - uint32_t file_id, - uint32_t offset, - uint32_t bytes_to_read, - const ReadFileCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls RenameObject method. |callback| is called after the method call - // succeeds, otherwise, |error_callback| is called. - // |object_is| is an id of object to be renamed. - // |new_name| is new name of the object. - virtual void RenameObject(const std::string& handle, - const uint32_t object_id, - const std::string& new_name, - const RenameObjectCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls CopyFileFromLocal method. |callback| is called after the method call - // succeeds, otherwise, |error_callback| is called. - // |source_file_descriptor| is a file descriptor of source file. - // |parent_id| is a object id of a target directory. - // |file_name| is a file name of a target file. - virtual void CopyFileFromLocal(const std::string& handle, - const int source_file_descriptor, - const uint32_t parent_id, - const std::string& file_name, - const CopyFileFromLocalCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Calls DeleteObject method. |callback| is called after the method call - // succeeds, otherwise, |error_callback| is called. - // |object_id| is an object id of a file or directory which is deleted. - virtual void DeleteObject(const std::string& handle, - const uint32_t object_id, - const DeleteObjectCallback& callback, - const ErrorCallback& error_callback) = 0; - - // Registers given callback for events. Should only be called once. - // |storage_event_handler| is called when a mtp storage attach or detach - // signal is received. - virtual void ListenForChanges(const MTPStorageEventHandler& handler) = 0; - - // Factory function, creates a new instance. - static std::unique_ptr<MediaTransferProtocolDaemonClient> Create( - dbus::Bus* bus); - - protected: - // Create() should be used instead. - MediaTransferProtocolDaemonClient(); - - private: - DISALLOW_COPY_AND_ASSIGN(MediaTransferProtocolDaemonClient); -}; - -} // namespace device - -#endif // DEVICE_MEDIA_TRANSFER_PROTOCOL_MEDIA_TRANSFER_PROTOCOL_DAEMON_CLIENT_H_ diff --git a/chromium/device/media_transfer_protocol/media_transfer_protocol_manager.cc b/chromium/device/media_transfer_protocol/media_transfer_protocol_manager.cc deleted file mode 100644 index c3a9ef0f4a0..00000000000 --- a/chromium/device/media_transfer_protocol/media_transfer_protocol_manager.cc +++ /dev/null @@ -1,707 +0,0 @@ -// Copyright (c) 2012 The Chromium 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 "device/media_transfer_protocol/media_transfer_protocol_manager.h" - -#include <algorithm> -#include <memory> -#include <utility> - -#include "base/bind.h" -#include "base/command_line.h" -#include "base/containers/flat_map.h" -#include "base/containers/flat_set.h" -#include "base/containers/queue.h" -#include "base/location.h" -#include "base/logging.h" -#include "base/macros.h" -#include "base/memory/weak_ptr.h" -#include "base/observer_list.h" -#include "base/sequenced_task_runner.h" -#include "base/stl_util.h" -#include "base/threading/thread_checker.h" -#include "chromeos/dbus/dbus_thread_manager.h" -#include "dbus/bus.h" -#include "device/media_transfer_protocol/media_transfer_protocol_daemon_client.h" -#include "device/media_transfer_protocol/mtp_storage_info.pb.h" -#include "third_party/cros_system_api/dbus/service_constants.h" - -namespace device { - -namespace { - -#if DCHECK_IS_ON() -MediaTransferProtocolManager* g_media_transfer_protocol_manager = nullptr; -#endif - -// When reading directory entries, this is the number of entries for -// GetFileInfo() to read in one operation. If set too low, efficiency goes down -// slightly due to the overhead of D-Bus calls. If set too high, then slow -// devices may trigger a D-Bus timeout. -// The value below is a good initial estimate. -const size_t kFileInfoToFetchChunkSize = 25; - -// On the first call to GetFileInfo, the offset to use is 0. -const size_t kInitialOffset = 0; - -// The MediaTransferProtocolManager implementation. -class MediaTransferProtocolManagerImpl : public MediaTransferProtocolManager { - public: - MediaTransferProtocolManagerImpl() - : bus_(chromeos::DBusThreadManager::Get()->GetSystemBus()), - weak_ptr_factory_(this) { - // Listen for future mtpd service owner changes, in case it is not - // available right now. There is no guarantee that mtpd is running already. - mtpd_owner_changed_callback_ = - base::Bind(&MediaTransferProtocolManagerImpl::FinishSetupOnOriginThread, - weak_ptr_factory_.GetWeakPtr()); - if (bus_) { - bus_->ListenForServiceOwnerChange(mtpd::kMtpdServiceName, - mtpd_owner_changed_callback_); - bus_->GetServiceOwner(mtpd::kMtpdServiceName, - mtpd_owner_changed_callback_); - } - } - - ~MediaTransferProtocolManagerImpl() override { -#if DCHECK_IS_ON() - DCHECK(g_media_transfer_protocol_manager); - g_media_transfer_protocol_manager = nullptr; -#endif - if (bus_) { - bus_->UnlistenForServiceOwnerChange(mtpd::kMtpdServiceName, - mtpd_owner_changed_callback_); - } - - VLOG(1) << "MediaTransferProtocolManager Shutdown completed"; - } - - // MediaTransferProtocolManager override. - void AddObserverAndEnumerateStorages( - Observer* observer, - EnumerateStoragesCallback callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - - // Return all available storage info. - std::vector<const mojom::MtpStorageInfo*> storage_info_list; - storage_info_list.reserve(storage_info_map_.size()); - for (const auto& info : storage_info_map_) - storage_info_list.push_back(&info.second); - std::move(callback).Run(std::move(storage_info_list)); - - observers_.AddObserver(observer); - } - - // MediaTransferProtocolManager override. - void RemoveObserver(Observer* observer) override { - DCHECK(thread_checker_.CalledOnValidThread()); - observers_.RemoveObserver(observer); - } - - // MediaTransferProtocolManager override. - void GetStorages(GetStoragesCallback callback) const override { - DCHECK(thread_checker_.CalledOnValidThread()); - std::vector<std::string> storages; - storages.reserve(storage_info_map_.size()); - for (const auto& info : storage_info_map_) - storages.push_back(info.first); - std::move(callback).Run(storages); - } - - // MediaTransferProtocolManager override. - void GetStorageInfo(const std::string& storage_name, - GetStorageInfoCallback callback) const override { - DCHECK(thread_checker_.CalledOnValidThread()); - const auto it = storage_info_map_.find(storage_name); - const auto* storage_info = - it != storage_info_map_.end() ? &it->second : nullptr; - std::move(callback).Run(storage_info); - } - - // MediaTransferProtocolManager override. - void GetStorageInfoFromDevice( - const std::string& storage_name, - const GetStorageInfoFromDeviceCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(storage_info_map_, storage_name) || !mtp_client_) { - mojom::MtpStorageInfo info; - callback.Run(info, true /* error */); - return; - } - get_storage_info_from_device_callbacks_.push(callback); - mtp_client_->GetStorageInfoFromDevice( - storage_name, - base::Bind( - &MediaTransferProtocolManagerImpl::OnGetStorageInfoFromDevice, - weak_ptr_factory_.GetWeakPtr()), - base::Bind( - &MediaTransferProtocolManagerImpl::OnGetStorageInfoFromDeviceError, - weak_ptr_factory_.GetWeakPtr())); - } - - // MediaTransferProtocolManager override. - void OpenStorage(const std::string& storage_name, - const std::string& mode, - const OpenStorageCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(storage_info_map_, storage_name) || !mtp_client_) { - callback.Run(std::string(), true); - return; - } - open_storage_callbacks_.push(callback); - mtp_client_->OpenStorage( - storage_name, - mode, - base::Bind(&MediaTransferProtocolManagerImpl::OnOpenStorage, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&MediaTransferProtocolManagerImpl::OnOpenStorageError, - weak_ptr_factory_.GetWeakPtr())); - } - - // MediaTransferProtocolManager override. - void CloseStorage(const std::string& storage_handle, - const CloseStorageCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, storage_handle) || !mtp_client_) { - callback.Run(true); - return; - } - close_storage_callbacks_.push(std::make_pair(callback, storage_handle)); - mtp_client_->CloseStorage( - storage_handle, - base::Bind(&MediaTransferProtocolManagerImpl::OnCloseStorage, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&MediaTransferProtocolManagerImpl::OnCloseStorageError, - weak_ptr_factory_.GetWeakPtr())); - } - - void CreateDirectory(const std::string& storage_handle, - const uint32_t parent_id, - const std::string& directory_name, - const CreateDirectoryCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, storage_handle) || !mtp_client_) { - callback.Run(true /* error */); - return; - } - create_directory_callbacks_.push(callback); - mtp_client_->CreateDirectory( - storage_handle, parent_id, directory_name, - base::Bind(&MediaTransferProtocolManagerImpl::OnCreateDirectory, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&MediaTransferProtocolManagerImpl::OnCreateDirectoryError, - weak_ptr_factory_.GetWeakPtr())); - } - - // MediaTransferProtocolManager override. - void ReadDirectory(const std::string& storage_handle, - const uint32_t file_id, - const size_t max_size, - const ReadDirectoryCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, storage_handle) || !mtp_client_) { - callback.Run(std::vector<mojom::MtpFileEntry>(), - false /* no more entries */, - true /* error */); - return; - } - read_directory_callbacks_.push(callback); - mtp_client_->ReadDirectoryEntryIds( - storage_handle, file_id, - base::Bind(&MediaTransferProtocolManagerImpl:: - OnReadDirectoryEntryIdsToReadDirectory, - weak_ptr_factory_.GetWeakPtr(), storage_handle, max_size), - base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectoryError, - weak_ptr_factory_.GetWeakPtr())); - } - - // MediaTransferProtocolManager override. - void ReadFileChunk(const std::string& storage_handle, - uint32_t file_id, - uint32_t offset, - uint32_t count, - const ReadFileCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, storage_handle) || !mtp_client_) { - callback.Run(std::string(), true); - return; - } - read_file_callbacks_.push(callback); - mtp_client_->ReadFileChunk( - storage_handle, file_id, offset, count, - base::Bind(&MediaTransferProtocolManagerImpl::OnReadFile, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&MediaTransferProtocolManagerImpl::OnReadFileError, - weak_ptr_factory_.GetWeakPtr())); - } - - void GetFileInfo(const std::string& storage_handle, - uint32_t file_id, - const GetFileInfoCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, storage_handle) || !mtp_client_) { - callback.Run(mojom::MtpFileEntry(), true); - return; - } - std::vector<uint32_t> file_ids; - file_ids.push_back(file_id); - get_file_info_callbacks_.push(callback); - mtp_client_->GetFileInfo( - storage_handle, - file_ids, - kInitialOffset, - file_ids.size(), - base::Bind(&MediaTransferProtocolManagerImpl::OnGetFileInfo, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&MediaTransferProtocolManagerImpl::OnGetFileInfoError, - weak_ptr_factory_.GetWeakPtr())); - } - - void RenameObject(const std::string& storage_handle, - const uint32_t object_id, - const std::string& new_name, - const RenameObjectCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, storage_handle) || !mtp_client_) { - callback.Run(true /* error */); - return; - } - rename_object_callbacks_.push(callback); - mtp_client_->RenameObject( - storage_handle, object_id, new_name, - base::Bind(&MediaTransferProtocolManagerImpl::OnRenameObject, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&MediaTransferProtocolManagerImpl::OnRenameObjectError, - weak_ptr_factory_.GetWeakPtr())); - } - - void CopyFileFromLocal(const std::string& storage_handle, - const int source_file_descriptor, - const uint32_t parent_id, - const std::string& file_name, - const CopyFileFromLocalCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, storage_handle) || !mtp_client_) { - callback.Run(true /* error */); - return; - } - copy_file_from_local_callbacks_.push(callback); - mtp_client_->CopyFileFromLocal( - storage_handle, source_file_descriptor, parent_id, file_name, - base::Bind(&MediaTransferProtocolManagerImpl::OnCopyFileFromLocal, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&MediaTransferProtocolManagerImpl::OnCopyFileFromLocalError, - weak_ptr_factory_.GetWeakPtr())); - } - - void DeleteObject(const std::string& storage_handle, - const uint32_t object_id, - const DeleteObjectCallback& callback) override { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, storage_handle) || !mtp_client_) { - callback.Run(true /* error */); - return; - } - delete_object_callbacks_.push(callback); - mtp_client_->DeleteObject( - storage_handle, object_id, - base::Bind(&MediaTransferProtocolManagerImpl::OnDeleteObject, - weak_ptr_factory_.GetWeakPtr()), - base::Bind(&MediaTransferProtocolManagerImpl::OnDeleteObjectError, - weak_ptr_factory_.GetWeakPtr())); - } - - private: - // Map of storage names to storage info. - using GetStorageInfoFromDeviceCallbackQueue = - base::queue<GetStorageInfoFromDeviceCallback>; - // Callback queues - DBus communication is in-order, thus callbacks are - // received in the same order as the requests. - using OpenStorageCallbackQueue = base::queue<OpenStorageCallback>; - // (callback, handle) - using CloseStorageCallbackQueue = - base::queue<std::pair<CloseStorageCallback, std::string>>; - using CreateDirectoryCallbackQueue = base::queue<CreateDirectoryCallback>; - using ReadDirectoryCallbackQueue = base::queue<ReadDirectoryCallback>; - using ReadFileCallbackQueue = base::queue<ReadFileCallback>; - using GetFileInfoCallbackQueue = base::queue<GetFileInfoCallback>; - using RenameObjectCallbackQueue = base::queue<RenameObjectCallback>; - using CopyFileFromLocalCallbackQueue = base::queue<CopyFileFromLocalCallback>; - using DeleteObjectCallbackQueue = base::queue<DeleteObjectCallback>; - - void OnStorageAttached(const std::string& storage_name) { - DCHECK(thread_checker_.CalledOnValidThread()); - mtp_client_->GetStorageInfo( - storage_name, - base::Bind(&MediaTransferProtocolManagerImpl::OnGetStorageInfo, - weak_ptr_factory_.GetWeakPtr()), - base::DoNothing()); - } - - void OnStorageDetached(const std::string& storage_name) { - DCHECK(thread_checker_.CalledOnValidThread()); - if (storage_info_map_.erase(storage_name) == 0) { - // This can happen for a storage where - // MediaTransferProtocolDaemonClient::GetStorageInfo() failed. - // Return to avoid giving observers phantom detach events. - return; - } - for (auto& observer : observers_) - observer.StorageDetached(storage_name); - } - - void OnStorageChanged(bool is_attach, const std::string& storage_name) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(mtp_client_); - if (is_attach) - OnStorageAttached(storage_name); - else - OnStorageDetached(storage_name); - } - - void OnEnumerateStorages(const std::vector<std::string>& storage_names) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(mtp_client_); - for (const auto& name : storage_names) { - if (base::ContainsKey(storage_info_map_, name)) { - // OnStorageChanged() might have gotten called first. - continue; - } - OnStorageAttached(name); - } - } - - void OnGetStorageInfo(const mojom::MtpStorageInfo& storage_info) { - DCHECK(thread_checker_.CalledOnValidThread()); - const std::string& storage_name = storage_info.storage_name; - if (base::ContainsKey(storage_info_map_, storage_name)) { - // This should not happen, since MediaTransferProtocolManagerImpl should - // only call EnumerateStorages() once, which populates |storage_info_map_| - // with the already-attached devices. - // After that, all incoming signals are either for new storage - // attachments, which should not be in |storage_info_map_|, or for - // storage detachments, which do not add to |storage_info_map_|. - // Return to avoid giving observers phantom detach events. - NOTREACHED(); - return; - } - - // New storage. Add it and let the observers know. - storage_info_map_.insert(std::make_pair(storage_name, storage_info)); - - for (auto& observer : observers_) - observer.StorageAttached(storage_info); - } - - void OnGetStorageInfoFromDevice(const mojom::MtpStorageInfo& storage_info) { - get_storage_info_from_device_callbacks_.front().Run(storage_info, - false /* no error */); - get_storage_info_from_device_callbacks_.pop(); - } - - void OnGetStorageInfoFromDeviceError() { - mojom::MtpStorageInfo info; - get_storage_info_from_device_callbacks_.front().Run(info, true /* error */); - get_storage_info_from_device_callbacks_.pop(); - } - - void OnOpenStorage(const std::string& handle) { - DCHECK(thread_checker_.CalledOnValidThread()); - if (!base::ContainsKey(handles_, handle)) { - handles_.insert(handle); - open_storage_callbacks_.front().Run(handle, false); - } else { - NOTREACHED(); - open_storage_callbacks_.front().Run(std::string(), true); - } - open_storage_callbacks_.pop(); - } - - void OnOpenStorageError() { - open_storage_callbacks_.front().Run(std::string(), true); - open_storage_callbacks_.pop(); - } - - void OnCloseStorage() { - DCHECK(thread_checker_.CalledOnValidThread()); - const std::string& handle = close_storage_callbacks_.front().second; - if (base::ContainsKey(handles_, handle)) { - handles_.erase(handle); - close_storage_callbacks_.front().first.Run(false); - } else { - NOTREACHED(); - close_storage_callbacks_.front().first.Run(true); - } - close_storage_callbacks_.pop(); - } - - void OnCloseStorageError() { - DCHECK(thread_checker_.CalledOnValidThread()); - close_storage_callbacks_.front().first.Run(true); - close_storage_callbacks_.pop(); - } - - void OnCreateDirectory() { - DCHECK(thread_checker_.CalledOnValidThread()); - create_directory_callbacks_.front().Run(false /* no error */); - create_directory_callbacks_.pop(); - } - - void OnCreateDirectoryError() { - DCHECK(thread_checker_.CalledOnValidThread()); - create_directory_callbacks_.front().Run(true /* error */); - create_directory_callbacks_.pop(); - } - - void OnReadDirectoryEntryIdsToReadDirectory( - const std::string& storage_handle, - const size_t max_size, - const std::vector<uint32_t>& file_ids) { - DCHECK(thread_checker_.CalledOnValidThread()); - - if (file_ids.empty()) { - OnGotDirectoryEntries(storage_handle, file_ids, kInitialOffset, max_size, - file_ids, std::vector<mojom::MtpFileEntry>()); - return; - } - - std::vector<uint32_t> sorted_file_ids = file_ids; - std::sort(sorted_file_ids.begin(), sorted_file_ids.end()); - - const size_t chunk_size = - max_size == 0 ? kFileInfoToFetchChunkSize - : std::min(max_size, kFileInfoToFetchChunkSize); - - mtp_client_->GetFileInfo( - storage_handle, file_ids, kInitialOffset, chunk_size, - base::Bind(&MediaTransferProtocolManagerImpl::OnGotDirectoryEntries, - weak_ptr_factory_.GetWeakPtr(), storage_handle, file_ids, - kInitialOffset, max_size, sorted_file_ids), - base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectoryError, - weak_ptr_factory_.GetWeakPtr())); - } - - void OnGotDirectoryEntries( - const std::string& storage_handle, - const std::vector<uint32_t>& file_ids, - const size_t offset, - const size_t max_size, - const std::vector<uint32_t>& sorted_file_ids, - const std::vector<mojom::MtpFileEntry>& file_entries) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK_EQ(file_ids.size(), sorted_file_ids.size()); - - // Use |sorted_file_ids| to sanity check and make sure the results are a - // subset of the requested file ids. - for (const auto& entry : file_entries) { - std::vector<uint32_t>::const_iterator it = std::lower_bound( - sorted_file_ids.begin(), sorted_file_ids.end(), entry.item_id); - if (it == sorted_file_ids.end()) { - OnReadDirectoryError(); - return; - } - } - - const size_t directory_size = - max_size == 0 ? file_ids.size() : std::min(file_ids.size(), max_size); - size_t next_offset = directory_size; - if (offset < SIZE_MAX - kFileInfoToFetchChunkSize) - next_offset = std::min(next_offset, offset + kFileInfoToFetchChunkSize); - bool has_more = next_offset < directory_size; - read_directory_callbacks_.front().Run(file_entries, - has_more, - false /* no error */); - - if (has_more) { - const size_t chunk_size = - std::min(directory_size - next_offset, kFileInfoToFetchChunkSize); - - mtp_client_->GetFileInfo( - storage_handle, file_ids, next_offset, chunk_size, - base::Bind(&MediaTransferProtocolManagerImpl::OnGotDirectoryEntries, - weak_ptr_factory_.GetWeakPtr(), storage_handle, file_ids, - next_offset, max_size, sorted_file_ids), - base::Bind(&MediaTransferProtocolManagerImpl::OnReadDirectoryError, - weak_ptr_factory_.GetWeakPtr())); - return; - } - read_directory_callbacks_.pop(); - } - - void OnReadDirectoryError() { - DCHECK(thread_checker_.CalledOnValidThread()); - read_directory_callbacks_.front().Run(std::vector<mojom::MtpFileEntry>(), - false /* no more entries */, - true /* error */); - read_directory_callbacks_.pop(); - } - - void OnReadFile(const std::string& data) { - DCHECK(thread_checker_.CalledOnValidThread()); - read_file_callbacks_.front().Run(data, false); - read_file_callbacks_.pop(); - } - - void OnReadFileError() { - DCHECK(thread_checker_.CalledOnValidThread()); - read_file_callbacks_.front().Run(std::string(), true); - read_file_callbacks_.pop(); - } - - void OnGetFileInfo(const std::vector<mojom::MtpFileEntry>& entries) { - DCHECK(thread_checker_.CalledOnValidThread()); - if (entries.size() == 1) { - get_file_info_callbacks_.front().Run(entries[0], false /* no error */); - get_file_info_callbacks_.pop(); - } else { - OnGetFileInfoError(); - } - } - - void OnGetFileInfoError() { - DCHECK(thread_checker_.CalledOnValidThread()); - get_file_info_callbacks_.front().Run(mojom::MtpFileEntry(), true); - get_file_info_callbacks_.pop(); - } - - void OnRenameObject() { - DCHECK(thread_checker_.CalledOnValidThread()); - rename_object_callbacks_.front().Run(false /* no error */); - rename_object_callbacks_.pop(); - } - - void OnRenameObjectError() { - DCHECK(thread_checker_.CalledOnValidThread()); - rename_object_callbacks_.front().Run(true /* error */); - rename_object_callbacks_.pop(); - } - - void OnCopyFileFromLocal() { - DCHECK(thread_checker_.CalledOnValidThread()); - copy_file_from_local_callbacks_.front().Run(false /* no error */); - copy_file_from_local_callbacks_.pop(); - } - - void OnCopyFileFromLocalError() { - DCHECK(thread_checker_.CalledOnValidThread()); - copy_file_from_local_callbacks_.front().Run(true /* error */); - copy_file_from_local_callbacks_.pop(); - } - - void OnDeleteObject() { - DCHECK(thread_checker_.CalledOnValidThread()); - delete_object_callbacks_.front().Run(false /* no error */); - delete_object_callbacks_.pop(); - } - - void OnDeleteObjectError() { - DCHECK(thread_checker_.CalledOnValidThread()); - delete_object_callbacks_.front().Run(true /* error */); - delete_object_callbacks_.pop(); - } - - // Callback to finish initialization after figuring out if the mtpd service - // has an owner, or if the service owner has changed. - // |mtpd_service_owner| contains the name of the current owner, if any. - void FinishSetupOnOriginThread(const std::string& mtpd_service_owner) { - DCHECK(thread_checker_.CalledOnValidThread()); - - if (mtpd_service_owner == current_mtpd_owner_) - return; - - // In the case of a new service owner, clear |storage_info_map_|. - // Assume all storages have been disconnected. If there is a new service - // owner, reconnecting to it will reconnect all the storages as well. - - // Save a copy of |storage_info_map_| keys as |storage_info_map_| can - // change in OnStorageDetached(). - std::vector<std::string> storage_names; - storage_names.reserve(storage_info_map_.size()); - for (const auto& info : storage_info_map_) - storage_names.push_back(info.first); - - for (const auto& name : storage_names) - OnStorageDetached(name); - - if (mtpd_service_owner.empty()) { - current_mtpd_owner_.clear(); - mtp_client_.reset(); - return; - } - - current_mtpd_owner_ = mtpd_service_owner; - - // |bus_| must be valid here. Otherwise, how did this method get called as a - // callback in the first place? - DCHECK(bus_); - mtp_client_ = MediaTransferProtocolDaemonClient::Create(bus_.get()); - - // Set up signals and start initializing |storage_info_map_|. - mtp_client_->ListenForChanges( - base::Bind(&MediaTransferProtocolManagerImpl::OnStorageChanged, - weak_ptr_factory_.GetWeakPtr())); - mtp_client_->EnumerateStorages( - base::Bind(&MediaTransferProtocolManagerImpl::OnEnumerateStorages, - weak_ptr_factory_.GetWeakPtr()), - base::DoNothing()); - } - - // Mtpd DBus client. - std::unique_ptr<MediaTransferProtocolDaemonClient> mtp_client_; - - // And a D-Bus session for talking to mtpd. Note: In production, this is never - // a nullptr, but in tests it oftentimes is. It may be too much work for - // DBusThreadManager to provide a bus in unit tests. - scoped_refptr<dbus::Bus> const bus_; - - // Device attachment / detachment observers. - base::ObserverList<Observer> observers_; - - // Map to keep track of attached storages by name. - base::flat_map<std::string, mojom::MtpStorageInfo> storage_info_map_; - - // Set of open storage handles. - base::flat_set<std::string> handles_; - - dbus::Bus::GetServiceOwnerCallback mtpd_owner_changed_callback_; - - std::string current_mtpd_owner_; - - // Queued callbacks. - GetStorageInfoFromDeviceCallbackQueue get_storage_info_from_device_callbacks_; - OpenStorageCallbackQueue open_storage_callbacks_; - CloseStorageCallbackQueue close_storage_callbacks_; - CreateDirectoryCallbackQueue create_directory_callbacks_; - ReadDirectoryCallbackQueue read_directory_callbacks_; - ReadFileCallbackQueue read_file_callbacks_; - GetFileInfoCallbackQueue get_file_info_callbacks_; - RenameObjectCallbackQueue rename_object_callbacks_; - CopyFileFromLocalCallbackQueue copy_file_from_local_callbacks_; - DeleteObjectCallbackQueue delete_object_callbacks_; - - base::ThreadChecker thread_checker_; - - base::WeakPtrFactory<MediaTransferProtocolManagerImpl> weak_ptr_factory_; - - DISALLOW_COPY_AND_ASSIGN(MediaTransferProtocolManagerImpl); -}; - -} // namespace - -// static -std::unique_ptr<MediaTransferProtocolManager> -MediaTransferProtocolManager::Initialize() { - auto manager = std::make_unique<MediaTransferProtocolManagerImpl>(); - - VLOG(1) << "MediaTransferProtocolManager initialized"; - -#if DCHECK_IS_ON() - DCHECK(!g_media_transfer_protocol_manager); - g_media_transfer_protocol_manager = manager.get(); -#endif - - return manager; -} - -} // namespace device diff --git a/chromium/device/media_transfer_protocol/media_transfer_protocol_manager.h b/chromium/device/media_transfer_protocol/media_transfer_protocol_manager.h deleted file mode 100644 index dde53322a8b..00000000000 --- a/chromium/device/media_transfer_protocol/media_transfer_protocol_manager.h +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef DEVICE_MEDIA_TRANSFER_PROTOCOL_MEDIA_TRANSFER_PROTOCOL_MANAGER_H_ -#define DEVICE_MEDIA_TRANSFER_PROTOCOL_MEDIA_TRANSFER_PROTOCOL_MANAGER_H_ - -#include <stddef.h> -#include <stdint.h> - -#include <memory> -#include <string> -#include <vector> - -#include "base/callback.h" -#include "base/memory/ref_counted.h" -#include "build/build_config.h" -#include "device/media_transfer_protocol/public/mojom/mtp_file_entry.mojom.h" -#include "device/media_transfer_protocol/public/mojom/mtp_storage_info.mojom.h" - -#if !defined(OS_CHROMEOS) -#error "Only used on ChromeOS" -#endif - -namespace device { - -// This class handles the interaction with mtpd. -// Other classes can add themselves as observers. -class MediaTransferProtocolManager { - public: - // A callback to handle the result of AddObserverAndEnumerateStorages(). - // The argument is the returned vector of available MTP storages info. - // The pointers in the vector are guaranteed to be non-NULL. - using EnumerateStoragesCallback = base::OnceCallback<void( - std::vector<const mojom::MtpStorageInfo*> storage_info_list)>; - - // A callback to handle the result of GetStorages(). - // The argument is the returned vector of available MTP storage names. - using GetStoragesCallback = - base::OnceCallback<void(const std::vector<std::string>& storages)>; - - // A callback to receive the result of GetStorageInfo(). - // On success, the |storage_info| argument contains the storage metadata. - // Otherwise, |storage_info| is a nullptr. - using GetStorageInfoCallback = - base::OnceCallback<void(const mojom::MtpStorageInfo* storage_info)>; - - // A callback to handle the result of GetStorageInfoFromDevice. - // The first argument is the returned storage info. - // The second argument is true if there was an error. - using GetStorageInfoFromDeviceCallback = - base::Callback<void(const mojom::MtpStorageInfo& storage_info, - const bool error)>; - - // A callback to handle the result of OpenStorage. - // The first argument is the returned handle. - // The second argument is true if there was an error. - using OpenStorageCallback = - base::Callback<void(const std::string& handle, bool error)>; - - // A callback to handle the result of CloseStorage. - // The argument is true if there was an error. - using CloseStorageCallback = base::Callback<void(bool error)>; - - // A callback to handle the result of CreateDirectory. - // The first argument is true if there was an error. - using CreateDirectoryCallback = base::Callback<void(bool error)>; - - // A callback to handle the result of ReadDirectory. - // The first argument is a vector of file entries. - // The second argument is true if there are more file entries. - // The third argument is true if there was an error. - using ReadDirectoryCallback = - base::Callback<void(const std::vector<mojom::MtpFileEntry>& file_entries, - bool has_more, - bool error)>; - - // A callback to handle the result of ReadFileChunk. - // The first argument is a string containing the file data. - // The second argument is true if there was an error. - using ReadFileCallback = - base::Callback<void(const std::string& data, bool error)>; - - // A callback to handle the result of GetFileInfo. - // The first argument is a file entry. - // The second argument is true if there was an error. - using GetFileInfoCallback = - base::Callback<void(const mojom::MtpFileEntry& file_entry, bool error)>; - - // A callback to handle the result of RenameObject. - // The first argument is true if there was an error. - using RenameObjectCallback = base::Callback<void(bool error)>; - - // A callback to handle the result of CopyFileFromLocal. - // The first argument is true if there was an error. - using CopyFileFromLocalCallback = base::Callback<void(bool error)>; - - // A callback to handle the result of DeleteObject. - // The first argument is true if there was an error. - using DeleteObjectCallback = base::Callback<void(bool error)>; - - // Implement this interface to be notified about MTP storage - // attachment / detachment events. - class Observer { - public: - virtual ~Observer() {} - - // Functions called after a MTP storage has been attached / detached. - virtual void StorageAttached( - const device::mojom::MtpStorageInfo& storage_info) = 0; - virtual void StorageDetached(const std::string& storage_name) = 0; - }; - - virtual ~MediaTransferProtocolManager() {} - - // Adds an observer and runs |callback| with a list of existing storages. - virtual void AddObserverAndEnumerateStorages( - Observer* observer, - EnumerateStoragesCallback callback) = 0; - - // Removes an observer. - virtual void RemoveObserver(Observer* observer) = 0; - - // Gets all available MTP storages and runs |callback|. - virtual void GetStorages(GetStoragesCallback callback) const = 0; - - // Gets the metadata for |storage_name| and runs |callback| synchronously. - virtual void GetStorageInfo(const std::string& storage_name, - GetStorageInfoCallback callback) const = 0; - - // Read the metadata of |storage_name| from device and runs |callback|. - virtual void GetStorageInfoFromDevice( - const std::string& storage_name, - const GetStorageInfoFromDeviceCallback& callback) = 0; - - // Opens |storage_name| in |mode| and runs |callback|. - virtual void OpenStorage(const std::string& storage_name, - const std::string& mode, - const OpenStorageCallback& callback) = 0; - - // Close |storage_handle| and runs |callback|. - virtual void CloseStorage(const std::string& storage_handle, - const CloseStorageCallback& callback) = 0; - - // Creates |directory_name| in |parent_id|. - virtual void CreateDirectory(const std::string& storage_handle, - const uint32_t parent_id, - const std::string& directory_name, - const CreateDirectoryCallback& callback) = 0; - - // Reads directory entries from |file_id| on |storage_handle| and runs - // |callback|. |max_size| is a maximum number of files to be read. - virtual void ReadDirectory(const std::string& storage_handle, - const uint32_t file_id, - const size_t max_size, - const ReadDirectoryCallback& callback) = 0; - - // Reads file data from |file_id| on |storage_handle| and runs |callback|. - // Reads |count| bytes of data starting at |offset|. - virtual void ReadFileChunk(const std::string& storage_handle, - uint32_t file_id, - uint32_t offset, - uint32_t count, - const ReadFileCallback& callback) = 0; - - // Gets the file metadata for |file_id| on |storage_handle| and runs - // |callback|. - virtual void GetFileInfo(const std::string& storage_handle, - uint32_t file_id, - const GetFileInfoCallback& callback) = 0; - - // Renames |object_id| to |new_name|. - virtual void RenameObject(const std::string& storage_handle, - const uint32_t object_id, - const std::string& new_name, - const RenameObjectCallback& callback) = 0; - - // Copies the file from |source_file_descriptor| to |file_name| on - // |parent_id|. - virtual void CopyFileFromLocal(const std::string& storage_handle, - const int source_file_descriptor, - const uint32_t parent_id, - const std::string& file_name, - const CopyFileFromLocalCallback& callback) = 0; - - // Deletes |object_id|. - virtual void DeleteObject(const std::string& storage_handle, - const uint32_t object_id, - const DeleteObjectCallback& callback) = 0; - - // Creates and returns the global MediaTransferProtocolManager instance. - static std::unique_ptr<MediaTransferProtocolManager> Initialize(); -}; - -} // namespace device - -#endif // DEVICE_MEDIA_TRANSFER_PROTOCOL_MEDIA_TRANSFER_PROTOCOL_MANAGER_H_ diff --git a/chromium/device/media_transfer_protocol/public/mojom/BUILD.gn b/chromium/device/media_transfer_protocol/public/mojom/BUILD.gn deleted file mode 100644 index 444a0ce1a64..00000000000 --- a/chromium/device/media_transfer_protocol/public/mojom/BUILD.gn +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright 2017 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import("//mojo/public/tools/bindings/mojom.gni") - -mojom("mojom") { - sources = [ - "mtp_file_entry.mojom", - "mtp_storage_info.mojom", - ] -} diff --git a/chromium/device/media_transfer_protocol/public/mojom/OWNERS b/chromium/device/media_transfer_protocol/public/mojom/OWNERS deleted file mode 100644 index 08850f42120..00000000000 --- a/chromium/device/media_transfer_protocol/public/mojom/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -per-file *.mojom=set noparent -per-file *.mojom=file://ipc/SECURITY_OWNERS diff --git a/chromium/device/media_transfer_protocol/public/mojom/mtp_file_entry.mojom b/chromium/device/media_transfer_protocol/public/mojom/mtp_file_entry.mojom deleted file mode 100644 index 3d65bb8a9c0..00000000000 --- a/chromium/device/media_transfer_protocol/public/mojom/mtp_file_entry.mojom +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -module device.mojom; - -// This is a mojo counterpart of the MtpFileEntry protobuf message from -// //src/third_party/cros_system_api/dbus/mtp_file_entry.proto -// See discussion on https://crbug.com/769630. -struct MtpFileEntry { - enum FileType { - FILE_TYPE_FOLDER = 0, - FILE_TYPE_JPEG = 14, - FILE_TYPE_JFIF = 15, - FILE_TYPE_TIFF = 16, - FILE_TYPE_BMP = 17, - FILE_TYPE_GIF = 18, - FILE_TYPE_PICT = 19, - FILE_TYPE_PNG = 20, - FILE_TYPE_WINDOWSIMAGEFORMAT = 25, - FILE_TYPE_JP2 = 40, - FILE_TYPE_JPX = 41, - FILE_TYPE_UNKNOWN = 44, - FILE_TYPE_OTHER = 9999 - }; - - uint32 item_id = 0xFFFFFFFF; - uint32 parent_id = 0XFFFFFFFF; - string file_name; - uint64 file_size = 0; - int64 modification_time = 0; - FileType file_type = FileType.FILE_TYPE_UNKNOWN; -}; diff --git a/chromium/device/media_transfer_protocol/public/mojom/mtp_storage_info.mojom b/chromium/device/media_transfer_protocol/public/mojom/mtp_storage_info.mojom deleted file mode 100644 index dcdb9175d7e..00000000000 --- a/chromium/device/media_transfer_protocol/public/mojom/mtp_storage_info.mojom +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -module device.mojom; - -// This is a mojo counterpart of the MtpStorageInfo protobuf message from -// //src/third_party/cros_system_api/dbus/mtp_storage_info.proto -// See discussion on https://crbug.com/769630. -struct MtpStorageInfo { - string storage_name; - string vendor; - uint32 vendor_id = 0; - string product; - uint32 product_id = 0; - uint32 device_flags = 0; - uint32 storage_type = 0; - uint32 filesystem_type = 0; - uint32 access_capability = 0; - uint64 max_capacity = 0; - uint64 free_space_in_bytes = 0; - uint64 free_space_in_objects = 0; - string storage_description; - string volume_identifier; -}; diff --git a/chromium/device/serial/BUILD.gn b/chromium/device/serial/BUILD.gn index fa48ed0c977..77132bfc86d 100644 --- a/chromium/device/serial/BUILD.gn +++ b/chromium/device/serial/BUILD.gn @@ -38,8 +38,6 @@ if (is_win || is_linux || is_mac) { "serial_device_enumerator_win.h", "serial_io_handler.cc", "serial_io_handler.h", - "serial_io_handler_posix.cc", - "serial_io_handler_posix.h", "serial_io_handler_win.cc", "serial_io_handler_win.h", ] @@ -58,6 +56,12 @@ if (is_win || is_linux || is_mac) { "//third_party/re2", ] + if (is_posix) { + sources += [ + "serial_io_handler_posix.cc", + "serial_io_handler_posix.h", + ] + } if (use_udev) { deps += [ "//device/udev_linux" ] } diff --git a/chromium/device/serial/serial_device_enumerator_unittest.cc b/chromium/device/serial/serial_device_enumerator_unittest.cc new file mode 100644 index 00000000000..4f0f55ac7d0 --- /dev/null +++ b/chromium/device/serial/serial_device_enumerator_unittest.cc @@ -0,0 +1,23 @@ +// Copyright 2018 The Chromium 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 "device/serial/serial_device_enumerator.h" + +#include <memory> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +TEST(SerialDeviceEnumeratorTest, GetDevices) { + // There is no guarantee that a test machine will have a serial device + // available. The purpose of this test is to ensure that the process of + // attempting to enumerate devices does not cause a crash. + auto enumerator = SerialDeviceEnumerator::Create(); + ASSERT_TRUE(enumerator); + std::vector<mojom::SerialDeviceInfoPtr> devices = enumerator->GetDevices(); +} + +} // namespace device diff --git a/chromium/device/serial/serial_io_handler_win.cc b/chromium/device/serial/serial_io_handler_win.cc index 2f5c2da4528..26efe2c9081 100644 --- a/chromium/device/serial/serial_io_handler_win.cc +++ b/chromium/device/serial/serial_io_handler_win.cc @@ -11,7 +11,7 @@ #include "base/bind.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_current.h" #include "base/scoped_observer.h" #include "base/sequence_checker.h" #include "device/base/device_info_query_win.h" @@ -237,8 +237,8 @@ bool SerialIoHandlerWin::PostOpen() { DCHECK(!read_context_); DCHECK(!write_context_); - base::MessageLoopForIO::current()->RegisterIOHandler(file().GetPlatformFile(), - this); + base::MessageLoopCurrentForIO::Get()->RegisterIOHandler( + file().GetPlatformFile(), this); comm_context_.reset(new base::MessagePumpForIO::IOContext()); read_context_.reset(new base::MessagePumpForIO::IOContext()); diff --git a/chromium/device/usb/mock_usb_device.h b/chromium/device/usb/mock_usb_device.h index f03954e392c..ef09b0a19dd 100644 --- a/chromium/device/usb/mock_usb_device.h +++ b/chromium/device/usb/mock_usb_device.h @@ -44,7 +44,7 @@ class MockUsbDevice : public UsbDevice { const std::string& serial_number, const std::vector<UsbConfigDescriptor>& configurations); - void Open(OpenCallback callback) { OpenInternal(callback); } + void Open(OpenCallback callback) override { OpenInternal(callback); } MOCK_METHOD1(OpenInternal, void(OpenCallback&)); void AddMockConfig(const UsbConfigDescriptor& config); diff --git a/chromium/device/usb/mojo/BUILD.gn b/chromium/device/usb/mojo/BUILD.gn index f31975ddede..0a556a864c5 100644 --- a/chromium/device/usb/mojo/BUILD.gn +++ b/chromium/device/usb/mojo/BUILD.gn @@ -19,7 +19,6 @@ source_set("mojo") { "//device/usb", "//device/usb/public/cpp", "//device/usb/public/mojom", - "//mojo/common", "//mojo/public/cpp/bindings", "//net", ] diff --git a/chromium/device/usb/mojo/DEPS b/chromium/device/usb/mojo/DEPS deleted file mode 100644 index 6cfa565faca..00000000000 --- a/chromium/device/usb/mojo/DEPS +++ /dev/null @@ -1,3 +0,0 @@ -include_rules = [ - "+mojo/common", -] diff --git a/chromium/device/usb/mojo/device_manager_impl_unittest.cc b/chromium/device/usb/mojo/device_manager_impl_unittest.cc index edb46481f7e..ed3ed79e95e 100644 --- a/chromium/device/usb/mojo/device_manager_impl_unittest.cc +++ b/chromium/device/usb/mojo/device_manager_impl_unittest.cc @@ -71,7 +71,7 @@ class USBDeviceManagerImplTest : public testing::Test { class MockDeviceManagerClient : public mojom::UsbDeviceManagerClient { public: MockDeviceManagerClient() : binding_(this) {} - ~MockDeviceManagerClient() = default; + ~MockDeviceManagerClient() override = default; UsbDeviceManagerClientPtr CreateInterfacePtrAndBind() { UsbDeviceManagerClientPtr client; @@ -80,12 +80,12 @@ class MockDeviceManagerClient : public mojom::UsbDeviceManagerClient { } MOCK_METHOD1(DoOnDeviceAdded, void(mojom::UsbDeviceInfo*)); - void OnDeviceAdded(UsbDeviceInfoPtr device_info) { + void OnDeviceAdded(UsbDeviceInfoPtr device_info) override { DoOnDeviceAdded(device_info.get()); } MOCK_METHOD1(DoOnDeviceRemoved, void(mojom::UsbDeviceInfo*)); - void OnDeviceRemoved(UsbDeviceInfoPtr device_info) { + void OnDeviceRemoved(UsbDeviceInfoPtr device_info) override { DoOnDeviceRemoved(device_info.get()); } diff --git a/chromium/device/usb/public/mojom/BUILD.gn b/chromium/device/usb/public/mojom/BUILD.gn index 56bb553f541..1c496ed638a 100644 --- a/chromium/device/usb/public/mojom/BUILD.gn +++ b/chromium/device/usb/public/mojom/BUILD.gn @@ -12,7 +12,6 @@ mojom("mojom") { ] deps = [ - "//mojo/common:common_custom_types", "//mojo/public/mojom/base", ] diff --git a/chromium/device/usb/usb_device_handle_unittest.cc b/chromium/device/usb/usb_device_handle_unittest.cc index 070003d017a..db4ffeba7cf 100644 --- a/chromium/device/usb/usb_device_handle_unittest.cc +++ b/chromium/device/usb/usb_device_handle_unittest.cc @@ -10,7 +10,6 @@ #include "base/bind.h" #include "base/memory/ref_counted_memory.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/test/scoped_task_environment.h" diff --git a/chromium/device/vr/BUILD.gn b/chromium/device/vr/BUILD.gn index ea806c5c9c0..7a50c06d5d6 100644 --- a/chromium/device/vr/BUILD.gn +++ b/chromium/device/vr/BUILD.gn @@ -65,16 +65,21 @@ if (enable_vr) { "android/gvr/gvr_gamepad_data_provider.h", ] + if (enable_arcore) { + sources += [ + "android/arcore/arcore_device_provider_factory.cc", + "android/arcore/arcore_device_provider_factory.h", + ] + } + deps += [ ":jni_headers", "//device/gamepad", "//device/gamepad/public/cpp:shared_with_blink", "//third_party/blink/public:blink_headers", + "//third_party/gvr-android-sdk:gvr_shim", ] ldflags = [ "-landroid" ] - libs = [ - "//third_party/gvr-android-sdk/libgvr_shim_static_${current_cpu}.a", - ] configs += [ "//third_party/gvr-android-sdk:libgvr_config" ] } diff --git a/chromium/device/vr/android/arcore/arcore_device_provider_factory.cc b/chromium/device/vr/android/arcore/arcore_device_provider_factory.cc new file mode 100644 index 00000000000..16da7db4e01 --- /dev/null +++ b/chromium/device/vr/android/arcore/arcore_device_provider_factory.cc @@ -0,0 +1,33 @@ +// Copyright 2018 The Chromium 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 "device/vr/android/arcore/arcore_device_provider_factory.h" + +#include "base/logging.h" +#include "device/vr/vr_device_provider.h" + +namespace device { + +namespace { +ARCoreDeviceProviderFactory* g_arcore_device_provider_factory = nullptr; +} // namespace + +// static +std::unique_ptr<device::VRDeviceProvider> +ARCoreDeviceProviderFactory::Create() { + if (!g_arcore_device_provider_factory) + return nullptr; + return g_arcore_device_provider_factory->CreateDeviceProvider(); +} + +// static +void ARCoreDeviceProviderFactory::Install( + std::unique_ptr<ARCoreDeviceProviderFactory> factory) { + DCHECK_NE(g_arcore_device_provider_factory, factory.get()); + if (g_arcore_device_provider_factory) + delete g_arcore_device_provider_factory; + g_arcore_device_provider_factory = factory.release(); +} + +} // namespace device diff --git a/chromium/device/vr/android/arcore/arcore_device_provider_factory.h b/chromium/device/vr/android/arcore/arcore_device_provider_factory.h new file mode 100644 index 00000000000..2b29ec2828a --- /dev/null +++ b/chromium/device/vr/android/arcore/arcore_device_provider_factory.h @@ -0,0 +1,34 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_PROVIDER_FACTORY_H_ +#define DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_PROVIDER_FACTORY_H_ + +#include <memory> +#include "base/macros.h" +#include "device/vr/vr_export.h" + +namespace device { + +class VRDeviceProvider; + +class DEVICE_VR_EXPORT ARCoreDeviceProviderFactory { + public: + static std::unique_ptr<device::VRDeviceProvider> Create(); + static void Install(std::unique_ptr<ARCoreDeviceProviderFactory> factory); + + virtual ~ARCoreDeviceProviderFactory() = default; + + protected: + ARCoreDeviceProviderFactory() = default; + + virtual std::unique_ptr<device::VRDeviceProvider> CreateDeviceProvider() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ARCoreDeviceProviderFactory); +}; + +} // namespace device + +#endif // DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_PROVIDER_FACTORY_H_ diff --git a/chromium/device/vr/android/gvr/gvr_delegate_provider_factory.cc b/chromium/device/vr/android/gvr/gvr_delegate_provider_factory.cc index 033fec4f65c..0cc3e09f1eb 100644 --- a/chromium/device/vr/android/gvr/gvr_delegate_provider_factory.cc +++ b/chromium/device/vr/android/gvr/gvr_delegate_provider_factory.cc @@ -4,6 +4,8 @@ #include "device/vr/android/gvr/gvr_delegate_provider_factory.h" +#include "base/logging.h" + namespace device { namespace { @@ -18,11 +20,14 @@ GvrDelegateProvider* GvrDelegateProviderFactory::Create() { } // static -void GvrDelegateProviderFactory::Install(GvrDelegateProviderFactory* f) { - if (g_gvr_delegate_provider_factory == f) - return; - delete g_gvr_delegate_provider_factory; - g_gvr_delegate_provider_factory = f; +void GvrDelegateProviderFactory::Install( + std::unique_ptr<GvrDelegateProviderFactory> factory) { + DCHECK_NE(g_gvr_delegate_provider_factory, factory.get()); + if (g_gvr_delegate_provider_factory) + delete g_gvr_delegate_provider_factory; + g_gvr_delegate_provider_factory = factory.release(); } +GvrDevice* GvrDelegateProviderFactory::device_ = nullptr; + } // namespace device diff --git a/chromium/device/vr/android/gvr/gvr_delegate_provider_factory.h b/chromium/device/vr/android/gvr/gvr_delegate_provider_factory.h index 3db30037ecb..88e2ffebdee 100644 --- a/chromium/device/vr/android/gvr/gvr_delegate_provider_factory.h +++ b/chromium/device/vr/android/gvr/gvr_delegate_provider_factory.h @@ -5,6 +5,8 @@ #ifndef DEVICE_VR_ANDROID_GVR_DELEGATE_PROVIDER_FACTORY_H_ #define DEVICE_VR_ANDROID_GVR_DELEGATE_PROVIDER_FACTORY_H_ +#include <memory> + #include "base/macros.h" #include "device/vr/vr_export.h" @@ -12,18 +14,24 @@ namespace device { class GvrDelegateProvider; +class GvrDevice; class DEVICE_VR_EXPORT GvrDelegateProviderFactory { public: static GvrDelegateProvider* Create(); - static void Install(GvrDelegateProviderFactory* factory); + static void Install(std::unique_ptr<GvrDelegateProviderFactory> factory); + static void SetDevice(GvrDevice* device) { device_ = device; } + static GvrDevice* GetDevice() { return device_; } + + virtual ~GvrDelegateProviderFactory() = default; protected: GvrDelegateProviderFactory() = default; - virtual ~GvrDelegateProviderFactory() = default; virtual GvrDelegateProvider* CreateGvrDelegateProvider() = 0; + static GvrDevice* device_; + private: DISALLOW_COPY_AND_ASSIGN(GvrDelegateProviderFactory); }; diff --git a/chromium/device/vr/android/gvr/gvr_device.cc b/chromium/device/vr/android/gvr/gvr_device.cc index bc5db81cae0..b713ae1b532 100644 --- a/chromium/device/vr/android/gvr/gvr_device.cc +++ b/chromium/device/vr/android/gvr/gvr_device.cc @@ -155,9 +155,11 @@ GvrDevice::GvrDevice() : weak_ptr_factory_(this) { env, non_presenting_context_); gvr_api_ = gvr::GvrApi::WrapNonOwned(reinterpret_cast<gvr_context*>(context)); SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId())); + GvrDelegateProviderFactory::SetDevice(this); } GvrDevice::~GvrDevice() { + GvrDelegateProviderFactory::SetDevice(nullptr); if (!non_presenting_context_.obj()) return; JNIEnv* env = base::android::AttachCurrentThread(); @@ -165,7 +167,6 @@ GvrDevice::~GvrDevice() { } void GvrDevice::RequestPresent( - VRDisplayImpl* display, mojom::VRSubmitFrameClientPtr submit_client, mojom::VRPresentationProviderRequest request, mojom::VRRequestPresentOptionsPtr present_options, @@ -182,17 +183,15 @@ void GvrDevice::RequestPresent( std::move(submit_client), std::move(request), GetVRDisplayInfo(), std::move(present_options), base::BindOnce(&GvrDevice::OnRequestPresentResult, - weak_ptr_factory_.GetWeakPtr(), std::move(callback), - base::Unretained(display))); + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } void GvrDevice::OnRequestPresentResult( mojom::VRDisplayHost::RequestPresentCallback callback, - VRDisplayImpl* display, bool result, mojom::VRDisplayFrameTransportOptionsPtr transport_options) { if (result) - SetPresentingDisplay(display); + SetIsPresenting(); std::move(callback).Run(result, std::move(transport_options)); } diff --git a/chromium/device/vr/android/gvr/gvr_device.h b/chromium/device/vr/android/gvr/gvr_device.h index e362ce270c2..3376beff589 100644 --- a/chromium/device/vr/android/gvr/gvr_device.h +++ b/chromium/device/vr/android/gvr/gvr_device.h @@ -17,7 +17,6 @@ namespace device { class GvrDelegateProvider; -class VRDisplayImpl; // TODO(mthiesse, crbug.com/769373): Remove DEVICE_VR_EXPORT. class DEVICE_VR_EXPORT GvrDevice : public VRDeviceBase { @@ -27,7 +26,6 @@ class DEVICE_VR_EXPORT GvrDevice : public VRDeviceBase { // VRDeviceBase void RequestPresent( - VRDisplayImpl* display, mojom::VRSubmitFrameClientPtr submit_client, mojom::VRPresentationProviderRequest request, mojom::VRRequestPresentOptionsPtr present_options, @@ -51,7 +49,6 @@ class DEVICE_VR_EXPORT GvrDevice : public VRDeviceBase { void OnRequestPresentResult( mojom::VRDisplayHost::RequestPresentCallback callback, - VRDisplayImpl* display, bool result, mojom::VRDisplayFrameTransportOptionsPtr transport_options); diff --git a/chromium/device/vr/buildflags/BUILD.gn b/chromium/device/vr/buildflags/BUILD.gn index 51c05d325c8..fa4c3d95ca4 100644 --- a/chromium/device/vr/buildflags/BUILD.gn +++ b/chromium/device/vr/buildflags/BUILD.gn @@ -9,6 +9,7 @@ import("//build/buildflag_header.gni") buildflag_header("buildflags") { header = "buildflags.h" flags = [ + "ENABLE_ARCORE=$enable_arcore", "ENABLE_VR=$enable_vr", "ENABLE_OPENVR=$enable_openvr", "ENABLE_OCULUS_VR=$enable_oculus_vr", diff --git a/chromium/device/vr/buildflags/buildflags.gni b/chromium/device/vr/buildflags/buildflags.gni index 905d9e799df..368caa16284 100644 --- a/chromium/device/vr/buildflags/buildflags.gni +++ b/chromium/device/vr/buildflags/buildflags.gni @@ -5,6 +5,7 @@ import("//build/config/chrome_build.gni") import("//build/config/chromecast_build.gni") import("//build/config/gclient_args.gni") +import("//chrome/android/channel.gni") declare_args() { # TODO(733935): Enable for other Android architectures too. Currently we only @@ -29,4 +30,17 @@ declare_args() { # Whether to include VR extras like test APKs in non-VR-specific targets include_vr_data = false + + # TODO(crbug.com/837999, crbug.com/841389): We currently only support arm and + # we are limiting to canary and dev until binary size issues are resolved. + # TODO(crbug.com/845080): add android_channel == "dev" || "canary" + package_arcore = is_android && !is_chromecast && current_cpu == "arm" && + android_channel == "default" + + # TODO(crbug.com/841389): We should eventually have a single flag for + # enabling arcore, but we currently don't support ARCore in 64bit, and we do + # not support all channels. This flag governs the inclusion of code that must + # be identical across configs. + enable_arcore = is_android && !is_chromecast && + (current_cpu == "arm" || current_cpu == "arm64") } diff --git a/chromium/device/vr/oculus/oculus_device.cc b/chromium/device/vr/oculus/oculus_device.cc index 769489c0a7d..9819906c7e2 100644 --- a/chromium/device/vr/oculus/oculus_device.cc +++ b/chromium/device/vr/oculus/oculus_device.cc @@ -95,7 +95,6 @@ OculusDevice::OculusDevice(ovrSession session, ovrGraphicsLuid luid) OculusDevice::~OculusDevice() {} void OculusDevice::RequestPresent( - VRDisplayImpl* display, mojom::VRSubmitFrameClientPtr submit_client, mojom::VRPresentationProviderRequest request, mojom::VRRequestPresentOptionsPtr present_options, @@ -130,6 +129,7 @@ void OculusDevice::ExitPresent() { render_loop_->task_runner()->PostTask( FROM_HERE, base::BindOnce(&OculusRenderLoop::ExitPresent, render_loop_->GetWeakPtr())); + OnExitPresent(); } void OculusDevice::OnMagicWindowPoseRequest( diff --git a/chromium/device/vr/oculus/oculus_device.h b/chromium/device/vr/oculus/oculus_device.h index 061f7362aaa..414d83b1899 100644 --- a/chromium/device/vr/oculus/oculus_device.h +++ b/chromium/device/vr/oculus/oculus_device.h @@ -25,7 +25,6 @@ class OculusDevice : public VRDeviceBase { // VRDeviceBase void RequestPresent( - VRDisplayImpl* display, mojom::VRSubmitFrameClientPtr submit_client, mojom::VRPresentationProviderRequest request, mojom::VRRequestPresentOptionsPtr present_options, diff --git a/chromium/device/vr/oculus/oculus_render_loop.cc b/chromium/device/vr/oculus/oculus_render_loop.cc index 598f9f8c85e..24c3a8269a2 100644 --- a/chromium/device/vr/oculus/oculus_render_loop.cc +++ b/chromium/device/vr/oculus/oculus_render_loop.cc @@ -92,7 +92,7 @@ void OculusRenderLoop::SubmitFrameWithTextureHandle( MojoPlatformHandle platform_handle; platform_handle.struct_size = sizeof(platform_handle); MojoResult result = MojoUnwrapPlatformHandle(texture_handle.release().value(), - &platform_handle); + nullptr, &platform_handle); if (result != MOJO_RESULT_OK) return; @@ -228,6 +228,8 @@ void OculusRenderLoop::RequestPresent( void OculusRenderLoop::ExitPresent() { is_presenting_ = false; + binding_.Close(); + submit_client_ = nullptr; } void OculusRenderLoop::Init() {} diff --git a/chromium/device/vr/openvr/openvr_device.cc b/chromium/device/vr/openvr/openvr_device.cc index 09f5a3041a4..8aee0efd125 100644 --- a/chromium/device/vr/openvr/openvr_device.cc +++ b/chromium/device/vr/openvr/openvr_device.cc @@ -169,7 +169,6 @@ void OpenVRDevice::Shutdown() { } void OpenVRDevice::RequestPresent( - VRDisplayImpl* display, mojom::VRSubmitFrameClientPtr submit_client, mojom::VRPresentationProviderRequest request, mojom::VRRequestPresentOptionsPtr present_options, @@ -224,6 +223,7 @@ void OpenVRDevice::ExitPresent() { FROM_HERE, base::Bind(&OpenVRRenderLoop::ExitPresent, render_loop_->GetWeakPtr())); render_loop_->Stop(); + OnExitPresent(); } void OpenVRDevice::OnMagicWindowPoseRequest( diff --git a/chromium/device/vr/openvr/openvr_device.h b/chromium/device/vr/openvr/openvr_device.h index d30505fff14..3a19299352b 100644 --- a/chromium/device/vr/openvr/openvr_device.h +++ b/chromium/device/vr/openvr/openvr_device.h @@ -30,7 +30,6 @@ class OpenVRDevice : public VRDeviceBase { // VRDeviceBase void RequestPresent( - VRDisplayImpl* display, mojom::VRSubmitFrameClientPtr submit_client, mojom::VRPresentationProviderRequest request, mojom::VRRequestPresentOptionsPtr present_options, diff --git a/chromium/device/vr/openvr/openvr_render_loop.cc b/chromium/device/vr/openvr/openvr_render_loop.cc index 97ec1f2d461..3cd7c8b4958 100644 --- a/chromium/device/vr/openvr/openvr_render_loop.cc +++ b/chromium/device/vr/openvr/openvr_render_loop.cc @@ -85,7 +85,7 @@ void OpenVRRenderLoop::SubmitFrameWithTextureHandle( MojoPlatformHandle platform_handle; platform_handle.struct_size = sizeof(platform_handle); MojoResult result = MojoUnwrapPlatformHandle(texture_handle.release().value(), - &platform_handle); + nullptr, &platform_handle); if (result != MOJO_RESULT_OK) return; @@ -195,6 +195,8 @@ void OpenVRRenderLoop::RequestPresent( void OpenVRRenderLoop::ExitPresent() { is_presenting_ = false; report_webxr_input_ = false; + binding_.Close(); + submit_client_ = nullptr; vr_compositor_->SuspendRendering(true); } diff --git a/chromium/device/vr/orientation/orientation_device.cc b/chromium/device/vr/orientation/orientation_device.cc index 5c826c2e7ec..730814b5315 100644 --- a/chromium/device/vr/orientation/orientation_device.cc +++ b/chromium/device/vr/orientation/orientation_device.cc @@ -17,7 +17,6 @@ namespace device { -using mojom::SensorType; using gfx::Quaternion; using gfx::Vector3dF; @@ -54,11 +53,8 @@ VROrientationDevice::VROrientationDevice( mojom::SensorProviderPtr* sensor_provider, base::OnceClosure ready_callback) : ready_callback_(std::move(ready_callback)), binding_(this) { - // Use RELATIVE_ORIENTATION_QUATERNION rather than - // ABSOLUTE_ORIENTATION_QUATERNION because compass readings can be innacurate - // when used indoors. (*sensor_provider) - ->GetSensor(SensorType::RELATIVE_ORIENTATION_QUATERNION, + ->GetSensor(kOrientationSensorType, base::BindOnce(&VROrientationDevice::SensorReady, base::Unretained(this))); diff --git a/chromium/device/vr/orientation/orientation_device.h b/chromium/device/vr/orientation/orientation_device.h index f82c6569aec..dde90f77cbc 100644 --- a/chromium/device/vr/orientation/orientation_device.h +++ b/chromium/device/vr/orientation/orientation_device.h @@ -10,6 +10,7 @@ #include "base/callback_forward.h" #include "base/macros.h" #include "base/threading/simple_thread.h" +#include "build/build_config.h" #include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/vr_device_base.h" #include "mojo/public/cpp/bindings/binding.h" @@ -20,6 +21,19 @@ namespace device { class SensorReadingSharedBufferReader; +// Use RELATIVE_ORIENTATION_QUATERNION rather than +// ABSOLUTE_ORIENTATION_QUATERNION because compass readings can be inacurate +// when used indoors, unless we're on Windows which doesn't support +// RELATIVE_ORIENTATION_QUATERNION. +// TODO(crbug.com/730440) If RELATIVE_ORIENTATION_QUATERNION is ever +// implemented on Windows, use that instead. +static constexpr mojom::SensorType kOrientationSensorType = +#if defined(OS_WIN) + mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION; +#else + mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION; +#endif + // This class connects the orientation sensor events to the Web VR apis. class DEVICE_VR_EXPORT VROrientationDevice : public VRDeviceBase, public mojom::SensorClient { diff --git a/chromium/device/vr/orientation/orientation_device_provider_unittest.cc b/chromium/device/vr/orientation/orientation_device_provider_unittest.cc index 14757b50ab8..3af37569e8f 100644 --- a/chromium/device/vr/orientation/orientation_device_provider_unittest.cc +++ b/chromium/device/vr/orientation/orientation_device_provider_unittest.cc @@ -70,16 +70,15 @@ class VROrientationDeviceProviderTest : public testing::Test { auto init_params = mojom::SensorInitParams::New(); init_params->sensor = std::move(sensor_ptr_); init_params->default_configuration = PlatformSensorConfiguration( - SensorTraits<mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION>:: - kDefaultFrequency); + SensorTraits<kOrientationSensorType>::kDefaultFrequency); init_params->client_request = mojo::MakeRequest(&sensor_client_ptr_); init_params->memory = shared_buffer_handle_->Clone( mojo::SharedBufferHandle::AccessMode::READ_ONLY); - init_params->buffer_offset = SensorReadingSharedBuffer::GetOffset( - mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION); + init_params->buffer_offset = + SensorReadingSharedBuffer::GetOffset(kOrientationSensorType); return init_params; } diff --git a/chromium/device/vr/orientation/orientation_device_unittest.cc b/chromium/device/vr/orientation/orientation_device_unittest.cc index 4bbde60ddb6..db07c16bcfa 100644 --- a/chromium/device/vr/orientation/orientation_device_unittest.cc +++ b/chromium/device/vr/orientation/orientation_device_unittest.cc @@ -100,8 +100,7 @@ class VROrientationDeviceTest : public testing::Test { void TearDown() override { shared_buffer_handle_.reset(); } double GetBufferOffset() { - return SensorReadingSharedBuffer::GetOffset( - mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION); + return SensorReadingSharedBuffer::GetOffset(kOrientationSensorType); } void InitializeDevice(mojom::SensorInitParamsPtr params) { @@ -154,8 +153,7 @@ class VROrientationDeviceTest : public testing::Test { auto init_params = mojom::SensorInitParams::New(); init_params->sensor = std::move(sensor_ptr_); init_params->default_configuration = PlatformSensorConfiguration( - SensorTraits<mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION>:: - kDefaultFrequency); + SensorTraits<kOrientationSensorType>::kDefaultFrequency); init_params->client_request = mojo::MakeRequest(&sensor_client_ptr_); diff --git a/chromium/device/vr/public/mojom/BUILD.gn b/chromium/device/vr/public/mojom/BUILD.gn index 0239637721e..c9befd61ef3 100644 --- a/chromium/device/vr/public/mojom/BUILD.gn +++ b/chromium/device/vr/public/mojom/BUILD.gn @@ -15,6 +15,7 @@ mojom_component("mojom") { public_deps = [ "//gpu/ipc/common:interfaces", "//mojo/public/mojom/base", + "//ui/display/mojo:interfaces", "//ui/gfx/geometry/mojo", "//ui/gfx/mojo:mojo", ] diff --git a/chromium/device/vr/public/mojom/vr_service.mojom b/chromium/device/vr/public/mojom/vr_service.mojom index e1b8281b228..1b5e4e5dd92 100644 --- a/chromium/device/vr/public/mojom/vr_service.mojom +++ b/chromium/device/vr/public/mojom/vr_service.mojom @@ -7,6 +7,7 @@ module device.mojom; import "mojo/public/mojom/base/time.mojom"; import "gpu/ipc/common/mailbox_holder.mojom"; import "gpu/ipc/common/sync_token.mojom"; +import "ui/display/mojo/display.mojom"; import "ui/gfx/geometry/mojo/geometry.mojom"; import "ui/gfx/mojo/gpu_fence_handle.mojom"; import "ui/gfx/mojo/transform.mojom"; @@ -81,13 +82,25 @@ struct VRPose { // For WebXR sessions only, reports the state of all active input devices // synced with the head pose. array<XRInputSourceState>? input_state; + + // Indicates that a reset pose event was triggered, either by device specific + // UI or by some other method, and handled on the browser side, and the + // renderer should now bubble up an event to the WebXRDevice API. + bool pose_reset; }; struct VRDisplayCapabilities { bool hasPosition; bool hasExternalDisplay; + // Indicates whether the display can actively show imagery on a headset. bool canPresent; + + // If true, this is an AR display that can provide a background image along + // with each pose. Clients who want them should send a frame image request + // via getFrameData instead of getPose. + // TODO(https://crbug.com/836349): this may need to change. + bool can_provide_pass_through_images; }; // Information about the optical properties for an eye in a VRDisplay. @@ -159,6 +172,22 @@ struct VRDisplayFrameTransportOptions { bool wait_for_gpu_fence; }; +// The data needed for each frame for a magic window experience +// that uses a background image / projection matrix instead of +// just a VRPose - ex: non-exclusive AR needs a camera image and +// to get a projection matrix directly from the backend rather than +// FOV values to support features like focus. +struct VRMagicWindowFrameData { + VRPose pose; + gpu.mojom.MailboxHolder buffer_holder; + gfx.mojom.Size buffer_size; + // TODO(https://crbug.com/838515): Is this delta since the last + // frame? OR an unspecified origin? Something else? + mojo_base.mojom.TimeDelta time_delta; + array<float, 16> projection_matrix; + +}; + enum VRDisplayEventReason { NONE = 0, NAVIGATION = 1, @@ -232,10 +261,24 @@ interface VRDisplayHost { ExitPresent(); }; -// Provides the necessary functionality for a non-presenting WebVR page to draw -// magic window content. +// Provides the necessary functionality for a non-presenting WebXR session to +// draw magic window content. +// This interface is hosted in the Browser process, but will move to a sandboxed +// utility process on Windows. The render process communicates with it. +// For AR displays (VRDisplayCapabilities.can_provide_pass_through_images +// is true), clients can use GetFrameData to get images. +// TODO(836478): rename VRMagicWindowProvider to NonExclusiveWindowProvider or +// similar. interface VRMagicWindowProvider { GetPose() => (VRPose? pose); + + // Different devices can have different native orientations - 0 + // is the native orientation, and then increments of 90 degrees + // from there. + // TODO(https://crbug.com/837944): frame_data should not be optional. + GetFrameData(gfx.mojom.Size frame_size, + display.mojom.Rotation display_rotation) => + (VRMagicWindowFrameData? frame_data); }; // Provides the necessary functionality for a presenting WebVR page to draw diff --git a/chromium/device/vr/vr_device.h b/chromium/device/vr/vr_device.h index 13fe9cc038e..792e923759a 100644 --- a/chromium/device/vr/vr_device.h +++ b/chromium/device/vr/vr_device.h @@ -36,6 +36,17 @@ enum class XrRuntimeAvailable { const unsigned int VR_DEVICE_LAST_ID = 0xFFFFFFFF; +class VRDeviceEventListener { + public: + virtual ~VRDeviceEventListener() {} + + virtual void OnChanged(mojom::VRDisplayInfoPtr vr_device_info) = 0; + virtual void OnExitPresent() = 0; + virtual void OnActivate(mojom::VRDisplayEventReason reason, + base::OnceCallback<void(bool)> on_handled) = 0; + virtual void OnDeactivate(mojom::VRDisplayEventReason reason) = 0; +}; + // Represents one of the platform's VR devices. Owned by the respective // VRDeviceProvider. // TODO(mthiesse, crbug.com/769373): Remove DEVICE_VR_EXPORT. @@ -46,10 +57,15 @@ class DEVICE_VR_EXPORT VRDevice { virtual unsigned int GetId() const = 0; virtual void PauseTracking() = 0; virtual void ResumeTracking() = 0; - virtual void Blur() = 0; - virtual void Focus() = 0; virtual mojom::VRDisplayInfoPtr GetVRDisplayInfo() = 0; virtual void SetMagicWindowEnabled(bool enabled) = 0; + virtual void ExitPresent() = 0; + virtual void RequestPresent( + mojom::VRSubmitFrameClientPtr submit_client, + mojom::VRPresentationProviderRequest request, + mojom::VRRequestPresentOptionsPtr present_options, + mojom::VRDisplayHost::RequestPresentCallback callback) = 0; + virtual void SetListeningForActivate(bool is_listening) = 0; // The fallback device should only be provided in lieu of other devices. virtual bool IsFallbackDevice() = 0; @@ -60,6 +76,8 @@ class DEVICE_VR_EXPORT VRDevice { // exiting of presentation before notifying displays. This is currently messy // because browser-side notions of presentation are mostly Android-specific. virtual void OnExitPresent() = 0; + + virtual void SetVRDeviceEventListener(VRDeviceEventListener* listener) = 0; }; } // namespace device diff --git a/chromium/device/vr/vr_device_base.cc b/chromium/device/vr/vr_device_base.cc index 725634bc06a..9cf6c9b733a 100644 --- a/chromium/device/vr/vr_device_base.cc +++ b/chromium/device/vr/vr_device_base.cc @@ -27,23 +27,14 @@ void VRDeviceBase::PauseTracking() {} void VRDeviceBase::ResumeTracking() {} -void VRDeviceBase::Blur() { - for (VRDisplayImpl* display : displays_) - display->OnBlur(); +void VRDeviceBase::OnExitPresent() { + if (listener_) + listener_->OnExitPresent(); + presenting_ = false; } -void VRDeviceBase::Focus() { - for (VRDisplayImpl* display : displays_) - display->OnFocus(); -} - -void VRDeviceBase::OnExitPresent() { - if (!presenting_display_) - return; - auto it = displays_.find(presenting_display_); - CHECK(it != displays_.end()); - (*it)->OnExitPresent(); - SetPresentingDisplay(nullptr); +void VRDeviceBase::SetIsPresenting() { + presenting_ = true; } bool VRDeviceBase::IsFallbackDevice() { @@ -56,7 +47,6 @@ mojom::VRDisplayInfoPtr VRDeviceBase::GetVRDisplayInfo() { } void VRDeviceBase::RequestPresent( - VRDisplayImpl* display, mojom::VRSubmitFrameClientPtr submit_client, mojom::VRPresentationProviderRequest request, mojom::VRRequestPresentOptionsPtr present_options, @@ -72,6 +62,10 @@ void VRDeviceBase::SetMagicWindowEnabled(bool enabled) { magic_window_enabled_ = enabled; } +void VRDeviceBase::SetVRDeviceEventListener(VRDeviceEventListener* listener) { + listener_ = listener; +} + void VRDeviceBase::GetMagicWindowPose( mojom::VRMagicWindowProvider::GetPoseCallback callback) { if (!magic_window_enabled_) { @@ -82,26 +76,22 @@ void VRDeviceBase::GetMagicWindowPose( OnMagicWindowPoseRequest(std::move(callback)); } -void VRDeviceBase::AddDisplay(VRDisplayImpl* display) { - displays_.insert(display); -} - -void VRDeviceBase::RemoveDisplay(VRDisplayImpl* display) { - if (CheckPresentingDisplay(display)) - ExitPresent(); - displays_.erase(display); - if (listening_for_activate_diplay_ == display) { - listening_for_activate_diplay_ = nullptr; - OnListeningForActivate(false); +void VRDeviceBase::GetMagicWindowFrameData( + const gfx::Size& frame_size, + display::Display::Rotation display_rotation, + mojom::VRMagicWindowProvider::GetFrameDataCallback callback) { + // TODO(https://crbug.com/836565): rename this boolean. + if (!magic_window_enabled_) { + std::move(callback).Run(nullptr); + return; } -} -bool VRDeviceBase::IsAccessAllowed(VRDisplayImpl* display) { - return (!presenting_display_ || presenting_display_ == display); + OnMagicWindowFrameDataRequest(frame_size, display_rotation, + std::move(callback)); } -bool VRDeviceBase::CheckPresentingDisplay(VRDisplayImpl* display) { - return (presenting_display_ && presenting_display_ == display); +bool VRDeviceBase::IsAccessAllowed(VRDisplayImpl* display) { + return !presenting_; } void VRDeviceBase::OnListeningForActivateChanged(VRDisplayImpl* display) { @@ -112,10 +102,6 @@ void VRDeviceBase::OnFrameFocusChanged(VRDisplayImpl* display) { UpdateListeningForActivate(display); } -void VRDeviceBase::SetPresentingDisplay(VRDisplayImpl* display) { - presenting_display_ = display; -} - void VRDeviceBase::SetVRDisplayInfo(mojom::VRDisplayInfoPtr display_info) { DCHECK(display_info); DCHECK(display_info->index == id_); @@ -125,17 +111,15 @@ void VRDeviceBase::SetVRDisplayInfo(mojom::VRDisplayInfoPtr display_info) { // Don't notify when the VRDisplayInfo is initially set. if (!initialized) return; - for (VRDisplayImpl* display : displays_) - display->OnChanged(display_info_.Clone()); + + if (listener_) + listener_->OnChanged(display_info_.Clone()); } void VRDeviceBase::OnActivate(mojom::VRDisplayEventReason reason, base::Callback<void(bool)> on_handled) { - if (listening_for_activate_diplay_) { - listening_for_activate_diplay_->OnActivate(reason, std::move(on_handled)); - } else { - std::move(on_handled).Run(true /* will_not_present */); - } + if (listener_) + listener_->OnActivate(reason, std::move(on_handled)); } void VRDeviceBase::OnListeningForActivate(bool listening) {} @@ -145,6 +129,17 @@ void VRDeviceBase::OnMagicWindowPoseRequest( std::move(callback).Run(nullptr); } +void VRDeviceBase::OnMagicWindowFrameDataRequest( + const gfx::Size& frame_size, + display::Display::Rotation display_rotation, + mojom::VRMagicWindowProvider::GetFrameDataCallback callback) { + std::move(callback).Run(nullptr); +} + +void VRDeviceBase::SetListeningForActivate(bool is_listening) { + OnListeningForActivate(is_listening); +} + void VRDeviceBase::UpdateListeningForActivate(VRDisplayImpl* display) { if (display->ListeningForActivate() && display->InFocusedFrame()) { bool was_listening = !!listening_for_activate_diplay_; diff --git a/chromium/device/vr/vr_device_base.h b/chromium/device/vr/vr_device_base.h index e4a520e8673..e6aa60a6a2e 100644 --- a/chromium/device/vr/vr_device_base.h +++ b/chromium/device/vr/vr_device_base.h @@ -10,6 +10,7 @@ #include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/vr_device.h" #include "device/vr/vr_export.h" +#include "ui/display/display.h" namespace device { @@ -27,51 +28,57 @@ class DEVICE_VR_EXPORT VRDeviceBase : public VRDevice { unsigned int GetId() const override; void PauseTracking() override; void ResumeTracking() override; - void Blur() override; - void Focus() override; void OnExitPresent() override; mojom::VRDisplayInfoPtr GetVRDisplayInfo() final; void SetMagicWindowEnabled(bool enabled) final; + void SetVRDeviceEventListener(VRDeviceEventListener* listener) final; - virtual void RequestPresent( - VRDisplayImpl* display, + void RequestPresent( mojom::VRSubmitFrameClientPtr submit_client, mojom::VRPresentationProviderRequest request, mojom::VRRequestPresentOptionsPtr present_options, - mojom::VRDisplayHost::RequestPresentCallback callback); - virtual void ExitPresent(); + mojom::VRDisplayHost::RequestPresentCallback callback) override; + void ExitPresent() override; bool IsFallbackDevice() override; - void AddDisplay(VRDisplayImpl* display); - void RemoveDisplay(VRDisplayImpl* display); bool IsAccessAllowed(VRDisplayImpl* display); - bool CheckPresentingDisplay(VRDisplayImpl* display); + void SetListeningForActivate(bool is_listening) override; void OnListeningForActivateChanged(VRDisplayImpl* display); void OnFrameFocusChanged(VRDisplayImpl* display); void GetMagicWindowPose( mojom::VRMagicWindowProvider::GetPoseCallback callback); - - VRDisplayImpl* GetPresentingDisplay() { return presenting_display_; } + // TODO(https://crbug.com/836478): Rename this, and probably + // GetMagicWindowPose to GetNonExclusiveFrameData. + void GetMagicWindowFrameData( + const gfx::Size& frame_size, + display::Display::Rotation display_rotation, + mojom::VRMagicWindowProvider::GetFrameDataCallback callback); protected: - void SetPresentingDisplay(VRDisplayImpl* display); + void SetIsPresenting(); + bool IsPresenting() { return presenting_; } // Exposed for test. void SetVRDisplayInfo(mojom::VRDisplayInfoPtr display_info); void OnActivate(mojom::VRDisplayEventReason reason, base::Callback<void(bool)> on_handled); private: - void UpdateListeningForActivate(VRDisplayImpl* display); + virtual void UpdateListeningForActivate(VRDisplayImpl* display); virtual void OnListeningForActivate(bool listening); virtual void OnMagicWindowPoseRequest( mojom::VRMagicWindowProvider::GetPoseCallback callback); + virtual void OnMagicWindowFrameDataRequest( + const gfx::Size& frame_size, + display::Display::Rotation display_rotation, + mojom::VRMagicWindowProvider::GetFrameDataCallback callback); - std::set<VRDisplayImpl*> displays_; + VRDeviceEventListener* listener_ = nullptr; - VRDisplayImpl* presenting_display_ = nullptr; VRDisplayImpl* listening_for_activate_diplay_ = nullptr; mojom::VRDisplayInfoPtr display_info_; + bool presenting_ = false; + unsigned int id_; static unsigned int next_id_; bool magic_window_enabled_ = true; diff --git a/chromium/device/vr/vr_device_base_unittest.cc b/chromium/device/vr/vr_device_base_unittest.cc index 37c9e8c5c8b..29118faa614 100644 --- a/chromium/device/vr/vr_device_base_unittest.cc +++ b/chromium/device/vr/vr_device_base_unittest.cc @@ -44,6 +44,25 @@ class VRDeviceBaseForTesting : public VRDeviceBase { DISALLOW_COPY_AND_ASSIGN(VRDeviceBaseForTesting); }; +class StubVRDeviceEventListener : public VRDeviceEventListener { + public: + ~StubVRDeviceEventListener() override {} + + MOCK_METHOD1(DoOnChanged, void(mojom::VRDisplayInfo* vr_device_info)); + void OnChanged(mojom::VRDisplayInfoPtr vr_device_info) override { + DoOnChanged(vr_device_info.get()); + } + + MOCK_METHOD2(OnActivate, + void(mojom::VRDisplayEventReason, + base::OnceCallback<void(bool)>)); + + MOCK_METHOD0(OnExitPresent, void()); + MOCK_METHOD0(OnBlur, void()); + MOCK_METHOD0(OnFocus, void()); + MOCK_METHOD1(OnDeactivate, void(mojom::VRDisplayEventReason)); +}; + } // namespace class VRDeviceTest : public testing::Test { @@ -58,8 +77,10 @@ class VRDeviceTest : public testing::Test { } std::unique_ptr<MockVRDisplayImpl> MakeMockDisplay(VRDeviceBase* device) { + mojom::VRDisplayClientPtr display_client; return std::make_unique<testing::NiceMock<MockVRDisplayImpl>>( - device, client(), nullptr, nullptr, false); + device, client(), nullptr, nullptr, mojo::MakeRequest(&display_client), + false); } std::unique_ptr<VRDeviceBaseForTesting> MakeVRDevice() { @@ -88,8 +109,9 @@ class VRDeviceTest : public testing::Test { // will receive the "vrdevicechanged" event. TEST_F(VRDeviceTest, DeviceChangedDispatched) { auto device = MakeVRDevice(); - auto display = MakeMockDisplay(device.get()); - EXPECT_CALL(*display, DoOnChanged(testing::_)).Times(1); + StubVRDeviceEventListener listener; + device->SetVRDeviceEventListener(&listener); + EXPECT_CALL(listener, DoOnChanged(testing::_)).Times(1); device->SetVRDisplayInfoForTest(MakeVRDisplayInfo(device->GetId())); } @@ -97,57 +119,14 @@ TEST_F(VRDeviceTest, DisplayActivateRegsitered) { device::mojom::VRDisplayEventReason mounted = device::mojom::VRDisplayEventReason::MOUNTED; auto device = MakeVRDevice(); - auto display1 = MakeMockDisplay(device.get()); - auto display2 = MakeMockDisplay(device.get()); - - EXPECT_CALL(*display1, ListeningForActivate()) - .WillRepeatedly(testing::Return(true)); - EXPECT_CALL(*display1, InFocusedFrame()) - .WillRepeatedly(testing::Return(false)); - device->OnListeningForActivateChanged(display1.get()); - EXPECT_FALSE(device->ListeningForActivate()); - - EXPECT_CALL(*display1, OnActivate(mounted, testing::_)).Times(0); - EXPECT_CALL(*display2, OnActivate(mounted, testing::_)).Times(0); - device->FireDisplayActivate(); - - EXPECT_CALL(*display1, InFocusedFrame()) - .WillRepeatedly(testing::Return(true)); - device->OnFrameFocusChanged(display1.get()); - EXPECT_TRUE(device->ListeningForActivate()); - - EXPECT_CALL(*display1, OnActivate(mounted, testing::_)).Times(1); - device->FireDisplayActivate(); - - EXPECT_CALL(*display2, ListeningForActivate()) - .WillRepeatedly(testing::Return(true)); - EXPECT_CALL(*display2, InFocusedFrame()) - .WillRepeatedly(testing::Return(true)); - device->OnListeningForActivateChanged(display2.get()); - EXPECT_TRUE(device->ListeningForActivate()); - - EXPECT_CALL(*display2, OnActivate(mounted, testing::_)).Times(2); - device->FireDisplayActivate(); - - EXPECT_CALL(*display1, ListeningForActivate()) - .WillRepeatedly(testing::Return(false)); - device->OnListeningForActivateChanged(display1.get()); - EXPECT_TRUE(device->ListeningForActivate()); + StubVRDeviceEventListener listener; + device->SetVRDeviceEventListener(&listener); - device->FireDisplayActivate(); - - EXPECT_CALL(*display2, ListeningForActivate()) - .WillRepeatedly(testing::Return(false)); - device->OnListeningForActivateChanged(display2.get()); EXPECT_FALSE(device->ListeningForActivate()); + device->SetListeningForActivate(true); + EXPECT_TRUE(device->ListeningForActivate()); - // Make sure we don't send the DisplayActivate event. - device->FireDisplayActivate(); - - EXPECT_CALL(*display2, InFocusedFrame()) - .WillRepeatedly(testing::Return(false)); - device->OnFrameFocusChanged(display2.get()); - + EXPECT_CALL(listener, OnActivate(mounted, testing::_)).Times(1); device->FireDisplayActivate(); } diff --git a/chromium/device/vr/vr_display_impl.cc b/chromium/device/vr/vr_display_impl.cc index e47cce8dd53..aba13dbb42b 100644 --- a/chromium/device/vr/vr_display_impl.cc +++ b/chromium/device/vr/vr_display_impl.cc @@ -9,79 +9,60 @@ #include "base/bind.h" #include "device/vr/vr_device_base.h" +namespace { +constexpr int kMaxImageHeightOrWidth = 8000; +} // namespace + namespace device { VRDisplayImpl::VRDisplayImpl(VRDevice* device, mojom::VRServiceClient* service_client, mojom::VRDisplayInfoPtr display_info, mojom::VRDisplayHostPtr display_host, + mojom::VRDisplayClientRequest client_request, bool in_focused_frame) : binding_(this), device_(static_cast<VRDeviceBase*>(device)), in_focused_frame_(in_focused_frame) { - device_->AddDisplay(this); mojom::VRMagicWindowProviderPtr magic_window_provider; binding_.Bind(mojo::MakeRequest(&magic_window_provider)); service_client->OnDisplayConnected( std::move(magic_window_provider), std::move(display_host), - mojo::MakeRequest(&client_), std::move(display_info)); -} - -VRDisplayImpl::~VRDisplayImpl() { - device_->RemoveDisplay(this); -} - -void VRDisplayImpl::OnChanged(mojom::VRDisplayInfoPtr vr_device_info) { - client_->OnChanged(std::move(vr_device_info)); -} - -void VRDisplayImpl::OnExitPresent() { - client_->OnExitPresent(); + std::move(client_request), std::move(display_info)); } -void VRDisplayImpl::OnBlur() { - client_->OnBlur(); -} +VRDisplayImpl::~VRDisplayImpl() = default; -void VRDisplayImpl::OnFocus() { - client_->OnFocus(); -} - -void VRDisplayImpl::OnActivate(mojom::VRDisplayEventReason reason, - base::Callback<void(bool)> on_handled) { - client_->OnActivate(reason, std::move(on_handled)); -} - -void VRDisplayImpl::OnDeactivate(mojom::VRDisplayEventReason reason) { - client_->OnDeactivate(reason); -} - -void VRDisplayImpl::RequestPresent( - mojom::VRSubmitFrameClientPtr submit_client, - mojom::VRPresentationProviderRequest request, - mojom::VRRequestPresentOptionsPtr present_options, - mojom::VRDisplayHost::RequestPresentCallback callback) { - if (!device_->IsAccessAllowed(this) || !InFocusedFrame()) { - std::move(callback).Run(false, nullptr); +// Gets a pose for magic window sessions. +void VRDisplayImpl::GetPose(GetPoseCallback callback) { + if (!device_->IsAccessAllowed(this)) { + std::move(callback).Run(nullptr); return; } - - device_->RequestPresent(this, std::move(submit_client), std::move(request), - std::move(present_options), std::move(callback)); -} - -void VRDisplayImpl::ExitPresent() { - if (device_->CheckPresentingDisplay(this)) - device_->ExitPresent(); + device_->GetMagicWindowPose(std::move(callback)); } -// Gets a pose for magic window sessions. -void VRDisplayImpl::GetPose(GetPoseCallback callback) { +// Gets frame image data for AR magic window sessions. +void VRDisplayImpl::GetFrameData(const gfx::Size& frame_size, + display::Display::Rotation rotation, + GetFrameDataCallback callback) { if (!device_->IsAccessAllowed(this)) { std::move(callback).Run(nullptr); return; } - device_->GetMagicWindowPose(std::move(callback)); + + // Check for a valid frame size. + // While Mojo should handle negative values, we also do not want to allow 0. + // TODO(https://crbug.com/841062): Reconsider how we check the sizes. + if (frame_size.width() <= 0 || frame_size.height() <= 0 || + frame_size.width() > kMaxImageHeightOrWidth || + frame_size.height() > kMaxImageHeightOrWidth) { + DLOG(ERROR) << "Invalid frame size passed to GetFrameData()."; + std::move(callback).Run(nullptr); + return; + } + + device_->GetMagicWindowFrameData(frame_size, rotation, std::move(callback)); } void VRDisplayImpl::SetListeningForActivate(bool listening) { diff --git a/chromium/device/vr/vr_display_impl.h b/chromium/device/vr/vr_display_impl.h index df334c3a0b7..1474337810f 100644 --- a/chromium/device/vr/vr_display_impl.h +++ b/chromium/device/vr/vr_display_impl.h @@ -12,6 +12,7 @@ #include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/vr_export.h" #include "mojo/public/cpp/bindings/binding.h" +#include "ui/display/display.h" namespace device { @@ -30,34 +31,23 @@ class DEVICE_VR_EXPORT VRDisplayImpl : public mojom::VRMagicWindowProvider { mojom::VRServiceClient* service_client, mojom::VRDisplayInfoPtr display_info, mojom::VRDisplayHostPtr display_host, + mojom::VRDisplayClientRequest client_request, bool in_frame_focused); ~VRDisplayImpl() override; - virtual void OnChanged(mojom::VRDisplayInfoPtr vr_device_info); - virtual void OnExitPresent(); - virtual void OnBlur(); - virtual void OnFocus(); - virtual void OnActivate(mojom::VRDisplayEventReason reason, - base::Callback<void(bool)> on_handled); - virtual void OnDeactivate(mojom::VRDisplayEventReason reason); - void SetListeningForActivate(bool listening); void SetInFocusedFrame(bool in_focused_frame); virtual bool ListeningForActivate(); virtual bool InFocusedFrame(); - void RequestPresent(mojom::VRSubmitFrameClientPtr submit_client, - mojom::VRPresentationProviderRequest request, - mojom::VRRequestPresentOptionsPtr options, - mojom::VRDisplayHost::RequestPresentCallback callback); - void ExitPresent(); - private: // mojom::VRMagicWindowProvider void GetPose(GetPoseCallback callback) override; + void GetFrameData(const gfx::Size& frame_size, + display::Display::Rotation rotation, + GetFrameDataCallback callback) override; mojo::Binding<mojom::VRMagicWindowProvider> binding_; - mojom::VRDisplayClientPtr client_; device::VRDeviceBase* device_; bool listening_for_activate_ = false; bool in_focused_frame_ = false; diff --git a/chromium/device/vr/vr_display_impl_unittest.cc b/chromium/device/vr/vr_display_impl_unittest.cc index 42fb49f6269..0d90fb8e269 100644 --- a/chromium/device/vr/vr_display_impl_unittest.cc +++ b/chromium/device/vr/vr_display_impl_unittest.cc @@ -35,8 +35,10 @@ class VRDisplayImplTest : public testing::Test { } std::unique_ptr<VRDisplayImpl> MakeDisplay() { + mojom::VRDisplayClientPtr display_client; return std::make_unique<VRDisplayImpl>( - device(), client(), device()->GetVRDisplayInfo(), nullptr, false); + device(), client(), device()->GetVRDisplayInfo(), nullptr, + mojo::MakeRequest(&display_client), false); } void RequestPresent(VRDisplayImpl* display_impl) { @@ -45,15 +47,15 @@ class VRDisplayImplTest : public testing::Test { // is ok. device::mojom::VRSubmitFrameClientPtr submit_client = nullptr; device::mojom::VRPresentationProviderRequest request = nullptr; - display_impl->RequestPresent( + device_->RequestPresent( std::move(submit_client), std::move(request), nullptr, - base::Bind(&VRDisplayImplTest::onPresentComplete, - base::Unretained(this))); + base::BindOnce(&VRDisplayImplTest::onPresentComplete, + base::Unretained(this))); } - void ExitPresent(VRDisplayImpl* display_impl) { display_impl->ExitPresent(); } + void ExitPresent() { device_->ExitPresent(); } - bool presenting() { return !!device_->GetPresentingDisplay(); } + bool presenting() { return device_->IsPresenting(); } VRDeviceBase* device() { return device_.get(); } FakeVRServiceClient* client() { return client_.get(); } @@ -73,33 +75,17 @@ TEST_F(VRDisplayImplTest, DevicePresentationIsolation) { EXPECT_TRUE(device()->IsAccessAllowed(display_1.get())); EXPECT_TRUE(device()->IsAccessAllowed(display_2.get())); - // Attempt to present without focus. - RequestPresent(display_1.get()); - EXPECT_FALSE(is_request_presenting_success_); - EXPECT_FALSE(presenting()); - - // Begin presenting to the fake device with service 1. - display_1->SetInFocusedFrame(true); + // Attempt to present. RequestPresent(display_1.get()); EXPECT_TRUE(is_request_presenting_success_); EXPECT_TRUE(presenting()); - // Service 2 should not be able to present to the device while service 1 - // is still presenting. - RequestPresent(display_2.get()); - EXPECT_FALSE(is_request_presenting_success_); - display_2->SetInFocusedFrame(true); - RequestPresent(display_2.get()); - EXPECT_FALSE(is_request_presenting_success_); - EXPECT_TRUE(device()->IsAccessAllowed(display_1.get())); + // While a device is presenting, noone should have access to magic window. + EXPECT_FALSE(device()->IsAccessAllowed(display_1.get())); EXPECT_FALSE(device()->IsAccessAllowed(display_2.get())); - // Service 2 should not be able to exit presentation to the device. - ExitPresent(display_2.get()); - EXPECT_TRUE(presenting()); - // Service 1 should be able to exit the presentation it initiated. - ExitPresent(display_1.get()); + ExitPresent(); EXPECT_FALSE(presenting()); // Once presentation had ended both services should be able to access the @@ -108,14 +94,4 @@ TEST_F(VRDisplayImplTest, DevicePresentationIsolation) { EXPECT_TRUE(device()->IsAccessAllowed(display_2.get())); } -// This test case tests that VRDisplayImpl dispatches a "vrdevicechanged" event -// to its client when it's been told the device has changed. -TEST_F(VRDisplayImplTest, DeviceChangedDispatched) { - auto display = MakeDisplay(); - display->OnChanged(device()->GetVRDisplayInfo()); - - base::RunLoop().RunUntilIdle(); - - EXPECT_TRUE(client()->CheckDeviceId(device()->GetId())); -} } |