blob: 4f84da811497816e39ad29fdfe758878a1fb0463 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/pairing_state.h"
#include <inttypes.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/bredr_connection_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/util.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/transport.h"
namespace bt::gap {
using pw::bluetooth::emboss::AuthenticationRequirements;
using pw::bluetooth::emboss::IoCapability;
using sm::util::IOCapabilityForHci;
namespace {
const char* const kInspectEncryptionStatusPropertyName = "encryption_status";
const char* const kInspectSecurityPropertiesPropertyName =
"security_properties";
} // namespace
PairingState::PairingState(Peer::WeakPtr peer,
hci::BrEdrConnection* link,
bool link_initiated,
fit::closure auth_cb,
StatusCallback status_cb)
: peer_id_(peer->identifier()),
peer_(std::move(peer)),
link_(link),
outgoing_connection_(link_initiated),
peer_missing_key_(false),
state_(State::kIdle),
send_auth_request_callback_(std::move(auth_cb)),
status_callback_(std::move(status_cb)) {
BT_ASSERT(link_);
BT_ASSERT(send_auth_request_callback_);
BT_ASSERT(status_callback_);
link_->set_encryption_change_callback(
fit::bind_member<&PairingState::OnEncryptionChange>(this));
cleanup_cb_ = [](PairingState* self) {
self->link_->set_encryption_change_callback(nullptr);
auto callbacks_to_signal =
self->CompletePairingRequests(ToResult(HostError::kLinkDisconnected));
bt_log(TRACE,
"gap-bredr",
"Signaling %zu unresolved pairing listeners for %#.4x",
callbacks_to_signal.size(),
self->handle());
for (auto& cb : callbacks_to_signal) {
cb();
}
};
}
PairingState::~PairingState() {
if (cleanup_cb_) {
cleanup_cb_(this);
}
}
void PairingState::InitiatePairing(
BrEdrSecurityRequirements security_requirements, StatusCallback status_cb) {
// TODO(https://fxbug.dev/42082728): Reject pairing if peer/local device don't
// support Secure Connections and SC is required
if (state() == State::kIdle) {
BT_ASSERT(!is_pairing());
// If the current link key already meets the security requirements, skip
// pairing and report success.
if (link_->ltk_type() && SecurityPropertiesMeetRequirements(
sm::SecurityProperties(*link_->ltk_type()),
security_requirements)) {
status_cb(handle(), fit::ok());
return;
}
// TODO(https://fxbug.dev/42118593): If there is no pairing delegate set AND
// the current peer does not have a bonded link key, there is no way to
// upgrade the link security, so we don't need to bother calling
// `send_auth_request`.
//
// TODO(https://fxbug.dev/42133435): If current IO capabilities would make
// meeting security requirements impossible, skip pairing and report failure
// immediately.
current_pairing_ =
Pairing::MakeInitiator(security_requirements, outgoing_connection_);
PairingRequest request{.security_requirements = security_requirements,
.status_callback = std::move(status_cb)};
request_queue_.push_back(std::move(request));
bt_log(DEBUG,
"gap-bredr",
"Initiating pairing on %#.4x (id %s)",
handle(),
bt_str(peer_id()));
state_ = State::kInitiatorWaitLinkKeyRequest;
send_auth_request_callback_();
return;
}
// More than one consumer may wish to initiate pairing (e.g. concurrent
// outbound L2CAP channels), but each should wait for the results of any
// ongoing pairing procedure instead of sending their own Authentication
// Request.
if (is_pairing()) {
BT_ASSERT(state() != State::kIdle);
bt_log(INFO,
"gap-bredr",
"Already pairing %#.4x (id: %s); blocking callback on completion",
handle(),
bt_str(peer_id()));
PairingRequest request{.security_requirements = security_requirements,
.status_callback = std::move(status_cb)};
request_queue_.push_back(std::move(request));
} else {
// In the error state, we should expect no pairing to be created and cancel
// this particular request immediately.
BT_ASSERT(state() == State::kFailed);
status_cb(handle(), ToResult(HostError::kCanceled));
}
}
void PairingState::InitiateNextPairingRequest() {
BT_ASSERT(state() == State::kIdle);
BT_ASSERT(!is_pairing());
if (request_queue_.empty()) {
return;
}
PairingRequest& request = request_queue_.front();
current_pairing_ = Pairing::MakeInitiator(request.security_requirements,
outgoing_connection_);
bt_log(DEBUG,
"gap-bredr",
"Initiating queued pairing on %#.4x (id %s)",
handle(),
bt_str(peer_id()));
state_ = State::kInitiatorWaitLinkKeyRequest;
send_auth_request_callback_();
}
std::optional<IoCapability> PairingState::OnIoCapabilityRequest() {
if (state() != State::kInitiatorWaitIoCapRequest &&
state() != State::kResponderWaitIoCapRequest) {
FailWithUnexpectedEvent(__func__);
return std::nullopt;
}
// Log an error and return std::nullopt if we can't respond to a pairing
// request because there's no pairing delegate. This corresponds to the
// non-bondable state as outlined in spec v5.2 Vol. 3 Part C 4.3.1.
if (!pairing_delegate().is_alive()) {
bt_log(WARN,
"gap-bredr",
"No pairing delegate set; not pairing link %#.4x (peer: %s)",
handle(),
bt_str(peer_id()));
// We set the state_ to Idle instead of Failed because it is possible that a
// PairingDelegate will be set before the next pairing attempt, allowing it
// to succeed.
state_ = State::kIdle;
SignalStatus(ToResult(HostError::kNotReady), __func__);
return std::nullopt;
}
current_pairing_->local_iocap =
sm::util::IOCapabilityForHci(pairing_delegate()->io_capability());
if (state() == State::kInitiatorWaitIoCapRequest) {
BT_ASSERT(initiator());
state_ = State::kInitiatorWaitIoCapResponse;
} else {
BT_ASSERT(is_pairing());
BT_ASSERT(!initiator());
current_pairing_->ComputePairingData();
state_ = GetStateForPairingEvent(current_pairing_->expected_event);
}
return current_pairing_->local_iocap;
}
void PairingState::OnIoCapabilityResponse(IoCapability peer_iocap) {
// If we preivously provided a key for peer to pair, but that didn't work,
// they may try to re-pair. Cancel the previous pairing if they try to
// restart.
if (state() == State::kWaitEncryption) {
BT_ASSERT(is_pairing());
current_pairing_ = nullptr;
state_ = State::kIdle;
}
if (state() == State::kIdle) {
BT_ASSERT(!is_pairing());
current_pairing_ = Pairing::MakeResponder(peer_iocap, outgoing_connection_);
// Defer gathering local IO Capability until OnIoCapabilityRequest, where
// the pairing can be rejected if there's no pairing delegate.
state_ = State::kResponderWaitIoCapRequest;
} else if (state() == State::kInitiatorWaitIoCapResponse) {
BT_ASSERT(initiator());
current_pairing_->peer_iocap = peer_iocap;
current_pairing_->ComputePairingData();
state_ = GetStateForPairingEvent(current_pairing_->expected_event);
} else {
FailWithUnexpectedEvent(__func__);
}
}
void PairingState::OnUserConfirmationRequest(uint32_t numeric_value,
UserConfirmationCallback cb) {
if (state() != State::kWaitUserConfirmationRequest) {
FailWithUnexpectedEvent(__func__);
cb(false);
return;
}
BT_ASSERT(is_pairing());
// TODO(https://fxbug.dev/42113087): Reject pairing if pairing delegate went
// away.
BT_ASSERT(pairing_delegate().is_alive());
state_ = State::kWaitPairingComplete;
if (current_pairing_->action == PairingAction::kAutomatic) {
if (!outgoing_connection_) {
bt_log(ERROR,
"gap-bredr",
"automatically rejecting incoming link pairing (peer: %s, handle: "
"%#.4x)",
bt_str(peer_id()),
handle());
} else {
bt_log(DEBUG,
"gap-bredr",
"automatically confirming outgoing link pairing (peer: %s, "
"handle: %#.4x)",
bt_str(peer_id()),
handle());
}
cb(outgoing_connection_);
return;
}
auto confirm_cb = [cb = std::move(cb),
pairing = current_pairing_->GetWeakPtr(),
peer_id = peer_id(),
handle = handle()](bool confirm) mutable {
if (!pairing.is_alive()) {
return;
}
bt_log(DEBUG,
"gap-bredr",
"%sing User Confirmation Request (peer: %s, handle: %#.4x)",
confirm ? "Confirm" : "Cancel",
bt_str(peer_id),
handle);
cb(confirm);
};
// PairingAction::kDisplayPasskey indicates that this device has a display and
// performs "Numeric Comparison with automatic confirmation" but
// auto-confirmation is delegated to PairingDelegate.
if (current_pairing_->action == PairingAction::kDisplayPasskey ||
current_pairing_->action == PairingAction::kComparePasskey) {
pairing_delegate()->DisplayPasskey(
peer_id(),
numeric_value,
PairingDelegate::DisplayMethod::kComparison,
std::move(confirm_cb));
} else if (current_pairing_->action == PairingAction::kGetConsent) {
pairing_delegate()->ConfirmPairing(peer_id(), std::move(confirm_cb));
} else {
BT_PANIC("%#.4x (id: %s): unexpected action %d",
handle(),
bt_str(peer_id()),
static_cast<int>(current_pairing_->action));
}
}
void PairingState::OnUserPasskeyRequest(UserPasskeyCallback cb) {
if (state() != State::kWaitUserPasskeyRequest) {
FailWithUnexpectedEvent(__func__);
cb(std::nullopt);
return;
}
BT_ASSERT(is_pairing());
// TODO(https://fxbug.dev/42113087): Reject pairing if pairing delegate went
// away.
BT_ASSERT(pairing_delegate().is_alive());
state_ = State::kWaitPairingComplete;
BT_ASSERT_MSG(current_pairing_->action == PairingAction::kRequestPasskey,
"%#.4x (id: %s): unexpected action %d",
handle(),
bt_str(peer_id()),
static_cast<int>(current_pairing_->action));
auto pairing = current_pairing_->GetWeakPtr();
auto passkey_cb =
[this, cb = std::move(cb), pairing](int64_t passkey) mutable {
if (!pairing.is_alive()) {
return;
}
bt_log(DEBUG,
"gap-bredr",
"%#.4x (id: %s): Replying %" PRId64 " to User Passkey Request",
handle(),
bt_str(peer_id()),
passkey);
if (passkey >= 0) {
cb(static_cast<uint32_t>(passkey));
} else {
cb(std::nullopt);
}
};
pairing_delegate()->RequestPasskey(peer_id(), std::move(passkey_cb));
}
void PairingState::OnUserPasskeyNotification(uint32_t numeric_value) {
if (state() != State::kWaitUserPasskeyNotification) {
FailWithUnexpectedEvent(__func__);
return;
}
BT_ASSERT(is_pairing());
// TODO(https://fxbug.dev/42113087): Reject pairing if pairing delegate went
// away.
BT_ASSERT(pairing_delegate().is_alive());
state_ = State::kWaitPairingComplete;
auto pairing = current_pairing_->GetWeakPtr();
auto confirm_cb = [this, pairing](bool confirm) {
if (!pairing.is_alive()) {
return;
}
bt_log(DEBUG,
"gap-bredr",
"%#.4x (id: %s): Can't %s pairing from Passkey Notification side",
handle(),
bt_str(peer_id()),
confirm ? "confirm" : "cancel");
};
pairing_delegate()->DisplayPasskey(peer_id(),
numeric_value,
PairingDelegate::DisplayMethod::kPeerEntry,
std::move(confirm_cb));
}
void PairingState::OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode status_code) {
// The pairing process may fail early, which the controller will deliver as an
// Simple Pairing Complete with a non-success status. Log and proxy the error
// code.
if (const fit::result result = ToResult(status_code);
is_pairing() && bt_is_error(result,
INFO,
"gap-bredr",
"Pairing failed on link %#.4x (id: %s)",
handle(),
bt_str(peer_id()))) {
// TODO(https://fxbug.dev/42113087): Checking pairing_delegate() for reset
// like this isn't thread safe.
if (pairing_delegate().is_alive()) {
pairing_delegate()->CompletePairing(peer_id(),
ToResult(HostError::kFailed));
}
state_ = State::kFailed;
SignalStatus(result, __func__);
return;
}
// Handle successful Authentication Complete events that are not expected.
if (state() != State::kWaitPairingComplete) {
FailWithUnexpectedEvent(__func__);
return;
}
BT_ASSERT(is_pairing());
pairing_delegate()->CompletePairing(peer_id(), fit::ok());
state_ = State::kWaitLinkKey;
}
std::optional<hci_spec::LinkKey> PairingState::OnLinkKeyRequest() {
if (state() != State::kIdle &&
state() != State::kInitiatorWaitLinkKeyRequest) {
FailWithUnexpectedEvent(__func__);
return std::nullopt;
}
BT_ASSERT(peer_.is_alive());
std::optional<sm::LTK> link_key;
if (peer_missing_key_) {
bt_log(INFO,
"gap-bredr",
"peer %s missing key, ignoring our key",
bt_str(peer_->identifier()));
} else if (peer_->bredr() && peer_->bredr()->bonded()) {
bt_log(INFO,
"gap-bredr",
"recalling link key for bonded peer %s",
bt_str(peer_->identifier()));
BT_ASSERT(peer_->bredr()->link_key().has_value());
link_key = peer_->bredr()->link_key();
BT_ASSERT(link_key->security().enc_key_size() ==
hci_spec::kBrEdrLinkKeySize);
const auto link_key_type = link_key->security().GetLinkKeyType();
BT_ASSERT(link_key_type.has_value());
link_->set_link_key(link_key->key(), link_key_type.value());
} else {
bt_log(
INFO, "gap-bredr", "peer %s not bonded", bt_str(peer_->identifier()));
}
// The link key request may be received outside of Simple Pairing (e.g. when
// the peer initiates the authentication procedure).
if (state() == State::kIdle) {
if (link_key.has_value()) {
BT_ASSERT(!is_pairing());
current_pairing_ = Pairing::MakeResponderForBonded();
state_ = State::kWaitEncryption;
return link_key->key();
}
return std::optional<hci_spec::LinkKey>();
}
BT_ASSERT(is_pairing());
if (link_key.has_value() &&
SecurityPropertiesMeetRequirements(
link_key->security(), current_pairing_->preferred_security)) {
// Skip Simple Pairing and just perform authentication with existing key.
state_ = State::kInitiatorWaitAuthComplete;
return link_key->key();
}
// Request that the controller perform Simple Pairing to generate a new key.
state_ = State::kInitiatorWaitIoCapRequest;
return std::nullopt;
}
void PairingState::OnLinkKeyNotification(
const UInt128& link_key,
hci_spec::LinkKeyType key_type,
bool local_secure_connections_supported) {
// TODO(https://fxbug.dev/42111880): We assume the controller is never in
// pairing debug mode because it's a security hazard to pair and bond using
// Debug Combination link keys.
BT_ASSERT_MSG(key_type != hci_spec::LinkKeyType::kDebugCombination,
"Pairing on link %#.4x (id: %s) resulted in insecure Debug "
"Combination link key",
handle(),
bt_str(peer_id()));
// When not pairing, only connection link key changes are allowed.
if (state() == State::kIdle &&
key_type == hci_spec::LinkKeyType::kChangedCombination) {
if (!link_->ltk()) {
bt_log(WARN,
"gap-bredr",
"Got Changed Combination key but link %#.4x (id: %s) has no "
"current key",
handle(),
bt_str(peer_id()));
state_ = State::kFailed;
SignalStatus(ToResult(HostError::kInsufficientSecurity),
"OnLinkKeyNotification with no current key");
return;
}
bt_log(DEBUG,
"gap-bredr",
"Changing link key on %#.4x (id: %s)",
handle(),
bt_str(peer_id()));
link_->set_link_key(hci_spec::LinkKey(link_key, 0, 0), key_type);
return;
}
if (state() != State::kWaitLinkKey) {
FailWithUnexpectedEvent(__func__);
return;
}
// The association model and resulting link security properties are computed
// by both the Link Manager (controller) and the host subsystem, so check that
// they agree.
BT_ASSERT(is_pairing());
sm::SecurityProperties sec_props = sm::SecurityProperties(key_type);
current_pairing_->security_properties = sec_props;
// Link keys resulting from legacy pairing are assigned lowest security level
// and we reject them.
if (sec_props.level() == sm::SecurityLevel::kNoSecurity) {
bt_log(WARN,
"gap-bredr",
"Link key (type %hhu) for %#.4x (id: %s) has insufficient security",
static_cast<unsigned char>(key_type),
handle(),
bt_str(peer_id()));
state_ = State::kFailed;
SignalStatus(ToResult(HostError::kInsufficientSecurity),
"OnLinkKeyNotification with insufficient security");
return;
}
// If we performed an association procedure for MITM protection then expect
// the controller to produce a corresponding "authenticated" link key.
// Inversely, do not accept a link key reported as authenticated if we haven't
// performed the corresponding association procedure because it may provide a
// false high expectation of security to the user or application.
if (sec_props.authenticated() != current_pairing_->authenticated) {
bt_log(WARN,
"gap-bredr",
"Expected %sauthenticated link key for %#.4x (id: %s), got %hhu",
current_pairing_->authenticated ? "" : "un",
handle(),
bt_str(peer_id()),
static_cast<unsigned char>(key_type));
state_ = State::kFailed;
SignalStatus(ToResult(HostError::kInsufficientSecurity),
"OnLinkKeyNotification with incorrect link authorization");
return;
}
// Set Security Properties for this BR/EDR connection
set_security_properties(sec_props);
// TODO(https://fxbug.dev/42082735): When in SC Only mode, all services
// require security mode 4, level 4
if (security_mode() == BrEdrSecurityMode::SecureConnectionsOnly &&
security_properties().level() !=
sm::SecurityLevel::kSecureAuthenticated) {
bt_log(WARN,
"gap-bredr",
"BR/EDR link key has insufficient security for Secure Connections "
"Only mode");
state_ = State::kFailed;
SignalStatus(ToResult(HostError::kInsufficientSecurity),
"OnLinkKeyNotification requires Secure Connections");
return;
}
// If peer and local Secure Connections support are present, the pairing logic
// needs to verify that the Link Key Type received in the Link Key
// Notification event is one of the Secure Connections types (0x07 and 0x08).
//
// Core Spec v5.2 Vol 4, Part E, 7.7.24: The values 0x07 and 0x08 shall only
// be used when the Host has indicated support for Secure Connections in the
// Secure_Connections_Host_Support parameter.
if (IsPeerSecureConnectionsSupported() &&
local_secure_connections_supported) {
if (!security_properties().secure_connections()) {
bt_log(WARN,
"gap-bredr",
"Link Key Type must be a Secure Connections key type;"
" Received type: %hhu (handle: %#.4x, id: %s)",
static_cast<unsigned char>(key_type),
handle(),
bt_str(peer_id()));
state_ = State::kFailed;
SignalStatus(ToResult(HostError::kInsufficientSecurity),
"OnLinkKeyNotification requires Secure Connections");
return;
}
link_->set_use_secure_connections(true);
}
link_->set_link_key(hci_spec::LinkKey(link_key, 0, 0), key_type);
if (initiator()) {
state_ = State::kInitiatorWaitAuthComplete;
} else {
EnableEncryption();
}
}
void PairingState::OnAuthenticationComplete(
pw::bluetooth::emboss::StatusCode status_code) {
if (is_pairing() && peer_->bredr() && peer_->bredr()->bonded() &&
status_code == pw::bluetooth::emboss::StatusCode::PIN_OR_KEY_MISSING) {
// We have provided our link key, but the remote side says they don't have a
// key. Pretend we don't have a link key, then start the pairing over. We
// will get consent even if we are otherwise kAutomatic
bt_log(
INFO,
"gap-bredr",
"Re-initiating pairing on %#.4x (id %s) as remote side reports no key.",
handle(),
bt_str(peer_id()));
peer_missing_key_ = true;
current_pairing_->allow_automatic = false;
state_ = State::kInitiatorWaitLinkKeyRequest;
send_auth_request_callback_();
return;
}
// The pairing process may fail early, which the controller will deliver as an
// Authentication Complete with a non-success status. Log and proxy the error
// code.
if (const fit::result result = ToResult(status_code);
bt_is_error(result,
INFO,
"gap-bredr",
"Authentication failed on link %#.4x (id: %s)",
handle(),
bt_str(peer_id()))) {
state_ = State::kFailed;
SignalStatus(result, __func__);
return;
}
// Handle successful Authentication Complete events that are not expected.
if (state() != State::kInitiatorWaitAuthComplete) {
FailWithUnexpectedEvent(__func__);
return;
}
BT_ASSERT(initiator());
EnableEncryption();
}
void PairingState::OnEncryptionChange(hci::Result<bool> result) {
// Update inspect properties
pw::bluetooth::emboss::EncryptionStatus encryption_status =
link_->encryption_status();
inspect_properties_.encryption_status.Set(
EncryptionStatusToString(encryption_status));
if (state() != State::kWaitEncryption) {
// Ignore encryption changes when not expecting them because they may be
// triggered by the peer at any time (v5.0 Vol 2, Part F, Sec 4.4).
bt_log(TRACE,
"gap-bredr",
"%#.4x (id: %s): %s(%s, %s) in state \"%s\"; taking no action",
handle(),
bt_str(peer_id()),
__func__,
bt_str(result),
result.is_ok() ? (result.value() ? "true" : "false") : "?",
ToString(state()));
return;
}
if (result.is_ok() && !result.value()) {
// With Secure Connections, encryption should never be disabled (v5.0 Vol 2,
// Part E, Sec 7.1.16) at all.
bt_log(WARN,
"gap-bredr",
"Pairing failed due to encryption disable on link %#.4x (id: %s)",
handle(),
bt_str(peer_id()));
result = fit::error(Error(HostError::kFailed));
}
// Perform state transition.
if (result.is_ok()) {
// Reset state for another pairing.
state_ = State::kIdle;
} else {
state_ = State::kFailed;
}
SignalStatus(result.is_ok() ? hci::Result<>(fit::ok()) : result.take_error(),
__func__);
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeInitiator(
BrEdrSecurityRequirements security_requirements, bool link_initiated) {
// Private ctor is inaccessible to std::make_unique.
std::unique_ptr<Pairing> pairing(new Pairing(link_initiated));
pairing->initiator = true;
pairing->preferred_security = security_requirements;
return pairing;
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeResponder(
pw::bluetooth::emboss::IoCapability peer_iocap, bool link_initiated) {
// Private ctor is inaccessible to std::make_unique.
std::unique_ptr<Pairing> pairing(new Pairing(link_initiated));
pairing->initiator = false;
pairing->peer_iocap = peer_iocap;
// Don't try to upgrade security as responder.
pairing->preferred_security = {.authentication = false,
.secure_connections = false};
return pairing;
}
std::unique_ptr<PairingState::Pairing>
PairingState::Pairing::MakeResponderForBonded() {
std::unique_ptr<Pairing> pairing(new Pairing(/* link initiated */ false));
pairing->initiator = false;
// Don't try to upgrade security as responder.
pairing->preferred_security = {.authentication = false,
.secure_connections = false};
return pairing;
}
void PairingState::Pairing::ComputePairingData() {
if (initiator) {
action = GetInitiatorPairingAction(local_iocap, peer_iocap);
} else {
action = GetResponderPairingAction(peer_iocap, local_iocap);
}
if (!allow_automatic && action == PairingAction::kAutomatic) {
action = PairingAction::kGetConsent;
}
expected_event = GetExpectedEvent(local_iocap, peer_iocap);
BT_DEBUG_ASSERT(GetStateForPairingEvent(expected_event) != State::kFailed);
authenticated = IsPairingAuthenticated(local_iocap, peer_iocap);
bt_log(DEBUG,
"gap-bredr",
"As %s with local %hhu/peer %hhu capabilities, expecting an "
"%sauthenticated %u pairing "
"using %#x%s",
initiator ? "initiator" : "responder",
static_cast<unsigned char>(local_iocap),
static_cast<unsigned char>(peer_iocap),
authenticated ? "" : "un",
static_cast<unsigned int>(action),
expected_event,
allow_automatic ? "" : " (auto not allowed)");
}
const char* PairingState::ToString(PairingState::State state) {
switch (state) {
case State::kIdle:
return "Idle";
case State::kInitiatorWaitLinkKeyRequest:
return "InitiatorWaitLinkKeyRequest";
case State::kInitiatorWaitIoCapRequest:
return "InitiatorWaitIoCapRequest";
case State::kInitiatorWaitIoCapResponse:
return "InitiatorWaitIoCapResponse";
case State::kResponderWaitIoCapRequest:
return "ResponderWaitIoCapRequest";
case State::kWaitUserConfirmationRequest:
return "WaitUserConfirmationRequest";
case State::kWaitUserPasskeyRequest:
return "WaitUserPasskeyRequest";
case State::kWaitUserPasskeyNotification:
return "WaitUserPasskeyNotification";
case State::kWaitPairingComplete:
return "WaitPairingComplete";
case State::kWaitLinkKey:
return "WaitLinkKey";
case State::kInitiatorWaitAuthComplete:
return "InitiatorWaitAuthComplete";
case State::kWaitEncryption:
return "WaitEncryption";
case State::kFailed:
return "Failed";
default:
break;
}
return "";
}
PairingState::State PairingState::GetStateForPairingEvent(
hci_spec::EventCode event_code) {
switch (event_code) {
case hci_spec::kUserConfirmationRequestEventCode:
return State::kWaitUserConfirmationRequest;
case hci_spec::kUserPasskeyRequestEventCode:
return State::kWaitUserPasskeyRequest;
case hci_spec::kUserPasskeyNotificationEventCode:
return State::kWaitUserPasskeyNotification;
default:
break;
}
return State::kFailed;
}
void PairingState::SignalStatus(hci::Result<> status, const char* caller) {
bt_log(INFO,
"gap-bredr",
"Signaling pairing listeners for %#.4x (id: %s) from %s with %s",
handle(),
bt_str(peer_id()),
caller,
bt_str(status));
// Collect the callbacks before invoking them so that
// CompletePairingRequests() can safely access members.
auto callbacks_to_signal = CompletePairingRequests(status);
// This PairingState may be destroyed by these callbacks (e.g. if signaling an
// error causes a disconnection), so care must be taken not to access any
// members.
status_callback_(handle(), status);
for (auto& cb : callbacks_to_signal) {
cb();
}
}
std::vector<fit::closure> PairingState::CompletePairingRequests(
hci::Result<> status) {
std::vector<fit::closure> callbacks_to_signal;
if (!is_pairing()) {
BT_ASSERT(request_queue_.empty());
return callbacks_to_signal;
}
if (status.is_error()) {
// On pairing failure, signal all requests.
for (auto& request : request_queue_) {
callbacks_to_signal.push_back(
[handle = handle(),
status,
cb = std::move(request.status_callback)]() { cb(handle, status); });
}
request_queue_.clear();
current_pairing_ = nullptr;
return callbacks_to_signal;
}
BT_ASSERT(state_ == State::kIdle);
BT_ASSERT(link_->ltk_type().has_value());
auto security_properties = sm::SecurityProperties(link_->ltk_type().value());
// If a new link key was received, notify all callbacks because we always
// negotiate the best security possible. Even though pairing succeeded, send
// an error status if the individual request security requirements are not
// satisfied.
// TODO(https://fxbug.dev/42075714): Only notify failure to callbacks of
// requests that have the same (or none) MITM requirements as the current
// pairing.
bool link_key_received = current_pairing_->security_properties.has_value();
if (link_key_received) {
for (auto& request : request_queue_) {
auto sec_props_satisfied = SecurityPropertiesMeetRequirements(
security_properties, request.security_requirements);
auto request_status = sec_props_satisfied
? status
: ToResult(HostError::kInsufficientSecurity);
callbacks_to_signal.push_back(
[handle = handle(),
request_status,
cb = std::move(request.status_callback)]() {
cb(handle, request_status);
});
}
request_queue_.clear();
} else {
// If no new link key was received, then only authentication with an old key
// was performed (Simple Pairing was not required), and unsatisfied requests
// should initiate a new pairing rather than failing. If any pairing
// requests are satisfied by the existing key, notify them.
auto it = request_queue_.begin();
while (it != request_queue_.end()) {
if (!SecurityPropertiesMeetRequirements(security_properties,
it->security_requirements)) {
it++;
continue;
}
callbacks_to_signal.push_back(
[handle = handle(), status, cb = std::move(it->status_callback)]() {
cb(handle, status);
});
it = request_queue_.erase(it);
}
}
current_pairing_ = nullptr;
InitiateNextPairingRequest();
return callbacks_to_signal;
}
void PairingState::EnableEncryption() {
if (!link_->StartEncryption()) {
bt_log(ERROR,
"gap-bredr",
"%#.4x (id: %s): Failed to enable encryption (state \"%s\")",
handle(),
bt_str(peer_id()),
ToString(state()));
status_callback_(link_->handle(), ToResult(HostError::kFailed));
state_ = State::kFailed;
return;
}
state_ = State::kWaitEncryption;
}
void PairingState::FailWithUnexpectedEvent(const char* handler_name) {
bt_log(ERROR,
"gap-bredr",
"%#.4x (id: %s): Unexpected event %s while in state \"%s\"",
handle(),
bt_str(peer_id()),
handler_name,
ToString(state()));
state_ = State::kFailed;
SignalStatus(ToResult(HostError::kNotSupported), __func__);
}
bool PairingState::IsPeerSecureConnectionsSupported() const {
return peer_->features().HasBit(
/*page=*/1, hci_spec::LMPFeature::kSecureConnectionsHostSupport) &&
peer_->features().HasBit(
/*page=*/2,
hci_spec::LMPFeature::kSecureConnectionsControllerSupport);
}
void PairingState::AttachInspect(inspect::Node& parent, std::string name) {
inspect_node_ = parent.CreateChild(name);
inspect_properties_.encryption_status = inspect_node_.CreateString(
kInspectEncryptionStatusPropertyName,
EncryptionStatusToString(link_->encryption_status()));
security_properties().AttachInspect(inspect_node_,
kInspectSecurityPropertiesPropertyName);
}
PairingAction GetInitiatorPairingAction(IoCapability initiator_cap,
IoCapability responder_cap) {
if (initiator_cap == IoCapability::NO_INPUT_NO_OUTPUT) {
return PairingAction::kAutomatic;
}
if (responder_cap == IoCapability::NO_INPUT_NO_OUTPUT) {
if (initiator_cap == IoCapability::DISPLAY_YES_NO) {
return PairingAction::kGetConsent;
}
return PairingAction::kAutomatic;
}
if (initiator_cap == IoCapability::KEYBOARD_ONLY) {
return PairingAction::kRequestPasskey;
}
if (responder_cap == IoCapability::DISPLAY_ONLY) {
if (initiator_cap == IoCapability::DISPLAY_YES_NO) {
return PairingAction::kComparePasskey;
}
return PairingAction::kAutomatic;
}
return PairingAction::kDisplayPasskey;
}
PairingAction GetResponderPairingAction(IoCapability initiator_cap,
IoCapability responder_cap) {
if (initiator_cap == IoCapability::NO_INPUT_NO_OUTPUT &&
responder_cap == IoCapability::KEYBOARD_ONLY) {
return PairingAction::kGetConsent;
}
if (initiator_cap == IoCapability::DISPLAY_YES_NO &&
responder_cap == IoCapability::DISPLAY_YES_NO) {
return PairingAction::kComparePasskey;
}
return GetInitiatorPairingAction(responder_cap, initiator_cap);
}
hci_spec::EventCode GetExpectedEvent(IoCapability local_cap,
IoCapability peer_cap) {
if (local_cap == IoCapability::NO_INPUT_NO_OUTPUT ||
peer_cap == IoCapability::NO_INPUT_NO_OUTPUT) {
return hci_spec::kUserConfirmationRequestEventCode;
}
if (local_cap == IoCapability::KEYBOARD_ONLY) {
return hci_spec::kUserPasskeyRequestEventCode;
}
if (peer_cap == IoCapability::KEYBOARD_ONLY) {
return hci_spec::kUserPasskeyNotificationEventCode;
}
return hci_spec::kUserConfirmationRequestEventCode;
}
bool IsPairingAuthenticated(IoCapability local_cap, IoCapability peer_cap) {
if (local_cap == IoCapability::NO_INPUT_NO_OUTPUT ||
peer_cap == IoCapability::NO_INPUT_NO_OUTPUT) {
return false;
}
if (local_cap == IoCapability::DISPLAY_YES_NO &&
peer_cap == IoCapability::DISPLAY_YES_NO) {
return true;
}
if (local_cap == IoCapability::KEYBOARD_ONLY ||
peer_cap == IoCapability::KEYBOARD_ONLY) {
return true;
}
return false;
}
AuthenticationRequirements GetInitiatorAuthenticationRequirements(
IoCapability local_cap) {
if (local_cap == IoCapability::NO_INPUT_NO_OUTPUT) {
return AuthenticationRequirements::GENERAL_BONDING;
}
return AuthenticationRequirements::MITM_GENERAL_BONDING;
}
AuthenticationRequirements GetResponderAuthenticationRequirements(
IoCapability local_cap, IoCapability peer_cap) {
if (IsPairingAuthenticated(local_cap, peer_cap)) {
return AuthenticationRequirements::MITM_GENERAL_BONDING;
}
return AuthenticationRequirements::GENERAL_BONDING;
}
} // namespace bt::gap