diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-03 13:42:47 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:27:51 +0000 |
commit | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (patch) | |
tree | d29d987c4d7b173cf853279b79a51598f104b403 /chromium/device/fido | |
parent | 830c9e163d31a9180fadca926b3e1d7dfffb5021 (diff) | |
download | qtwebengine-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')
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 Binary files differnew file mode 100644 index 00000000000..597e6c0646f --- /dev/null +++ b/chromium/device/fido/ctap_response_fuzzer_corpus/get_assertion_response_corpus 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 Binary files differnew file mode 100644 index 00000000000..448f92c1c35 --- /dev/null +++ b/chromium/device/fido/ctap_response_fuzzer_corpus/make_credential_response_corpus 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 Binary files differnew file mode 100644 index 00000000000..b8f3fef89b3 --- /dev/null +++ b/chromium/device/fido/response_data_fuzzer_corpus/register1 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 Binary files differnew file mode 100644 index 00000000000..6d8cc24d0ea --- /dev/null +++ b/chromium/device/fido/response_data_fuzzer_corpus/sign1 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_), + [¤t_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_ |