blob: ce642808d05d3e297c7667e2e65be42425a196c4 [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 "chromeos/services/secure_channel/secure_channel_impl.h"
#include <ostream>
#include <sstream>
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/services/secure_channel/active_connection_manager_impl.h"
#include "chromeos/services/secure_channel/authenticated_channel.h"
#include "chromeos/services/secure_channel/ble_connection_manager_impl.h"
#include "chromeos/services/secure_channel/ble_service_data_helper_impl.h"
#include "chromeos/services/secure_channel/client_connection_parameters_impl.h"
#include "chromeos/services/secure_channel/device_id_pair.h"
#include "chromeos/services/secure_channel/pending_connection_manager_impl.h"
#include "chromeos/services/secure_channel/timer_factory_impl.h"
#include "device/bluetooth/bluetooth_adapter.h"
namespace chromeos {
namespace secure_channel {
// static
SecureChannelImpl::Factory* SecureChannelImpl::Factory::test_factory_ = nullptr;
// static
SecureChannelImpl::Factory* SecureChannelImpl::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<Factory> factory;
return factory.get();
}
// static
void SecureChannelImpl::Factory::SetFactoryForTesting(Factory* test_factory) {
test_factory_ = test_factory;
}
SecureChannelImpl::Factory::~Factory() = default;
std::unique_ptr<mojom::SecureChannel> SecureChannelImpl::Factory::BuildInstance(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
return base::WrapUnique(new SecureChannelImpl(bluetooth_adapter));
}
SecureChannelImpl::ConnectionRequestWaitingForDisconnection::
ConnectionRequestWaitingForDisconnection(
std::unique_ptr<ClientConnectionParameters>
client_connection_parameters,
ConnectionAttemptDetails connection_attempt_details,
ConnectionPriority connection_priority)
: client_connection_parameters(std::move(client_connection_parameters)),
connection_attempt_details(connection_attempt_details),
connection_priority(connection_priority) {}
SecureChannelImpl::ConnectionRequestWaitingForDisconnection::
ConnectionRequestWaitingForDisconnection(
ConnectionRequestWaitingForDisconnection&&) noexcept = default;
SecureChannelImpl::ConnectionRequestWaitingForDisconnection&
SecureChannelImpl::ConnectionRequestWaitingForDisconnection::operator=(
ConnectionRequestWaitingForDisconnection&&) noexcept = default;
SecureChannelImpl::ConnectionRequestWaitingForDisconnection::
~ConnectionRequestWaitingForDisconnection() = default;
SecureChannelImpl::SecureChannelImpl(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)
: bluetooth_adapter_(std::move(bluetooth_adapter)),
timer_factory_(TimerFactoryImpl::Factory::Get()->BuildInstance()),
remote_device_cache_(
multidevice::RemoteDeviceCache::Factory::Get()->BuildInstance()),
ble_service_data_helper_(
BleServiceDataHelperImpl::Factory::Get()->BuildInstance(
remote_device_cache_.get())),
ble_connection_manager_(
BleConnectionManagerImpl::Factory::Get()->BuildInstance(
bluetooth_adapter_,
ble_service_data_helper_.get(),
timer_factory_.get())),
pending_connection_manager_(
PendingConnectionManagerImpl::Factory::Get()->BuildInstance(
this /* delegate */,
ble_connection_manager_.get(),
bluetooth_adapter_)),
active_connection_manager_(
ActiveConnectionManagerImpl::Factory::Get()->BuildInstance(
this /* delegate */)) {}
SecureChannelImpl::~SecureChannelImpl() = default;
void SecureChannelImpl::ListenForConnectionFromDevice(
const multidevice::RemoteDevice& device_to_connect,
const multidevice::RemoteDevice& local_device,
const std::string& feature,
ConnectionPriority connection_priority,
mojom::ConnectionDelegatePtr delegate) {
ProcessConnectionRequest(
ApiFunctionName::kListenForConnection, device_to_connect, local_device,
ClientConnectionParametersImpl::Factory::Get()->BuildInstance(
feature, std::move(delegate)),
ConnectionRole::kListenerRole, connection_priority,
ConnectionMedium::kBluetoothLowEnergy);
}
void SecureChannelImpl::InitiateConnectionToDevice(
const multidevice::RemoteDevice& device_to_connect,
const multidevice::RemoteDevice& local_device,
const std::string& feature,
ConnectionPriority connection_priority,
mojom::ConnectionDelegatePtr delegate) {
ProcessConnectionRequest(
ApiFunctionName::kInitiateConnection, device_to_connect, local_device,
ClientConnectionParametersImpl::Factory::Get()->BuildInstance(
feature, std::move(delegate)),
ConnectionRole::kInitiatorRole, connection_priority,
ConnectionMedium::kBluetoothLowEnergy);
}
void SecureChannelImpl::OnDisconnected(
const ConnectionDetails& connection_details) {
auto pending_requests_it =
disconnecting_details_to_requests_map_.find(connection_details);
if (pending_requests_it == disconnecting_details_to_requests_map_.end()) {
// If there were no queued client requests that were waiting for a
// disconnection, there is nothing to do.
PA_LOG(INFO) << "SecureChannelImpl::OnDisconnected(): Previously-active "
<< "connection became disconnected. Details: "
<< connection_details;
return;
}
// For each request which was pending (i.e., waiting for a disconnecting
// connection to disconnect), pass the request off to
// PendingConnectionManager.
for (auto& details : pending_requests_it->second) {
PA_LOG(VERBOSE)
<< "SecureChannelImpl::OnDisconnected(): Disconnection "
<< "completed; starting pending connection attempt. Request: "
<< *details.client_connection_parameters
<< ", Attempt details: " << details.connection_attempt_details;
pending_connection_manager_->HandleConnectionRequest(
details.connection_attempt_details,
std::move(details.client_connection_parameters),
details.connection_priority);
}
// Now that each item has been passed on, remove the map entry.
disconnecting_details_to_requests_map_.erase(connection_details);
}
void SecureChannelImpl::OnConnection(
std::unique_ptr<AuthenticatedChannel> authenticated_channel,
std::vector<std::unique_ptr<ClientConnectionParameters>> clients,
const ConnectionDetails& connection_details) {
ActiveConnectionManager::ConnectionState state =
active_connection_manager_->GetConnectionState(connection_details);
if (state != ActiveConnectionManager::ConnectionState::kNoConnectionExists) {
PA_LOG(ERROR) << "SecureChannelImpl::OnConnection(): Connection created "
<< "for detail " << connection_details << ", but a "
<< "connection already existed for those details.";
NOTREACHED();
}
// Build string of clients whose connection attempts succeeded.
std::stringstream ss;
ss << "[";
for (const auto& client : clients)
ss << *client << ", ";
ss.seekp(-2, ss.cur); // Remove last ", " from the stream.
ss << "]";
PA_LOG(INFO) << "SecureChannelImpl::OnConnection(): Connection created "
<< "successfully. Details: " << connection_details
<< ", Clients: " << ss.str();
active_connection_manager_->AddActiveConnection(
std::move(authenticated_channel), std::move(clients), connection_details);
}
void SecureChannelImpl::ProcessConnectionRequest(
ApiFunctionName api_fn_name,
const multidevice::RemoteDevice& device_to_connect,
const multidevice::RemoteDevice& local_device,
std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
ConnectionRole connection_role,
ConnectionPriority connection_priority,
ConnectionMedium connection_medium) {
// Check 1: Is the provided ConnectionDelegate valid? If not, return early.
if (CheckForInvalidRequest(api_fn_name, client_connection_parameters.get()))
return;
// Check 2: Is the provided device to connect valid? If not, notify client and
// return early.
if (CheckForInvalidInputDevice(api_fn_name, device_to_connect,
client_connection_parameters.get(),
false /* is_local_device */)) {
return;
}
// Check 3: Is the provided local device valid? If not, notify client and
// return early.
if (CheckForInvalidInputDevice(api_fn_name, local_device,
client_connection_parameters.get(),
true /* is_local_device */)) {
return;
}
// Check 4: Medium-specific verification.
switch (connection_medium) {
case ConnectionMedium::kBluetoothLowEnergy:
// Is the local Bluetooth adapter disabled or not present? If either,
// notify client and return early.
if (CheckIfBluetoothAdapterDisabledOrNotPresent(
api_fn_name, client_connection_parameters.get()))
return;
break;
}
// At this point, the request has been deemed valid.
ConnectionAttemptDetails connection_attempt_details(
device_to_connect.GetDeviceId(), local_device.GetDeviceId(),
connection_medium, connection_role);
ConnectionDetails connection_details =
connection_attempt_details.GetAssociatedConnectionDetails();
switch (active_connection_manager_->GetConnectionState(connection_details)) {
case ActiveConnectionManager::ConnectionState::kActiveConnectionExists:
PA_LOG(VERBOSE) << "SecureChannelImpl::" << api_fn_name << "(): Adding "
<< "request to active channel. Request: "
<< *client_connection_parameters
<< ", Local device ID: \""
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
local_device.GetDeviceId())
<< "\""
<< ", Role: " << connection_role
<< ", Priority: " << connection_priority
<< ", Details: " << connection_details;
active_connection_manager_->AddClientToChannel(
std::move(client_connection_parameters), connection_details);
break;
case ActiveConnectionManager::ConnectionState::kNoConnectionExists:
PA_LOG(VERBOSE) << "SecureChannelImpl::" << api_fn_name << "(): Starting "
<< "pending connection attempt. Request: "
<< *client_connection_parameters
<< ", Local device ID: \""
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
local_device.GetDeviceId())
<< "\""
<< ", Role: " << connection_role
<< ", Priority: " << connection_priority
<< ", Details: " << connection_details;
pending_connection_manager_->HandleConnectionRequest(
connection_attempt_details, std::move(client_connection_parameters),
connection_priority);
break;
case ActiveConnectionManager::ConnectionState::
kDisconnectingConnectionExists:
PA_LOG(VERBOSE)
<< "SecureChannelImpl::" << api_fn_name << "(): Received "
<< "request for which a disconnecting connection exists. "
<< "Waiting for connection to disconnect completely before "
<< "continuing. Request: " << *client_connection_parameters
<< ", Local device ID: \""
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
local_device.GetDeviceId())
<< "\""
<< ", Role: " << connection_role
<< ", Priority: " << connection_priority
<< ", Details: " << connection_details;
disconnecting_details_to_requests_map_[connection_details].emplace_back(
std::move(client_connection_parameters), connection_attempt_details,
connection_priority);
break;
}
}
void SecureChannelImpl::RejectRequestForReason(
ApiFunctionName api_fn_name,
mojom::ConnectionAttemptFailureReason reason,
ClientConnectionParameters* client_connection_parameters) {
PA_LOG(WARNING) << "SecureChannelImpl::" << api_fn_name << "(): Rejecting "
<< "request ID: " << client_connection_parameters->id() << " "
<< "for reason: " << reason;
client_connection_parameters->SetConnectionAttemptFailed(reason);
}
bool SecureChannelImpl::CheckForInvalidRequest(
ApiFunctionName api_fn_name,
ClientConnectionParameters* client_connection_parameters) const {
if (!client_connection_parameters->IsClientWaitingForResponse()) {
PA_LOG(ERROR) << "SecureChannelImpl::" << api_fn_name << "(): "
<< "ConnectionDelegate is not waiting for a response.";
return true;
}
return false;
}
bool SecureChannelImpl::CheckForInvalidInputDevice(
ApiFunctionName api_fn_name,
const multidevice::RemoteDevice& device,
ClientConnectionParameters* client_connection_parameters,
bool is_local_device) {
base::Optional<InvalidRemoteDeviceReason> potential_invalid_reason =
AddDeviceToCacheIfPossible(api_fn_name, device);
if (!potential_invalid_reason)
return false;
switch (*potential_invalid_reason) {
case InvalidRemoteDeviceReason::kInvalidPublicKey:
RejectRequestForReason(api_fn_name,
is_local_device
? mojom::ConnectionAttemptFailureReason::
LOCAL_DEVICE_INVALID_PUBLIC_KEY
: mojom::ConnectionAttemptFailureReason::
REMOTE_DEVICE_INVALID_PUBLIC_KEY,
client_connection_parameters);
break;
case InvalidRemoteDeviceReason::kInvalidPsk:
RejectRequestForReason(
api_fn_name,
is_local_device
? mojom::ConnectionAttemptFailureReason::LOCAL_DEVICE_INVALID_PSK
: mojom::ConnectionAttemptFailureReason::
REMOTE_DEVICE_INVALID_PSK,
client_connection_parameters);
break;
}
return true;
}
bool SecureChannelImpl::CheckIfBluetoothAdapterDisabledOrNotPresent(
ApiFunctionName api_fn_name,
ClientConnectionParameters* client_connection_parameters) {
if (!bluetooth_adapter_->IsPresent()) {
RejectRequestForReason(
api_fn_name, mojom::ConnectionAttemptFailureReason::ADAPTER_NOT_PRESENT,
client_connection_parameters);
return true;
}
if (!bluetooth_adapter_->IsPowered()) {
RejectRequestForReason(
api_fn_name, mojom::ConnectionAttemptFailureReason::ADAPTER_DISABLED,
client_connection_parameters);
return true;
}
return false;
}
base::Optional<SecureChannelImpl::InvalidRemoteDeviceReason>
SecureChannelImpl::AddDeviceToCacheIfPossible(
ApiFunctionName api_fn_name,
const multidevice::RemoteDevice& device) {
if (device.public_key.empty()) {
PA_LOG(WARNING) << "SecureChannelImpl::" << api_fn_name << "(): "
<< "Provided device has an invalid public key. Cannot "
<< "process request.";
return InvalidRemoteDeviceReason::kInvalidPublicKey;
}
if (device.persistent_symmetric_key.empty()) {
PA_LOG(WARNING) << "SecureChannelImpl::" << api_fn_name << "(): "
<< "Provided device has an invalid PSK. Cannot process "
<< "request.";
return InvalidRemoteDeviceReason::kInvalidPsk;
}
remote_device_cache_->SetRemoteDevices({device});
return base::nullopt;
}
std::ostream& operator<<(std::ostream& stream,
const SecureChannelImpl::ApiFunctionName& role) {
switch (role) {
case SecureChannelImpl::ApiFunctionName::kListenForConnection:
stream << "ListenForConnectionFromDevice";
break;
case SecureChannelImpl::ApiFunctionName::kInitiateConnection:
stream << "InitiateConnectionToDevice";
break;
}
return stream;
}
} // namespace secure_channel
} // namespace chromeos