blob: 57b2921c44c9c02b7153b79050bb862d3da1f2cc [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/gap/pairing_state.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/util.h"
namespace bt {
namespace gap {
using hci::AuthRequirements;
using hci::IOCapability;
using sm::util::IOCapabilityForHci;
PairingState::PairingState(PeerId peer_id, hci::Connection* link, StatusCallback status_cb)
: peer_id_(peer_id), link_(link), state_(State::kIdle), status_callback_(std::move(status_cb)) {
ZX_ASSERT(link_);
ZX_ASSERT(link_->ll_type() != hci::Connection::LinkType::kLE);
ZX_ASSERT(status_callback_);
link_->set_encryption_change_callback(fit::bind_member(this, &PairingState::OnEncryptionChange));
cleanup_cb_ = [](PairingState* self) { self->link_->set_encryption_change_callback(nullptr); };
}
PairingState::~PairingState() {
if (cleanup_cb_) {
cleanup_cb_(this);
}
}
PairingState::InitiatorAction PairingState::InitiatePairing(StatusCallback status_cb) {
// Raise an error to only the initiator—and not others—if we can't pair because there's no pairing
// delegate.
if (!pairing_delegate()) {
bt_log(TRACE, "gap-bredr", "No pairing delegate for link %#.4x (id: %s); not pairing", handle(),
bt_str(peer_id()));
status_cb(handle(), hci::Status(HostError::kNotReady));
return InitiatorAction::kDoNotSendAuthenticationRequest;
}
if (state() == State::kIdle) {
ZX_ASSERT(!is_pairing());
current_pairing_ = Pairing::MakeInitiator(std::move(status_cb));
bt_log(TRACE, "gap-bredr", "Initiating pairing on %#.4x (id %s)", handle(), bt_str(peer_id()));
state_ = State::kInitiatorPairingStarted;
return InitiatorAction::kSendAuthenticationRequest;
}
// 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()) {
ZX_ASSERT(state() != State::kIdle);
bt_log(TRACE, "gap-bredr", "Already pairing %#.4x (id: %s); blocking callback on completion",
handle(), bt_str(peer_id()));
current_pairing_->initiator_callbacks.push_back(std::move(status_cb));
} else {
// In the error state, we should expect no pairing to be created and cancel this particular
// request immediately.
ZX_ASSERT(state() == State::kFailed);
status_cb(handle(), hci::Status(HostError::kCanceled));
}
return InitiatorAction::kDoNotSendAuthenticationRequest;
}
std::optional<IOCapability> PairingState::OnIoCapabilityRequest() {
if (state() == State::kInitiatorPairingStarted) {
ZX_ASSERT(initiator());
ZX_ASSERT_MSG(pairing_delegate(), "PairingDelegate was reset after pairing began");
// TODO(37447): PairingDelegate may be reset if bt-gap exits and clears PairingDelegate (which
// is processed on a different thread).
current_pairing_->local_iocap =
sm::util::IOCapabilityForHci(pairing_delegate()->io_capability());
state_ = State::kInitiatorWaitIoCapResponse;
} else if (state() == State::kResponderWaitIoCapRequest) {
ZX_ASSERT(is_pairing());
ZX_ASSERT(!initiator());
// Raise an error if we can't respond to a pairing request because there's no pairing delegate.
if (!pairing_delegate()) {
bt_log(ERROR, "gap-bredr", "No pairing delegate for link %#.4x (id: %s); not pairing",
handle(), bt_str(peer_id()));
state_ = State::kIdle;
SignalStatus(hci::Status(HostError::kNotReady));
return std::nullopt;
}
// TODO(37447): PairingDelegate may be reset if bt-gap exits and clears PairingDelegate (which
// is processed on a different thread).
current_pairing_->local_iocap =
sm::util::IOCapabilityForHci(pairing_delegate()->io_capability());
current_pairing_->ComputePairingData();
state_ = GetStateForPairingEvent(current_pairing_->expected_event);
} else {
FailWithUnexpectedEvent(__func__);
return std::nullopt;
}
return current_pairing_->local_iocap;
}
void PairingState::OnIoCapabilityResponse(IOCapability peer_iocap) {
if (state() == State::kIdle) {
ZX_ASSERT(!is_pairing());
current_pairing_ = Pairing::MakeResponder(peer_iocap);
// 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) {
ZX_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;
}
ZX_ASSERT(is_pairing());
// TODO(37447): Reject pairing if pairing delegate went away.
ZX_ASSERT(pairing_delegate());
state_ = State::kWaitPairingComplete;
// 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) {
auto pairing = current_pairing_->GetWeakPtr();
auto confirm_cb = [this, cb = std::move(cb), pairing](bool confirm) mutable {
if (!pairing) {
return;
}
bt_log(TRACE, "gap-bredr", "%#.4x (id: %s): %sing User Confirmation Request", handle(),
bt_str(peer_id()), confirm ? "Confirm" : "Cancel");
cb(confirm);
};
pairing_delegate()->DisplayPasskey(peer_id(), numeric_value,
PairingDelegate::DisplayMethod::kComparison,
std::move(confirm_cb));
} else if (current_pairing_->action == PairingAction::kGetConsent) {
auto pairing = current_pairing_->GetWeakPtr();
auto confirm_cb = [this, cb = std::move(cb), pairing](bool confirm) mutable {
if (!pairing) {
return;
}
bt_log(TRACE, "gap-bredr", "%#.4x (id: %s): %sing User Confirmation Request", handle(),
bt_str(peer_id()), confirm ? "Confirm" : "Cancel");
cb(confirm);
};
pairing_delegate()->ConfirmPairing(peer_id(), std::move(confirm_cb));
} else {
ZX_ASSERT_MSG(current_pairing_->action == PairingAction::kAutomatic,
"%#.4x (id: %s): unexpected action %d", handle(), bt_str(peer_id()),
current_pairing_->action);
bt_log(TRACE, "gap-bredr", "%#.4x (id: %s): automatically confirming User Confirmation Request",
handle(), bt_str(peer_id()));
cb(true);
}
}
void PairingState::OnUserPasskeyRequest(UserPasskeyCallback cb) {
if (state() != State::kWaitUserPasskeyRequest) {
FailWithUnexpectedEvent(__func__);
cb(std::nullopt);
return;
}
ZX_ASSERT(is_pairing());
// TODO(37447): Reject pairing if pairing delegate went away.
ZX_ASSERT(pairing_delegate());
state_ = State::kWaitPairingComplete;
ZX_ASSERT_MSG(current_pairing_->action == PairingAction::kRequestPasskey,
"%#.4x (id: %s): unexpected action %d", handle(), bt_str(peer_id()),
current_pairing_->action);
auto pairing = current_pairing_->GetWeakPtr();
auto passkey_cb = [this, cb = std::move(cb), pairing](int64_t passkey) mutable {
if (!pairing) {
return;
}
bt_log(TRACE, "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;
}
ZX_ASSERT(is_pairing());
// TODO(37447): Reject pairing if pairing delegate went away.
ZX_ASSERT(pairing_delegate());
state_ = State::kWaitPairingComplete;
auto pairing = current_pairing_->GetWeakPtr();
auto confirm_cb = [this, pairing](bool confirm) {
if (!pairing) {
return;
}
bt_log(TRACE, "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(hci::StatusCode status_code) {
if (state() != State::kWaitPairingComplete) {
FailWithUnexpectedEvent(__func__);
return;
}
ZX_ASSERT(is_pairing());
if (const hci::Status status(status_code);
bt_is_error(status, INFO, "gap-bredr", "Pairing failed on link %#.4x (id: %s)", handle(),
bt_str(peer_id()))) {
// TODO(37447): Checking pairing_delegate() for reset like this isn't thread safe.
if (pairing_delegate()) {
pairing_delegate()->CompletePairing(peer_id(), sm::Status(HostError::kFailed));
}
state_ = State::kFailed;
SignalStatus(status);
return;
}
pairing_delegate()->CompletePairing(peer_id(), sm::Status());
state_ = State::kWaitLinkKey;
}
void PairingState::OnLinkKeyNotification(const UInt128& link_key, hci::LinkKeyType key_type) {
// TODO(36360): 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.
ZX_ASSERT_MSG(key_type != hci::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::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(hci::Status(HostError::kInsufficientSecurity));
return;
}
bt_log(TRACE, "gap-bredr", "Changing link key on %#.4x (id: %s)", handle(), bt_str(peer_id()));
link_->set_bredr_link_key(hci::LinkKey(link_key, 0, 0), key_type);
return;
} else 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.
ZX_ASSERT(is_pairing());
const 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",
key_type, handle(), bt_str(peer_id()));
state_ = State::kFailed;
SignalStatus(hci::Status(HostError::kInsufficientSecurity));
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()), key_type);
state_ = State::kFailed;
SignalStatus(hci::Status(HostError::kInsufficientSecurity));
return;
}
link_->set_bredr_link_key(hci::LinkKey(link_key, 0, 0), key_type);
if (initiator()) {
state_ = State::kInitiatorWaitAuthComplete;
} else {
EnableEncryption();
}
}
void PairingState::OnAuthenticationComplete(hci::StatusCode status_code) {
if (state() != State::kInitiatorPairingStarted && state() != State::kInitiatorWaitAuthComplete) {
FailWithUnexpectedEvent(__func__);
return;
}
ZX_ASSERT(initiator());
if (const hci::Status status(status_code);
bt_is_error(status, INFO, "gap-bredr", "Authentication failed on link %#.4x (id: %s)",
handle(), bt_str(peer_id()))) {
state_ = State::kFailed;
SignalStatus(status);
return;
}
EnableEncryption();
}
void PairingState::OnEncryptionChange(hci::Status status, bool enabled) {
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(INFO, "gap-bredr",
"%#.4x (id: %s): Ignoring %s(%s, %s) in state \"%s\", before pairing completed",
handle(), bt_str(peer_id()), __func__, bt_str(status), enabled ? "true" : "false",
ToString(state()));
return;
}
if (status && !enabled) {
// 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()));
status = hci::Status(HostError::kFailed);
}
// Perform state transition.
if (status) {
// Reset state for another pairing.
state_ = State::kIdle;
} else {
state_ = State::kFailed;
}
SignalStatus(status);
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeInitiator(
StatusCallback status_callback) {
// Private ctor is inaccessible to std::make_unique.
std::unique_ptr<Pairing> pairing(new Pairing);
pairing->initiator = true;
pairing->initiator_callbacks.push_back(std::move(status_callback));
return pairing;
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeResponder(
hci::IOCapability peer_iocap) {
// Private ctor is inaccessible to std::make_unique.
std::unique_ptr<Pairing> pairing(new Pairing);
pairing->initiator = false;
pairing->peer_iocap = peer_iocap;
return pairing;
}
void PairingState::Pairing::ComputePairingData() {
if (initiator) {
action = GetInitiatorPairingAction(local_iocap, peer_iocap);
} else {
action = GetResponderPairingAction(peer_iocap, local_iocap);
}
expected_event = GetExpectedEvent(local_iocap, peer_iocap);
ZX_DEBUG_ASSERT(GetStateForPairingEvent(expected_event) != State::kFailed);
authenticated = IsPairingAuthenticated(local_iocap, peer_iocap);
bt_log(TRACE, "gap-bredr",
"As %s with local %hhu/peer %hhu capabilities, expecting an %sauthenticated %u pairing "
"using %#x",
initiator ? "initiator" : "responder", local_iocap, peer_iocap, authenticated ? "" : "un",
action, expected_event);
}
const char* PairingState::ToString(PairingState::State state) {
switch (state) {
case State::kIdle:
return "Idle";
case State::kInitiatorPairingStarted:
return "InitiatorPairingStarted";
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::EventCode event_code) {
switch (event_code) {
case hci::kUserConfirmationRequestEventCode:
return State::kWaitUserConfirmationRequest;
case hci::kUserPasskeyRequestEventCode:
return State::kWaitUserPasskeyRequest;
case hci::kUserPasskeyNotificationEventCode:
return State::kWaitUserPasskeyNotification;
default:
break;
}
return State::kFailed;
}
void PairingState::SignalStatus(hci::Status status) {
bt_log(SPEW, "gap-bredr", "Signaling pairing listeners for %#.4x (id: %s) with %s", handle(),
bt_str(peer_id()), bt_str(status));
std::vector<StatusCallback> callbacks_to_signal;
if (is_pairing()) {
std::swap(callbacks_to_signal, current_pairing_->initiator_callbacks);
current_pairing_ = nullptr;
}
// 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.
const auto handle = this->handle();
status_callback_(handle, status);
for (auto& cb : callbacks_to_signal) {
cb(handle, status);
}
}
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(), hci::Status(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(hci::Status(HostError::kNotSupported));
}
PairingAction GetInitiatorPairingAction(IOCapability initiator_cap, IOCapability responder_cap) {
if (initiator_cap == IOCapability::kNoInputNoOutput) {
return PairingAction::kAutomatic;
}
if (responder_cap == IOCapability::kNoInputNoOutput) {
if (initiator_cap == IOCapability::kDisplayYesNo) {
return PairingAction::kGetConsent;
}
return PairingAction::kAutomatic;
}
if (initiator_cap == IOCapability::kKeyboardOnly) {
return PairingAction::kRequestPasskey;
}
if (responder_cap == IOCapability::kDisplayOnly) {
if (initiator_cap == IOCapability::kDisplayYesNo) {
return PairingAction::kComparePasskey;
}
return PairingAction::kAutomatic;
}
return PairingAction::kDisplayPasskey;
}
PairingAction GetResponderPairingAction(IOCapability initiator_cap, IOCapability responder_cap) {
if (initiator_cap == IOCapability::kNoInputNoOutput &&
responder_cap == IOCapability::kKeyboardOnly) {
return PairingAction::kGetConsent;
}
if (initiator_cap == IOCapability::kDisplayYesNo &&
responder_cap == IOCapability::kDisplayYesNo) {
return PairingAction::kComparePasskey;
}
return GetInitiatorPairingAction(responder_cap, initiator_cap);
}
hci::EventCode GetExpectedEvent(IOCapability local_cap, IOCapability peer_cap) {
if (local_cap == IOCapability::kNoInputNoOutput || peer_cap == IOCapability::kNoInputNoOutput) {
return hci::kUserConfirmationRequestEventCode;
}
if (local_cap == IOCapability::kKeyboardOnly) {
return hci::kUserPasskeyRequestEventCode;
}
if (peer_cap == IOCapability::kKeyboardOnly) {
return hci::kUserPasskeyNotificationEventCode;
}
return hci::kUserConfirmationRequestEventCode;
}
bool IsPairingAuthenticated(IOCapability local_cap, IOCapability peer_cap) {
if (local_cap == IOCapability::kNoInputNoOutput || peer_cap == IOCapability::kNoInputNoOutput) {
return false;
}
if (local_cap == IOCapability::kDisplayYesNo && peer_cap == IOCapability::kDisplayYesNo) {
return true;
}
if (local_cap == IOCapability::kKeyboardOnly || peer_cap == IOCapability::kKeyboardOnly) {
return true;
}
return false;
}
AuthRequirements GetInitiatorAuthRequirements(IOCapability local_cap) {
if (local_cap == IOCapability::kNoInputNoOutput) {
return AuthRequirements::kGeneralBonding;
}
return AuthRequirements::kMITMGeneralBonding;
}
AuthRequirements GetResponderAuthRequirements(IOCapability local_cap, IOCapability peer_cap) {
if (IsPairingAuthenticated(local_cap, peer_cap)) {
return AuthRequirements::kMITMGeneralBonding;
}
return AuthRequirements::kGeneralBonding;
}
} // namespace gap
} // namespace bt