1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
// Copyright 2023 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/android/cable_registration_state.h"
#include <array>
#include "base/base64.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "crypto/random.h"
#include "device/fido/cable/v2_handshake.h"
#include "device/fido/features.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
using device::cablev2::authenticator::Registration;
namespace webauthn::authenticator {
RegistrationState::SystemInterface::~SystemInterface() = default;
RegistrationState::RegistrationState(std::unique_ptr<SystemInterface> interface)
: interface_(std::move(interface)) {}
RegistrationState::~RegistrationState() = default;
void RegistrationState::Register() {
DCHECK(!linking_registration_);
DCHECK(!sync_registration_);
linking_registration_ = interface_->NewRegistration(
device::cablev2::authenticator::Registration::Type::LINKING,
base::BindOnce(&RegistrationState::OnLinkingRegistrationReady,
base::Unretained(this)),
base::BindRepeating(&RegistrationState::OnEvent, base::Unretained(this)));
sync_registration_ = interface_->NewRegistration(
device::cablev2::authenticator::Registration::Type::SYNC,
base::BindOnce(&RegistrationState::OnSyncRegistrationReady,
base::Unretained(this)),
base::BindRepeating(&RegistrationState::OnEvent, base::Unretained(this)));
interface_->CanDeviceSupportCable(base::BindOnce(
&RegistrationState::OnDeviceSupportResult, base::Unretained(this)));
interface_->AmInWorkProfile(base::BindOnce(
&RegistrationState::OnWorkProfileResult, base::Unretained(this)));
}
// have_data_for_sync returns true if this object has loaded enough state to
// put information into sync's DeviceInfo.
bool RegistrationState::have_data_for_sync() const {
return device_supports_cable_.has_value() &&
am_in_work_profile_.has_value() && sync_registration_ != nullptr &&
sync_registration_->contact_id() && have_play_services_data();
}
void RegistrationState::SignalSyncWhenReady() {
if (sync_registration_ && !sync_registration_->contact_id()) {
sync_registration_->PrepareContactID();
}
if (!have_play_services_data() && !play_services_query_pending_) {
QueryPlayServices();
}
signal_sync_when_ready_ = true;
}
bool RegistrationState::have_play_services_data() const {
// If there's no result, then we're not ready.
if (!have_link_data_from_play_services_) {
return false;
}
// If there's a query already pending then the result must be stale and
// there's nothing more to do here.
if (play_services_query_pending_) {
return false;
}
const base::TimeDelta staleness =
base::TimeTicks::Now() - link_data_from_play_services_timeticks_;
return staleness < base::Hours(12);
}
void RegistrationState::QueryPlayServices() {
DCHECK(!play_services_query_pending_);
play_services_query_pending_ = true;
interface_->GetPrelinkFromPlayServices(
base::BindOnce(&RegistrationState::OnHavePlayServicesLinkingInformation,
base::Unretained(this)));
}
void RegistrationState::OnHavePlayServicesLinkingInformation(
std::optional<std::vector<uint8_t>> cbor) {
DCHECK(play_services_query_pending_);
play_services_query_pending_ = false;
link_data_from_play_services_ = std::move(cbor);
have_link_data_from_play_services_ = true;
link_data_from_play_services_timeticks_ = base::TimeTicks::Now();
MaybeSignalSync();
}
void RegistrationState::OnLinkingRegistrationReady() {
MaybeFlushPendingEvent();
}
void RegistrationState::OnSyncRegistrationReady() {
MaybeSignalSync();
}
void RegistrationState::OnEvent(std::unique_ptr<Registration::Event> event) {
pending_event_ = std::move(event);
MaybeFlushPendingEvent();
}
void RegistrationState::MaybeFlushPendingEvent() {
if (!pending_event_) {
return;
}
if (pending_event_->source == Registration::Type::LINKING &&
!pending_event_->contact_id) {
// This GCM message is from a QR-linked peer so it needs the contact ID
// to be processed.
pending_event_->contact_id = linking_registration_->contact_id();
if (!pending_event_->contact_id) {
// The contact ID isn't ready yet. Wait until it is.
linking_registration_->PrepareContactID();
return;
}
}
std::unique_ptr<Registration::Event> event(std::move(pending_event_));
if (event->source == Registration::Type::SYNC) {
// If this is from a synced peer then we limit how old the keys can be.
// Clank will update its device information once per day (when launched)
// and we piggyback on that to transmit fresh keys. Therefore syncing
// peers should have reasonably recent information.
uint64_t id;
static_assert(std::tuple_size_v<decltype(event->pairing_id)> == sizeof(id));
UNSAFE_TODO(memcpy(&id, event->pairing_id.data(), sizeof(id)));
// A maximum age is enforced for sync secrets so that any leak of
// information isn't valid forever. The desktop ignores DeviceInfo
// records with information that is too old so this should never happen
// with honest clients.
if (id > std::numeric_limits<uint32_t>::max() ||
device::cablev2::sync::IDIsMoreThanNPeriodsOld(
static_cast<uint32_t>(id),
device::cablev2::kMaxSyncInfoDaysForProducer)) {
return;
}
}
}
void RegistrationState::MaybeSignalSync() {
if (!signal_sync_when_ready_ || !have_data_for_sync()) {
return;
}
signal_sync_when_ready_ = false;
interface_->RefreshLocalDeviceInfo();
}
void RegistrationState::OnDeviceSupportResult(bool result) {
device_supports_cable_ = result;
MaybeSignalSync();
}
void RegistrationState::OnWorkProfileResult(bool result) {
am_in_work_profile_ = result;
MaybeSignalSync();
}
} // namespace webauthn::authenticator
|