blob: 330009f366108cd842f5bb97e98db6d353f219ca [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webauthn/cablev2_devices.h"
#include <algorithm>
#include <array>
#include <string>
#include <string_view>
#include <vector>
#include "base/base64.h"
#include "base/containers/contains.h"
#include "base/i18n/time_formatting.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/device_info_sync_service_factory.h"
#include "components/sync_device_info/device_info.h"
#include "components/sync_device_info/device_info_sync_service.h"
#include "components/sync_device_info/device_info_tracker.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/cable/v2_constants.h"
#include "device/fido/cable/v2_handshake.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
using device::cablev2::Pairing;
namespace cablev2 {
namespace {
// NameForDisplay removes line-breaking characters from `raw_name` to ensure
// that the transport-selection UI isn't too badly broken by nonsense names.
static std::string NameForDisplay(std::string_view raw_name) {
std::u16string unicode_name = base::UTF8ToUTF16(raw_name);
std::u16string_view trimmed_name =
base::TrimWhitespace(unicode_name, base::TRIM_ALL);
// These are all the Unicode mandatory line-breaking characters
// (https://www.unicode.org/reports/tr14/tr14-32.html#Properties).
constexpr char16_t kLineTerminators[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x85, 0x2028,
0x2029,
// Array must be NUL terminated.
0};
std::u16string nonbreaking_name;
base::RemoveChars(trimmed_name, kLineTerminators, &nonbreaking_name);
return base::UTF16ToUTF8(nonbreaking_name);
}
std::vector<std::unique_ptr<Pairing>> GetSyncedDevices(Profile* const profile) {
std::vector<std::unique_ptr<Pairing>> ret;
syncer::DeviceInfoSyncService* const sync_service =
DeviceInfoSyncServiceFactory::GetForProfile(profile);
if (!sync_service) {
return ret;
}
syncer::DeviceInfoTracker* const tracker =
sync_service->GetDeviceInfoTracker();
std::vector<const syncer::DeviceInfo*> devices = tracker->GetAllDeviceInfo();
const base::Time now = base::Time::Now();
for (const syncer::DeviceInfo* device : devices) {
std::unique_ptr<Pairing> pairing = PairingFromSyncedDevice(device, now);
if (!pairing) {
continue;
}
ret.emplace_back(std::move(pairing));
}
return ret;
}
} // namespace
// PairingFromSyncedDevice extracts the caBLEv2 information from Sync's
// DeviceInfo (if any) into a caBLEv2 pairing. It may return nullptr.
std::unique_ptr<Pairing> PairingFromSyncedDevice(
const syncer::DeviceInfo* device,
const base::Time& now) {
const std::optional<syncer::DeviceInfo::PhoneAsASecurityKeyInfo>&
maybe_paask_info = device->paask_info();
if (!maybe_paask_info) {
return nullptr;
}
const syncer::DeviceInfo::PhoneAsASecurityKeyInfo& paask_info =
*maybe_paask_info;
if (device::cablev2::sync::IDIsMoreThanNPeriodsOld(
paask_info.id, device::cablev2::kMaxSyncInfoDaysForConsumer)) {
// Old entries are dropped as phones won't honor linking information that is
// excessively old.
return nullptr;
}
auto pairing = std::make_unique<Pairing>();
pairing->from_sync_deviceinfo = true;
pairing->name = NameForDisplay(device->client_name());
const std::optional<device::cablev2::tunnelserver::KnownDomainID>
tunnel_server_domain = device::cablev2::tunnelserver::ToKnownDomainID(
paask_info.tunnel_server_domain);
if (!tunnel_server_domain) {
// It's possible that a phone is running a more modern version of Chrome
// and uses an assigned tunnel server domain that is unknown to this code.
return nullptr;
}
pairing->tunnel_server_domain = *tunnel_server_domain;
pairing->contact_id = paask_info.contact_id;
pairing->peer_public_key_x962 = paask_info.peer_public_key_x962;
pairing->secret.assign(paask_info.secret.begin(), paask_info.secret.end());
pairing->last_updated = device->last_updated_timestamp();
// The pairing ID from sync is zero-padded to the standard length.
static_assert(device::cablev2::kPairingIDSize >= sizeof(paask_info.id), "");
pairing->id.assign(device::cablev2::kPairingIDSize, 0);
base::span(pairing->id)
.copy_prefix_from(base::byte_span_from_ref(paask_info.id));
// The channel priority is only approximate and exists to help testing and
// development. I.e. we want the development or Canary install on a device to
// shadow the stable channel so that it's possible to test things. This code
// is matching the string generated by `FormatUserAgentForSync`.
const std::string& user_agent = device->sync_user_agent();
if (user_agent.find("-devel") != std::string::npos) {
pairing->channel_priority = 5;
} else if (user_agent.find("(canary)") != std::string::npos) {
pairing->channel_priority = 4;
} else if (user_agent.find("(dev)") != std::string::npos) {
pairing->channel_priority = 3;
} else if (user_agent.find("(beta)") != std::string::npos) {
pairing->channel_priority = 2;
} else if (user_agent.find("(stable)") != std::string::npos) {
pairing->channel_priority = 1;
} else {
pairing->channel_priority = 0;
}
return pairing;
}
KnownDevices::KnownDevices() = default;
KnownDevices::~KnownDevices() = default;
// static
std::unique_ptr<KnownDevices> KnownDevices::FromProfile(Profile* profile) {
if (profile->IsOffTheRecord()) {
// For Incognito windows we collect the devices from the parent profile.
// The `AuthenticatorRequestDialogController` will notice that it's an OTR
// profile and display a confirmation interstitial for makeCredential calls.
profile = profile->GetOriginalProfile();
}
auto ret = std::make_unique<KnownDevices>();
ret->synced_devices = GetSyncedDevices(profile);
return ret;
}
std::vector<std::string_view> KnownDevices::Names() const {
std::vector<std::string_view> names;
names.reserve(this->synced_devices.size());
for (const std::unique_ptr<device::cablev2::Pairing>& device :
this->synced_devices) {
names.push_back(device->name);
}
return names;
}
} // namespace cablev2