blob: 79c4bf53da3a0d99b267364a941a8c28528e3050 [file] [log] [blame]
// 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/cable/fido_cable_discovery.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "crypto/random.h"
#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/bluetooth_discovery_session.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/fido/ble/fido_ble_uuids.h"
#include "device/fido/cable/fido_cable_device.h"
#include "device/fido/cable/fido_cable_handshake_handler.h"
#include "device/fido/features.h"
#include "device/fido/fido_parsing_utils.h"
#include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/hkdf.h"
#include "third_party/boringssl/src/include/openssl/mem.h"
namespace device {
namespace {
// Construct advertisement data with different formats depending on client's
// operating system. Ideally, we advertise EIDs as part of Service Data, but
// this isn't available on all platforms. On Windows we use Manufacturer Data
// instead, and on Mac our only option is to advertise an additional service
// with the EID as its UUID.
std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData(
base::span<const uint8_t, kCableEphemeralIdSize> client_eid) {
auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>(
BluetoothAdvertisement::AdvertisementType::ADVERTISEMENT_TYPE_BROADCAST);
#if defined(OS_MACOSX)
auto list = std::make_unique<BluetoothAdvertisement::UUIDList>();
list->emplace_back(kCableAdvertisementUUID16);
list->emplace_back(fido_parsing_utils::ConvertBytesToUuid(client_eid));
advertisement_data->set_service_uuids(std::move(list));
#elif defined(OS_WIN)
// References:
// https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers
// go/google-ble-manufacturer-data-format
static constexpr uint16_t kGoogleManufacturerId = 0x00E0;
static constexpr uint8_t kCableGoogleManufacturerDataType = 0x15;
// Reference:
// https://github.com/arnar/fido-2-specs/blob/fido-client-to-authenticator-protocol.bs#L4314
static constexpr uint8_t kCableFlags = 0x20;
static constexpr uint8_t kCableGoogleManufacturerDataLength =
3u + kCableEphemeralIdSize;
std::array<uint8_t, 4> kCableGoogleManufacturerDataHeader = {
kCableGoogleManufacturerDataLength, kCableGoogleManufacturerDataType,
kCableFlags, /*version=*/1};
auto manufacturer_data =
std::make_unique<BluetoothAdvertisement::ManufacturerData>();
std::vector<uint8_t> manufacturer_data_value;
fido_parsing_utils::Append(&manufacturer_data_value,
kCableGoogleManufacturerDataHeader);
fido_parsing_utils::Append(&manufacturer_data_value, client_eid);
manufacturer_data->emplace(kGoogleManufacturerId,
std::move(manufacturer_data_value));
advertisement_data->set_manufacturer_data(std::move(manufacturer_data));
#elif defined(OS_LINUX) || defined(OS_CHROMEOS)
// Reference:
// https://github.com/arnar/fido-2-specs/blob/fido-client-to-authenticator-protocol.bs#L4314
static constexpr uint8_t kCableFlags = 0x20;
// Service data for ChromeOS and Linux is 1 byte corresponding to Cable flags,
// followed by 1 byte corresponding to Cable version number, followed by 16
// bytes corresponding to client EID.
auto service_data = std::make_unique<BluetoothAdvertisement::ServiceData>();
std::vector<uint8_t> service_data_value(18, 0);
// Since the remainder of this service data field is a Cable EID, set the 5th
// bit of the flag byte.
service_data_value[0] = kCableFlags;
service_data_value[1] = 1 /* version */;
std::copy(client_eid.begin(), client_eid.end(),
service_data_value.begin() + 2);
service_data->emplace(kCableAdvertisementUUID128,
std::move(service_data_value));
advertisement_data->set_service_data(std::move(service_data));
#endif
return advertisement_data;
}
} // namespace
// CableDiscoveryData -------------------------------------
CableDiscoveryData::CableDiscoveryData() = default;
CableDiscoveryData::CableDiscoveryData(
CableDiscoveryData::Version version,
const CableEidArray& client_eid,
const CableEidArray& authenticator_eid,
const CableSessionPreKeyArray& session_pre_key)
: version(version) {
CHECK_EQ(Version::V1, version);
v1.emplace();
v1->client_eid = client_eid;
v1->authenticator_eid = authenticator_eid;
v1->session_pre_key = session_pre_key;
}
CableDiscoveryData::CableDiscoveryData(
base::span<const uint8_t, kCableQRSecretSize> qr_secret) {
version = Version::V2;
v2.emplace();
static const char kEIDGen[] = "caBLE QR to EID generator key";
bool ok =
HKDF(v2->eid_gen_key.data(), v2->eid_gen_key.size(), EVP_sha256(),
qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0,
reinterpret_cast<const uint8_t*>(kEIDGen), sizeof(kEIDGen) - 1);
DCHECK(ok);
static const char kPSKGen[] = "caBLE QR to PSK generator key";
ok = HKDF(v2->psk_gen_key.data(), v2->psk_gen_key.size(), EVP_sha256(),
qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0,
reinterpret_cast<const uint8_t*>(kPSKGen), sizeof(kPSKGen) - 1);
DCHECK(ok);
}
CableDiscoveryData::CableDiscoveryData(const CableDiscoveryData& data) =
default;
CableDiscoveryData& CableDiscoveryData::operator=(
const CableDiscoveryData& other) = default;
CableDiscoveryData::~CableDiscoveryData() = default;
bool CableDiscoveryData::operator==(const CableDiscoveryData& other) const {
if (version != other.version) {
return false;
}
switch (version) {
case CableDiscoveryData::Version::V1:
return v1->client_eid == other.v1->client_eid &&
v1->authenticator_eid == other.v1->authenticator_eid &&
v1->session_pre_key == other.v1->session_pre_key;
case CableDiscoveryData::Version::V2:
return v2->eid_gen_key == other.v2->eid_gen_key &&
v2->psk_gen_key == other.v2->psk_gen_key &&
v2->peer_identity == other.v2->peer_identity &&
v2->peer_name == other.v2->peer_name;
case CableDiscoveryData::Version::INVALID:
CHECK(false);
return false;
}
}
base::Optional<CableNonce> CableDiscoveryData::Match(
const CableEidArray& eid) const {
switch (version) {
case Version::V1: {
if (eid != v1->authenticator_eid) {
return base::nullopt;
}
// The nonce is the first eight bytes of the EID.
CableNonce nonce;
const bool ok =
fido_parsing_utils::ExtractArray(v1->client_eid, 0, &nonce);
DCHECK(ok);
return nonce;
}
case Version::V2: {
// Attempt to decrypt the EID with the EID generator key and check whether
// it has a valid structure.
AES_KEY key;
CHECK(AES_set_decrypt_key(v2->eid_gen_key.data(),
/*bits=*/8 * v2->eid_gen_key.size(),
&key) == 0);
static_assert(kCableEphemeralIdSize == AES_BLOCK_SIZE,
"EIDs are not AES blocks");
CableEidArray decrypted;
AES_decrypt(/*in=*/eid.data(), /*out=*/decrypted.data(), &key);
const uint8_t kZeroTrailer[8] = {0};
static_assert(8 + sizeof(kZeroTrailer) ==
std::tuple_size<decltype(decrypted)>::value,
"Trailer is wrong size");
if (CRYPTO_memcmp(kZeroTrailer, decrypted.data() + 8,
sizeof(kZeroTrailer)) != 0) {
return base::nullopt;
}
CableNonce nonce;
static_assert(
sizeof(nonce) <= std::tuple_size<decltype(decrypted)>::value,
"nonce too large");
memcpy(nonce.data(), decrypted.data(), sizeof(nonce));
return nonce;
}
case Version::INVALID:
DCHECK(false);
return base::nullopt;
}
}
// static
QRGeneratorKey CableDiscoveryData::NewQRKey() {
QRGeneratorKey key;
crypto::RandBytes(key.data(), key.size());
return key;
}
// static
int64_t CableDiscoveryData::CurrentTimeTick() {
// The ticks are currently 256ms.
return base::TimeTicks::Now().since_origin().InMilliseconds() >> 8;
}
// static
std::array<uint8_t, kCableQRSecretSize> CableDiscoveryData::DeriveQRSecret(
base::span<const uint8_t, 32> qr_generator_key,
const int64_t tick) {
union {
int64_t i;
uint8_t bytes[8];
} current_tick;
current_tick.i = tick;
std::array<uint8_t, kCableQRSecretSize> ret;
bool ok = HKDF(ret.data(), ret.size(), EVP_sha256(), qr_generator_key.data(),
qr_generator_key.size(),
/*salt=*/nullptr, 0, current_tick.bytes, sizeof(current_tick));
DCHECK(ok);
return ret;
}
CableDiscoveryData::V2Data::V2Data() = default;
CableDiscoveryData::V2Data::V2Data(const V2Data&) = default;
CableDiscoveryData::V2Data::~V2Data() = default;
// FidoCableDiscovery::Result -------------------------------------------------
FidoCableDiscovery::Result::Result() = default;
FidoCableDiscovery::Result::Result(const CableDiscoveryData& in_discovery_data,
const CableNonce& in_nonce,
const CableEidArray& in_eid,
base::Optional<int> in_ticks_back)
: discovery_data(in_discovery_data),
nonce(in_nonce),
eid(in_eid),
ticks_back(in_ticks_back) {}
FidoCableDiscovery::Result::Result(const Result& other) = default;
FidoCableDiscovery::Result::~Result() = default;
// FidoCableDiscovery::ObservedDeviceData -------------------------------------
FidoCableDiscovery::ObservedDeviceData::ObservedDeviceData() = default;
FidoCableDiscovery::ObservedDeviceData::~ObservedDeviceData() = default;
// FidoCableDiscovery ---------------------------------------------------------
FidoCableDiscovery::FidoCableDiscovery(
std::vector<CableDiscoveryData> discovery_data,
base::Optional<QRGeneratorKey> qr_generator_key,
base::Optional<
base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)>>
pairing_callback)
: FidoBleDiscoveryBase(
FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy),
discovery_data_(std::move(discovery_data)),
qr_generator_key_(std::move(qr_generator_key)),
pairing_callback_(std::move(pairing_callback)) {
// Windows currently does not support multiple EIDs, thus we ignore any extra
// discovery data.
// TODO(https://crbug.com/837088): Add support for multiple EIDs on Windows.
#if defined(OS_WIN)
if (discovery_data_.size() > 1u)
discovery_data_.erase(discovery_data_.begin() + 1, discovery_data_.end());
#endif
}
// This is a workaround for https://crbug.com/846522
FidoCableDiscovery::~FidoCableDiscovery() {
for (auto advertisement : advertisements_)
advertisement.second->Unregister(base::DoNothing(), base::DoNothing());
}
base::Optional<std::unique_ptr<FidoCableHandshakeHandler>>
FidoCableDiscovery::CreateHandshakeHandler(
FidoCableDevice* device,
const CableDiscoveryData& discovery_data,
const CableNonce& nonce,
const CableEidArray& eid) {
std::unique_ptr<FidoCableHandshakeHandler> handler;
switch (discovery_data.version) {
case CableDiscoveryData::Version::V1: {
// Nonce is embedded as first 8 bytes of client EID.
std::array<uint8_t, 8> nonce;
const bool ok = fido_parsing_utils::ExtractArray(
discovery_data.v1->client_eid, 0, &nonce);
DCHECK(ok);
handler.reset(new FidoCableV1HandshakeHandler(
device, nonce, discovery_data.v1->session_pre_key));
break;
}
case CableDiscoveryData::Version::V2: {
if (!base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) {
return base::nullopt;
}
if (!pairing_callback_) {
FIDO_LOG(DEBUG) << "Discarding caBLE v2 handshake because of missing "
"pairing callback";
return base::nullopt;
}
handler.reset(new FidoCableV2HandshakeHandler(
device, discovery_data.v2->psk_gen_key, nonce, eid,
discovery_data.v2->peer_identity, *pairing_callback_));
break;
}
case CableDiscoveryData::Version::INVALID:
CHECK(false);
return base::nullopt;
}
return handler;
}
void FidoCableDiscovery::DeviceAdded(BluetoothAdapter* adapter,
BluetoothDevice* device) {
if (!IsCableDevice(device))
return;
CableDeviceFound(adapter, device);
}
void FidoCableDiscovery::DeviceChanged(BluetoothAdapter* adapter,
BluetoothDevice* device) {
if (!IsCableDevice(device))
return;
CableDeviceFound(adapter, device);
}
void FidoCableDiscovery::DeviceRemoved(BluetoothAdapter* adapter,
BluetoothDevice* device) {
if (IsCableDevice(device) && GetCableDiscoveryData(device)) {
const auto& device_address = device->GetAddress();
FIDO_LOG(DEBUG) << "caBLE device removed: " << device_address;
RemoveDevice(FidoBleDevice::GetIdForAddress(device_address));
}
}
void FidoCableDiscovery::AdapterPoweredChanged(BluetoothAdapter* adapter,
bool powered) {
// If Bluetooth adapter is powered on, resume scanning for nearby Cable
// devices and start advertising client EIDs.
if (powered) {
StartCableDiscovery();
} else {
// In order to prevent duplicate client EIDs from being advertised when
// BluetoothAdapter is powered back on, unregister all existing client
// EIDs.
StopAdvertisements(base::DoNothing());
}
}
void FidoCableDiscovery::OnSetPowered() {
DCHECK(adapter());
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&FidoCableDiscovery::StartCableDiscovery,
weak_factory_.GetWeakPtr()));
}
void FidoCableDiscovery::StartCableDiscovery() {
// Error callback OnStartDiscoverySessionError() is defined in the base class
// FidoBleDiscoveryBase.
adapter()->StartDiscoverySessionWithFilter(
std::make_unique<BluetoothDiscoveryFilter>(
BluetoothTransport::BLUETOOTH_TRANSPORT_LE),
base::AdaptCallbackForRepeating(
base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionWithFilter,
weak_factory_.GetWeakPtr())),
base::AdaptCallbackForRepeating(
base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionError,
weak_factory_.GetWeakPtr())));
}
void FidoCableDiscovery::OnStartDiscoverySessionWithFilter(
std::unique_ptr<BluetoothDiscoverySession> session) {
SetDiscoverySession(std::move(session));
FIDO_LOG(DEBUG) << "Discovery session started.";
// Advertising is delayed by 500ms to ensure that any UI has a chance to
// appear as we don't want to start broadcasting without the user being
// aware.
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FidoCableDiscovery::StartAdvertisement,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(500));
}
void FidoCableDiscovery::StartAdvertisement() {
DCHECK(adapter());
bool advertisements_pending = false;
for (const auto& data : discovery_data_) {
if (data.version != CableDiscoveryData::Version::V1) {
continue;
}
if (!advertisements_pending) {
FIDO_LOG(DEBUG) << "Starting to advertise clientEIDs.";
advertisements_pending = true;
}
adapter()->RegisterAdvertisement(
ConstructAdvertisementData(data.v1->client_eid),
base::AdaptCallbackForRepeating(
base::BindOnce(&FidoCableDiscovery::OnAdvertisementRegistered,
weak_factory_.GetWeakPtr(), data.v1->client_eid)),
base::AdaptCallbackForRepeating(
base::BindOnce(&FidoCableDiscovery::OnAdvertisementRegisterError,
weak_factory_.GetWeakPtr())));
}
if (!advertisements_pending) {
// If no V1 extensions were provided then this discovery is ready
// immediately. Otherwise |NotifyDiscoveryStarted| will be called once
// all the advertising requests have been resolved.
NotifyDiscoveryStarted(true);
}
}
void FidoCableDiscovery::StopAdvertisements(base::OnceClosure callback) {
auto barrier_closure =
base::BarrierClosure(advertisement_success_counter_, std::move(callback));
for (auto advertisement : advertisements_) {
advertisement.second->Unregister(barrier_closure, base::DoNothing());
FIDO_LOG(DEBUG) << "Stopped caBLE advertisement.";
}
#if !defined(OS_WIN)
// On Windows the discovery is the only owner of the advertisements, meaning
// the advertisements would be destroyed before |barrier_closure| could be
// invoked.
advertisements_.clear();
#endif // !defined(OS_WIN)
}
void FidoCableDiscovery::OnAdvertisementRegistered(
const CableEidArray& client_eid,
scoped_refptr<BluetoothAdvertisement> advertisement) {
FIDO_LOG(DEBUG) << "Advertisement registered.";
advertisements_.emplace(client_eid, std::move(advertisement));
RecordAdvertisementResult(true /* is_success */);
}
void FidoCableDiscovery::OnAdvertisementRegisterError(
BluetoothAdvertisement::ErrorCode error_code) {
FIDO_LOG(ERROR) << "Failed to register advertisement: " << error_code;
RecordAdvertisementResult(false /* is_success */);
}
void FidoCableDiscovery::RecordAdvertisementResult(bool is_success) {
// If at least one advertisement succeeds, then notify discovery start.
if (is_success) {
if (!advertisement_success_counter_++)
NotifyDiscoveryStarted(true);
return;
}
// No advertisements succeeded, no point in continuing with Cable discovery.
if (++advertisement_failure_counter_ == discovery_data_.size())
NotifyDiscoveryStarted(false);
}
void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter,
BluetoothDevice* device) {
const std::string device_address = device->GetAddress();
if (base::Contains(active_devices_, device_address)) {
return;
}
base::Optional<Result> maybe_result = GetCableDiscoveryData(device);
if (!maybe_result ||
base::Contains(active_authenticator_eids_, maybe_result->eid)) {
return;
}
FIDO_LOG(EVENT) << "Found new caBLE device.";
active_devices_.insert(device_address);
active_authenticator_eids_.insert(maybe_result->eid);
auto cable_device =
std::make_unique<FidoCableDevice>(adapter, device->GetAddress());
StopAdvertisements(
base::BindOnce(&FidoCableDiscovery::ConductEncryptionHandshake,
weak_factory_.GetWeakPtr(), std::move(cable_device),
std::move(*maybe_result)));
}
void FidoCableDiscovery::ConductEncryptionHandshake(
std::unique_ptr<FidoCableDevice> cable_device,
FidoCableDiscovery::Result result) {
base::Optional<std::unique_ptr<FidoCableHandshakeHandler>> handshake_handler =
CreateHandshakeHandler(cable_device.get(), result.discovery_data,
result.nonce, result.eid);
if (!handshake_handler) {
return;
}
auto* const handshake_handler_ptr = handshake_handler->get();
cable_handshake_handlers_.emplace_back(std::move(*handshake_handler));
handshake_handler_ptr->InitiateCableHandshake(
base::BindOnce(&FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage,
weak_factory_.GetWeakPtr(), std::move(cable_device),
handshake_handler_ptr));
}
void FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage(
std::unique_ptr<FidoCableDevice> cable_device,
FidoCableHandshakeHandler* handshake_handler,
base::Optional<std::vector<uint8_t>> handshake_response) {
if (!handshake_response)
return;
if (handshake_handler->ValidateAuthenticatorHandshakeMessage(
*handshake_response)) {
FIDO_LOG(DEBUG) << "Authenticator handshake validated";
AddDevice(std::move(cable_device));
} else {
FIDO_LOG(DEBUG) << "Authenticator handshake invalid";
}
}
base::Optional<FidoCableDiscovery::Result>
FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) const {
base::Optional<CableEidArray> maybe_eid_from_service_data =
MaybeGetEidFromServiceData(device);
std::vector<CableEidArray> uuids = GetUUIDs(device);
const std::string address = device->GetAddress();
const auto it = observed_devices_.find(address);
const bool known = it != observed_devices_.end();
if (known) {
std::unique_ptr<ObservedDeviceData>& data = it->second;
if (maybe_eid_from_service_data == data->service_data &&
uuids == data->uuids) {
// Duplicate data. Ignore.
return base::nullopt;
}
}
auto data = std::make_unique<ObservedDeviceData>();
data->service_data = maybe_eid_from_service_data;
data->uuids = uuids;
observed_devices_.insert_or_assign(address, std::move(data));
// New or updated device information.
if (known) {
FIDO_LOG(DEBUG) << "Updated information for caBLE device " << address
<< ":";
} else {
FIDO_LOG(DEBUG) << "New caBLE device " << address << ":";
}
base::Optional<FidoCableDiscovery::Result> ret;
if (maybe_eid_from_service_data.has_value()) {
ret =
GetCableDiscoveryDataFromAuthenticatorEid(*maybe_eid_from_service_data);
FIDO_LOG(DEBUG) << " Service data: "
<< ResultDebugString(*maybe_eid_from_service_data, ret);
} else {
FIDO_LOG(DEBUG) << " Service data: <none>";
}
if (!uuids.empty()) {
FIDO_LOG(DEBUG) << " UUIDs:";
for (const auto& uuid : uuids) {
auto result = GetCableDiscoveryDataFromAuthenticatorEid(uuid);
FIDO_LOG(DEBUG) << " " << ResultDebugString(uuid, result);
if (!ret.has_value() && result.has_value()) {
ret = result;
}
}
}
return ret;
}
// static
base::Optional<CableEidArray> FidoCableDiscovery::MaybeGetEidFromServiceData(
const BluetoothDevice* device) {
const auto* service_data =
device->GetServiceDataForUUID(CableAdvertisementUUID());
if (!service_data) {
return base::nullopt;
}
// Received service data from authenticator must have a flag that signals that
// the service data includes Cable EID.
if (service_data->empty() || !(service_data->at(0) >> 5 & 1u))
return base::nullopt;
CableEidArray received_authenticator_eid;
bool extract_success = fido_parsing_utils::ExtractArray(
*service_data, 2, &received_authenticator_eid);
if (!extract_success)
return base::nullopt;
return received_authenticator_eid;
}
// static
std::vector<CableEidArray> FidoCableDiscovery::GetUUIDs(
const BluetoothDevice* device) {
std::vector<CableEidArray> ret;
const auto service_uuids = device->GetUUIDs();
for (const auto& uuid : service_uuids) {
// |uuid_hex| is a hex string with the format:
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
const std::string& uuid_hex = uuid.canonical_value();
DCHECK_EQ(32u + 4u, uuid_hex.size());
// Copy substrings of |uuid_hex| to drop the hyphens.
std::string hex;
hex.reserve(32);
hex.append(uuid_hex, 0, 8);
hex.append(uuid_hex, 9, 4);
hex.append(uuid_hex, 14, 4);
hex.append(uuid_hex, 19, 4);
hex.append(uuid_hex, 24, 12);
DCHECK_EQ(32u, hex.size());
std::vector<uint8_t> uuid_binary;
const bool ok = base::HexStringToBytes(hex, &uuid_binary);
DCHECK(ok);
CableEidArray authenticator_eid;
DCHECK_EQ(authenticator_eid.size(), uuid_binary.size());
memcpy(authenticator_eid.data(), uuid_binary.data(),
authenticator_eid.size());
ret.emplace_back(std::move(authenticator_eid));
}
return ret;
}
base::Optional<FidoCableDiscovery::Result>
FidoCableDiscovery::GetCableDiscoveryDataFromAuthenticatorEid(
CableEidArray authenticator_eid) const {
for (const auto& candidate : discovery_data_) {
auto maybe_nonce = candidate.Match(authenticator_eid);
if (maybe_nonce) {
return Result(candidate, *maybe_nonce, authenticator_eid, base::nullopt);
}
}
if (qr_generator_key_) {
// Attempt to match |authenticator_eid| as the result of scanning a QR code.
const int64_t current_tick = CableDiscoveryData::CurrentTimeTick();
// kNumPreviousTicks is the number of previous ticks that will be accepted
// as valid. Ticks are currently 256ms so the value of sixteen translates to
// about four seconds.
constexpr int kNumPreviousTicks = 16;
for (int i = 0; i < kNumPreviousTicks; i++) {
auto qr_secret = CableDiscoveryData::DeriveQRSecret(*qr_generator_key_,
current_tick - i);
CableDiscoveryData candidate(qr_secret);
auto maybe_nonce = candidate.Match(authenticator_eid);
if (maybe_nonce) {
return Result(candidate, *maybe_nonce, authenticator_eid, i);
}
}
if (base::Contains(noted_obsolete_eids_, authenticator_eid)) {
for (int i = kNumPreviousTicks; i < 2 * kNumPreviousTicks; i++) {
auto qr_secret = CableDiscoveryData::DeriveQRSecret(*qr_generator_key_,
current_tick - i);
CableDiscoveryData candidate(qr_secret);
if (candidate.Match(authenticator_eid)) {
noted_obsolete_eids_.insert(authenticator_eid);
FIDO_LOG(DEBUG)
<< "(EID " << base::HexEncode(authenticator_eid) << " is " << i
<< " ticks old and would be valid but for the cutoff)";
break;
}
}
}
}
return base::nullopt;
}
// static
std::string FidoCableDiscovery::ResultDebugString(
const CableEidArray& eid,
const base::Optional<FidoCableDiscovery::Result>& result) {
static const uint8_t kAppleContinuity[16] = {
0xd0, 0x61, 0x1e, 0x78, 0xbb, 0xb4, 0x45, 0x91,
0xa5, 0xf8, 0x48, 0x79, 0x10, 0xae, 0x43, 0x66,
};
static const uint8_t kAppleUnknown[16] = {
0x9f, 0xa4, 0x80, 0xe0, 0x49, 0x67, 0x45, 0x42,
0x93, 0x90, 0xd3, 0x43, 0xdc, 0x5d, 0x04, 0xae,
};
static const uint8_t kAppleMedia[16] = {
0x89, 0xd3, 0x50, 0x2b, 0x0f, 0x36, 0x43, 0x3a,
0x8e, 0xf4, 0xc5, 0x02, 0xad, 0x55, 0xf8, 0xdc,
};
static const uint8_t kAppleNotificationCenter[16] = {
0x79, 0x05, 0xf4, 0x31, 0xb5, 0xce, 0x4e, 0x99,
0xa4, 0x0f, 0x4b, 0x1e, 0x12, 0x2d, 0x00, 0xd0,
};
static const uint8_t kCable[16] = {
0x00, 0x00, 0xfd, 0xe2, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
};
std::string ret = base::HexEncode(eid) + "";
if (!result) {
// Try to identify some common UUIDs that are random and thus otherwise look
// like potential EIDs.
if (memcmp(eid.data(), kAppleContinuity, eid.size()) == 0) {
ret += " (Apple Continuity service)";
} else if (memcmp(eid.data(), kAppleUnknown, eid.size()) == 0) {
ret += " (Apple service)";
} else if (memcmp(eid.data(), kAppleMedia, eid.size()) == 0) {
ret += " (Apple Media service)";
} else if (memcmp(eid.data(), kAppleNotificationCenter, eid.size()) == 0) {
ret += " (Apple Notification service)";
} else if (memcmp(eid.data(), kCable, eid.size()) == 0) {
ret += " (caBLE indicator)";
}
return ret;
}
switch (result->discovery_data.version) {
case CableDiscoveryData::Version::V1:
ret += " (version one match";
break;
case CableDiscoveryData::Version::V2:
ret += " (version two match";
break;
case CableDiscoveryData::Version::INVALID:
NOTREACHED();
}
if (!result->ticks_back) {
ret += " against pairing data)";
} else {
ret += " from QR, " + base::NumberToString(*result->ticks_back) +
" tick(s) ago)";
}
return ret;
}
} // namespace device