summaryrefslogtreecommitdiff
path: root/chromium/device/fido
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-03 13:42:47 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:27:51 +0000
commit8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (patch)
treed29d987c4d7b173cf853279b79a51598f104b403 /chromium/device/fido
parent830c9e163d31a9180fadca926b3e1d7dfffb5021 (diff)
downloadqtwebengine-chromium-8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec.tar.gz
BASELINE: Update Chromium to 66.0.3359.156
Change-Id: I0c9831ad39911a086b6377b16f995ad75a51e441 Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/device/fido')
-rw-r--r--chromium/device/fido/BUILD.gn232
-rw-r--r--chromium/device/fido/DEPS6
-rw-r--r--chromium/device/fido/OWNERS5
-rw-r--r--chromium/device/fido/README.md41
-rw-r--r--chromium/device/fido/attestation_object.cc61
-rw-r--r--chromium/device/fido/attestation_object.h59
-rw-r--r--chromium/device/fido/attestation_statement.cc26
-rw-r--r--chromium/device/fido/attestation_statement.h58
-rw-r--r--chromium/device/fido/attested_credential_data.cc75
-rw-r--r--chromium/device/fido/attested_credential_data.h62
-rw-r--r--chromium/device/fido/authenticator_data.cc46
-rw-r--r--chromium/device/fido/authenticator_data.h66
-rw-r--r--chromium/device/fido/authenticator_get_assertion_response.cc43
-rw-r--r--chromium/device/fido/authenticator_get_assertion_response.h62
-rw-r--r--chromium/device/fido/authenticator_get_info_response.cc51
-rw-r--r--chromium/device/fido/authenticator_get_info_response.h69
-rw-r--r--chromium/device/fido/authenticator_make_credential_response.cc26
-rw-r--r--chromium/device/fido/authenticator_make_credential_response.h46
-rw-r--r--chromium/device/fido/authenticator_supported_options.cc52
-rw-r--r--chromium/device/fido/authenticator_supported_options.h63
-rw-r--r--chromium/device/fido/ctap_constants.cc25
-rw-r--r--chromium/device/fido/ctap_constants.h185
-rw-r--r--chromium/device/fido/ctap_empty_authenticator_request.cc19
-rw-r--r--chromium/device/fido/ctap_empty_authenticator_request.h70
-rw-r--r--chromium/device/fido/ctap_get_assertion_request.cc98
-rw-r--r--chromium/device/fido/ctap_get_assertion_request.h76
-rw-r--r--chromium/device/fido/ctap_make_credential_request.cc104
-rw-r--r--chromium/device/fido/ctap_make_credential_request.h76
-rw-r--r--chromium/device/fido/ctap_request_unittest.cc244
-rw-r--r--chromium/device/fido/ctap_response_fuzzer.cc45
-rw-r--r--chromium/device/fido/ctap_response_fuzzer_corpus/get_assertion_response_corpusbin0 -> 344 bytes
-rw-r--r--chromium/device/fido/ctap_response_fuzzer_corpus/make_credential_response_corpusbin0 -> 665 bytes
-rw-r--r--chromium/device/fido/ctap_response_unittest.cc319
-rw-r--r--chromium/device/fido/device_response_converter.cc247
-rw-r--r--chromium/device/fido/device_response_converter.h50
-rw-r--r--chromium/device/fido/ec_public_key.cc67
-rw-r--r--chromium/device/fido/ec_public_key.h51
-rw-r--r--chromium/device/fido/fake_hid_impl_for_testing.cc153
-rw-r--r--chromium/device/fido/fake_hid_impl_for_testing.h112
-rw-r--r--chromium/device/fido/fido_attestation_statement.cc76
-rw-r--r--chromium/device/fido/fido_attestation_statement.h44
-rw-r--r--chromium/device/fido/fido_hid_message.cc156
-rw-r--r--chromium/device/fido/fido_hid_message.h68
-rw-r--r--chromium/device/fido/fido_hid_message_fuzzer.cc36
-rw-r--r--chromium/device/fido/fido_hid_message_unittest.cc200
-rw-r--r--chromium/device/fido/fido_hid_packet.cc142
-rw-r--r--chromium/device/fido/fido_hid_packet.h104
-rw-r--r--chromium/device/fido/mock_u2f_ble_connection.cc37
-rw-r--r--chromium/device/fido/mock_u2f_ble_connection.h52
-rw-r--r--chromium/device/fido/mock_u2f_device.cc84
-rw-r--r--chromium/device/fido/mock_u2f_device.h58
-rw-r--r--chromium/device/fido/mock_u2f_discovery.cc61
-rw-r--r--chromium/device/fido/mock_u2f_discovery.h56
-rw-r--r--chromium/device/fido/public_key.cc18
-rw-r--r--chromium/device/fido/public_key.h36
-rw-r--r--chromium/device/fido/public_key_credential_descriptor.cc67
-rw-r--r--chromium/device/fido/public_key_credential_descriptor.h48
-rw-r--r--chromium/device/fido/public_key_credential_params.cc38
-rw-r--r--chromium/device/fido/public_key_credential_params.h49
-rw-r--r--chromium/device/fido/public_key_credential_rp_entity.cc44
-rw-r--r--chromium/device/fido/public_key_credential_rp_entity.h47
-rw-r--r--chromium/device/fido/public_key_credential_user_entity.cc106
-rw-r--r--chromium/device/fido/public_key_credential_user_entity.h57
-rw-r--r--chromium/device/fido/register_response_data.cc97
-rw-r--r--chromium/device/fido/register_response_data.h59
-rw-r--r--chromium/device/fido/register_response_data_fuzzer.cc17
-rw-r--r--chromium/device/fido/response_data.cc34
-rw-r--r--chromium/device/fido/response_data.h42
-rw-r--r--chromium/device/fido/response_data_fuzzer_corpus/register1bin0 -> 791 bytes
-rw-r--r--chromium/device/fido/response_data_fuzzer_corpus/sign01
-rw-r--r--chromium/device/fido/response_data_fuzzer_corpus/sign1bin0 -> 76 bytes
-rw-r--r--chromium/device/fido/sign_response_data.cc71
-rw-r--r--chromium/device/fido/sign_response_data.h49
-rw-r--r--chromium/device/fido/sign_response_data_fuzzer.cc19
-rw-r--r--chromium/device/fido/u2f_apdu_command.cc192
-rw-r--r--chromium/device/fido/u2f_apdu_command.h118
-rw-r--r--chromium/device/fido/u2f_apdu_fuzzer.cc19
-rw-r--r--chromium/device/fido/u2f_apdu_response.cc32
-rw-r--r--chromium/device/fido/u2f_apdu_response.h48
-rw-r--r--chromium/device/fido/u2f_apdu_unittest.cc273
-rw-r--r--chromium/device/fido/u2f_ble_connection.cc579
-rw-r--r--chromium/device/fido/u2f_ble_connection.h162
-rw-r--r--chromium/device/fido/u2f_ble_connection_unittest.cc778
-rw-r--r--chromium/device/fido/u2f_ble_device.cc184
-rw-r--r--chromium/device/fido/u2f_ble_device.h90
-rw-r--r--chromium/device/fido/u2f_ble_device_unittest.cc154
-rw-r--r--chromium/device/fido/u2f_ble_discovery.cc148
-rw-r--r--chromium/device/fido/u2f_ble_discovery.h58
-rw-r--r--chromium/device/fido/u2f_ble_discovery_unittest.cc204
-rw-r--r--chromium/device/fido/u2f_ble_frames.cc169
-rw-r--r--chromium/device/fido/u2f_ble_frames.h180
-rw-r--r--chromium/device/fido/u2f_ble_frames_fuzzer.cc59
-rw-r--r--chromium/device/fido/u2f_ble_frames_unittest.cc143
-rw-r--r--chromium/device/fido/u2f_ble_transaction.cc139
-rw-r--r--chromium/device/fido/u2f_ble_transaction.h61
-rw-r--r--chromium/device/fido/u2f_ble_uuids.cc18
-rw-r--r--chromium/device/fido/u2f_ble_uuids.h25
-rw-r--r--chromium/device/fido/u2f_command_type.h44
-rw-r--r--chromium/device/fido/u2f_device.cc141
-rw-r--r--chromium/device/fido/u2f_device.h84
-rw-r--r--chromium/device/fido/u2f_discovery.cc90
-rw-r--r--chromium/device/fido/u2f_discovery.h67
-rw-r--r--chromium/device/fido/u2f_discovery_unittest.cc103
-rw-r--r--chromium/device/fido/u2f_hid_device.cc357
-rw-r--r--chromium/device/fido/u2f_hid_device.h121
-rw-r--r--chromium/device/fido/u2f_hid_device_unittest.cc488
-rw-r--r--chromium/device/fido/u2f_hid_discovery.cc63
-rw-r--r--chromium/device/fido/u2f_hid_discovery.h55
-rw-r--r--chromium/device/fido/u2f_hid_discovery_unittest.cc119
-rw-r--r--chromium/device/fido/u2f_parsing_utils.cc32
-rw-r--r--chromium/device/fido/u2f_parsing_utils.h32
-rw-r--r--chromium/device/fido/u2f_register.cc161
-rw-r--r--chromium/device/fido/u2f_register.h85
-rw-r--r--chromium/device/fido/u2f_register_unittest.cc873
-rw-r--r--chromium/device/fido/u2f_request.cc222
-rw-r--r--chromium/device/fido/u2f_request.h119
-rw-r--r--chromium/device/fido/u2f_request_unittest.cc355
-rw-r--r--chromium/device/fido/u2f_response_test_data.h109
-rw-r--r--chromium/device/fido/u2f_return_code.h19
-rw-r--r--chromium/device/fido/u2f_sign.cc142
-rw-r--r--chromium/device/fido/u2f_sign.h78
-rw-r--r--chromium/device/fido/u2f_sign_unittest.cc588
-rw-r--r--chromium/device/fido/u2f_transport_protocol.h19
123 files changed, 13562 insertions, 0 deletions
diff --git a/chromium/device/fido/BUILD.gn b/chromium/device/fido/BUILD.gn
new file mode 100644
index 00000000000..70184488cd7
--- /dev/null
+++ b/chromium/device/fido/BUILD.gn
@@ -0,0 +1,232 @@
+# Copyright 2016 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("//testing/libfuzzer/fuzzer_test.gni")
+
+source_set("fido") {
+ sources = [
+ "attestation_object.cc",
+ "attestation_object.h",
+ "attestation_statement.cc",
+ "attestation_statement.h",
+ "attested_credential_data.cc",
+ "attested_credential_data.h",
+ "authenticator_data.cc",
+ "authenticator_data.h",
+ "authenticator_get_assertion_response.cc",
+ "authenticator_get_assertion_response.h",
+ "authenticator_get_info_response.cc",
+ "authenticator_get_info_response.h",
+ "authenticator_make_credential_response.cc",
+ "authenticator_make_credential_response.h",
+ "authenticator_supported_options.cc",
+ "authenticator_supported_options.h",
+ "ctap_constants.cc",
+ "ctap_constants.h",
+ "ctap_empty_authenticator_request.cc",
+ "ctap_empty_authenticator_request.h",
+ "ctap_get_assertion_request.cc",
+ "ctap_get_assertion_request.h",
+ "ctap_make_credential_request.cc",
+ "ctap_make_credential_request.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_hid_message.cc",
+ "fido_hid_message.h",
+ "fido_hid_packet.cc",
+ "fido_hid_packet.h",
+ "public_key.cc",
+ "public_key.h",
+ "public_key_credential_descriptor.cc",
+ "public_key_credential_descriptor.h",
+ "public_key_credential_params.cc",
+ "public_key_credential_params.h",
+ "public_key_credential_rp_entity.cc",
+ "public_key_credential_rp_entity.h",
+ "public_key_credential_user_entity.cc",
+ "public_key_credential_user_entity.h",
+ "register_response_data.cc",
+ "register_response_data.h",
+ "response_data.cc",
+ "response_data.h",
+ "sign_response_data.cc",
+ "sign_response_data.h",
+ "u2f_apdu_command.cc",
+ "u2f_apdu_command.h",
+ "u2f_apdu_response.cc",
+ "u2f_apdu_response.h",
+ "u2f_ble_connection.cc",
+ "u2f_ble_connection.h",
+ "u2f_ble_device.cc",
+ "u2f_ble_device.h",
+ "u2f_ble_discovery.cc",
+ "u2f_ble_discovery.h",
+ "u2f_ble_frames.cc",
+ "u2f_ble_frames.h",
+ "u2f_ble_transaction.cc",
+ "u2f_ble_transaction.h",
+ "u2f_ble_uuids.cc",
+ "u2f_ble_uuids.h",
+ "u2f_command_type.h",
+ "u2f_device.cc",
+ "u2f_device.h",
+ "u2f_discovery.cc",
+ "u2f_discovery.h",
+ "u2f_parsing_utils.cc",
+ "u2f_parsing_utils.h",
+ "u2f_register.cc",
+ "u2f_register.h",
+ "u2f_request.cc",
+ "u2f_request.h",
+ "u2f_return_code.h",
+ "u2f_sign.cc",
+ "u2f_sign.h",
+ "u2f_transport_protocol.h",
+ ]
+
+ deps = [
+ "//base",
+ "//components/cbor",
+ "//crypto",
+ "//device/base",
+ "//device/bluetooth",
+ "//net",
+ "//services/device/public/mojom",
+ "//services/service_manager/public/cpp",
+ "//services/service_manager/public/mojom",
+ "//third_party/boringssl",
+ ]
+
+ # HID is not supported on Android.
+ if (!is_android) {
+ sources += [
+ "u2f_hid_device.cc",
+ "u2f_hid_device.h",
+ "u2f_hid_discovery.cc",
+ "u2f_hid_discovery.h",
+ ]
+
+ deps += [
+ "//services/device/public/cpp/hid",
+ "//services/device/public/mojom",
+ ]
+ }
+}
+
+source_set("mocks") {
+ testonly = true
+
+ sources = [
+ "mock_u2f_ble_connection.cc",
+ "mock_u2f_ble_connection.h",
+ "mock_u2f_device.cc",
+ "mock_u2f_device.h",
+ "mock_u2f_discovery.cc",
+ "mock_u2f_discovery.h",
+ ]
+
+ deps = [
+ ":fido",
+ "//base",
+ "//testing/gmock",
+ ]
+}
+
+fuzzer_test("u2f_apdu_fuzzer") {
+ sources = [
+ "u2f_apdu_fuzzer.cc",
+ ]
+ deps = [
+ ":fido",
+ "//net",
+ ]
+ libfuzzer_options = [ "max_len=65537" ]
+}
+
+fuzzer_test("fido_hid_message_fuzzer") {
+ sources = [
+ "fido_hid_message_fuzzer.cc",
+ ]
+ deps = [
+ ":fido",
+ "//base",
+ ]
+ libfuzzer_options = [ "max_len=2048" ]
+}
+
+fuzzer_test("u2f_ble_frames_fuzzer") {
+ sources = [
+ "u2f_ble_frames_fuzzer.cc",
+ ]
+ deps = [
+ ":fido",
+ "//net",
+ ]
+ libfuzzer_options = [ "max_len=65535" ]
+}
+
+fuzzer_test("sign_response_data_fuzzer") {
+ sources = [
+ "sign_response_data_fuzzer.cc",
+ ]
+ deps = [
+ ":fido",
+ "//base",
+ "//net",
+ ]
+ seed_corpus = "response_data_fuzzer_corpus/"
+ libfuzzer_options = [ "max_len=65537" ]
+}
+
+fuzzer_test("register_response_data_fuzzer") {
+ sources = [
+ "register_response_data_fuzzer.cc",
+ ]
+ deps = [
+ ":fido",
+ "//net",
+ ]
+
+ seed_corpus = "response_data_fuzzer_corpus/"
+ libfuzzer_options = [ "max_len=65537" ]
+}
+
+fuzzer_test("ctap_response_fuzzer") {
+ sources = [
+ "ctap_response_fuzzer.cc",
+ ]
+ deps = [
+ ":fido",
+ "//base",
+ "//base:i18n",
+ ]
+ seed_corpus = "ctap_response_fuzzer_corpus/"
+ libfuzzer_options = [ "max_len=65537" ]
+}
+
+is_linux_without_udev = is_linux && !use_udev
+
+source_set("test_support") {
+ testonly = true
+
+ # Android doesn't compile.
+ # Linux, requires udev.
+ if (!is_linux_without_udev && !is_android) {
+ sources = [
+ "fake_hid_impl_for_testing.cc",
+ "fake_hid_impl_for_testing.h",
+ ]
+ deps = [
+ "//base",
+ "//mojo/public/cpp/bindings",
+ "//services/device/public/mojom",
+ "//testing/gmock",
+ ]
+ }
+}
diff --git a/chromium/device/fido/DEPS b/chromium/device/fido/DEPS
new file mode 100644
index 00000000000..7f30d6c31f9
--- /dev/null
+++ b/chromium/device/fido/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+components/cbor",
+ "+crypto",
+ "+net/base",
+ "+third_party/boringssl/src/include",
+]
diff --git a/chromium/device/fido/OWNERS b/chromium/device/fido/OWNERS
new file mode 100644
index 00000000000..732c53e8b6f
--- /dev/null
+++ b/chromium/device/fido/OWNERS
@@ -0,0 +1,5 @@
+reillyg@chromium.org
+jdoerrie@chromium.org
+
+# TEAM: security-dev@chromium.org
+# COMPONENT: Blink>WebAuthentication
diff --git a/chromium/device/fido/README.md b/chromium/device/fido/README.md
new file mode 100644
index 00000000000..fc1eaaa9d35
--- /dev/null
+++ b/chromium/device/fido/README.md
@@ -0,0 +1,41 @@
+# FIDO
+
+`//device/fido` contains abstractions for [FIDO](https://fidoalliance.org/)
+security keys across multiple platforms.
+
+## U2F Security Keys
+
+Support for [U2F (FIDO
+1.2)](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.html)
+security keys is present for both USB Human Interface Devices (USB HID) and
+Bluetooth Low Energy (BLE) devices. Clients can perform U2F operations using the
+[`U2fRegister`](u2f_register.h) and [`U2fSign`](u2f_sign.h) classes. These
+abstractions automatically perform device discovery and handle communication
+with the underlying devices. Talking to HID devices is done using the [HID Mojo
+service](/services/device/public/interfaces/hid.mojom), while communication with
+BLE devices is done using abstractions found in
+[`//device/bluetooth`](/device/bluetooth/). HID is supported on all desktop
+platforms, while BLE lacks some support on Windows (see
+[`//device/bluetooth/README.md`](/device/bluetooth/README.md) for details).
+
+## CTAP Security Keys
+
+Support for [CTAP2 (FIDO
+2.0)](https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html)
+security keys is in active development and aims to unify the implementations for
+both U2F and CTAP keys.
+
+## Testing
+
+### Unit Tests
+
+Standard use of `*_unittest.cc` files for must code coverage. Files prefixed
+with `mock_` provide GoogleMock based fake objects for easy mocking of
+dependencies during testing.
+
+### Fuzzers
+
+[libFuzzer] tests are in `*_fuzzer.cc` files. They test for bad input from
+devices, e.g. when parsing responses to register or sign operations.
+
+[libFuzzer]: /testing/libfuzzer/README.md
diff --git a/chromium/device/fido/attestation_object.cc b/chromium/device/fido/attestation_object.cc
new file mode 100644
index 00000000000..f1c0eefcb00
--- /dev/null
+++ b/chromium/device/fido/attestation_object.cc
@@ -0,0 +1,61 @@
+// 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.
+
+#include "device/fido/attestation_object.h"
+
+#include <utility>
+
+#include "components/cbor/cbor_values.h"
+#include "components/cbor/cbor_writer.h"
+#include "device/fido/attestation_statement.h"
+
+namespace device {
+
+namespace {
+constexpr char kAuthDataKey[] = "authData";
+constexpr char kFormatKey[] = "fmt";
+constexpr char kAttestationKey[] = "attStmt";
+} // namespace
+
+AttestationObject::AttestationObject(
+ AuthenticatorData data,
+ std::unique_ptr<AttestationStatement> statement)
+ : authenticator_data_(std::move(data)),
+ attestation_statement_(std::move(statement)) {}
+
+AttestationObject::AttestationObject(AttestationObject&& other) = default;
+AttestationObject& AttestationObject::operator=(AttestationObject&& other) =
+ default;
+
+AttestationObject::~AttestationObject() = default;
+
+void AttestationObject::EraseAttestationStatement() {
+ attestation_statement_.reset(new NoneAttestationStatement);
+
+#if !defined(NDEBUG)
+ std::vector<uint8_t> auth_data = authenticator_data_.SerializeToByteArray();
+ // See diagram at https://w3c.github.io/webauthn/#sctn-attestation
+ constexpr size_t kAAGUIDOffset =
+ 32 /* RP ID hash */ + 1 /* flags */ + 4 /* signature counter */;
+ constexpr size_t kAAGUIDSize = 16;
+ DCHECK(auth_data.size() >= kAAGUIDOffset + kAAGUIDSize);
+ DCHECK(std::all_of(&auth_data[kAAGUIDOffset],
+ &auth_data[kAAGUIDOffset + kAAGUIDSize],
+ [](uint8_t v) { return v == 0; }));
+#endif
+}
+
+std::vector<uint8_t> AttestationObject::SerializeToCBOREncodedBytes() const {
+ cbor::CBORValue::MapValue map;
+ map[cbor::CBORValue(kFormatKey)] =
+ cbor::CBORValue(attestation_statement_->format_name());
+ map[cbor::CBORValue(kAuthDataKey)] =
+ cbor::CBORValue(authenticator_data_.SerializeToByteArray());
+ map[cbor::CBORValue(kAttestationKey)] =
+ cbor::CBORValue(attestation_statement_->GetAsCBORMap());
+ return cbor::CBORWriter::Write(cbor::CBORValue(std::move(map)))
+ .value_or(std::vector<uint8_t>());
+}
+
+} // namespace device
diff --git a/chromium/device/fido/attestation_object.h b/chromium/device/fido/attestation_object.h
new file mode 100644
index 00000000000..90198e388ad
--- /dev/null
+++ b/chromium/device/fido/attestation_object.h
@@ -0,0 +1,59 @@
+// 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.
+
+#ifndef DEVICE_FIDO_ATTESTATION_OBJECT_H_
+#define DEVICE_FIDO_ATTESTATION_OBJECT_H_
+
+#include <stdint.h>
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "device/fido/authenticator_data.h"
+
+namespace device {
+
+class AttestationStatement;
+
+// Object containing the authenticator-provided attestation every time
+// a credential is created, per
+// https://www.w3.org/TR/2017/WD-webauthn-20170505/#cred-attestation.
+class AttestationObject {
+ public:
+ AttestationObject(AuthenticatorData data,
+ std::unique_ptr<AttestationStatement> statement);
+
+ // Moveable.
+ AttestationObject(AttestationObject&& other);
+ AttestationObject& operator=(AttestationObject&& other);
+
+ ~AttestationObject();
+
+ // Replaces the attestation statement with a “none†attestation, as
+ // specified for step 20.3 in
+ // https://w3c.github.io/webauthn/#createCredential. (This does not,
+ // currently, erase the AAGUID (in AttestedCredentialData in
+ // |authenticator_data_|) because it is already always zero for U2F devices.
+ // If CTAP2 is supported in the future, that will need to be taken into
+ // account.)
+ //
+ // TODO(https://crbug.com/780078): erase AAGUID when CTAP2 is supported.
+ void EraseAttestationStatement();
+
+ // Produces a CBOR-encoded byte-array in the following format:
+ // {"authData": authenticator data bytes,
+ // "fmt": attestation format name,
+ // "attStmt": attestation statement bytes }
+ std::vector<uint8_t> SerializeToCBOREncodedBytes() const;
+
+ private:
+ AuthenticatorData authenticator_data_;
+ std::unique_ptr<AttestationStatement> attestation_statement_;
+
+ DISALLOW_COPY_AND_ASSIGN(AttestationObject);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_ATTESTATION_OBJECT_H_
diff --git a/chromium/device/fido/attestation_statement.cc b/chromium/device/fido/attestation_statement.cc
new file mode 100644
index 00000000000..bbb1638be4d
--- /dev/null
+++ b/chromium/device/fido/attestation_statement.cc
@@ -0,0 +1,26 @@
+// 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.
+
+#include "device/fido/attestation_statement.h"
+
+#include <string>
+#include <utility>
+
+namespace device {
+
+AttestationStatement::~AttestationStatement() = default;
+
+AttestationStatement::AttestationStatement(std::string format)
+ : format_(std::move(format)) {}
+
+NoneAttestationStatement::NoneAttestationStatement()
+ : AttestationStatement("none") {}
+
+NoneAttestationStatement::~NoneAttestationStatement() = default;
+
+cbor::CBORValue::MapValue NoneAttestationStatement::GetAsCBORMap() {
+ return cbor::CBORValue::MapValue();
+}
+
+} // namespace device
diff --git a/chromium/device/fido/attestation_statement.h b/chromium/device/fido/attestation_statement.h
new file mode 100644
index 00000000000..db7fc795601
--- /dev/null
+++ b/chromium/device/fido/attestation_statement.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef DEVICE_FIDO_ATTESTATION_STATEMENT_H_
+#define DEVICE_FIDO_ATTESTATION_STATEMENT_H_
+
+#include "base/macros.h"
+#include "components/cbor/cbor_values.h"
+
+namespace device {
+
+// A signed data object containing statements about a credential itself and
+// the authenticator that created it.
+// Each attestation statement format is defined by the following attributes:
+// - The attestation statement format identifier.
+// - The set of attestation types supported by the format.
+// - The syntax of an attestation statement produced in this format.
+// https://www.w3.org/TR/2017/WD-webauthn-20170505/#cred-attestation.
+class AttestationStatement {
+ public:
+ virtual ~AttestationStatement();
+
+ // The CBOR map data to be added to the attestation object, structured
+ // in a way that is specified by its particular attestation format:
+ // https://www.w3.org/TR/2017/WD-webauthn-20170505/#defined-attestation-formats
+ // This is not a CBOR-encoded byte array, but the map that will be
+ // nested within another CBOR object and encoded then.
+ virtual cbor::CBORValue::MapValue GetAsCBORMap() = 0;
+
+ const std::string& format_name() { return format_; }
+
+ protected:
+ AttestationStatement(std::string format);
+
+ private:
+ const std::string format_;
+
+ DISALLOW_COPY_AND_ASSIGN(AttestationStatement);
+};
+
+// NoneAttestationStatement represents a “none†attestation, which is used when
+// attestation information will not be returned. See
+// https://w3c.github.io/webauthn/#none-attestation
+class NoneAttestationStatement : public AttestationStatement {
+ public:
+ NoneAttestationStatement();
+
+ ~NoneAttestationStatement() override;
+ cbor::CBORValue::MapValue GetAsCBORMap() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NoneAttestationStatement);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_ATTESTATION_STATEMENT_H_
diff --git a/chromium/device/fido/attested_credential_data.cc b/chromium/device/fido/attested_credential_data.cc
new file mode 100644
index 00000000000..d00efce481b
--- /dev/null
+++ b/chromium/device/fido/attested_credential_data.cc
@@ -0,0 +1,75 @@
+// 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.
+
+#include "device/fido/attested_credential_data.h"
+
+#include <utility>
+
+#include "base/numerics/safe_math.h"
+#include "device/fido/public_key.h"
+#include "device/fido/u2f_parsing_utils.h"
+
+namespace device {
+
+// static
+base::Optional<AttestedCredentialData>
+AttestedCredentialData::CreateFromU2fRegisterResponse(
+ base::span<const uint8_t> u2f_data,
+ std::vector<uint8_t> aaguid,
+ std::unique_ptr<PublicKey> public_key) {
+ // 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);
+
+ if (extracted_length.empty()) {
+ return base::nullopt;
+ }
+
+ // Note that U2F responses only use one byte for length.
+ std::vector<uint8_t> credential_id_length = {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,
+ base::strict_cast<size_t>(credential_id_length[1]));
+
+ if (credential_id.empty()) {
+ return base::nullopt;
+ }
+
+ return AttestedCredentialData(
+ std::move(aaguid), std::move(credential_id_length),
+ std::move(credential_id), std::move(public_key));
+}
+
+AttestedCredentialData::AttestedCredentialData(
+ std::vector<uint8_t> aaguid,
+ std::vector<uint8_t> length,
+ std::vector<uint8_t> credential_id,
+ std::unique_ptr<PublicKey> public_key)
+ : aaguid_(std::move(aaguid)),
+ credential_id_length_(std::move(length)),
+ credential_id_(std::move(credential_id)),
+ public_key_(std::move(public_key)) {}
+
+AttestedCredentialData::AttestedCredentialData(AttestedCredentialData&& other) =
+ default;
+
+AttestedCredentialData& AttestedCredentialData::operator=(
+ AttestedCredentialData&& other) = default;
+
+AttestedCredentialData::~AttestedCredentialData() = default;
+
+std::vector<uint8_t> AttestedCredentialData::SerializeAsBytes() const {
+ std::vector<uint8_t> attestation_data;
+ u2f_parsing_utils::Append(&attestation_data, aaguid_);
+ u2f_parsing_utils::Append(&attestation_data, credential_id_length_);
+ u2f_parsing_utils::Append(&attestation_data, credential_id_);
+ u2f_parsing_utils::Append(&attestation_data, public_key_->EncodeAsCOSEKey());
+ return attestation_data;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/attested_credential_data.h b/chromium/device/fido/attested_credential_data.h
new file mode 100644
index 00000000000..63c7e4d5156
--- /dev/null
+++ b/chromium/device/fido/attested_credential_data.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef DEVICE_FIDO_ATTESTED_CREDENTIAL_DATA_H_
+#define DEVICE_FIDO_ATTESTED_CREDENTIAL_DATA_H_
+
+#include <stdint.h>
+#include <memory>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "base/optional.h"
+
+namespace device {
+
+class PublicKey;
+
+// https://www.w3.org/TR/2017/WD-webauthn-20170505/#sec-attestation-data
+class AttestedCredentialData {
+ public:
+ static base::Optional<AttestedCredentialData> CreateFromU2fRegisterResponse(
+ base::span<const uint8_t> u2f_data,
+ std::vector<uint8_t> aaguid,
+ std::unique_ptr<PublicKey> public_key);
+
+ AttestedCredentialData(std::vector<uint8_t> aaguid,
+ std::vector<uint8_t> length,
+ std::vector<uint8_t> credential_id,
+ std::unique_ptr<PublicKey> public_key);
+
+ // Moveable.
+ AttestedCredentialData(AttestedCredentialData&& other);
+ AttestedCredentialData& operator=(AttestedCredentialData&& other);
+
+ ~AttestedCredentialData();
+
+ const std::vector<uint8_t>& credential_id() { return credential_id_; }
+
+ // Produces a byte array consisting of:
+ // * AAGUID (16 bytes)
+ // * Len (2 bytes)
+ // * Credential Id (Len bytes)
+ // * Credential Public Key.
+ std::vector<uint8_t> SerializeAsBytes() const;
+
+ private:
+ // The 16-byte AAGUID of the authenticator.
+ std::vector<uint8_t> aaguid_;
+
+ // Big-endian length of the credential (i.e. key handle).
+ std::vector<uint8_t> credential_id_length_;
+ std::vector<uint8_t> credential_id_;
+ std::unique_ptr<PublicKey> public_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(AttestedCredentialData);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_ATTESTED_CREDENTIAL_DATA_H_
diff --git a/chromium/device/fido/authenticator_data.cc b/chromium/device/fido/authenticator_data.cc
new file mode 100644
index 00000000000..d541135070a
--- /dev/null
+++ b/chromium/device/fido/authenticator_data.cc
@@ -0,0 +1,46 @@
+// 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.
+
+#include "device/fido/authenticator_data.h"
+
+#include <utility>
+
+#include "device/fido/u2f_parsing_utils.h"
+
+namespace device {
+
+AuthenticatorData::AuthenticatorData(
+ std::vector<uint8_t> application_parameter,
+ uint8_t flags,
+ std::vector<uint8_t> counter,
+ base::Optional<AttestedCredentialData> data)
+ : application_parameter_(std::move(application_parameter)),
+ flags_(flags),
+ counter_(std::move(counter)),
+ attested_data_(std::move(data)) {
+ // TODO(kpaulhamus): use std::array for these small, fixed-sized vectors.
+ CHECK_EQ(counter_.size(), 4u);
+}
+
+AuthenticatorData::AuthenticatorData(AuthenticatorData&& other) = default;
+AuthenticatorData& AuthenticatorData::operator=(AuthenticatorData&& other) =
+ default;
+
+AuthenticatorData::~AuthenticatorData() = default;
+
+std::vector<uint8_t> AuthenticatorData::SerializeToByteArray() const {
+ std::vector<uint8_t> authenticator_data;
+ u2f_parsing_utils::Append(&authenticator_data, application_parameter_);
+ authenticator_data.insert(authenticator_data.end(), flags_);
+ u2f_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());
+ }
+ return authenticator_data;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/authenticator_data.h b/chromium/device/fido/authenticator_data.h
new file mode 100644
index 00000000000..a4c5059cac7
--- /dev/null
+++ b/chromium/device/fido/authenticator_data.h
@@ -0,0 +1,66 @@
+// 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.
+
+#ifndef DEVICE_FIDO_AUTHENTICATOR_DATA_H_
+#define DEVICE_FIDO_AUTHENTICATOR_DATA_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/fido/attested_credential_data.h"
+
+namespace device {
+
+// https://www.w3.org/TR/2017/WD-webauthn-20170505/#sec-authenticator-data.
+class AuthenticatorData {
+ public:
+ enum class Flag : uint8_t {
+ kTestOfUserPresence = 1u << 0,
+ kAttestation = 1u << 6
+ };
+
+ AuthenticatorData(std::vector<uint8_t> application_parameter,
+ uint8_t flags,
+ std::vector<uint8_t> counter,
+ base::Optional<AttestedCredentialData> data);
+
+ // Moveable.
+ AuthenticatorData(AuthenticatorData&& other);
+ AuthenticatorData& operator=(AuthenticatorData&& other);
+
+ ~AuthenticatorData();
+
+ // Produces a byte array consisting of:
+ // * hash(relying_party_id / appid)
+ // * flags
+ // * counter
+ // * attestation_data.
+ std::vector<uint8_t> SerializeToByteArray() const;
+
+ private:
+ // The application parameter: a SHA-256 hash of either the RP ID or the AppID
+ // associated with the credential.
+ std::vector<uint8_t> application_parameter_;
+
+ // Flags (bit 0 is the least significant bit):
+ // [ED | AT | RFU | RFU | RFU | RFU | RFU | UP ]
+ // * Bit 0: Test of User Presence (TUP) result.
+ // * Bits 1-5: Reserved for future use (RFU).
+ // * Bit 6: Attestation data included (AT).
+ // * Bit 7: Extension data included (ED).
+ uint8_t flags_;
+
+ // Signature counter, 32-bit unsigned big-endian integer.
+ std::vector<uint8_t> counter_;
+ base::Optional<AttestedCredentialData> attested_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(AuthenticatorData);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_AUTHENTICATOR_DATA_H_
diff --git a/chromium/device/fido/authenticator_get_assertion_response.cc b/chromium/device/fido/authenticator_get_assertion_response.cc
new file mode 100644
index 00000000000..4c72c109adf
--- /dev/null
+++ b/chromium/device/fido/authenticator_get_assertion_response.cc
@@ -0,0 +1,43 @@
+// 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/authenticator_get_assertion_response.h"
+
+#include <utility>
+
+namespace device {
+
+AuthenticatorGetAssertionResponse::AuthenticatorGetAssertionResponse(
+ CtapDeviceResponseCode response_code,
+ std::vector<uint8_t> auth_data,
+ std::vector<uint8_t> signature,
+ PublicKeyCredentialUserEntity user)
+ : response_code_(response_code),
+ auth_data_(std::move(auth_data)),
+ signature_(std::move(signature)),
+ user_(std::move(user)) {}
+
+AuthenticatorGetAssertionResponse::AuthenticatorGetAssertionResponse(
+ AuthenticatorGetAssertionResponse&& that) = default;
+
+AuthenticatorGetAssertionResponse& AuthenticatorGetAssertionResponse::operator=(
+ AuthenticatorGetAssertionResponse&& other) = default;
+
+AuthenticatorGetAssertionResponse::~AuthenticatorGetAssertionResponse() =
+ default;
+
+AuthenticatorGetAssertionResponse&
+AuthenticatorGetAssertionResponse::SetNumCredentials(uint8_t num_credentials) {
+ num_credentials_ = num_credentials;
+ return *this;
+}
+
+AuthenticatorGetAssertionResponse&
+AuthenticatorGetAssertionResponse::SetCredential(
+ PublicKeyCredentialDescriptor credential) {
+ credential_ = std::move(credential);
+ return *this;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/authenticator_get_assertion_response.h b/chromium/device/fido/authenticator_get_assertion_response.h
new file mode 100644
index 00000000000..30a979abbb0
--- /dev/null
+++ b/chromium/device/fido/authenticator_get_assertion_response.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_AUTHENTICATOR_GET_ASSERTION_RESPONSE_H_
+#define DEVICE_FIDO_AUTHENTICATOR_GET_ASSERTION_RESPONSE_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/fido/ctap_constants.h"
+#include "device/fido/public_key_credential_descriptor.h"
+#include "device/fido/public_key_credential_user_entity.h"
+
+namespace device {
+
+// Represents response from authenticators for AuthenticatorGetAssertion and
+// AuthenticatorGetNextAssertion requests.
+// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticatorGetAssertion
+class AuthenticatorGetAssertionResponse {
+ public:
+ AuthenticatorGetAssertionResponse(CtapDeviceResponseCode response_code,
+ std::vector<uint8_t> auth_data,
+ std::vector<uint8_t> signature,
+ PublicKeyCredentialUserEntity user);
+ AuthenticatorGetAssertionResponse(AuthenticatorGetAssertionResponse&& that);
+ AuthenticatorGetAssertionResponse& operator=(
+ AuthenticatorGetAssertionResponse&& other);
+ ~AuthenticatorGetAssertionResponse();
+
+ AuthenticatorGetAssertionResponse& SetNumCredentials(uint8_t num_credentials);
+ AuthenticatorGetAssertionResponse& SetCredential(
+ PublicKeyCredentialDescriptor credential);
+
+ CtapDeviceResponseCode response_code() const { return response_code_; }
+ const std::vector<uint8_t>& auth_data() const { return auth_data_; }
+ const std::vector<uint8_t>& signature() const { return signature_; }
+ const PublicKeyCredentialUserEntity& user() const { return user_; }
+ const base::Optional<uint8_t>& num_credentials() const {
+ return num_credentials_;
+ }
+ const base::Optional<PublicKeyCredentialDescriptor>& credential() const {
+ return credential_;
+ }
+
+ private:
+ CtapDeviceResponseCode response_code_;
+ std::vector<uint8_t> auth_data_;
+ std::vector<uint8_t> signature_;
+ PublicKeyCredentialUserEntity user_;
+ base::Optional<uint8_t> num_credentials_;
+ base::Optional<PublicKeyCredentialDescriptor> credential_;
+
+ DISALLOW_COPY_AND_ASSIGN(AuthenticatorGetAssertionResponse);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_AUTHENTICATOR_GET_ASSERTION_RESPONSE_H_
diff --git a/chromium/device/fido/authenticator_get_info_response.cc b/chromium/device/fido/authenticator_get_info_response.cc
new file mode 100644
index 00000000000..83d75b1a646
--- /dev/null
+++ b/chromium/device/fido/authenticator_get_info_response.cc
@@ -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.
+
+#include "device/fido/authenticator_get_info_response.h"
+
+#include <utility>
+
+namespace device {
+
+AuthenticatorGetInfoResponse::AuthenticatorGetInfoResponse(
+ CtapDeviceResponseCode response_code,
+ std::vector<std::string> versions,
+ std::vector<uint8_t> aaguid)
+ : response_code_(response_code),
+ versions_(std::move(versions)),
+ aaguid_(std::move(aaguid)) {}
+
+AuthenticatorGetInfoResponse::AuthenticatorGetInfoResponse(
+ AuthenticatorGetInfoResponse&& that) = default;
+
+AuthenticatorGetInfoResponse& AuthenticatorGetInfoResponse::operator=(
+ AuthenticatorGetInfoResponse&& other) = default;
+
+AuthenticatorGetInfoResponse::~AuthenticatorGetInfoResponse() = default;
+
+AuthenticatorGetInfoResponse& AuthenticatorGetInfoResponse::SetMaxMsgSize(
+ uint8_t max_msg_size) {
+ max_msg_size_ = max_msg_size;
+ return *this;
+}
+
+AuthenticatorGetInfoResponse& AuthenticatorGetInfoResponse::SetPinProtocols(
+ std::vector<uint8_t> pin_protocols) {
+ pin_protocols_ = std::move(pin_protocols);
+ return *this;
+}
+
+AuthenticatorGetInfoResponse& AuthenticatorGetInfoResponse::SetExtensions(
+ std::vector<std::string> extensions) {
+ extensions_ = std::move(extensions);
+ return *this;
+}
+
+AuthenticatorGetInfoResponse& AuthenticatorGetInfoResponse::SetOptions(
+ AuthenticatorSupportedOptions options) {
+ options_ = std::move(options);
+ return *this;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/authenticator_get_info_response.h b/chromium/device/fido/authenticator_get_info_response.h
new file mode 100644
index 00000000000..96169c0e274
--- /dev/null
+++ b/chromium/device/fido/authenticator_get_info_response.h
@@ -0,0 +1,69 @@
+// 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_AUTHENTICATOR_GET_INFO_RESPONSE_H_
+#define DEVICE_FIDO_AUTHENTICATOR_GET_INFO_RESPONSE_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/fido/authenticator_supported_options.h"
+#include "device/fido/ctap_constants.h"
+
+namespace device {
+
+// Authenticator response for AuthenticatorGetInfo request that encapsulates
+// versions, options, AAGUID(Authenticator Attestation GUID), other
+// authenticator device information.
+// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticatorGetInfo
+class AuthenticatorGetInfoResponse {
+ public:
+ AuthenticatorGetInfoResponse(CtapDeviceResponseCode response_code,
+ std::vector<std::string> versions,
+ std::vector<uint8_t> aaguid);
+ AuthenticatorGetInfoResponse(AuthenticatorGetInfoResponse&& that);
+ AuthenticatorGetInfoResponse& operator=(AuthenticatorGetInfoResponse&& other);
+ ~AuthenticatorGetInfoResponse();
+
+ AuthenticatorGetInfoResponse& SetMaxMsgSize(uint8_t max_msg_size);
+ AuthenticatorGetInfoResponse& SetPinProtocols(
+ std::vector<uint8_t> pin_protocols);
+ AuthenticatorGetInfoResponse& SetExtensions(
+ std::vector<std::string> extensions);
+ AuthenticatorGetInfoResponse& SetOptions(
+ AuthenticatorSupportedOptions options);
+
+ CtapDeviceResponseCode response_code() const { return response_code_; }
+ const std::vector<std::string>& versions() { return versions_; }
+ const std::vector<uint8_t>& aaguid() const { return aaguid_; }
+ const base::Optional<uint8_t>& max_msg_size() const { return max_msg_size_; }
+ const base::Optional<std::vector<uint8_t>>& pin_protocol() const {
+ return pin_protocols_;
+ }
+ const base::Optional<std::vector<std::string>>& extensions() const {
+ return extensions_;
+ }
+ const base::Optional<AuthenticatorSupportedOptions>& options() const {
+ return options_;
+ }
+
+ private:
+ CtapDeviceResponseCode response_code_;
+ std::vector<std::string> versions_;
+ std::vector<uint8_t> aaguid_;
+ base::Optional<uint8_t> max_msg_size_;
+ base::Optional<std::vector<uint8_t>> pin_protocols_;
+ base::Optional<std::vector<std::string>> extensions_;
+ base::Optional<AuthenticatorSupportedOptions> options_;
+
+ DISALLOW_COPY_AND_ASSIGN(AuthenticatorGetInfoResponse);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_AUTHENTICATOR_GET_INFO_RESPONSE_H_
diff --git a/chromium/device/fido/authenticator_make_credential_response.cc b/chromium/device/fido/authenticator_make_credential_response.cc
new file mode 100644
index 00000000000..07fdf30f2f9
--- /dev/null
+++ b/chromium/device/fido/authenticator_make_credential_response.cc
@@ -0,0 +1,26 @@
+// 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/authenticator_make_credential_response.h"
+
+#include <utility>
+
+namespace device {
+
+AuthenticatorMakeCredentialResponse::AuthenticatorMakeCredentialResponse(
+ CtapDeviceResponseCode response_code,
+ std::vector<uint8_t> attestation_object)
+ : response_code_(response_code),
+ attestation_object_(std::move(attestation_object)) {}
+
+AuthenticatorMakeCredentialResponse::AuthenticatorMakeCredentialResponse(
+ AuthenticatorMakeCredentialResponse&& that) = default;
+
+AuthenticatorMakeCredentialResponse& AuthenticatorMakeCredentialResponse::
+operator=(AuthenticatorMakeCredentialResponse&& other) = default;
+
+AuthenticatorMakeCredentialResponse::~AuthenticatorMakeCredentialResponse() =
+ default;
+
+} // namespace device
diff --git a/chromium/device/fido/authenticator_make_credential_response.h b/chromium/device/fido/authenticator_make_credential_response.h
new file mode 100644
index 00000000000..5d25fa35103
--- /dev/null
+++ b/chromium/device/fido/authenticator_make_credential_response.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_AUTHENTICATOR_MAKE_CREDENTIAL_RESPONSE_H_
+#define DEVICE_FIDO_AUTHENTICATOR_MAKE_CREDENTIAL_RESPONSE_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "device/fido/ctap_constants.h"
+
+namespace device {
+
+// Attestation object which includes attestation format, authentication
+// data, and attestation statement returned by the authenticator as a response
+// to MakeCredential request.
+// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticatorMakeCredential
+class AuthenticatorMakeCredentialResponse {
+ public:
+ AuthenticatorMakeCredentialResponse(CtapDeviceResponseCode response_code,
+ std::vector<uint8_t> attestation_object);
+
+ AuthenticatorMakeCredentialResponse(
+ AuthenticatorMakeCredentialResponse&& that);
+ AuthenticatorMakeCredentialResponse& operator=(
+ AuthenticatorMakeCredentialResponse&& other);
+ ~AuthenticatorMakeCredentialResponse();
+
+ CtapDeviceResponseCode response_code() const { return response_code_; }
+ const std::vector<uint8_t>& attestation_object() const {
+ return attestation_object_;
+ }
+
+ private:
+ CtapDeviceResponseCode response_code_;
+ std::vector<uint8_t> attestation_object_;
+
+ DISALLOW_COPY_AND_ASSIGN(AuthenticatorMakeCredentialResponse);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_AUTHENTICATOR_MAKE_CREDENTIAL_RESPONSE_H_
diff --git a/chromium/device/fido/authenticator_supported_options.cc b/chromium/device/fido/authenticator_supported_options.cc
new file mode 100644
index 00000000000..4f6c3a988c0
--- /dev/null
+++ b/chromium/device/fido/authenticator_supported_options.cc
@@ -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.
+
+#include "device/fido/authenticator_supported_options.h"
+
+namespace device {
+
+AuthenticatorSupportedOptions::AuthenticatorSupportedOptions() = default;
+
+AuthenticatorSupportedOptions::AuthenticatorSupportedOptions(
+ AuthenticatorSupportedOptions&& other) = default;
+
+AuthenticatorSupportedOptions& AuthenticatorSupportedOptions::operator=(
+ AuthenticatorSupportedOptions&& other) = default;
+
+AuthenticatorSupportedOptions::~AuthenticatorSupportedOptions() = default;
+
+AuthenticatorSupportedOptions&
+AuthenticatorSupportedOptions::SetIsPlatformDevice(bool is_platform_device) {
+ is_platform_device_ = is_platform_device;
+ return *this;
+}
+
+AuthenticatorSupportedOptions&
+AuthenticatorSupportedOptions::SetSupportsResidentKey(
+ bool supports_resident_key) {
+ supports_resident_key_ = supports_resident_key;
+ return *this;
+}
+
+AuthenticatorSupportedOptions&
+AuthenticatorSupportedOptions::SetUserVerificationRequired(
+ bool user_verification_required) {
+ user_verification_required_ = user_verification_required;
+ return *this;
+}
+
+AuthenticatorSupportedOptions&
+AuthenticatorSupportedOptions::SetUserPresenceRequired(
+ bool user_presence_required) {
+ user_presence_required_ = user_presence_required;
+ return *this;
+}
+
+AuthenticatorSupportedOptions&
+AuthenticatorSupportedOptions::SetClientPinStored(bool client_pin_stored) {
+ client_pin_stored_ = client_pin_stored;
+ return *this;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/authenticator_supported_options.h b/chromium/device/fido/authenticator_supported_options.h
new file mode 100644
index 00000000000..973742de1af
--- /dev/null
+++ b/chromium/device/fido/authenticator_supported_options.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_AUTHENTICATOR_SUPPORTED_OPTIONS_H_
+#define DEVICE_FIDO_AUTHENTICATOR_SUPPORTED_OPTIONS_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "components/cbor/cbor_values.h"
+
+namespace device {
+
+// Represents CTAP device properties and capabilities received as a response to
+// AuthenticatorGetInfo command.
+class AuthenticatorSupportedOptions {
+ public:
+ AuthenticatorSupportedOptions();
+ AuthenticatorSupportedOptions(AuthenticatorSupportedOptions&& other);
+ AuthenticatorSupportedOptions& operator=(
+ AuthenticatorSupportedOptions&& other);
+ ~AuthenticatorSupportedOptions();
+
+ cbor::CBORValue ConvertToCBOR() const;
+ AuthenticatorSupportedOptions& SetIsPlatformDevice(bool is_platform_device);
+ AuthenticatorSupportedOptions& SetSupportsResidentKey(
+ bool supports_resident_key);
+ AuthenticatorSupportedOptions& SetUserVerificationRequired(
+ bool user_verification_required);
+ AuthenticatorSupportedOptions& SetUserPresenceRequired(
+ bool user_presence_required);
+ AuthenticatorSupportedOptions& SetClientPinStored(bool client_pin_stored);
+
+ bool is_platform_device() const { return is_platform_device_; }
+ bool supports_resident_key() const { return supports_resident_key_; }
+ bool user_verification_required() const {
+ return user_verification_required_;
+ }
+ bool user_presence_required() const { return user_presence_required_; }
+ const base::Optional<bool>& client_pin_stored() const {
+ return client_pin_stored_;
+ }
+
+ private:
+ // Indicates that the device is attached to the client and therefore can't be
+ // removed and used on another client.
+ bool is_platform_device_ = false;
+ // Indicates that the device is capable of storing keys on the device itself
+ // and therefore can satisfy the authenticatorGetAssertion request with
+ // allowList parameter not specified or empty.
+ bool supports_resident_key_ = false;
+ bool user_verification_required_ = false;
+ bool user_presence_required_ = true;
+ // Represents whether client pin in set and stored in device. Set as null
+ // optional if client pin capability is not supported by the authenticator.
+ base::Optional<bool> client_pin_stored_;
+
+ DISALLOW_COPY_AND_ASSIGN(AuthenticatorSupportedOptions);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_AUTHENTICATOR_SUPPORTED_OPTIONS_H_
diff --git a/chromium/device/fido/ctap_constants.cc b/chromium/device/fido/ctap_constants.cc
new file mode 100644
index 00000000000..60c111a8131
--- /dev/null
+++ b/chromium/device/fido/ctap_constants.cc
@@ -0,0 +1,25 @@
+// 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_constants.h"
+
+namespace device {
+
+const char kResidentKeyMapKey[] = "rk";
+const char kUserVerificationMapKey[] = "uv";
+const char kUserPresenceMapKey[] = "up";
+
+const size_t kHidPacketSize = 64;
+const uint32_t kHidBroadcastChannel = 0xffffffff;
+const size_t kHidInitPacketHeaderSize = 7;
+const size_t kHidContinuationPacketHeader = 5;
+const size_t kHidMaxPacketSize = 64;
+const size_t kHidInitPacketDataSize =
+ kHidMaxPacketSize - kHidInitPacketHeaderSize;
+const size_t kHidContinuationPacketDataSize =
+ kHidMaxPacketSize - kHidContinuationPacketHeader;
+const uint8_t kHidMaxLockSeconds = 10;
+const size_t kHidMaxMessageSize = 7609;
+
+} // namespace device
diff --git a/chromium/device/fido/ctap_constants.h b/chromium/device/fido/ctap_constants.h
new file mode 100644
index 00000000000..9f7bcb915d6
--- /dev/null
+++ b/chromium/device/fido/ctap_constants.h
@@ -0,0 +1,185 @@
+// 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_CONSTANTS_H_
+#define DEVICE_FIDO_CTAP_CONSTANTS_H_
+
+#include <stdint.h>
+
+#include <array>
+#include <vector>
+
+namespace device {
+
+// CTAP protocol device response code, as specified in
+// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticator-api
+enum class CtapDeviceResponseCode : uint8_t {
+ kSuccess = 0x00,
+ kCtap1ErrInvalidCommand = 0x01,
+ kCtap1ErrInvalidParameter = 0x02,
+ kCtap1ErrInvalidLength = 0x03,
+ kCtap1ErrInvalidSeq = 0x04,
+ kCtap1ErrTimeout = 0x05,
+ kCtap1ErrChannelBusy = 0x06,
+ kCtap1ErrLockRequired = 0x0A,
+ kCtap1ErrInvalidChannel = 0x0B,
+ kCtap2ErrCBORParsing = 0x10,
+ kCtap2ErrUnexpectedType = 0x11,
+ kCtap2ErrInvalidCBOR = 0x12,
+ kCtap2ErrInvalidCBORType = 0x13,
+ kCtap2ErrMissingParameter = 0x14,
+ kCtap2ErrLimitExceeded = 0x15,
+ kCtap2ErrUnsupportedExtension = 0x16,
+ kCtap2ErrTooManyElements = 0x17,
+ kCtap2ErrExtensionNotSupported = 0x18,
+ kCtap2ErrCredentialExcluded = 0x19,
+ kCtap2ErrCredentialNotValid = 0x20,
+ kCtap2ErrProcesssing = 0x21,
+ kCtap2ErrInvalidCredential = 0x22,
+ kCtap2ErrUserActionPending = 0x23,
+ kCtap2ErrOperationPending = 0x24,
+ kCtap2ErrNoOperations = 0x25,
+ kCtap2ErrUnsupportedAlgorithms = 0x26,
+ kCtap2ErrOperationDenied = 0x27,
+ kCtap2ErrKeyStoreFull = 0x28,
+ kCtap2ErrNotBusy = 0x29,
+ kCtap2ErrNoOperationPending = 0x2A,
+ kCtap2ErrUnsupportedOption = 0x2B,
+ kCtap2ErrInvalidOption = 0x2C,
+ kCtap2ErrKeepAliveCancel = 0x2D,
+ kCtap2ErrNoCredentials = 0x2E,
+ kCtap2ErrUserActionTimeout = 0x2F,
+ kCtap2ErrNotAllowed = 0x30,
+ kCtap2ErrPinInvalid = 0x31,
+ kCtap2ErrPinBlocked = 0x32,
+ kCtap2ErrPinAuthInvalid = 0x33,
+ kCtap2ErrPinAuthBlocked = 0x34,
+ kCtap2ErrPinNotSet = 0x35,
+ kCtap2ErrPinRequired = 0x36,
+ kCtap2ErrPinPolicyViolation = 0x37,
+ kCtap2ErrPinTokenExpired = 0x38,
+ kCtap2ErrRequestTooLarge = 0x39,
+ kCtap2ErrOther = 0x7F,
+ kCtap2ErrSpecLast = 0xDF,
+ kCtap2ErrExtensionFirst = 0xE0,
+ kCtap2ErrExtensionLast = 0xEF,
+ kCtap2ErrVendorFirst = 0xF0,
+ kCtap2ErrVendorLast = 0xFF
+};
+
+constexpr std::array<CtapDeviceResponseCode, 51> GetCtapResponseCodeList() {
+ return {CtapDeviceResponseCode::kSuccess,
+ CtapDeviceResponseCode::kCtap1ErrInvalidCommand,
+ CtapDeviceResponseCode::kCtap1ErrInvalidParameter,
+ CtapDeviceResponseCode::kCtap1ErrInvalidLength,
+ CtapDeviceResponseCode::kCtap1ErrInvalidSeq,
+ CtapDeviceResponseCode::kCtap1ErrTimeout,
+ CtapDeviceResponseCode::kCtap1ErrChannelBusy,
+ CtapDeviceResponseCode::kCtap1ErrLockRequired,
+ CtapDeviceResponseCode::kCtap1ErrInvalidChannel,
+ CtapDeviceResponseCode::kCtap2ErrCBORParsing,
+ CtapDeviceResponseCode::kCtap2ErrUnexpectedType,
+ CtapDeviceResponseCode::kCtap2ErrInvalidCBOR,
+ CtapDeviceResponseCode::kCtap2ErrInvalidCBORType,
+ CtapDeviceResponseCode::kCtap2ErrMissingParameter,
+ CtapDeviceResponseCode::kCtap2ErrLimitExceeded,
+ CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension,
+ CtapDeviceResponseCode::kCtap2ErrTooManyElements,
+ CtapDeviceResponseCode::kCtap2ErrExtensionNotSupported,
+ CtapDeviceResponseCode::kCtap2ErrCredentialExcluded,
+ CtapDeviceResponseCode::kCtap2ErrCredentialNotValid,
+ CtapDeviceResponseCode::kCtap2ErrProcesssing,
+ CtapDeviceResponseCode::kCtap2ErrInvalidCredential,
+ CtapDeviceResponseCode::kCtap2ErrUserActionPending,
+ CtapDeviceResponseCode::kCtap2ErrOperationPending,
+ CtapDeviceResponseCode::kCtap2ErrNoOperations,
+ CtapDeviceResponseCode::kCtap2ErrUnsupportedAlgorithms,
+ CtapDeviceResponseCode::kCtap2ErrOperationDenied,
+ CtapDeviceResponseCode::kCtap2ErrKeyStoreFull,
+ CtapDeviceResponseCode::kCtap2ErrNotBusy,
+ CtapDeviceResponseCode::kCtap2ErrNoOperationPending,
+ CtapDeviceResponseCode::kCtap2ErrUnsupportedOption,
+ CtapDeviceResponseCode::kCtap2ErrInvalidOption,
+ CtapDeviceResponseCode::kCtap2ErrKeepAliveCancel,
+ CtapDeviceResponseCode::kCtap2ErrNoCredentials,
+ CtapDeviceResponseCode::kCtap2ErrUserActionTimeout,
+ CtapDeviceResponseCode::kCtap2ErrNotAllowed,
+ CtapDeviceResponseCode::kCtap2ErrPinInvalid,
+ CtapDeviceResponseCode::kCtap2ErrPinBlocked,
+ CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid,
+ CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked,
+ CtapDeviceResponseCode::kCtap2ErrPinNotSet,
+ CtapDeviceResponseCode::kCtap2ErrPinRequired,
+ CtapDeviceResponseCode::kCtap2ErrPinPolicyViolation,
+ CtapDeviceResponseCode::kCtap2ErrPinTokenExpired,
+ CtapDeviceResponseCode::kCtap2ErrRequestTooLarge,
+ CtapDeviceResponseCode::kCtap2ErrOther,
+ CtapDeviceResponseCode::kCtap2ErrSpecLast,
+ CtapDeviceResponseCode::kCtap2ErrExtensionFirst,
+ CtapDeviceResponseCode::kCtap2ErrExtensionLast,
+ CtapDeviceResponseCode::kCtap2ErrVendorFirst,
+ CtapDeviceResponseCode::kCtap2ErrVendorLast};
+}
+
+// Commands supported by CTAPHID device as specified in
+// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#ctaphid-commands
+enum class CtapHidDeviceCommand : uint8_t {
+ kCtapHidMsg = 0x03,
+ kCtapHidCBOR = 0x10,
+ kCtapHidInit = 0x06,
+ kCtapHidPing = 0x01,
+ kCtapHidCancel = 0x11,
+ kCtapHidError = 0x3F,
+ kCtapHidKeepAlive = 0x3B,
+ kCtapHidWink = 0x08,
+ kCtapHidLock = 0x04,
+};
+
+constexpr std::array<CtapHidDeviceCommand, 9> GetCtapHidDeviceCommandList() {
+ return {CtapHidDeviceCommand::kCtapHidMsg,
+ CtapHidDeviceCommand::kCtapHidCBOR,
+ CtapHidDeviceCommand::kCtapHidInit,
+ CtapHidDeviceCommand::kCtapHidPing,
+ CtapHidDeviceCommand::kCtapHidCancel,
+ CtapHidDeviceCommand::kCtapHidError,
+ CtapHidDeviceCommand::kCtapHidKeepAlive,
+ CtapHidDeviceCommand::kCtapHidWink,
+ CtapHidDeviceCommand::kCtapHidLock};
+}
+
+// Authenticator API commands supported by CTAP devices, as specified in
+// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticator-api
+enum class CtapRequestCommand : uint8_t {
+ kAuthenticatorMakeCredential = 0x01,
+ kAuthenticatorGetAssertion = 0x02,
+ kAuthenticatorGetNextAssertion = 0x08,
+ kAuthenticatorCancel = 0x03,
+ kAuthenticatorGetInfo = 0x04,
+ kAuthenticatorClientPin = 0x06,
+ kAuthenticatorReset = 0x07,
+};
+
+enum class kCoseAlgorithmIdentifier : int { kCoseEs256 = -7 };
+
+// String key values for CTAP request optional parameters and
+// AuthenticatorGetInfo response.
+extern const char kResidentKeyMapKey[];
+extern const char kUserVerificationMapKey[];
+extern const char kUserPresenceMapKey[];
+
+// HID transport specific constants.
+extern const size_t kHidPacketSize;
+extern const uint32_t kHidBroadcastChannel;
+extern const size_t kHidInitPacketHeaderSize;
+extern const size_t kHidContinuationPacketHeader;
+extern const size_t kHidMaxPacketSize;
+extern const size_t kHidInitPacketDataSize;
+extern const size_t kHidContinuationPacketDataSize;
+extern const uint8_t kHidMaxLockSeconds;
+// Messages are limited to an initiation packet and 128 continuation packets.
+extern const size_t kHidMaxMessageSize;
+
+} // namespace device
+
+#endif // DEVICE_FIDO_CTAP_CONSTANTS_H_
diff --git a/chromium/device/fido/ctap_empty_authenticator_request.cc b/chromium/device/fido/ctap_empty_authenticator_request.cc
new file mode 100644
index 00000000000..ec96b0c9a35
--- /dev/null
+++ b/chromium/device/fido/ctap_empty_authenticator_request.cc
@@ -0,0 +1,19 @@
+// 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_empty_authenticator_request.h"
+
+#include "base/numerics/safe_conversions.h"
+
+namespace device {
+
+namespace internal {
+
+std::vector<uint8_t> CtapEmptyAuthenticatorRequest::Serialize() const {
+ return std::vector<uint8_t>{base::strict_cast<uint8_t>(cmd_)};
+}
+
+} // namespace internal
+
+} // namespace device
diff --git a/chromium/device/fido/ctap_empty_authenticator_request.h b/chromium/device/fido/ctap_empty_authenticator_request.h
new file mode 100644
index 00000000000..d7285fa299c
--- /dev/null
+++ b/chromium/device/fido/ctap_empty_authenticator_request.h
@@ -0,0 +1,70 @@
+// 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_EMPTY_AUTHENTICATOR_REQUEST_H_
+#define DEVICE_FIDO_CTAP_EMPTY_AUTHENTICATOR_REQUEST_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "device/fido/ctap_constants.h"
+
+namespace device {
+
+namespace internal {
+
+// Represents CTAP requests with empty parameters, including
+// AuthenticatorGetInfo, AuthenticatorCancel, AuthenticatorReset and
+// AuthenticatorGetNextAssertion commands.
+class CtapEmptyAuthenticatorRequest {
+ public:
+ CtapRequestCommand cmd() const { return cmd_; }
+ std::vector<uint8_t> Serialize() const;
+
+ protected:
+ explicit CtapEmptyAuthenticatorRequest(CtapRequestCommand cmd) : cmd_(cmd) {}
+
+ private:
+ CtapRequestCommand cmd_;
+};
+
+} // namespace internal
+
+class AuthenticatorGetNextAssertionRequest
+ : public internal::CtapEmptyAuthenticatorRequest {
+ public:
+ AuthenticatorGetNextAssertionRequest()
+ : CtapEmptyAuthenticatorRequest(
+ CtapRequestCommand::kAuthenticatorGetNextAssertion) {}
+};
+
+class AuthenticatorGetInfoRequest
+ : public internal::CtapEmptyAuthenticatorRequest {
+ public:
+ AuthenticatorGetInfoRequest()
+ : CtapEmptyAuthenticatorRequest(
+ CtapRequestCommand::kAuthenticatorGetInfo) {}
+};
+
+class AuthenticatorResetRequest
+ : public internal::CtapEmptyAuthenticatorRequest {
+ public:
+ AuthenticatorResetRequest()
+ : CtapEmptyAuthenticatorRequest(CtapRequestCommand::kAuthenticatorReset) {
+ }
+};
+
+class AuthenticatorCancelRequest
+ : public internal::CtapEmptyAuthenticatorRequest {
+ public:
+ AuthenticatorCancelRequest()
+ : CtapEmptyAuthenticatorRequest(
+ CtapRequestCommand::kAuthenticatorCancel) {}
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_CTAP_EMPTY_AUTHENTICATOR_REQUEST_H_
diff --git a/chromium/device/fido/ctap_get_assertion_request.cc b/chromium/device/fido/ctap_get_assertion_request.cc
new file mode 100644
index 00000000000..67e4eb8a358
--- /dev/null
+++ b/chromium/device/fido/ctap_get_assertion_request.cc
@@ -0,0 +1,98 @@
+// 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.
+
+#include "device/fido/ctap_get_assertion_request.h"
+
+#include <utility>
+
+#include "base/numerics/safe_conversions.h"
+#include "components/cbor/cbor_writer.h"
+#include "device/fido/ctap_constants.h"
+
+namespace device {
+
+CtapGetAssertionRequest::CtapGetAssertionRequest(
+ std::string rp_id,
+ std::vector<uint8_t> client_data_hash)
+ : rp_id_(std::move(rp_id)),
+ client_data_hash_(std::move(client_data_hash)) {}
+
+CtapGetAssertionRequest::CtapGetAssertionRequest(
+ CtapGetAssertionRequest&& that) = default;
+
+CtapGetAssertionRequest& CtapGetAssertionRequest::operator=(
+ CtapGetAssertionRequest&& other) = default;
+
+CtapGetAssertionRequest::~CtapGetAssertionRequest() = default;
+
+std::vector<uint8_t> CtapGetAssertionRequest::EncodeAsCBOR() const {
+ cbor::CBORValue::MapValue cbor_map;
+ cbor_map[cbor::CBORValue(1)] = cbor::CBORValue(rp_id_);
+ cbor_map[cbor::CBORValue(2)] = cbor::CBORValue(client_data_hash_);
+
+ if (allow_list_) {
+ cbor::CBORValue::ArrayValue allow_list_array;
+ for (const auto& descriptor : *allow_list_) {
+ allow_list_array.push_back(descriptor.ConvertToCBOR());
+ }
+ cbor_map[cbor::CBORValue(3)] = cbor::CBORValue(std::move(allow_list_array));
+ }
+
+ if (pin_auth_) {
+ cbor_map[cbor::CBORValue(6)] = cbor::CBORValue(*pin_auth_);
+ }
+
+ if (pin_protocol_) {
+ cbor_map[cbor::CBORValue(7)] = cbor::CBORValue(*pin_protocol_);
+ }
+
+ cbor::CBORValue::MapValue option_map;
+ option_map[cbor::CBORValue(kUserPresenceMapKey)] =
+ cbor::CBORValue(user_presence_required_);
+ option_map[cbor::CBORValue(kUserVerificationMapKey)] =
+ cbor::CBORValue(user_verification_required_);
+ cbor_map[cbor::CBORValue(7)] = cbor::CBORValue(std::move(option_map));
+
+ auto serialized_param =
+ cbor::CBORWriter::Write(cbor::CBORValue(std::move(cbor_map)));
+ DCHECK(serialized_param);
+
+ std::vector<uint8_t> cbor_request({base::strict_cast<uint8_t>(
+ CtapRequestCommand::kAuthenticatorGetAssertion)});
+ cbor_request.insert(cbor_request.end(), serialized_param->begin(),
+ serialized_param->end());
+ return cbor_request;
+}
+
+CtapGetAssertionRequest& CtapGetAssertionRequest::SetUserVerificationRequired(
+ bool user_verification_required) {
+ user_verification_required_ = user_verification_required;
+ return *this;
+}
+
+CtapGetAssertionRequest& CtapGetAssertionRequest::SetUserPresenceRequired(
+ bool user_presence_required) {
+ user_presence_required_ = user_presence_required;
+ return *this;
+}
+
+CtapGetAssertionRequest& CtapGetAssertionRequest::SetAllowList(
+ std::vector<PublicKeyCredentialDescriptor> allow_list) {
+ allow_list_ = std::move(allow_list);
+ return *this;
+}
+
+CtapGetAssertionRequest& CtapGetAssertionRequest::SetPinAuth(
+ std::vector<uint8_t> pin_auth) {
+ pin_auth_ = std::move(pin_auth);
+ return *this;
+}
+
+CtapGetAssertionRequest& CtapGetAssertionRequest::SetPinProtocol(
+ uint8_t pin_protocol) {
+ pin_protocol_ = pin_protocol;
+ return *this;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/ctap_get_assertion_request.h b/chromium/device/fido/ctap_get_assertion_request.h
new file mode 100644
index 00000000000..84a6a57f46b
--- /dev/null
+++ b/chromium/device/fido/ctap_get_assertion_request.h
@@ -0,0 +1,76 @@
+// 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.
+
+#ifndef DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_
+#define DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/fido/public_key_credential_descriptor.h"
+
+namespace device {
+
+// Object that encapsulates request parameters for AuthenticatorGetAssertion as
+// specified in the CTAP spec.
+// https://fidoalliance.org/specs/fido-v2.0-rd-20161004/fido-client-to-authenticator-protocol-v2.0-rd-20161004.html#authenticatorgetassertion
+class CtapGetAssertionRequest {
+ public:
+ CtapGetAssertionRequest(std::string rp_id,
+ std::vector<uint8_t> client_data_hash);
+ CtapGetAssertionRequest(CtapGetAssertionRequest&& that);
+ CtapGetAssertionRequest& operator=(CtapGetAssertionRequest&& other);
+ ~CtapGetAssertionRequest();
+
+ std::vector<uint8_t> EncodeAsCBOR() const;
+
+ CtapGetAssertionRequest& SetUserVerificationRequired(
+ bool user_verfication_required);
+ CtapGetAssertionRequest& SetUserPresenceRequired(bool user_presence_required);
+ CtapGetAssertionRequest& SetAllowList(
+ std::vector<PublicKeyCredentialDescriptor> allow_list);
+ CtapGetAssertionRequest& SetPinAuth(std::vector<uint8_t> pin_auth);
+ CtapGetAssertionRequest& SetPinProtocol(uint8_t pin_protocol);
+
+ const std::string& rp_id() const { return rp_id_; }
+ const std::vector<uint8_t>& client_data_hash() const {
+ return client_data_hash_;
+ }
+
+ bool user_verification_required() const {
+ return user_verification_required_;
+ }
+
+ bool user_presence_required() const { return user_presence_required_; }
+ const base::Optional<std::vector<PublicKeyCredentialDescriptor>>& allow_list()
+ const {
+ return allow_list_;
+ }
+
+ const base::Optional<std::vector<uint8_t>>& pin_auth() const {
+ return pin_auth_;
+ }
+
+ const base::Optional<uint8_t>& pin_protocol() const { return pin_protocol_; }
+
+ private:
+ std::string rp_id_;
+ std::vector<uint8_t> client_data_hash_;
+ bool user_verification_required_ = false;
+ bool user_presence_required_ = true;
+
+ base::Optional<std::vector<PublicKeyCredentialDescriptor>> allow_list_;
+ base::Optional<std::vector<uint8_t>> pin_auth_;
+ base::Optional<uint8_t> pin_protocol_;
+
+ DISALLOW_COPY_AND_ASSIGN(CtapGetAssertionRequest);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_
diff --git a/chromium/device/fido/ctap_make_credential_request.cc b/chromium/device/fido/ctap_make_credential_request.cc
new file mode 100644
index 00000000000..2ddee20b399
--- /dev/null
+++ b/chromium/device/fido/ctap_make_credential_request.cc
@@ -0,0 +1,104 @@
+// 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.
+
+#include "device/fido/ctap_make_credential_request.h"
+
+#include <utility>
+
+#include "base/numerics/safe_conversions.h"
+#include "components/cbor/cbor_writer.h"
+#include "device/fido/ctap_constants.h"
+
+namespace device {
+
+CtapMakeCredentialRequest::CtapMakeCredentialRequest(
+ std::vector<uint8_t> client_data_hash,
+ PublicKeyCredentialRpEntity rp,
+ PublicKeyCredentialUserEntity user,
+ PublicKeyCredentialParams public_key_credential_params)
+ : client_data_hash_(std::move(client_data_hash)),
+ rp_(std::move(rp)),
+ user_(std::move(user)),
+ public_key_credential_params_(std::move(public_key_credential_params)) {}
+
+CtapMakeCredentialRequest::CtapMakeCredentialRequest(
+ CtapMakeCredentialRequest&& that) = default;
+
+CtapMakeCredentialRequest& CtapMakeCredentialRequest::operator=(
+ CtapMakeCredentialRequest&& that) = default;
+
+CtapMakeCredentialRequest::~CtapMakeCredentialRequest() = default;
+
+std::vector<uint8_t> CtapMakeCredentialRequest::EncodeAsCBOR() const {
+ cbor::CBORValue::MapValue cbor_map;
+ cbor_map[cbor::CBORValue(1)] = cbor::CBORValue(client_data_hash_);
+ cbor_map[cbor::CBORValue(2)] = rp_.ConvertToCBOR();
+ cbor_map[cbor::CBORValue(3)] = user_.ConvertToCBOR();
+ cbor_map[cbor::CBORValue(4)] = public_key_credential_params_.ConvertToCBOR();
+ if (exclude_list_) {
+ cbor::CBORValue::ArrayValue exclude_list_array;
+ for (const auto& descriptor : *exclude_list_) {
+ exclude_list_array.push_back(descriptor.ConvertToCBOR());
+ }
+ cbor_map[cbor::CBORValue(5)] =
+ cbor::CBORValue(std::move(exclude_list_array));
+ }
+ if (pin_auth_) {
+ cbor_map[cbor::CBORValue(8)] = cbor::CBORValue(*pin_auth_);
+ }
+
+ if (pin_protocol_) {
+ cbor_map[cbor::CBORValue(9)] = cbor::CBORValue(*pin_protocol_);
+ }
+
+ cbor::CBORValue::MapValue option_map;
+ option_map[cbor::CBORValue(kResidentKeyMapKey)] =
+ cbor::CBORValue(resident_key_supported_);
+ option_map[cbor::CBORValue(kUserVerificationMapKey)] =
+ cbor::CBORValue(user_verification_required_);
+ cbor_map[cbor::CBORValue(7)] = cbor::CBORValue(std::move(option_map));
+
+ auto serialized_param =
+ cbor::CBORWriter::Write(cbor::CBORValue(std::move(cbor_map)));
+ DCHECK(serialized_param);
+
+ std::vector<uint8_t> cbor_request({base::strict_cast<uint8_t>(
+ CtapRequestCommand::kAuthenticatorMakeCredential)});
+ cbor_request.insert(cbor_request.end(), serialized_param->begin(),
+ serialized_param->end());
+ return cbor_request;
+}
+
+CtapMakeCredentialRequest&
+CtapMakeCredentialRequest::SetUserVerificationRequired(
+ bool user_verification_required) {
+ user_verification_required_ = user_verification_required;
+ return *this;
+}
+
+CtapMakeCredentialRequest& CtapMakeCredentialRequest::SetResidentKeySupported(
+ bool resident_key_supported) {
+ resident_key_supported_ = resident_key_supported;
+ return *this;
+}
+
+CtapMakeCredentialRequest& CtapMakeCredentialRequest::SetExcludeList(
+ std::vector<PublicKeyCredentialDescriptor> exclude_list) {
+ exclude_list_ = std::move(exclude_list);
+ return *this;
+}
+
+CtapMakeCredentialRequest& CtapMakeCredentialRequest::SetPinAuth(
+ std::vector<uint8_t> pin_auth) {
+ pin_auth_ = std::move(pin_auth);
+ return *this;
+}
+
+CtapMakeCredentialRequest& CtapMakeCredentialRequest::SetPinProtocol(
+ uint8_t pin_protocol) {
+ pin_protocol_ = pin_protocol;
+ return *this;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/ctap_make_credential_request.h b/chromium/device/fido/ctap_make_credential_request.h
new file mode 100644
index 00000000000..28fa3e10cd4
--- /dev/null
+++ b/chromium/device/fido/ctap_make_credential_request.h
@@ -0,0 +1,76 @@
+// 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.
+
+#ifndef DEVICE_FIDO_CTAP_MAKE_CREDENTIAL_REQUEST_H_
+#define DEVICE_FIDO_CTAP_MAKE_CREDENTIAL_REQUEST_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/fido/public_key_credential_descriptor.h"
+#include "device/fido/public_key_credential_params.h"
+#include "device/fido/public_key_credential_rp_entity.h"
+#include "device/fido/public_key_credential_user_entity.h"
+
+namespace device {
+
+// Object containing request parameters for AuthenticatorMakeCredential command
+// as specified in
+// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html
+class CtapMakeCredentialRequest {
+ public:
+ CtapMakeCredentialRequest(
+ std::vector<uint8_t> client_data_hash,
+ PublicKeyCredentialRpEntity rp,
+ PublicKeyCredentialUserEntity user,
+ PublicKeyCredentialParams public_key_credential_params);
+ CtapMakeCredentialRequest(CtapMakeCredentialRequest&& that);
+ CtapMakeCredentialRequest& operator=(CtapMakeCredentialRequest&& that);
+ ~CtapMakeCredentialRequest();
+
+ std::vector<uint8_t> EncodeAsCBOR() const;
+
+ CtapMakeCredentialRequest& SetUserVerificationRequired(
+ bool user_verfication_required);
+ CtapMakeCredentialRequest& SetResidentKeySupported(bool resident_key);
+ CtapMakeCredentialRequest& SetExcludeList(
+ std::vector<PublicKeyCredentialDescriptor> exclude_list);
+ CtapMakeCredentialRequest& SetPinAuth(std::vector<uint8_t> pin_auth);
+ CtapMakeCredentialRequest& SetPinProtocol(uint8_t pin_protocol);
+
+ const std::vector<uint8_t>& client_data_hash() const {
+ return client_data_hash_;
+ }
+ const PublicKeyCredentialRpEntity& rp() const { return rp_; }
+ const PublicKeyCredentialUserEntity user() const { return user_; }
+ const PublicKeyCredentialParams& public_key_credential_params() const {
+ return public_key_credential_params_;
+ }
+ bool user_verification_required() const {
+ return user_verification_required_;
+ }
+ bool resident_key_supported() const { return resident_key_supported_; }
+
+ private:
+ std::vector<uint8_t> client_data_hash_;
+ PublicKeyCredentialRpEntity rp_;
+ PublicKeyCredentialUserEntity user_;
+ PublicKeyCredentialParams public_key_credential_params_;
+ bool user_verification_required_ = false;
+ bool resident_key_supported_ = false;
+
+ base::Optional<std::vector<PublicKeyCredentialDescriptor>> exclude_list_;
+ base::Optional<std::vector<uint8_t>> pin_auth_;
+ base::Optional<uint8_t> pin_protocol_;
+
+ DISALLOW_COPY_AND_ASSIGN(CtapMakeCredentialRequest);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_CTAP_MAKE_CREDENTIAL_REQUEST_H_
diff --git a/chromium/device/fido/ctap_request_unittest.cc b/chromium/device/fido/ctap_request_unittest.cc
new file mode 100644
index 00000000000..1d1bd3fcc74
--- /dev/null
+++ b/chromium/device/fido/ctap_request_unittest.cc
@@ -0,0 +1,244 @@
+// 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.
+
+#include "device/fido/ctap_constants.h"
+#include "device/fido/ctap_empty_authenticator_request.h"
+#include "device/fido/ctap_get_assertion_request.h"
+#include "device/fido/ctap_make_credential_request.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+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
+ 0xa5, // map(5)
+ 0x01, // clientDataHash
+ 0x58, 0x20, // bytes(32)
+ 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,
+
+ 0x02, // unsigned(2) - rp
+ 0xa2, // map(2)
+ 0x62, // text(2)
+ 0x69, 0x64, // "id"
+ 0x68, // text(8)
+ // "acme.com"
+ 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x64, // text(4)
+ 0x6e, 0x61, 0x6d, 0x65, // "name"
+ 0x64, // text(4)
+ 0x41, 0x63, 0x6d, 0x65, // "Acme"
+
+ 0x03, // unsigned(3) - user
+ 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,
+
+ 0x04, // unsigned(4) - pubKeyCredParams
+ 0x82, // array(2)
+ 0xa2, // map(2)
+ 0x63, // text(3)
+ 0x61, 0x6c, 0x67, // "alg"
+ 0x07, // 7
+ 0x64, // text(4)
+ 0x74, 0x79, 0x70, 0x65, // "type"
+ 0x6a, // text(10)
+ // "public-key"
+ 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+ 0xa2, // map(2)
+ 0x63, // text(3)
+ 0x61, 0x6c, 0x67, // "alg"
+ 0x19, 0x01, 0x01, // 257
+ 0x64, // text(4)
+ 0x74, 0x79, 0x70, 0x65, // "type"
+ 0x6a, // text(10)
+ // "public-key"
+ 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+
+ 0x07, // unsigned(7) - options
+ 0xa2, // map(2)
+ 0x62, // text(2)
+ 0x72, 0x6b, // "rk"
+ 0xf5, // True(21)
+ 0x62, // text(2)
+ 0x75, 0x76, // "uv"
+ 0xf5 // True(21)
+ // clang-format on
+ };
+
+ PublicKeyCredentialRpEntity rp("acme.com");
+ rp.SetRpName("Acme");
+
+ PublicKeyCredentialUserEntity user(
+ std::vector<uint8_t>(kUserId, std::end(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)),
+ std::move(rp), std::move(user),
+ PublicKeyCredentialParams({{"public-key", 7}, {"public-key", 257}}));
+ auto serialized_data = make_credential_param.SetResidentKeySupported(true)
+ .SetUserVerificationRequired(true)
+ .EncodeAsCBOR();
+ EXPECT_THAT(serialized_data, testing::ElementsAreArray(kSerializedRequest));
+}
+
+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
+ 0xa4, // map(4)
+
+ 0x01, // rpId
+ 0x68, // text(8)
+ // "acme.com"
+ 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+
+ 0x02, // unsigned(2) - client data hash
+ 0x58, 0x20, // bytes(32)
+ 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,
+
+ 0x03, // unsigned(3) - allow list
+ 0x82, // array(2)
+ 0xa2, // map(2)
+ 0x62, // text(2)
+ 0x69, 0x64, // "id"
+ 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,
+
+ 0x64, // text(4)
+ 0x74, 0x79, 0x70, 0x65, // "type"
+ 0x6a, // text(10)
+ // "public-key"
+ 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+ 0xa2, // map(2)
+ 0x62, // text(2)
+ 0x69, 0x64, // "id"
+ 0x58, 0x32, // text(22)
+ // credential ID
+ 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, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03,
+ 0x64, // text(4)
+ 0x74, 0x79, 0x70, 0x65, // "type"
+ 0x6a, // text(10)
+ // "public-key"
+ 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+
+ 0x07, // unsigned(7) - options
+ 0xa2, // map(2)
+ 0x62, // text(2)
+ 0x75, 0x70, // "up"
+ 0xf5, // True(21)
+ 0x62, // text(2)
+ 0x75, 0x76, // "uv"
+ 0xf5 // True(21)
+
+ // clang-format on
+ };
+
+ CtapGetAssertionRequest get_assertion_req(
+ "acme.com",
+ std::vector<uint8_t>(kClientDataHash, std::end(kClientDataHash)));
+
+ std::vector<PublicKeyCredentialDescriptor> allowed_list;
+ allowed_list.push_back(PublicKeyCredentialDescriptor(
+ "public-key",
+ {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}));
+ allowed_list.push_back(PublicKeyCredentialDescriptor(
+ "public-key",
+ {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,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03}));
+
+ get_assertion_req.SetAllowList(std::move(allowed_list))
+ .SetUserPresenceRequired(true)
+ .SetUserVerificationRequired(true);
+
+ auto serialized_data = get_assertion_req.EncodeAsCBOR();
+ EXPECT_THAT(serialized_data, testing::ElementsAreArray(kSerializedRequest));
+}
+
+TEST(CTAPRequestTest, TestConstructCtapAuthenticatorRequestParam) {
+ static constexpr uint8_t kSerializedGetInfoCmd = 0x04;
+ static constexpr uint8_t kSerializedGetNextAssertionCmd = 0x08;
+ static constexpr uint8_t kSerializedCancelCmd = 0x03;
+ static constexpr uint8_t kSerializedResetCmd = 0x07;
+
+ EXPECT_THAT(AuthenticatorGetInfoRequest().Serialize(),
+ testing::ElementsAre(kSerializedGetInfoCmd));
+ EXPECT_THAT(AuthenticatorGetNextAssertionRequest().Serialize(),
+ testing::ElementsAre(kSerializedGetNextAssertionCmd));
+ EXPECT_THAT(AuthenticatorCancelRequest().Serialize(),
+ testing::ElementsAre(kSerializedCancelCmd));
+ EXPECT_THAT(AuthenticatorResetRequest().Serialize(),
+ testing::ElementsAre(kSerializedResetCmd));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/ctap_response_fuzzer.cc b/chromium/device/fido/ctap_response_fuzzer.cc
new file mode 100644
index 00000000000..068fa658451
--- /dev/null
+++ b/chromium/device/fido/ctap_response_fuzzer.cc
@@ -0,0 +1,45 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "base/at_exit.h"
+#include "base/i18n/icu_util.h"
+#include "device/fido/authenticator_get_assertion_response.h"
+#include "device/fido/authenticator_get_info_response.h"
+#include "device/fido/authenticator_make_credential_response.h"
+#include "device/fido/ctap_constants.h"
+#include "device/fido/device_response_converter.h"
+
+namespace device {
+
+// Creating a PublicKeyCredentialUserEntity from a CBOR value can involve URL
+// parsing, which relies on ICU for IDN handling. This is why ICU needs to be
+// initialized explicitly.
+// See: http://crbug/808412
+struct IcuEnvironment {
+ IcuEnvironment() { CHECK(base::i18n::InitializeICU()); }
+ // Used by ICU integration.
+ base::AtExitManager at_exit_manager;
+};
+
+IcuEnvironment* env = new IcuEnvironment();
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::vector<uint8_t> input(data, data + size);
+ device::ReadCTAPMakeCredentialResponse(
+ device::CtapDeviceResponseCode::kSuccess, input);
+ device::ReadCTAPGetAssertionResponse(device::CtapDeviceResponseCode::kSuccess,
+ input);
+ device::ReadCTAPGetInfoResponse(device::CtapDeviceResponseCode::kSuccess,
+ input);
+
+ return 0;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/ctap_response_fuzzer_corpus/get_assertion_response_corpus b/chromium/device/fido/ctap_response_fuzzer_corpus/get_assertion_response_corpus
new file mode 100644
index 00000000000..597e6c0646f
--- /dev/null
+++ b/chromium/device/fido/ctap_response_fuzzer_corpus/get_assertion_response_corpus
Binary files differ
diff --git a/chromium/device/fido/ctap_response_fuzzer_corpus/make_credential_response_corpus b/chromium/device/fido/ctap_response_fuzzer_corpus/make_credential_response_corpus
new file mode 100644
index 00000000000..448f92c1c35
--- /dev/null
+++ b/chromium/device/fido/ctap_response_fuzzer_corpus/make_credential_response_corpus
Binary files differ
diff --git a/chromium/device/fido/ctap_response_unittest.cc b/chromium/device/fido/ctap_response_unittest.cc
new file mode 100644
index 00000000000..c842da3916c
--- /dev/null
+++ b/chromium/device/fido/ctap_response_unittest.cc
@@ -0,0 +1,319 @@
+// 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 "components/cbor/cbor_reader.h"
+#include "components/cbor/cbor_values.h"
+#include "device/fido/authenticator_get_assertion_response.h"
+#include "device/fido/authenticator_make_credential_response.h"
+#include "device/fido/ctap_constants.h"
+#include "device/fido/device_response_converter.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+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(CTAPResponseTest, TestReadMakeCredentialResponse) {
+ const std::vector<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};
+
+ const std::vector<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};
+
+ const std::vector<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};
+
+ const std::vector<uint8_t> kDeviceResponse = {
+ // clang-format off
+ 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
+ };
+
+ auto make_credential_response = ReadCTAPMakeCredentialResponse(
+ CtapDeviceResponseCode::kSuccess, kDeviceResponse);
+ ASSERT_TRUE(make_credential_response);
+ EXPECT_EQ(make_credential_response->response_code(),
+ CtapDeviceResponseCode::kSuccess);
+
+ auto cbor_attestation_object =
+ cbor::CBORReader::Read(make_credential_response->attestation_object());
+ ASSERT_TRUE(cbor_attestation_object);
+ ASSERT_TRUE(cbor_attestation_object->is_map());
+
+ const auto& attestation_object_map = cbor_attestation_object->GetMap();
+ auto it = attestation_object_map.find(cbor::CBORValue("fmt"));
+ ASSERT_TRUE(it != attestation_object_map.end());
+ ASSERT_TRUE(it->second.is_string());
+ EXPECT_EQ(it->second.GetString(), "packed");
+
+ it = attestation_object_map.find(cbor::CBORValue("authData"));
+ ASSERT_TRUE(it != attestation_object_map.end());
+ ASSERT_TRUE(it->second.is_bytestring());
+ EXPECT_THAT(it->second.GetBytestring(), testing::ElementsAreArray(kAuthData));
+
+ it = attestation_object_map.find(cbor::CBORValue("attStmt"));
+ ASSERT_TRUE(it != attestation_object_map.end());
+ ASSERT_TRUE(it->second.is_map());
+
+ const auto& attestation_statement_map = it->second.GetMap();
+ auto attStmt_it = attestation_statement_map.find(cbor::CBORValue("alg"));
+
+ ASSERT_TRUE(attStmt_it != attestation_statement_map.end());
+ ASSERT_TRUE(attStmt_it->second.is_unsigned());
+ EXPECT_EQ(attStmt_it->second.GetUnsigned(), 7u);
+
+ 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));
+
+ attStmt_it = attestation_statement_map.find(cbor::CBORValue("x5c"));
+ ASSERT_TRUE(attStmt_it != attestation_statement_map.end());
+ const auto& certificate = attStmt_it->second;
+ 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));
+}
+
+// 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
+TEST(CTAPResponseTest, TestReadGetAssertionResponse) {
+ const std::vector<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};
+
+ const std::vector<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,
+ };
+
+ const std::vector<uint8_t> kDeviceResponse = {
+ // clang-format off
+ 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(
+ CtapDeviceResponseCode::kSuccess, kDeviceResponse);
+ ASSERT_TRUE(get_assertion_response);
+
+ EXPECT_EQ(get_assertion_response->response_code(),
+ CtapDeviceResponseCode::kSuccess);
+ ASSERT_TRUE(get_assertion_response->num_credentials());
+ EXPECT_EQ(*get_assertion_response->num_credentials(), 1u);
+
+ EXPECT_THAT(get_assertion_response->auth_data(),
+ testing::ElementsAreArray(kAuthData));
+ EXPECT_THAT(get_assertion_response->signature(),
+ testing::ElementsAreArray(kSignature));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/device_response_converter.cc b/chromium/device/fido/device_response_converter.cc
new file mode 100644
index 00000000000..050ff2fc5d1
--- /dev/null
+++ b/chromium/device/fido/device_response_converter.cc
@@ -0,0 +1,247 @@
+// 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/device_response_converter.h"
+
+#include <string>
+#include <utility>
+
+#include "base/numerics/safe_conversions.h"
+#include "base/optional.h"
+#include "base/stl_util.h"
+#include "components/cbor/cbor_reader.h"
+#include "components/cbor/cbor_writer.h"
+#include "device/fido/authenticator_supported_options.h"
+#include "device/fido/ctap_constants.h"
+
+namespace device {
+
+using CBOR = cbor::CBORValue;
+
+CtapDeviceResponseCode GetResponseCode(const std::vector<uint8_t>& buffer) {
+ if (buffer.empty())
+ return CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
+
+ auto code = static_cast<CtapDeviceResponseCode>(buffer[0]);
+ return base::ContainsValue(GetCtapResponseCodeList(), code)
+ ? code
+ : CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
+}
+
+// Decodes byte array response from authenticator to CBOR value object and
+// checks for correct encoding format. Then re-serialize the decoded CBOR value
+// to byte array in format specified by the WebAuthN spec (i.e the keys for
+// CBOR map value are converted from unsigned integers to string type.)
+base::Optional<AuthenticatorMakeCredentialResponse>
+ReadCTAPMakeCredentialResponse(CtapDeviceResponseCode response_code,
+ const std::vector<uint8_t>& buffer) {
+ base::Optional<CBOR> decoded_response = cbor::CBORReader::Read(buffer);
+
+ if (!decoded_response || !decoded_response->is_map())
+ return base::nullopt;
+
+ const auto& decoded_map = decoded_response->GetMap();
+ CBOR::MapValue response_map;
+
+ auto it = decoded_map.find(CBOR(1));
+ if (it == decoded_map.end() || !it->second.is_string())
+ return base::nullopt;
+
+ response_map[CBOR("fmt")] = it->second.Clone();
+
+ it = decoded_map.find(CBOR(2));
+ if (it == decoded_map.end() || !it->second.is_bytestring())
+ return base::nullopt;
+
+ response_map[CBOR("authData")] = it->second.Clone();
+
+ it = decoded_map.find(CBOR(3));
+ if (it == decoded_map.end() || !it->second.is_map())
+ return base::nullopt;
+
+ response_map[CBOR("attStmt")] = it->second.Clone();
+
+ auto attestation_object =
+ cbor::CBORWriter::Write(CBOR(std::move(response_map)));
+ if (!attestation_object)
+ return base::nullopt;
+
+ return AuthenticatorMakeCredentialResponse(response_code,
+ std::move(*attestation_object));
+}
+
+base::Optional<AuthenticatorGetAssertionResponse> ReadCTAPGetAssertionResponse(
+ CtapDeviceResponseCode response_code,
+ const std::vector<uint8_t>& buffer) {
+ base::Optional<CBOR> decoded_response = cbor::CBORReader::Read(buffer);
+
+ if (!decoded_response || !decoded_response->is_map())
+ return base::nullopt;
+
+ auto& response_map = decoded_response->GetMap();
+
+ auto it = response_map.find(CBOR(4));
+ if (it == response_map.end() || !it->second.is_map())
+ return base::nullopt;
+
+ auto user = PublicKeyCredentialUserEntity::CreateFromCBORValue(it->second);
+ if (!user)
+ return base::nullopt;
+
+ it = response_map.find(CBOR(2));
+ if (it == response_map.end() || !it->second.is_bytestring())
+ return base::nullopt;
+ auto auth_data = it->second.GetBytestring();
+
+ it = response_map.find(CBOR(3));
+ if (it == response_map.end() || !it->second.is_bytestring())
+ return base::nullopt;
+ auto signature = it->second.GetBytestring();
+
+ AuthenticatorGetAssertionResponse response(
+ response_code, std::move(auth_data), std::move(signature),
+ std::move(*user));
+
+ it = response_map.find(CBOR(1));
+ if (it != response_map.end()) {
+ auto descriptor =
+ PublicKeyCredentialDescriptor::CreateFromCBORValue(it->second);
+ if (!descriptor)
+ return base::nullopt;
+
+ response.SetCredential(std::move(*descriptor));
+ }
+
+ it = response_map.find(CBOR(5));
+ if (it != response_map.end()) {
+ if (!it->second.is_unsigned())
+ return base::nullopt;
+
+ response.SetNumCredentials(it->second.GetUnsigned());
+ }
+
+ return response;
+}
+
+base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
+ CtapDeviceResponseCode response_code,
+ const std::vector<uint8_t>& buffer) {
+ base::Optional<CBOR> decoded_response = cbor::CBORReader::Read(buffer);
+
+ if (!decoded_response || !decoded_response->is_map())
+ return base::nullopt;
+
+ const auto& response_map = decoded_response->GetMap();
+
+ auto it = response_map.find(CBOR(1));
+ if (it == response_map.end() || !it->second.is_array())
+ return base::nullopt;
+
+ std::vector<std::string> versions;
+ for (const auto& version : it->second.GetArray()) {
+ if (!version.is_string())
+ return base::nullopt;
+
+ versions.push_back(version.GetString());
+ }
+
+ it = response_map.find(CBOR(3));
+ if (it == response_map.end() || !it->second.is_bytestring())
+ return base::nullopt;
+
+ AuthenticatorGetInfoResponse response(response_code, std::move(versions),
+ it->second.GetBytestring());
+
+ it = response_map.find(CBOR(2));
+ if (it != response_map.end()) {
+ if (!it->second.is_array())
+ return base::nullopt;
+
+ std::vector<std::string> extensions;
+ for (const auto& extension : it->second.GetArray()) {
+ if (!extension.is_string())
+ return base::nullopt;
+
+ extensions.push_back(extension.GetString());
+ }
+ response.SetExtensions(std::move(extensions));
+ }
+
+ it = response_map.find(CBOR(4));
+ if (it != response_map.end()) {
+ if (!it->second.is_map())
+ return base::nullopt;
+
+ const auto& option_map = it->second.GetMap();
+ AuthenticatorSupportedOptions options;
+
+ auto option_map_it = option_map.find(CBOR("plat"));
+ if (option_map_it != option_map.end()) {
+ if (!option_map_it->second.is_bool())
+ return base::nullopt;
+
+ options.SetIsPlatformDevice(option_map_it->second.GetBool());
+ }
+
+ option_map_it = option_map.find(CBOR("rk"));
+ if (option_map_it != option_map.end()) {
+ if (!option_map_it->second.is_bool())
+ return base::nullopt;
+
+ options.SetSupportsResidentKey(option_map_it->second.GetBool());
+ }
+
+ option_map_it = option_map.find(CBOR("up"));
+ if (option_map_it != option_map.end()) {
+ if (!option_map_it->second.is_bool())
+ return base::nullopt;
+
+ options.SetUserPresenceRequired(option_map_it->second.GetBool());
+ }
+
+ option_map_it = option_map.find(CBOR("uv"));
+ if (option_map_it != option_map.end()) {
+ if (!option_map_it->second.is_bool())
+ return base::nullopt;
+
+ options.SetUserVerificationRequired(option_map_it->second.GetBool());
+ }
+
+ option_map_it = option_map.find(CBOR("client_pin"));
+ if (option_map_it != option_map.end()) {
+ if (!option_map_it->second.is_bool())
+ return base::nullopt;
+
+ options.SetClientPinStored(option_map_it->second.GetBool());
+ }
+ response.SetOptions(std::move(options));
+ }
+
+ it = response_map.find(CBOR(5));
+ if (it != response_map.end()) {
+ if (!it->second.is_unsigned())
+ return base::nullopt;
+
+ response.SetMaxMsgSize(it->second.GetUnsigned());
+ }
+
+ it = response_map.find(CBOR(6));
+ if (it != response_map.end()) {
+ if (!it->second.is_array())
+ return base::nullopt;
+
+ std::vector<uint8_t> supported_pin_protocols;
+ for (const auto& protocol : it->second.GetArray()) {
+ if (!protocol.is_unsigned())
+ return base::nullopt;
+
+ supported_pin_protocols.push_back(protocol.GetUnsigned());
+ }
+ response.SetPinProtocols(std::move(supported_pin_protocols));
+ }
+
+ return response;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/device_response_converter.h b/chromium/device/fido/device_response_converter.h
new file mode 100644
index 00000000000..d1627c610f3
--- /dev/null
+++ b/chromium/device/fido/device_response_converter.h
@@ -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.
+
+#ifndef DEVICE_FIDO_DEVICE_RESPONSE_CONVERTER_H_
+#define DEVICE_FIDO_DEVICE_RESPONSE_CONVERTER_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/optional.h"
+#include "device/fido/authenticator_get_assertion_response.h"
+#include "device/fido/authenticator_get_info_response.h"
+#include "device/fido/authenticator_make_credential_response.h"
+#include "device/fido/ctap_constants.h"
+
+// Converts response from authenticators to CTAPResponse objects. If the
+// response of the authenticator does not conform to format specified by the
+// CTAP protocol, null optional is returned.
+namespace device {
+
+// Parses response code from response received from the authenticator. If
+// unknown response code value is received, then CTAP2_ERR_OTHER is returned.
+CtapDeviceResponseCode GetResponseCode(const std::vector<uint8_t>& buffer);
+
+// De-serializes CBOR encoded response, checks for valid CBOR map formatting,
+// and converts response to AuthenticatorMakeCredentialResponse object with
+// CBOR map keys that conform to format of attestation object defined by the
+// WebAuthN spec : https://w3c.github.io/webauthn/#fig-attStructs
+base::Optional<AuthenticatorMakeCredentialResponse>
+ReadCTAPMakeCredentialResponse(CtapDeviceResponseCode response_code,
+ const std::vector<uint8_t>& buffer);
+
+// De-serializes CBOR encoded response to AuthenticatorGetAssertion /
+// AuthenticatorGetNextAssertion request to AuthenticatorGetAssertionResponse
+// object.
+base::Optional<AuthenticatorGetAssertionResponse> ReadCTAPGetAssertionResponse(
+ CtapDeviceResponseCode response_code,
+ const std::vector<uint8_t>& buffer);
+
+// De-serializes CBOR encoded response to AuthenticatorGetInfo request to
+// AuthenticatorGetInfoResponse object.
+base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
+ CtapDeviceResponseCode response_code,
+ const std::vector<uint8_t>& buffer);
+
+} // namespace device
+
+#endif // DEVICE_FIDO_DEVICE_RESPONSE_CONVERTER_H_
diff --git a/chromium/device/fido/ec_public_key.cc b/chromium/device/fido/ec_public_key.cc
new file mode 100644
index 00000000000..044e6117444
--- /dev/null
+++ b/chromium/device/fido/ec_public_key.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "device/fido/ec_public_key.h"
+
+#include <utility>
+
+#include "components/cbor/cbor_writer.h"
+#include "device/fido/u2f_parsing_utils.h"
+
+namespace device {
+
+namespace {
+// The key is located after the first byte of the response
+// (which is a reserved byte).
+// The uncompressed form consists of 65 bytes:
+// - a constant 0x04 prefix
+// - the 32-byte x coordinate
+// - the 32-byte y coordinate.
+constexpr size_t kHeaderLength = 2; // Account for reserved byte and prefix.
+constexpr size_t kKeyLength = 32;
+} // namespace
+
+// static
+std::unique_ptr<ECPublicKey> ECPublicKey::ExtractFromU2fRegistrationResponse(
+ std::string algorithm,
+ base::span<const uint8_t> u2f_data) {
+ std::vector<uint8_t> x =
+ u2f_parsing_utils::Extract(u2f_data, kHeaderLength, kKeyLength);
+
+ if (x.empty())
+ return nullptr;
+
+ std::vector<uint8_t> y = u2f_parsing_utils::Extract(
+ u2f_data, kHeaderLength + kKeyLength, kKeyLength);
+
+ if (y.empty())
+ return nullptr;
+
+ return std::make_unique<ECPublicKey>(std::move(algorithm), std::move(x),
+ std::move(y));
+}
+
+ECPublicKey::ECPublicKey(std::string algorithm,
+ std::vector<uint8_t> x,
+ std::vector<uint8_t> y)
+ : 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);
+}
+
+ECPublicKey::~ECPublicKey() = default;
+
+std::vector<uint8_t> ECPublicKey::EncodeAsCOSEKey() const {
+ cbor::CBORValue::MapValue map;
+ map[cbor::CBORValue(1)] = cbor::CBORValue(2);
+ map[cbor::CBORValue(3)] = cbor::CBORValue(-7);
+ map[cbor::CBORValue(-1)] = cbor::CBORValue(1);
+ map[cbor::CBORValue(-2)] = cbor::CBORValue(x_coordinate_);
+ map[cbor::CBORValue(-3)] = cbor::CBORValue(y_coordinate_);
+ return *cbor::CBORWriter::Write(cbor::CBORValue(std::move(map)));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/ec_public_key.h b/chromium/device/fido/ec_public_key.h
new file mode 100644
index 00000000000..39734fa457e
--- /dev/null
+++ b/chromium/device/fido/ec_public_key.h
@@ -0,0 +1,51 @@
+// 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.
+
+#ifndef DEVICE_FIDO_EC_PUBLIC_KEY_H_
+#define DEVICE_FIDO_EC_PUBLIC_KEY_H_
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "device/fido/public_key.h"
+
+namespace device {
+
+// An uncompressed ECPublicKey consisting of 64 bytes:
+// - the 32-byte x coordinate
+// - the 32-byte y coordinate.
+class ECPublicKey : public PublicKey {
+ public:
+ static std::unique_ptr<ECPublicKey> ExtractFromU2fRegistrationResponse(
+ std::string algorithm,
+ base::span<const uint8_t> u2f_data);
+
+ ECPublicKey(std::string algorithm,
+ std::vector<uint8_t> x,
+ std::vector<uint8_t> y);
+
+ ~ECPublicKey() override;
+
+ // Produces a key in COSE_key format, which is an integer-keyed CBOR map:
+ // { 1 ("kty") : 2 (the EC2 key id),
+ // 3 ("alg") : -7 (the ES256 COSEAlgorithmIdentifier),
+ // -1 ("crv"): 1 (the P-256 EC identifier),
+ // -2: x-coordinate,
+ // -3: y-coordinate }
+ std::vector<uint8_t> EncodeAsCOSEKey() const override;
+
+ private:
+ const std::vector<uint8_t> x_coordinate_;
+ const std::vector<uint8_t> y_coordinate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ECPublicKey);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_EC_PUBLIC_KEY_H_
diff --git a/chromium/device/fido/fake_hid_impl_for_testing.cc b/chromium/device/fido/fake_hid_impl_for_testing.cc
new file mode 100644
index 00000000000..f7984d59449
--- /dev/null
+++ b/chromium/device/fido/fake_hid_impl_for_testing.cc
@@ -0,0 +1,153 @@
+// 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.
+
+#include "device/fido/fake_hid_impl_for_testing.h"
+
+#include <utility>
+
+#include "base/optional.h"
+
+namespace device {
+
+MockHidConnection::MockHidConnection(
+ device::mojom::HidDeviceInfoPtr device,
+ device::mojom::HidConnectionRequest request,
+ std::vector<uint8_t> connection_channel_id)
+ : binding_(this, std::move(request)),
+ device_(std::move(device)),
+ connection_channel_id_(connection_channel_id) {}
+
+MockHidConnection::~MockHidConnection() {}
+
+void MockHidConnection::Read(ReadCallback callback) {
+ return ReadPtr(&callback);
+}
+
+void MockHidConnection::Write(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ WriteCallback callback) {
+ return WritePtr(report_id, buffer, &callback);
+}
+
+void MockHidConnection::GetFeatureReport(uint8_t report_id,
+ GetFeatureReportCallback callback) {
+ NOTREACHED();
+}
+
+void MockHidConnection::SendFeatureReport(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ SendFeatureReportCallback callback) {
+ NOTREACHED();
+}
+
+void MockHidConnection::SetNonce(base::span<uint8_t const> nonce) {
+ nonce_ = std::vector<uint8_t>(nonce.begin(), nonce.end());
+}
+
+bool FakeHidConnection::mock_connection_error_ = false;
+
+FakeHidConnection::FakeHidConnection(device::mojom::HidDeviceInfoPtr device)
+ : device_(std::move(device)) {}
+
+FakeHidConnection::~FakeHidConnection() = default;
+
+void FakeHidConnection::Read(ReadCallback callback) {
+ std::vector<uint8_t> buffer = {'F', 'a', 'k', 'e', ' ', 'H', 'i', 'd'};
+ std::move(callback).Run(true, 0, buffer);
+}
+
+void FakeHidConnection::Write(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ WriteCallback callback) {
+ if (mock_connection_error_) {
+ std::move(callback).Run(false);
+ return;
+ }
+
+ std::move(callback).Run(true);
+}
+
+void FakeHidConnection::GetFeatureReport(uint8_t report_id,
+ GetFeatureReportCallback callback) {
+ NOTREACHED();
+}
+
+void FakeHidConnection::SendFeatureReport(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ SendFeatureReportCallback callback) {
+ NOTREACHED();
+}
+
+FakeHidManager::FakeHidManager() = default;
+
+FakeHidManager::~FakeHidManager() = default;
+
+void FakeHidManager::AddBinding(mojo::ScopedMessagePipeHandle handle) {
+ bindings_.AddBinding(this,
+ device::mojom::HidManagerRequest(std::move(handle)));
+}
+
+void FakeHidManager::AddBinding2(device::mojom::HidManagerRequest request) {
+ bindings_.AddBinding(this, std::move(request));
+}
+
+void FakeHidManager::GetDevicesAndSetClient(
+ device::mojom::HidManagerClientAssociatedPtrInfo client,
+ GetDevicesCallback callback) {
+ GetDevices(std::move(callback));
+
+ device::mojom::HidManagerClientAssociatedPtr client_ptr;
+ client_ptr.Bind(std::move(client));
+ clients_.AddPtr(std::move(client_ptr));
+}
+
+void FakeHidManager::GetDevices(GetDevicesCallback callback) {
+ std::vector<device::mojom::HidDeviceInfoPtr> device_list;
+ for (auto& map_entry : devices_)
+ device_list.push_back(map_entry.second->Clone());
+
+ std::move(callback).Run(std::move(device_list));
+}
+
+void FakeHidManager::Connect(const std::string& device_guid,
+ ConnectCallback callback) {
+ auto device_it = devices_.find(device_guid);
+ auto connection_it = connections_.find(device_guid);
+ if (device_it == devices_.end() || connection_it == connections_.end()) {
+ std::move(callback).Run(nullptr);
+ return;
+ }
+
+ std::move(callback).Run(std::move(connection_it->second));
+}
+
+void FakeHidManager::AddDevice(device::mojom::HidDeviceInfoPtr device) {
+ device::mojom::HidDeviceInfo* device_info = device.get();
+ clients_.ForAllPtrs([device_info](device::mojom::HidManagerClient* client) {
+ client->DeviceAdded(device_info->Clone());
+ });
+
+ devices_[device->guid] = std::move(device);
+}
+
+void FakeHidManager::AddDeviceAndSetConnection(
+ device::mojom::HidDeviceInfoPtr device,
+ device::mojom::HidConnectionPtr connection) {
+ connections_[device->guid] = std::move(connection);
+ AddDevice(std::move(device));
+}
+
+void FakeHidManager::RemoveDevice(const std::string device_guid) {
+ auto it = devices_.find(device_guid);
+ if (it == devices_.end())
+ return;
+
+ device::mojom::HidDeviceInfo* device_info = it->second.get();
+ clients_.ForAllPtrs([device_info](device::mojom::HidManagerClient* client) {
+ client->DeviceRemoved(device_info->Clone());
+ });
+ devices_.erase(it);
+}
+
+} // namespace device
diff --git a/chromium/device/fido/fake_hid_impl_for_testing.h b/chromium/device/fido/fake_hid_impl_for_testing.h
new file mode 100644
index 00000000000..f9832c064a1
--- /dev/null
+++ b/chromium/device/fido/fake_hid_impl_for_testing.h
@@ -0,0 +1,112 @@
+// 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.
+
+#ifndef DEVICE_FIDO_FAKE_HID_IMPL_FOR_TESTING_H_
+#define DEVICE_FIDO_FAKE_HID_IMPL_FOR_TESTING_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/containers/span.h"
+#include "base/memory/ptr_util.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/device/public/mojom/hid.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace device {
+
+class MockHidConnection : public device::mojom::HidConnection {
+ public:
+ explicit MockHidConnection(device::mojom::HidDeviceInfoPtr device,
+ device::mojom::HidConnectionRequest request,
+ std::vector<uint8_t> connection_channel_id);
+
+ ~MockHidConnection() override;
+ MOCK_METHOD1(ReadPtr, void(ReadCallback* callback));
+ MOCK_METHOD3(WritePtr,
+ void(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ WriteCallback* callback));
+
+ void Read(ReadCallback callback) override;
+
+ void Write(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ WriteCallback callback) override;
+
+ void GetFeatureReport(uint8_t report_id,
+ GetFeatureReportCallback callback) override;
+ void SendFeatureReport(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ SendFeatureReportCallback callback) override;
+ void SetNonce(base::span<uint8_t const> nonce);
+
+ const std::vector<uint8_t>& connection_channel_id() const {
+ return connection_channel_id_;
+ }
+ const std::vector<uint8_t>& nonce() const { return nonce_; }
+
+ private:
+ mojo::Binding<device::mojom::HidConnection> binding_;
+ device::mojom::HidDeviceInfoPtr device_;
+ std::vector<uint8_t> nonce_;
+ std::vector<uint8_t> connection_channel_id_;
+};
+
+class FakeHidConnection : public device::mojom::HidConnection {
+ public:
+ explicit FakeHidConnection(device::mojom::HidDeviceInfoPtr device);
+
+ ~FakeHidConnection() override;
+
+ // device::mojom::HidConnection implemenation:
+ void Read(ReadCallback callback) override;
+ void Write(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ WriteCallback callback) override;
+ void GetFeatureReport(uint8_t report_id,
+ GetFeatureReportCallback callback) override;
+ void SendFeatureReport(uint8_t report_id,
+ const std::vector<uint8_t>& buffer,
+ SendFeatureReportCallback callback) override;
+
+ static bool mock_connection_error_;
+
+ private:
+ device::mojom::HidDeviceInfoPtr device_;
+};
+
+class FakeHidManager : public device::mojom::HidManager {
+ public:
+ FakeHidManager();
+ ~FakeHidManager() override;
+
+ // device::mojom::HidManager implementation:
+ void GetDevicesAndSetClient(
+ device::mojom::HidManagerClientAssociatedPtrInfo client,
+ GetDevicesCallback callback) override;
+ void GetDevices(GetDevicesCallback callback) override;
+ void Connect(const std::string& device_guid,
+ ConnectCallback callback) override;
+ void AddBinding(mojo::ScopedMessagePipeHandle handle);
+ void AddBinding2(device::mojom::HidManagerRequest request);
+ void AddDevice(device::mojom::HidDeviceInfoPtr device);
+ void AddDeviceAndSetConnection(device::mojom::HidDeviceInfoPtr device,
+ device::mojom::HidConnectionPtr connection);
+ void RemoveDevice(const std::string device_guid);
+
+ private:
+ std::map<std::string, device::mojom::HidDeviceInfoPtr> devices_;
+ std::map<std::string, device::mojom::HidConnectionPtr> connections_;
+ mojo::AssociatedInterfacePtrSet<device::mojom::HidManagerClient> clients_;
+ mojo::BindingSet<device::mojom::HidManager> bindings_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_FAKE_HID_IMPL_FOR_TESTING_H_
diff --git a/chromium/device/fido/fido_attestation_statement.cc b/chromium/device/fido/fido_attestation_statement.cc
new file mode 100644
index 00000000000..9198cc8ffc6
--- /dev/null
+++ b/chromium/device/fido/fido_attestation_statement.cc
@@ -0,0 +1,76 @@
+// 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.
+
+#include "device/fido/fido_attestation_statement.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "device/fido/u2f_parsing_utils.h"
+#include "third_party/boringssl/src/include/openssl/bytestring.h"
+
+namespace device {
+
+namespace {
+constexpr char kFidoFormatName[] = "fido-u2f";
+constexpr char kSignatureKey[] = "sig";
+constexpr char kX509CertKey[] = "x5c";
+} // namespace
+
+// static
+std::unique_ptr<FidoAttestationStatement>
+FidoAttestationStatement::CreateFromU2fRegisterResponse(
+ base::span<const uint8_t> u2f_data) {
+ CBS response, cert;
+ CBS_init(&response, u2f_data.data(), u2f_data.size());
+
+ // 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) ||
+ !CBS_get_u8(&response, &credential_length) ||
+ !CBS_skip(&response, credential_length) ||
+ !CBS_get_asn1_element(&response, &cert, CBS_ASN1_SEQUENCE)) {
+ DLOG(ERROR)
+ << "Invalid U2F response. Unable to unpack attestation statement.";
+ return nullptr;
+ }
+
+ std::vector<std::vector<uint8_t>> x509_certificates;
+ x509_certificates.emplace_back(CBS_data(&cert),
+ CBS_data(&cert) + CBS_len(&cert));
+
+ // The remaining bytes are the signature.
+ std::vector<uint8_t> signature(CBS_data(&response),
+ CBS_data(&response) + CBS_len(&response));
+ return std::make_unique<FidoAttestationStatement>(
+ std::move(signature), std::move(x509_certificates));
+}
+
+FidoAttestationStatement::FidoAttestationStatement(
+ std::vector<uint8_t> signature,
+ std::vector<std::vector<uint8_t>> x509_certificates)
+ : AttestationStatement(kFidoFormatName),
+ signature_(std::move(signature)),
+ x509_certificates_(std::move(x509_certificates)) {}
+
+FidoAttestationStatement::~FidoAttestationStatement() = default;
+
+cbor::CBORValue::MapValue FidoAttestationStatement::GetAsCBORMap() {
+ cbor::CBORValue::MapValue attestation_statement_map;
+ attestation_statement_map[cbor::CBORValue(kSignatureKey)] =
+ cbor::CBORValue(signature_);
+
+ std::vector<cbor::CBORValue> certificate_array;
+ for (const auto& cert : x509_certificates_) {
+ certificate_array.push_back(cbor::CBORValue(cert));
+ }
+
+ attestation_statement_map[cbor::CBORValue(kX509CertKey)] =
+ cbor::CBORValue(std::move(certificate_array));
+
+ return attestation_statement_map;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/fido_attestation_statement.h b/chromium/device/fido/fido_attestation_statement.h
new file mode 100644
index 00000000000..55e4ab7e3b5
--- /dev/null
+++ b/chromium/device/fido/fido_attestation_statement.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef DEVICE_FIDO_FIDO_ATTESTATION_STATEMENT_H_
+#define DEVICE_FIDO_FIDO_ATTESTATION_STATEMENT_H_
+
+#include <stdint.h>
+#include <memory>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "components/cbor/cbor_values.h"
+#include "device/fido/attestation_statement.h"
+
+namespace device {
+
+// https://www.w3.org/TR/2017/WD-webauthn-20170505/#fido-u2f-attestation
+class FidoAttestationStatement : public AttestationStatement {
+ public:
+ static std::unique_ptr<FidoAttestationStatement>
+ CreateFromU2fRegisterResponse(base::span<const uint8_t> u2f_data);
+
+ FidoAttestationStatement(std::vector<uint8_t> signature,
+ std::vector<std::vector<uint8_t>> x509_certificates);
+ ~FidoAttestationStatement() override;
+
+ // AttestationStatement overrides
+
+ // Produces a map in the following format:
+ // { "x5c": [ x509_certs bytes ], "sig": signature bytes ] }
+ cbor::CBORValue::MapValue GetAsCBORMap() override;
+
+ private:
+ const std::vector<uint8_t> signature_;
+ const std::vector<std::vector<uint8_t>> x509_certificates_;
+
+ DISALLOW_COPY_AND_ASSIGN(FidoAttestationStatement);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_FIDO_ATTESTATION_STATEMENT_H_
diff --git a/chromium/device/fido/fido_hid_message.cc b/chromium/device/fido/fido_hid_message.cc
new file mode 100644
index 00000000000..25db86c38c1
--- /dev/null
+++ b/chromium/device/fido/fido_hid_message.cc
@@ -0,0 +1,156 @@
+// 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.
+
+#include "device/fido/fido_hid_message.h"
+
+#include <algorithm>
+#include <numeric>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
+
+namespace device {
+
+// static
+std::unique_ptr<FidoHidMessage> FidoHidMessage::Create(
+ uint32_t channel_id,
+ CtapHidDeviceCommand type,
+ base::span<const uint8_t> data) {
+ if (data.size() > kHidMaxMessageSize)
+ return nullptr;
+
+ switch (type) {
+ case CtapHidDeviceCommand::kCtapHidPing:
+ break;
+ case CtapHidDeviceCommand::kCtapHidMsg:
+ case CtapHidDeviceCommand::kCtapHidCBOR: {
+ if (data.empty())
+ return nullptr;
+ break;
+ }
+
+ case CtapHidDeviceCommand::kCtapHidCancel:
+ case CtapHidDeviceCommand::kCtapHidWink: {
+ if (!data.empty())
+ return nullptr;
+ break;
+ }
+ case CtapHidDeviceCommand::kCtapHidLock: {
+ if (data.size() != 1 || data[0] > kHidMaxLockSeconds)
+ return nullptr;
+ break;
+ }
+ case CtapHidDeviceCommand::kCtapHidInit: {
+ if (data.size() != 8)
+ return nullptr;
+ break;
+ }
+ case CtapHidDeviceCommand::kCtapHidKeepAlive:
+ case CtapHidDeviceCommand::kCtapHidError:
+ if (data.size() != 1)
+ return nullptr;
+ }
+
+ return base::WrapUnique(new FidoHidMessage(channel_id, type, data));
+}
+
+// static
+std::unique_ptr<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;
+
+ auto init_packet = FidoHidInitPacket::CreateFromSerializedData(
+ serialized_data, &remaining_size);
+
+ if (init_packet == nullptr)
+ return nullptr;
+
+ return base::WrapUnique(
+ new FidoHidMessage(std::move(init_packet), remaining_size));
+}
+
+FidoHidMessage::~FidoHidMessage() = default;
+
+bool FidoHidMessage::MessageComplete() const {
+ return remaining_size_ == 0;
+}
+
+std::vector<uint8_t> FidoHidMessage::GetMessagePayload() const {
+ std::vector<uint8_t> data;
+ size_t data_size = 0;
+ for (const auto& packet : packets_) {
+ data_size += packet->GetPacketPayload().size();
+ }
+ data.reserve(data_size);
+
+ for (const auto& packet : packets_) {
+ const auto& packet_data = packet->GetPacketPayload();
+ data.insert(std::end(data), packet_data.cbegin(), packet_data.cend());
+ }
+
+ return data;
+}
+
+std::vector<uint8_t> FidoHidMessage::PopNextPacket() {
+ if (packets_.empty())
+ return {};
+
+ std::vector<uint8_t> data = packets_.front()->GetSerializedData();
+ packets_.pop_front();
+ return data;
+}
+
+bool FidoHidMessage::AddContinuationPacket(base::span<const uint8_t> buf) {
+ size_t remaining_size = remaining_size_;
+ auto cont_packet =
+ FidoHidContinuationPacket::CreateFromSerializedData(buf, &remaining_size);
+
+ // Reject packets with a different channel id.
+ if (!cont_packet || channel_id_ != cont_packet->channel_id())
+ return false;
+
+ remaining_size_ = remaining_size;
+ packets_.push_back(std::move(cont_packet));
+ return true;
+}
+
+size_t FidoHidMessage::NumPackets() const {
+ return packets_.size();
+}
+
+FidoHidMessage::FidoHidMessage(uint32_t channel_id,
+ CtapHidDeviceCommand type,
+ base::span<const uint8_t> data)
+ : channel_id_(channel_id) {
+ uint8_t sequence = 0;
+
+ auto init_data = data.first(std::min(kHidInitPacketDataSize, data.size()));
+ packets_.push_back(std::make_unique<FidoHidInitPacket>(
+ channel_id, type,
+ std::vector<uint8_t>(init_data.begin(), init_data.end()), data.size()));
+ data = data.subspan(init_data.size());
+
+ while (!data.empty()) {
+ auto cont_data =
+ data.first(std::min(kHidContinuationPacketDataSize, data.size()));
+ packets_.push_back(std::make_unique<FidoHidContinuationPacket>(
+ channel_id, sequence++,
+ std::vector<uint8_t>(cont_data.begin(), cont_data.end())));
+ data = data.subspan(cont_data.size());
+ }
+}
+
+FidoHidMessage::FidoHidMessage(std::unique_ptr<FidoHidInitPacket> init_packet,
+ size_t remaining_size)
+ : remaining_size_(remaining_size) {
+ channel_id_ = init_packet->channel_id();
+ cmd_ = init_packet->command();
+ packets_.push_back(std::move(init_packet));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/fido_hid_message.h b/chromium/device/fido/fido_hid_message.h
new file mode 100644
index 00000000000..cb182a3e4f9
--- /dev/null
+++ b/chromium/device/fido/fido_hid_message.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef DEVICE_FIDO_FIDO_HID_MESSAGE_H_
+#define DEVICE_FIDO_FIDO_HID_MESSAGE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <list>
+#include <memory>
+#include <vector>
+
+#include "base/containers/circular_deque.h"
+#include "base/containers/queue.h"
+#include "base/containers/span.h"
+#include "device/fido/ctap_constants.h"
+#include "device/fido/fido_hid_packet.h"
+
+namespace device {
+
+// Represents HID message format defined by the specification at
+// https://fidoalliance.org/specs/fido-v2.0-rd-20161004/fido-client-to-authenticator-protocol-v2.0-rd-20161004.html#message-and-packet-structure
+class FidoHidMessage {
+ public:
+ // Static functions to create CTAP/U2F HID commands.
+ static std::unique_ptr<FidoHidMessage> Create(uint32_t channel_id,
+ CtapHidDeviceCommand cmd,
+ base::span<const uint8_t> data);
+
+ // Reconstruct a message from serialized message data.
+ static std::unique_ptr<FidoHidMessage> CreateFromSerializedData(
+ base::span<const uint8_t> serialized_data);
+
+ ~FidoHidMessage();
+
+ bool MessageComplete() const;
+ std::vector<uint8_t> GetMessagePayload() const;
+ // Pop front of queue with next packet.
+ std::vector<uint8_t> PopNextPacket();
+ // Adds a continuation packet to the packet list, from the serialized
+ // response value.
+ bool AddContinuationPacket(base::span<const uint8_t> packet_buf);
+
+ size_t NumPackets() const;
+ uint32_t channel_id() const { return channel_id_; }
+ CtapHidDeviceCommand cmd() const { return cmd_; }
+ const base::circular_deque<std::unique_ptr<FidoHidPacket>>&
+ GetPacketsForTesting() const {
+ return packets_;
+ }
+
+ private:
+ FidoHidMessage(uint32_t channel_id,
+ CtapHidDeviceCommand type,
+ base::span<const uint8_t> data);
+ FidoHidMessage(std::unique_ptr<FidoHidInitPacket> init_packet,
+ size_t remaining_size);
+ uint32_t channel_id_ = kHidBroadcastChannel;
+ CtapHidDeviceCommand cmd_ = CtapHidDeviceCommand::kCtapHidMsg;
+ base::circular_deque<std::unique_ptr<FidoHidPacket>> packets_;
+ size_t remaining_size_ = 0;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_FIDO_HID_MESSAGE_H_
diff --git a/chromium/device/fido/fido_hid_message_fuzzer.cc b/chromium/device/fido/fido_hid_message_fuzzer.cc
new file mode 100644
index 00000000000..4357cb455b1
--- /dev/null
+++ b/chromium/device/fido/fido_hid_message_fuzzer.cc
@@ -0,0 +1,36 @@
+// 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.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "device/fido/fido_hid_message.h"
+
+namespace device {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ constexpr size_t kHidPacketSize = 64;
+ auto span = base::make_span(data, size);
+
+ auto packet = span.first(std::min(kHidPacketSize, span.size()));
+ auto msg = FidoHidMessage::CreateFromSerializedData(
+ std::vector<uint8_t>(packet.begin(), packet.end()));
+ if (!msg)
+ return 0;
+
+ span = span.subspan(packet.size());
+ while (!span.empty()) {
+ packet = span.first(std::min(kHidPacketSize, span.size()));
+ msg->AddContinuationPacket(
+ std::vector<uint8_t>(packet.begin(), packet.end()));
+ span = span.subspan(packet.size());
+ }
+ return 0;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/fido_hid_message_unittest.cc b/chromium/device/fido/fido_hid_message_unittest.cc
new file mode 100644
index 00000000000..940f98d1a77
--- /dev/null
+++ b/chromium/device/fido/fido_hid_message_unittest.cc
@@ -0,0 +1,200 @@
+// 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.
+
+#include "device/fido/fido_hid_message.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
+#include "device/fido/ctap_constants.h"
+#include "device/fido/fido_hid_packet.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+// Packets should be 64 bytes + 1 report ID byte.
+TEST(FidoHidMessageTest, TestPacketSize) {
+ uint32_t channel_id = 0x05060708;
+ std::vector<uint8_t> data;
+
+ auto init_packet = std::make_unique<FidoHidInitPacket>(
+ channel_id, CtapHidDeviceCommand::kCtapHidInit, data, data.size());
+ EXPECT_EQ(64u, init_packet->GetSerializedData().size());
+
+ auto continuation_packet =
+ std::make_unique<FidoHidContinuationPacket>(channel_id, 0, data);
+ EXPECT_EQ(64u, continuation_packet->GetSerializedData().size());
+}
+
+/*
+ * U2f Init Packets are of the format:
+ * Byte 0: 0
+ * Byte 1-4: Channel ID
+ * Byte 5: Command byte
+ * Byte 6-7: Big Endian size of data
+ * Byte 8-n: Data block
+ *
+ * Remaining buffer is padded with 0
+ */
+TEST(FidoHidMessageTest, TestPacketData) {
+ uint32_t channel_id = 0xF5060708;
+ std::vector<uint8_t> data{10, 11};
+ CtapHidDeviceCommand cmd = CtapHidDeviceCommand::kCtapHidWink;
+ auto init_packet =
+ std::make_unique<FidoHidInitPacket>(channel_id, cmd, data, data.size());
+ size_t index = 0;
+
+ std::vector<uint8_t> serialized = init_packet->GetSerializedData();
+ EXPECT_EQ((channel_id >> 24) & 0xff, serialized[index++]);
+ EXPECT_EQ((channel_id >> 16) & 0xff, serialized[index++]);
+ EXPECT_EQ((channel_id >> 8) & 0xff, serialized[index++]);
+ EXPECT_EQ(channel_id & 0xff, serialized[index++]);
+ EXPECT_EQ(base::strict_cast<uint8_t>(cmd), serialized[index++] & 0x7f);
+
+ EXPECT_EQ(data.size() >> 8, serialized[index++]);
+ EXPECT_EQ(data.size() & 0xff, serialized[index++]);
+ EXPECT_EQ(data[0], serialized[index++]);
+ EXPECT_EQ(data[1], serialized[index++]);
+ for (; index < serialized.size(); index++)
+ EXPECT_EQ(0, serialized[index]) << "mismatch at index " << index;
+}
+
+TEST(FidoHidMessageTest, TestPacketConstructors) {
+ uint32_t channel_id = 0x05060708;
+ std::vector<uint8_t> data{10, 11};
+ CtapHidDeviceCommand cmd = CtapHidDeviceCommand::kCtapHidWink;
+ auto orig_packet =
+ std::make_unique<FidoHidInitPacket>(channel_id, cmd, data, data.size());
+
+ size_t payload_length = static_cast<size_t>(orig_packet->payload_length());
+ std::vector<uint8_t> orig_data = orig_packet->GetSerializedData();
+
+ auto reconstructed_packet =
+ FidoHidInitPacket::CreateFromSerializedData(orig_data, &payload_length);
+ EXPECT_EQ(orig_packet->command(), reconstructed_packet->command());
+ EXPECT_EQ(orig_packet->payload_length(),
+ reconstructed_packet->payload_length());
+ EXPECT_THAT(orig_packet->GetPacketPayload(),
+ testing::ContainerEq(reconstructed_packet->GetPacketPayload()));
+
+ EXPECT_EQ(channel_id, reconstructed_packet->channel_id());
+
+ ASSERT_EQ(orig_packet->GetSerializedData().size(),
+ reconstructed_packet->GetSerializedData().size());
+ for (size_t index = 0; index < orig_packet->GetSerializedData().size();
+ ++index) {
+ EXPECT_EQ(orig_packet->GetSerializedData()[index],
+ reconstructed_packet->GetSerializedData()[index])
+ << "mismatch at index " << index;
+ }
+}
+
+TEST(FidoHidMessageTest, TestMaxLengthPacketConstructors) {
+ uint32_t channel_id = 0xAAABACAD;
+ std::vector<uint8_t> data;
+ for (size_t i = 0; i < kHidMaxMessageSize; ++i)
+ data.push_back(static_cast<uint8_t>(i % 0xff));
+
+ auto orig_msg = FidoHidMessage::Create(
+ channel_id, CtapHidDeviceCommand::kCtapHidMsg, data);
+ ASSERT_TRUE(orig_msg);
+
+ const auto& original_msg_packets = orig_msg->GetPacketsForTesting();
+ auto it = original_msg_packets.begin();
+ auto msg_data = (*it)->GetSerializedData();
+ auto new_msg = FidoHidMessage::CreateFromSerializedData(msg_data);
+ it++;
+
+ for (; it != original_msg_packets.end(); ++it) {
+ msg_data = (*it)->GetSerializedData();
+ new_msg->AddContinuationPacket(msg_data);
+ }
+
+ auto orig_it = original_msg_packets.begin();
+ const auto& new_msg_packets = new_msg->GetPacketsForTesting();
+ auto new_msg_it = new_msg_packets.begin();
+
+ for (; orig_it != original_msg_packets.end() &&
+ new_msg_it != new_msg_packets.end();
+ ++orig_it, ++new_msg_it) {
+ EXPECT_THAT((*orig_it)->GetPacketPayload(),
+ testing::ContainerEq((*new_msg_it)->GetPacketPayload()));
+
+ EXPECT_EQ((*orig_it)->channel_id(), (*new_msg_it)->channel_id());
+
+ ASSERT_EQ((*orig_it)->GetSerializedData().size(),
+ (*new_msg_it)->GetSerializedData().size());
+ for (size_t index = 0; index < (*orig_it)->GetSerializedData().size();
+ ++index) {
+ EXPECT_EQ((*orig_it)->GetSerializedData()[index],
+ (*new_msg_it)->GetSerializedData()[index])
+ << "mismatch at index " << index;
+ }
+ }
+}
+
+TEST(FidoHidMessageTest, TestMessagePartitoning) {
+ uint32_t channel_id = 0x01010203;
+ std::vector<uint8_t> data(kHidInitPacketDataSize + 1);
+ auto two_packet_message = FidoHidMessage::Create(
+ channel_id, CtapHidDeviceCommand::kCtapHidPing, data);
+ ASSERT_TRUE(two_packet_message);
+ EXPECT_EQ(2U, two_packet_message->NumPackets());
+
+ data.resize(kHidInitPacketDataSize);
+ auto one_packet_message = FidoHidMessage::Create(
+ channel_id, CtapHidDeviceCommand::kCtapHidPing, data);
+ ASSERT_TRUE(one_packet_message);
+ EXPECT_EQ(1U, one_packet_message->NumPackets());
+
+ data.resize(kHidInitPacketDataSize + kHidContinuationPacketDataSize + 1);
+ auto three_packet_message = FidoHidMessage::Create(
+ channel_id, CtapHidDeviceCommand::kCtapHidPing, data);
+ ASSERT_TRUE(three_packet_message);
+ EXPECT_EQ(3U, three_packet_message->NumPackets());
+}
+
+TEST(FidoHidMessageTest, TestMaxSize) {
+ uint32_t channel_id = 0x00010203;
+ std::vector<uint8_t> data(kHidMaxMessageSize + 1);
+ auto oversize_message = FidoHidMessage::Create(
+ channel_id, CtapHidDeviceCommand::kCtapHidPing, data);
+ EXPECT_EQ(nullptr, oversize_message);
+}
+
+TEST(FidoHidMessageTest, TestDeconstruct) {
+ uint32_t channel_id = 0x0A0B0C0D;
+ std::vector<uint8_t> data(kHidMaxMessageSize, 0x7F);
+ auto filled_message = FidoHidMessage::Create(
+ channel_id, CtapHidDeviceCommand::kCtapHidPing, data);
+ ASSERT_TRUE(filled_message);
+ EXPECT_THAT(data, testing::ContainerEq(filled_message->GetMessagePayload()));
+}
+
+TEST(FidoHidMessageTest, TestDeserialize) {
+ uint32_t channel_id = 0x0A0B0C0D;
+ std::vector<uint8_t> data(kHidMaxMessageSize);
+
+ auto orig_message = FidoHidMessage::Create(
+ channel_id, CtapHidDeviceCommand::kCtapHidPing, data);
+ ASSERT_TRUE(orig_message);
+
+ base::circular_deque<std::vector<uint8_t>> orig_list;
+ auto buf = orig_message->PopNextPacket();
+ orig_list.push_back(buf);
+
+ auto new_message = FidoHidMessage::CreateFromSerializedData(buf);
+ while (!new_message->MessageComplete()) {
+ buf = orig_message->PopNextPacket();
+ orig_list.push_back(buf);
+ new_message->AddContinuationPacket(buf);
+ }
+
+ while (!(buf = new_message->PopNextPacket()).empty()) {
+ EXPECT_EQ(buf, orig_list.front());
+ orig_list.pop_front();
+ }
+}
+
+} // namespace device
diff --git a/chromium/device/fido/fido_hid_packet.cc b/chromium/device/fido/fido_hid_packet.cc
new file mode 100644
index 00000000000..82063ffa4d8
--- /dev/null
+++ b/chromium/device/fido/fido_hid_packet.cc
@@ -0,0 +1,142 @@
+// 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.
+
+#include "device/fido/fido_hid_packet.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/stl_util.h"
+
+namespace device {
+
+FidoHidPacket::FidoHidPacket(std::vector<uint8_t> data, uint32_t channel_id)
+ : data_(std::move(data)), channel_id_(channel_id) {}
+
+FidoHidPacket::FidoHidPacket() = default;
+
+FidoHidPacket::~FidoHidPacket() = default;
+
+// static
+std::unique_ptr<FidoHidInitPacket> FidoHidInitPacket::CreateFromSerializedData(
+ base::span<const uint8_t> serialized,
+ size_t* remaining_size) {
+ if (!remaining_size || serialized.size() != kHidPacketSize)
+ return nullptr;
+
+ size_t index = 0;
+ auto channel_id = (serialized[index++] & 0xff) << 24;
+ channel_id |= (serialized[index++] & 0xff) << 16;
+ channel_id |= (serialized[index++] & 0xff) << 8;
+ channel_id |= serialized[index++] & 0xff;
+
+ auto command = static_cast<CtapHidDeviceCommand>(serialized[index++] & 0x7f);
+ if (!base::ContainsValue(GetCtapHidDeviceCommandList(), command))
+ return nullptr;
+
+ uint16_t payload_size = serialized[index++] << 8;
+ payload_size |= serialized[index++];
+
+ // Check to see if payload is less than maximum size and padded with 0s.
+ uint16_t data_size =
+ std::min(payload_size, static_cast<uint16_t>(kHidPacketSize - index));
+
+ // Update remaining size to determine the payload size of follow on packets.
+ *remaining_size = payload_size - data_size;
+
+ auto data = std::vector<uint8_t>(serialized.begin() + index,
+ serialized.begin() + index + data_size);
+
+ return std::make_unique<FidoHidInitPacket>(channel_id, command,
+ std::move(data), payload_size);
+}
+
+// U2F Initialization packet is defined as:
+// Offset Length
+// 0 4 Channel ID
+// 4 1 Command ID
+// 5 1 High order packet payload size
+// 6 1 Low order packet payload size
+// 7 (s-7) Payload data
+FidoHidInitPacket::FidoHidInitPacket(uint32_t channel_id,
+ CtapHidDeviceCommand cmd,
+ std::vector<uint8_t> data,
+ uint16_t payload_length)
+ : FidoHidPacket(std::move(data), channel_id),
+ command_(cmd),
+ payload_length_(payload_length) {}
+
+FidoHidInitPacket::~FidoHidInitPacket() = default;
+
+std::vector<uint8_t> FidoHidInitPacket::GetSerializedData() const {
+ std::vector<uint8_t> serialized;
+ serialized.reserve(kHidPacketSize);
+ serialized.push_back((channel_id_ >> 24) & 0xff);
+ serialized.push_back((channel_id_ >> 16) & 0xff);
+ serialized.push_back((channel_id_ >> 8) & 0xff);
+ serialized.push_back(channel_id_ & 0xff);
+ serialized.push_back(base::strict_cast<uint8_t>(command_) | 0x80);
+ serialized.push_back((payload_length_ >> 8) & 0xff);
+ serialized.push_back(payload_length_ & 0xff);
+ serialized.insert(serialized.end(), data_.begin(), data_.end());
+ serialized.resize(kHidPacketSize, 0);
+
+ return serialized;
+}
+
+// static
+std::unique_ptr<FidoHidContinuationPacket>
+FidoHidContinuationPacket::CreateFromSerializedData(
+ base::span<const uint8_t> serialized,
+ size_t* remaining_size) {
+ if (!remaining_size || serialized.size() != kHidPacketSize)
+ return nullptr;
+
+ size_t index = 0;
+ auto channel_id = (serialized[index++] & 0xff) << 24;
+ channel_id |= (serialized[index++] & 0xff) << 16;
+ channel_id |= (serialized[index++] & 0xff) << 8;
+ channel_id |= serialized[index++] & 0xff;
+ auto sequence = serialized[index++];
+
+ // Check to see if packet payload is less than maximum size and padded with
+ // 0s.
+ size_t data_size = std::min(*remaining_size, kHidPacketSize - index);
+ *remaining_size -= data_size;
+ auto data = std::vector<uint8_t>(serialized.begin() + index,
+ serialized.begin() + index + data_size);
+
+ return std::make_unique<FidoHidContinuationPacket>(channel_id, sequence,
+ std::move(data));
+}
+
+// U2F Continuation packet is defined as:
+// Offset Length
+// 0 4 Channel ID
+// 4 1 Packet sequence 0x00..0x7f
+// 5 (s-5) Payload data
+FidoHidContinuationPacket::FidoHidContinuationPacket(const uint32_t channel_id,
+ const uint8_t sequence,
+ std::vector<uint8_t> data)
+ : FidoHidPacket(std::move(data), channel_id), sequence_(sequence) {}
+
+FidoHidContinuationPacket::~FidoHidContinuationPacket() = default;
+
+std::vector<uint8_t> FidoHidContinuationPacket::GetSerializedData() const {
+ std::vector<uint8_t> serialized;
+ serialized.reserve(kHidPacketSize);
+ serialized.push_back((channel_id_ >> 24) & 0xff);
+ serialized.push_back((channel_id_ >> 16) & 0xff);
+ serialized.push_back((channel_id_ >> 8) & 0xff);
+ serialized.push_back(channel_id_ & 0xff);
+ serialized.push_back(sequence_);
+ serialized.insert(serialized.end(), data_.begin(), data_.end());
+ serialized.resize(kHidPacketSize, 0);
+
+ return serialized;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/fido_hid_packet.h b/chromium/device/fido/fido_hid_packet.h
new file mode 100644
index 00000000000..c8d9d89811c
--- /dev/null
+++ b/chromium/device/fido/fido_hid_packet.h
@@ -0,0 +1,104 @@
+// 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.
+
+#ifndef DEVICE_FIDO_FIDO_HID_PACKET_H_
+#define DEVICE_FIDO_FIDO_HID_PACKET_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "device/fido/ctap_constants.h"
+
+namespace device {
+
+// HID Packets are defined by the specification at
+// https://fidoalliance.org/specs/fido-v2.0-rd-20161004/fido-client-to-authenticator-protocol-v2.0-rd-20161004.html#message-and-packet-structure
+// Packets are one of two types, initialization packets and continuation
+// packets. HID Packets have header information and a payload. If a
+// FidoHidInitPacket cannot store the entire payload, further payload
+// information is stored in HidContinuationPackets.
+class FidoHidPacket {
+ public:
+ FidoHidPacket(std::vector<uint8_t> data, uint32_t channel_id);
+ virtual ~FidoHidPacket();
+
+ virtual std::vector<uint8_t> GetSerializedData() const = 0;
+ const std::vector<uint8_t>& GetPacketPayload() const { return data_; }
+ uint32_t channel_id() const { return channel_id_; }
+
+ protected:
+ FidoHidPacket();
+
+ std::vector<uint8_t> data_;
+ uint32_t channel_id_ = kHidBroadcastChannel;
+
+ private:
+ friend class HidMessage;
+};
+
+// FidoHidInitPacket, based on the CTAP specification consists of a header with
+// data that is serialized into a IOBuffer. A channel identifier is allocated by
+// the CTAP device to ensure its system-wide uniqueness. Command identifiers
+// determine the type of message the packet corresponds to. Payload length
+// is the length of the entire message payload, and the data is only the portion
+// of the payload that will fit into the HidInitPacket.
+class FidoHidInitPacket : public FidoHidPacket {
+ public:
+ // Creates a packet from the serialized data of an initialization packet. As
+ // this is the first packet, the payload length of the entire message will be
+ // included within the serialized data. Remaining size will be returned to
+ // inform the callee how many additional packets to expect.
+ static std::unique_ptr<FidoHidInitPacket> CreateFromSerializedData(
+ base::span<const uint8_t> serialized,
+ size_t* remaining_size);
+
+ FidoHidInitPacket(uint32_t channel_id,
+ CtapHidDeviceCommand cmd,
+ std::vector<uint8_t> data,
+ uint16_t payload_length);
+ ~FidoHidInitPacket() final;
+
+ std::vector<uint8_t> GetSerializedData() const final;
+ CtapHidDeviceCommand command() const { return command_; }
+ uint16_t payload_length() const { return payload_length_; }
+
+ private:
+ CtapHidDeviceCommand command_;
+ uint16_t payload_length_;
+};
+
+// FidoHidContinuationPacket, based on the CTAP Specification consists of a
+// header with data that is serialized into an IOBuffer. The channel identifier
+// will be identical to the identifier in all other packets of the message. The
+// packet sequence will be the sequence number of this particular packet, from
+// 0x00 to 0x7f.
+class FidoHidContinuationPacket : public FidoHidPacket {
+ public:
+ // Creates a packet from the serialized data of a continuation packet. As an
+ // HidInitPacket would have arrived earlier with the total payload size,
+ // the remaining size should be passed to inform the packet of how much data
+ // to expect.
+ static std::unique_ptr<FidoHidContinuationPacket> CreateFromSerializedData(
+ base::span<const uint8_t> serialized,
+ size_t* remaining_size);
+
+ FidoHidContinuationPacket(uint32_t channel_id,
+ uint8_t sequence,
+ std::vector<uint8_t> data);
+ ~FidoHidContinuationPacket() final;
+
+ std::vector<uint8_t> GetSerializedData() const final;
+ uint8_t sequence() const { return sequence_; }
+
+ private:
+ uint8_t sequence_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_FIDO_HID_PACKET_H_
diff --git a/chromium/device/fido/mock_u2f_ble_connection.cc b/chromium/device/fido/mock_u2f_ble_connection.cc
new file mode 100644
index 00000000000..122cf8200aa
--- /dev/null
+++ b/chromium/device/fido/mock_u2f_ble_connection.cc
@@ -0,0 +1,37 @@
+// 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.
+
+#include "device/fido/mock_u2f_ble_connection.h"
+
+#include <utility>
+
+namespace device {
+
+MockU2fBleConnection::MockU2fBleConnection(std::string device_address)
+ : U2fBleConnection(std::move(device_address)) {}
+
+MockU2fBleConnection::~MockU2fBleConnection() = default;
+
+void MockU2fBleConnection::ReadControlPointLength(
+ ControlPointLengthCallback callback) {
+ ReadControlPointLengthPtr(&callback);
+}
+
+void MockU2fBleConnection::ReadServiceRevisions(
+ ServiceRevisionsCallback callback) {
+ ReadServiceRevisionsPtr(&callback);
+}
+
+void MockU2fBleConnection::WriteControlPoint(const std::vector<uint8_t>& data,
+ WriteCallback callback) {
+ WriteControlPointPtr(data, &callback);
+}
+
+void MockU2fBleConnection::WriteServiceRevision(
+ ServiceRevision service_revision,
+ WriteCallback callback) {
+ WriteServiceRevisionPtr(service_revision, &callback);
+}
+
+} // namespace device
diff --git a/chromium/device/fido/mock_u2f_ble_connection.h b/chromium/device/fido/mock_u2f_ble_connection.h
new file mode 100644
index 00000000000..773d73dc759
--- /dev/null
+++ b/chromium/device/fido/mock_u2f_ble_connection.h
@@ -0,0 +1,52 @@
+// 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.
+
+#ifndef DEVICE_FIDO_MOCK_U2F_BLE_CONNECTION_H_
+#define DEVICE_FIDO_MOCK_U2F_BLE_CONNECTION_H_
+
+#include <string>
+#include <vector>
+
+#include "device/fido/u2f_ble_connection.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace device {
+
+class MockU2fBleConnection : public U2fBleConnection {
+ public:
+ explicit MockU2fBleConnection(std::string device_address);
+ ~MockU2fBleConnection() override;
+
+ MOCK_METHOD0(Connect, void());
+ // GMock cannot mock a method taking a move-only type.
+ // TODO(https://crbug.com/729950): Remove these workarounds once support for
+ // move-only types is added to GMock.
+ MOCK_METHOD1(ReadControlPointLengthPtr, void(ControlPointLengthCallback* cb));
+ MOCK_METHOD1(ReadServiceRevisionsPtr, void(ServiceRevisionsCallback* cb));
+ MOCK_METHOD2(WriteControlPointPtr,
+ void(const std::vector<uint8_t>& data, WriteCallback* cb));
+ MOCK_METHOD2(WriteServiceRevisionPtr,
+ void(ServiceRevision service_revision, WriteCallback* cb));
+
+ void ReadControlPointLength(ControlPointLengthCallback cb) override;
+ void ReadServiceRevisions(ServiceRevisionsCallback cb) override;
+ void WriteControlPoint(const std::vector<uint8_t>& data,
+ WriteCallback cb) override;
+ void WriteServiceRevision(ServiceRevision service_revision,
+ WriteCallback cb) override;
+
+ ConnectionStatusCallback& connection_status_callback() {
+ return connection_status_callback_;
+ }
+
+ ReadCallback& read_callback() { return read_callback_; }
+
+ private:
+ ConnectionStatusCallback connection_status_callback_;
+ ReadCallback read_callback_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_MOCK_U2F_BLE_CONNECTION_H_
diff --git a/chromium/device/fido/mock_u2f_device.cc b/chromium/device/fido/mock_u2f_device.cc
new file mode 100644
index 00000000000..477cc02f11a
--- /dev/null
+++ b/chromium/device/fido/mock_u2f_device.cc
@@ -0,0 +1,84 @@
+// 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.
+
+#include "device/fido/mock_u2f_device.h"
+
+#include <utility>
+
+#include "device/fido/u2f_response_test_data.h"
+
+namespace device {
+
+MockU2fDevice::MockU2fDevice() : weak_factory_(this) {}
+
+MockU2fDevice::~MockU2fDevice() = default;
+
+void MockU2fDevice::TryWink(WinkCallback cb) {
+ TryWinkRef(cb);
+}
+
+void MockU2fDevice::DeviceTransact(std::vector<uint8_t> command,
+ DeviceCallback cb) {
+ DeviceTransactPtr(std::move(command), cb);
+}
+
+// static
+void MockU2fDevice::NotSatisfied(const std::vector<uint8_t>& cmd,
+ DeviceCallback& cb) {
+ std::move(cb).Run(true,
+ std::make_unique<U2fApduResponse>(
+ std::vector<uint8_t>(),
+ U2fApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED));
+}
+
+// static
+void MockU2fDevice::WrongData(const std::vector<uint8_t>& cmd,
+ DeviceCallback& cb) {
+ std::move(cb).Run(true, std::make_unique<U2fApduResponse>(
+ std::vector<uint8_t>(),
+ U2fApduResponse::Status::SW_WRONG_DATA));
+}
+
+// static
+void MockU2fDevice::NoErrorSign(const std::vector<uint8_t>& cmd,
+ DeviceCallback& cb) {
+ std::move(cb).Run(true, std::make_unique<U2fApduResponse>(
+ std::vector<uint8_t>(
+ std::begin(test_data::kTestU2fSignResponse),
+ std::end(test_data::kTestU2fSignResponse)),
+ U2fApduResponse::Status::SW_NO_ERROR));
+}
+
+// static
+void MockU2fDevice::NoErrorRegister(const std::vector<uint8_t>& cmd,
+ DeviceCallback& cb) {
+ std::move(cb).Run(
+ true,
+ std::make_unique<U2fApduResponse>(
+ std::vector<uint8_t>(std::begin(test_data::kTestU2fRegisterResponse),
+ std::end(test_data::kTestU2fRegisterResponse)),
+ U2fApduResponse::Status::SW_NO_ERROR));
+}
+
+// static
+void MockU2fDevice::SignWithCorruptedResponse(const std::vector<uint8_t>& cmd,
+ DeviceCallback& cb) {
+ std::move(cb).Run(
+ true, std::make_unique<U2fApduResponse>(
+ std::vector<uint8_t>(
+ std::begin(test_data::kTestCorruptedU2fSignResponse),
+ std::end(test_data::kTestCorruptedU2fSignResponse)),
+ U2fApduResponse::Status::SW_NO_ERROR));
+}
+
+// static
+void MockU2fDevice::WinkDoNothing(WinkCallback& cb) {
+ std::move(cb).Run();
+}
+
+base::WeakPtr<U2fDevice> MockU2fDevice::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+} // namespace device
diff --git a/chromium/device/fido/mock_u2f_device.h b/chromium/device/fido/mock_u2f_device.h
new file mode 100644
index 00000000000..fc2f8027b13
--- /dev/null
+++ b/chromium/device/fido/mock_u2f_device.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef DEVICE_FIDO_MOCK_U2F_DEVICE_H_
+#define DEVICE_FIDO_MOCK_U2F_DEVICE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "device/fido/u2f_device.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace device {
+
+class MockU2fDevice : public U2fDevice {
+ public:
+ MockU2fDevice();
+ ~MockU2fDevice() override;
+
+ // GMock cannot mock a method taking a move-only type.
+ // TODO(crbug.com/729950): Remove these workarounds once support for move-only
+ // types is added to GMock.
+ MOCK_METHOD1(TryWinkRef, void(WinkCallback& cb));
+ void TryWink(WinkCallback cb) override;
+
+ MOCK_CONST_METHOD0(GetId, std::string(void));
+ // GMock cannot mock a method taking a move-only type.
+ // TODO(crbug.com/729950): Remove these workarounds once support for move-only
+ // types is added to GMock.
+ MOCK_METHOD2(DeviceTransactPtr,
+ void(std::vector<uint8_t> command, DeviceCallback& cb));
+ void DeviceTransact(std::vector<uint8_t> command, DeviceCallback cb) override;
+ base::WeakPtr<U2fDevice> GetWeakPtr() override;
+ static void TransactNoError(const std::vector<uint8_t>& command,
+ DeviceCallback cb);
+ static void NotSatisfied(const std::vector<uint8_t>& command,
+ DeviceCallback& cb);
+ static void WrongData(const std::vector<uint8_t>& command,
+ DeviceCallback& cb);
+ static void NoErrorSign(const std::vector<uint8_t>& command,
+ DeviceCallback& cb);
+ static void NoErrorRegister(const std::vector<uint8_t>& command,
+ DeviceCallback& cb);
+ static void SignWithCorruptedResponse(const std::vector<uint8_t>& command,
+ DeviceCallback& cb);
+ static void WinkDoNothing(WinkCallback& cb);
+
+ private:
+ base::WeakPtrFactory<U2fDevice> weak_factory_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_MOCK_U2F_DEVICE_H_
diff --git a/chromium/device/fido/mock_u2f_discovery.cc b/chromium/device/fido/mock_u2f_discovery.cc
new file mode 100644
index 00000000000..a2ad6ecdc3d
--- /dev/null
+++ b/chromium/device/fido/mock_u2f_discovery.cc
@@ -0,0 +1,61 @@
+// 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.
+
+#include "device/fido/mock_u2f_discovery.h"
+
+#include <utility>
+
+#include "base/threading/thread_task_runner_handle.h"
+
+namespace device {
+
+MockU2fDiscoveryObserver::MockU2fDiscoveryObserver() = default;
+
+MockU2fDiscoveryObserver::~MockU2fDiscoveryObserver() = default;
+
+MockU2fDiscovery::MockU2fDiscovery() = default;
+
+MockU2fDiscovery::~MockU2fDiscovery() = default;
+
+bool MockU2fDiscovery::AddDevice(std::unique_ptr<U2fDevice> device) {
+ return U2fDiscovery::AddDevice(std::move(device));
+}
+
+bool MockU2fDiscovery::RemoveDevice(base::StringPiece device_id) {
+ return U2fDiscovery::RemoveDevice(device_id);
+}
+
+void MockU2fDiscovery::AddDeviceWithoutNotification(
+ std::unique_ptr<U2fDevice> device) {
+ std::string device_id = device->GetId();
+ devices_.emplace(std::move(device_id), std::move(device));
+}
+
+base::ObserverList<U2fDiscovery::Observer>& MockU2fDiscovery::GetObservers() {
+ return observers_;
+}
+
+void MockU2fDiscovery::StartSuccess() {
+ NotifyDiscoveryStarted(true);
+}
+
+void MockU2fDiscovery::StartFailure() {
+ NotifyDiscoveryStarted(false);
+}
+
+// static
+void MockU2fDiscovery::StartSuccessAsync() {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&MockU2fDiscovery::StartSuccess, base::Unretained(this)));
+}
+
+// static
+void MockU2fDiscovery::StartFailureAsync() {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&MockU2fDiscovery::StartFailure, base::Unretained(this)));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/mock_u2f_discovery.h b/chromium/device/fido/mock_u2f_discovery.h
new file mode 100644
index 00000000000..6e12e89f4f1
--- /dev/null
+++ b/chromium/device/fido/mock_u2f_discovery.h
@@ -0,0 +1,56 @@
+// 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.
+
+#ifndef DEVICE_FIDO_MOCK_U2F_DISCOVERY_H_
+#define DEVICE_FIDO_MOCK_U2F_DISCOVERY_H_
+
+#include <memory>
+#include <string>
+
+#include "device/fido/mock_u2f_device.h"
+#include "device/fido/u2f_device.h"
+#include "device/fido/u2f_discovery.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace device {
+
+class MockU2fDiscoveryObserver : public U2fDiscovery::Observer {
+ public:
+ MockU2fDiscoveryObserver();
+ ~MockU2fDiscoveryObserver() override;
+
+ MOCK_METHOD2(DiscoveryStarted, void(U2fDiscovery*, bool));
+ MOCK_METHOD2(DiscoveryStopped, void(U2fDiscovery*, bool));
+ MOCK_METHOD2(DeviceAdded, void(U2fDiscovery*, U2fDevice*));
+ MOCK_METHOD2(DeviceRemoved, void(U2fDiscovery*, U2fDevice*));
+};
+
+class MockU2fDiscovery : public U2fDiscovery {
+ public:
+ MockU2fDiscovery();
+ ~MockU2fDiscovery() override;
+
+ MOCK_METHOD0(Start, void());
+ MOCK_METHOD0(Stop, void());
+
+ bool AddDevice(std::unique_ptr<U2fDevice> device) override;
+ bool RemoveDevice(base::StringPiece device_id) override;
+
+ // AddDeviceWithoutNotification adds |device| to the list of devices, but
+ // doesn't notify observers. This can be used before |StartSuccess| to mock
+ // devices that are discovered as soon as the discovery is started.
+ void AddDeviceWithoutNotification(std::unique_ptr<U2fDevice> device);
+
+ base::ObserverList<Observer>& GetObservers();
+
+ void StartSuccess();
+ void StartFailure();
+
+ void StartSuccessAsync();
+ void StartFailureAsync();
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_MOCK_U2F_DISCOVERY_H_
diff --git a/chromium/device/fido/public_key.cc b/chromium/device/fido/public_key.cc
new file mode 100644
index 00000000000..9616a9d51ed
--- /dev/null
+++ b/chromium/device/fido/public_key.cc
@@ -0,0 +1,18 @@
+// 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.
+
+#include "device/fido/public_key.h"
+
+#include <utility>
+
+#include "base/macros.h"
+
+namespace device {
+
+PublicKey::~PublicKey() = default;
+
+PublicKey::PublicKey(std::string algorithm)
+ : algorithm_(std::move(algorithm)) {}
+
+} // namespace device
diff --git a/chromium/device/fido/public_key.h b/chromium/device/fido/public_key.h
new file mode 100644
index 00000000000..9c0f0879d20
--- /dev/null
+++ b/chromium/device/fido/public_key.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef DEVICE_FIDO_PUBLIC_KEY_H_
+#define DEVICE_FIDO_PUBLIC_KEY_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+namespace device {
+
+// https://www.w3.org/TR/2017/WD-webauthn-20170505/#sec-attestation-data.
+class PublicKey {
+ public:
+ virtual ~PublicKey();
+
+ // The credential public key as a COSE_Key map as defined in Section 7
+ // of https://tools.ietf.org/html/rfc8152.
+ virtual std::vector<uint8_t> EncodeAsCOSEKey() const = 0;
+
+ protected:
+ explicit PublicKey(std::string algorithm);
+
+ const std::string algorithm_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PublicKey);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_PUBLIC_KEY_H_
diff --git a/chromium/device/fido/public_key_credential_descriptor.cc b/chromium/device/fido/public_key_credential_descriptor.cc
new file mode 100644
index 00000000000..0b0d90a4b30
--- /dev/null
+++ b/chromium/device/fido/public_key_credential_descriptor.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include <utility>
+
+#include "device/fido/public_key_credential_descriptor.h"
+
+namespace device {
+
+namespace {
+
+// Keys for storing credential descriptor information in CBOR map.
+constexpr char kCredentialIdKey[] = "id";
+constexpr char kCredentialTypeKey[] = "type";
+
+} // namespace
+
+// static
+base::Optional<PublicKeyCredentialDescriptor>
+PublicKeyCredentialDescriptor::CreateFromCBORValue(
+ const cbor::CBORValue& cbor) {
+ if (!cbor.is_map()) {
+ return base::nullopt;
+ }
+
+ const cbor::CBORValue::MapValue& map = cbor.GetMap();
+ auto type = map.find(cbor::CBORValue(kCredentialTypeKey));
+ if (type == map.end() || !type->second.is_string())
+ 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(),
+ id->second.GetBytestring());
+}
+
+PublicKeyCredentialDescriptor::PublicKeyCredentialDescriptor(
+ std::string credential_type,
+ std::vector<uint8_t> id)
+ : credential_type_(std::move(credential_type)), id_(std::move(id)) {}
+
+PublicKeyCredentialDescriptor::PublicKeyCredentialDescriptor(
+ PublicKeyCredentialDescriptor&& other) = default;
+
+PublicKeyCredentialDescriptor::PublicKeyCredentialDescriptor(
+ const PublicKeyCredentialDescriptor& other) = default;
+
+PublicKeyCredentialDescriptor& PublicKeyCredentialDescriptor::operator=(
+ PublicKeyCredentialDescriptor&& other) = default;
+
+PublicKeyCredentialDescriptor& PublicKeyCredentialDescriptor::operator=(
+ const PublicKeyCredentialDescriptor& other) = default;
+
+PublicKeyCredentialDescriptor::~PublicKeyCredentialDescriptor() = default;
+
+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_);
+ return cbor::CBORValue(std::move(cbor_descriptor_map));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/public_key_credential_descriptor.h b/chromium/device/fido/public_key_credential_descriptor.h
new file mode 100644
index 00000000000..c2b40fe0f8a
--- /dev/null
+++ b/chromium/device/fido/public_key_credential_descriptor.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_DESCRIPTOR_H_
+#define DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_DESCRIPTOR_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "base/optional.h"
+#include "components/cbor/cbor_values.h"
+
+namespace device {
+
+// Data structure containing public key credential type (string) and credential
+// id (byte array) as specified in the CTAP spec. Used for exclude_list for
+// AuthenticatorMakeCredential command and allow_list parameter for
+// AuthenticatorGetAssertion command.
+class PublicKeyCredentialDescriptor {
+ public:
+ static base::Optional<PublicKeyCredentialDescriptor> CreateFromCBORValue(
+ const cbor::CBORValue& cbor);
+
+ PublicKeyCredentialDescriptor(std::string credential_type,
+ std::vector<uint8_t> id);
+ PublicKeyCredentialDescriptor(PublicKeyCredentialDescriptor&& other);
+ PublicKeyCredentialDescriptor(const PublicKeyCredentialDescriptor& other);
+ PublicKeyCredentialDescriptor& operator=(
+ PublicKeyCredentialDescriptor&& other);
+ PublicKeyCredentialDescriptor& operator=(
+ const PublicKeyCredentialDescriptor& other);
+ ~PublicKeyCredentialDescriptor();
+
+ cbor::CBORValue ConvertToCBOR() const;
+
+ const std::string& credential_type() const { return credential_type_; }
+ const std::vector<uint8_t>& id() const { return id_; }
+
+ private:
+ std::string credential_type_;
+ std::vector<uint8_t> id_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_DESCRIPTOR_H_
diff --git a/chromium/device/fido/public_key_credential_params.cc b/chromium/device/fido/public_key_credential_params.cc
new file mode 100644
index 00000000000..f124faa76cc
--- /dev/null
+++ b/chromium/device/fido/public_key_credential_params.cc
@@ -0,0 +1,38 @@
+// 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.
+
+#include "device/fido/public_key_credential_params.h"
+
+#include <utility>
+
+namespace device {
+
+PublicKeyCredentialParams::PublicKeyCredentialParams(
+ std::vector<CredentialInfo> credential_params)
+ : public_key_credential_params_(std::move(credential_params)) {}
+
+PublicKeyCredentialParams::PublicKeyCredentialParams(
+ PublicKeyCredentialParams&& other) = default;
+
+PublicKeyCredentialParams& PublicKeyCredentialParams::operator=(
+ PublicKeyCredentialParams&& other) = default;
+
+PublicKeyCredentialParams::~PublicKeyCredentialParams() = default;
+
+cbor::CBORValue PublicKeyCredentialParams::ConvertToCBOR() const {
+ cbor::CBORValue::ArrayValue credential_param_array;
+ credential_param_array.reserve(public_key_credential_params_.size());
+
+ for (const auto& credential : public_key_credential_params_) {
+ cbor::CBORValue::MapValue cbor_credential_map;
+ cbor_credential_map[cbor::CBORValue("type")] =
+ cbor::CBORValue(credential.type);
+ cbor_credential_map[cbor::CBORValue("alg")] =
+ cbor::CBORValue(credential.algorithm);
+ credential_param_array.emplace_back(std::move(cbor_credential_map));
+ }
+ return cbor::CBORValue(std::move(credential_param_array));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/public_key_credential_params.h b/chromium/device/fido/public_key_credential_params.h
new file mode 100644
index 00000000000..a1c97ec4055
--- /dev/null
+++ b/chromium/device/fido/public_key_credential_params.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_PARAMS_H_
+#define DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_PARAMS_H_
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/numerics/safe_conversions.h"
+#include "components/cbor/cbor_values.h"
+#include "device/fido/ctap_constants.h"
+
+namespace device {
+
+// Data structure containing public key credential type(string) and
+// cryptographic algorithm(integer) as specified by the CTAP spec. Used as a
+// request parameter for AuthenticatorMakeCredential.
+class PublicKeyCredentialParams {
+ public:
+ struct CredentialInfo {
+ std::string type;
+ int algorithm =
+ base::strict_cast<int>(kCoseAlgorithmIdentifier::kCoseEs256);
+ };
+
+ explicit PublicKeyCredentialParams(
+ std::vector<CredentialInfo> credential_params);
+ PublicKeyCredentialParams(PublicKeyCredentialParams&& other);
+ PublicKeyCredentialParams& operator=(PublicKeyCredentialParams&& other);
+ ~PublicKeyCredentialParams();
+
+ cbor::CBORValue ConvertToCBOR() const;
+ const std::vector<CredentialInfo>& public_key_credential_params() const {
+ return public_key_credential_params_;
+ }
+
+ private:
+ std::vector<CredentialInfo> public_key_credential_params_;
+
+ DISALLOW_COPY_AND_ASSIGN(PublicKeyCredentialParams);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_PARAMS_H_
diff --git a/chromium/device/fido/public_key_credential_rp_entity.cc b/chromium/device/fido/public_key_credential_rp_entity.cc
new file mode 100644
index 00000000000..b596fe2f5d9
--- /dev/null
+++ b/chromium/device/fido/public_key_credential_rp_entity.cc
@@ -0,0 +1,44 @@
+// 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.
+
+#include "device/fido/public_key_credential_rp_entity.h"
+
+#include <utility>
+
+namespace device {
+
+PublicKeyCredentialRpEntity::PublicKeyCredentialRpEntity(std::string rp_id)
+ : rp_id_(std::move(rp_id)) {}
+
+PublicKeyCredentialRpEntity::PublicKeyCredentialRpEntity(
+ PublicKeyCredentialRpEntity&& other) = default;
+
+PublicKeyCredentialRpEntity& PublicKeyCredentialRpEntity::operator=(
+ PublicKeyCredentialRpEntity&& other) = default;
+
+PublicKeyCredentialRpEntity::~PublicKeyCredentialRpEntity() = default;
+
+PublicKeyCredentialRpEntity& PublicKeyCredentialRpEntity::SetRpName(
+ std::string rp_name) {
+ rp_name_ = std::move(rp_name);
+ return *this;
+}
+
+PublicKeyCredentialRpEntity& PublicKeyCredentialRpEntity::SetRpIconUrl(
+ GURL icon_url) {
+ rp_icon_url_ = std::move(icon_url);
+ return *this;
+}
+
+cbor::CBORValue PublicKeyCredentialRpEntity::ConvertToCBOR() const {
+ cbor::CBORValue::MapValue rp_map;
+ rp_map[cbor::CBORValue("id")] = cbor::CBORValue(rp_id_);
+ if (rp_name_)
+ rp_map[cbor::CBORValue("name")] = cbor::CBORValue(*rp_name_);
+ if (rp_icon_url_)
+ rp_map[cbor::CBORValue("icon")] = cbor::CBORValue(rp_icon_url_->spec());
+ return cbor::CBORValue(std::move(rp_map));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/public_key_credential_rp_entity.h b/chromium/device/fido/public_key_credential_rp_entity.h
new file mode 100644
index 00000000000..4e44616e09e
--- /dev/null
+++ b/chromium/device/fido/public_key_credential_rp_entity.h
@@ -0,0 +1,47 @@
+// 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.
+
+#ifndef DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_RP_ENTITY_H_
+#define DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_RP_ENTITY_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "components/cbor/cbor_values.h"
+#include "url/gurl.h"
+
+namespace device {
+
+// Data structure containing information about relying party that invoked
+// WebAuth API. Includes a relying party id, an optional relying party name,,
+// and optional relying party display image url.
+class PublicKeyCredentialRpEntity {
+ public:
+ explicit PublicKeyCredentialRpEntity(std::string rp_id);
+ PublicKeyCredentialRpEntity(PublicKeyCredentialRpEntity&& other);
+ PublicKeyCredentialRpEntity& operator=(PublicKeyCredentialRpEntity&& other);
+ ~PublicKeyCredentialRpEntity();
+
+ cbor::CBORValue ConvertToCBOR() const;
+
+ PublicKeyCredentialRpEntity& SetRpName(std::string rp_name);
+ PublicKeyCredentialRpEntity& SetRpIconUrl(GURL icon_url);
+
+ const std::string& rp_id() const { return rp_id_; }
+ const base::Optional<std::string>& rp_name() const { return rp_name_; }
+ const base::Optional<GURL>& rp_icon_url() const { return rp_icon_url_; }
+
+ private:
+ std::string rp_id_;
+ base::Optional<std::string> rp_name_;
+ base::Optional<GURL> rp_icon_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(PublicKeyCredentialRpEntity);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_RP_ENTITY_H_
diff --git a/chromium/device/fido/public_key_credential_user_entity.cc b/chromium/device/fido/public_key_credential_user_entity.cc
new file mode 100644
index 00000000000..5a010eae5d3
--- /dev/null
+++ b/chromium/device/fido/public_key_credential_user_entity.cc
@@ -0,0 +1,106 @@
+// 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.
+
+#include "device/fido/public_key_credential_user_entity.h"
+
+#include <utility>
+
+namespace device {
+
+namespace {
+
+// Keys for storing user entity information in CBOR map.
+constexpr char kUserIdKey[] = "id";
+constexpr char kUserNameKey[] = "name";
+constexpr char kUserDisplayNameKey[] = "displayName";
+constexpr char kUserIconUrlKey[] = "icon";
+
+} // namespace
+
+// static
+base::Optional<PublicKeyCredentialUserEntity>
+PublicKeyCredentialUserEntity::CreateFromCBORValue(
+ const cbor::CBORValue& cbor) {
+ if (!cbor.is_map())
+ return base::nullopt;
+
+ const cbor::CBORValue::MapValue& cbor_map = cbor.GetMap();
+
+ auto user_id = cbor_map.find(cbor::CBORValue(kUserIdKey));
+ if (user_id == cbor_map.end() || !user_id->second.is_bytestring())
+ return base::nullopt;
+
+ PublicKeyCredentialUserEntity user(user_id->second.GetBytestring());
+
+ auto user_name = cbor_map.find(cbor::CBORValue(kUserNameKey));
+ if (user_name != cbor_map.end() && user_name->second.is_string()) {
+ user.SetUserName(user_name->second.GetString());
+ }
+
+ auto user_display_name = cbor_map.find(cbor::CBORValue(kUserDisplayNameKey));
+ if (user_display_name != cbor_map.end() &&
+ user_display_name->second.is_string()) {
+ user.SetDisplayName(user_display_name->second.GetString());
+ }
+
+ auto user_icon_url = cbor_map.find(cbor::CBORValue(kUserIconUrlKey));
+ if (user_icon_url != cbor_map.end() && user_icon_url->second.is_string()) {
+ user.SetIconUrl(GURL(user_icon_url->second.GetString()));
+ }
+
+ return user;
+}
+
+PublicKeyCredentialUserEntity::PublicKeyCredentialUserEntity(
+ std::vector<uint8_t> user_id)
+ : user_id_(std::move(user_id)) {}
+
+PublicKeyCredentialUserEntity::PublicKeyCredentialUserEntity(
+ PublicKeyCredentialUserEntity&& other) = default;
+
+PublicKeyCredentialUserEntity::PublicKeyCredentialUserEntity(
+ const PublicKeyCredentialUserEntity& other) = default;
+
+PublicKeyCredentialUserEntity& PublicKeyCredentialUserEntity::operator=(
+ PublicKeyCredentialUserEntity&& other) = default;
+
+PublicKeyCredentialUserEntity& PublicKeyCredentialUserEntity::operator=(
+ const PublicKeyCredentialUserEntity& other) = default;
+
+PublicKeyCredentialUserEntity::~PublicKeyCredentialUserEntity() = default;
+
+cbor::CBORValue PublicKeyCredentialUserEntity::ConvertToCBOR() const {
+ cbor::CBORValue::MapValue user_map;
+ user_map[cbor::CBORValue(kUserIdKey)] = cbor::CBORValue(user_id_);
+ if (user_name_)
+ user_map[cbor::CBORValue(kUserNameKey)] = cbor::CBORValue(*user_name_);
+ if (user_icon_url_)
+ user_map[cbor::CBORValue(kUserIconUrlKey)] =
+ cbor::CBORValue(user_icon_url_->spec());
+ if (user_display_name_) {
+ user_map[cbor::CBORValue(kUserDisplayNameKey)] =
+ cbor::CBORValue(*user_display_name_);
+ }
+ return cbor::CBORValue(std::move(user_map));
+}
+
+PublicKeyCredentialUserEntity& PublicKeyCredentialUserEntity::SetUserName(
+ std::string user_name) {
+ user_name_ = std::move(user_name);
+ return *this;
+}
+
+PublicKeyCredentialUserEntity& PublicKeyCredentialUserEntity::SetDisplayName(
+ std::string user_display_name) {
+ user_display_name_ = std::move(user_display_name);
+ return *this;
+}
+
+PublicKeyCredentialUserEntity& PublicKeyCredentialUserEntity::SetIconUrl(
+ GURL icon_url) {
+ user_icon_url_ = std::move(icon_url);
+ return *this;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/public_key_credential_user_entity.h b/chromium/device/fido/public_key_credential_user_entity.h
new file mode 100644
index 00000000000..0456b1bb55d
--- /dev/null
+++ b/chromium/device/fido/public_key_credential_user_entity.h
@@ -0,0 +1,57 @@
+// 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.
+
+#ifndef DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_USER_ENTITY_H_
+#define DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_USER_ENTITY_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "base/optional.h"
+#include "components/cbor/cbor_values.h"
+#include "url/gurl.h"
+
+namespace device {
+
+// Data structure containing a user id, an optional user name, an optional user
+// display image url, and an optional user display name as specified by the CTAP
+// spec. Used as required parameter type for AuthenticatorMakeCredential
+// request.
+class PublicKeyCredentialUserEntity {
+ public:
+ static base::Optional<PublicKeyCredentialUserEntity> CreateFromCBORValue(
+ const cbor::CBORValue& cbor);
+
+ explicit PublicKeyCredentialUserEntity(std::vector<uint8_t> user_id);
+ PublicKeyCredentialUserEntity(PublicKeyCredentialUserEntity&& other);
+ PublicKeyCredentialUserEntity(const PublicKeyCredentialUserEntity& other);
+ PublicKeyCredentialUserEntity& operator=(
+ PublicKeyCredentialUserEntity&& other);
+ PublicKeyCredentialUserEntity& operator=(
+ const PublicKeyCredentialUserEntity& other);
+ ~PublicKeyCredentialUserEntity();
+
+ cbor::CBORValue ConvertToCBOR() const;
+ PublicKeyCredentialUserEntity& SetUserName(std::string user_name);
+ PublicKeyCredentialUserEntity& SetDisplayName(std::string display_name);
+ PublicKeyCredentialUserEntity& SetIconUrl(GURL icon_url);
+
+ const std::vector<uint8_t>& user_id() const { return user_id_; }
+ const base::Optional<std::string>& user_name() const { return user_name_; }
+ const base::Optional<std::string>& user_display_name() const {
+ return user_display_name_;
+ }
+ const base::Optional<GURL>& user_icon_url() const { return user_icon_url_; }
+
+ private:
+ std::vector<uint8_t> user_id_;
+ base::Optional<std::string> user_name_;
+ base::Optional<std::string> user_display_name_;
+ base::Optional<GURL> user_icon_url_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_PUBLIC_KEY_CREDENTIAL_USER_ENTITY_H_
diff --git a/chromium/device/fido/register_response_data.cc b/chromium/device/fido/register_response_data.cc
new file mode 100644
index 00000000000..b3655c24c2b
--- /dev/null
+++ b/chromium/device/fido/register_response_data.cc
@@ -0,0 +1,97 @@
+// 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.
+
+#include "device/fido/register_response_data.h"
+
+#include <utility>
+
+#include "device/fido/attestation_object.h"
+#include "device/fido/attested_credential_data.h"
+#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"
+
+namespace device {
+
+// static
+base::Optional<RegisterResponseData>
+RegisterResponseData::CreateFromU2fRegisterResponse(
+ const std::vector<uint8_t>& relying_party_id_hash,
+ base::span<const uint8_t> u2f_data) {
+ std::unique_ptr<ECPublicKey> public_key =
+ ECPublicKey::ExtractFromU2fRegistrationResponse(u2f_parsing_utils::kEs256,
+ u2f_data);
+
+ if (!public_key) {
+ return base::nullopt;
+ }
+
+ // Construct the attestation data.
+ // AAGUID is zeroed out for U2F responses.
+ std::vector<uint8_t> aaguid(16u);
+
+ auto attested_credential_data =
+ AttestedCredentialData::CreateFromU2fRegisterResponse(
+ u2f_data, std::move(aaguid), std::move(public_key));
+
+ if (!attested_credential_data) {
+ return base::nullopt;
+ }
+
+ // Extract the credential_id for packing into the reponse data.
+ std::vector<uint8_t> credential_id =
+ attested_credential_data->credential_id();
+
+ // Construct the authenticator data.
+ // The counter is zeroed out for Register requests.
+ std::vector<uint8_t> counter(4u);
+ constexpr uint8_t flags =
+ static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence) |
+ static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation);
+
+ AuthenticatorData authenticator_data(relying_party_id_hash, flags,
+ std::move(counter),
+ std::move(attested_credential_data));
+
+ // Construct the attestation statement.
+ auto fido_attestation_statement =
+ FidoAttestationStatement::CreateFromU2fRegisterResponse(u2f_data);
+
+ if (!fido_attestation_statement) {
+ return base::nullopt;
+ }
+
+ return RegisterResponseData(std::move(credential_id),
+ std::make_unique<AttestationObject>(
+ std::move(authenticator_data),
+ std::move(fido_attestation_statement)));
+}
+
+RegisterResponseData::RegisterResponseData() = default;
+
+RegisterResponseData::RegisterResponseData(
+ std::vector<uint8_t> credential_id,
+ std::unique_ptr<AttestationObject> object)
+ : ResponseData(std::move(credential_id)),
+ attestation_object_(std::move(object)) {}
+
+RegisterResponseData::RegisterResponseData(RegisterResponseData&& other) =
+ default;
+
+RegisterResponseData& RegisterResponseData::operator=(
+ RegisterResponseData&& other) = default;
+
+RegisterResponseData::~RegisterResponseData() = default;
+
+void RegisterResponseData::EraseAttestationStatement() {
+ attestation_object_->EraseAttestationStatement();
+}
+
+std::vector<uint8_t> RegisterResponseData::GetCBOREncodedAttestationObject()
+ const {
+ return attestation_object_->SerializeToCBOREncodedBytes();
+}
+
+} // namespace device
diff --git a/chromium/device/fido/register_response_data.h b/chromium/device/fido/register_response_data.h
new file mode 100644
index 00000000000..8b3d445c96b
--- /dev/null
+++ b/chromium/device/fido/register_response_data.h
@@ -0,0 +1,59 @@
+// 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.
+
+#ifndef DEVICE_FIDO_REGISTER_RESPONSE_DATA_H_
+#define DEVICE_FIDO_REGISTER_RESPONSE_DATA_H_
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/fido/response_data.h"
+
+namespace device {
+
+class AttestationObject;
+
+// See figure 2: https://goo.gl/rsgvXk
+class RegisterResponseData : public ResponseData {
+ public:
+ static base::Optional<RegisterResponseData> CreateFromU2fRegisterResponse(
+ const std::vector<uint8_t>& relying_party_id_hash,
+ base::span<const uint8_t> u2f_data);
+
+ RegisterResponseData();
+
+ RegisterResponseData(std::vector<uint8_t> credential_id,
+ std::unique_ptr<AttestationObject> object);
+
+ // Moveable.
+ RegisterResponseData(RegisterResponseData&& other);
+ RegisterResponseData& operator=(RegisterResponseData&& other);
+
+ ~RegisterResponseData();
+
+ // Replaces the attestation statement with a “none†attestation, as specified
+ // for step 20.3 in https://w3c.github.io/webauthn/#createCredential. (This
+ // does not, currently, erase the AAGUID because it is already always zero
+ // for U2F devices. If CTAP2 is supported in the future, that will need to be
+ // taken into account.)
+ //
+ // TODO(https://crbug.com/780078): erase AAGUID when CTAP2 is supported.
+ void EraseAttestationStatement();
+
+ std::vector<uint8_t> GetCBOREncodedAttestationObject() const;
+
+ private:
+ std::unique_ptr<AttestationObject> attestation_object_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegisterResponseData);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_REGISTER_RESPONSE_DATA_H_
diff --git a/chromium/device/fido/register_response_data_fuzzer.cc b/chromium/device/fido/register_response_data_fuzzer.cc
new file mode 100644
index 00000000000..4fb89fb9b65
--- /dev/null
+++ b/chromium/device/fido/register_response_data_fuzzer.cc
@@ -0,0 +1,17 @@
+// 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.
+
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "device/fido/register_response_data.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::vector<uint8_t> input(data, data + size);
+ std::vector<uint8_t> relying_party_id_hash(32);
+ auto response = device::RegisterResponseData::CreateFromU2fRegisterResponse(
+ relying_party_id_hash, input);
+ return 0;
+}
diff --git a/chromium/device/fido/response_data.cc b/chromium/device/fido/response_data.cc
new file mode 100644
index 00000000000..3a4d6d602cc
--- /dev/null
+++ b/chromium/device/fido/response_data.cc
@@ -0,0 +1,34 @@
+// 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.
+
+#include "device/fido/response_data.h"
+
+#include <utility>
+
+#include "base/base64url.h"
+#include "base/strings/string_piece.h"
+
+namespace device {
+
+ResponseData::ResponseData() = default;
+
+ResponseData::ResponseData(std::vector<uint8_t> credential_id)
+ : raw_id_(std::move(credential_id)) {}
+
+ResponseData::ResponseData(ResponseData&& other) = default;
+
+ResponseData& ResponseData::operator=(ResponseData&& other) = default;
+
+ResponseData::~ResponseData() = default;
+
+std::string ResponseData::GetId() const {
+ std::string id;
+ base::Base64UrlEncode(
+ base::StringPiece(reinterpret_cast<const char*>(raw_id_.data()),
+ raw_id_.size()),
+ base::Base64UrlEncodePolicy::OMIT_PADDING, &id);
+ return id;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/response_data.h b/chromium/device/fido/response_data.h
new file mode 100644
index 00000000000..60ce8651cf6
--- /dev/null
+++ b/chromium/device/fido/response_data.h
@@ -0,0 +1,42 @@
+// 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.
+
+#ifndef DEVICE_FIDO_RESPONSE_DATA_H_
+#define DEVICE_FIDO_RESPONSE_DATA_H_
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+namespace device {
+
+// Base class for RegisterResponseData and SignResponseData.
+class ResponseData {
+ public:
+ std::string GetId() const;
+ const std::vector<uint8_t>& raw_id() const { return raw_id_; }
+
+ protected:
+ explicit ResponseData(std::vector<uint8_t> credential_id);
+
+ ResponseData();
+
+ // Moveable.
+ ResponseData(ResponseData&& other);
+ ResponseData& operator=(ResponseData&& other);
+
+ ~ResponseData();
+
+ std::vector<uint8_t> raw_id_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResponseData);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_RESPONSE_DATA_H_
diff --git a/chromium/device/fido/response_data_fuzzer_corpus/register1 b/chromium/device/fido/response_data_fuzzer_corpus/register1
new file mode 100644
index 00000000000..b8f3fef89b3
--- /dev/null
+++ b/chromium/device/fido/response_data_fuzzer_corpus/register1
Binary files differ
diff --git a/chromium/device/fido/response_data_fuzzer_corpus/sign0 b/chromium/device/fido/response_data_fuzzer_corpus/sign0
new file mode 100644
index 00000000000..41be68d7b89
--- /dev/null
+++ b/chromium/device/fido/response_data_fuzzer_corpus/sign0
@@ -0,0 +1 @@
+‰¯µ$‘@+tYÉò!¯æåVe…è[IMUUôj¼D{üba¥þëåŸ^Üu2˜oDi×öëªê3ûÕŽ¿Æ \ No newline at end of file
diff --git a/chromium/device/fido/response_data_fuzzer_corpus/sign1 b/chromium/device/fido/response_data_fuzzer_corpus/sign1
new file mode 100644
index 00000000000..6d8cc24d0ea
--- /dev/null
+++ b/chromium/device/fido/response_data_fuzzer_corpus/sign1
Binary files differ
diff --git a/chromium/device/fido/sign_response_data.cc b/chromium/device/fido/sign_response_data.cc
new file mode 100644
index 00000000000..7e59240a01d
--- /dev/null
+++ b/chromium/device/fido/sign_response_data.cc
@@ -0,0 +1,71 @@
+// 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.
+
+#include "device/fido/sign_response_data.h"
+
+#include <utility>
+
+#include "base/optional.h"
+#include "device/fido/u2f_parsing_utils.h"
+
+namespace device {
+
+constexpr size_t kFlagIndex = 0;
+constexpr size_t kFlagLength = 1;
+constexpr size_t kCounterIndex = 1;
+constexpr size_t kCounterLength = 4;
+constexpr size_t kSignatureIndex = 5;
+
+// static
+base::Optional<SignResponseData> SignResponseData::CreateFromU2fSignResponse(
+ const std::vector<uint8_t>& relying_party_id_hash,
+ const std::vector<uint8_t>& u2f_data,
+ const std::vector<uint8_t>& key_handle) {
+ if (key_handle.empty())
+ return base::nullopt;
+
+ std::vector<uint8_t> flags =
+ u2f_parsing_utils::Extract(u2f_data, kFlagIndex, kFlagLength);
+ if (flags.empty())
+ return base::nullopt;
+
+ // Extract the 4-byte counter following the flag byte.
+ std::vector<uint8_t> counter =
+ u2f_parsing_utils::Extract(u2f_data, kCounterIndex, kCounterLength);
+ if (counter.empty())
+ return base::nullopt;
+
+ // Construct the authenticator data.
+ AuthenticatorData authenticator_data(relying_party_id_hash, flags[0],
+ std::move(counter), base::nullopt);
+
+ // Extract the signature from the remainder of the U2fResponse bytes.
+ std::vector<uint8_t> signature = u2f_parsing_utils::Extract(
+ u2f_data, kSignatureIndex, u2f_data.size() - kSignatureIndex);
+ if (signature.empty())
+ return base::nullopt;
+
+ return SignResponseData(std::move(key_handle), std::move(authenticator_data),
+ std::move(signature));
+}
+
+SignResponseData::SignResponseData(std::vector<uint8_t> credential_id,
+ AuthenticatorData authenticator_data,
+ std::vector<uint8_t> signature)
+ : ResponseData(std::move(credential_id)),
+ authenticator_data_(std::move(authenticator_data)),
+ signature_(std::move(signature)) {}
+
+SignResponseData::SignResponseData(SignResponseData&& other) = default;
+
+SignResponseData& SignResponseData::operator=(SignResponseData&& other) =
+ default;
+
+SignResponseData::~SignResponseData() = default;
+
+std::vector<uint8_t> SignResponseData::GetAuthenticatorDataBytes() const {
+ return authenticator_data_.SerializeToByteArray();
+}
+
+} // namespace device
diff --git a/chromium/device/fido/sign_response_data.h b/chromium/device/fido/sign_response_data.h
new file mode 100644
index 00000000000..cc152c74c58
--- /dev/null
+++ b/chromium/device/fido/sign_response_data.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef DEVICE_FIDO_SIGN_RESPONSE_DATA_H_
+#define DEVICE_FIDO_SIGN_RESPONSE_DATA_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "device/fido/authenticator_data.h"
+#include "device/fido/response_data.h"
+
+namespace device {
+
+// Corresponds to a CTAP AuthenticatorGetAssertion response.
+// See mapping from a U2F response to a CTAP response
+// at https://goo.gl/eZTacx.
+class SignResponseData : public ResponseData {
+ public:
+ static base::Optional<SignResponseData> CreateFromU2fSignResponse(
+ const std::vector<uint8_t>& relying_party_id_hash,
+ const std::vector<uint8_t>& u2f_data,
+ const std::vector<uint8_t>& key_handle);
+
+ SignResponseData(std::vector<uint8_t> credential_id,
+ AuthenticatorData authenticator_data,
+ std::vector<uint8_t> signature);
+
+ SignResponseData(SignResponseData&& other);
+ SignResponseData& operator=(SignResponseData&& other);
+
+ ~SignResponseData();
+
+ std::vector<uint8_t> GetAuthenticatorDataBytes() const;
+ const std::vector<uint8_t>& signature() const { return signature_; }
+
+ private:
+ AuthenticatorData authenticator_data_;
+ std::vector<uint8_t> signature_;
+
+ DISALLOW_COPY_AND_ASSIGN(SignResponseData);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_SIGN_RESPONSE_DATA_H_
diff --git a/chromium/device/fido/sign_response_data_fuzzer.cc b/chromium/device/fido/sign_response_data_fuzzer.cc
new file mode 100644
index 00000000000..711ace8fa4e
--- /dev/null
+++ b/chromium/device/fido/sign_response_data_fuzzer.cc
@@ -0,0 +1,19 @@
+// 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.
+
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "base/optional.h"
+#include "device/fido/sign_response_data.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::vector<uint8_t> u2f_response_data(data, data + size);
+ std::vector<uint8_t> key_handle(data, data + size);
+ std::vector<uint8_t> relying_party_id_hash(32);
+ auto response = device::SignResponseData::CreateFromU2fSignResponse(
+ relying_party_id_hash, u2f_response_data, key_handle);
+ return 0;
+}
diff --git a/chromium/device/fido/u2f_apdu_command.cc b/chromium/device/fido/u2f_apdu_command.cc
new file mode 100644
index 00000000000..171d0bb9853
--- /dev/null
+++ b/chromium/device/fido/u2f_apdu_command.cc
@@ -0,0 +1,192 @@
+// 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.
+
+#include "base/memory/ptr_util.h"
+
+#include "device/fido/u2f_apdu_command.h"
+
+namespace device {
+
+std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateFromMessage(
+ const std::vector<uint8_t>& message) {
+ uint16_t data_length = 0;
+ size_t index = 0;
+ size_t response_length = 0;
+ std::vector<uint8_t> data;
+ std::vector<uint8_t> suffix;
+
+ if (message.size() < kApduMinHeader || message.size() > kApduMaxLength)
+ return nullptr;
+ uint8_t cla = message[index++];
+ uint8_t ins = message[index++];
+ uint8_t p1 = message[index++];
+ uint8_t p2 = message[index++];
+
+ switch (message.size()) {
+ // No data present; no expected response
+ case kApduMinHeader:
+ break;
+ // Invalid encoding sizes
+ case kApduMinHeader + 1:
+ case kApduMinHeader + 2:
+ return nullptr;
+ // No data present; response expected
+ case kApduMinHeader + 3:
+ // Fifth byte must be 0
+ if (message[index++] != 0)
+ return nullptr;
+ response_length = message[index++] << 8;
+ response_length |= message[index++];
+ // Special case where response length of 0x0000 corresponds to 65536
+ // Defined in ISO7816-4
+ if (response_length == 0)
+ response_length = kApduMaxResponseLength;
+ break;
+ default:
+ // Fifth byte must be 0
+ if (message[index++] != 0)
+ return nullptr;
+ data_length = message[index++] << 8;
+ data_length |= message[index++];
+
+ if (message.size() == data_length + index) {
+ // No response expected
+ data.insert(data.end(), message.begin() + index, message.end());
+ } else if (message.size() == data_length + index + 2) {
+ // Maximum response size is stored in final 2 bytes
+ data.insert(data.end(), message.begin() + index, message.end() - 2);
+ index += data_length;
+ response_length = message[index++] << 8;
+ response_length |= message[index++];
+ // Special case where response length of 0x0000 corresponds to 65536
+ // Defined in ISO7816-4
+ if (response_length == 0)
+ response_length = kApduMaxResponseLength;
+ // Non-ISO7816-4 special legacy case where 2 suffix bytes are passed
+ // along with a version message
+ if (data_length == 0 && ins == kInsU2fVersion)
+ suffix = {0x0, 0x0};
+ } else {
+ return nullptr;
+ }
+ break;
+ }
+
+ return std::make_unique<U2fApduCommand>(cla, ins, p1, p2, response_length,
+ std::move(data), std::move(suffix));
+}
+
+std::vector<uint8_t> U2fApduCommand::GetEncodedCommand() const {
+ std::vector<uint8_t> encoded = {cla_, ins_, p1_, p2_};
+
+ // If data exists, request size (Lc) is encoded in 3 bytes, with the first
+ // byte always being null, and the other two bytes being a big-endian
+ // representation of the request size. If data length is 0, response size (Le)
+ // will be prepended with a null byte.
+ if (data_.size() > 0) {
+ size_t data_length = data_.size();
+
+ encoded.push_back(0x0);
+ if (data_length > kApduMaxDataLength)
+ data_length = kApduMaxDataLength;
+ encoded.push_back((data_length >> 8) & 0xff);
+ encoded.push_back(data_length & 0xff);
+ encoded.insert(encoded.end(), data_.begin(), data_.begin() + data_length);
+ } else if (response_length_ > 0) {
+ encoded.push_back(0x0);
+ }
+
+ if (response_length_ > 0) {
+ encoded.push_back((response_length_ >> 8) & 0xff);
+ encoded.push_back(response_length_ & 0xff);
+ }
+ // Add suffix, if required, for legacy compatibility
+ encoded.insert(encoded.end(), suffix_.begin(), suffix_.end());
+ return encoded;
+}
+
+U2fApduCommand::U2fApduCommand()
+ : cla_(0), ins_(0), p1_(0), p2_(0), response_length_(0) {}
+
+U2fApduCommand::U2fApduCommand(uint8_t cla,
+ uint8_t ins,
+ uint8_t p1,
+ uint8_t p2,
+ size_t response_length,
+ std::vector<uint8_t> data,
+ std::vector<uint8_t> suffix)
+ : cla_(cla),
+ ins_(ins),
+ p1_(p1),
+ p2_(p2),
+ response_length_(response_length),
+ data_(std::move(data)),
+ suffix_(std::move(suffix)) {}
+
+U2fApduCommand::~U2fApduCommand() = default;
+
+// static
+std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateRegister(
+ const std::vector<uint8_t>& appid_digest,
+ const std::vector<uint8_t>& challenge_digest,
+ bool individual_attestation_ok) {
+ if (appid_digest.size() != kAppIdDigestLen ||
+ challenge_digest.size() != kChallengeDigestLen) {
+ return nullptr;
+ }
+
+ auto command = std::make_unique<U2fApduCommand>();
+ std::vector<uint8_t> data(challenge_digest.begin(), challenge_digest.end());
+ data.insert(data.end(), appid_digest.begin(), appid_digest.end());
+ command->set_ins(kInsU2fEnroll);
+ command->set_p1(kP1TupRequiredConsumed |
+ (individual_attestation_ok ? kP1IndividualAttestation : 0));
+ command->set_data(data);
+ return command;
+}
+
+// static
+std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateVersion() {
+ auto command = std::make_unique<U2fApduCommand>();
+ command->set_ins(kInsU2fVersion);
+ command->set_response_length(kApduMaxResponseLength);
+ return command;
+}
+
+// static
+std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateLegacyVersion() {
+ auto command = std::make_unique<U2fApduCommand>();
+ command->set_ins(kInsU2fVersion);
+ command->set_response_length(kApduMaxResponseLength);
+ // 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
+ command->set_suffix(std::vector<uint8_t>(2, 0));
+ return command;
+}
+
+// static
+std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateSign(
+ const std::vector<uint8_t>& appid_digest,
+ const std::vector<uint8_t>& challenge_digest,
+ const std::vector<uint8_t>& key_handle,
+ bool check_only) {
+ if (appid_digest.size() != kAppIdDigestLen ||
+ challenge_digest.size() != kChallengeDigestLen ||
+ key_handle.size() > kMaxKeyHandleLength) {
+ return nullptr;
+ }
+
+ auto command = std::make_unique<U2fApduCommand>();
+ std::vector<uint8_t> data(challenge_digest.begin(), challenge_digest.end());
+ data.insert(data.end(), appid_digest.begin(), appid_digest.end());
+ data.push_back(static_cast<uint8_t>(key_handle.size()));
+ data.insert(data.end(), key_handle.begin(), key_handle.end());
+ command->set_ins(kInsU2fSign);
+ command->set_p1(check_only ? kP1CheckOnly : kP1TupRequiredConsumed);
+ command->set_data(data);
+ return command;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_apdu_command.h b/chromium/device/fido/u2f_apdu_command.h
new file mode 100644
index 00000000000..a51b5986433
--- /dev/null
+++ b/chromium/device/fido/u2f_apdu_command.h
@@ -0,0 +1,118 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_APDU_COMMAND_H_
+#define DEVICE_FIDO_U2F_APDU_COMMAND_H_
+
+#include <cinttypes>
+#include <memory>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+
+namespace device {
+
+// APDU commands are defined as part of ISO 7816-4. Commands can be serialized
+// into either short length encodings, where the maximum data length is 256
+// bytes, or an extended length encoding, where the maximum data length is 65536
+// bytes. This class implements only the extended length encoding. Serialized
+// commands consist of a CLA byte, denoting the class of instruction, an INS
+// byte, denoting the instruction code, P1 and P2, each one byte denoting
+// instruction parameters, a length field (Lc), a data field of length Lc, and
+// a maximum expected response length (Le).
+class U2fApduCommand {
+ public:
+ U2fApduCommand();
+ U2fApduCommand(uint8_t cla,
+ uint8_t ins,
+ uint8_t p1,
+ uint8_t p2,
+ size_t response_length,
+ std::vector<uint8_t> data,
+ std::vector<uint8_t> suffix);
+ ~U2fApduCommand();
+
+ // Constructs an APDU command from the serialized message data.
+ static std::unique_ptr<U2fApduCommand> CreateFromMessage(
+ const std::vector<uint8_t>& data);
+ // Returns serialized message data.
+ std::vector<uint8_t> GetEncodedCommand() const;
+ void set_cla(uint8_t cla) { cla_ = cla; }
+ void set_ins(uint8_t ins) { ins_ = ins; }
+ void set_p1(uint8_t p1) { p1_ = p1; }
+ void set_p2(uint8_t p2) { p2_ = p2; }
+ void set_data(const std::vector<uint8_t>& data) { data_ = data; }
+ void set_response_length(size_t response_length) {
+ response_length_ = response_length;
+ }
+ void set_suffix(const std::vector<uint8_t>& suffix) { suffix_ = suffix; }
+ static std::unique_ptr<U2fApduCommand> CreateRegister(
+ const std::vector<uint8_t>& appid_digest,
+ const std::vector<uint8_t>& challenge_digest,
+ bool individual_attestation_ok);
+ static std::unique_ptr<U2fApduCommand> CreateVersion();
+ // Early U2F drafts defined a non-ISO 7816-4 conforming layout.
+ static std::unique_ptr<U2fApduCommand> CreateLegacyVersion();
+
+ // Returns an APDU command for sign(). If optional parameter |check_only| is
+ // set to true, then control byte is set to 0X07.
+ static std::unique_ptr<U2fApduCommand> CreateSign(
+ const std::vector<uint8_t>& appid_digest,
+ const std::vector<uint8_t>& challenge_digest,
+ const std::vector<uint8_t>& key_handle,
+ bool check_only = false);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestDeserializeBasic);
+ FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestDeserializeComplex);
+ FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestSerializeEdgeCases);
+ FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestCreateSign);
+ FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestCreateRegister);
+ FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestCreateVersion);
+ FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestCreateLegacyVersion);
+
+ static constexpr size_t kApduMinHeader = 4;
+ static constexpr size_t kApduMaxHeader = 7;
+ // As defined in ISO7816-4, extended length APDU request data is limited to
+ // 16 bits in length with a maximum value of 65535. Response data length is
+ // also limited to 16 bits in length with a value of 0x0000 corresponding to
+ // a length of 65536.
+ static constexpr size_t kApduMaxDataLength = 65535;
+ static constexpr size_t kApduMaxResponseLength = 65536;
+ static constexpr size_t kApduMaxLength =
+ kApduMaxDataLength + kApduMaxHeader + 2;
+
+ // APDU instructions.
+ static constexpr uint8_t kInsU2fEnroll = 0x01;
+ static constexpr uint8_t kInsU2fSign = 0x02;
+ static constexpr uint8_t kInsU2fVersion = 0x03;
+
+ // P1 instructions.
+ static constexpr uint8_t kP1TupRequired = 0x01;
+ static constexpr uint8_t kP1TupConsumed = 0x02;
+ static constexpr uint8_t kP1TupRequiredConsumed =
+ kP1TupRequired | kP1TupConsumed;
+ // Control byte used for check-only setting. The check-only command is used to
+ // determine if the provided key handle was originally created by this token
+ // and whether it was created for the provided application parameter.
+ static constexpr uint8_t kP1CheckOnly = 0x07;
+ // Indicates that an individual attestation certificate is acceptable to
+ // return with this registration.
+ static constexpr uint8_t kP1IndividualAttestation = 0x80;
+ static constexpr size_t kMaxKeyHandleLength = 255;
+ static constexpr size_t kChallengeDigestLen = 32;
+ static constexpr size_t kAppIdDigestLen = 32;
+
+ uint8_t cla_;
+ uint8_t ins_;
+ uint8_t p1_;
+ uint8_t p2_;
+ size_t response_length_;
+ std::vector<uint8_t> data_;
+ std::vector<uint8_t> suffix_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_APDU_COMMAND_H_
diff --git a/chromium/device/fido/u2f_apdu_fuzzer.cc b/chromium/device/fido/u2f_apdu_fuzzer.cc
new file mode 100644
index 00000000000..badd97f5162
--- /dev/null
+++ b/chromium/device/fido/u2f_apdu_fuzzer.cc
@@ -0,0 +1,19 @@
+// 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.
+
+#include <stddef.h>
+#include <stdint.h>
+#include <algorithm>
+
+#include "device/fido/u2f_apdu_command.h"
+#include "device/fido/u2f_apdu_response.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::vector<uint8_t> input(data, data + size);
+ std::unique_ptr<device::U2fApduCommand> cmd =
+ device::U2fApduCommand::CreateFromMessage(input);
+ std::unique_ptr<device::U2fApduResponse> rsp =
+ device::U2fApduResponse::CreateFromMessage(input);
+ return 0;
+}
diff --git a/chromium/device/fido/u2f_apdu_response.cc b/chromium/device/fido/u2f_apdu_response.cc
new file mode 100644
index 00000000000..407453a9ac7
--- /dev/null
+++ b/chromium/device/fido/u2f_apdu_response.cc
@@ -0,0 +1,32 @@
+// 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.
+
+#include "base/memory/ptr_util.h"
+
+#include "device/fido/u2f_apdu_response.h"
+
+namespace device {
+std::unique_ptr<U2fApduResponse> U2fApduResponse::CreateFromMessage(
+ const std::vector<uint8_t>& message) {
+ uint16_t status_bytes;
+ Status response_status;
+
+ // Invalid message size, data is appended by status byte
+ if (message.size() < 2)
+ return nullptr;
+ status_bytes = message[message.size() - 2] << 8;
+ status_bytes |= message[message.size() - 1];
+ response_status = static_cast<Status>(status_bytes);
+ std::vector<uint8_t> data(message.begin(), message.end() - 2);
+
+ return std::make_unique<U2fApduResponse>(std::move(data), response_status);
+}
+
+U2fApduResponse::U2fApduResponse(std::vector<uint8_t> message,
+ Status response_status)
+ : response_status_(response_status), data_(std::move(message)) {}
+
+U2fApduResponse::~U2fApduResponse() = default;
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_apdu_response.h b/chromium/device/fido/u2f_apdu_response.h
new file mode 100644
index 00000000000..8bb37f702b1
--- /dev/null
+++ b/chromium/device/fido/u2f_apdu_response.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_APDU_RESPONSE_H_
+#define DEVICE_FIDO_U2F_APDU_RESPONSE_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+
+namespace device {
+
+// APDU responses are defined as part of ISO 7816-4. Serialized responses
+// consist of a data field of varying length, up to a maximum 65536, and a
+// two byte status field.
+class U2fApduResponse {
+ public:
+ // Status bytes are specified in ISO 7816-4
+ enum class Status : uint16_t {
+ SW_NO_ERROR = 0x9000,
+ SW_CONDITIONS_NOT_SATISFIED = 0x6985,
+ SW_WRONG_DATA = 0x6A80,
+ SW_WRONG_LENGTH = 0x6700,
+ };
+
+ U2fApduResponse(std::vector<uint8_t> message, Status response_status);
+ ~U2fApduResponse();
+
+ // Create a APDU response from the serialized message
+ static std::unique_ptr<U2fApduResponse> CreateFromMessage(
+ const std::vector<uint8_t>& data);
+ std::vector<uint8_t> GetEncodedResponse() const;
+ const std::vector<uint8_t> data() const { return data_; };
+ Status status() const { return response_status_; };
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestDeserializeResponse);
+
+ Status response_status_;
+ std::vector<uint8_t> data_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_APDU_RESPONSE_H_
diff --git a/chromium/device/fido/u2f_apdu_unittest.cc b/chromium/device/fido/u2f_apdu_unittest.cc
new file mode 100644
index 00000000000..4a717bec7e8
--- /dev/null
+++ b/chromium/device/fido/u2f_apdu_unittest.cc
@@ -0,0 +1,273 @@
+// 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.
+
+#include "base/memory/ptr_util.h"
+#include "device/fido/u2f_apdu_command.h"
+#include "device/fido/u2f_apdu_response.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+class U2fApduTest : public testing::Test {};
+
+TEST_F(U2fApduTest, TestDeserializeBasic) {
+ uint8_t cla = 0xAA;
+ uint8_t ins = 0xAB;
+ uint8_t p1 = 0xAC;
+ uint8_t p2 = 0xAD;
+ std::vector<uint8_t> message = {cla, ins, p1, p2};
+ std::unique_ptr<U2fApduCommand> cmd =
+ U2fApduCommand::CreateFromMessage(message);
+
+ EXPECT_EQ(static_cast<size_t>(0), cmd->response_length_);
+ EXPECT_THAT(cmd->data_, testing::ContainerEq(std::vector<uint8_t>()));
+ EXPECT_EQ(cmd->cla_, cla);
+ EXPECT_EQ(cmd->ins_, ins);
+ EXPECT_EQ(cmd->p1_, p1);
+ EXPECT_EQ(cmd->p2_, p2);
+
+ // Invalid length
+ message = {cla, ins, p1};
+ EXPECT_EQ(nullptr, U2fApduCommand::CreateFromMessage(message));
+ message.push_back(p2);
+ message.push_back(0);
+ message.push_back(0xFF);
+ message.push_back(0xFF);
+ std::vector<uint8_t> oversized(U2fApduCommand::kApduMaxDataLength);
+ message.insert(message.end(), oversized.begin(), oversized.end());
+ message.push_back(0);
+ message.push_back(0);
+ EXPECT_NE(nullptr, U2fApduCommand::CreateFromMessage(message));
+ message.push_back(0);
+ EXPECT_EQ(nullptr, U2fApduCommand::CreateFromMessage(message));
+}
+
+TEST_F(U2fApduTest, TestDeserializeComplex) {
+ uint8_t cla = 0xAA;
+ uint8_t ins = 0xAB;
+ uint8_t p1 = 0xAC;
+ uint8_t p2 = 0xAD;
+ std::vector<uint8_t> data(
+ U2fApduCommand::kApduMaxDataLength - U2fApduCommand::kApduMaxHeader - 2,
+ 0x7F);
+ std::vector<uint8_t> message = {cla, ins, p1, p2, 0};
+ message.push_back((data.size() >> 8) & 0xff);
+ message.push_back(data.size() & 0xff);
+ message.insert(message.end(), data.begin(), data.end());
+
+ // Create a message with no response expected
+ std::unique_ptr<U2fApduCommand> cmd_no_response =
+ U2fApduCommand::CreateFromMessage(message);
+ EXPECT_EQ(static_cast<size_t>(0), cmd_no_response->response_length_);
+ EXPECT_THAT(data, testing::ContainerEq(cmd_no_response->data_));
+ EXPECT_EQ(cmd_no_response->cla_, cla);
+ EXPECT_EQ(cmd_no_response->ins_, ins);
+ EXPECT_EQ(cmd_no_response->p1_, p1);
+ EXPECT_EQ(cmd_no_response->p2_, p2);
+
+ // Add response length to message
+ message.push_back(0xF1);
+ message.push_back(0xD0);
+ std::unique_ptr<U2fApduCommand> cmd =
+ U2fApduCommand::CreateFromMessage(message);
+ EXPECT_THAT(data, testing::ContainerEq(cmd->data_));
+ EXPECT_EQ(cmd->cla_, cla);
+ EXPECT_EQ(cmd->ins_, ins);
+ EXPECT_EQ(cmd->p1_, p1);
+ EXPECT_EQ(cmd->p2_, p2);
+ EXPECT_EQ(static_cast<size_t>(0xF1D0), cmd->response_length_);
+}
+
+TEST_F(U2fApduTest, TestDeserializeResponse) {
+ U2fApduResponse::Status status;
+ std::unique_ptr<U2fApduResponse> response;
+ std::vector<uint8_t> test_vector;
+
+ // Invalid length
+ std::vector<uint8_t> message = {0xAA};
+ EXPECT_EQ(nullptr, U2fApduResponse::CreateFromMessage(message));
+
+ // Valid length and status
+ status = U2fApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED;
+ message = {static_cast<uint8_t>(static_cast<uint16_t>(status) >> 8),
+ static_cast<uint8_t>(status)};
+ response = U2fApduResponse::CreateFromMessage(message);
+ ASSERT_NE(nullptr, response);
+ EXPECT_EQ(U2fApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED,
+ response->response_status_);
+ EXPECT_THAT(response->data_, testing::ContainerEq(std::vector<uint8_t>()));
+
+ // Valid length and status
+ status = U2fApduResponse::Status::SW_NO_ERROR;
+ message = {static_cast<uint8_t>(static_cast<uint16_t>(status) >> 8),
+ static_cast<uint8_t>(status)};
+ test_vector = {0x01, 0x02, 0xEF, 0xFF};
+ message.insert(message.begin(), test_vector.begin(), test_vector.end());
+ response = U2fApduResponse::CreateFromMessage(message);
+ ASSERT_NE(nullptr, response);
+ EXPECT_EQ(U2fApduResponse::Status::SW_NO_ERROR, response->response_status_);
+ EXPECT_THAT(response->data_, testing::ContainerEq(test_vector));
+}
+
+TEST_F(U2fApduTest, TestSerializeCommand) {
+ auto cmd = std::make_unique<U2fApduCommand>();
+
+ cmd->set_cla(0xA);
+ cmd->set_ins(0xB);
+ cmd->set_p1(0xC);
+ cmd->set_p2(0xD);
+
+ // No data, no response expected
+ std::vector<uint8_t> expected({0xA, 0xB, 0xC, 0xD});
+ ASSERT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
+ EXPECT_THAT(
+ expected,
+ testing::ContainerEq(
+ U2fApduCommand::CreateFromMessage(expected)->GetEncodedCommand()));
+
+ // No data, response expected
+ cmd->set_response_length(0xCAFE);
+ expected = {0xA, 0xB, 0xC, 0xD, 0x0, 0xCA, 0xFE};
+ EXPECT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
+ EXPECT_THAT(
+ expected,
+ testing::ContainerEq(
+ U2fApduCommand::CreateFromMessage(expected)->GetEncodedCommand()));
+
+ // Data exists, response expected
+ std::vector<uint8_t> data({0x1, 0x2, 0x3, 0x4});
+ cmd->set_data(data);
+ expected = {0xA, 0xB, 0xC, 0xD, 0x0, 0x0, 0x4,
+ 0x1, 0x2, 0x3, 0x4, 0xCA, 0xFE};
+ EXPECT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
+ EXPECT_THAT(
+ expected,
+ testing::ContainerEq(
+ U2fApduCommand::CreateFromMessage(expected)->GetEncodedCommand()));
+
+ // Data exists, no response expected
+ cmd->set_response_length(0);
+ expected = {0xA, 0xB, 0xC, 0xD, 0x0, 0x0, 0x4, 0x1, 0x2, 0x3, 0x4};
+ EXPECT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
+ EXPECT_THAT(
+ expected,
+ testing::ContainerEq(
+ U2fApduCommand::CreateFromMessage(expected)->GetEncodedCommand()));
+}
+
+TEST_F(U2fApduTest, TestSerializeEdgeCases) {
+ auto cmd = std::make_unique<U2fApduCommand>();
+
+ cmd->set_cla(0xA);
+ cmd->set_ins(0xB);
+ cmd->set_p1(0xC);
+ cmd->set_p2(0xD);
+
+ // Set response length to maximum, which should serialize to 0x0000
+ cmd->set_response_length(U2fApduCommand::kApduMaxResponseLength);
+ std::vector<uint8_t> expected = {0xA, 0xB, 0xC, 0xD, 0x0, 0x0, 0x0};
+ EXPECT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
+ EXPECT_THAT(
+ expected,
+ testing::ContainerEq(
+ U2fApduCommand::CreateFromMessage(expected)->GetEncodedCommand()));
+
+ // Maximum data size
+ std::vector<uint8_t> oversized(U2fApduCommand::kApduMaxDataLength);
+ cmd->set_data(oversized);
+ EXPECT_THAT(cmd->GetEncodedCommand(),
+ testing::ContainerEq(
+ U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
+ ->GetEncodedCommand()));
+}
+
+TEST_F(U2fApduTest, TestCreateSign) {
+ std::vector<uint8_t> appid(U2fApduCommand::kAppIdDigestLen, 0x01);
+ std::vector<uint8_t> challenge(U2fApduCommand::kChallengeDigestLen, 0xff);
+ std::vector<uint8_t> key_handle(U2fApduCommand::kMaxKeyHandleLength);
+
+ std::unique_ptr<U2fApduCommand> cmd =
+ U2fApduCommand::CreateSign(appid, challenge, key_handle);
+ ASSERT_NE(nullptr, cmd);
+ EXPECT_THAT(U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
+ ->GetEncodedCommand(),
+ testing::ContainerEq(cmd->GetEncodedCommand()));
+ // Expect null result with incorrectly sized key handle
+ key_handle.push_back(0x0f);
+ cmd = U2fApduCommand::CreateSign(appid, challenge, key_handle);
+ EXPECT_EQ(nullptr, cmd);
+ key_handle.pop_back();
+ // Expect null result with incorrectly sized appid
+ appid.pop_back();
+ cmd = U2fApduCommand::CreateSign(appid, challenge, key_handle);
+ EXPECT_EQ(nullptr, cmd);
+ appid.push_back(0xff);
+ // Expect null result with incorrectly sized challenge
+ challenge.push_back(0x0);
+ cmd = U2fApduCommand::CreateSign(appid, challenge, key_handle);
+ EXPECT_EQ(nullptr, cmd);
+}
+
+TEST_F(U2fApduTest, TestCreateRegister) {
+ constexpr bool kNoIndividualAttestation = false;
+ constexpr bool kIndividualAttestation = true;
+ std::vector<uint8_t> appid(U2fApduCommand::kAppIdDigestLen, 0x01);
+ std::vector<uint8_t> challenge(U2fApduCommand::kChallengeDigestLen, 0xff);
+ std::unique_ptr<U2fApduCommand> cmd = U2fApduCommand::CreateRegister(
+ appid, challenge, kNoIndividualAttestation);
+ ASSERT_NE(nullptr, cmd);
+ std::vector<uint8_t> encoded = cmd->GetEncodedCommand();
+ ASSERT_LE(2u, encoded.size());
+ // Individual attestation bit should be cleared.
+ EXPECT_EQ(0, encoded[2] & 0x80);
+ EXPECT_THAT(U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
+ ->GetEncodedCommand(),
+ testing::ContainerEq(encoded));
+
+ cmd =
+ U2fApduCommand::CreateRegister(appid, challenge, kIndividualAttestation);
+ ASSERT_NE(nullptr, cmd);
+ encoded = cmd->GetEncodedCommand();
+ ASSERT_LE(2u, encoded.size());
+ // Individual attestation bit should be set.
+ EXPECT_EQ(0x80, encoded[2] & 0x80);
+
+ // Expect null result with incorrectly sized appid
+ appid.push_back(0xff);
+ cmd = U2fApduCommand::CreateRegister(appid, challenge,
+ kNoIndividualAttestation);
+ EXPECT_EQ(nullptr, cmd);
+ appid.pop_back();
+ // Expect null result with incorrectly sized challenge
+ challenge.push_back(0xff);
+ cmd = U2fApduCommand::CreateRegister(appid, challenge,
+ kNoIndividualAttestation);
+ EXPECT_EQ(nullptr, cmd);
+}
+
+TEST_F(U2fApduTest, TestCreateVersion) {
+ std::unique_ptr<U2fApduCommand> cmd = U2fApduCommand::CreateVersion();
+ std::vector<uint8_t> expected = {
+ 0x0, U2fApduCommand::kInsU2fVersion, 0x0, 0x0, 0x0, 0x0, 0x0};
+
+ EXPECT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
+ EXPECT_THAT(U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
+ ->GetEncodedCommand(),
+ testing::ContainerEq(cmd->GetEncodedCommand()));
+}
+
+TEST_F(U2fApduTest, TestCreateLegacyVersion) {
+ std::unique_ptr<U2fApduCommand> cmd = U2fApduCommand::CreateLegacyVersion();
+ // Legacy version command contains 2 extra null bytes compared to ISO 7816-4
+ // format
+ std::vector<uint8_t> expected = {
+ 0x0, U2fApduCommand::kInsU2fVersion, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+
+ EXPECT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
+ EXPECT_THAT(U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
+ ->GetEncodedCommand(),
+ testing::ContainerEq(cmd->GetEncodedCommand()));
+}
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_connection.cc b/chromium/device/fido/u2f_ble_connection.cc
new file mode 100644
index 00000000000..0e8a13a17bf
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_connection.cc
@@ -0,0 +1,579 @@
+// 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.
+
+#include "device/fido/u2f_ble_connection.h"
+
+#include <utility>
+
+#include "base/barrier_closure.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_gatt_connection.h"
+#include "device/bluetooth/bluetooth_gatt_notify_session.h"
+#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
+#include "device/bluetooth/bluetooth_remote_gatt_service.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "device/fido/u2f_ble_uuids.h"
+
+namespace device {
+
+namespace {
+
+constexpr const char* ToString(BluetoothDevice::ConnectErrorCode error_code) {
+ switch (error_code) {
+ case BluetoothDevice::ERROR_AUTH_CANCELED:
+ return "ERROR_AUTH_CANCELED";
+ case BluetoothDevice::ERROR_AUTH_FAILED:
+ return "ERROR_AUTH_FAILED";
+ case BluetoothDevice::ERROR_AUTH_REJECTED:
+ return "ERROR_AUTH_REJECTED";
+ case BluetoothDevice::ERROR_AUTH_TIMEOUT:
+ return "ERROR_AUTH_TIMEOUT";
+ case BluetoothDevice::ERROR_FAILED:
+ return "ERROR_FAILED";
+ case BluetoothDevice::ERROR_INPROGRESS:
+ return "ERROR_INPROGRESS";
+ case BluetoothDevice::ERROR_UNKNOWN:
+ return "ERROR_UNKNOWN";
+ case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
+ return "ERROR_UNSUPPORTED_DEVICE";
+ default:
+ NOTREACHED();
+ return "";
+ }
+}
+
+constexpr const char* ToString(BluetoothGattService::GattErrorCode error_code) {
+ switch (error_code) {
+ case BluetoothGattService::GATT_ERROR_UNKNOWN:
+ return "GATT_ERROR_UNKNOWN";
+ case BluetoothGattService::GATT_ERROR_FAILED:
+ return "GATT_ERROR_FAILED";
+ case BluetoothGattService::GATT_ERROR_IN_PROGRESS:
+ return "GATT_ERROR_IN_PROGRESS";
+ case BluetoothGattService::GATT_ERROR_INVALID_LENGTH:
+ return "GATT_ERROR_INVALID_LENGTH";
+ case BluetoothGattService::GATT_ERROR_NOT_PERMITTED:
+ return "GATT_ERROR_NOT_PERMITTED";
+ case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED:
+ return "GATT_ERROR_NOT_AUTHORIZED";
+ case BluetoothGattService::GATT_ERROR_NOT_PAIRED:
+ return "GATT_ERROR_NOT_PAIRED";
+ case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED:
+ return "GATT_ERROR_NOT_SUPPORTED";
+ default:
+ NOTREACHED();
+ return "";
+ }
+}
+
+} // namespace
+
+U2fBleConnection::U2fBleConnection(std::string device_address)
+ : address_(std::move(device_address)), weak_factory_(this) {}
+
+U2fBleConnection::U2fBleConnection(
+ std::string device_address,
+ ConnectionStatusCallback connection_status_callback,
+ ReadCallback read_callback)
+ : address_(std::move(device_address)),
+ connection_status_callback_(std::move(connection_status_callback)),
+ read_callback_(std::move(read_callback)),
+ weak_factory_(this) {
+ DCHECK(!address_.empty());
+}
+
+U2fBleConnection::~U2fBleConnection() {
+ if (adapter_)
+ adapter_->RemoveObserver(this);
+}
+
+void U2fBleConnection::Connect() {
+ BluetoothAdapterFactory::GetAdapter(
+ base::Bind(&U2fBleConnection::OnGetAdapter, weak_factory_.GetWeakPtr()));
+}
+
+void U2fBleConnection::ReadControlPointLength(
+ ControlPointLengthCallback callback) {
+ const BluetoothRemoteGattService* u2f_service = GetU2fService();
+ if (!u2f_service) {
+ std::move(callback).Run(base::nullopt);
+ return;
+ }
+
+ BluetoothRemoteGattCharacteristic* control_point_length =
+ u2f_service->GetCharacteristic(*control_point_length_id_);
+ if (!control_point_length) {
+ DLOG(ERROR) << "No Control Point Length characteristic present.";
+ std::move(callback).Run(base::nullopt);
+ return;
+ }
+
+ auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
+ control_point_length->ReadRemoteCharacteristic(
+ base::Bind(OnReadControlPointLength, copyable_callback),
+ base::Bind(OnReadControlPointLengthError, copyable_callback));
+}
+
+void U2fBleConnection::ReadServiceRevisions(ServiceRevisionsCallback callback) {
+ const BluetoothRemoteGattService* u2f_service = GetU2fService();
+ if (!u2f_service) {
+ std::move(callback).Run({});
+ return;
+ }
+
+ DCHECK(service_revision_id_ || service_revision_bitfield_id_);
+ BluetoothRemoteGattCharacteristic* service_revision =
+ service_revision_id_
+ ? u2f_service->GetCharacteristic(*service_revision_id_)
+ : nullptr;
+
+ BluetoothRemoteGattCharacteristic* service_revision_bitfield =
+ service_revision_bitfield_id_
+ ? u2f_service->GetCharacteristic(*service_revision_bitfield_id_)
+ : nullptr;
+
+ if (!service_revision && !service_revision_bitfield) {
+ DLOG(ERROR) << "Service Revision Characteristics do not exist.";
+ std::move(callback).Run({});
+ return;
+ }
+
+ // Start from a clean state.
+ service_revisions_.clear();
+
+ // In order to obtain the full set of supported service revisions it is
+ // possible that both the |service_revision_| and |service_revision_bitfield_|
+ // characteristics must be read. Potentially we need to take the union of
+ // the individually supported service revisions, hence the indirection to
+ // ReturnServiceRevisions() is introduced.
+ base::Closure copyable_callback = base::AdaptCallbackForRepeating(
+ base::BindOnce(&U2fBleConnection::ReturnServiceRevisions,
+ weak_factory_.GetWeakPtr(), std::move(callback)));
+
+ // If the Service Revision Bitfield characteristic is not present, only
+ // attempt to read the Service Revision characteristic.
+ if (!service_revision_bitfield) {
+ service_revision->ReadRemoteCharacteristic(
+ base::Bind(&U2fBleConnection::OnReadServiceRevision,
+ weak_factory_.GetWeakPtr(), copyable_callback),
+ base::Bind(&U2fBleConnection::OnReadServiceRevisionError,
+ weak_factory_.GetWeakPtr(), copyable_callback));
+ return;
+ }
+
+ // If the Service Revision characteristic is not present, only
+ // attempt to read the Service Revision Bitfield characteristic.
+ if (!service_revision) {
+ service_revision_bitfield->ReadRemoteCharacteristic(
+ base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfield,
+ weak_factory_.GetWeakPtr(), copyable_callback),
+ base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfieldError,
+ weak_factory_.GetWeakPtr(), copyable_callback));
+ return;
+ }
+
+ // This is the case where both characteristics are present. These reads can
+ // happen in parallel, but both must finish before a result can be returned.
+ // Hence a BarrierClosure is introduced invoking ReturnServiceRevisions() once
+ // both characteristic reads are done.
+ base::RepeatingClosure barrier_closure =
+ base::BarrierClosure(2, copyable_callback);
+
+ service_revision->ReadRemoteCharacteristic(
+ base::Bind(&U2fBleConnection::OnReadServiceRevision,
+ weak_factory_.GetWeakPtr(), barrier_closure),
+ base::Bind(&U2fBleConnection::OnReadServiceRevisionError,
+ weak_factory_.GetWeakPtr(), barrier_closure));
+
+ service_revision_bitfield->ReadRemoteCharacteristic(
+ base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfield,
+ weak_factory_.GetWeakPtr(), barrier_closure),
+ base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfieldError,
+ weak_factory_.GetWeakPtr(), barrier_closure));
+}
+
+void U2fBleConnection::WriteControlPoint(const std::vector<uint8_t>& data,
+ WriteCallback callback) {
+ const BluetoothRemoteGattService* u2f_service = GetU2fService();
+ if (!u2f_service) {
+ std::move(callback).Run(false);
+ return;
+ }
+
+ BluetoothRemoteGattCharacteristic* control_point =
+ u2f_service->GetCharacteristic(*control_point_id_);
+ if (!control_point) {
+ DLOG(ERROR) << "Control Point characteristic not present.";
+ std::move(callback).Run(false);
+ return;
+ }
+
+ auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
+ control_point->WriteRemoteCharacteristic(
+ data, base::Bind(OnWrite, copyable_callback),
+ base::Bind(OnWriteError, copyable_callback));
+}
+
+void U2fBleConnection::WriteServiceRevision(ServiceRevision service_revision,
+ WriteCallback callback) {
+ const BluetoothRemoteGattService* u2f_service = GetU2fService();
+ if (!u2f_service) {
+ std::move(callback).Run(false);
+ return;
+ }
+
+ BluetoothRemoteGattCharacteristic* service_revision_bitfield =
+ u2f_service->GetCharacteristic(*service_revision_bitfield_id_);
+ if (!service_revision_bitfield) {
+ DLOG(ERROR) << "Service Revision Bitfield characteristic not present.";
+ std::move(callback).Run(false);
+ return;
+ }
+
+ std::vector<uint8_t> payload;
+ switch (service_revision) {
+ case ServiceRevision::VERSION_1_1:
+ payload.push_back(0x80);
+ break;
+ case ServiceRevision::VERSION_1_2:
+ payload.push_back(0x40);
+ break;
+ default:
+ DLOG(ERROR)
+ << "Write Service Revision Failed: Unsupported Service Revision.";
+ std::move(callback).Run(false);
+ return;
+ }
+
+ auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
+ service_revision_bitfield->WriteRemoteCharacteristic(
+ payload, base::Bind(OnWrite, copyable_callback),
+ base::Bind(OnWriteError, copyable_callback));
+}
+
+void U2fBleConnection::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) {
+ if (!adapter) {
+ DLOG(ERROR) << "Failed to get Adapter.";
+ OnConnectionError();
+ return;
+ }
+
+ DVLOG(2) << "Got Adapter: " << adapter->GetAddress();
+ adapter_ = std::move(adapter);
+ adapter_->AddObserver(this);
+ CreateGattConnection();
+}
+
+void U2fBleConnection::CreateGattConnection() {
+ BluetoothDevice* device = adapter_->GetDevice(address_);
+ if (!device) {
+ DLOG(ERROR) << "Failed to get Device.";
+ OnConnectionError();
+ return;
+ }
+
+ device->CreateGattConnection(
+ base::Bind(&U2fBleConnection::OnCreateGattConnection,
+ weak_factory_.GetWeakPtr()),
+ base::Bind(&U2fBleConnection::OnCreateGattConnectionError,
+ weak_factory_.GetWeakPtr()));
+}
+
+void U2fBleConnection::OnCreateGattConnection(
+ std::unique_ptr<BluetoothGattConnection> connection) {
+ connection_ = std::move(connection);
+
+ BluetoothDevice* device = adapter_->GetDevice(address_);
+ if (!device) {
+ DLOG(ERROR) << "Failed to get Device.";
+ OnConnectionError();
+ return;
+ }
+
+ if (device->IsGattServicesDiscoveryComplete())
+ ConnectToU2fService();
+}
+
+void U2fBleConnection::OnCreateGattConnectionError(
+ BluetoothDevice::ConnectErrorCode error_code) {
+ DLOG(ERROR) << "CreateGattConnection() failed: " << ToString(error_code);
+ OnConnectionError();
+}
+
+void U2fBleConnection::ConnectToU2fService() {
+ BluetoothDevice* device = adapter_->GetDevice(address_);
+ if (!device) {
+ DLOG(ERROR) << "Failed to get Device.";
+ OnConnectionError();
+ return;
+ }
+
+ DCHECK(device->IsGattServicesDiscoveryComplete());
+ const std::vector<BluetoothRemoteGattService*> services =
+ device->GetGattServices();
+ auto found =
+ std::find_if(services.begin(), services.end(), [](const auto* service) {
+ return service->GetUUID().canonical_value() == kU2fServiceUUID;
+ });
+
+ if (found == services.end()) {
+ DLOG(ERROR) << "Failed to get U2F Service.";
+ OnConnectionError();
+ return;
+ }
+
+ const BluetoothRemoteGattService* u2f_service = *found;
+ u2f_service_id_ = u2f_service->GetIdentifier();
+ DVLOG(2) << "Got U2F Service: " << *u2f_service_id_;
+
+ for (const auto* characteristic : u2f_service->GetCharacteristics()) {
+ // NOTE: Since GetUUID() returns a temporary |uuid| can't be a reference,
+ // even though canonical_value() returns a const reference.
+ const std::string uuid = characteristic->GetUUID().canonical_value();
+ if (uuid == kU2fControlPointLengthUUID) {
+ control_point_length_id_ = characteristic->GetIdentifier();
+ DVLOG(2) << "Got U2F Control Point Length: " << *control_point_length_id_;
+ } else if (uuid == kU2fControlPointUUID) {
+ control_point_id_ = characteristic->GetIdentifier();
+ DVLOG(2) << "Got U2F Control Point: " << *control_point_id_;
+ } else if (uuid == kU2fStatusUUID) {
+ status_id_ = characteristic->GetIdentifier();
+ DVLOG(2) << "Got U2F Status: " << *status_id_;
+ } else if (uuid == kU2fServiceRevisionUUID) {
+ service_revision_id_ = characteristic->GetIdentifier();
+ DVLOG(2) << "Got U2F Service Revision: " << *service_revision_id_;
+ } else if (uuid == kU2fServiceRevisionBitfieldUUID) {
+ service_revision_bitfield_id_ = characteristic->GetIdentifier();
+ DVLOG(2) << "Got U2F Service Revision Bitfield: "
+ << *service_revision_bitfield_id_;
+ }
+ }
+
+ if (!control_point_length_id_ || !control_point_id_ || !status_id_ ||
+ (!service_revision_id_ && !service_revision_bitfield_id_)) {
+ DLOG(ERROR) << "U2F characteristics missing.";
+ OnConnectionError();
+ return;
+ }
+
+ u2f_service->GetCharacteristic(*status_id_)
+ ->StartNotifySession(
+ base::Bind(&U2fBleConnection::OnStartNotifySession,
+ weak_factory_.GetWeakPtr()),
+ base::Bind(&U2fBleConnection::OnStartNotifySessionError,
+ weak_factory_.GetWeakPtr()));
+}
+
+void U2fBleConnection::OnStartNotifySession(
+ std::unique_ptr<BluetoothGattNotifySession> notify_session) {
+ notify_session_ = std::move(notify_session);
+ DVLOG(2) << "Created notification session. Connection established.";
+ connection_status_callback_.Run(true);
+}
+
+void U2fBleConnection::OnStartNotifySessionError(
+ BluetoothGattService::GattErrorCode error_code) {
+ DLOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code);
+ OnConnectionError();
+}
+
+void U2fBleConnection::OnConnectionError() {
+ connection_status_callback_.Run(false);
+
+ connection_.reset();
+ notify_session_.reset();
+
+ u2f_service_id_.reset();
+ control_point_length_id_.reset();
+ control_point_id_.reset();
+ status_id_.reset();
+ service_revision_id_.reset();
+ service_revision_bitfield_id_.reset();
+}
+
+const BluetoothRemoteGattService* U2fBleConnection::GetU2fService() const {
+ if (!adapter_) {
+ DLOG(ERROR) << "No adapter present.";
+ return nullptr;
+ }
+
+ const BluetoothDevice* device = adapter_->GetDevice(address_);
+ if (!device) {
+ DLOG(ERROR) << "No device present.";
+ return nullptr;
+ }
+
+ if (!u2f_service_id_) {
+ DLOG(ERROR) << "Unknown U2F service id.";
+ return nullptr;
+ }
+
+ const BluetoothRemoteGattService* u2f_service =
+ device->GetGattService(*u2f_service_id_);
+ if (!u2f_service) {
+ DLOG(ERROR) << "No U2F service present.";
+ return nullptr;
+ }
+
+ return u2f_service;
+}
+
+void U2fBleConnection::DeviceAdded(BluetoothAdapter* adapter,
+ BluetoothDevice* device) {
+ if (adapter != adapter_ || device->GetAddress() != address_)
+ return;
+ CreateGattConnection();
+}
+
+void U2fBleConnection::DeviceAddressChanged(BluetoothAdapter* adapter,
+ BluetoothDevice* device,
+ const std::string& old_address) {
+ if (adapter != adapter_ || old_address != address_)
+ return;
+ address_ = device->GetAddress();
+}
+
+void U2fBleConnection::DeviceChanged(BluetoothAdapter* adapter,
+ BluetoothDevice* device) {
+ if (adapter != adapter_ || device->GetAddress() != address_)
+ return;
+ if (connection_ && !device->IsGattConnected()) {
+ DLOG(ERROR) << "GATT Disconnected: " << device->GetAddress();
+ OnConnectionError();
+ }
+}
+
+void U2fBleConnection::GattCharacteristicValueChanged(
+ BluetoothAdapter* adapter,
+ BluetoothRemoteGattCharacteristic* characteristic,
+ const std::vector<uint8_t>& value) {
+ if (characteristic->GetIdentifier() != status_id_)
+ return;
+ DVLOG(2) << "Status characteristic value changed.";
+ read_callback_.Run(value);
+}
+
+void U2fBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter,
+ BluetoothDevice* device) {
+ if (adapter != adapter_ || device->GetAddress() != address_)
+ return;
+ ConnectToU2fService();
+}
+
+// static
+void U2fBleConnection::OnReadControlPointLength(
+ ControlPointLengthCallback callback,
+ const std::vector<uint8_t>& value) {
+ if (value.size() != 2) {
+ DLOG(ERROR) << "Wrong Control Point Length: " << value.size() << " bytes";
+ std::move(callback).Run(base::nullopt);
+ return;
+ }
+
+ uint16_t length = (value[0] << 8) | value[1];
+ DVLOG(2) << "Control Point Length: " << length;
+ std::move(callback).Run(length);
+}
+
+// static
+void U2fBleConnection::OnReadControlPointLengthError(
+ ControlPointLengthCallback callback,
+ BluetoothGattService::GattErrorCode error_code) {
+ DLOG(ERROR) << "Error reading Control Point Length: " << ToString(error_code);
+ std::move(callback).Run(base::nullopt);
+}
+
+void U2fBleConnection::OnReadServiceRevision(
+ base::OnceClosure callback,
+ const std::vector<uint8_t>& value) {
+ std::string service_revision(value.begin(), value.end());
+ DVLOG(2) << "Service Revision: " << service_revision;
+
+ if (service_revision == "1.0") {
+ service_revisions_.insert(ServiceRevision::VERSION_1_0);
+ } else if (service_revision == "1.1") {
+ service_revisions_.insert(ServiceRevision::VERSION_1_1);
+ } else if (service_revision == "1.2") {
+ service_revisions_.insert(ServiceRevision::VERSION_1_2);
+ } else {
+ DLOG(ERROR) << "Unknown Service Revision: " << service_revision;
+ std::move(callback).Run();
+ return;
+ }
+
+ std::move(callback).Run();
+}
+
+void U2fBleConnection::OnReadServiceRevisionError(
+ base::OnceClosure callback,
+ BluetoothGattService::GattErrorCode error_code) {
+ DLOG(ERROR) << "Error reading Service Revision: " << ToString(error_code);
+ std::move(callback).Run();
+}
+
+void U2fBleConnection::OnReadServiceRevisionBitfield(
+ base::OnceClosure callback,
+ const std::vector<uint8_t>& value) {
+ if (value.empty()) {
+ DLOG(ERROR) << "Service Revision Bitfield is empty.";
+ std::move(callback).Run();
+ return;
+ }
+
+ if (value.size() != 1u) {
+ DLOG(ERROR) << "Service Revision Bitfield has unexpected size: "
+ << value.size() << ". Ignoring all but the first byte.";
+ }
+
+ const uint8_t bitset = value[0];
+ if (bitset & 0x3F) {
+ DLOG(ERROR) << "Service Revision Bitfield has unexpected bits set: 0x"
+ << std::hex << (bitset & 0x3F)
+ << ". Ignoring all but the first two bits.";
+ }
+
+ if (bitset & 0x80) {
+ service_revisions_.insert(ServiceRevision::VERSION_1_1);
+ DVLOG(2) << "Detected Support for Service Revision 1.1";
+ }
+
+ if (bitset & 0x40) {
+ service_revisions_.insert(ServiceRevision::VERSION_1_2);
+ DVLOG(2) << "Detected Support for Service Revision 1.2";
+ }
+
+ std::move(callback).Run();
+}
+
+void U2fBleConnection::OnReadServiceRevisionBitfieldError(
+ base::OnceClosure callback,
+ BluetoothGattService::GattErrorCode error_code) {
+ DLOG(ERROR) << "Error reading Service Revision Bitfield: "
+ << ToString(error_code);
+ std::move(callback).Run();
+}
+
+void U2fBleConnection::ReturnServiceRevisions(
+ ServiceRevisionsCallback callback) {
+ std::move(callback).Run(std::move(service_revisions_));
+}
+
+// static
+void U2fBleConnection::OnWrite(WriteCallback callback) {
+ DVLOG(2) << "Write succeeded.";
+ std::move(callback).Run(true);
+}
+
+// static
+void U2fBleConnection::OnWriteError(
+ WriteCallback callback,
+ BluetoothGattService::GattErrorCode error_code) {
+ DLOG(ERROR) << "Write Failed: " << ToString(error_code);
+ std::move(callback).Run(false);
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_connection.h b/chromium/device/fido/u2f_ble_connection.h
new file mode 100644
index 00000000000..ac45a15a0a2
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_connection.h
@@ -0,0 +1,162 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_BLE_CONNECTION_H_
+#define DEVICE_FIDO_U2F_BLE_CONNECTION_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_gatt_service.h"
+
+namespace device {
+
+class BluetoothGattConnection;
+class BluetoothGattNotifySession;
+class BluetoothRemoteGattCharacteristic;
+class BluetoothRemoteGattService;
+
+// A connection to the U2F service of an authenticator over BLE. Detailed
+// specification of the BLE device can be found here:
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_gatt-service-description
+//
+// Currently this code does not handle devices that need pairing. This is fine
+// for non-BlueZ platforms, as here accessing a protected characteristic will
+// trigger an OS level dialog if pairing is required. However, for BlueZ
+// platforms pairing must have been done externally, for example using the
+// `bluetoothctl` command.
+//
+// TODO(crbug.com/763303): Add support for pairing from within this class and
+// provide users with an option to manually specify a PIN code.
+class U2fBleConnection : public BluetoothAdapter::Observer {
+ public:
+ enum class ServiceRevision {
+ VERSION_1_0,
+ VERSION_1_1,
+ VERSION_1_2,
+ };
+
+ // This callback informs clients repeatedly about changes in the device
+ // connection. This class makes an initial connection attempt on construction,
+ // which result in returned via this callback. Future invocations happen if
+ // devices connect or disconnect from the adapter.
+ using ConnectionStatusCallback = base::RepeatingCallback<void(bool)>;
+ using WriteCallback = base::OnceCallback<void(bool)>;
+ using ReadCallback = base::RepeatingCallback<void(std::vector<uint8_t>)>;
+ using ControlPointLengthCallback =
+ base::OnceCallback<void(base::Optional<uint16_t>)>;
+ using ServiceRevisionsCallback =
+ base::OnceCallback<void(std::set<ServiceRevision>)>;
+
+ U2fBleConnection(std::string device_address,
+ ConnectionStatusCallback connection_status_callback,
+ ReadCallback read_callback);
+ ~U2fBleConnection() override;
+
+ const std::string& address() const { return address_; }
+
+ virtual void Connect();
+ virtual void ReadControlPointLength(ControlPointLengthCallback callback);
+ virtual void ReadServiceRevisions(ServiceRevisionsCallback callback);
+ virtual void WriteControlPoint(const std::vector<uint8_t>& data,
+ WriteCallback callback);
+ virtual void WriteServiceRevision(ServiceRevision service_revision,
+ WriteCallback callback);
+
+ protected:
+ explicit U2fBleConnection(std::string device_address);
+
+ private:
+ // BluetoothAdapter::Observer:
+ void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override;
+ void DeviceAddressChanged(BluetoothAdapter* adapter,
+ BluetoothDevice* device,
+ const std::string& old_address) override;
+ void DeviceChanged(BluetoothAdapter* adapter,
+ BluetoothDevice* device) override;
+ void GattCharacteristicValueChanged(
+ BluetoothAdapter* adapter,
+ BluetoothRemoteGattCharacteristic* characteristic,
+ const std::vector<uint8_t>& value) override;
+ void GattServicesDiscovered(BluetoothAdapter* adapter,
+ BluetoothDevice* device) override;
+
+ void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter);
+
+ void CreateGattConnection();
+ void OnCreateGattConnection(
+ std::unique_ptr<BluetoothGattConnection> connection);
+ void OnCreateGattConnectionError(
+ BluetoothDevice::ConnectErrorCode error_code);
+
+ void ConnectToU2fService();
+
+ void OnStartNotifySession(
+ std::unique_ptr<BluetoothGattNotifySession> notify_session);
+ void OnStartNotifySessionError(
+ BluetoothGattService::GattErrorCode error_code);
+
+ void OnConnectionError();
+
+ const BluetoothRemoteGattService* GetU2fService() const;
+
+ static void OnReadControlPointLength(ControlPointLengthCallback callback,
+ const std::vector<uint8_t>& value);
+ static void OnReadControlPointLengthError(
+ ControlPointLengthCallback callback,
+ BluetoothGattService::GattErrorCode error_code);
+
+ void OnReadServiceRevision(base::OnceClosure callback,
+ const std::vector<uint8_t>& value);
+ void OnReadServiceRevisionError(
+ base::OnceClosure callback,
+ BluetoothGattService::GattErrorCode error_code);
+
+ void OnReadServiceRevisionBitfield(base::OnceClosure callback,
+ const std::vector<uint8_t>& value);
+ void OnReadServiceRevisionBitfieldError(
+ base::OnceClosure callback,
+ BluetoothGattService::GattErrorCode error_code);
+ void ReturnServiceRevisions(ServiceRevisionsCallback callback);
+
+ static void OnWrite(WriteCallback callback);
+ static void OnWriteError(WriteCallback callback,
+ BluetoothGattService::GattErrorCode error_code);
+
+ std::string address_;
+ ConnectionStatusCallback connection_status_callback_;
+ ReadCallback read_callback_;
+
+ scoped_refptr<BluetoothAdapter> adapter_;
+ std::unique_ptr<BluetoothGattConnection> connection_;
+ std::unique_ptr<BluetoothGattNotifySession> notify_session_;
+
+ base::Optional<std::string> u2f_service_id_;
+ base::Optional<std::string> control_point_length_id_;
+ base::Optional<std::string> control_point_id_;
+ base::Optional<std::string> status_id_;
+ base::Optional<std::string> service_revision_id_;
+ base::Optional<std::string> service_revision_bitfield_id_;
+
+ std::set<ServiceRevision> service_revisions_;
+
+ base::WeakPtrFactory<U2fBleConnection> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(U2fBleConnection);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_BLE_CONNECTION_H_
diff --git a/chromium/device/fido/u2f_ble_connection_unittest.cc b/chromium/device/fido/u2f_ble_connection_unittest.cc
new file mode 100644
index 00000000000..7b288770dda
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_connection_unittest.cc
@@ -0,0 +1,778 @@
+// 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.
+
+#include "device/fido/u2f_ble_connection.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/strings/string_piece.h"
+#include "base/test/scoped_task_environment.h"
+#include "build/build_config.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/bluetooth_test.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_service.h"
+#include "device/fido/u2f_ble_uuids.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_ANDROID)
+#include "device/bluetooth/test/bluetooth_test_android.h"
+#elif defined(OS_MACOSX)
+#include "device/bluetooth/test/bluetooth_test_mac.h"
+#elif defined(OS_WIN)
+#include "device/bluetooth/test/bluetooth_test_win.h"
+#elif defined(OS_CHROMEOS) || defined(OS_LINUX)
+#include "device/bluetooth/test/bluetooth_test_bluez.h"
+#endif
+
+namespace device {
+
+using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::Invoke;
+using ::testing::IsEmpty;
+using ::testing::Return;
+
+using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>;
+using NiceMockBluetoothDevice = ::testing::NiceMock<MockBluetoothDevice>;
+using NiceMockBluetoothGattService =
+ ::testing::NiceMock<MockBluetoothGattService>;
+using NiceMockBluetoothGattCharacteristic =
+ ::testing::NiceMock<MockBluetoothGattCharacteristic>;
+using NiceMockBluetoothGattConnection =
+ ::testing::NiceMock<MockBluetoothGattConnection>;
+using NiceMockBluetoothGattNotifySession =
+ ::testing::NiceMock<MockBluetoothGattNotifySession>;
+
+namespace {
+
+std::vector<uint8_t> ToByteVector(base::StringPiece str) {
+ return std::vector<uint8_t>(str.begin(), str.end());
+}
+
+BluetoothDevice* GetMockDevice(MockBluetoothAdapter* adapter,
+ const std::string& address) {
+ const std::vector<BluetoothDevice*> devices = adapter->GetMockDevices();
+ auto found = std::find_if(devices.begin(), devices.end(),
+ [&address](const auto* device) {
+ return device->GetAddress() == address;
+ });
+ return found != devices.end() ? *found : nullptr;
+}
+
+class TestConnectionStatusCallback {
+ public:
+ void OnStatus(bool status) {
+ status_ = status;
+ run_loop_->Quit();
+ }
+
+ bool WaitForResult() {
+ run_loop_->Run();
+ run_loop_.emplace();
+ return status_;
+ }
+
+ U2fBleConnection::ConnectionStatusCallback GetCallback() {
+ return base::BindRepeating(&TestConnectionStatusCallback::OnStatus,
+ base::Unretained(this));
+ }
+
+ private:
+ bool status_ = false;
+ base::Optional<base::RunLoop> run_loop_{base::in_place};
+};
+
+class TestReadCallback {
+ public:
+ void OnRead(std::vector<uint8_t> value) {
+ value_ = std::move(value);
+ run_loop_->Quit();
+ }
+
+ const std::vector<uint8_t> WaitForResult() {
+ run_loop_->Run();
+ run_loop_.emplace();
+ return value_;
+ }
+
+ U2fBleConnection::ReadCallback GetCallback() {
+ return base::BindRepeating(&TestReadCallback::OnRead,
+ base::Unretained(this));
+ }
+
+ private:
+ std::vector<uint8_t> value_;
+ 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_;
+ }
+
+ U2fBleConnection::ControlPointLengthCallback GetCallback() {
+ return base::BindOnce(
+ &TestReadControlPointLengthCallback::OnReadControlPointLength,
+ base::Unretained(this));
+ }
+
+ private:
+ base::Optional<uint16_t> value_;
+ base::Optional<base::RunLoop> run_loop_{base::in_place};
+};
+
+class TestReadServiceRevisionsCallback {
+ public:
+ void OnReadServiceRevisions(
+ std::set<U2fBleConnection::ServiceRevision> revisions) {
+ revisions_ = std::move(revisions);
+ run_loop_->Quit();
+ }
+
+ const std::set<U2fBleConnection::ServiceRevision>& WaitForResult() {
+ run_loop_->Run();
+ run_loop_.emplace();
+ return revisions_;
+ }
+
+ U2fBleConnection::ServiceRevisionsCallback GetCallback() {
+ return base::BindOnce(
+ &TestReadServiceRevisionsCallback::OnReadServiceRevisions,
+ base::Unretained(this));
+ }
+
+ private:
+ std::set<U2fBleConnection::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_;
+ }
+
+ U2fBleConnection::WriteCallback GetCallback() {
+ return base::BindOnce(&TestWriteCallback::OnWrite, base::Unretained(this));
+ }
+
+ private:
+ bool success_ = false;
+ base::Optional<base::RunLoop> run_loop_{base::in_place};
+};
+
+} // namespace
+
+class U2fBleConnectionTest : public ::testing::Test {
+ public:
+ U2fBleConnectionTest() {
+ ON_CALL(*adapter_, GetDevice(_))
+ .WillByDefault(Invoke([this](const std::string& address) {
+ return GetMockDevice(adapter_.get(), address);
+ }));
+
+ BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
+ }
+
+ void AddU2Device(const std::string& device_address) {
+ auto u2f_device = std::make_unique<NiceMockBluetoothDevice>(
+ adapter_.get(), /* bluetooth_class */ 0u,
+ BluetoothTest::kTestDeviceNameU2f, device_address, /* paired */ true,
+ /* connected */ false);
+ u2f_device_ = u2f_device.get();
+ adapter_->AddMockDevice(std::move(u2f_device));
+
+ ON_CALL(*u2f_device_, GetGattServices())
+ .WillByDefault(
+ Invoke(u2f_device_, &MockBluetoothDevice::GetMockServices));
+
+ ON_CALL(*u2f_device_, GetGattService(_))
+ .WillByDefault(
+ Invoke(u2f_device_, &MockBluetoothDevice::GetMockService));
+ AddU2fService();
+ }
+
+ void SetupConnectingU2fDevice(const std::string& device_address) {
+ auto run_cb_with_connection = [this, &device_address](
+ const auto& callback,
+ const auto& error_callback) {
+ auto connection = std::make_unique<NiceMockBluetoothGattConnection>(
+ adapter_, device_address);
+ connection_ = connection.get();
+ callback.Run(std::move(connection));
+ };
+
+ auto run_cb_with_notify_session = [this](const auto& callback,
+ const auto& error_callback) {
+ auto notify_session =
+ std::make_unique<NiceMockBluetoothGattNotifySession>(
+ u2f_status_->GetWeakPtr());
+ notify_session_ = notify_session.get();
+ callback.Run(std::move(notify_session));
+ };
+
+ ON_CALL(*u2f_device_, CreateGattConnection(_, _))
+ .WillByDefault(Invoke(run_cb_with_connection));
+
+ ON_CALL(*u2f_device_, IsGattServicesDiscoveryComplete())
+ .WillByDefault(Return(true));
+
+ ON_CALL(*u2f_status_, StartNotifySession(_, _))
+ .WillByDefault(Invoke(run_cb_with_notify_session));
+ }
+
+ void SimulateDisconnect(const std::string& device_address) {
+ if (u2f_device_->GetAddress() != device_address)
+ return;
+
+ u2f_device_->SetConnected(false);
+ adapter_->NotifyDeviceChanged(u2f_device_);
+ }
+
+ void SimulateDeviceAddressChange(const std::string& old_address,
+ const std::string& new_address) {
+ if (!u2f_device_ || u2f_device_->GetAddress() != old_address)
+ return;
+
+ ON_CALL(*u2f_device_, GetAddress()).WillByDefault(Return(new_address));
+
+ adapter_->NotifyDeviceChanged(u2f_device_);
+ for (auto& observer : adapter_->GetObservers())
+ observer.DeviceAddressChanged(adapter_.get(), u2f_device_, old_address);
+ }
+
+ void NotifyDeviceAdded(const std::string& device_address) {
+ auto* device = adapter_->GetDevice(device_address);
+ if (!device)
+ return;
+
+ for (auto& observer : adapter_->GetObservers())
+ observer.DeviceAdded(adapter_.get(), device);
+ }
+
+ void NotifyStatusChanged(const std::vector<uint8_t>& value) {
+ for (auto& observer : adapter_->GetObservers())
+ observer.GattCharacteristicValueChanged(adapter_.get(), u2f_status_,
+ value);
+ }
+
+ void SetNextReadControlPointLengthReponse(bool success,
+ const std::vector<uint8_t>& value) {
+ EXPECT_CALL(*u2f_control_point_length_, ReadRemoteCharacteristic(_, _))
+ .WillOnce(Invoke([success, value](const auto& callback,
+ const auto& error_callback) {
+ success ? callback.Run(value)
+ : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
+ }));
+ }
+
+ void SetNextReadServiceRevisionResponse(bool success,
+ const std::vector<uint8_t>& value) {
+ EXPECT_CALL(*u2f_service_revision_, ReadRemoteCharacteristic(_, _))
+ .WillOnce(Invoke([success, value](const auto& callback,
+ const auto& error_callback) {
+ success ? callback.Run(value)
+ : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
+ }));
+ }
+
+ void SetNextReadServiceRevisionBitfieldResponse(
+ bool success,
+ const std::vector<uint8_t>& value) {
+ EXPECT_CALL(*u2f_service_revision_bitfield_, ReadRemoteCharacteristic(_, _))
+ .WillOnce(Invoke([success, value](const auto& callback,
+ const auto& error_callback) {
+ success ? callback.Run(value)
+ : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
+ }));
+ }
+
+ void SetNextWriteControlPointResponse(bool success) {
+ EXPECT_CALL(*u2f_control_point_, WriteRemoteCharacteristic(_, _, _))
+ .WillOnce(Invoke([success](const auto& data, const auto& callback,
+ const auto& error_callback) {
+ success ? callback.Run()
+ : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
+ }));
+ }
+
+ void SetNextWriteServiceRevisionResponse(bool success) {
+ EXPECT_CALL(*u2f_service_revision_bitfield_,
+ WriteRemoteCharacteristic(_, _, _))
+ .WillOnce(Invoke([success](const auto& data, const auto& callback,
+ const auto& error_callback) {
+ success ? callback.Run()
+ : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
+ }));
+ }
+
+ void AddU2fService() {
+ auto u2f_service = std::make_unique<NiceMockBluetoothGattService>(
+ u2f_device_, "u2f_service", BluetoothUUID(kU2fServiceUUID),
+ /* is_primary */ true, /* is_local */ false);
+ u2f_service_ = u2f_service.get();
+ u2f_device_->AddMockService(std::move(u2f_service));
+
+ ON_CALL(*u2f_service_, GetCharacteristics())
+ .WillByDefault(Invoke(
+ u2f_service_, &MockBluetoothGattService::GetMockCharacteristics));
+
+ ON_CALL(*u2f_service_, GetCharacteristic(_))
+ .WillByDefault(Invoke(
+ u2f_service_, &MockBluetoothGattService::GetMockCharacteristic));
+ AddU2fCharacteristics();
+ }
+
+ void AddU2fCharacteristics() {
+ const bool is_local = false;
+ {
+ auto u2f_control_point =
+ std::make_unique<NiceMockBluetoothGattCharacteristic>(
+ u2f_service_, "u2f_control_point",
+ BluetoothUUID(kU2fControlPointUUID), is_local,
+ BluetoothGattCharacteristic::PROPERTY_WRITE,
+ BluetoothGattCharacteristic::PERMISSION_NONE);
+ u2f_control_point_ = u2f_control_point.get();
+ u2f_service_->AddMockCharacteristic(std::move(u2f_control_point));
+ }
+
+ {
+ auto u2f_status = std::make_unique<NiceMockBluetoothGattCharacteristic>(
+ u2f_service_, "u2f_status", BluetoothUUID(kU2fStatusUUID), is_local,
+ BluetoothGattCharacteristic::PROPERTY_NOTIFY,
+ BluetoothGattCharacteristic::PERMISSION_NONE);
+ u2f_status_ = u2f_status.get();
+ u2f_service_->AddMockCharacteristic(std::move(u2f_status));
+ }
+
+ {
+ auto u2f_control_point_length =
+ std::make_unique<NiceMockBluetoothGattCharacteristic>(
+ u2f_service_, "u2f_control_point_length",
+ BluetoothUUID(kU2fControlPointLengthUUID), is_local,
+ BluetoothGattCharacteristic::PROPERTY_READ,
+ BluetoothGattCharacteristic::PERMISSION_NONE);
+ u2f_control_point_length_ = u2f_control_point_length.get();
+ u2f_service_->AddMockCharacteristic(std::move(u2f_control_point_length));
+ }
+
+ {
+ auto u2f_service_revision =
+ std::make_unique<NiceMockBluetoothGattCharacteristic>(
+ u2f_service_, "u2f_service_revision",
+ BluetoothUUID(kU2fServiceRevisionUUID), is_local,
+ BluetoothGattCharacteristic::PROPERTY_READ,
+ BluetoothGattCharacteristic::PERMISSION_NONE);
+ u2f_service_revision_ = u2f_service_revision.get();
+ u2f_service_->AddMockCharacteristic(std::move(u2f_service_revision));
+ }
+
+ {
+ auto u2f_service_revision_bitfield =
+ std::make_unique<NiceMockBluetoothGattCharacteristic>(
+ u2f_service_, "u2f_service_revision_bitfield",
+ BluetoothUUID(kU2fServiceRevisionBitfieldUUID), is_local,
+ BluetoothGattCharacteristic::PROPERTY_READ |
+ BluetoothGattCharacteristic::PROPERTY_WRITE,
+ BluetoothGattCharacteristic::PERMISSION_NONE);
+ u2f_service_revision_bitfield_ = u2f_service_revision_bitfield.get();
+ u2f_service_->AddMockCharacteristic(
+ std::move(u2f_service_revision_bitfield));
+ }
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ scoped_refptr<MockBluetoothAdapter> adapter_ =
+ base::MakeRefCounted<NiceMockBluetoothAdapter>();
+
+ MockBluetoothDevice* u2f_device_;
+ MockBluetoothGattService* u2f_service_;
+
+ MockBluetoothGattCharacteristic* u2f_control_point_;
+ MockBluetoothGattCharacteristic* u2f_status_;
+ MockBluetoothGattCharacteristic* u2f_control_point_length_;
+ MockBluetoothGattCharacteristic* u2f_service_revision_;
+ MockBluetoothGattCharacteristic* u2f_service_revision_bitfield_;
+
+ MockBluetoothGattConnection* connection_;
+ MockBluetoothGattNotifySession* notify_session_;
+};
+
+TEST_F(U2fBleConnectionTest, Address) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ auto connect_do_nothing = [](bool) {};
+ auto read_do_nothing = [](std::vector<uint8_t>) {};
+
+ U2fBleConnection connection(device_address,
+ base::BindRepeating(connect_do_nothing),
+ base::BindRepeating(read_do_nothing));
+ connection.Connect();
+ EXPECT_EQ(device_address, connection.address());
+ AddU2Device(device_address);
+
+ SimulateDeviceAddressChange(device_address, "new_device_address");
+ EXPECT_EQ("new_device_address", connection.address());
+}
+
+TEST_F(U2fBleConnectionTest, DeviceNotPresent) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+ auto do_nothing = [](std::vector<uint8_t>) {};
+
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(do_nothing));
+ connection.Connect();
+ bool result = connection_status_callback.WaitForResult();
+ EXPECT_FALSE(result);
+}
+
+TEST_F(U2fBleConnectionTest, PreConnected) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+
+ auto do_nothing = [](std::vector<uint8_t>) {};
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(do_nothing));
+ connection.Connect();
+ EXPECT_TRUE(connection_status_callback.WaitForResult());
+}
+
+TEST_F(U2fBleConnectionTest, PostConnected) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+ auto do_nothing = [](std::vector<uint8_t>) {};
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(do_nothing));
+ connection.Connect();
+ bool result = connection_status_callback.WaitForResult();
+ EXPECT_FALSE(result);
+
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+ NotifyDeviceAdded(device_address);
+ EXPECT_TRUE(connection_status_callback.WaitForResult());
+}
+
+TEST_F(U2fBleConnectionTest, DeviceDisconnect) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+ auto do_nothing = [](std::vector<uint8_t>) {};
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(do_nothing));
+ connection.Connect();
+ bool result = connection_status_callback.WaitForResult();
+ EXPECT_TRUE(result);
+
+ SimulateDisconnect(device_address);
+ result = connection_status_callback.WaitForResult();
+ EXPECT_FALSE(result);
+}
+
+TEST_F(U2fBleConnectionTest, ReadStatusNotifications) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+ TestReadCallback read_callback;
+
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ read_callback.GetCallback());
+ connection.Connect();
+ EXPECT_TRUE(connection_status_callback.WaitForResult());
+
+ std::vector<uint8_t> payload = ToByteVector("foo");
+ NotifyStatusChanged(payload);
+ EXPECT_EQ(payload, read_callback.WaitForResult());
+
+ payload = ToByteVector("bar");
+ NotifyStatusChanged(payload);
+ EXPECT_EQ(payload, read_callback.WaitForResult());
+}
+
+TEST_F(U2fBleConnectionTest, ReadControlPointLength) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+ auto read_do_nothing = [](std::vector<uint8_t>) {};
+
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(read_do_nothing));
+ connection.Connect();
+ EXPECT_TRUE(connection_status_callback.WaitForResult());
+
+ TestReadControlPointLengthCallback length_callback;
+ SetNextReadControlPointLengthReponse(false, {});
+ connection.ReadControlPointLength(length_callback.GetCallback());
+ EXPECT_EQ(base::nullopt, length_callback.WaitForResult());
+
+ // 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());
+
+ SetNextReadControlPointLengthReponse(true, {0xAB});
+ connection.ReadControlPointLength(length_callback.GetCallback());
+ EXPECT_EQ(base::nullopt, length_callback.WaitForResult());
+
+ SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD});
+ connection.ReadControlPointLength(length_callback.GetCallback());
+ EXPECT_EQ(0xABCD, *length_callback.WaitForResult());
+
+ SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD, 0xEF});
+ connection.ReadControlPointLength(length_callback.GetCallback());
+ EXPECT_EQ(base::nullopt, length_callback.WaitForResult());
+}
+
+TEST_F(U2fBleConnectionTest, ReadServiceRevisions) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+ auto read_do_nothing = [](std::vector<uint8_t>) {};
+
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(read_do_nothing));
+ connection.Connect();
+ EXPECT_TRUE(connection_status_callback.WaitForResult());
+
+ TestReadServiceRevisionsCallback revisions_callback;
+ SetNextReadServiceRevisionResponse(false, {});
+ SetNextReadServiceRevisionBitfieldResponse(false, {});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty());
+
+ SetNextReadServiceRevisionResponse(true, ToByteVector("bogus"));
+ SetNextReadServiceRevisionBitfieldResponse(false, {});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty());
+
+ SetNextReadServiceRevisionResponse(true, ToByteVector("1.0"));
+ SetNextReadServiceRevisionBitfieldResponse(false, {});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(),
+ ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_0));
+
+ SetNextReadServiceRevisionResponse(true, ToByteVector("1.1"));
+ SetNextReadServiceRevisionBitfieldResponse(false, {});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(),
+ ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1));
+
+ SetNextReadServiceRevisionResponse(true, ToByteVector("1.2"));
+ SetNextReadServiceRevisionBitfieldResponse(false, {});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(),
+ ElementsAre(U2fBleConnection::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());
+
+ SetNextReadServiceRevisionResponse(false, {});
+ SetNextReadServiceRevisionBitfieldResponse(true, {0x00});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty());
+
+ SetNextReadServiceRevisionResponse(false, {});
+ SetNextReadServiceRevisionBitfieldResponse(true, {0x80});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(),
+ ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1));
+
+ SetNextReadServiceRevisionResponse(false, {});
+ SetNextReadServiceRevisionBitfieldResponse(true, {0x40});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(),
+ ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_2));
+
+ SetNextReadServiceRevisionResponse(false, {});
+ SetNextReadServiceRevisionBitfieldResponse(true, {0xC0});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(),
+ ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1,
+ U2fBleConnection::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(),
+ ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1,
+ U2fBleConnection::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(),
+ ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_1,
+ U2fBleConnection::ServiceRevision::VERSION_1_2));
+
+ // The combination of a service revision string and bitfield should be
+ // supported as well.
+ SetNextReadServiceRevisionResponse(true, ToByteVector("1.0"));
+ SetNextReadServiceRevisionBitfieldResponse(true, {0xC0});
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(),
+ ElementsAre(U2fBleConnection::ServiceRevision::VERSION_1_0,
+ U2fBleConnection::ServiceRevision::VERSION_1_1,
+ U2fBleConnection::ServiceRevision::VERSION_1_2));
+}
+
+TEST_F(U2fBleConnectionTest, WriteControlPoint) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+ auto read_do_nothing = [](std::vector<uint8_t>) {};
+
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(read_do_nothing));
+ connection.Connect();
+ bool result = connection_status_callback.WaitForResult();
+ EXPECT_TRUE(result);
+
+ TestWriteCallback write_callback;
+ SetNextWriteControlPointResponse(false);
+ connection.WriteControlPoint({}, write_callback.GetCallback());
+ result = write_callback.WaitForResult();
+ EXPECT_FALSE(result);
+
+ SetNextWriteControlPointResponse(true);
+ connection.WriteControlPoint({}, write_callback.GetCallback());
+ result = write_callback.WaitForResult();
+ EXPECT_TRUE(result);
+}
+
+TEST_F(U2fBleConnectionTest, WriteServiceRevision) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+ auto read_do_nothing = [](std::vector<uint8_t>) {};
+
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(read_do_nothing));
+ connection.Connect();
+ bool result = connection_status_callback.WaitForResult();
+ EXPECT_TRUE(result);
+
+ // Expect that errors are properly propagated.
+ TestWriteCallback write_callback;
+ SetNextWriteServiceRevisionResponse(false);
+ connection.WriteServiceRevision(
+ U2fBleConnection::ServiceRevision::VERSION_1_1,
+ write_callback.GetCallback());
+ result = write_callback.WaitForResult();
+ EXPECT_FALSE(result);
+
+ // Expect a successful write of version 1.1.
+ SetNextWriteServiceRevisionResponse(true);
+ connection.WriteServiceRevision(
+ U2fBleConnection::ServiceRevision::VERSION_1_1,
+ write_callback.GetCallback());
+ result = write_callback.WaitForResult();
+ EXPECT_TRUE(result);
+
+ // Expect a successful write of version 1.2.
+ SetNextWriteServiceRevisionResponse(true);
+ connection.WriteServiceRevision(
+ U2fBleConnection::ServiceRevision::VERSION_1_2,
+ write_callback.GetCallback());
+ result = write_callback.WaitForResult();
+ EXPECT_TRUE(result);
+
+ // Writing version 1.0 to the bitfield is not intended, so this should fail.
+ connection.WriteServiceRevision(
+ U2fBleConnection::ServiceRevision::VERSION_1_0,
+ write_callback.GetCallback());
+ result = write_callback.WaitForResult();
+ EXPECT_FALSE(result);
+}
+
+TEST_F(U2fBleConnectionTest, ReadsAndWriteFailWhenDisconnected) {
+ const std::string device_address = BluetoothTest::kTestDeviceAddress1;
+ TestConnectionStatusCallback connection_status_callback;
+
+ AddU2Device(device_address);
+ SetupConnectingU2fDevice(device_address);
+ auto do_nothing = [](std::vector<uint8_t>) {};
+ U2fBleConnection connection(device_address,
+ connection_status_callback.GetCallback(),
+ base::BindRepeating(do_nothing));
+ connection.Connect();
+ bool result = connection_status_callback.WaitForResult();
+ EXPECT_TRUE(result);
+
+ SimulateDisconnect(device_address);
+ result = connection_status_callback.WaitForResult();
+ EXPECT_FALSE(result);
+
+ // Reads should always fail on a disconnected device.
+ TestReadControlPointLengthCallback length_callback;
+ connection.ReadControlPointLength(length_callback.GetCallback());
+ EXPECT_EQ(base::nullopt, length_callback.WaitForResult());
+
+ TestReadServiceRevisionsCallback revisions_callback;
+ connection.ReadServiceRevisions(revisions_callback.GetCallback());
+ EXPECT_THAT(revisions_callback.WaitForResult(), IsEmpty());
+
+ // Writes should always fail on a disconnected device.
+ TestWriteCallback write_callback;
+ connection.WriteServiceRevision(
+ U2fBleConnection::ServiceRevision::VERSION_1_1,
+ write_callback.GetCallback());
+ result = write_callback.WaitForResult();
+ EXPECT_FALSE(result);
+
+ connection.WriteControlPoint({}, write_callback.GetCallback());
+ result = write_callback.WaitForResult();
+ EXPECT_FALSE(result);
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_device.cc b/chromium/device/fido/u2f_ble_device.cc
new file mode 100644
index 00000000000..330b4c57a6a
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_device.cc
@@ -0,0 +1,184 @@
+// 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.
+
+#include "device/fido/u2f_ble_device.h"
+
+#include "base/bind.h"
+#include "base/strings/string_piece.h"
+#include "device/fido/u2f_apdu_response.h"
+#include "device/fido/u2f_ble_frames.h"
+#include "device/fido/u2f_ble_transaction.h"
+
+namespace device {
+
+U2fBleDevice::U2fBleDevice(std::string address) : weak_factory_(this) {
+ connection_ = std::make_unique<U2fBleConnection>(
+ std::move(address),
+ base::BindRepeating(&U2fBleDevice::OnConnectionStatus,
+ base::Unretained(this)),
+ base::BindRepeating(&U2fBleDevice::OnStatusMessage,
+ base::Unretained(this)));
+}
+
+U2fBleDevice::U2fBleDevice(std::unique_ptr<U2fBleConnection> connection)
+ : connection_(std::move(connection)), weak_factory_(this) {}
+
+U2fBleDevice::~U2fBleDevice() = default;
+
+void U2fBleDevice::Connect() {
+ if (state_ != State::INIT)
+ return;
+
+ StartTimeout();
+ state_ = State::BUSY;
+ connection_->Connect();
+}
+
+void U2fBleDevice::SendPing(std::vector<uint8_t> data,
+ MessageCallback callback) {
+ pending_frames_.emplace(
+ U2fBleFrame(U2fCommandType::CMD_PING, std::move(data)),
+ base::BindOnce(
+ [](MessageCallback callback, base::Optional<U2fBleFrame> frame) {
+ std::move(callback).Run(
+ frame ? U2fReturnCode::SUCCESS : U2fReturnCode::FAILURE,
+ frame ? frame->data() : std::vector<uint8_t>());
+ },
+ std::move(callback)));
+ Transition();
+}
+
+// static
+std::string U2fBleDevice::GetId(base::StringPiece address) {
+ return std::string("ble:").append(address.begin(), address.end());
+}
+
+void U2fBleDevice::TryWink(WinkCallback callback) {
+ // U2F over BLE does not support winking.
+ std::move(callback).Run();
+}
+
+std::string U2fBleDevice::GetId() const {
+ return GetId(connection_->address());
+}
+
+U2fBleConnection::ConnectionStatusCallback
+U2fBleDevice::GetConnectionStatusCallbackForTesting() {
+ return base::BindRepeating(&U2fBleDevice::OnConnectionStatus,
+ base::Unretained(this));
+}
+
+U2fBleConnection::ReadCallback U2fBleDevice::GetReadCallbackForTesting() {
+ return base::BindRepeating(&U2fBleDevice::OnStatusMessage,
+ base::Unretained(this));
+}
+
+void U2fBleDevice::DeviceTransact(std::vector<uint8_t> command,
+ DeviceCallback callback) {
+ pending_frames_.emplace(
+ U2fBleFrame(U2fCommandType::CMD_MSG, std::move(command)),
+ base::BindOnce(
+ [](DeviceCallback callback, base::Optional<U2fBleFrame> frame) {
+ std::move(callback).Run(
+ frame.has_value(),
+ frame ? U2fApduResponse::CreateFromMessage(frame->data())
+ : nullptr);
+ },
+ std::move(callback)));
+ Transition();
+}
+
+base::WeakPtr<U2fDevice> U2fBleDevice::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+void U2fBleDevice::Transition() {
+ switch (state_) {
+ case State::INIT:
+ Connect();
+ break;
+ case State::CONNECTED:
+ StartTimeout();
+ state_ = State::BUSY;
+ connection_->ReadControlPointLength(base::BindOnce(
+ &U2fBleDevice::OnReadControlPointLength, base::Unretained(this)));
+ break;
+ case State::READY:
+ if (!pending_frames_.empty()) {
+ U2fBleFrame frame;
+ FrameCallback callback;
+ std::tie(frame, callback) = std::move(pending_frames_.front());
+ pending_frames_.pop();
+ SendRequestFrame(std::move(frame), std::move(callback));
+ }
+ break;
+ case State::BUSY:
+ break;
+ case State::DEVICE_ERROR:
+ auto self = GetWeakPtr();
+ // Executing callbacks may free |this|. Check |self| first.
+ while (self && !pending_frames_.empty()) {
+ // Respond to any pending frames.
+ FrameCallback cb = std::move(pending_frames_.front().second);
+ pending_frames_.pop();
+ std::move(cb).Run(base::nullopt);
+ }
+ break;
+ }
+}
+
+void U2fBleDevice::OnConnectionStatus(bool success) {
+ StopTimeout();
+ state_ = success ? State::CONNECTED : State::DEVICE_ERROR;
+ Transition();
+}
+
+void U2fBleDevice::OnReadControlPointLength(base::Optional<uint16_t> length) {
+ StopTimeout();
+ if (length) {
+ transaction_.emplace(connection_.get(), *length);
+ state_ = State::READY;
+ } else {
+ state_ = State::DEVICE_ERROR;
+ }
+ Transition();
+}
+
+void U2fBleDevice::OnStatusMessage(std::vector<uint8_t> data) {
+ if (transaction_)
+ transaction_->OnResponseFragment(std::move(data));
+}
+
+void U2fBleDevice::SendRequestFrame(U2fBleFrame frame, FrameCallback callback) {
+ state_ = State::BUSY;
+ transaction_->WriteRequestFrame(
+ std::move(frame),
+ base::BindOnce(&U2fBleDevice::OnResponseFrame, base::Unretained(this),
+ std::move(callback)));
+}
+
+void U2fBleDevice::OnResponseFrame(FrameCallback callback,
+ base::Optional<U2fBleFrame> frame) {
+ state_ = frame ? State::READY : State::DEVICE_ERROR;
+ auto self = GetWeakPtr();
+ std::move(callback).Run(std::move(frame));
+ // Executing callbacks may free |this|. Check |self| first.
+ if (self)
+ Transition();
+}
+
+void U2fBleDevice::StartTimeout() {
+ timer_.Start(FROM_HERE, U2fDevice::kDeviceTimeout, this,
+ &U2fBleDevice::OnTimeout);
+}
+
+void U2fBleDevice::StopTimeout() {
+ timer_.Stop();
+}
+
+void U2fBleDevice::OnTimeout() {
+ state_ = State::DEVICE_ERROR;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_device.h b/chromium/device/fido/u2f_ble_device.h
new file mode 100644
index 00000000000..d368cf48f12
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_device.h
@@ -0,0 +1,90 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_BLE_DEVICE_H_
+#define DEVICE_FIDO_U2F_BLE_DEVICE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/containers/queue.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/strings/string_piece.h"
+#include "base/timer/timer.h"
+#include "device/fido/u2f_ble_connection.h"
+#include "device/fido/u2f_ble_transaction.h"
+#include "device/fido/u2f_device.h"
+
+namespace device {
+
+class U2fBleFrame;
+
+class U2fBleDevice : public U2fDevice {
+ public:
+ using FrameCallback = U2fBleTransaction::FrameCallback;
+ explicit U2fBleDevice(std::string address);
+ explicit U2fBleDevice(std::unique_ptr<U2fBleConnection> connection);
+ ~U2fBleDevice() override;
+
+ void Connect();
+ void SendPing(std::vector<uint8_t> data, MessageCallback callback);
+ static std::string GetId(base::StringPiece address);
+
+ // U2fDevice:
+ void TryWink(WinkCallback callback) override;
+ std::string GetId() const override;
+
+ U2fBleConnection::ConnectionStatusCallback
+ GetConnectionStatusCallbackForTesting();
+ U2fBleConnection::ReadCallback GetReadCallbackForTesting();
+
+ protected:
+ // U2fDevice:
+ void DeviceTransact(std::vector<uint8_t> command,
+ DeviceCallback callback) override;
+ base::WeakPtr<U2fDevice> GetWeakPtr() override;
+
+ private:
+ // INIT --> BUSY --> CONNECTED --> BUSY <--> READY.
+ // DEVICE_ERROR persists.
+ enum class State { INIT, CONNECTED, READY, BUSY, DEVICE_ERROR };
+
+ void Transition();
+
+ void OnConnectionStatus(bool success);
+ void OnStatusMessage(std::vector<uint8_t> data);
+
+ void ReadControlPointLength();
+ void OnReadControlPointLength(base::Optional<uint16_t> length);
+
+ void SendPendingRequestFrame();
+ void SendRequestFrame(U2fBleFrame frame, FrameCallback callback);
+ void OnResponseFrame(FrameCallback callback,
+ base::Optional<U2fBleFrame> frame);
+
+ void StartTimeout();
+ void StopTimeout();
+ void OnTimeout();
+
+ State state_ = State::INIT;
+ base::OneShotTimer timer_;
+
+ std::unique_ptr<U2fBleConnection> connection_;
+ uint16_t control_point_length_ = 0;
+
+ base::queue<std::pair<U2fBleFrame, FrameCallback>> pending_frames_;
+ base::Optional<U2fBleTransaction> transaction_;
+
+ base::WeakPtrFactory<U2fBleDevice> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(U2fBleDevice);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_BLE_DEVICE_H_
diff --git a/chromium/device/fido/u2f_ble_device_unittest.cc b/chromium/device/fido/u2f_ble_device_unittest.cc
new file mode 100644
index 00000000000..ce6b063cb44
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_device_unittest.cc
@@ -0,0 +1,154 @@
+// 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.
+
+#include "device/fido/u2f_ble_device.h"
+
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "device/bluetooth/test/bluetooth_test.h"
+#include "device/fido/mock_u2f_ble_connection.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Test;
+
+namespace {
+
+class TestMessageCallback {
+ public:
+ void OnMessage(U2fReturnCode code, const std::vector<uint8_t>& data) {
+ result_ = std::make_pair(code, data);
+ run_loop_->Quit();
+ }
+
+ const std::pair<U2fReturnCode, std::vector<uint8_t>>& WaitForResult() {
+ run_loop_->Run();
+ run_loop_.emplace();
+ return result_;
+ }
+
+ U2fDevice::MessageCallback GetCallback() {
+ return base::BindRepeating(&TestMessageCallback::OnMessage,
+ base::Unretained(this));
+ }
+
+ private:
+ std::pair<U2fReturnCode, std::vector<uint8_t>> result_;
+ base::Optional<base::RunLoop> run_loop_{base::in_place};
+};
+
+} // namespace
+
+class U2fBleDeviceTest : public Test {
+ public:
+ U2fBleDeviceTest() {
+ auto connection = std::make_unique<MockU2fBleConnection>(
+ BluetoothTestBase::kTestDeviceAddress1);
+ connection_ = connection.get();
+ device_ = std::make_unique<U2fBleDevice>(std::move(connection));
+ connection_->connection_status_callback() =
+ device_->GetConnectionStatusCallbackForTesting();
+ connection_->read_callback() = device_->GetReadCallbackForTesting();
+ }
+
+ U2fBleDevice* device() { return device_.get(); }
+ MockU2fBleConnection* connection() { return connection_; }
+
+ 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();
+ }
+
+ protected:
+ base::test::ScopedTaskEnvironment scoped_task_environment_{
+ base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME};
+
+ private:
+ MockU2fBleConnection* connection_;
+ std::unique_ptr<U2fBleDevice> device_;
+};
+
+TEST_F(U2fBleDeviceTest, ConnectionFailureTest) {
+ EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] {
+ connection()->connection_status_callback().Run(false);
+ }));
+ device()->Connect();
+}
+
+TEST_F(U2fBleDeviceTest, SendPingTest_Failure_Callback) {
+ ConnectWithLength(20);
+
+ EXPECT_CALL(*connection(), WriteControlPointPtr(_, _))
+ .WillOnce(Invoke(
+ [this](const auto& data, auto* cb) { std::move(*cb).Run(false); }));
+
+ TestMessageCallback callback;
+ device()->SendPing({'T', 'E', 'S', 'T'}, callback.GetCallback());
+
+ EXPECT_EQ(std::make_pair(U2fReturnCode::FAILURE, std::vector<uint8_t>()),
+ callback.WaitForResult());
+}
+
+TEST_F(U2fBleDeviceTest, SendPingTest_Failure_Timeout) {
+ ConnectWithLength(20);
+
+ EXPECT_CALL(*connection(), WriteControlPointPtr(_, _))
+ .WillOnce(Invoke([this](const auto& data, auto* cb) {
+ scoped_task_environment_.FastForwardBy(U2fDevice::kDeviceTimeout);
+ }));
+
+ TestMessageCallback callback;
+ device()->SendPing({'T', 'E', 'S', 'T'}, callback.GetCallback());
+
+ EXPECT_EQ(std::make_pair(U2fReturnCode::FAILURE, std::vector<uint8_t>()),
+ callback.WaitForResult());
+}
+
+TEST_F(U2fBleDeviceTest, SendPingTest) {
+ ConnectWithLength(20);
+
+ const std::vector<uint8_t> ping_data = {'T', 'E', 'S', 'T'};
+ EXPECT_CALL(*connection(), WriteControlPointPtr(_, _))
+ .WillOnce(Invoke([this](const auto& data, auto* cb) {
+ auto almost_time_out =
+ U2fDevice::kDeviceTimeout - base::TimeDelta::FromMicroseconds(1);
+ scoped_task_environment_.FastForwardBy(almost_time_out);
+ connection()->read_callback().Run(data);
+ std::move(*cb).Run(true);
+ }));
+
+ TestMessageCallback callback;
+ device()->SendPing(ping_data, callback.GetCallback());
+ EXPECT_EQ(std::make_pair(U2fReturnCode::SUCCESS, ping_data),
+ callback.WaitForResult());
+}
+
+TEST_F(U2fBleDeviceTest, StaticGetIdTest) {
+ std::string address = BluetoothTestBase::kTestDeviceAddress1;
+ EXPECT_EQ("ble:" + address, U2fBleDevice::GetId(address));
+}
+
+TEST_F(U2fBleDeviceTest, TryWinkTest) {
+ base::RunLoop run_loop;
+ device()->TryWink(run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+TEST_F(U2fBleDeviceTest, GetIdTest) {
+ EXPECT_EQ(std::string("ble:") + BluetoothTestBase::kTestDeviceAddress1,
+ device()->GetId());
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_discovery.cc b/chromium/device/fido/u2f_ble_discovery.cc
new file mode 100644
index 00000000000..37356eaf82d
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_discovery.cc
@@ -0,0 +1,148 @@
+// 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.
+
+#include "device/fido/u2f_ble_discovery.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "device/bluetooth/bluetooth_adapter_factory.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/u2f_apdu_command.h"
+#include "device/fido/u2f_ble_device.h"
+#include "device/fido/u2f_ble_uuids.h"
+
+namespace device {
+
+U2fBleDiscovery::U2fBleDiscovery() : weak_factory_(this) {}
+
+U2fBleDiscovery::~U2fBleDiscovery() {
+ if (adapter_)
+ adapter_->RemoveObserver(this);
+
+ // Pretend we are able to successfully stop a discovery session in case it is
+ // still present.
+ if (discovery_session_)
+ OnStopped(true);
+}
+
+void U2fBleDiscovery::Start() {
+ auto& factory = BluetoothAdapterFactory::Get();
+ factory.GetAdapter(
+ base::Bind(&U2fBleDiscovery::OnGetAdapter, weak_factory_.GetWeakPtr()));
+}
+
+void U2fBleDiscovery::Stop() {
+ DCHECK(adapter_);
+ adapter_->RemoveObserver(this);
+
+ DCHECK(discovery_session_);
+ discovery_session_->Stop(
+ base::Bind(&U2fBleDiscovery::OnStopped, weak_factory_.GetWeakPtr(), true),
+ base::Bind(&U2fBleDiscovery::OnStopped, weak_factory_.GetWeakPtr(),
+ false));
+}
+
+// static
+const BluetoothUUID& U2fBleDiscovery::U2fServiceUUID() {
+ static const BluetoothUUID service_uuid(kU2fServiceUUID);
+ return service_uuid;
+}
+
+void U2fBleDiscovery::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(&U2fBleDiscovery::OnSetPowered, weak_factory_.GetWeakPtr()),
+ base::Bind(&U2fBleDiscovery::OnSetPoweredError,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void U2fBleDiscovery::OnSetPowered() {
+ DCHECK(adapter_);
+ VLOG(2) << "Adapter " << adapter_->GetAddress() << " is powered on.";
+
+ for (BluetoothDevice* device : adapter_->GetDevices()) {
+ if (base::ContainsKey(device->GetUUIDs(), U2fServiceUUID())) {
+ VLOG(2) << "U2F BLE device: " << device->GetAddress();
+ AddDevice(std::make_unique<U2fBleDevice>(device->GetAddress()));
+ }
+ }
+
+ auto filter = std::make_unique<BluetoothDiscoveryFilter>(
+ BluetoothTransport::BLUETOOTH_TRANSPORT_LE);
+ filter->AddUUID(U2fServiceUUID());
+
+ adapter_->StartDiscoverySessionWithFilter(
+ std::move(filter),
+ base::Bind(&U2fBleDiscovery::OnStartDiscoverySessionWithFilter,
+ weak_factory_.GetWeakPtr()),
+ base::Bind(&U2fBleDiscovery::OnStartDiscoverySessionWithFilterError,
+ weak_factory_.GetWeakPtr()));
+}
+
+void U2fBleDiscovery::OnSetPoweredError() {
+ DLOG(ERROR) << "Failed to power on the adapter.";
+ NotifyDiscoveryStarted(false);
+}
+
+void U2fBleDiscovery::OnStartDiscoverySessionWithFilter(
+ std::unique_ptr<BluetoothDiscoverySession> session) {
+ discovery_session_ = std::move(session);
+ DVLOG(2) << "Discovery session started.";
+ NotifyDiscoveryStarted(true);
+}
+
+void U2fBleDiscovery::OnStartDiscoverySessionWithFilterError() {
+ DLOG(ERROR) << "Discovery session not started.";
+ NotifyDiscoveryStarted(false);
+}
+
+void U2fBleDiscovery::DeviceAdded(BluetoothAdapter* adapter,
+ BluetoothDevice* device) {
+ if (base::ContainsKey(device->GetUUIDs(), U2fServiceUUID())) {
+ VLOG(2) << "Discovered U2F BLE device: " << device->GetAddress();
+ AddDevice(std::make_unique<U2fBleDevice>(device->GetAddress()));
+ }
+}
+
+void U2fBleDiscovery::DeviceChanged(BluetoothAdapter* adapter,
+ BluetoothDevice* device) {
+ if (base::ContainsKey(device->GetUUIDs(), U2fServiceUUID()) &&
+ !GetDevice(U2fBleDevice::GetId(device->GetAddress()))) {
+ VLOG(2) << "Discovered U2F service on existing BLE device: "
+ << device->GetAddress();
+ AddDevice(std::make_unique<U2fBleDevice>(device->GetAddress()));
+ }
+}
+
+void U2fBleDiscovery::DeviceRemoved(BluetoothAdapter* adapter,
+ BluetoothDevice* device) {
+ if (base::ContainsKey(device->GetUUIDs(), U2fServiceUUID())) {
+ VLOG(2) << "U2F BLE device removed: " << device->GetAddress();
+ RemoveDevice(U2fBleDevice::GetId(device->GetAddress()));
+ }
+}
+
+void U2fBleDiscovery::OnStopped(bool success) {
+ discovery_session_.reset();
+ NotifyDiscoveryStopped(success);
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_discovery.h b/chromium/device/fido/u2f_ble_discovery.h
new file mode 100644
index 00000000000..e1f4f203de4
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_discovery.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_BLE_DISCOVERY_H_
+#define DEVICE_FIDO_U2F_BLE_DISCOVERY_H_
+
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/fido/u2f_discovery.h"
+
+namespace device {
+
+class BluetoothDevice;
+class BluetoothDiscoverySession;
+class BluetoothUUID;
+
+class U2fBleDiscovery : public U2fDiscovery, BluetoothAdapter::Observer {
+ public:
+ U2fBleDiscovery();
+ ~U2fBleDiscovery() override;
+
+ // U2fDiscovery:
+ void Start() override;
+ void Stop() override;
+
+ private:
+ static const BluetoothUUID& U2fServiceUUID();
+
+ void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter);
+ void OnSetPowered();
+ void OnSetPoweredError();
+ void OnStartDiscoverySessionWithFilter(
+ std::unique_ptr<BluetoothDiscoverySession>);
+ void OnStartDiscoverySessionWithFilterError();
+
+ // BluetoothAdapter::Observer:
+ void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override;
+ void DeviceChanged(BluetoothAdapter* adapter,
+ BluetoothDevice* device) override;
+ void DeviceRemoved(BluetoothAdapter* adapter,
+ BluetoothDevice* device) override;
+ void OnStopped(bool success);
+
+ scoped_refptr<BluetoothAdapter> adapter_;
+ std::unique_ptr<BluetoothDiscoverySession> discovery_session_;
+
+ base::WeakPtrFactory<U2fBleDiscovery> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(U2fBleDiscovery);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_BLE_DISCOVERY_H_
diff --git a/chromium/device/fido/u2f_ble_discovery_unittest.cc b/chromium/device/fido/u2f_ble_discovery_unittest.cc
new file mode 100644
index 00000000000..a3b5a140eb0
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_discovery_unittest.cc
@@ -0,0 +1,204 @@
+// 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.
+
+#include "device/fido/u2f_ble_discovery.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "build/build_config.h"
+#include "device/bluetooth/test/bluetooth_test.h"
+#include "device/fido/mock_u2f_discovery.h"
+#include "device/fido/u2f_ble_device.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_ANDROID)
+#include "device/bluetooth/test/bluetooth_test_android.h"
+#elif defined(OS_MACOSX)
+#include "device/bluetooth/test/bluetooth_test_mac.h"
+#elif defined(OS_WIN)
+#include "device/bluetooth/test/bluetooth_test_win.h"
+#elif defined(OS_CHROMEOS) || defined(OS_LINUX)
+#include "device/bluetooth/test/bluetooth_test_bluez.h"
+#endif
+
+using ::testing::_;
+
+namespace device {
+
+ACTION_P(ReturnFromAsyncCall, closure) {
+ closure.Run();
+}
+
+MATCHER_P(IdMatches, id, "") {
+ return arg->GetId() == std::string("ble:") + id;
+}
+
+TEST_F(BluetoothTest, U2fBleDiscoveryNoAdapter) {
+ // We purposefully construct a temporary and provide no fake adapter,
+ // simulating cases where the discovery is destroyed before obtaining a handle
+ // to an adapter. This should be handled gracefully and not result in a crash.
+ U2fBleDiscovery discovery;
+
+ // We don't expect any calls to the notification methods.
+ MockU2fDiscoveryObserver observer;
+ discovery.AddObserver(&observer);
+ EXPECT_CALL(observer, DiscoveryStarted(&discovery, _)).Times(0);
+ EXPECT_CALL(observer, DiscoveryStopped(&discovery, _)).Times(0);
+ EXPECT_CALL(observer, DeviceAdded(&discovery, _)).Times(0);
+ EXPECT_CALL(observer, DeviceRemoved(&discovery, _)).Times(0);
+}
+
+TEST_F(BluetoothTest, U2fBleDiscoveryFindsKnownDevice) {
+ if (!PlatformSupportsLowEnergy()) {
+ LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
+ return;
+ }
+ InitWithFakeAdapter();
+
+ SimulateLowEnergyDevice(4); // This device should be ignored.
+ SimulateLowEnergyDevice(7);
+
+ U2fBleDiscovery discovery;
+ MockU2fDiscoveryObserver observer;
+ discovery.AddObserver(&observer);
+
+ {
+ base::RunLoop run_loop;
+ auto quit = run_loop.QuitClosure();
+ EXPECT_CALL(observer,
+ DeviceAdded(&discovery,
+ IdMatches(BluetoothTestBase::kTestDeviceAddress1)));
+ EXPECT_CALL(observer, DiscoveryStarted(&discovery, true))
+ .WillOnce(ReturnFromAsyncCall(quit));
+
+ discovery.Start();
+ run_loop.Run();
+ }
+
+ // TODO(crbug/763303): Delete device and check OnDeviceDeleted invocation.
+
+ {
+ base::RunLoop run_loop;
+ auto quit = run_loop.QuitClosure();
+
+ EXPECT_CALL(observer, DiscoveryStopped(&discovery, true))
+ .WillOnce(ReturnFromAsyncCall(quit));
+
+ discovery.Stop();
+ run_loop.Run();
+ }
+}
+
+TEST_F(BluetoothTest, U2fBleDiscoveryFindsNewDevice) {
+ if (!PlatformSupportsLowEnergy()) {
+ LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
+ return;
+ }
+ InitWithFakeAdapter();
+
+ U2fBleDiscovery discovery;
+ MockU2fDiscoveryObserver observer;
+ discovery.AddObserver(&observer);
+
+ {
+ base::RunLoop run_loop;
+ auto quit = run_loop.QuitClosure();
+ EXPECT_CALL(observer, DiscoveryStarted(&discovery, true))
+ .WillOnce(ReturnFromAsyncCall(quit));
+
+ discovery.Start();
+ run_loop.Run();
+ }
+
+ {
+ base::RunLoop run_loop;
+ auto quit = run_loop.QuitClosure();
+ EXPECT_CALL(observer,
+ DeviceAdded(&discovery,
+ IdMatches(BluetoothTestBase::kTestDeviceAddress1)))
+ .WillOnce(ReturnFromAsyncCall(quit));
+
+ SimulateLowEnergyDevice(4); // This device should be ignored.
+ SimulateLowEnergyDevice(7);
+
+ run_loop.Run();
+ }
+
+ // TODO(crbug/763303): Delete device and check OnDeviceDeleted invocation.
+
+ {
+ base::RunLoop run_loop;
+ auto quit = run_loop.QuitClosure();
+ EXPECT_CALL(observer, DiscoveryStopped(&discovery, true))
+ .WillOnce(ReturnFromAsyncCall(quit));
+ discovery.Stop();
+ run_loop.Run();
+ }
+}
+
+// Simulate the scenario where the BLE device is already known at start-up time,
+// but no service advertisements have been received from the device yet, so we
+// do not know if it is a U2F device or not. As soon as it is discovered that
+// the device supports the U2F service, the observer should be notified of a new
+// U2fBleDevice.
+TEST_F(BluetoothTest, U2fBleDiscoveryFindsUpdatedDevice) {
+ if (!PlatformSupportsLowEnergy()) {
+ LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
+ return;
+ }
+ InitWithFakeAdapter();
+
+ SimulateLowEnergyDevice(3);
+
+ U2fBleDiscovery discovery;
+ MockU2fDiscoveryObserver observer;
+ discovery.AddObserver(&observer);
+
+ {
+ base::RunLoop run_loop;
+ auto quit = run_loop.QuitClosure();
+ EXPECT_CALL(observer, DiscoveryStarted(&discovery, true))
+ .WillOnce(ReturnFromAsyncCall(quit));
+
+ discovery.Start();
+ run_loop.Run();
+
+ EXPECT_THAT(discovery.GetDevices(), ::testing::IsEmpty());
+ }
+
+ {
+ base::RunLoop run_loop;
+ auto quit = run_loop.QuitClosure();
+ EXPECT_CALL(observer,
+ DeviceAdded(&discovery,
+ IdMatches(BluetoothTestBase::kTestDeviceAddress1)))
+ .WillOnce(ReturnFromAsyncCall(quit));
+
+ // This will update properties for device 3.
+ SimulateLowEnergyDevice(7);
+
+ run_loop.Run();
+
+ const auto devices = discovery.GetDevices();
+ ASSERT_THAT(devices, ::testing::SizeIs(1u));
+ EXPECT_EQ(U2fBleDevice::GetId(BluetoothTestBase::kTestDeviceAddress1),
+ devices[0]->GetId());
+ }
+
+ // TODO(crbug/763303): Delete device and check OnDeviceDeleted invocation.
+
+ {
+ base::RunLoop run_loop;
+ auto quit = run_loop.QuitClosure();
+ EXPECT_CALL(observer, DiscoveryStopped(&discovery, true))
+ .WillOnce(ReturnFromAsyncCall(quit));
+ discovery.Stop();
+ run_loop.Run();
+ }
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_frames.cc b/chromium/device/fido/u2f_ble_frames.cc
new file mode 100644
index 00000000000..c361351388a
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_frames.cc
@@ -0,0 +1,169 @@
+// 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.
+
+#include "device/fido/u2f_ble_frames.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+
+namespace device {
+
+U2fBleFrame::U2fBleFrame() = default;
+
+U2fBleFrame::U2fBleFrame(U2fCommandType command, std::vector<uint8_t> data)
+ : command_(command), data_(std::move(data)) {}
+
+U2fBleFrame::U2fBleFrame(U2fBleFrame&&) = default;
+U2fBleFrame& U2fBleFrame::operator=(U2fBleFrame&&) = default;
+
+U2fBleFrame::~U2fBleFrame() = default;
+
+bool U2fBleFrame::IsValid() const {
+ switch (command_) {
+ case U2fCommandType::CMD_PING:
+ case U2fCommandType::CMD_MSG:
+ return true;
+ case U2fCommandType::CMD_KEEPALIVE:
+ case U2fCommandType::CMD_ERROR:
+ return data_.size() == 1;
+ case U2fCommandType::UNDEFINED:
+ default:
+ return false;
+ }
+}
+
+U2fBleFrame::KeepaliveCode U2fBleFrame::GetKeepaliveCode() const {
+ DCHECK_EQ(command_, U2fCommandType::CMD_KEEPALIVE);
+ DCHECK_EQ(data_.size(), 1u);
+ return static_cast<KeepaliveCode>(data_[0]);
+}
+
+U2fBleFrame::ErrorCode U2fBleFrame::GetErrorCode() const {
+ DCHECK_EQ(command_, U2fCommandType::CMD_ERROR);
+ DCHECK_EQ(data_.size(), 1u);
+ return static_cast<ErrorCode>(data_[0]);
+}
+
+std::pair<U2fBleFrameInitializationFragment,
+ base::queue<U2fBleFrameContinuationFragment>>
+U2fBleFrame::ToFragments(size_t max_fragment_size) const {
+ DCHECK_LE(data_.size(), std::numeric_limits<uint16_t>::max());
+ DCHECK_GE(max_fragment_size, 3u);
+
+ // Cast is necessary to ignore too high bits.
+ auto data_view =
+ base::make_span(data_.data(), static_cast<uint16_t>(data_.size()));
+
+ // Subtract 3 to account for CMD, HLEN and LLEN bytes.
+ const size_t init_fragment_size =
+ std::min(max_fragment_size - 3, data_view.size());
+
+ U2fBleFrameInitializationFragment initial_fragment(
+ command_, data_view.size(), data_view.first(init_fragment_size));
+
+ base::queue<U2fBleFrameContinuationFragment> other_fragments;
+ data_view = data_view.subspan(init_fragment_size);
+
+ while (!data_view.empty()) {
+ // Subtract 1 to account for SEQ byte.
+ const size_t cont_fragment_size =
+ std::min(max_fragment_size - 1, data_view.size());
+ // High bit must stay cleared.
+ other_fragments.emplace(data_view.first(cont_fragment_size),
+ other_fragments.size() & 0x7F);
+
+ data_view = data_view.subspan(cont_fragment_size);
+ }
+
+ return {initial_fragment, std::move(other_fragments)};
+}
+
+U2fBleFrameFragment::U2fBleFrameFragment() = default;
+
+U2fBleFrameFragment::U2fBleFrameFragment(const U2fBleFrameFragment& frame) =
+ default;
+U2fBleFrameFragment::~U2fBleFrameFragment() = default;
+
+U2fBleFrameFragment::U2fBleFrameFragment(base::span<const uint8_t> fragment)
+ : fragment_(fragment) {}
+
+bool U2fBleFrameInitializationFragment::Parse(
+ base::span<const uint8_t> data,
+ U2fBleFrameInitializationFragment* fragment) {
+ if (data.size() < 3)
+ return false;
+
+ const auto command = static_cast<U2fCommandType>(data[0]);
+ const uint16_t data_length = (static_cast<uint16_t>(data[1]) << 8) + data[2];
+ if (static_cast<size_t>(data_length) + 3 < data.size())
+ return false;
+
+ *fragment =
+ U2fBleFrameInitializationFragment(command, data_length, data.subspan(3));
+ return true;
+}
+
+size_t U2fBleFrameInitializationFragment::Serialize(
+ std::vector<uint8_t>* buffer) const {
+ buffer->push_back(static_cast<uint8_t>(command_));
+ buffer->push_back((data_length_ >> 8) & 0xFF);
+ buffer->push_back(data_length_ & 0xFF);
+ buffer->insert(buffer->end(), fragment().begin(), fragment().end());
+ return fragment().size() + 3;
+}
+
+bool U2fBleFrameContinuationFragment::Parse(
+ base::span<const uint8_t> data,
+ U2fBleFrameContinuationFragment* fragment) {
+ if (data.empty())
+ return false;
+ const uint8_t sequence = data[0];
+ *fragment = U2fBleFrameContinuationFragment(data.subspan(1), sequence);
+ return true;
+}
+
+size_t U2fBleFrameContinuationFragment::Serialize(
+ std::vector<uint8_t>* buffer) const {
+ buffer->push_back(sequence_);
+ buffer->insert(buffer->end(), fragment().begin(), fragment().end());
+ return fragment().size() + 1;
+}
+
+U2fBleFrameAssembler::U2fBleFrameAssembler(
+ const U2fBleFrameInitializationFragment& fragment)
+ : data_length_(fragment.data_length()),
+ frame_(fragment.command(),
+ std::vector<uint8_t>(fragment.fragment().begin(),
+ fragment.fragment().end())) {}
+
+bool U2fBleFrameAssembler::AddFragment(
+ const U2fBleFrameContinuationFragment& fragment) {
+ if (fragment.sequence() != sequence_number_)
+ return false;
+ sequence_number_ = (sequence_number_ + 1) & 0x7F;
+
+ if (static_cast<size_t>(data_length_) <
+ frame_.data().size() + fragment.fragment().size()) {
+ return false;
+ }
+
+ frame_.data().insert(frame_.data().end(), fragment.fragment().begin(),
+ fragment.fragment().end());
+ return true;
+}
+
+bool U2fBleFrameAssembler::IsDone() const {
+ return frame_.data().size() == data_length_;
+}
+
+U2fBleFrame* U2fBleFrameAssembler::GetFrame() {
+ return IsDone() ? &frame_ : nullptr;
+}
+
+U2fBleFrameAssembler::~U2fBleFrameAssembler() = default;
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_frames.h b/chromium/device/fido/u2f_ble_frames.h
new file mode 100644
index 00000000000..445f40a77b5
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_frames.h
@@ -0,0 +1,180 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_BLE_FRAMES_H_
+#define DEVICE_FIDO_U2F_BLE_FRAMES_H_
+
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/containers/queue.h"
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "device/fido/u2f_command_type.h"
+
+namespace device {
+
+class U2fBleFrameInitializationFragment;
+class U2fBleFrameContinuationFragment;
+
+// Encapsulates a frame, i.e., a single request to or response from a U2F
+// authenticator, designed to be transported via BLE. The frame is further split
+// into fragments (see U2fBleFrameFragment class).
+//
+// The specification of what constitues a frame can be found here:
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_framing
+//
+// TODO(crbug/763303): Consider refactoring U2fMessage to support BLE frames.
+class U2fBleFrame {
+ public:
+ // The values which can be carried in the |data| section of a KEEPALIVE
+ // message sent from an authenticator.
+ enum class KeepaliveCode : uint8_t {
+ // The request is still being processed. The authenticator will be sending
+ // this message every |kKeepAliveMillis| milliseconds until completion.
+ PROCESSING = 0x01,
+ // The authenticator is waiting for the Test of User Presence to complete.
+ TUP_NEEDED = 0x02,
+ };
+
+ // The types of errors an authenticator can return to the client. Carried in
+ // the |data| section of an ERROR command.
+ enum class ErrorCode : uint8_t {
+ INVALID_CMD = 0x01, // The command in the request is unknown/invalid.
+ INVALID_PAR = 0x02, // The parameters of the command are invalid/missing.
+ INVALID_LEN = 0x03, // The length of the request is invalid.
+ INVALID_SEQ = 0x04, // The sequence number is invalid.
+ REQ_TIMEOUT = 0x05, // The request timed out.
+ NA_1 = 0x06, // Value reserved (HID).
+ NA_2 = 0x0A, // Value reserved (HID).
+ NA_3 = 0x0B, // Value reserved (HID).
+ OTHER = 0x7F, // Other, unspecified error.
+ };
+
+ U2fBleFrame();
+ U2fBleFrame(U2fCommandType command, std::vector<uint8_t> data);
+
+ U2fBleFrame(U2fBleFrame&&);
+ U2fBleFrame& operator=(U2fBleFrame&&);
+
+ ~U2fBleFrame();
+
+ U2fCommandType command() const { return command_; }
+
+ bool IsValid() const;
+ KeepaliveCode GetKeepaliveCode() const;
+ ErrorCode GetErrorCode() const;
+
+ const std::vector<uint8_t>& data() const { return data_; }
+ std::vector<uint8_t>& data() { return data_; }
+
+ // Splits the frame into fragments suitable for sending over BLE. Returns the
+ // first fragment via |initial_fragment|, and pushes the remaining ones back
+ // to the |other_fragments| vector.
+ //
+ // The |max_fragment_size| parameter ought to be at least 3. The resulting
+ // fragments' binary sizes will not exceed this value.
+ std::pair<U2fBleFrameInitializationFragment,
+ base::queue<U2fBleFrameContinuationFragment>>
+ ToFragments(size_t max_fragment_size) const;
+
+ private:
+ U2fCommandType command_ = U2fCommandType::UNDEFINED;
+ std::vector<uint8_t> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(U2fBleFrame);
+};
+
+// A single frame sent over BLE may be split over multiple writes and
+// notifications because the technology was not designed for large messages.
+// This class represents a single fragment. Not to be used directly.
+//
+// A frame is divided into an initialization fragment and zero, one or more
+// continuation fragments. See the below section of the spec for the details:
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_framing-fragmentation
+//
+// Note: This class and its subclasses don't own the |data|.
+class U2fBleFrameFragment {
+ public:
+ base::span<const uint8_t> fragment() const { return fragment_; }
+ virtual size_t Serialize(std::vector<uint8_t>* buffer) const = 0;
+
+ protected:
+ U2fBleFrameFragment();
+ explicit U2fBleFrameFragment(base::span<const uint8_t> fragment);
+ U2fBleFrameFragment(const U2fBleFrameFragment& frame);
+ virtual ~U2fBleFrameFragment();
+
+ private:
+ base::span<const uint8_t> fragment_;
+};
+
+// An initialization fragment of a frame.
+class U2fBleFrameInitializationFragment : public U2fBleFrameFragment {
+ public:
+ static bool Parse(base::span<const uint8_t> data,
+ U2fBleFrameInitializationFragment* fragment);
+
+ U2fBleFrameInitializationFragment() = default;
+ U2fBleFrameInitializationFragment(U2fCommandType command,
+ uint16_t data_length,
+ base::span<const uint8_t> fragment)
+ : U2fBleFrameFragment(fragment),
+ command_(command),
+ data_length_(data_length) {}
+
+ U2fCommandType command() const { return command_; }
+ uint16_t data_length() const { return data_length_; }
+
+ size_t Serialize(std::vector<uint8_t>* buffer) const override;
+
+ private:
+ U2fCommandType command_ = U2fCommandType::UNDEFINED;
+ uint16_t data_length_ = 0;
+};
+
+// A continuation fragment of a frame.
+class U2fBleFrameContinuationFragment : public U2fBleFrameFragment {
+ public:
+ static bool Parse(base::span<const uint8_t> data,
+ U2fBleFrameContinuationFragment* fragment);
+
+ U2fBleFrameContinuationFragment() = default;
+ U2fBleFrameContinuationFragment(base::span<const uint8_t> fragment,
+ uint8_t sequence)
+ : U2fBleFrameFragment(fragment), sequence_(sequence) {}
+
+ uint8_t sequence() const { return sequence_; }
+
+ size_t Serialize(std::vector<uint8_t>* buffer) const override;
+
+ private:
+ uint8_t sequence_ = 0;
+};
+
+// The helper used to construct a U2fBleFrame from a sequence of its fragments.
+class U2fBleFrameAssembler {
+ public:
+ explicit U2fBleFrameAssembler(
+ const U2fBleFrameInitializationFragment& fragment);
+ ~U2fBleFrameAssembler();
+
+ bool IsDone() const;
+
+ bool AddFragment(const U2fBleFrameContinuationFragment& fragment);
+ U2fBleFrame* GetFrame();
+
+ private:
+ uint16_t data_length_ = 0;
+ uint8_t sequence_number_ = 0;
+ U2fBleFrame frame_;
+
+ DISALLOW_COPY_AND_ASSIGN(U2fBleFrameAssembler);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_BLE_FRAMES_H_
diff --git a/chromium/device/fido/u2f_ble_frames_fuzzer.cc b/chromium/device/fido/u2f_ble_frames_fuzzer.cc
new file mode 100644
index 00000000000..772a7e6826a
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_frames_fuzzer.cc
@@ -0,0 +1,59 @@
+// 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.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "device/fido/u2f_ble_frames.h"
+#include "device/fido/u2f_command_type.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* raw_data, size_t size) {
+ auto data_span = base::make_span(raw_data, size);
+ std::vector<uint8_t> data(data_span.begin(), data_span.end());
+
+ {
+ device::U2fBleFrameInitializationFragment fragment(
+ device::U2fCommandType::CMD_MSG, 21123, data_span);
+ std::vector<uint8_t> buffer;
+ fragment.Serialize(&buffer);
+
+ device::U2fBleFrameInitializationFragment parsed_fragment;
+ device::U2fBleFrameInitializationFragment::Parse(data, &parsed_fragment);
+ device::U2fBleFrameInitializationFragment::Parse(buffer, &parsed_fragment);
+
+ buffer.clear();
+ parsed_fragment.Serialize(&buffer);
+ }
+
+ {
+ device::U2fBleFrameContinuationFragment fragment(data_span, 61);
+ std::vector<uint8_t> buffer;
+ fragment.Serialize(&buffer);
+
+ device::U2fBleFrameContinuationFragment parsed_fragment;
+ device::U2fBleFrameContinuationFragment::Parse(data, &parsed_fragment);
+ device::U2fBleFrameContinuationFragment::Parse(buffer, &parsed_fragment);
+
+ buffer.clear();
+ parsed_fragment.Serialize(&buffer);
+ }
+
+ {
+ device::U2fBleFrame frame(device::U2fCommandType::CMD_PING, data);
+ auto fragments = frame.ToFragments(20);
+
+ device::U2fBleFrameAssembler assembler(fragments.first);
+ while (!fragments.second.empty()) {
+ assembler.AddFragment(fragments.second.front());
+ fragments.second.pop();
+ }
+
+ auto result_frame = std::move(*assembler.GetFrame());
+ result_frame.command();
+ }
+
+ return 0;
+}
diff --git a/chromium/device/fido/u2f_ble_frames_unittest.cc b/chromium/device/fido/u2f_ble_frames_unittest.cc
new file mode 100644
index 00000000000..5b2757c02d5
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_frames_unittest.cc
@@ -0,0 +1,143 @@
+// 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.
+
+#include "device/fido/u2f_ble_frames.h"
+
+#include <vector>
+
+#include "device/fido/u2f_command_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+std::vector<uint8_t> GetSomeData(size_t size) {
+ std::vector<uint8_t> data(size);
+ for (size_t i = 0; i < size; ++i)
+ data[i] = static_cast<uint8_t>((i * i) & 0xFF);
+ return data;
+}
+
+} // namespace
+
+namespace device {
+
+TEST(U2fBleFramesTest, InitializationFragment) {
+ const std::vector<uint8_t> data = GetSomeData(25);
+ constexpr uint16_t kDataLength = 21123;
+
+ U2fBleFrameInitializationFragment fragment(
+ U2fCommandType::CMD_MSG, kDataLength, base::make_span(data));
+
+ std::vector<uint8_t> buffer;
+ const size_t binary_size = fragment.Serialize(&buffer);
+ EXPECT_EQ(buffer.size(), binary_size);
+
+ EXPECT_EQ(data.size() + 3, binary_size);
+
+ U2fBleFrameInitializationFragment parsed_fragment;
+ ASSERT_TRUE(
+ U2fBleFrameInitializationFragment::Parse(buffer, &parsed_fragment));
+
+ EXPECT_EQ(kDataLength, parsed_fragment.data_length());
+ EXPECT_EQ(base::make_span(data), parsed_fragment.fragment());
+ EXPECT_EQ(U2fCommandType::CMD_MSG, parsed_fragment.command());
+}
+
+TEST(U2fBleFramesTest, ContinuationFragment) {
+ const auto data = GetSomeData(25);
+ constexpr uint8_t kSequence = 61;
+
+ U2fBleFrameContinuationFragment fragment(base::make_span(data), kSequence);
+
+ std::vector<uint8_t> buffer;
+ const size_t binary_size = fragment.Serialize(&buffer);
+ EXPECT_EQ(buffer.size(), binary_size);
+
+ EXPECT_EQ(data.size() + 1, binary_size);
+
+ U2fBleFrameContinuationFragment parsed_fragment;
+ ASSERT_TRUE(U2fBleFrameContinuationFragment::Parse(buffer, &parsed_fragment));
+
+ EXPECT_EQ(base::make_span(data), parsed_fragment.fragment());
+ EXPECT_EQ(kSequence, parsed_fragment.sequence());
+}
+
+TEST(U2fBleFramesTest, SplitAndAssemble) {
+ for (size_t size : {0, 1, 16, 17, 18, 20, 21, 22, 35, 36,
+ 37, 39, 40, 41, 54, 55, 56, 60, 100, 65535}) {
+ SCOPED_TRACE(size);
+
+ U2fBleFrame frame(U2fCommandType::CMD_PING, GetSomeData(size));
+
+ auto fragments = frame.ToFragments(20);
+
+ EXPECT_EQ(frame.command(), fragments.first.command());
+ EXPECT_EQ(frame.data().size(),
+ static_cast<size_t>(fragments.first.data_length()));
+
+ U2fBleFrameAssembler assembler(fragments.first);
+ while (!fragments.second.empty()) {
+ ASSERT_TRUE(assembler.AddFragment(fragments.second.front()));
+ fragments.second.pop();
+ }
+
+ EXPECT_TRUE(assembler.IsDone());
+ ASSERT_TRUE(assembler.GetFrame());
+
+ auto result_frame = std::move(*assembler.GetFrame());
+ EXPECT_EQ(frame.command(), result_frame.command());
+ EXPECT_EQ(frame.data(), result_frame.data());
+ }
+}
+
+TEST(U2fBleFramesTest, FrameAssemblerError) {
+ U2fBleFrame frame(U2fCommandType::CMD_PING, GetSomeData(30));
+
+ auto fragments = frame.ToFragments(20);
+ ASSERT_EQ(1u, fragments.second.size());
+
+ fragments.second.front() =
+ U2fBleFrameContinuationFragment(fragments.second.front().fragment(), 51);
+
+ U2fBleFrameAssembler assembler(fragments.first);
+ EXPECT_FALSE(assembler.IsDone());
+ EXPECT_FALSE(assembler.GetFrame());
+ EXPECT_FALSE(assembler.AddFragment(fragments.second.front()));
+ EXPECT_FALSE(assembler.IsDone());
+ EXPECT_FALSE(assembler.GetFrame());
+}
+
+TEST(U2fBleFramesTest, FrameGettersAndValidity) {
+ {
+ U2fBleFrame frame(U2fCommandType::CMD_KEEPALIVE, std::vector<uint8_t>(2));
+ EXPECT_FALSE(frame.IsValid());
+ }
+ {
+ U2fBleFrame frame(U2fCommandType::CMD_ERROR, {});
+ EXPECT_FALSE(frame.IsValid());
+ }
+
+ for (auto code : {U2fBleFrame::KeepaliveCode::TUP_NEEDED,
+ U2fBleFrame::KeepaliveCode::PROCESSING}) {
+ U2fBleFrame frame(U2fCommandType::CMD_KEEPALIVE,
+ std::vector<uint8_t>(1, static_cast<uint8_t>(code)));
+ EXPECT_TRUE(frame.IsValid());
+ EXPECT_EQ(code, frame.GetKeepaliveCode());
+ }
+
+ for (auto code : {
+ U2fBleFrame::ErrorCode::INVALID_CMD,
+ U2fBleFrame::ErrorCode::INVALID_PAR,
+ U2fBleFrame::ErrorCode::INVALID_SEQ,
+ U2fBleFrame::ErrorCode::INVALID_LEN,
+ U2fBleFrame::ErrorCode::REQ_TIMEOUT, U2fBleFrame::ErrorCode::NA_1,
+ U2fBleFrame::ErrorCode::NA_2, U2fBleFrame::ErrorCode::NA_3,
+ }) {
+ U2fBleFrame frame(U2fCommandType::CMD_ERROR, {static_cast<uint8_t>(code)});
+ EXPECT_TRUE(frame.IsValid());
+ EXPECT_EQ(code, frame.GetErrorCode());
+ }
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_transaction.cc b/chromium/device/fido/u2f_ble_transaction.cc
new file mode 100644
index 00000000000..0c8b1d50c82
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_transaction.cc
@@ -0,0 +1,139 @@
+// 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.
+
+#include "device/fido/u2f_ble_transaction.h"
+
+#include <utility>
+
+#include "device/fido/u2f_ble_connection.h"
+#include "device/fido/u2f_device.h"
+
+namespace device {
+
+U2fBleTransaction::U2fBleTransaction(U2fBleConnection* connection,
+ uint16_t control_point_length)
+ : connection_(connection),
+ control_point_length_(control_point_length),
+ weak_factory_(this) {
+ buffer_.reserve(control_point_length_);
+}
+
+U2fBleTransaction::~U2fBleTransaction() = default;
+
+void U2fBleTransaction::WriteRequestFrame(U2fBleFrame request_frame,
+ FrameCallback callback) {
+ DCHECK(!request_frame_ && callback_.is_null());
+ request_frame_ = std::move(request_frame);
+ callback_ = std::move(callback);
+
+ U2fBleFrameInitializationFragment request_init_fragment;
+ std::tie(request_init_fragment, request_cont_fragments_) =
+ request_frame_->ToFragments(control_point_length_);
+ WriteRequestFragment(request_init_fragment);
+}
+
+void U2fBleTransaction::WriteRequestFragment(
+ const U2fBleFrameFragment& fragment) {
+ buffer_.clear();
+ fragment.Serialize(&buffer_);
+ // A weak pointer is required, since this call might time out. If that
+ // happens, the current U2fBleTransaction could be destroyed.
+ connection_->WriteControlPoint(
+ buffer_, base::BindOnce(&U2fBleTransaction::OnRequestFragmentWritten,
+ weak_factory_.GetWeakPtr()));
+ // WriteRequestFragment() expects an invocation of OnRequestFragmentWritten()
+ // soon after.
+ StartTimeout();
+}
+
+void U2fBleTransaction::OnRequestFragmentWritten(bool success) {
+ StopTimeout();
+ if (!success) {
+ OnError();
+ return;
+ }
+
+ if (request_cont_fragments_.empty()) {
+ // The transaction wrote the full request frame. A response should follow
+ // soon after.
+ StartTimeout();
+ return;
+ }
+
+ auto next_request_fragment = std::move(request_cont_fragments_.front());
+ request_cont_fragments_.pop();
+ WriteRequestFragment(next_request_fragment);
+}
+
+void U2fBleTransaction::OnResponseFragment(std::vector<uint8_t> data) {
+ StopTimeout();
+ if (!response_frame_assembler_) {
+ U2fBleFrameInitializationFragment fragment;
+ if (!U2fBleFrameInitializationFragment::Parse(data, &fragment)) {
+ DLOG(ERROR) << "Malformed Frame Initialization Fragment";
+ OnError();
+ return;
+ }
+
+ response_frame_assembler_.emplace(fragment);
+ } else {
+ U2fBleFrameContinuationFragment fragment;
+ if (!U2fBleFrameContinuationFragment::Parse(data, &fragment)) {
+ DLOG(ERROR) << "Malformed Frame Continuation Fragment";
+ OnError();
+ return;
+ }
+
+ response_frame_assembler_->AddFragment(fragment);
+ }
+
+ if (!response_frame_assembler_->IsDone()) {
+ // Expect the next reponse fragment to arrive soon.
+ StartTimeout();
+ return;
+ }
+
+ U2fBleFrame frame = std::move(*response_frame_assembler_->GetFrame());
+ response_frame_assembler_.reset();
+ ProcessResponseFrame(std::move(frame));
+}
+
+void U2fBleTransaction::ProcessResponseFrame(U2fBleFrame response_frame) {
+ if (response_frame.command() == request_frame_->command()) {
+ request_frame_.reset();
+ std::move(callback_).Run(std::move(response_frame));
+ return;
+ }
+
+ if (response_frame.command() == U2fCommandType::CMD_KEEPALIVE) {
+ DVLOG(2) << "CMD_KEEPALIVE: "
+ << static_cast<uint8_t>(response_frame.GetKeepaliveCode());
+ // Expect another reponse frame soon.
+ StartTimeout();
+ return;
+ }
+
+ DCHECK_EQ(response_frame.command(), U2fCommandType::CMD_ERROR);
+ DLOG(ERROR) << "CMD_ERROR: "
+ << static_cast<uint8_t>(response_frame.GetErrorCode());
+ OnError();
+}
+
+void U2fBleTransaction::StartTimeout() {
+ timer_.Start(FROM_HERE, U2fDevice::kDeviceTimeout, this,
+ &U2fBleTransaction::OnError);
+}
+
+void U2fBleTransaction::StopTimeout() {
+ timer_.Stop();
+}
+
+void U2fBleTransaction::OnError() {
+ request_frame_.reset();
+ request_cont_fragments_ = {};
+ response_frame_assembler_.reset();
+ std::move(callback_).Run(base::nullopt);
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_transaction.h b/chromium/device/fido/u2f_ble_transaction.h
new file mode 100644
index 00000000000..b0b016e1539
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_transaction.h
@@ -0,0 +1,61 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_BLE_TRANSACTION_H_
+#define DEVICE_FIDO_U2F_BLE_TRANSACTION_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/containers/queue.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/timer/timer.h"
+#include "device/fido/u2f_ble_frames.h"
+
+namespace device {
+
+class U2fBleConnection;
+
+// This class encapsulates logic related to a single U2F BLE request and
+// response. U2fBleTransaction is owned by U2fBleDevice, which is the only class
+// that should make use of this class.
+class U2fBleTransaction {
+ public:
+ using FrameCallback = base::OnceCallback<void(base::Optional<U2fBleFrame>)>;
+
+ U2fBleTransaction(U2fBleConnection* connection,
+ uint16_t control_point_length);
+ ~U2fBleTransaction();
+
+ void WriteRequestFrame(U2fBleFrame request_frame, FrameCallback callback);
+ void OnResponseFragment(std::vector<uint8_t> data);
+
+ private:
+ void WriteRequestFragment(const U2fBleFrameFragment& fragment);
+ void OnRequestFragmentWritten(bool success);
+ void ProcessResponseFrame(U2fBleFrame response_frame);
+
+ void StartTimeout();
+ void StopTimeout();
+ void OnError();
+
+ U2fBleConnection* connection_;
+ uint16_t control_point_length_;
+
+ base::Optional<U2fBleFrame> request_frame_;
+ FrameCallback callback_;
+
+ base::queue<U2fBleFrameContinuationFragment> request_cont_fragments_;
+ base::Optional<U2fBleFrameAssembler> response_frame_assembler_;
+
+ std::vector<uint8_t> buffer_;
+ base::OneShotTimer timer_;
+
+ base::WeakPtrFactory<U2fBleTransaction> weak_factory_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_BLE_TRANSACTION_H_
diff --git a/chromium/device/fido/u2f_ble_uuids.cc b/chromium/device/fido/u2f_ble_uuids.cc
new file mode 100644
index 00000000000..d8188a8094d
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_uuids.cc
@@ -0,0 +1,18 @@
+// 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.
+
+#include "device/fido/u2f_ble_uuids.h"
+
+namespace device {
+
+const char kU2fServiceUUID[] = "0000fffd-0000-1000-8000-00805f9b34fb";
+const char kU2fControlPointUUID[] = "f1d0fff1-deaa-ecee-b42f-c9ba7ed623bb";
+const char kU2fStatusUUID[] = "f1d0fff2-deaa-ecee-b42f-c9ba7ed623bb";
+const char kU2fControlPointLengthUUID[] =
+ "f1d0fff3-deaa-ecee-b42f-c9ba7ed623bb";
+const char kU2fServiceRevisionUUID[] = "00002a28-0000-1000-8000-00805f9b34fb";
+const char kU2fServiceRevisionBitfieldUUID[] =
+ "f1d0fff4-deaa-ecee-b42f-c9ba7ed623bb";
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_ble_uuids.h b/chromium/device/fido/u2f_ble_uuids.h
new file mode 100644
index 00000000000..7beb33b5fc2
--- /dev/null
+++ b/chromium/device/fido/u2f_ble_uuids.h
@@ -0,0 +1,25 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_BLE_UUIDS_H_
+#define DEVICE_FIDO_U2F_BLE_UUIDS_H_
+
+namespace device {
+
+// U2F GATT Service's UUIDs as defined by the standard:
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h3_u2f-service
+//
+// For details on how the short UUIDs for U2F Service (0xFFFD) and U2F Service
+// Revision (0x2A28) were converted to the long canonical ones, see
+// https://www.bluetooth.com/specifications/assigned-numbers/service-discovery
+extern const char kU2fServiceUUID[];
+extern const char kU2fControlPointUUID[];
+extern const char kU2fStatusUUID[];
+extern const char kU2fControlPointLengthUUID[];
+extern const char kU2fServiceRevisionUUID[];
+extern const char kU2fServiceRevisionBitfieldUUID[];
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_BLE_UUIDS_H_
diff --git a/chromium/device/fido/u2f_command_type.h b/chromium/device/fido/u2f_command_type.h
new file mode 100644
index 00000000000..26811e60b89
--- /dev/null
+++ b/chromium/device/fido/u2f_command_type.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_COMMAND_TYPE_H_
+#define DEVICE_FIDO_U2F_COMMAND_TYPE_H_
+
+#include <stdint.h>
+
+namespace device {
+
+// The type of a command that can be sent either to or from a U2F authenticator,
+// i.e. a request or a response.
+//
+// Each request sent to a device results in a response of *the same* type sent
+// back, unless there was an error in which case a CMD_ERROR is returned.
+enum class U2fCommandType : uint8_t {
+ UNDEFINED = 0x00,
+
+ // Sends arbitrary data to the device which echoes the same data back.
+ CMD_PING = 0x81,
+
+ // Authenticator sends this in response to requests that it could not process
+ // within a time limit. The client should take action appropriate to the
+ // message reason (e.g., notify the user to perform a test-of-user-presence),
+ // and wait for the next message.
+ CMD_KEEPALIVE = 0x82,
+
+ // Encapsulates a U2F protocol raw message.
+ CMD_MSG = 0x83,
+
+ // Requests a unique channel from a USB/HID device.
+ CMD_INIT = 0x86,
+
+ // Instructs a USB/HID authenticator to show the user that it is active.
+ CMD_WINK = 0x88,
+
+ // Used as a response in case an error occurs during a request.
+ CMD_ERROR = 0xBF,
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_COMMAND_TYPE_H_
diff --git a/chromium/device/fido/u2f_device.cc b/chromium/device/fido/u2f_device.cc
new file mode 100644
index 00000000000..67b88ddb1d5
--- /dev/null
+++ b/chromium/device/fido/u2f_device.cc
@@ -0,0 +1,141 @@
+// 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.
+
+#include "device/fido/u2f_device.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "device/fido/u2f_apdu_command.h"
+#include "device/fido/u2f_apdu_response.h"
+
+namespace device {
+
+constexpr base::TimeDelta U2fDevice::kDeviceTimeout;
+
+U2fDevice::U2fDevice() = default;
+
+U2fDevice::~U2fDevice() = default;
+
+void U2fDevice::Register(const std::vector<uint8_t>& application_parameter,
+ const std::vector<uint8_t>& challenge_param,
+ bool individual_attestation_ok,
+ MessageCallback callback) {
+ auto register_cmd = U2fApduCommand::CreateRegister(
+ application_parameter, challenge_param, individual_attestation_ok);
+ if (!register_cmd) {
+ std::move(callback).Run(U2fReturnCode::INVALID_PARAMS,
+ std::vector<uint8_t>());
+ return;
+ }
+ DeviceTransact(register_cmd->GetEncodedCommand(),
+ base::BindOnce(&U2fDevice::OnRegisterComplete, GetWeakPtr(),
+ std::move(callback)));
+}
+
+void U2fDevice::Sign(const std::vector<uint8_t>& application_parameter,
+ const std::vector<uint8_t>& challenge_param,
+ const std::vector<uint8_t>& key_handle,
+ MessageCallback callback,
+ bool check_only) {
+ auto sign_cmd = U2fApduCommand::CreateSign(
+ application_parameter, challenge_param, key_handle, check_only);
+ if (!sign_cmd) {
+ std::move(callback).Run(U2fReturnCode::INVALID_PARAMS,
+ std::vector<uint8_t>());
+ return;
+ }
+ DeviceTransact(sign_cmd->GetEncodedCommand(),
+ base::BindOnce(&U2fDevice::OnSignComplete, GetWeakPtr(),
+ std::move(callback)));
+}
+
+void U2fDevice::Version(VersionCallback callback) {
+ auto version_cmd = U2fApduCommand::CreateVersion();
+ if (!version_cmd) {
+ std::move(callback).Run(false, ProtocolVersion::UNKNOWN);
+ return;
+ }
+ DeviceTransact(version_cmd->GetEncodedCommand(),
+ base::BindOnce(&U2fDevice::OnVersionComplete, GetWeakPtr(),
+ std::move(callback), false /* legacy */));
+}
+
+void U2fDevice::OnRegisterComplete(
+ MessageCallback callback,
+ bool success,
+ std::unique_ptr<U2fApduResponse> register_response) {
+ if (!success || !register_response) {
+ std::move(callback).Run(U2fReturnCode::FAILURE, std::vector<uint8_t>());
+ return;
+ }
+ switch (register_response->status()) {
+ case U2fApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED:
+ std::move(callback).Run(U2fReturnCode::CONDITIONS_NOT_SATISFIED,
+ std::vector<uint8_t>());
+ break;
+ case U2fApduResponse::Status::SW_NO_ERROR:
+ std::move(callback).Run(U2fReturnCode::SUCCESS,
+ register_response->data());
+ break;
+ case U2fApduResponse::Status::SW_WRONG_DATA:
+ std::move(callback).Run(U2fReturnCode::INVALID_PARAMS,
+ std::vector<uint8_t>());
+ break;
+ default:
+ std::move(callback).Run(U2fReturnCode::FAILURE, std::vector<uint8_t>());
+ break;
+ }
+}
+
+void U2fDevice::OnSignComplete(MessageCallback callback,
+ bool success,
+ std::unique_ptr<U2fApduResponse> sign_response) {
+ if (!success || !sign_response) {
+ std::move(callback).Run(U2fReturnCode::FAILURE, std::vector<uint8_t>());
+ return;
+ }
+ switch (sign_response->status()) {
+ case U2fApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED:
+ std::move(callback).Run(U2fReturnCode::CONDITIONS_NOT_SATISFIED,
+ std::vector<uint8_t>());
+ break;
+ case U2fApduResponse::Status::SW_NO_ERROR:
+ std::move(callback).Run(U2fReturnCode::SUCCESS, sign_response->data());
+ break;
+ case U2fApduResponse::Status::SW_WRONG_DATA:
+ case U2fApduResponse::Status::SW_WRONG_LENGTH:
+ default:
+ std::move(callback).Run(U2fReturnCode::INVALID_PARAMS,
+ std::vector<uint8_t>());
+ break;
+ }
+}
+
+void U2fDevice::OnVersionComplete(
+ VersionCallback callback,
+ bool legacy,
+ bool success,
+ std::unique_ptr<U2fApduResponse> version_response) {
+ if (success && version_response &&
+ version_response->status() == U2fApduResponse::Status::SW_NO_ERROR &&
+ version_response->data() ==
+ std::vector<uint8_t>({'U', '2', 'F', '_', 'V', '2'})) {
+ std::move(callback).Run(success, ProtocolVersion::U2F_V2);
+ } else if (!legacy) {
+ // Standard GetVersion failed, attempt legacy GetVersion command
+ auto version_cmd = U2fApduCommand::CreateLegacyVersion();
+ if (!version_cmd) {
+ std::move(callback).Run(false, ProtocolVersion::UNKNOWN);
+ } else {
+ DeviceTransact(version_cmd->GetEncodedCommand(),
+ base::BindOnce(&U2fDevice::OnVersionComplete, GetWeakPtr(),
+ std::move(callback), true /* legacy */));
+ }
+ } else {
+ std::move(callback).Run(success, ProtocolVersion::UNKNOWN);
+ }
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_device.h b/chromium/device/fido/u2f_device.h
new file mode 100644
index 00000000000..70eb0c0934d
--- /dev/null
+++ b/chromium/device/fido/u2f_device.h
@@ -0,0 +1,84 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_DEVICE_H_
+#define DEVICE_FIDO_U2F_DEVICE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "device/fido/u2f_apdu_response.h"
+#include "device/fido/u2f_return_code.h"
+
+namespace device {
+
+// Device abstraction for an individual U2F device. A U2F device defines the
+// standardized Register, Sign, and GetVersion methods.
+class U2fDevice {
+ public:
+ enum class ProtocolVersion {
+ U2F_V2,
+ UNKNOWN,
+ };
+
+ using MessageCallback =
+ base::OnceCallback<void(U2fReturnCode, const std::vector<uint8_t>&)>;
+ using VersionCallback =
+ base::OnceCallback<void(bool success, ProtocolVersion version)>;
+ using DeviceCallback =
+ base::OnceCallback<void(bool success,
+ std::unique_ptr<U2fApduResponse> response)>;
+ using WinkCallback = base::OnceCallback<void()>;
+
+ static constexpr auto kDeviceTimeout = base::TimeDelta::FromSeconds(3);
+
+ U2fDevice();
+ virtual ~U2fDevice();
+
+ // TODO(hongjunchoi): https://crbug.com/810229 Move all encoding logic from
+ // U2fDevice to U2fRequest.
+ // Raw messages parameters are defined by the specification at
+ // https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html
+ void Register(const std::vector<uint8_t>& appid_digest,
+ const std::vector<uint8_t>& challenge_digest,
+ bool individual_attestation_ok,
+ MessageCallback callback);
+ void Version(VersionCallback callback);
+ void Sign(const std::vector<uint8_t>& appid_digest,
+ const std::vector<uint8_t>& challenge_digest,
+ const std::vector<uint8_t>& key_handle,
+ MessageCallback callback,
+ bool check_only = false);
+
+ virtual void TryWink(WinkCallback callback) = 0;
+ virtual std::string GetId() const = 0;
+
+ protected:
+ // Pure virtual function defined by each device type, implementing
+ // the device communication transaction.
+ virtual void DeviceTransact(std::vector<uint8_t> command,
+ DeviceCallback callback) = 0;
+ virtual base::WeakPtr<U2fDevice> GetWeakPtr() = 0;
+
+ private:
+ void OnRegisterComplete(MessageCallback callback,
+ bool success,
+ std::unique_ptr<U2fApduResponse> register_response);
+ void OnSignComplete(MessageCallback callback,
+ bool success,
+ std::unique_ptr<U2fApduResponse> sign_response);
+ void OnVersionComplete(VersionCallback callback,
+ bool legacy,
+ bool success,
+ std::unique_ptr<U2fApduResponse> version_response);
+
+ DISALLOW_COPY_AND_ASSIGN(U2fDevice);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_DEVICE_H_
diff --git a/chromium/device/fido/u2f_discovery.cc b/chromium/device/fido/u2f_discovery.cc
new file mode 100644
index 00000000000..e1af0da7182
--- /dev/null
+++ b/chromium/device/fido/u2f_discovery.cc
@@ -0,0 +1,90 @@
+// 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.
+
+#include "device/fido/u2f_discovery.h"
+
+#include <utility>
+
+#include "device/fido/u2f_device.h"
+
+namespace device {
+
+U2fDiscovery::U2fDiscovery() = default;
+
+U2fDiscovery::~U2fDiscovery() = default;
+
+void U2fDiscovery::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void U2fDiscovery::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void U2fDiscovery::NotifyDiscoveryStarted(bool success) {
+ for (auto& observer : observers_)
+ observer.DiscoveryStarted(this, success);
+}
+
+void U2fDiscovery::NotifyDiscoveryStopped(bool success) {
+ for (auto& observer : observers_)
+ observer.DiscoveryStopped(this, success);
+}
+
+void U2fDiscovery::NotifyDeviceAdded(U2fDevice* device) {
+ for (auto& observer : observers_)
+ observer.DeviceAdded(this, device);
+}
+
+void U2fDiscovery::NotifyDeviceRemoved(U2fDevice* device) {
+ for (auto& observer : observers_)
+ observer.DeviceRemoved(this, device);
+}
+
+std::vector<U2fDevice*> U2fDiscovery::GetDevices() {
+ std::vector<U2fDevice*> devices;
+ devices.reserve(devices_.size());
+ for (const auto& device : devices_)
+ devices.push_back(device.second.get());
+ return devices;
+}
+
+std::vector<const U2fDevice*> U2fDiscovery::GetDevices() const {
+ std::vector<const U2fDevice*> devices;
+ devices.reserve(devices_.size());
+ for (const auto& device : devices_)
+ devices.push_back(device.second.get());
+ return devices;
+}
+
+U2fDevice* U2fDiscovery::GetDevice(base::StringPiece device_id) {
+ return const_cast<U2fDevice*>(
+ static_cast<const U2fDiscovery*>(this)->GetDevice(device_id));
+}
+
+const U2fDevice* U2fDiscovery::GetDevice(base::StringPiece device_id) const {
+ auto found = devices_.find(device_id);
+ return found != devices_.end() ? found->second.get() : nullptr;
+}
+
+bool U2fDiscovery::AddDevice(std::unique_ptr<U2fDevice> device) {
+ std::string device_id = device->GetId();
+ const auto result = devices_.emplace(std::move(device_id), std::move(device));
+ if (result.second)
+ NotifyDeviceAdded(result.first->second.get());
+ return result.second;
+}
+
+bool U2fDiscovery::RemoveDevice(base::StringPiece device_id) {
+ auto found = devices_.find(device_id);
+ if (found == devices_.end())
+ return false;
+
+ auto device = std::move(found->second);
+ devices_.erase(found);
+ NotifyDeviceRemoved(device.get());
+ return true;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_discovery.h b/chromium/device/fido/u2f_discovery.h
new file mode 100644
index 00000000000..0078b0f08f9
--- /dev/null
+++ b/chromium/device/fido/u2f_discovery.h
@@ -0,0 +1,67 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_DISCOVERY_H_
+#define DEVICE_FIDO_U2F_DISCOVERY_H_
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/strings/string_piece.h"
+
+namespace device {
+
+class U2fDevice;
+
+class U2fDiscovery {
+ public:
+ class Observer {
+ public:
+ virtual ~Observer() = default;
+ virtual void DiscoveryStarted(U2fDiscovery* discovery, bool success) = 0;
+ virtual void DiscoveryStopped(U2fDiscovery* discovery, bool success) = 0;
+ virtual void DeviceAdded(U2fDiscovery* discovery, U2fDevice* device) = 0;
+ virtual void DeviceRemoved(U2fDiscovery* discovery, U2fDevice* device) = 0;
+ };
+
+ U2fDiscovery();
+ virtual ~U2fDiscovery();
+
+ virtual void Start() = 0;
+ virtual void Stop() = 0;
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ void NotifyDiscoveryStarted(bool success);
+ void NotifyDiscoveryStopped(bool success);
+ void NotifyDeviceAdded(U2fDevice* device);
+ void NotifyDeviceRemoved(U2fDevice* device);
+
+ std::vector<U2fDevice*> GetDevices();
+ std::vector<const U2fDevice*> GetDevices() const;
+
+ U2fDevice* GetDevice(base::StringPiece device_id);
+ const U2fDevice* GetDevice(base::StringPiece device_id) const;
+
+ protected:
+ virtual bool AddDevice(std::unique_ptr<U2fDevice> device);
+ virtual bool RemoveDevice(base::StringPiece device_id);
+
+ std::map<std::string, std::unique_ptr<U2fDevice>, std::less<>> devices_;
+ base::ObserverList<Observer> observers_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(U2fDiscovery);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_DISCOVERY_H_
diff --git a/chromium/device/fido/u2f_discovery_unittest.cc b/chromium/device/fido/u2f_discovery_unittest.cc
new file mode 100644
index 00000000000..6fdf6d1df38
--- /dev/null
+++ b/chromium/device/fido/u2f_discovery_unittest.cc
@@ -0,0 +1,103 @@
+// 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.
+
+#include "device/fido/u2f_discovery.h"
+
+#include <utility>
+
+#include "device/fido/mock_u2f_discovery.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::UnorderedElementsAre;
+
+TEST(U2fDiscoveryTest, TestAddAndRemoveObserver) {
+ MockU2fDiscovery discovery;
+ MockU2fDiscoveryObserver observer;
+ EXPECT_FALSE(discovery.GetObservers().HasObserver(&observer));
+
+ discovery.AddObserver(&observer);
+ EXPECT_TRUE(discovery.GetObservers().HasObserver(&observer));
+
+ discovery.RemoveObserver(&observer);
+ EXPECT_FALSE(discovery.GetObservers().HasObserver(&observer));
+}
+
+TEST(U2fDiscoveryTest, TestNotifications) {
+ MockU2fDiscovery discovery;
+ MockU2fDiscoveryObserver observer;
+ discovery.AddObserver(&observer);
+
+ EXPECT_CALL(observer, DiscoveryStarted(&discovery, true));
+ discovery.NotifyDiscoveryStarted(true);
+
+ EXPECT_CALL(observer, DiscoveryStarted(&discovery, false));
+ discovery.NotifyDiscoveryStarted(false);
+
+ EXPECT_CALL(observer, DiscoveryStopped(&discovery, true));
+ discovery.NotifyDiscoveryStopped(true);
+
+ EXPECT_CALL(observer, DiscoveryStopped(&discovery, false));
+ discovery.NotifyDiscoveryStopped(false);
+
+ MockU2fDevice device;
+ EXPECT_CALL(observer, DeviceAdded(&discovery, &device));
+ discovery.NotifyDeviceAdded(&device);
+
+ EXPECT_CALL(observer, DeviceRemoved(&discovery, &device));
+ discovery.NotifyDeviceRemoved(&device);
+}
+
+TEST(U2fDiscoveryTest, TestAddRemoveDevices) {
+ MockU2fDiscovery discovery;
+ MockU2fDiscoveryObserver observer;
+ discovery.AddObserver(&observer);
+
+ // Expect successful insertion.
+ auto device0 = std::make_unique<MockU2fDevice>();
+ auto* device0_raw = device0.get();
+ EXPECT_CALL(observer, DeviceAdded(&discovery, device0_raw));
+ EXPECT_CALL(*device0, GetId()).WillOnce(Return("device0"));
+ EXPECT_TRUE(discovery.AddDevice(std::move(device0)));
+
+ // // Expect successful insertion.
+ auto device1 = std::make_unique<MockU2fDevice>();
+ auto* device1_raw = device1.get();
+ EXPECT_CALL(observer, DeviceAdded(&discovery, device1_raw));
+ EXPECT_CALL(*device1, GetId()).WillOnce(Return("device1"));
+ EXPECT_TRUE(discovery.AddDevice(std::move(device1)));
+
+ // Inserting a device with an already present id should be prevented.
+ auto device1_dup = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(observer, DeviceAdded(_, _)).Times(0);
+ EXPECT_CALL(*device1_dup, GetId()).WillOnce(Return("device1"));
+ EXPECT_FALSE(discovery.AddDevice(std::move(device1_dup)));
+
+ EXPECT_EQ(device0_raw, discovery.GetDevice("device0"));
+ EXPECT_EQ(device1_raw, discovery.GetDevice("device1"));
+ EXPECT_THAT(discovery.GetDevices(),
+ UnorderedElementsAre(device0_raw, device1_raw));
+
+ const U2fDiscovery& const_discovery = discovery;
+ EXPECT_EQ(device0_raw, const_discovery.GetDevice("device0"));
+ EXPECT_EQ(device1_raw, const_discovery.GetDevice("device1"));
+ EXPECT_THAT(const_discovery.GetDevices(),
+ UnorderedElementsAre(device0_raw, device1_raw));
+
+ // Trying to remove a non-present device should fail.
+ EXPECT_CALL(observer, DeviceRemoved(_, _)).Times(0);
+ EXPECT_FALSE(discovery.RemoveDevice("device2"));
+
+ EXPECT_CALL(observer, DeviceRemoved(&discovery, device1_raw));
+ EXPECT_TRUE(discovery.RemoveDevice("device1"));
+
+ EXPECT_CALL(observer, DeviceRemoved(&discovery, device0_raw));
+ EXPECT_TRUE(discovery.RemoveDevice("device0"));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_hid_device.cc b/chromium/device/fido/u2f_hid_device.cc
new file mode 100644
index 00000000000..57b43254641
--- /dev/null
+++ b/chromium/device/fido/u2f_hid_device.cc
@@ -0,0 +1,357 @@
+// 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.
+
+#include "device/fido/u2f_hid_device.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "crypto/random.h"
+#include "device/fido/fido_hid_message.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+
+namespace device {
+
+namespace switches {
+static constexpr char kEnableU2fHidTest[] = "enable-u2f-hid-tests";
+} // namespace switches
+
+namespace {
+// U2F devices only provide a single report so specify a report ID of 0 here.
+static constexpr uint8_t kReportId = 0x00;
+} // namespace
+
+U2fHidDevice::U2fHidDevice(device::mojom::HidDeviceInfoPtr device_info,
+ device::mojom::HidManager* hid_manager)
+ : U2fDevice(),
+ state_(State::INIT),
+ hid_manager_(hid_manager),
+ device_info_(std::move(device_info)),
+ weak_factory_(this) {}
+
+U2fHidDevice::~U2fHidDevice() = default;
+
+void U2fHidDevice::DeviceTransact(std::vector<uint8_t> command,
+ DeviceCallback callback) {
+ Transition(std::move(command), std::move(callback));
+}
+
+void U2fHidDevice::Transition(std::vector<uint8_t> command,
+ DeviceCallback callback) {
+ // This adapter is needed to support the calls to ArmTimeout(). However, it is
+ // still guaranteed that |callback| will only be invoked once.
+ auto repeating_callback =
+ base::AdaptCallbackForRepeating(std::move(callback));
+ switch (state_) {
+ case State::INIT:
+ state_ = State::BUSY;
+ ArmTimeout(repeating_callback);
+ Connect(base::BindOnce(&U2fHidDevice::OnConnect,
+ weak_factory_.GetWeakPtr(), std::move(command),
+ repeating_callback));
+ break;
+ case State::CONNECTED:
+ state_ = State::BUSY;
+ ArmTimeout(repeating_callback);
+ AllocateChannel(std::move(command), repeating_callback);
+ break;
+ case State::IDLE: {
+ state_ = State::BUSY;
+ ArmTimeout(repeating_callback);
+ // Write message to the device
+ WriteMessage(
+ FidoHidMessage::Create(channel_id_, CtapHidDeviceCommand::kCtapHidMsg,
+ std::move(command)),
+ true,
+ base::BindOnce(&U2fHidDevice::MessageReceived,
+ weak_factory_.GetWeakPtr(), repeating_callback));
+ break;
+ }
+ case State::BUSY:
+ pending_transactions_.emplace(std::move(command), repeating_callback);
+ break;
+ case State::DEVICE_ERROR:
+ default:
+ base::WeakPtr<U2fHidDevice> self = weak_factory_.GetWeakPtr();
+ repeating_callback.Run(false, nullptr);
+
+ // Executing callbacks may free |this|. Check |self| first.
+ while (self && !pending_transactions_.empty()) {
+ // Respond to any pending requests
+ DeviceCallback pending_cb =
+ std::move(pending_transactions_.front().second);
+ pending_transactions_.pop();
+ std::move(pending_cb).Run(false, nullptr);
+ }
+ break;
+ }
+}
+
+void U2fHidDevice::Connect(ConnectCallback callback) {
+ DCHECK(hid_manager_);
+ hid_manager_->Connect(device_info_->guid, std::move(callback));
+}
+
+void U2fHidDevice::OnConnect(std::vector<uint8_t> command,
+ DeviceCallback callback,
+ device::mojom::HidConnectionPtr connection) {
+ if (state_ == State::DEVICE_ERROR)
+ return;
+ timeout_callback_.Cancel();
+
+ if (connection) {
+ connection_ = std::move(connection);
+ state_ = State::CONNECTED;
+ } else {
+ state_ = State::DEVICE_ERROR;
+ }
+ Transition(std::move(command), std::move(callback));
+}
+
+void U2fHidDevice::AllocateChannel(std::vector<uint8_t> command,
+ DeviceCallback callback) {
+ // Send random nonce to device to verify received message
+ std::vector<uint8_t> nonce(8);
+ crypto::RandBytes(nonce.data(), nonce.size());
+ WriteMessage(FidoHidMessage::Create(
+ channel_id_, CtapHidDeviceCommand::kCtapHidInit, nonce),
+ true,
+ base::BindOnce(&U2fHidDevice::OnAllocateChannel,
+ weak_factory_.GetWeakPtr(), nonce,
+ std::move(command), std::move(callback)));
+}
+
+void U2fHidDevice::OnAllocateChannel(std::vector<uint8_t> nonce,
+ std::vector<uint8_t> command,
+ DeviceCallback callback,
+ bool success,
+ std::unique_ptr<FidoHidMessage> message) {
+ if (state_ == State::DEVICE_ERROR)
+ return;
+ timeout_callback_.Cancel();
+
+ if (!success || !message) {
+ state_ = State::DEVICE_ERROR;
+ Transition(std::vector<uint8_t>(), std::move(callback));
+ return;
+ }
+
+ // Channel allocation response is defined as:
+ // 0: 8 byte nonce
+ // 8: 4 byte channel id
+ // 12: Protocol version id
+ // 13: Major device version
+ // 14: Minor device version
+ // 15: Build device version
+ // 16: Capabilities
+ std::vector<uint8_t> payload = message->GetMessagePayload();
+ if (payload.size() != 17) {
+ state_ = State::DEVICE_ERROR;
+ Transition(std::vector<uint8_t>(), std::move(callback));
+ return;
+ }
+
+ auto received_nonce = base::make_span(payload).first(8);
+ // Received a broadcast message for a different client. Disregard and continue
+ // reading.
+ if (base::make_span(nonce) != received_nonce) {
+ auto repeating_callback =
+ base::AdaptCallbackForRepeating(std::move(callback));
+ ArmTimeout(repeating_callback);
+ ReadMessage(base::BindOnce(&U2fHidDevice::OnAllocateChannel,
+ weak_factory_.GetWeakPtr(), nonce,
+ std::move(command), repeating_callback));
+ return;
+ }
+
+ size_t index = 8;
+ channel_id_ = payload[index++] << 24;
+ channel_id_ |= payload[index++] << 16;
+ channel_id_ |= payload[index++] << 8;
+ channel_id_ |= payload[index++];
+ capabilities_ = payload[16];
+ state_ = State::IDLE;
+ Transition(std::move(command), std::move(callback));
+}
+
+void U2fHidDevice::WriteMessage(std::unique_ptr<FidoHidMessage> message,
+ bool response_expected,
+ U2fHidMessageCallback callback) {
+ if (!connection_ || !message || message->NumPackets() == 0) {
+ std::move(callback).Run(false, nullptr);
+ return;
+ }
+ const auto& packet = message->PopNextPacket();
+ connection_->Write(
+ kReportId, packet,
+ base::BindOnce(&U2fHidDevice::PacketWritten, weak_factory_.GetWeakPtr(),
+ std::move(message), true, std::move(callback)));
+}
+
+void U2fHidDevice::PacketWritten(std::unique_ptr<FidoHidMessage> message,
+ bool response_expected,
+ U2fHidMessageCallback callback,
+ bool success) {
+ if (success && message->NumPackets() > 0) {
+ WriteMessage(std::move(message), response_expected, std::move(callback));
+ } else if (success && response_expected) {
+ ReadMessage(std::move(callback));
+ } else {
+ std::move(callback).Run(success, nullptr);
+ }
+}
+
+void U2fHidDevice::ReadMessage(U2fHidMessageCallback callback) {
+ if (!connection_) {
+ std::move(callback).Run(false, nullptr);
+ return;
+ }
+ connection_->Read(base::BindOnce(
+ &U2fHidDevice::OnRead, weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void U2fHidDevice::OnRead(U2fHidMessageCallback callback,
+ bool success,
+ uint8_t report_id,
+ const base::Optional<std::vector<uint8_t>>& buf) {
+ if (!success) {
+ std::move(callback).Run(success, nullptr);
+ return;
+ }
+
+ DCHECK(buf);
+ auto read_message = FidoHidMessage::CreateFromSerializedData(*buf);
+ if (!read_message) {
+ std::move(callback).Run(false, nullptr);
+ return;
+ }
+
+ // Received a message from a different channel, so try again
+ if (channel_id_ != read_message->channel_id()) {
+ connection_->Read(base::BindOnce(&U2fHidDevice::OnRead,
+ weak_factory_.GetWeakPtr(),
+ std::move(callback)));
+ return;
+ }
+
+ if (read_message->MessageComplete()) {
+ std::move(callback).Run(success, std::move(read_message));
+ return;
+ }
+
+ // Continue reading additional packets
+ connection_->Read(base::BindOnce(
+ &U2fHidDevice::OnReadContinuation, weak_factory_.GetWeakPtr(),
+ std::move(read_message), std::move(callback)));
+}
+
+void U2fHidDevice::OnReadContinuation(
+ std::unique_ptr<FidoHidMessage> message,
+ U2fHidMessageCallback callback,
+ bool success,
+ uint8_t report_id,
+ const base::Optional<std::vector<uint8_t>>& buf) {
+ if (!success) {
+ std::move(callback).Run(success, nullptr);
+ return;
+ }
+
+ DCHECK(buf);
+ message->AddContinuationPacket(*buf);
+ if (message->MessageComplete()) {
+ std::move(callback).Run(success, std::move(message));
+ return;
+ }
+ connection_->Read(base::BindOnce(&U2fHidDevice::OnReadContinuation,
+ weak_factory_.GetWeakPtr(),
+ std::move(message), std::move(callback)));
+}
+
+void U2fHidDevice::MessageReceived(DeviceCallback callback,
+ bool success,
+ std::unique_ptr<FidoHidMessage> message) {
+ if (state_ == State::DEVICE_ERROR)
+ return;
+ timeout_callback_.Cancel();
+ if (!success) {
+ state_ = State::DEVICE_ERROR;
+ Transition(std::vector<uint8_t>(), std::move(callback));
+ return;
+ }
+ std::unique_ptr<U2fApduResponse> response = nullptr;
+ if (message)
+ response = U2fApduResponse::CreateFromMessage(message->GetMessagePayload());
+
+ state_ = State::IDLE;
+ base::WeakPtr<U2fHidDevice> self = weak_factory_.GetWeakPtr();
+ std::move(callback).Run(success, std::move(response));
+
+ // Executing |callback| may have freed |this|. Check |self| first.
+ if (self && !pending_transactions_.empty()) {
+ // If any transactions were queued, process the first one
+ auto pending_cmd = std::move(pending_transactions_.front().first);
+ auto pending_cb = std::move(pending_transactions_.front().second);
+ pending_transactions_.pop();
+ Transition(std::move(pending_cmd), std::move(pending_cb));
+ }
+}
+
+void U2fHidDevice::TryWink(WinkCallback callback) {
+ // Only try to wink if device claims support
+ if (!(capabilities_ & kWinkCapability) || state_ != State::IDLE) {
+ std::move(callback).Run();
+ return;
+ }
+
+ WriteMessage(
+ FidoHidMessage::Create(channel_id_, CtapHidDeviceCommand::kCtapHidWink,
+ std::vector<uint8_t>()),
+ true,
+ base::BindOnce(&U2fHidDevice::OnWink, weak_factory_.GetWeakPtr(),
+ std::move(callback)));
+}
+
+void U2fHidDevice::OnWink(WinkCallback callback,
+ bool success,
+ std::unique_ptr<FidoHidMessage> response) {
+ std::move(callback).Run();
+}
+
+void U2fHidDevice::ArmTimeout(DeviceCallback callback) {
+ DCHECK(timeout_callback_.IsCancelled());
+ timeout_callback_.Reset(base::BindOnce(&U2fHidDevice::OnTimeout,
+ weak_factory_.GetWeakPtr(),
+ std::move(callback)));
+ // Setup timeout task for 3 seconds
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, timeout_callback_.callback(), kDeviceTimeout);
+}
+
+void U2fHidDevice::OnTimeout(DeviceCallback callback) {
+ state_ = State::DEVICE_ERROR;
+ Transition(std::vector<uint8_t>(), std::move(callback));
+}
+
+std::string U2fHidDevice::GetId() const {
+ return GetIdForDevice(*device_info_);
+}
+
+// static
+std::string U2fHidDevice::GetIdForDevice(
+ const device::mojom::HidDeviceInfo& device_info) {
+ return "hid:" + device_info.guid;
+}
+
+// static
+bool U2fHidDevice::IsTestEnabled() {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ return command_line->HasSwitch(switches::kEnableU2fHidTest);
+}
+
+base::WeakPtr<U2fDevice> U2fHidDevice::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_hid_device.h b/chromium/device/fido/u2f_hid_device.h
new file mode 100644
index 00000000000..40ce28ec160
--- /dev/null
+++ b/chromium/device/fido/u2f_hid_device.h
@@ -0,0 +1,121 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_HID_DEVICE_H_
+#define DEVICE_FIDO_U2F_HID_DEVICE_H_
+
+#include <memory>
+#include <queue>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/cancelable_callback.h"
+#include "device/fido/u2f_device.h"
+#include "services/device/public/mojom/hid.mojom.h"
+
+namespace device {
+
+class FidoHidMessage;
+
+class U2fHidDevice : public U2fDevice {
+ public:
+ U2fHidDevice(device::mojom::HidDeviceInfoPtr device_info,
+ device::mojom::HidManager* hid_manager);
+ ~U2fHidDevice() final;
+
+ // Send a U2f command to this device
+ void DeviceTransact(std::vector<uint8_t> command,
+ DeviceCallback callback) final;
+ // Send a wink command if supported
+ void TryWink(WinkCallback callback) final;
+ // Use a string identifier to compare to other devices
+ std::string GetId() const final;
+ // Get a string identifier for a given device info
+ static std::string GetIdForDevice(
+ const device::mojom::HidDeviceInfo& device_info);
+ // Command line flag to enable tests on actual U2f HID hardware
+ static bool IsTestEnabled();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(U2fHidDeviceTest, TestConnectionFailure);
+ FRIEND_TEST_ALL_PREFIXES(U2fHidDeviceTest, TestDeviceError);
+
+ static constexpr uint8_t kWinkCapability = 0x01;
+ static constexpr uint8_t kLockCapability = 0x02;
+ static constexpr uint32_t kBroadcastChannel = 0xffffffff;
+
+ // Internal state machine states
+ enum class State { INIT, CONNECTED, BUSY, IDLE, DEVICE_ERROR };
+
+ using U2fHidMessageCallback =
+ base::OnceCallback<void(bool, std::unique_ptr<FidoHidMessage>)>;
+ using ConnectCallback = device::mojom::HidManager::ConnectCallback;
+
+ // Open a connection to this device
+ void Connect(ConnectCallback callback);
+ void OnConnect(std::vector<uint8_t> command,
+ DeviceCallback callback,
+ device::mojom::HidConnectionPtr connection);
+ // Ask device to allocate a unique channel id for this connection
+ void AllocateChannel(std::vector<uint8_t> command, DeviceCallback callback);
+ void OnAllocateChannel(std::vector<uint8_t> nonce,
+ std::vector<uint8_t> command,
+ DeviceCallback callback,
+ bool success,
+ std::unique_ptr<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,
+ bool response_expected,
+ U2fHidMessageCallback callback);
+ void PacketWritten(std::unique_ptr<FidoHidMessage> message,
+ bool response_expected,
+ U2fHidMessageCallback callback,
+ bool success);
+ // Read all response message packets from device
+ void ReadMessage(U2fHidMessageCallback callback);
+ void MessageReceived(DeviceCallback callback,
+ bool success,
+ std::unique_ptr<FidoHidMessage> message);
+ void OnRead(U2fHidMessageCallback callback,
+ bool success,
+ uint8_t report_id,
+ const base::Optional<std::vector<uint8_t>>& buf);
+ void OnReadContinuation(std::unique_ptr<FidoHidMessage> message,
+ U2fHidMessageCallback callback,
+ bool success,
+ uint8_t report_id,
+ const base::Optional<std::vector<uint8_t>>& buf);
+ void OnWink(WinkCallback callback,
+ bool success,
+ std::unique_ptr<FidoHidMessage> response);
+ void ArmTimeout(DeviceCallback callback);
+ void OnTimeout(DeviceCallback callback);
+ void OnDeviceTransact(bool success,
+ std::unique_ptr<U2fApduResponse> response);
+ base::WeakPtr<U2fDevice> GetWeakPtr() override;
+
+ uint32_t channel_id_ = kBroadcastChannel;
+ uint8_t capabilities_ = 0;
+ State state_ = State::INIT;
+
+ base::CancelableOnceClosure timeout_callback_;
+ std::queue<std::pair<std::vector<uint8_t>, DeviceCallback>>
+ pending_transactions_;
+
+ // All the U2fHidDevice instances are owned by U2fRequest. So it is safe to
+ // let the U2fHidDevice share the device::mojo::HidManager raw pointer from
+ // U2fRequest.
+ device::mojom::HidManager* hid_manager_;
+ device::mojom::HidDeviceInfoPtr device_info_;
+ device::mojom::HidConnectionPtr connection_;
+ base::WeakPtrFactory<U2fHidDevice> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(U2fHidDevice);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_HID_DEVICE_H_
diff --git a/chromium/device/fido/u2f_hid_device_unittest.cc b/chromium/device/fido/u2f_hid_device_unittest.cc
new file mode 100644
index 00000000000..97dba85b8c9
--- /dev/null
+++ b/chromium/device/fido/u2f_hid_device_unittest.cc
@@ -0,0 +1,488 @@
+// 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.
+
+#include <list>
+
+#include "base/bind.h"
+#include "base/containers/span.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/scoped_mock_time_message_loop_task_runner.h"
+#include "base/test/scoped_task_environment.h"
+#include "device/fido/fake_hid_impl_for_testing.h"
+#include "device/fido/u2f_apdu_command.h"
+#include "device/fido/u2f_apdu_response.h"
+#include "device/fido/u2f_command_type.h"
+#include "device/fido/u2f_hid_device.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"
+#include "services/device/public/mojom/hid.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+namespace {
+
+void ResponseCallback(std::unique_ptr<device::U2fApduResponse>* output,
+ bool success,
+ std::unique_ptr<device::U2fApduResponse> response) {
+ *output = std::move(response);
+}
+
+std::string HexEncode(base::span<const uint8_t> in) {
+ return base::HexEncode(in.data(), in.size());
+}
+
+std::vector<uint8_t> HexDecode(base::StringPiece in) {
+ std::vector<uint8_t> out;
+ bool result = base::HexStringToBytes(in.as_string(), &out);
+ DCHECK(result);
+ return out;
+}
+
+// Converts hex encoded StringPiece to byte vector and pads zero to fit HID
+// packet size.
+std::vector<uint8_t> MakePacket(base::StringPiece hex) {
+ std::vector<uint8_t> out = HexDecode(hex);
+ out.resize(64);
+ return out;
+}
+
+// Returns HID_INIT request to send to device with mock connection.
+std::vector<uint8_t> CreateMockHidInitResponse(
+ std::vector<uint8_t> nonce,
+ std::vector<uint8_t> channel_id) {
+ // 4 bytes of broadcast channel identifier(ffffffff), followed by
+ // HID_INIT command(86) and 2 byte payload length(11)
+ return MakePacket("ffffffff860011" + HexEncode(nonce) +
+ HexEncode(channel_id));
+}
+
+// Returns "U2F_v2" as a mock response to version request with given channel id.
+std::vector<uint8_t> CreateMockVersionResponse(
+ std::vector<uint8_t> channel_id) {
+ // HID_MSG command(83), followed by payload length(0008), followed by
+ // hex encoded "U2F_V2" and NO_ERROR response code(9000).
+ return MakePacket(HexEncode(channel_id) + "8300085532465f56329000");
+}
+
+// Returns a failure mock response to version request with given channel id.
+std::vector<uint8_t> CreateFailureMockVersionResponse(
+ std::vector<uint8_t> channel_id) {
+ // HID_MSG command(83), followed by payload length(0002), followed by
+ // an invalid class response code (6E00).
+ return MakePacket(HexEncode(channel_id) + "8300026E00");
+}
+
+device::mojom::HidDeviceInfoPtr TestHidDevice() {
+ auto c_info = device::mojom::HidCollectionInfo::New();
+ c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
+ auto hid_device = device::mojom::HidDeviceInfo::New();
+ hid_device->guid = "A";
+ hid_device->product_name = "Test Fido device";
+ hid_device->serial_number = "123FIDO";
+ hid_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
+ hid_device->collections.push_back(std::move(c_info));
+ hid_device->max_input_report_size = 64;
+ hid_device->max_output_report_size = 64;
+ return hid_device;
+}
+
+} // namespace
+
+class U2fDeviceEnumerate {
+ public:
+ explicit U2fDeviceEnumerate(device::mojom::HidManager* hid_manager)
+ : closure_(),
+ callback_(base::BindOnce(&U2fDeviceEnumerate::ReceivedCallback,
+ base::Unretained(this))),
+ hid_manager_(hid_manager),
+ run_loop_() {}
+ ~U2fDeviceEnumerate() = default;
+
+ void ReceivedCallback(std::vector<device::mojom::HidDeviceInfoPtr> devices) {
+ std::list<std::unique_ptr<U2fHidDevice>> u2f_devices;
+ filter_.SetUsagePage(0xf1d0);
+ for (auto& device_info : devices) {
+ if (filter_.Matches(*device_info))
+ u2f_devices.push_front(std::make_unique<U2fHidDevice>(
+ std::move(device_info), hid_manager_));
+ }
+ devices_ = std::move(u2f_devices);
+ closure_.Run();
+ }
+
+ std::list<std::unique_ptr<U2fHidDevice>>& WaitForCallback() {
+ closure_ = run_loop_.QuitClosure();
+ run_loop_.Run();
+ return devices_;
+ }
+
+ device::mojom::HidManager::GetDevicesCallback callback() {
+ return std::move(callback_);
+ }
+
+ private:
+ HidDeviceFilter filter_;
+ std::list<std::unique_ptr<U2fHidDevice>> devices_;
+ base::Closure closure_;
+ device::mojom::HidManager::GetDevicesCallback callback_;
+ device::mojom::HidManager* hid_manager_;
+ base::RunLoop run_loop_;
+};
+
+class TestVersionCallback {
+ public:
+ TestVersionCallback()
+ : closure_(),
+ callback_(base::BindOnce(&TestVersionCallback::ReceivedCallback,
+ base::Unretained(this))),
+ run_loop_() {}
+ ~TestVersionCallback() = default;
+
+ void ReceivedCallback(bool success, U2fDevice::ProtocolVersion version) {
+ version_ = version;
+ std::move(closure_).Run();
+ }
+
+ U2fDevice::ProtocolVersion WaitForCallback() {
+ closure_ = run_loop_.QuitClosure();
+ run_loop_.Run();
+ return version_;
+ }
+
+ U2fDevice::VersionCallback callback() { return std::move(callback_); }
+
+ private:
+ U2fDevice::ProtocolVersion version_;
+ base::OnceClosure closure_;
+ U2fDevice::VersionCallback callback_;
+ base::RunLoop run_loop_;
+};
+
+class TestDeviceCallback {
+ public:
+ TestDeviceCallback()
+ : closure_(),
+ callback_(base::Bind(&TestDeviceCallback::ReceivedCallback,
+ base::Unretained(this))),
+ run_loop_() {}
+ ~TestDeviceCallback() = default;
+
+ void ReceivedCallback(bool success,
+ std::unique_ptr<U2fApduResponse> response) {
+ response_ = std::move(response);
+ closure_.Run();
+ }
+
+ std::unique_ptr<U2fApduResponse> WaitForCallback() {
+ closure_ = run_loop_.QuitClosure();
+ run_loop_.Run();
+ return std::move(response_);
+ }
+
+ const U2fDevice::DeviceCallback& callback() { return callback_; }
+
+ private:
+ std::unique_ptr<U2fApduResponse> response_;
+ base::Closure closure_;
+ U2fDevice::DeviceCallback callback_;
+ base::RunLoop run_loop_;
+};
+
+class U2fHidDeviceTest : public testing::Test {
+ public:
+ U2fHidDeviceTest()
+ : scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
+
+ void SetUp() override {
+ fake_hid_manager_ = std::make_unique<FakeHidManager>();
+ fake_hid_manager_->AddBinding2(mojo::MakeRequest(&hid_manager_));
+ }
+
+ protected:
+ device::mojom::HidManagerPtr hid_manager_;
+ std::unique_ptr<FakeHidManager> fake_hid_manager_;
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+TEST_F(U2fHidDeviceTest, TestHidDeviceVersion) {
+ if (!U2fHidDevice::IsTestEnabled())
+ return;
+
+ U2fDeviceEnumerate callback(hid_manager_.get());
+ hid_manager_->GetDevices(callback.callback());
+ std::list<std::unique_ptr<U2fHidDevice>>& u2f_devices =
+ callback.WaitForCallback();
+
+ for (auto& device : u2f_devices) {
+ TestVersionCallback vc;
+ device->Version(vc.callback());
+ U2fDevice::ProtocolVersion version = vc.WaitForCallback();
+ EXPECT_EQ(version, U2fDevice::ProtocolVersion::U2F_V2);
+ }
+}
+
+TEST_F(U2fHidDeviceTest, TestMultipleRequests) {
+ if (!U2fHidDevice::IsTestEnabled())
+ return;
+
+ U2fDeviceEnumerate callback(hid_manager_.get());
+ hid_manager_->GetDevices(callback.callback());
+ std::list<std::unique_ptr<U2fHidDevice>>& u2f_devices =
+ callback.WaitForCallback();
+
+ for (auto& device : u2f_devices) {
+ TestVersionCallback vc;
+ TestVersionCallback vc2;
+ // Call version twice to check message queueing
+ device->Version(vc.callback());
+ device->Version(vc2.callback());
+ U2fDevice::ProtocolVersion version = vc.WaitForCallback();
+ EXPECT_EQ(version, U2fDevice::ProtocolVersion::U2F_V2);
+ version = vc2.WaitForCallback();
+ EXPECT_EQ(version, U2fDevice::ProtocolVersion::U2F_V2);
+ }
+}
+
+TEST_F(U2fHidDeviceTest, TestConnectionFailure) {
+ // Setup and enumerate mock device
+ U2fDeviceEnumerate callback(hid_manager_.get());
+ auto hid_device = TestHidDevice();
+ fake_hid_manager_->AddDevice(std::move(hid_device));
+ hid_manager_->GetDevices(callback.callback());
+
+ std::list<std::unique_ptr<U2fHidDevice>>& u2f_devices =
+ callback.WaitForCallback();
+
+ ASSERT_EQ(static_cast<size_t>(1), u2f_devices.size());
+ auto& device = u2f_devices.front();
+ // Put device in IDLE state
+ TestDeviceCallback cb0;
+ device->state_ = U2fHidDevice::State::IDLE;
+
+ // Manually delete connection
+ device->connection_ = nullptr;
+ // Add pending transactions manually and ensure they are processed
+ std::unique_ptr<U2fApduResponse> response1(
+ U2fApduResponse::CreateFromMessage(std::vector<uint8_t>({0x0, 0x0})));
+ device->pending_transactions_.emplace(
+ U2fApduCommand::CreateVersion()->GetEncodedCommand(),
+ base::BindOnce(&ResponseCallback, &response1));
+ std::unique_ptr<U2fApduResponse> response2(
+ U2fApduResponse::CreateFromMessage(std::vector<uint8_t>({0x0, 0x0})));
+ device->pending_transactions_.emplace(
+ U2fApduCommand::CreateVersion()->GetEncodedCommand(),
+ base::BindOnce(&ResponseCallback, &response2));
+ std::unique_ptr<U2fApduResponse> response3(
+ U2fApduResponse::CreateFromMessage(std::vector<uint8_t>({0x0, 0x0})));
+ device->DeviceTransact(U2fApduCommand::CreateVersion()->GetEncodedCommand(),
+ base::Bind(&ResponseCallback, &response3));
+ EXPECT_EQ(U2fHidDevice::State::DEVICE_ERROR, device->state_);
+ EXPECT_EQ(nullptr, response1);
+ EXPECT_EQ(nullptr, response2);
+ EXPECT_EQ(nullptr, response3);
+}
+
+TEST_F(U2fHidDeviceTest, TestDeviceError) {
+ // Setup and enumerate mock device
+ U2fDeviceEnumerate callback(hid_manager_.get());
+
+ auto hid_device = TestHidDevice();
+
+ fake_hid_manager_->AddDevice(std::move(hid_device));
+ hid_manager_->GetDevices(callback.callback());
+
+ std::list<std::unique_ptr<U2fHidDevice>>& u2f_devices =
+ callback.WaitForCallback();
+
+ ASSERT_EQ(static_cast<size_t>(1), u2f_devices.size());
+ auto& device = u2f_devices.front();
+ // Mock connection where writes always fail
+ FakeHidConnection::mock_connection_error_ = true;
+ device->state_ = U2fHidDevice::State::IDLE;
+ std::unique_ptr<U2fApduResponse> response0(
+ U2fApduResponse::CreateFromMessage(std::vector<uint8_t>({0x0, 0x0})));
+ device->DeviceTransact(U2fApduCommand::CreateVersion()->GetEncodedCommand(),
+ base::Bind(&ResponseCallback, &response0));
+ EXPECT_EQ(nullptr, response0);
+ EXPECT_EQ(U2fHidDevice::State::DEVICE_ERROR, device->state_);
+
+ // Add pending transactions manually and ensure they are processed
+ std::unique_ptr<U2fApduResponse> response1(
+ U2fApduResponse::CreateFromMessage(std::vector<uint8_t>({0x0, 0x0})));
+ device->pending_transactions_.emplace(
+ U2fApduCommand::CreateVersion()->GetEncodedCommand(),
+ base::BindOnce(&ResponseCallback, &response1));
+ std::unique_ptr<U2fApduResponse> response2(
+ U2fApduResponse::CreateFromMessage(std::vector<uint8_t>({0x0, 0x0})));
+ device->pending_transactions_.emplace(
+ U2fApduCommand::CreateVersion()->GetEncodedCommand(),
+ base::BindOnce(&ResponseCallback, &response2));
+ std::unique_ptr<U2fApduResponse> response3(
+ U2fApduResponse::CreateFromMessage(std::vector<uint8_t>({0x0, 0x0})));
+ device->DeviceTransact(U2fApduCommand::CreateVersion()->GetEncodedCommand(),
+ base::Bind(&ResponseCallback, &response3));
+ FakeHidConnection::mock_connection_error_ = false;
+
+ EXPECT_EQ(U2fHidDevice::State::DEVICE_ERROR, device->state_);
+ EXPECT_EQ(nullptr, response1);
+ EXPECT_EQ(nullptr, response2);
+ EXPECT_EQ(nullptr, response3);
+}
+
+TEST_F(U2fHidDeviceTest, TestLegacyVersion) {
+ const std::vector<uint8_t> kChannelId = {0x01, 0x02, 0x03, 0x04};
+
+ U2fDeviceEnumerate callback(hid_manager_.get());
+ auto hid_device = TestHidDevice();
+
+ // 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), kChannelId);
+
+ // Delegate custom functions to be invoked for mock hid connection.
+ EXPECT_CALL(mock_connection, WritePtr(_, _, _))
+ // HID_INIT request to authenticator for channel allocation.
+ .WillOnce(WithArgs<1, 2>(
+ Invoke([&](const std::vector<uint8_t>& buffer,
+ device::mojom::HidConnection::WriteCallback* cb) {
+ mock_connection.SetNonce(base::make_span(buffer).subspan(7, 8));
+ std::move(*cb).Run(true);
+ })))
+
+ // HID_MSG request to authenticator for version request.
+ .WillOnce(WithArgs<2>(
+ Invoke([](device::mojom::HidConnection::WriteCallback* cb) {
+ std::move(*cb).Run(true);
+ })))
+
+ .WillOnce(WithArgs<2>(
+ Invoke([](device::mojom::HidConnection::WriteCallback* cb) {
+ std::move(*cb).Run(true);
+ })));
+
+ EXPECT_CALL(mock_connection, ReadPtr(_))
+ // Response to HID_INIT request with correct nonce.
+ .WillOnce(WithArg<0>(Invoke(
+ [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
+ std::move(*cb).Run(true, 0,
+ CreateMockHidInitResponse(
+ mock_connection.nonce(),
+ mock_connection.connection_channel_id()));
+ })))
+ // Invalid version response from the authenticator.
+ .WillOnce(WithArg<0>(Invoke(
+ [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
+ std::move(*cb).Run(true, 0,
+ CreateFailureMockVersionResponse(
+ mock_connection.connection_channel_id()));
+ })))
+ // Legacy version response from the authenticator.
+ .WillOnce(WithArg<0>(Invoke(
+ [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
+ std::move(*cb).Run(true, 0,
+ CreateMockVersionResponse(
+ mock_connection.connection_channel_id()));
+ })));
+
+ // Add device and set mock connection to fake hid manager.
+ fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
+ std::move(connection_client));
+
+ hid_manager_->GetDevices(callback.callback());
+ std::list<std::unique_ptr<U2fHidDevice>>& u2f_devices =
+ callback.WaitForCallback();
+
+ ASSERT_EQ(1u, u2f_devices.size());
+ auto& device = u2f_devices.front();
+ TestVersionCallback vc;
+ device->Version(vc.callback());
+ EXPECT_EQ(vc.WaitForCallback(), U2fDevice::ProtocolVersion::U2F_V2);
+}
+
+TEST_F(U2fHidDeviceTest, TestRetryChannelAllocation) {
+ const std::vector<uint8_t> kIncorrectNonce = {0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
+ const std::vector<uint8_t> kChannelId = {0x01, 0x02, 0x03, 0x04};
+
+ U2fDeviceEnumerate callback(hid_manager_.get());
+ auto hid_device = TestHidDevice();
+
+ // 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), kChannelId);
+
+ // Delegate custom functions to be invoked for mock hid connection
+ EXPECT_CALL(mock_connection, WritePtr(_, _, _))
+ // HID_INIT request to authenticator for channel allocation.
+ .WillOnce(WithArgs<1, 2>(
+ Invoke([&](const std::vector<uint8_t>& buffer,
+ device::mojom::HidConnection::WriteCallback* cb) {
+ mock_connection.SetNonce(base::make_span(buffer).subspan(7, 8));
+ std::move(*cb).Run(true);
+ })))
+
+ // HID_MSG request to authenticator for version request.
+ .WillOnce(WithArgs<2>(
+ Invoke([](device::mojom::HidConnection::WriteCallback* cb) {
+ std::move(*cb).Run(true);
+ })));
+
+ EXPECT_CALL(mock_connection, ReadPtr(_))
+ // First response to HID_INIT request with incorrect nonce.
+ .WillOnce(WithArg<0>(
+ Invoke([kIncorrectNonce, &mock_connection](
+ device::mojom::HidConnection::ReadCallback* cb) {
+ std::move(*cb).Run(
+ true, 0,
+ CreateMockHidInitResponse(
+ kIncorrectNonce, mock_connection.connection_channel_id()));
+ })))
+ // Second response to HID_INIT request with correct nonce.
+ .WillOnce(WithArg<0>(Invoke(
+ [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
+ std::move(*cb).Run(true, 0,
+ CreateMockHidInitResponse(
+ mock_connection.nonce(),
+ mock_connection.connection_channel_id()));
+ })))
+ // Version response from the authenticator.
+ .WillOnce(WithArg<0>(Invoke(
+ [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
+ std::move(*cb).Run(true, 0,
+ CreateMockVersionResponse(
+ mock_connection.connection_channel_id()));
+ })));
+
+ // Add device and set mock connection to fake hid manager
+ fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
+ std::move(connection_client));
+
+ hid_manager_->GetDevices(callback.callback());
+ std::list<std::unique_ptr<U2fHidDevice>>& u2f_devices =
+ callback.WaitForCallback();
+
+ ASSERT_EQ(1u, u2f_devices.size());
+ auto& device = u2f_devices.front();
+ TestVersionCallback vc;
+ device->Version(vc.callback());
+ EXPECT_EQ(vc.WaitForCallback(), U2fDevice::ProtocolVersion::U2F_V2);
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_hid_discovery.cc b/chromium/device/fido/u2f_hid_discovery.cc
new file mode 100644
index 00000000000..6cb05350431
--- /dev/null
+++ b/chromium/device/fido/u2f_hid_discovery.cc
@@ -0,0 +1,63 @@
+// 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.
+
+#include "device/fido/u2f_hid_discovery.h"
+
+#include <utility>
+
+#include "device/fido/u2f_hid_device.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "services/device/public/mojom/constants.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace device {
+
+U2fHidDiscovery::U2fHidDiscovery(service_manager::Connector* connector)
+ : connector_(connector), binding_(this), weak_factory_(this) {
+ // TODO(piperc@): Give this constant a name.
+ filter_.SetUsagePage(0xf1d0);
+}
+
+U2fHidDiscovery::~U2fHidDiscovery() = default;
+
+void U2fHidDiscovery::Start() {
+ DCHECK(connector_);
+ connector_->BindInterface(device::mojom::kServiceName,
+ mojo::MakeRequest(&hid_manager_));
+ device::mojom::HidManagerClientAssociatedPtrInfo client;
+ binding_.Bind(mojo::MakeRequest(&client));
+
+ hid_manager_->GetDevicesAndSetClient(
+ std::move(client), base::BindOnce(&U2fHidDiscovery::OnGetDevices,
+ weak_factory_.GetWeakPtr()));
+}
+void U2fHidDiscovery::Stop() {
+ binding_.Unbind();
+ NotifyDiscoveryStopped(true);
+}
+
+void U2fHidDiscovery::DeviceAdded(device::mojom::HidDeviceInfoPtr device_info) {
+ // Ignore non-U2F devices
+ if (filter_.Matches(*device_info)) {
+ AddDevice(std::make_unique<U2fHidDevice>(std::move(device_info),
+ hid_manager_.get()));
+ }
+}
+
+void U2fHidDiscovery::DeviceRemoved(
+ device::mojom::HidDeviceInfoPtr device_info) {
+ // Ignore non-U2F devices
+ if (filter_.Matches(*device_info)) {
+ RemoveDevice(U2fHidDevice::GetIdForDevice(*device_info));
+ }
+}
+
+void U2fHidDiscovery::OnGetDevices(
+ std::vector<device::mojom::HidDeviceInfoPtr> device_infos) {
+ for (auto& device_info : device_infos)
+ DeviceAdded(std::move(device_info));
+ NotifyDiscoveryStarted(true);
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_hid_discovery.h b/chromium/device/fido/u2f_hid_discovery.h
new file mode 100644
index 00000000000..a6650632e2f
--- /dev/null
+++ b/chromium/device/fido/u2f_hid_discovery.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_HID_DISCOVERY_H_
+#define DEVICE_FIDO_U2F_HID_DISCOVERY_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "device/fido/u2f_discovery.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "services/device/public/cpp/hid/hid_device_filter.h"
+#include "services/device/public/mojom/hid.mojom.h"
+
+namespace service_manager {
+class Connector;
+}
+
+namespace device {
+
+// TODO(crbug/769631): Now the U2F is talking to HID via mojo, once the U2F
+// servicification is unblocked, we'll move U2F back to //service/device/.
+// Then it will talk to HID via C++ as part of servicifying U2F.
+
+class U2fHidDiscovery : public U2fDiscovery, device::mojom::HidManagerClient {
+ public:
+ explicit U2fHidDiscovery(service_manager::Connector* connector);
+ ~U2fHidDiscovery() override;
+
+ // U2fDiscovery:
+ void Start() override;
+ void Stop() override;
+
+ private:
+ // device::mojom::HidManagerClient implementation:
+ void DeviceAdded(device::mojom::HidDeviceInfoPtr device_info) override;
+ void DeviceRemoved(device::mojom::HidDeviceInfoPtr device_info) override;
+
+ void OnGetDevices(std::vector<device::mojom::HidDeviceInfoPtr> devices);
+
+ service_manager::Connector* connector_;
+ device::mojom::HidManagerPtr hid_manager_;
+ mojo::AssociatedBinding<device::mojom::HidManagerClient> binding_;
+ HidDeviceFilter filter_;
+ base::WeakPtrFactory<U2fHidDiscovery> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(U2fHidDiscovery);
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_HID_DISCOVERY_H_
diff --git a/chromium/device/fido/u2f_hid_discovery_unittest.cc b/chromium/device/fido/u2f_hid_discovery_unittest.cc
new file mode 100644
index 00000000000..c401bbaa7f5
--- /dev/null
+++ b/chromium/device/fido/u2f_hid_discovery_unittest.cc
@@ -0,0 +1,119 @@
+// 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.
+
+#include "device/fido/u2f_hid_discovery.h"
+
+#include <string>
+#include <utility>
+
+#include "base/test/scoped_task_environment.h"
+#include "device/fido/fake_hid_impl_for_testing.h"
+#include "device/fido/mock_u2f_discovery.h"
+#include "device/fido/u2f_hid_device.h"
+#include "services/device/public/mojom/constants.mojom.h"
+#include "services/device/public/mojom/hid.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/mojom/connector.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+using ::testing::_;
+
+namespace {
+
+device::mojom::HidDeviceInfoPtr MakeU2fDevice(std::string guid) {
+ auto c_info = device::mojom::HidCollectionInfo::New();
+ c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
+
+ auto u2f_device = device::mojom::HidDeviceInfo::New();
+ u2f_device->guid = std::move(guid);
+ u2f_device->product_name = "Test Fido Device";
+ u2f_device->serial_number = "123FIDO";
+ u2f_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
+ u2f_device->collections.push_back(std::move(c_info));
+ u2f_device->max_input_report_size = 64;
+ u2f_device->max_output_report_size = 64;
+ return u2f_device;
+}
+
+device::mojom::HidDeviceInfoPtr MakeOtherDevice(std::string guid) {
+ auto other_device = device::mojom::HidDeviceInfo::New();
+ other_device->guid = std::move(guid);
+ other_device->product_name = "Other Device";
+ other_device->serial_number = "OtherDevice";
+ other_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
+ return other_device;
+}
+
+MATCHER_P(IdMatches, id, "") {
+ return arg->GetId() == std::string("hid:") + id;
+}
+
+} // namespace
+
+class U2fHidDiscoveryTest : public testing::Test {
+ public:
+ base::test::ScopedTaskEnvironment& scoped_task_environment() {
+ return scoped_task_environment_;
+ }
+
+ void SetUp() override {
+ fake_hid_manager_ = std::make_unique<FakeHidManager>();
+
+ service_manager::mojom::ConnectorRequest request;
+ connector_ = service_manager::Connector::Create(&request);
+ service_manager::Connector::TestApi test_api(connector_.get());
+ test_api.OverrideBinderForTesting(
+ service_manager::Identity(device::mojom::kServiceName),
+ device::mojom::HidManager::Name_,
+ base::Bind(&FakeHidManager::AddBinding,
+ base::Unretained(fake_hid_manager_.get())));
+ }
+
+ protected:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ std::unique_ptr<service_manager::Connector> connector_;
+ std::unique_ptr<FakeHidManager> fake_hid_manager_;
+};
+
+TEST_F(U2fHidDiscoveryTest, TestAddRemoveDevice) {
+ U2fHidDiscovery discovery(connector_.get());
+ MockU2fDiscoveryObserver observer;
+
+ fake_hid_manager_->AddDevice(MakeU2fDevice("known"));
+
+ EXPECT_CALL(observer, DiscoveryStarted(&discovery, true));
+ discovery.AddObserver(&observer);
+ discovery.Start();
+
+ // Devices initially known to the service before discovery started should be
+ // reported as KNOWN.
+ EXPECT_CALL(observer, DeviceAdded(&discovery, IdMatches("known")));
+ scoped_task_environment().RunUntilIdle();
+
+ // Devices added during the discovery should be reported as ADDED.
+ EXPECT_CALL(observer, DeviceAdded(&discovery, IdMatches("added")));
+ fake_hid_manager_->AddDevice(MakeU2fDevice("added"));
+ scoped_task_environment().RunUntilIdle();
+
+ // Added non-U2F devices should not be reported at all.
+ EXPECT_CALL(observer, DeviceAdded(_, _)).Times(0);
+ fake_hid_manager_->AddDevice(MakeOtherDevice("other"));
+
+ // Removed non-U2F devices should not be reported at all.
+ EXPECT_CALL(observer, DeviceRemoved(_, _)).Times(0);
+ fake_hid_manager_->RemoveDevice("other");
+ scoped_task_environment().RunUntilIdle();
+
+ // Removed U2F devices should be reported as REMOVED.
+ EXPECT_CALL(observer, DeviceRemoved(&discovery, IdMatches("known")));
+ EXPECT_CALL(observer, DeviceRemoved(&discovery, IdMatches("added")));
+ fake_hid_manager_->RemoveDevice("known");
+ fake_hid_manager_->RemoveDevice("added");
+ scoped_task_environment().RunUntilIdle();
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_parsing_utils.cc b/chromium/device/fido/u2f_parsing_utils.cc
new file mode 100644
index 00000000000..c0278956b6c
--- /dev/null
+++ b/chromium/device/fido/u2f_parsing_utils.cc
@@ -0,0 +1,32 @@
+// 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.
+
+#include "device/fido/u2f_parsing_utils.h"
+
+#include "base/logging.h"
+
+namespace device {
+namespace u2f_parsing_utils {
+
+const uint32_t kU2fResponseKeyHandleLengthPos = 66u;
+const uint32_t kU2fResponseKeyHandleStartPos = 67u;
+const char kEs256[] = "ES256";
+
+void Append(std::vector<uint8_t>* target, base::span<const uint8_t> in_values) {
+ target->insert(target->end(), in_values.begin(), in_values.end());
+}
+
+std::vector<uint8_t> Extract(base::span<const uint8_t> source,
+ size_t pos,
+ size_t length) {
+ if (!(pos <= source.size() && length <= source.size() - pos)) {
+ return std::vector<uint8_t>();
+ }
+
+ return std::vector<uint8_t>(source.begin() + pos,
+ source.begin() + pos + length);
+}
+
+} // namespace u2f_parsing_utils
+} // namespace device
diff --git a/chromium/device/fido/u2f_parsing_utils.h b/chromium/device/fido/u2f_parsing_utils.h
new file mode 100644
index 00000000000..beefa9b1fe0
--- /dev/null
+++ b/chromium/device/fido/u2f_parsing_utils.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_PARSING_UTILS_H_
+#define DEVICE_FIDO_U2F_PARSING_UTILS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "base/containers/span.h"
+
+namespace device {
+namespace u2f_parsing_utils {
+// 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
+extern const uint32_t kU2fResponseKeyHandleLengthPos;
+extern const uint32_t kU2fResponseKeyHandleStartPos;
+extern const char kEs256[];
+
+void Append(std::vector<uint8_t>* target, base::span<const uint8_t> in_values);
+
+// Parses out a sub-vector after verifying no out-of-bound reads.
+std::vector<uint8_t> Extract(base::span<const uint8_t> source,
+ size_t pos,
+ size_t length);
+
+} // namespace u2f_parsing_utils
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_PARSING_UTILS_H_
diff --git a/chromium/device/fido/u2f_register.cc b/chromium/device/fido/u2f_register.cc
new file mode 100644
index 00000000000..9477fea2b3b
--- /dev/null
+++ b/chromium/device/fido/u2f_register.cc
@@ -0,0 +1,161 @@
+// 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.
+
+#include "device/fido/u2f_register.h"
+
+#include <utility>
+
+#include "base/stl_util.h"
+#include "device/fido/register_response_data.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace device {
+
+// static
+std::unique_ptr<U2fRequest> U2fRegister::TryRegistration(
+ service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<std::vector<uint8_t>> registered_keys,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<uint8_t> application_parameter,
+ bool individual_attestation_ok,
+ RegisterResponseCallback completion_callback) {
+ std::unique_ptr<U2fRequest> request = std::make_unique<U2fRegister>(
+ connector, protocols, std::move(registered_keys),
+ std::move(challenge_digest), std::move(application_parameter),
+ individual_attestation_ok, std::move(completion_callback));
+ request->Start();
+ return request;
+}
+
+U2fRegister::U2fRegister(service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<std::vector<uint8_t>> registered_keys,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<uint8_t> application_parameter,
+ bool individual_attestation_ok,
+ RegisterResponseCallback completion_callback)
+ : U2fRequest(connector,
+ protocols,
+ std::move(application_parameter),
+ std::move(challenge_digest),
+ std::move(registered_keys)),
+ individual_attestation_ok_(individual_attestation_ok),
+ completion_callback_(std::move(completion_callback)),
+ weak_factory_(this) {}
+
+U2fRegister::~U2fRegister() = default;
+
+void U2fRegister::TryDevice() {
+ DCHECK(current_device_);
+ if (registered_keys_.size() > 0 && !CheckedForDuplicateRegistration()) {
+ auto it = registered_keys_.cbegin();
+ current_device_->Sign(application_parameter_, challenge_digest_, *it,
+ base::Bind(&U2fRegister::OnTryCheckRegistration,
+ weak_factory_.GetWeakPtr(), it),
+ true);
+ } else {
+ current_device_->Register(application_parameter_, challenge_digest_,
+ individual_attestation_ok_,
+ base::Bind(&U2fRegister::OnTryDevice,
+ weak_factory_.GetWeakPtr(), false));
+ }
+}
+
+void U2fRegister::OnTryCheckRegistration(
+ std::vector<std::vector<uint8_t>>::const_iterator it,
+ U2fReturnCode return_code,
+ const std::vector<uint8_t>& response_data) {
+ switch (return_code) {
+ case U2fReturnCode::SUCCESS:
+ case U2fReturnCode::CONDITIONS_NOT_SATISFIED:
+ // Duplicate registration found. Call bogus registration to check for
+ // user presence (touch) and terminate the registration process.
+ current_device_->Register(U2fRequest::GetBogusApplicationParameter(),
+ U2fRequest::GetBogusChallenge(),
+ false /* no individual attestation */,
+ base::Bind(&U2fRegister::OnTryDevice,
+ weak_factory_.GetWeakPtr(), true));
+ break;
+
+ case U2fReturnCode::INVALID_PARAMS:
+ // Continue to iterate through the provided key handles in the exclude
+ // list and check for already registered keys.
+ if (++it != registered_keys_.end()) {
+ current_device_->Sign(application_parameter_, challenge_digest_, *it,
+ base::Bind(&U2fRegister::OnTryCheckRegistration,
+ weak_factory_.GetWeakPtr(), it),
+ true);
+ } else {
+ checked_device_id_list_.insert(current_device_->GetId());
+ if (devices_.empty()) {
+ // When all devices have been checked, proceed to registration.
+ CompleteNewDeviceRegistration();
+ } else {
+ state_ = State::IDLE;
+ Transition();
+ }
+ }
+ break;
+ default:
+ // Some sort of failure occurred. Abandon this device and move on.
+ state_ = State::IDLE;
+ current_device_ = nullptr;
+ Transition();
+ break;
+ }
+}
+
+void U2fRegister::CompleteNewDeviceRegistration() {
+ if (current_device_)
+ attempted_devices_.push_back(std::move(current_device_));
+
+ devices_.splice(devices_.end(), std::move(attempted_devices_));
+ state_ = State::IDLE;
+ Transition();
+ return;
+}
+
+bool U2fRegister::CheckedForDuplicateRegistration() {
+ return base::ContainsKey(checked_device_id_list_, current_device_->GetId());
+}
+
+void U2fRegister::OnTryDevice(bool is_duplicate_registration,
+ U2fReturnCode return_code,
+ const std::vector<uint8_t>& response_data) {
+ switch (return_code) {
+ case U2fReturnCode::SUCCESS: {
+ state_ = State::COMPLETE;
+ if (is_duplicate_registration) {
+ std::move(completion_callback_)
+ .Run(U2fReturnCode::CONDITIONS_NOT_SATISFIED, base::nullopt);
+ break;
+ }
+ auto response = RegisterResponseData::CreateFromU2fRegisterResponse(
+ application_parameter_, std::move(response_data));
+ if (!response) {
+ // The response data was corrupted / didn't parse properly.
+ std::move(completion_callback_)
+ .Run(U2fReturnCode::FAILURE, base::nullopt);
+ break;
+ }
+ std::move(completion_callback_)
+ .Run(U2fReturnCode::SUCCESS, std::move(response));
+ break;
+ }
+ case U2fReturnCode::CONDITIONS_NOT_SATISFIED:
+ // Waiting for user touch, move on and try this device later.
+ state_ = State::IDLE;
+ Transition();
+ break;
+ default:
+ state_ = State::IDLE;
+ // An error has occurred, quit trying this device.
+ current_device_ = nullptr;
+ Transition();
+ break;
+ }
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_register.h b/chromium/device/fido/u2f_register.h
new file mode 100644
index 00000000000..606273050cf
--- /dev/null
+++ b/chromium/device/fido/u2f_register.h
@@ -0,0 +1,85 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_REGISTER_H_
+#define DEVICE_FIDO_U2F_REGISTER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/optional.h"
+#include "device/fido/u2f_request.h"
+#include "device/fido/u2f_transport_protocol.h"
+
+namespace service_manager {
+class Connector;
+};
+
+namespace device {
+
+class RegisterResponseData;
+
+class U2fRegister : public U2fRequest {
+ public:
+ using RegisterResponseCallback = base::OnceCallback<void(
+ U2fReturnCode status_code,
+ base::Optional<RegisterResponseData> response_data)>;
+
+ static std::unique_ptr<U2fRequest> TryRegistration(
+ service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<std::vector<uint8_t>> registered_keys,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<uint8_t> application_parameter,
+ bool individual_attestation_ok,
+ RegisterResponseCallback completion_callback);
+
+ U2fRegister(service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<std::vector<uint8_t>> registered_keys,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<uint8_t> application_parameter,
+ bool individual_attestation_ok,
+ RegisterResponseCallback completion_callback);
+ ~U2fRegister() override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(U2fRegisterTest, TestCreateU2fRegisterCommand);
+
+ void TryDevice() override;
+ void OnTryDevice(bool is_duplicate_registration,
+ U2fReturnCode return_code,
+ const std::vector<uint8_t>& response_data);
+
+ // Callback function called when non-empty exclude list was provided. This
+ // function iterates through all key handles in |registered_keys_| for all
+ // devices and checks for already registered keys.
+ void OnTryCheckRegistration(
+ std::vector<std::vector<uint8_t>>::const_iterator it,
+ U2fReturnCode return_code,
+ const std::vector<uint8_t>& response_data);
+ // Function handling registration flow after all devices were checked for
+ // already registered keys.
+ void CompleteNewDeviceRegistration();
+ // Returns whether |current_device_| has been checked for duplicate
+ // registration for all key handles provided in |registered_keys_|.
+ bool CheckedForDuplicateRegistration();
+
+ // Indicates whether the token should be signaled that using an individual
+ // attestation certificate is acceptable.
+ const bool individual_attestation_ok_;
+ RegisterResponseCallback completion_callback_;
+
+ // List of authenticators that did not create any of the key handles in the
+ // exclude list.
+ std::set<std::string> checked_device_id_list_;
+ base::WeakPtrFactory<U2fRegister> weak_factory_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_REGISTER_H_
diff --git a/chromium/device/fido/u2f_register_unittest.cc b/chromium/device/fido/u2f_register_unittest.cc
new file mode 100644
index 00000000000..48f7365ae1c
--- /dev/null
+++ b/chromium/device/fido/u2f_register_unittest.cc
@@ -0,0 +1,873 @@
+// 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.
+
+#include "device/fido/u2f_register.h"
+
+#include <iterator>
+#include <utility>
+
+#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/ec_public_key.h"
+#include "device/fido/fido_attestation_statement.h"
+#include "device/fido/mock_u2f_device.h"
+#include "device/fido/mock_u2f_discovery.h"
+#include "device/fido/register_response_data.h"
+#include "device/fido/u2f_parsing_utils.h"
+#include "device/fido/u2f_response_test_data.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+using ::testing::_;
+
+namespace {
+
+constexpr uint8_t kTestRelyingPartyIdSHA256[32] = {
+ 0xd4, 0xc9, 0xd9, 0x02, 0x73, 0x26, 0x27, 0x1a, 0x89, 0xce, 0x51,
+ 0xfc, 0xaf, 0x32, 0x8e, 0xd6, 0x73, 0xf1, 0x7b, 0xe3, 0x34, 0x69,
+ 0xff, 0x97, 0x9e, 0x8a, 0xb8, 0xdd, 0x50, 0x1e, 0x66, 0x4f,
+};
+constexpr bool kNoIndividualAttestation = false;
+constexpr bool kIndividualAttestation = true;
+
+// EC public key encoded in COSE_Key format.
+// x : F868CE3869605224CE1059C0047EF01B830F2AD93BE27A3211F44E894560E695
+// y : 4E11538CABA2DF1CC1A6F250ED9F0C8B28B39DA44539DFABD46B589CD0E202E5
+constexpr uint8_t kTestECPublicKeyCOSE[] = {
+ // clang-format off
+ 0xA5, // map(5)
+ 0x01, 0x02, // kty: EC key type
+ 0x03, 0x26, // alg: EC256 signature algorithm
+ 0x20, 0x01, // crv: P-256 curve
+ 0x21, // x-coordinate
+ 0x58, 0x20, // bytes(32)
+ 0xF8, 0x68, 0xCE, 0x38, 0x69, 0x60, 0x52, 0x24, 0xCE, 0x10, 0x59, 0xC0,
+ 0x04, 0x7E, 0xF0, 0x1B, 0x83, 0x0F, 0x2A, 0xD9, 0x3B, 0xE2, 0x7A, 0x32,
+ 0x11, 0xF4, 0x4E, 0x89, 0x45, 0x60, 0xE6, 0x95,
+ 0x22, // y-coordinate
+ 0x58, 0x20, // bytes(32)
+ 0x4E, 0x11, 0x53, 0x8C, 0xAB, 0xA2, 0xDF, 0x1C, 0xC1, 0xA6, 0xF2, 0x50,
+ 0xED, 0x9F, 0x0C, 0x8B, 0x28, 0xB3, 0x9D, 0xA4, 0x45, 0x39, 0xDF, 0xAB,
+ 0xD4, 0x6B, 0x58, 0x9C, 0xD0, 0xE2, 0x02, 0xE5,
+ // 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[] = {
+ // 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
+ 0x89, 0xAF, 0xB5, 0x24, 0x91, 0x1C, 0x40, 0x2B, 0x7F, 0x74, 0x59, 0xC9,
+ 0xF2, 0x21, 0xAF, 0xE6, 0xE5, 0x56, 0x65, 0x85, 0x04, 0xE8, 0x5B, 0x49,
+ 0x4D, 0x07, 0x55, 0x55, 0xF4, 0x6A, 0xBC, 0x44, 0x7B, 0x15, 0xFC, 0x62,
+ 0x61, 0x90, 0xA5, 0xFE, 0xEB, 0xE5, 0x9F, 0x5E, 0xDC, 0x75, 0x32, 0x98,
+ 0x6F, 0x44, 0x69, 0xD7, 0xF6, 0x13, 0xEB, 0xAA, 0xEA, 0x33, 0xFB, 0xD5,
+ 0x8E, 0xBF, 0xC6, 0x09 // 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 kTestRelyingPartyId
+ 0xD4, 0xC9, 0xD9, 0x02, 0x73, 0x26, 0x27, 0x1A, 0x89, 0xCE, 0x51,
+ 0xFC, 0xAF, 0x32, 0x8E, 0xD6, 0x73, 0xF1, 0x7B, 0xE3, 0x34, 0x69,
+ 0xFF, 0x97, 0x9E, 0x8A, 0xB8, 0xDD, 0x50, 0x1E, 0x66, 0x4F,
+ 0x41, // flags (TUP and AT bits set)
+ 0x00, 0x00, 0x00, 0x00 // counter
+ // clang-format on
+};
+
+// The attestation statement, a CBOR-encoded byte array.
+// Diagnostic notation:
+// {"sig":
+// h'3044022008C3F8DB6E29FD8B14D9DE1BD98E84072CB813385989AA2CA289395E0009B8B70 \
+// 2202607B4F9AD05DE26F56F48B82569EAD8231A5A6C3A1448DEAAAF15C0EF29631A',
+// "x5c": [h'3082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0 \
+// 500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269 \
+// 616C203435373230303633313020170D3134303830313030303030305A180F3230353030393 \
+// 0343030303030305A302C312A302806035504030C2159756269636F20553246204545205365 \
+// 7269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D0 \
+// 30107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742B \
+// B476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B30393 \
+// 02206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013 \
+// 060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B05000382010 \
+// 1009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78 \
+// B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874 \
+// F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F600438 \
+// 5BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B265 \
+// 6E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DE \
+// F9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC \
+// 74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F5']}
+constexpr uint8_t kU2fAttestationStatementCBOR[] = {
+ // clang-format off
+ 0xA2, // map(2)
+ 0x63, // text(3)
+ 0x73, 0x69, 0x67, // "sig"
+ 0x58, 0x46, // bytes(70)
+ 0x30, 0x44, 0x02, 0x20, 0x08, 0xC3, 0xF8, 0xDB, 0x6E, 0x29, 0xFD, 0x8B,
+ 0x14, 0xD9, 0xDE, 0x1B, 0xD9, 0x8E, 0x84, 0x07, 0x2C, 0xB8, 0x13, 0x38,
+ 0x59, 0x89, 0xAA, 0x2C, 0xA2, 0x89, 0x39, 0x5E, 0x00, 0x09, 0xB8, 0xB7,
+ 0x02, 0x20, 0x26, 0x07, 0xB4, 0xF9, 0xAD, 0x05, 0xDE, 0x26, 0xF5, 0x6F,
+ 0x48, 0xB8, 0x25, 0x69, 0xEA, 0xD8, 0x23, 0x1A, 0x5A, 0x6C, 0x3A, 0x14,
+ 0x48, 0xDE, 0xAA, 0xAF, 0x15, 0xC0, 0xEF, 0x29, 0x63, 0x1A,
+ 0x63, // text(3)
+ 0x78, 0x35, 0x63, // "x5c"
+ 0x81, // array(1)
+ 0x59, 0x02, 0x4E, // bytes(590)
+ 0x30, 0x82, 0x02, 0x4A, 0x30, 0x82, 0x01, 0x32, 0xA0, 0x03, 0x02,
+ 0x01, 0x02, 0x02, 0x04, 0x04, 0x6C, 0x88, 0x22, 0x30, 0x0D, 0x06,
+ 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05,
+ 0x00, 0x30, 0x2E, 0x31, 0x2C, 0x30, 0x2A, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x23, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x55,
+ 0x32, 0x46, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20,
+ 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x34, 0x35, 0x37, 0x32,
+ 0x30, 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0x0D, 0x31, 0x34,
+ 0x30, 0x38, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A,
+ 0x18, 0x0F, 0x32, 0x30, 0x35, 0x30, 0x30, 0x39, 0x30, 0x34, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, 0x2C, 0x31, 0x2A, 0x30,
+ 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x21, 0x59, 0x75, 0x62,
+ 0x69, 0x63, 0x6F, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20,
+ 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x32, 0x34, 0x39, 0x31,
+ 0x38, 0x32, 0x33, 0x32, 0x34, 0x37, 0x37, 0x30, 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, 0x3C, 0xCA, 0xB9, 0x2C, 0xCB, 0x97, 0x28, 0x7E, 0xE8,
+ 0xE6, 0x39, 0x43, 0x7E, 0x21, 0xFC, 0xD6, 0xB6, 0xF1, 0x65, 0xB2,
+ 0xD5, 0xA3, 0xF3, 0xDB, 0x13, 0x1D, 0x31, 0xC1, 0x6B, 0x74, 0x2B,
+ 0xB4, 0x76, 0xD8, 0xD1, 0xE9, 0x90, 0x80, 0xEB, 0x54, 0x6C, 0x9B,
+ 0xBD, 0xF5, 0x56, 0xE6, 0x21, 0x0F, 0xD4, 0x27, 0x85, 0x89, 0x9E,
+ 0x78, 0xCC, 0x58, 0x9E, 0xBE, 0x31, 0x0F, 0x6C, 0xDB, 0x9F, 0xF4,
+ 0xA3, 0x3B, 0x30, 0x39, 0x30, 0x22, 0x06, 0x09, 0x2B, 0x06, 0x01,
+ 0x04, 0x01, 0x82, 0xC4, 0x0A, 0x02, 0x04, 0x15, 0x31, 0x2E, 0x33,
+ 0x2E, 0x36, 0x2E, 0x31, 0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x34, 0x31,
+ 0x34, 0x38, 0x32, 0x2E, 0x31, 0x2E, 0x32, 0x30, 0x13, 0x06, 0x0B,
+ 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xE5, 0x1C, 0x02, 0x01, 0x01,
+ 0x04, 0x04, 0x03, 0x02, 0x04, 0x30, 0x30, 0x0D, 0x06, 0x09, 0x2A,
+ 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x9F, 0x9B, 0x05, 0x22, 0x48, 0xBC, 0x4C,
+ 0xF4, 0x2C, 0xC5, 0x99, 0x1F, 0xCA, 0xAB, 0xAC, 0x9B, 0x65, 0x1B,
+ 0xBE, 0x5B, 0xDC, 0xDC, 0x8E, 0xF0, 0xAD, 0x2C, 0x1C, 0x1F, 0xFB,
+ 0x36, 0xD1, 0x87, 0x15, 0xD4, 0x2E, 0x78, 0xB2, 0x49, 0x22, 0x4F,
+ 0x92, 0xC7, 0xE6, 0xE7, 0xA0, 0x5C, 0x49, 0xF0, 0xE7, 0xE4, 0xC8,
+ 0x81, 0xBF, 0x2E, 0x94, 0xF4, 0x5E, 0x4A, 0x21, 0x83, 0x3D, 0x74,
+ 0x56, 0x85, 0x1D, 0x0F, 0x6C, 0x14, 0x5A, 0x29, 0x54, 0x0C, 0x87,
+ 0x4F, 0x30, 0x92, 0xC9, 0x34, 0xB4, 0x3D, 0x22, 0x2B, 0x89, 0x62,
+ 0xC0, 0xF4, 0x10, 0xCE, 0xF1, 0xDB, 0x75, 0x89, 0x2A, 0xF1, 0x16,
+ 0xB4, 0x4A, 0x96, 0xF5, 0xD3, 0x5A, 0xDE, 0xA3, 0x82, 0x2F, 0xC7,
+ 0x14, 0x6F, 0x60, 0x04, 0x38, 0x5B, 0xCB, 0x69, 0xB6, 0x5C, 0x99,
+ 0xE7, 0xEB, 0x69, 0x19, 0x78, 0x67, 0x03, 0xC0, 0xD8, 0xCD, 0x41,
+ 0xE8, 0xF7, 0x5C, 0xCA, 0x44, 0xAA, 0x8A, 0xB7, 0x25, 0xAD, 0x8E,
+ 0x79, 0x9F, 0xF3, 0xA8, 0x69, 0x6A, 0x6F, 0x1B, 0x26, 0x56, 0xE6,
+ 0x31, 0xB1, 0xE4, 0x01, 0x83, 0xC0, 0x8F, 0xDA, 0x53, 0xFA, 0x4A,
+ 0x8F, 0x85, 0xA0, 0x56, 0x93, 0x94, 0x4A, 0xE1, 0x79, 0xA1, 0x33,
+ 0x9D, 0x00, 0x2D, 0x15, 0xCA, 0xBD, 0x81, 0x00, 0x90, 0xEC, 0x72,
+ 0x2E, 0xF5, 0xDE, 0xF9, 0x96, 0x5A, 0x37, 0x1D, 0x41, 0x5D, 0x62,
+ 0x4B, 0x68, 0xA2, 0x70, 0x7C, 0xAD, 0x97, 0xBC, 0xDD, 0x17, 0x85,
+ 0xAF, 0x97, 0xE2, 0x58, 0xF3, 0x3D, 0xF5, 0x6A, 0x03, 0x1A, 0xA0,
+ 0x35, 0x6D, 0x8E, 0x8D, 0x5E, 0xBC, 0xAD, 0xC7, 0x4E, 0x07, 0x16,
+ 0x36, 0xC6, 0xB1, 0x10, 0xAC, 0xE5, 0xCC, 0x9B, 0x90, 0xDF, 0xEA,
+ 0xCA, 0xE6, 0x40, 0xFF, 0x1B, 0xB0, 0xF1, 0xFE, 0x5D, 0xB4, 0xEF,
+ 0xF7, 0xA9, 0x5F, 0x06, 0x07, 0x33, 0xF5
+ // 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
+};
+
+constexpr uint8_t kAppIdDigest[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01,
+ 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01};
+
+constexpr uint8_t kChallengeDigest[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01,
+ 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01};
+
+// Helpers for testing U2f register responses.
+std::vector<uint8_t> GetTestECPublicKeyCOSE() {
+ return std::vector<uint8_t>(std::begin(kTestECPublicKeyCOSE),
+ std::end(kTestECPublicKeyCOSE));
+}
+
+std::vector<uint8_t> GetTestRegisterResponse() {
+ return std::vector<uint8_t>(std::begin(test_data::kTestU2fRegisterResponse),
+ std::end(test_data::kTestU2fRegisterResponse));
+}
+
+std::vector<uint8_t> GetTestCredentialRawIdBytes() {
+ return std::vector<uint8_t>(std::begin(test_data::kTestCredentialRawIdBytes),
+ std::end(test_data::kTestCredentialRawIdBytes));
+}
+
+std::vector<uint8_t> GetU2fAttestationStatementCBOR() {
+ return std::vector<uint8_t>(std::begin(kU2fAttestationStatementCBOR),
+ std::end(kU2fAttestationStatementCBOR));
+}
+
+std::vector<uint8_t> GetTestAttestedCredentialDataBytes() {
+ // Combine kTestAttestedCredentialDataPrefix and kTestECPublicKeyCOSE.
+ std::vector<uint8_t> test_attested_data(
+ std::begin(kTestAttestedCredentialDataPrefix),
+ std::end(kTestAttestedCredentialDataPrefix));
+ test_attested_data.insert(test_attested_data.end(),
+ std::begin(kTestECPublicKeyCOSE),
+ std::end(kTestECPublicKeyCOSE));
+ return test_attested_data;
+}
+
+std::vector<uint8_t> GetTestAuthenticatorDataBytes() {
+ // Build the test authenticator data.
+ std::vector<uint8_t> test_authenticator_data(
+ std::begin(kTestAuthenticatorDataPrefix),
+ std::end(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() {
+ std::vector<uint8_t> test_authenticator_object(std::begin(kFormatFidoU2fCBOR),
+ std::end(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(kU2fAttestationStatementCBOR),
+ std::end(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;
+}
+
+// Convenience functions for setting a mock discovery.
+MockU2fDiscovery* SetMockDiscovery(
+ U2fRequest* request,
+ std::unique_ptr<MockU2fDiscovery> discovery) {
+ auto* raw_discovery = discovery.get();
+ std::vector<std::unique_ptr<U2fDiscovery>> discoveries;
+ discoveries.push_back(std::move(discovery));
+ request->SetDiscoveriesForTesting(std::move(discoveries));
+ return raw_discovery;
+}
+
+} // namespace
+
+class U2fRegisterTest : public testing::Test {
+ public:
+ U2fRegisterTest()
+ : scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
+
+ protected:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+class TestRegisterCallback {
+ public:
+ TestRegisterCallback()
+ : callback_(base::BindOnce(&TestRegisterCallback::ReceivedCallback,
+ base::Unretained(this))) {}
+ ~TestRegisterCallback() = default;
+
+ void ReceivedCallback(U2fReturnCode status_code,
+ base::Optional<RegisterResponseData> response_data) {
+ response_ = std::make_pair(status_code, std::move(response_data));
+ closure_.Run();
+ }
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ WaitForCallback() {
+ closure_ = run_loop_.QuitClosure();
+ run_loop_.Run();
+ return response_;
+ }
+
+ U2fRegister::RegisterResponseCallback callback() {
+ return std::move(callback_);
+ }
+
+ private:
+ std::pair<U2fReturnCode, base::Optional<RegisterResponseData>> response_;
+ base::Closure closure_;
+ U2fRegister::RegisterResponseCallback callback_;
+ base::RunLoop run_loop_;
+};
+
+TEST_F(U2fRegisterTest, TestCreateU2fRegisterCommand) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> registration_keys;
+ TestRegisterCallback cb;
+
+ constexpr uint8_t kRegisterApduCommandWithoutAttestation[] = {
+ 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x40, 0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01};
+
+ U2fRegister register_request(
+ nullptr, protocols, registration_keys,
+ std::vector<uint8_t>(std::begin(kChallengeDigest),
+ std::end(kChallengeDigest)),
+ std::vector<uint8_t>(std::begin(kAppIdDigest), std::end(kAppIdDigest)),
+ kNoIndividualAttestation, cb.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->GetEncodedCommand(),
+ testing::ElementsAreArray(kRegisterApduCommandWithoutAttestation));
+
+ constexpr uint8_t kRegisterApduCommandWithAttestation[] = {
+ // clang-format off
+ 0x00, 0x01, 0x83, 0x00, // CLA, INS, P1, P2 APDU instructions
+ 0x00, 0x00, 0x40, // Data length in 3 bytes in big endian order
+ // Application parameter
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01,
+ // Challenge parameter
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01
+ // clang-format on
+ };
+
+ 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->GetEncodedCommand(),
+ testing::ElementsAreArray(kRegisterApduCommandWithAttestation));
+}
+
+TEST_F(U2fRegisterTest, TestRegisterSuccess) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> registration_keys;
+ TestRegisterCallback cb;
+
+ auto request = std::make_unique<U2fRegister>(
+ nullptr, protocols, registration_keys, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), kNoIndividualAttestation, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ EXPECT_CALL(*device, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ response = cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, std::get<0>(response));
+ EXPECT_EQ(GetTestCredentialRawIdBytes(), std::get<1>(response)->raw_id());
+}
+
+TEST_F(U2fRegisterTest, TestDelayedSuccess) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> registration_keys;
+ TestRegisterCallback cb;
+
+ auto request = std::make_unique<U2fRegister>(
+ nullptr, protocols, registration_keys, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), kNoIndividualAttestation, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ // Go through the state machine twice before success.
+ EXPECT_CALL(*device, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .Times(2)
+ .WillRepeatedly(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ response = cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, std::get<0>(response));
+ EXPECT_EQ(GetTestCredentialRawIdBytes(), std::get<1>(response)->raw_id());
+}
+
+TEST_F(U2fRegisterTest, TestMultipleDevices) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> registration_keys;
+ TestRegisterCallback cb;
+
+ auto request = std::make_unique<U2fRegister>(
+ nullptr, protocols, registration_keys, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), kNoIndividualAttestation, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device0 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device0, GetId()).WillRepeatedly(testing::Return("device0"));
+ EXPECT_CALL(*device0, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied));
+ // One wink per device.
+ EXPECT_CALL(*device0, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device0));
+
+ // Second device will have a successful touch.
+ auto device1 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device1, GetId()).WillRepeatedly(testing::Return("device1"));
+ EXPECT_CALL(*device1, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ // One wink per device.
+ EXPECT_CALL(*device1, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device1));
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ response = cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, std::get<0>(response));
+ EXPECT_EQ(GetTestCredentialRawIdBytes(),
+ std::get<1>(response).value().raw_id());
+}
+
+// Tests a scenario where a single device is connected and registration call
+// is received with three unknown key handles. We expect that three check only
+// sign-in calls be processed before registration.
+TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithExclusionList) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ // Simulate three unknown key handles.
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xB);
+ handles.emplace_back(32, 0xC);
+ handles.emplace_back(32, 0xD);
+ TestRegisterCallback cb;
+
+ auto request = std::make_unique<U2fRegister>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), kNoIndividualAttestation, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ // DeviceTransact() will be called four times including three check
+ // only sign-in calls and one registration call. For the first three calls,
+ // device will invoke MockU2fDevice::WrongData as the authenticator did not
+ // create the three key handles provided in the exclude list. At the fourth
+ // call, MockU2fDevice::NoErrorRegister will be invoked after registration.
+ EXPECT_CALL(*device.get(), DeviceTransactPtr(_, _))
+ .Times(4)
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ // TryWink() will be called twice. First during the check only sign-in. After
+ // check only sign operation is complete, request state is changed to IDLE,
+ // and TryWink() is called again before Register() is called.
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ response = cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, std::get<0>(response));
+ EXPECT_EQ(GetTestCredentialRawIdBytes(), std::get<1>(response)->raw_id());
+}
+
+// Tests a scenario where two devices are connected and registration call is
+// received with three unknown key handles. We assume that user will proceed the
+// registration with second device, "device1".
+TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithExclusionList) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ // Simulate three unknown key handles.
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xB);
+ handles.emplace_back(32, 0xC);
+ handles.emplace_back(32, 0xD);
+ TestRegisterCallback cb;
+
+ auto request = std::make_unique<U2fRegister>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), kNoIndividualAttestation, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device0 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device0, GetId()).WillRepeatedly(testing::Return("device0"));
+ // DeviceTransact() will be called four times: three times to check for
+ // duplicate key handles and once for registration. Since user
+ // will register using "device1", the fourth call will invoke
+ // MockU2fDevice::NotSatisfied.
+ EXPECT_CALL(*device0, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied));
+ // TryWink() will be called twice on both devices -- during check only
+ // sign-in operation and during registration attempt.
+ EXPECT_CALL(*device0, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device0));
+
+ auto device1 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device1, GetId()).WillRepeatedly(testing::Return("device1"));
+ // We assume that user registers with second device. Therefore, the fourth
+ // DeviceTransact() will invoke MockU2fDevice::NoErrorRegister after
+ // successful registration.
+ EXPECT_CALL(*device1, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ // TryWink() will be called twice on both devices -- during check only
+ // sign-in operation and during registration attempt.
+ EXPECT_CALL(*device1, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device1));
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ response = cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, std::get<0>(response));
+ EXPECT_EQ(GetTestCredentialRawIdBytes(), std::get<1>(response)->raw_id());
+}
+
+// Tests a scenario where single device is connected and registration is called
+// with a key in the exclude list that was created by this device. We assume
+// that the duplicate key is the last key handle in the exclude list. Therefore,
+// after duplicate key handle is found, the process is expected to terminate
+// after calling bogus registration which checks for user presence.
+TEST_F(U2fRegisterTest, TestSingleDeviceRegistrationWithDuplicateHandle) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ // Simulate three unknown key handles followed by a duplicate key.
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xB);
+ handles.emplace_back(32, 0xC);
+ handles.emplace_back(32, 0xD);
+ handles.emplace_back(32, 0xA);
+ TestRegisterCallback cb;
+
+ auto request = std::make_unique<U2fRegister>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), kNoIndividualAttestation, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ // For four keys in exclude list, the first three keys will invoke
+ // MockU2fDevice::WrongData and the final duplicate key handle will invoke
+ // MockU2fDevice::NoErrorSign. Once duplicate key handle is found, bogus
+ // registration is called to confirm user presence. This invokes
+ // MockU2fDevice::NoErrorRegister.
+ EXPECT_CALL(*device, DeviceTransactPtr(_, _))
+ .Times(5)
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ // Since duplicate key handle is found, registration process is terminated
+ // before actual Register() is called on the device. Therefore, TryWink() is
+ // invoked once.
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ response = cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::CONDITIONS_NOT_SATISFIED, std::get<0>(response));
+ EXPECT_EQ(base::nullopt, std::get<1>(response));
+}
+
+// Tests a scenario where one (device1) of the two devices connected has created
+// a key handle provided in exclude list. We assume that duplicate key is the
+// fourth key handle provided in the exclude list.
+TEST_F(U2fRegisterTest, TestMultipleDeviceRegistrationWithDuplicateHandle) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ // Simulate three unknown key handles followed by a duplicate key.
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xB);
+ handles.emplace_back(32, 0xC);
+ handles.emplace_back(32, 0xD);
+ handles.emplace_back(32, 0xA);
+ TestRegisterCallback cb;
+
+ auto request = std::make_unique<U2fRegister>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), kNoIndividualAttestation, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device0 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device0, GetId()).WillRepeatedly(testing::Return("device0"));
+ // Since the first device did not create any of the key handles provided in
+ // exclude list, we expect that check only sign() should be called
+ // four times, and all the calls to DeviceTransact() invoke
+ // MockU2fDevice::WrongData.
+ EXPECT_CALL(*device0, DeviceTransactPtr(_, _))
+ .Times(4)
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData));
+ EXPECT_CALL(*device0, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device0));
+
+ auto device1 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device1, GetId()).WillRepeatedly(testing::Return("device1"));
+ // Since the last key handle in exclude list is a duplicate key, we expect
+ // that the first three calls to check only sign() invoke
+ // MockU2fDevice::WrongData and that fourth sign() call invoke
+ // MockU2fDevice::NoErrorSign. After duplicate key is found, process is
+ // terminated after user presence is verified using bogus registration, which
+ // invokes MockU2fDevice::NoErrorRegister.
+ EXPECT_CALL(*device1, DeviceTransactPtr(_, _))
+ .Times(5)
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ EXPECT_CALL(*device1, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device1));
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ response = cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::CONDITIONS_NOT_SATISFIED, std::get<0>(response));
+ EXPECT_EQ(base::nullopt, std::get<1>(response));
+}
+
+// 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::vector<uint8_t>(16) /* aaguid */,
+ 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::vector<uint8_t>(16) /* aaguid */,
+ 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(
+ std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ 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::vector<uint8_t>(16) /* aaguid */,
+ 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(
+ std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ 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<RegisterResponseData> response =
+ RegisterResponseData::CreateFromU2fRegisterResponse(
+ std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ GetTestRegisterResponse());
+ EXPECT_EQ(GetTestCredentialRawIdBytes(), response->raw_id());
+ EXPECT_EQ(GetTestAttestationObjectBytes(),
+ response->GetCBOREncodedAttestationObject());
+}
+
+MATCHER_P(IndicatesIndividualAttestation, expected, "") {
+ return arg.size() >= 2 && ((arg[2] & 0x80) == 0x80) == expected;
+}
+
+TEST_F(U2fRegisterTest, TestIndividualAttestation) {
+ // Test that the individual attestation flag is correctly reflected in the
+ // resulting registration APDU.
+ for (const auto& individual_attestation : {false, true}) {
+ SCOPED_TRACE(individual_attestation);
+
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> registration_keys;
+ TestRegisterCallback cb;
+
+ auto request = std::make_unique<U2fRegister>(
+ nullptr, protocols, registration_keys, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), individual_attestation, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ EXPECT_CALL(*device,
+ DeviceTransactPtr(
+ IndicatesIndividualAttestation(individual_attestation), _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ const std::pair<U2fReturnCode, base::Optional<RegisterResponseData>>&
+ response = cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, std::get<0>(response));
+ EXPECT_EQ(GetTestCredentialRawIdBytes(), std::get<1>(response)->raw_id());
+ }
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_request.cc b/chromium/device/fido/u2f_request.cc
new file mode 100644
index 00000000000..40c89f0745c
--- /dev/null
+++ b/chromium/device/fido/u2f_request.cc
@@ -0,0 +1,222 @@
+// 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.
+
+#include "device/fido/u2f_request.h"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "device/fido/u2f_apdu_command.h"
+#include "device/fido/u2f_ble_discovery.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+// HID is not supported on Android.
+#if !defined(OS_ANDROID)
+#include "device/fido/u2f_hid_discovery.h"
+#endif // !defined(OS_ANDROID)
+
+namespace device {
+
+U2fRequest::U2fRequest(service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<uint8_t> application_parameter,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<std::vector<uint8_t>> registered_keys)
+ : state_(State::INIT),
+ application_parameter_(application_parameter),
+ challenge_digest_(challenge_digest),
+ registered_keys_(registered_keys),
+ weak_factory_(this) {
+ for (const auto protocol : protocols) {
+ std::unique_ptr<U2fDiscovery> discovery;
+ switch (protocol) {
+ case U2fTransportProtocol::kUsbHumanInterfaceDevice:
+#if !defined(OS_ANDROID)
+ DCHECK(connector);
+ discovery = std::make_unique<U2fHidDiscovery>(connector);
+#endif // !defined(OS_ANDROID)
+ break;
+ case U2fTransportProtocol::kBluetoothLowEnergy:
+ discovery = std::make_unique<U2fBleDiscovery>();
+ break;
+ }
+
+ discovery->AddObserver(this);
+ discoveries_.push_back(std::move(discovery));
+ }
+}
+
+U2fRequest::~U2fRequest() {
+ for (auto& discovery : discoveries_)
+ discovery->RemoveObserver(this);
+}
+
+void U2fRequest::Start() {
+ if (state_ == State::INIT) {
+ state_ = State::IDLE;
+ for (auto& discovery : discoveries_)
+ discovery->Start();
+ }
+}
+
+void U2fRequest::SetDiscoveriesForTesting(
+ std::vector<std::unique_ptr<U2fDiscovery>> discoveries) {
+ discoveries_ = std::move(discoveries);
+ for (auto& discovery : discoveries_)
+ discovery->AddObserver(this);
+}
+
+// static
+const std::vector<uint8_t>& U2fRequest::GetBogusApplicationParameter() {
+ static const std::vector<uint8_t> kBogusAppParam(32, 0x41);
+ return kBogusAppParam;
+}
+
+// static
+const std::vector<uint8_t>& U2fRequest::GetBogusChallenge() {
+ static const std::vector<uint8_t> kBogusChallenge(32, 0x42);
+ return kBogusChallenge;
+}
+
+// static
+std::unique_ptr<U2fApduCommand> U2fRequest::GetU2fVersionApduCommand(
+ bool is_legacy_version) {
+ return is_legacy_version ? U2fApduCommand::CreateLegacyVersion()
+ : U2fApduCommand::CreateVersion();
+}
+
+std::unique_ptr<U2fApduCommand> U2fRequest::GetU2fSignApduCommand(
+ const std::vector<uint8_t>& key_handle,
+ bool is_check_only_sign) const {
+ return U2fApduCommand::CreateSign(application_parameter_, challenge_digest_,
+ key_handle, is_check_only_sign);
+}
+
+std::unique_ptr<U2fApduCommand> U2fRequest::GetU2fRegisterApduCommand(
+ bool is_individual_attestation) const {
+ return U2fApduCommand::CreateRegister(
+ application_parameter_, challenge_digest_, is_individual_attestation);
+}
+
+void U2fRequest::Transition() {
+ switch (state_) {
+ case State::IDLE:
+ IterateDevice();
+ if (!current_device_) {
+ // No devices available
+ state_ = State::OFF;
+ break;
+ }
+ state_ = State::WINK;
+ current_device_->TryWink(
+ base::Bind(&U2fRequest::Transition, weak_factory_.GetWeakPtr()));
+ break;
+ case State::WINK:
+ state_ = State::BUSY;
+ TryDevice();
+ break;
+ default:
+ break;
+ }
+}
+
+void U2fRequest::DiscoveryStarted(U2fDiscovery* discovery, bool success) {
+ if (success) {
+ // The discovery might know about devices that have already been added to
+ // the system. Add them to the |devices_| list if we don't already know
+ // about them. This case could happen if a DeviceAdded() event is emitted
+ // before DiscoveryStarted() is invoked. In that case both the U2fRequest
+ // and the U2fDiscovery already know about the just added device.
+ std::set<std::string> current_device_ids;
+ for (const auto* device : attempted_devices_)
+ current_device_ids.insert(device->GetId());
+ if (current_device_)
+ current_device_ids.insert(current_device_->GetId());
+ for (const auto* device : devices_)
+ current_device_ids.insert(device->GetId());
+
+ auto new_devices = discovery->GetDevices();
+ std::copy_if(
+ new_devices.begin(), new_devices.end(), std::back_inserter(devices_),
+ [&current_device_ids](U2fDevice* device) {
+ return !base::ContainsKey(current_device_ids, device->GetId());
+ });
+ }
+
+ started_count_++;
+
+ if ((state_ == State::IDLE || state_ == State::OFF) &&
+ (success || started_count_ == discoveries_.size())) {
+ state_ = State::IDLE;
+ Transition();
+ }
+}
+
+void U2fRequest::DiscoveryStopped(U2fDiscovery* discovery, bool success) {}
+
+void U2fRequest::DeviceAdded(U2fDiscovery* discovery, U2fDevice* device) {
+ devices_.push_back(device);
+
+ // Start the state machine if this is the only device
+ if (state_ == State::OFF) {
+ state_ = State::IDLE;
+ delay_callback_.Cancel();
+ Transition();
+ }
+}
+
+void U2fRequest::DeviceRemoved(U2fDiscovery* discovery, U2fDevice* device) {
+ const std::string device_id = device->GetId();
+ auto device_id_eq = [&device_id](const U2fDevice* this_device) {
+ return device_id == this_device->GetId();
+ };
+
+ // Check if the active device was removed
+ if (current_device_ && device_id_eq(current_device_)) {
+ current_device_ = nullptr;
+ state_ = State::IDLE;
+ Transition();
+ return;
+ }
+
+ // Remove the device if it exists in either device list
+ devices_.remove_if(device_id_eq);
+ attempted_devices_.remove_if(device_id_eq);
+}
+
+void U2fRequest::IterateDevice() {
+ // Move active device to attempted device list
+ if (current_device_) {
+ attempted_devices_.push_back(current_device_);
+ current_device_ = nullptr;
+ }
+
+ // If there is an additional device on device list, make it active.
+ // Otherwise, if all devices have been tried, move attempted devices back to
+ // the main device list.
+ if (devices_.size() > 0) {
+ current_device_ = devices_.front();
+ devices_.pop_front();
+ } else if (attempted_devices_.size() > 0) {
+ devices_ = std::move(attempted_devices_);
+ // After trying every device, wait 200ms before trying again
+ delay_callback_.Reset(
+ base::Bind(&U2fRequest::OnWaitComplete, weak_factory_.GetWeakPtr()));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, delay_callback_.callback(),
+ base::TimeDelta::FromMilliseconds(200));
+ }
+}
+
+void U2fRequest::OnWaitComplete() {
+ state_ = State::IDLE;
+ Transition();
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_request.h b/chromium/device/fido/u2f_request.h
new file mode 100644
index 00000000000..cf49e9b10eb
--- /dev/null
+++ b/chromium/device/fido/u2f_request.h
@@ -0,0 +1,119 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_REQUEST_H_
+#define DEVICE_FIDO_U2F_REQUEST_H_
+
+#include <list>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/cancelable_callback.h"
+#include "base/containers/flat_set.h"
+#include "base/optional.h"
+#include "device/fido/u2f_apdu_command.h"
+#include "device/fido/u2f_device.h"
+#include "device/fido/u2f_discovery.h"
+#include "device/fido/u2f_transport_protocol.h"
+
+namespace service_manager {
+class Connector;
+}; // namespace service_manager
+
+namespace device {
+
+class U2fRequest : public U2fDiscovery::Observer {
+ public:
+ // U2fRequest will create a discovery instance and register itself as an
+ // observer for each passed in transport protocol.
+ // TODO(https://crbug.com/769631): Remove the dependency on Connector once U2F
+ // is servicified.
+ U2fRequest(service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<uint8_t> application_parameter,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<std::vector<uint8_t>> registered_keys);
+ ~U2fRequest() override;
+
+ void Start();
+
+ // Enables the overriding of discoveries for testing. Useful for fakes such as
+ // MockU2fDiscovery.
+ void SetDiscoveriesForTesting(
+ std::vector<std::unique_ptr<U2fDiscovery>> discoveries);
+
+ // Returns bogus application parameter and challenge to be used to verify user
+ // presence.
+ static const std::vector<uint8_t>& GetBogusApplicationParameter();
+ static const std::vector<uint8_t>& GetBogusChallenge();
+ // 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::unique_ptr<U2fApduCommand> GetU2fVersionApduCommand(
+ bool is_legacy_version);
+ // Returns APDU U2F request commands. Nullptr is returned for
+ // incorrectly formatted parameter.
+ std::unique_ptr<U2fApduCommand> GetU2fSignApduCommand(
+ const std::vector<uint8_t>& key_handle,
+ bool is_check_only_sign = false) const;
+ std::unique_ptr<U2fApduCommand> GetU2fRegisterApduCommand(
+ bool is_individual_attestation) const;
+
+ protected:
+ enum class State {
+ INIT,
+ BUSY,
+ WINK,
+ IDLE,
+ OFF,
+ COMPLETE,
+ };
+
+ void Transition();
+
+ virtual void TryDevice() = 0;
+
+ // Hold handles to the devices known to the system. Known devices are
+ // partitioned into three parts:
+ // [attempted_devices_), current_device_, [devices_)
+ // During device iteration the |current_device_| gets pushed to
+ // |attempted_devices_|, and, if possible, the first element of |devices_|
+ // gets popped and becomes the new |current_device_|. Once all |devices_| are
+ // exhausted, |attempted_devices_| get moved into |devices_| and
+ // |current_device_| is reset.
+ U2fDevice* current_device_ = nullptr;
+ std::list<U2fDevice*> devices_;
+ std::list<U2fDevice*> attempted_devices_;
+ State state_;
+ std::vector<std::unique_ptr<U2fDiscovery>> discoveries_;
+ std::vector<uint8_t> application_parameter_;
+ std::vector<uint8_t> challenge_digest_;
+ std::vector<std::vector<uint8_t>> registered_keys_;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestIterateDevice);
+ FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestBasicMachine);
+ FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestAlreadyPresentDevice);
+ FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestMultipleDiscoveries);
+ FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestSlowDiscovery);
+ FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestMultipleDiscoveriesWithFailures);
+
+ // U2fDiscovery::Observer
+ void DiscoveryStarted(U2fDiscovery* discovery, bool success) override;
+ void DiscoveryStopped(U2fDiscovery* discovery, bool success) override;
+ void DeviceAdded(U2fDiscovery* discovery, U2fDevice* device) override;
+ void DeviceRemoved(U2fDiscovery* discovery, U2fDevice* device) override;
+
+ void IterateDevice();
+ void OnWaitComplete();
+
+ base::CancelableClosure delay_callback_;
+ size_t started_count_ = 0;
+
+ base::WeakPtrFactory<U2fRequest> weak_factory_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_REQUEST_H_
diff --git a/chromium/device/fido/u2f_request_unittest.cc b/chromium/device/fido/u2f_request_unittest.cc
new file mode 100644
index 00000000000..e6dcefb75cd
--- /dev/null
+++ b/chromium/device/fido/u2f_request_unittest.cc
@@ -0,0 +1,355 @@
+// 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.
+
+#include <list>
+#include <string>
+#include <utility>
+
+#include "base/test/scoped_task_environment.h"
+#include "device/fido/mock_u2f_device.h"
+#include "device/fido/mock_u2f_discovery.h"
+#include "device/fido/u2f_request.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace device {
+
+namespace {
+
+class FakeU2fRequest : public U2fRequest {
+ public:
+ explicit FakeU2fRequest()
+ : U2fRequest(nullptr /* connector */,
+ base::flat_set<U2fTransportProtocol>(),
+ std::vector<uint8_t>(),
+ std::vector<uint8_t>(),
+ std::vector<std::vector<uint8_t>>()) {}
+ ~FakeU2fRequest() override = default;
+
+ void TryDevice() override {
+ // Do nothing.
+ }
+};
+
+// Convenience functions for setting one and two mock discoveries, respectively.
+MockU2fDiscovery* SetMockDiscovery(
+ U2fRequest* request,
+ std::unique_ptr<MockU2fDiscovery> discovery) {
+ auto* raw_discovery = discovery.get();
+ std::vector<std::unique_ptr<U2fDiscovery>> discoveries;
+ discoveries.push_back(std::move(discovery));
+ request->SetDiscoveriesForTesting(std::move(discoveries));
+ return raw_discovery;
+}
+
+std::pair<MockU2fDiscovery*, MockU2fDiscovery*> SetMockDiscoveries(
+ U2fRequest* request,
+ std::unique_ptr<MockU2fDiscovery> discovery_1,
+ std::unique_ptr<MockU2fDiscovery> discovery_2) {
+ auto* raw_discovery_1 = discovery_1.get();
+ auto* raw_discovery_2 = discovery_2.get();
+ std::vector<std::unique_ptr<U2fDiscovery>> discoveries;
+ discoveries.push_back(std::move(discovery_1));
+ discoveries.push_back(std::move(discovery_2));
+ request->SetDiscoveriesForTesting(std::move(discoveries));
+ return {raw_discovery_1, raw_discovery_2};
+}
+
+} // namespace
+
+class U2fRequestTest : public testing::Test {
+ protected:
+ base::test::ScopedTaskEnvironment scoped_task_environment_{
+ base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME};
+};
+
+TEST_F(U2fRequestTest, TestIterateDevice) {
+ FakeU2fRequest request;
+ auto* discovery =
+ SetMockDiscovery(&request, std::make_unique<MockU2fDiscovery>());
+ auto device0 = std::make_unique<MockU2fDevice>();
+ auto device1 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device0, GetId()).WillRepeatedly(testing::Return("device0"));
+ EXPECT_CALL(*device1, GetId()).WillRepeatedly(testing::Return("device1"));
+ // Add two U2F devices
+ discovery->AddDevice(std::move(device0));
+ discovery->AddDevice(std::move(device1));
+
+ // Move first device to current
+ request.IterateDevice();
+ ASSERT_NE(nullptr, request.current_device_);
+ EXPECT_EQ(static_cast<size_t>(1), request.devices_.size());
+
+ // Move second device to current, first to attempted
+ request.IterateDevice();
+ ASSERT_NE(nullptr, request.current_device_);
+ EXPECT_EQ(static_cast<size_t>(1), request.attempted_devices_.size());
+
+ // Move second device from current to attempted, move attempted to devices as
+ // all devices have been attempted
+ request.IterateDevice();
+
+ ASSERT_EQ(nullptr, request.current_device_);
+ EXPECT_EQ(static_cast<size_t>(2), request.devices_.size());
+ EXPECT_EQ(static_cast<size_t>(0), request.attempted_devices_.size());
+
+ // Moving attempted devices results in a delayed retry, after which the first
+ // device will be tried again. Check for the expected behavior here.
+ auto* mock_device = static_cast<MockU2fDevice*>(request.devices_.front());
+ EXPECT_CALL(*mock_device, TryWinkRef(_));
+ scoped_task_environment_.FastForwardUntilNoTasksRemain();
+
+ EXPECT_EQ(mock_device, request.current_device_);
+ EXPECT_EQ(static_cast<size_t>(1), request.devices_.size());
+ EXPECT_EQ(static_cast<size_t>(0), request.attempted_devices_.size());
+}
+
+TEST_F(U2fRequestTest, TestBasicMachine) {
+ FakeU2fRequest request;
+ auto* discovery =
+ SetMockDiscovery(&request, std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(testing::Invoke(discovery, &MockU2fDiscovery::StartSuccess));
+ request.Start();
+
+ // Add one U2F device
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId());
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ EXPECT_EQ(U2fRequest::State::BUSY, request.state_);
+}
+
+TEST_F(U2fRequestTest, TestAlreadyPresentDevice) {
+ auto discovery = std::make_unique<MockU2fDiscovery>();
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(::testing::Return("device"));
+ discovery->AddDevice(std::move(device));
+
+ FakeU2fRequest request;
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery.get(), &MockU2fDiscovery::StartSuccess));
+ SetMockDiscovery(&request, std::move(discovery));
+ request.Start();
+
+ EXPECT_NE(nullptr, request.current_device_);
+}
+
+TEST_F(U2fRequestTest, TestMultipleDiscoveries) {
+ // Create a fake request with two different discoveries that both start up
+ // successfully.
+ FakeU2fRequest request;
+ MockU2fDiscovery* discoveries[2];
+ std::tie(discoveries[0], discoveries[1]) =
+ SetMockDiscoveries(&request, std::make_unique<MockU2fDiscovery>(),
+ std::make_unique<MockU2fDiscovery>());
+
+ EXPECT_CALL(*discoveries[0], Start())
+ .WillOnce(
+ testing::Invoke(discoveries[0], &MockU2fDiscovery::StartSuccess));
+ EXPECT_CALL(*discoveries[1], Start())
+ .WillOnce(
+ testing::Invoke(discoveries[1], &MockU2fDiscovery::StartSuccess));
+ request.Start();
+
+ // Let each discovery find a device.
+ auto device_1 = std::make_unique<MockU2fDevice>();
+ auto device_2 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device_1, GetId()).WillRepeatedly(testing::Return("device_1"));
+ EXPECT_CALL(*device_2, GetId()).WillRepeatedly(testing::Return("device_2"));
+ auto* device_1_ptr = device_1.get();
+ auto* device_2_ptr = device_2.get();
+ discoveries[0]->AddDevice(std::move(device_1));
+ discoveries[1]->AddDevice(std::move(device_2));
+
+ // Iterate through the devices and make sure they are considered in the same
+ // order as they were added.
+ EXPECT_EQ(device_1_ptr, request.current_device_);
+ request.IterateDevice();
+
+ EXPECT_EQ(device_2_ptr, request.current_device_);
+ request.IterateDevice();
+
+ EXPECT_EQ(nullptr, request.current_device_);
+ EXPECT_EQ(2u, request.devices_.size());
+
+ // Add a third device.
+ auto device_3 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device_3, GetId()).WillRepeatedly(testing::Return("device_3"));
+ auto* device_3_ptr = device_3.get();
+ discoveries[0]->AddDevice(std::move(device_3));
+
+ // Exhaust the timeout and remove the first two devices, making sure the just
+ // added one is the only device considered.
+ scoped_task_environment_.FastForwardUntilNoTasksRemain();
+ discoveries[1]->RemoveDevice("device_2");
+ discoveries[0]->RemoveDevice("device_1");
+ EXPECT_EQ(device_3_ptr, request.current_device_);
+
+ // Finally remove the last remaining device.
+ discoveries[0]->RemoveDevice("device_3");
+ EXPECT_EQ(nullptr, request.current_device_);
+}
+
+TEST_F(U2fRequestTest, TestSlowDiscovery) {
+ // Create a fake request with two different discoveries that start at
+ // different times.
+ FakeU2fRequest request;
+ MockU2fDiscovery* fast_discovery;
+ MockU2fDiscovery* slow_discovery;
+ std::tie(fast_discovery, slow_discovery) =
+ SetMockDiscoveries(&request, std::make_unique<MockU2fDiscovery>(),
+ std::make_unique<MockU2fDiscovery>());
+
+ EXPECT_CALL(*fast_discovery, Start())
+ .WillOnce(
+ testing::Invoke(fast_discovery, &MockU2fDiscovery::StartSuccess));
+ // slow_discovery does not succeed immediately.
+ EXPECT_CALL(*slow_discovery, Start());
+
+ // Let each discovery find a device.
+ auto fast_device = std::make_unique<MockU2fDevice>();
+ auto slow_device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*fast_device, GetId())
+ .WillRepeatedly(testing::Return("fast_device"));
+ EXPECT_CALL(*slow_device, GetId())
+ .WillRepeatedly(testing::Return("slow_device"));
+
+ bool fast_winked = false;
+ EXPECT_CALL(*fast_device, TryWinkRef(_))
+ .WillOnce(testing::DoAll(testing::Assign(&fast_winked, true),
+ testing::Invoke(MockU2fDevice::WinkDoNothing)))
+ .WillRepeatedly(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ bool slow_winked = false;
+ EXPECT_CALL(*slow_device, TryWinkRef(_))
+ .WillOnce(testing::DoAll(testing::Assign(&slow_winked, true),
+ testing::Invoke(MockU2fDevice::WinkDoNothing)));
+ auto* fast_device_ptr = fast_device.get();
+ auto* slow_device_ptr = slow_device.get();
+ fast_discovery->AddDeviceWithoutNotification(std::move(fast_device));
+
+ EXPECT_EQ(nullptr, request.current_device_);
+ request.state_ = U2fRequest::State::INIT;
+
+ // The discoveries will be started and |fast_discovery| will succeed
+ // immediately with a device already found.
+ EXPECT_FALSE(fast_winked);
+ request.Start();
+
+ EXPECT_TRUE(fast_winked);
+ EXPECT_EQ(fast_device_ptr, request.current_device_);
+ EXPECT_EQ(U2fRequest::State::BUSY, request.state_);
+
+ // There are no more devices at this time.
+ request.state_ = U2fRequest::State::IDLE;
+ request.Transition();
+ EXPECT_EQ(nullptr, request.current_device_);
+ EXPECT_EQ(U2fRequest::State::OFF, request.state_);
+
+ // All devices have been tried and have been re-enqueued to try again in the
+ // future. Now |slow_discovery| starts:
+
+ slow_discovery->AddDeviceWithoutNotification(std::move(slow_device));
+ slow_discovery->StartSuccess();
+
+ // |fast_device| is already enqueued and will be retried immediately.
+ EXPECT_EQ(fast_device_ptr, request.current_device_);
+ EXPECT_EQ(U2fRequest::State::BUSY, request.state_);
+
+ // Next the newly found |slow_device| will be tried.
+ request.state_ = U2fRequest::State::IDLE;
+ EXPECT_FALSE(slow_winked);
+ request.Transition();
+ EXPECT_TRUE(slow_winked);
+ EXPECT_EQ(slow_device_ptr, request.current_device_);
+ EXPECT_EQ(U2fRequest::State::BUSY, request.state_);
+
+ // All discoveries are complete so the request transitions to |OFF|.
+ request.state_ = U2fRequest::State::IDLE;
+ request.Transition();
+ EXPECT_EQ(nullptr, request.current_device_);
+ EXPECT_EQ(U2fRequest::State::OFF, request.state_);
+}
+
+TEST_F(U2fRequestTest, TestMultipleDiscoveriesWithFailures) {
+ {
+ // Create a fake request with two different discoveries that both start up
+ // unsuccessfully.
+ FakeU2fRequest request;
+ MockU2fDiscovery* discoveries[2];
+ std::tie(discoveries[0], discoveries[1]) =
+ SetMockDiscoveries(&request, std::make_unique<MockU2fDiscovery>(),
+ std::make_unique<MockU2fDiscovery>());
+
+ EXPECT_CALL(*discoveries[0], Start())
+ .WillOnce(
+ testing::Invoke(discoveries[0], &MockU2fDiscovery::StartFailure));
+ EXPECT_CALL(*discoveries[1], Start())
+ .WillOnce(
+ testing::Invoke(discoveries[1], &MockU2fDiscovery::StartFailure));
+ request.Start();
+ EXPECT_EQ(U2fRequest::State::OFF, request.state_);
+ }
+
+ {
+ // Create a fake request with two different discoveries, where only one
+ // starts up successfully.
+ FakeU2fRequest request;
+ MockU2fDiscovery* discoveries[2];
+ std::tie(discoveries[0], discoveries[1]) =
+ SetMockDiscoveries(&request, std::make_unique<MockU2fDiscovery>(),
+ std::make_unique<MockU2fDiscovery>());
+
+ EXPECT_CALL(*discoveries[0], Start())
+ .WillOnce(
+ testing::Invoke(discoveries[0], &MockU2fDiscovery::StartSuccess));
+ EXPECT_CALL(*discoveries[1], Start())
+ .WillOnce(
+ testing::Invoke(discoveries[1], &MockU2fDiscovery::StartFailure));
+
+ auto device0 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device0, GetId()).WillRepeatedly(testing::Return("device_0"));
+ EXPECT_CALL(*device0, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discoveries[0]->AddDevice(std::move(device0));
+
+ request.Start();
+ EXPECT_EQ(U2fRequest::State::BUSY, request.state_);
+
+ // Simulate an action that sets the request state to idle.
+ // This and the call to Transition() below is necessary to trigger iterating
+ // and trying the new device.
+ request.state_ = U2fRequest::State::IDLE;
+
+ // Adding another device should trigger examination and a busy state.
+ auto device1 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device1, GetId()).WillRepeatedly(testing::Return("device_1"));
+ EXPECT_CALL(*device1, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discoveries[0]->AddDevice(std::move(device1));
+
+ request.Transition();
+ EXPECT_EQ(U2fRequest::State::BUSY, request.state_);
+ }
+}
+
+TEST_F(U2fRequestTest, TestEncodeVersionRequest) {
+ constexpr uint8_t kEncodedU2fVersionRequest[] = {0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00};
+ EXPECT_THAT(U2fRequest::GetU2fVersionApduCommand(false)->GetEncodedCommand(),
+ 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)->GetEncodedCommand(),
+ testing::ElementsAreArray(kEncodedU2fLegacyVersionRequest));
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_response_test_data.h b/chromium/device/fido/u2f_response_test_data.h
new file mode 100644
index 00000000000..5be76740cc9
--- /dev/null
+++ b/chromium/device/fido/u2f_response_test_data.h
@@ -0,0 +1,109 @@
+// 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.
+
+// This file contains common data used to test U2F register and sign responses.
+
+#ifndef DEVICE_FIDO_U2F_RESPONSE_TEST_DATA_H_
+#define DEVICE_FIDO_U2F_RESPONSE_TEST_DATA_H_
+
+namespace device {
+
+namespace test_data {
+
+constexpr uint8_t kTestCredentialRawIdBytes[] = {
+ 0x89, 0xAF, 0xB5, 0x24, 0x91, 0x1C, 0x40, 0x2B, 0x7F, 0x74, 0x59,
+ 0xC9, 0xF2, 0x21, 0xAF, 0xE6, 0xE5, 0x56, 0x65, 0x85, 0x04, 0xE8,
+ 0x5B, 0x49, 0x4D, 0x07, 0x55, 0x55, 0xF4, 0x6A, 0xBC, 0x44, 0x7B,
+ 0x15, 0xFC, 0x62, 0x61, 0x90, 0xA5, 0xFE, 0xEB, 0xE5, 0x9F, 0x5E,
+ 0xDC, 0x75, 0x32, 0x98, 0x6F, 0x44, 0x69, 0xD7, 0xF6, 0x13, 0xEB,
+ 0xAA, 0xEA, 0x33, 0xFB, 0xD5, 0x8E, 0xBF, 0xC6, 0x09};
+
+// U2F response blob produced by a U2F registration request. This example
+// data uses testClientDataJson and 'created' a key with testCredentialRawId
+// as its key handle and with the above x- and y- coordinates.
+constexpr uint8_t kTestU2fRegisterResponse[] = {
+ 0x05, 0x04, 0xF8, 0x68, 0xCE, 0x38, 0x69, 0x60, 0x52, 0x24, 0xCE, 0x10,
+ 0x59, 0xC0, 0x04, 0x7E, 0xF0, 0x1B, 0x83, 0x0F, 0x2A, 0xD9, 0x3B, 0xE2,
+ 0x7A, 0x32, 0x11, 0xF4, 0x4E, 0x89, 0x45, 0x60, 0xE6, 0x95, 0x4E, 0x11,
+ 0x53, 0x8C, 0xAB, 0xA2, 0xDF, 0x1C, 0xC1, 0xA6, 0xF2, 0x50, 0xED, 0x9F,
+ 0x0C, 0x8B, 0x28, 0xB3, 0x9D, 0xA4, 0x45, 0x39, 0xDF, 0xAB, 0xD4, 0x6B,
+ 0x58, 0x9C, 0xD0, 0xE2, 0x02, 0xE5, 0x40, 0x89, 0xAF, 0xB5, 0x24, 0x91,
+ 0x1C, 0x40, 0x2B, 0x7F, 0x74, 0x59, 0xC9, 0xF2, 0x21, 0xAF, 0xE6, 0xE5,
+ 0x56, 0x65, 0x85, 0x04, 0xE8, 0x5B, 0x49, 0x4D, 0x07, 0x55, 0x55, 0xF4,
+ 0x6A, 0xBC, 0x44, 0x7B, 0x15, 0xFC, 0x62, 0x61, 0x90, 0xA5, 0xFE, 0xEB,
+ 0xE5, 0x9F, 0x5E, 0xDC, 0x75, 0x32, 0x98, 0x6F, 0x44, 0x69, 0xD7, 0xF6,
+ 0x13, 0xEB, 0xAA, 0xEA, 0x33, 0xFB, 0xD5, 0x8E, 0xBF, 0xC6, 0x09, 0x30,
+ 0x82, 0x02, 0x4A, 0x30, 0x82, 0x01, 0x32, 0xA0, 0x03, 0x02, 0x01, 0x02,
+ 0x02, 0x04, 0x04, 0x6C, 0x88, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86,
+ 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x30, 0x2E, 0x31,
+ 0x2C, 0x30, 0x2A, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x23, 0x59, 0x75,
+ 0x62, 0x69, 0x63, 0x6F, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6F, 0x6F,
+ 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20,
+ 0x34, 0x35, 0x37, 0x32, 0x30, 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17,
+ 0x0D, 0x31, 0x34, 0x30, 0x38, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x5A, 0x18, 0x0F, 0x32, 0x30, 0x35, 0x30, 0x30, 0x39, 0x30, 0x34,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, 0x2C, 0x31, 0x2A, 0x30,
+ 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x21, 0x59, 0x75, 0x62, 0x69,
+ 0x63, 0x6F, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65,
+ 0x72, 0x69, 0x61, 0x6C, 0x20, 0x32, 0x34, 0x39, 0x31, 0x38, 0x32, 0x33,
+ 0x32, 0x34, 0x37, 0x37, 0x30, 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, 0x3C, 0xCA, 0xB9, 0x2C,
+ 0xCB, 0x97, 0x28, 0x7E, 0xE8, 0xE6, 0x39, 0x43, 0x7E, 0x21, 0xFC, 0xD6,
+ 0xB6, 0xF1, 0x65, 0xB2, 0xD5, 0xA3, 0xF3, 0xDB, 0x13, 0x1D, 0x31, 0xC1,
+ 0x6B, 0x74, 0x2B, 0xB4, 0x76, 0xD8, 0xD1, 0xE9, 0x90, 0x80, 0xEB, 0x54,
+ 0x6C, 0x9B, 0xBD, 0xF5, 0x56, 0xE6, 0x21, 0x0F, 0xD4, 0x27, 0x85, 0x89,
+ 0x9E, 0x78, 0xCC, 0x58, 0x9E, 0xBE, 0x31, 0x0F, 0x6C, 0xDB, 0x9F, 0xF4,
+ 0xA3, 0x3B, 0x30, 0x39, 0x30, 0x22, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04,
+ 0x01, 0x82, 0xC4, 0x0A, 0x02, 0x04, 0x15, 0x31, 0x2E, 0x33, 0x2E, 0x36,
+ 0x2E, 0x31, 0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x34, 0x31, 0x34, 0x38, 0x32,
+ 0x2E, 0x31, 0x2E, 0x32, 0x30, 0x13, 0x06, 0x0B, 0x2B, 0x06, 0x01, 0x04,
+ 0x01, 0x82, 0xE5, 0x1C, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x04,
+ 0x30, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01,
+ 0x01, 0x0B, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x9F, 0x9B, 0x05,
+ 0x22, 0x48, 0xBC, 0x4C, 0xF4, 0x2C, 0xC5, 0x99, 0x1F, 0xCA, 0xAB, 0xAC,
+ 0x9B, 0x65, 0x1B, 0xBE, 0x5B, 0xDC, 0xDC, 0x8E, 0xF0, 0xAD, 0x2C, 0x1C,
+ 0x1F, 0xFB, 0x36, 0xD1, 0x87, 0x15, 0xD4, 0x2E, 0x78, 0xB2, 0x49, 0x22,
+ 0x4F, 0x92, 0xC7, 0xE6, 0xE7, 0xA0, 0x5C, 0x49, 0xF0, 0xE7, 0xE4, 0xC8,
+ 0x81, 0xBF, 0x2E, 0x94, 0xF4, 0x5E, 0x4A, 0x21, 0x83, 0x3D, 0x74, 0x56,
+ 0x85, 0x1D, 0x0F, 0x6C, 0x14, 0x5A, 0x29, 0x54, 0x0C, 0x87, 0x4F, 0x30,
+ 0x92, 0xC9, 0x34, 0xB4, 0x3D, 0x22, 0x2B, 0x89, 0x62, 0xC0, 0xF4, 0x10,
+ 0xCE, 0xF1, 0xDB, 0x75, 0x89, 0x2A, 0xF1, 0x16, 0xB4, 0x4A, 0x96, 0xF5,
+ 0xD3, 0x5A, 0xDE, 0xA3, 0x82, 0x2F, 0xC7, 0x14, 0x6F, 0x60, 0x04, 0x38,
+ 0x5B, 0xCB, 0x69, 0xB6, 0x5C, 0x99, 0xE7, 0xEB, 0x69, 0x19, 0x78, 0x67,
+ 0x03, 0xC0, 0xD8, 0xCD, 0x41, 0xE8, 0xF7, 0x5C, 0xCA, 0x44, 0xAA, 0x8A,
+ 0xB7, 0x25, 0xAD, 0x8E, 0x79, 0x9F, 0xF3, 0xA8, 0x69, 0x6A, 0x6F, 0x1B,
+ 0x26, 0x56, 0xE6, 0x31, 0xB1, 0xE4, 0x01, 0x83, 0xC0, 0x8F, 0xDA, 0x53,
+ 0xFA, 0x4A, 0x8F, 0x85, 0xA0, 0x56, 0x93, 0x94, 0x4A, 0xE1, 0x79, 0xA1,
+ 0x33, 0x9D, 0x00, 0x2D, 0x15, 0xCA, 0xBD, 0x81, 0x00, 0x90, 0xEC, 0x72,
+ 0x2E, 0xF5, 0xDE, 0xF9, 0x96, 0x5A, 0x37, 0x1D, 0x41, 0x5D, 0x62, 0x4B,
+ 0x68, 0xA2, 0x70, 0x7C, 0xAD, 0x97, 0xBC, 0xDD, 0x17, 0x85, 0xAF, 0x97,
+ 0xE2, 0x58, 0xF3, 0x3D, 0xF5, 0x6A, 0x03, 0x1A, 0xA0, 0x35, 0x6D, 0x8E,
+ 0x8D, 0x5E, 0xBC, 0xAD, 0xC7, 0x4E, 0x07, 0x16, 0x36, 0xC6, 0xB1, 0x10,
+ 0xAC, 0xE5, 0xCC, 0x9B, 0x90, 0xDF, 0xEA, 0xCA, 0xE6, 0x40, 0xFF, 0x1B,
+ 0xB0, 0xF1, 0xFE, 0x5D, 0xB4, 0xEF, 0xF7, 0xA9, 0x5F, 0x06, 0x07, 0x33,
+ 0xF5, 0x30, 0x44, 0x02, 0x20, 0x08, 0xC3, 0xF8, 0xDB, 0x6E, 0x29, 0xFD,
+ 0x8B, 0x14, 0xD9, 0xDE, 0x1B, 0xD9, 0x8E, 0x84, 0x07, 0x2C, 0xB8, 0x13,
+ 0x38, 0x59, 0x89, 0xAA, 0x2C, 0xA2, 0x89, 0x39, 0x5E, 0x00, 0x09, 0xB8,
+ 0xB7, 0x02, 0x20, 0x26, 0x07, 0xB4, 0xF9, 0xAD, 0x05, 0xDE, 0x26, 0xF5,
+ 0x6F, 0x48, 0xB8, 0x25, 0x69, 0xEA, 0xD8, 0x23, 0x1A, 0x5A, 0x6C, 0x3A,
+ 0x14, 0x48, 0xDE, 0xAA, 0xAF, 0x15, 0xC0, 0xEF, 0x29, 0x63, 0x1A};
+
+// U2F response blob produced by a U2F sign request.
+constexpr uint8_t kTestU2fSignResponse[] = {
+ 0x01, 0x00, 0x00, 0x00, 0x25, 0x30, 0x45, 0x02, 0x21, 0x00, 0xCA,
+ 0xA5, 0x3E, 0x91, 0x0D, 0xB7, 0x5E, 0xDE, 0xAF, 0x72, 0xCF, 0x9F,
+ 0x6F, 0x54, 0xE5, 0x20, 0x5B, 0xBB, 0xB9, 0x2F, 0x0B, 0x9F, 0x7D,
+ 0xC6, 0xF8, 0xD4, 0x7B, 0x19, 0x70, 0xED, 0xFE, 0xBC, 0x02, 0x20,
+ 0x06, 0x32, 0x83, 0x65, 0x26, 0x4E, 0xBE, 0xFE, 0x35, 0x3C, 0x95,
+ 0x91, 0xDF, 0xCE, 0x7D, 0x73, 0x15, 0x98, 0x64, 0xDF, 0xEA, 0xB7,
+ 0x87, 0xF1, 0x5D, 0xF8, 0xA5, 0x97, 0xD0, 0x85, 0x0C, 0xA2};
+
+// A sample corrupted response to a U2F sign request.
+constexpr uint8_t kTestCorruptedU2fSignResponse[] = {0x01, 0x00, 0x00, 0x00};
+} // namespace test_data
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_RESPONSE_TEST_DATA_H_
diff --git a/chromium/device/fido/u2f_return_code.h b/chromium/device/fido/u2f_return_code.h
new file mode 100644
index 00000000000..c6ffd2d2c30
--- /dev/null
+++ b/chromium/device/fido/u2f_return_code.h
@@ -0,0 +1,19 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_RETURN_CODE_H_
+#define DEVICE_FIDO_U2F_RETURN_CODE_H_
+
+namespace device {
+
+enum class U2fReturnCode : uint8_t {
+ SUCCESS,
+ FAILURE,
+ INVALID_PARAMS,
+ CONDITIONS_NOT_SATISFIED,
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_RETURN_CODE_H_
diff --git a/chromium/device/fido/u2f_sign.cc b/chromium/device/fido/u2f_sign.cc
new file mode 100644
index 00000000000..79d4efffdbf
--- /dev/null
+++ b/chromium/device/fido/u2f_sign.cc
@@ -0,0 +1,142 @@
+// 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.
+
+#include "device/fido/u2f_sign.h"
+
+#include <utility>
+
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace device {
+
+// static
+std::unique_ptr<U2fRequest> U2fSign::TrySign(
+ service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<std::vector<uint8_t>> registered_keys,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<uint8_t> application_parameter,
+ base::Optional<std::vector<uint8_t>> alt_application_parameter,
+ SignResponseCallback completion_callback) {
+ std::unique_ptr<U2fRequest> request = std::make_unique<U2fSign>(
+ connector, protocols, registered_keys, challenge_digest,
+ application_parameter, std::move(alt_application_parameter),
+ std::move(completion_callback));
+ request->Start();
+
+ return request;
+}
+
+U2fSign::U2fSign(service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<std::vector<uint8_t>> registered_keys,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<uint8_t> application_parameter,
+ base::Optional<std::vector<uint8_t>> alt_application_parameter,
+ SignResponseCallback completion_callback)
+ : U2fRequest(connector,
+ protocols,
+ std::move(application_parameter),
+ std::move(challenge_digest),
+ std::move(registered_keys)),
+ alt_application_parameter_(std::move(alt_application_parameter)),
+ completion_callback_(std::move(completion_callback)),
+ weak_factory_(this) {}
+
+U2fSign::~U2fSign() = default;
+
+void U2fSign::TryDevice() {
+ DCHECK(current_device_);
+
+ if (registered_keys_.size() == 0) {
+ // Send registration (Fake enroll) if no keys were provided
+ current_device_->Register(
+ U2fRequest::GetBogusApplicationParameter(),
+ U2fRequest::GetBogusChallenge(), false /* no individual attestation */,
+ base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(),
+ registered_keys_.cend(),
+ ApplicationParameterType::kPrimary));
+ return;
+ }
+ // Try signing current device with the first registered key
+ auto it = registered_keys_.cbegin();
+ current_device_->Sign(
+ application_parameter_, challenge_digest_, *it,
+ base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(), it,
+ ApplicationParameterType::kPrimary));
+}
+
+void U2fSign::OnTryDevice(std::vector<std::vector<uint8_t>>::const_iterator it,
+ ApplicationParameterType application_parameter_type,
+ U2fReturnCode return_code,
+ const std::vector<uint8_t>& response_data) {
+ switch (return_code) {
+ case U2fReturnCode::SUCCESS: {
+ state_ = State::COMPLETE;
+ if (it == registered_keys_.cend()) {
+ // This was a response to a fake enrollment. Return an empty key handle.
+ std::move(completion_callback_)
+ .Run(U2fReturnCode::CONDITIONS_NOT_SATISFIED, base::nullopt);
+ } else {
+ const std::vector<uint8_t>* const application_parameter_used =
+ application_parameter_type == ApplicationParameterType::kPrimary
+ ? &application_parameter_
+ : &alt_application_parameter_.value();
+ auto sign_response = SignResponseData::CreateFromU2fSignResponse(
+ *application_parameter_used, std::move(response_data), *it);
+ if (!sign_response) {
+ std::move(completion_callback_)
+ .Run(U2fReturnCode::FAILURE, base::nullopt);
+ } else {
+ std::move(completion_callback_)
+ .Run(U2fReturnCode::SUCCESS, std::move(sign_response));
+ }
+ }
+ break;
+ }
+ case U2fReturnCode::CONDITIONS_NOT_SATISFIED: {
+ // Key handle is accepted by this device, but waiting on user touch. Move
+ // on and try this device again later.
+ state_ = State::IDLE;
+ Transition();
+ break;
+ }
+ case U2fReturnCode::INVALID_PARAMS: {
+ if (application_parameter_type == ApplicationParameterType::kPrimary &&
+ alt_application_parameter_) {
+ // |application_parameter_| failed, but there is also
+ // |alt_application_parameter_| to try.
+ current_device_->Sign(
+ *alt_application_parameter_, challenge_digest_, *it,
+ base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(), it,
+ ApplicationParameterType::kAlternative));
+ } else if (++it != registered_keys_.end()) {
+ // Key is not for this device. Try signing with the next key.
+ current_device_->Sign(
+ application_parameter_, challenge_digest_, *it,
+ base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(), it,
+ ApplicationParameterType::kPrimary));
+ } else {
+ // No provided key was accepted by this device. Send registration
+ // (Fake enroll) request to device.
+ current_device_->Register(
+ U2fRequest::GetBogusApplicationParameter(),
+ U2fRequest::GetBogusChallenge(),
+ false /* no individual attestation */,
+ base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(),
+ registered_keys_.cend(),
+ ApplicationParameterType::kPrimary));
+ }
+ break;
+ }
+ default:
+ // Some sort of failure occured. Abandon this device and move on.
+ state_ = State::IDLE;
+ current_device_ = nullptr;
+ Transition();
+ break;
+ }
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_sign.h b/chromium/device/fido/u2f_sign.h
new file mode 100644
index 00000000000..0327a33db02
--- /dev/null
+++ b/chromium/device/fido/u2f_sign.h
@@ -0,0 +1,78 @@
+// 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.
+
+#ifndef DEVICE_FIDO_U2F_SIGN_H_
+#define DEVICE_FIDO_U2F_SIGN_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/optional.h"
+#include "device/fido/sign_response_data.h"
+#include "device/fido/u2f_request.h"
+#include "device/fido/u2f_transport_protocol.h"
+
+namespace service_manager {
+class Connector;
+}
+
+namespace device {
+
+class U2fSign : public U2fRequest {
+ public:
+ using SignResponseCallback =
+ base::OnceCallback<void(U2fReturnCode status_code,
+ base::Optional<SignResponseData> response_data)>;
+
+ static std::unique_ptr<U2fRequest> TrySign(
+ service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<std::vector<uint8_t>> registered_keys,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<uint8_t> application_parameter,
+ base::Optional<std::vector<uint8_t>> alt_application_parameter,
+ SignResponseCallback completion_callback);
+
+ U2fSign(service_manager::Connector* connector,
+ const base::flat_set<U2fTransportProtocol>& protocols,
+ std::vector<std::vector<uint8_t>> registered_keys,
+ std::vector<uint8_t> challenge_digest,
+ std::vector<uint8_t> application_parameter,
+ base::Optional<std::vector<uint8_t>> alt_application_parameter,
+ SignResponseCallback completion_callback);
+ ~U2fSign() override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(U2fSignTest, TestCreateSignApduCommand);
+
+ // 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,
+ U2fReturnCode return_code,
+ const std::vector<uint8_t>& response_data);
+
+ base::Optional<std::vector<uint8_t>> alt_application_parameter_;
+ SignResponseCallback completion_callback_;
+
+ base::WeakPtrFactory<U2fSign> weak_factory_;
+};
+
+} // namespace device
+
+#endif // DEVICE_FIDO_U2F_SIGN_H_
diff --git a/chromium/device/fido/u2f_sign_unittest.cc b/chromium/device/fido/u2f_sign_unittest.cc
new file mode 100644
index 00000000000..82847f20546
--- /dev/null
+++ b/chromium/device/fido/u2f_sign_unittest.cc
@@ -0,0 +1,588 @@
+// 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.
+
+#include "device/fido/u2f_sign.h"
+
+#include <utility>
+
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "device/fido/authenticator_data.h"
+#include "device/fido/mock_u2f_device.h"
+#include "device/fido/mock_u2f_discovery.h"
+#include "device/fido/sign_response_data.h"
+#include "device/fido/u2f_response_test_data.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace device {
+
+namespace {
+
+constexpr uint8_t kAppId[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01};
+
+constexpr uint8_t kChallengeDigest[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01,
+ 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01};
+
+constexpr uint8_t kKeyHandle[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01,
+ 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01};
+
+constexpr uint8_t kTestRelyingPartyIdSHA256[32] = {
+ 0xd4, 0xc9, 0xd9, 0x02, 0x73, 0x26, 0x27, 0x1a, 0x89, 0xce, 0x51,
+ 0xfc, 0xaf, 0x32, 0x8e, 0xd6, 0x73, 0xf1, 0x7b, 0xe3, 0x34, 0x69,
+ 0xff, 0x97, 0x9e, 0x8a, 0xb8, 0xdd, 0x50, 0x1e, 0x66, 0x4f,
+};
+
+// Signature counter returned within the authenticator data.
+constexpr uint8_t kTestSignatureCounter[] = {0x00, 0x00, 0x00, 0x25};
+
+// Test data specific to GetAssertion/Sign.
+constexpr uint8_t kTestAssertionSignature[] = {
+ 0x30, 0x45, 0x02, 0x21, 0x00, 0xCA, 0xA5, 0x3E, 0x91, 0x0D, 0xB7, 0x5E,
+ 0xDE, 0xAF, 0x72, 0xCF, 0x9F, 0x6F, 0x54, 0xE5, 0x20, 0x5B, 0xBB, 0xB9,
+ 0x2F, 0x0B, 0x9F, 0x7D, 0xC6, 0xF8, 0xD4, 0x7B, 0x19, 0x70, 0xED, 0xFE,
+ 0xBC, 0x02, 0x20, 0x06, 0x32, 0x83, 0x65, 0x26, 0x4E, 0xBE, 0xFE, 0x35,
+ 0x3C, 0x95, 0x91, 0xDF, 0xCE, 0x7D, 0x73, 0x15, 0x98, 0x64, 0xDF, 0xEA,
+ 0xB7, 0x87, 0xF1, 0x5D, 0xF8, 0xA5, 0x97, 0xD0, 0x85, 0x0C, 0xA2};
+
+// The authenticator data for sign responses.
+constexpr uint8_t kTestSignAuthenticatorData[] = {
+ // clang-format off
+ // sha256 hash of kTestRelyingPartyId
+ 0xD4, 0xC9, 0xD9, 0x02, 0x73, 0x26, 0x27, 0x1A, 0x89, 0xCE, 0x51,
+ 0xFC, 0xAF, 0x32, 0x8E, 0xD6, 0x73, 0xF1, 0x7B, 0xE3, 0x34, 0x69,
+ 0xFF, 0x97, 0x9E, 0x8A, 0xB8, 0xDD, 0x50, 0x1E, 0x66, 0x4F,
+ 0x01, // flags (TUP bit set)
+ 0x00, 0x00, 0x00, 0x25 // counter
+ // clang-format on
+};
+
+std::vector<uint8_t> GetTestCredentialRawIdBytes() {
+ return std::vector<uint8_t>(std::begin(test_data::kTestCredentialRawIdBytes),
+ std::end(test_data::kTestCredentialRawIdBytes));
+}
+
+std::vector<uint8_t> GetTestSignResponse() {
+ return std::vector<uint8_t>(std::begin(test_data::kTestU2fSignResponse),
+ std::end(test_data::kTestU2fSignResponse));
+}
+
+std::vector<uint8_t> GetTestAuthenticatorData() {
+ return std::vector<uint8_t>(std::begin(kTestSignAuthenticatorData),
+ std::end(kTestSignAuthenticatorData));
+}
+
+std::vector<uint8_t> GetTestAssertionSignature() {
+ return std::vector<uint8_t>(std::begin(kTestAssertionSignature),
+ std::end(kTestAssertionSignature));
+}
+
+std::vector<uint8_t> GetTestSignatureCounter() {
+ return std::vector<uint8_t>(std::begin(kTestSignatureCounter),
+ std::end(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 std::vector<uint8_t>(test_data::kTestU2fSignResponse,
+ test_data::kTestU2fSignResponse + length);
+}
+
+// Convenience functions for setting a mock discovery.
+MockU2fDiscovery* SetMockDiscovery(
+ U2fRequest* request,
+ std::unique_ptr<MockU2fDiscovery> discovery) {
+ auto* raw_discovery = discovery.get();
+ std::vector<std::unique_ptr<U2fDiscovery>> discoveries;
+ discoveries.push_back(std::move(discovery));
+ request->SetDiscoveriesForTesting(std::move(discoveries));
+ return raw_discovery;
+}
+
+} // namespace
+
+class U2fSignTest : public testing::Test {
+ public:
+ U2fSignTest()
+ : scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
+
+ protected:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+class TestSignCallback {
+ public:
+ TestSignCallback()
+ : callback_(base::Bind(&TestSignCallback::ReceivedCallback,
+ base::Unretained(this))) {}
+ ~TestSignCallback() = default;
+
+ void ReceivedCallback(U2fReturnCode status_code,
+ base::Optional<SignResponseData> response_data) {
+ response_ = std::make_pair(status_code, std::move(response_data));
+ closure_.Run();
+ }
+
+ void WaitForCallback() {
+ closure_ = run_loop_.QuitClosure();
+ run_loop_.Run();
+ }
+
+ U2fReturnCode GetReturnCode() { return std::get<0>(response_); }
+
+ const base::Optional<SignResponseData>& GetResponseData() {
+ return std::get<1>(response_);
+ }
+
+ U2fSign::SignResponseCallback callback() { return std::move(callback_); }
+
+ private:
+ std::pair<U2fReturnCode, base::Optional<SignResponseData>> response_;
+ base::Closure closure_;
+ U2fSign::SignResponseCallback callback_;
+ base::RunLoop run_loop_;
+};
+
+TEST_F(U2fSignTest, TestCreateSignApduCommand) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> handles;
+ handles.push_back(
+ std::vector<uint8_t>(std::begin(kKeyHandle), std::end(kKeyHandle)));
+ TestSignCallback cb;
+
+ constexpr uint8_t kSignApduEncoded[] = {
+ // clang-format off
+ 0x00, 0x02, 0x03, 0x00, // CLA, INS, P1, P2 APDU instruction parameters
+ 0x00, 0x00, 0x61, // Data Length (3 bytes in big endian order)
+ // Application parameter
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01,
+ // Challenge parameter
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01,
+ 0x20, // Key handle length
+ // Key Handle
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01,
+ 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01
+ // clang-format on
+ };
+
+ U2fSign u2f_sign(nullptr, protocols, handles,
+ std::vector<uint8_t>(std::begin(kChallengeDigest),
+ std::end(kChallengeDigest)),
+ std::vector<uint8_t>(std::begin(kAppId), std::end(kAppId)),
+ base::nullopt, cb.callback());
+ const auto encoded_sign = u2f_sign.GetU2fSignApduCommand(handles[0]);
+ ASSERT_TRUE(encoded_sign);
+ EXPECT_THAT(encoded_sign->GetEncodedCommand(),
+ testing::ElementsAreArray(kSignApduEncoded));
+
+ constexpr uint8_t kSignApduEncodedCheckOnly[] = {
+ // clang-format off
+ 0x00, 0x02, 0x07, 0x00, // CLA, INS, P1, P2 APDU instruction parameters
+ 0x00, 0x00, 0x61, // Data Length (3 bytes in big endian order)
+ // Application parameter
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01,
+ // Challenge parameter
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01,
+ 0x20, // Key handle length
+ // Key Handle
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01,
+ 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01
+ // clang-format on
+ };
+
+ const auto encoded_sign_check_only =
+ u2f_sign.GetU2fSignApduCommand(handles[0], true);
+ ASSERT_TRUE(encoded_sign_check_only);
+ EXPECT_THAT(encoded_sign_check_only->GetEncodedCommand(),
+ testing::ElementsAreArray(kSignApduEncodedCheckOnly));
+}
+
+TEST_F(U2fSignTest, TestSignSuccess) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xA);
+ TestSignCallback cb;
+
+ auto request = std::make_unique<U2fSign>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), std::vector<uint8_t>(0), cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ EXPECT_CALL(*device, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, cb.GetReturnCode());
+
+ // Correct key was sent so a sign response is expected.
+ EXPECT_EQ(GetTestAssertionSignature(), cb.GetResponseData()->signature());
+
+ // Verify that we get the key handle used for signing.
+ EXPECT_EQ(handles[0], cb.GetResponseData()->raw_id());
+}
+
+TEST_F(U2fSignTest, TestDelayedSuccess) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xA);
+ TestSignCallback cb;
+
+ auto request = std::make_unique<U2fSign>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), std::vector<uint8_t>(0), cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ // Go through the state machine twice before success.
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ EXPECT_CALL(*device, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .Times(2)
+ .WillRepeatedly(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, cb.GetReturnCode());
+
+ // Correct key was sent so a sign response is expected
+ EXPECT_EQ(GetTestAssertionSignature(), cb.GetResponseData()->signature());
+
+ // Verify that we get the key handle used for signing
+ EXPECT_EQ(handles[0], cb.GetResponseData()->raw_id());
+}
+
+TEST_F(U2fSignTest, TestMultipleHandles) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ // Three wrong keys followed by a correct key ensuring the wrong keys will be
+ // tested first.
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xB);
+ handles.emplace_back(32, 0xC);
+ handles.emplace_back(32, 0xD);
+ handles.emplace_back(32, 0xA);
+ TestSignCallback cb;
+
+ auto request = std::make_unique<U2fSign>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), std::vector<uint8_t>(0), cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ // Wrong key would respond with SW_WRONG_DATA.
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ EXPECT_CALL(*device, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+ // Only one wink expected per device.
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, cb.GetReturnCode());
+
+ // Correct key was sent so a sign response is expected.
+ EXPECT_EQ(GetTestAssertionSignature(), cb.GetResponseData()->signature());
+
+ // Verify that we get the key handle used for signing.
+ EXPECT_EQ(handles.back(), cb.GetResponseData()->raw_id());
+}
+
+TEST_F(U2fSignTest, TestMultipleDevices) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xA);
+ handles.emplace_back(32, 0xB);
+ TestSignCallback cb;
+
+ auto request = std::make_unique<U2fSign>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), std::vector<uint8_t>(0), cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device0 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device0, GetId()).WillRepeatedly(testing::Return("device0"));
+ EXPECT_CALL(*device0, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied));
+ // One wink per device.
+ EXPECT_CALL(*device0, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device0));
+
+ // Second device will have a successful touch.
+ auto device1 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device1, GetId()).WillRepeatedly(testing::Return("device1"));
+ EXPECT_CALL(*device1, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+ // One wink per device.
+ EXPECT_CALL(*device1, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device1));
+
+ cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, cb.GetReturnCode());
+
+ // Correct key was sent so a sign response is expected.
+ EXPECT_EQ(GetTestAssertionSignature(), cb.GetResponseData()->signature());
+
+ // Verify that we get the key handle used for signing.
+ EXPECT_EQ(handles[0], cb.GetResponseData()->raw_id());
+}
+
+TEST_F(U2fSignTest, TestFakeEnroll) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xA);
+ handles.emplace_back(32, 0xB);
+ TestSignCallback cb;
+
+ auto request = std::make_unique<U2fSign>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), std::vector<uint8_t>(0), cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device0 = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device0, GetId()).WillRepeatedly(testing::Return("device0"));
+ EXPECT_CALL(*device0, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied));
+ // One wink per device.
+ EXPECT_CALL(*device0, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device0));
+
+ // Second device will be have a successful touch.
+ auto device1 = std::make_unique<MockU2fDevice>();
+ 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(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+ // One wink per device.
+ EXPECT_CALL(*device1, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device1));
+
+ cb.WaitForCallback();
+ // Device that responded had no correct keys.
+ EXPECT_EQ(U2fReturnCode::CONDITIONS_NOT_SATISFIED, cb.GetReturnCode());
+ EXPECT_FALSE(cb.GetResponseData());
+}
+
+TEST_F(U2fSignTest, TestAuthenticatorDataForSign) {
+ constexpr uint8_t flags =
+ static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence);
+
+ EXPECT_EQ(
+ GetTestAuthenticatorData(),
+ AuthenticatorData(std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ flags, GetTestSignatureCounter(), base::nullopt)
+ .SerializeToByteArray());
+}
+
+TEST_F(U2fSignTest, TestSignResponseData) {
+ base::Optional<SignResponseData> response =
+ SignResponseData::CreateFromU2fSignResponse(
+ std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ GetTestSignResponse(), GetTestCredentialRawIdBytes());
+ ASSERT_TRUE(response.has_value());
+ EXPECT_EQ(GetTestCredentialRawIdBytes(), response->raw_id());
+ EXPECT_EQ(GetTestAuthenticatorData(), response->GetAuthenticatorDataBytes());
+ EXPECT_EQ(GetTestAssertionSignature(), response->signature());
+}
+
+TEST_F(U2fSignTest, TestNullKeyHandle) {
+ base::Optional<SignResponseData> response =
+ SignResponseData::CreateFromU2fSignResponse(
+ std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ GetTestSignResponse(), std::vector<uint8_t>());
+ EXPECT_FALSE(response);
+}
+
+TEST_F(U2fSignTest, TestNullResponse) {
+ base::Optional<SignResponseData> response =
+ SignResponseData::CreateFromU2fSignResponse(
+ std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ std::vector<uint8_t>(), GetTestCredentialRawIdBytes());
+ EXPECT_FALSE(response);
+}
+
+TEST_F(U2fSignTest, TestCorruptedCounter) {
+ // A sign response of less than 5 bytes.
+ base::Optional<SignResponseData> response =
+ SignResponseData::CreateFromU2fSignResponse(
+ std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ GetTestCorruptedSignResponse(3), GetTestCredentialRawIdBytes());
+ EXPECT_FALSE(response);
+}
+
+TEST_F(U2fSignTest, TestCorruptedSignature) {
+ // A sign response no more than 5 bytes.
+ base::Optional<SignResponseData> response =
+ SignResponseData::CreateFromU2fSignResponse(
+ std::vector<uint8_t>(kTestRelyingPartyIdSHA256,
+ kTestRelyingPartyIdSHA256 + 32),
+ GetTestCorruptedSignResponse(5), GetTestCredentialRawIdBytes());
+ EXPECT_FALSE(response);
+}
+
+// Device returns success, but the response is unparse-able.
+TEST_F(U2fSignTest, TestSignWithCorruptedResponse) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xA);
+ TestSignCallback cb;
+
+ auto request = std::make_unique<U2fSign>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32),
+ std::vector<uint8_t>(32), std::vector<uint8_t>(0), cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ EXPECT_CALL(*device, DeviceTransactPtr(_, _))
+ .WillOnce(testing::Invoke(MockU2fDevice::SignWithCorruptedResponse));
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::FAILURE, cb.GetReturnCode());
+ EXPECT_FALSE(cb.GetResponseData());
+}
+
+MATCHER_P(WithApplicationParameter, expected, "") {
+ // See
+ // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#authentication-request-message---u2f_authenticate
+ if (arg.size() < 71) {
+ return false;
+ }
+
+ base::span<const uint8_t> cmd_bytes(arg);
+ auto application_parameter = cmd_bytes.subspan(
+ 4 /* framing bytes */ + 3 /* request length */ + 32 /* challenge hash */,
+ 32);
+ return std::equal(application_parameter.begin(), application_parameter.end(),
+ expected.begin());
+}
+
+TEST_F(U2fSignTest, TestAlternativeApplicationParameter) {
+ base::flat_set<U2fTransportProtocol> protocols;
+ std::vector<std::vector<uint8_t>> handles;
+ handles.emplace_back(32, 0xA);
+ TestSignCallback cb;
+
+ const std::vector<uint8_t> primary_app_param(32, 1);
+ const std::vector<uint8_t> alt_app_param(32, 2);
+
+ auto request = std::make_unique<U2fSign>(
+ nullptr, protocols, handles, std::vector<uint8_t>(32), primary_app_param,
+ alt_app_param, cb.callback());
+
+ auto* discovery =
+ SetMockDiscovery(request.get(), std::make_unique<MockU2fDiscovery>());
+ EXPECT_CALL(*discovery, Start())
+ .WillOnce(
+ testing::Invoke(discovery, &MockU2fDiscovery::StartSuccessAsync));
+ request->Start();
+
+ auto device = std::make_unique<MockU2fDevice>();
+ EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device"));
+ // The first request will use the primary app_param, which will be rejected.
+ EXPECT_CALL(*device,
+ DeviceTransactPtr(WithApplicationParameter(primary_app_param), _))
+ .WillOnce(testing::Invoke(MockU2fDevice::WrongData));
+ // After the rejection, the alternative should be tried.
+ EXPECT_CALL(*device,
+ DeviceTransactPtr(WithApplicationParameter(alt_app_param), _))
+ .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+ EXPECT_CALL(*device, TryWinkRef(_))
+ .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+ discovery->AddDevice(std::move(device));
+
+ cb.WaitForCallback();
+ EXPECT_EQ(U2fReturnCode::SUCCESS, cb.GetReturnCode());
+
+ EXPECT_EQ(GetTestAssertionSignature(), cb.GetResponseData()->signature());
+ EXPECT_EQ(handles[0], cb.GetResponseData()->raw_id());
+}
+
+} // namespace device
diff --git a/chromium/device/fido/u2f_transport_protocol.h b/chromium/device/fido/u2f_transport_protocol.h
new file mode 100644
index 00000000000..0d61c121053
--- /dev/null
+++ b/chromium/device/fido/u2f_transport_protocol.h
@@ -0,0 +1,19 @@
+// 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_U2F_U2F_TRANSPORT_PROTOCOL_H_
+#define DEVICE_U2F_U2F_TRANSPORT_PROTOCOL_H_
+
+namespace device {
+
+// This enum represents the transport protocols over which U2F is currently
+// supported.
+enum class U2fTransportProtocol {
+ kUsbHumanInterfaceDevice,
+ kBluetoothLowEnergy,
+};
+
+} // namespace device
+
+#endif // DEVICE_U2F_U2F_TRANSPORT_PROTOCOL_H_