| // 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/hci-spec/constants.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/util.h" |
| #include "src/connectivity/bluetooth/core/bt-host/transport/transport.h" |
| |
| namespace bt::gap { |
| |
| using hci_spec::AuthRequirements; |
| using hci_spec::IOCapability; |
| using sm::util::IOCapabilityForHci; |
| |
| PairingState::PairingState(fxl::WeakPtr<Peer> 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)) { |
| ZX_ASSERT(link_); |
| ZX_ASSERT(send_auth_request_callback_); |
| ZX_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) { |
| if (state() == State::kIdle) { |
| ZX_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(), fitx::ok()); |
| return; |
| } |
| // TODO(fxbug.dev/42403): 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(fxbug.dev/55770): 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()) { |
| ZX_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. |
| ZX_ASSERT(state() == State::kFailed); |
| status_cb(handle(), ToResult(HostError::kCanceled)); |
| } |
| } |
| |
| void PairingState::InitiateNextPairingRequest() { |
| ZX_ASSERT(state() == State::kIdle); |
| ZX_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()) { |
| 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)); |
| return std::nullopt; |
| } |
| |
| current_pairing_->local_iocap = sm::util::IOCapabilityForHci(pairing_delegate()->io_capability()); |
| if (state() == State::kInitiatorWaitIoCapRequest) { |
| ZX_ASSERT(initiator()); |
| state_ = State::kInitiatorWaitIoCapResponse; |
| } else { |
| ZX_ASSERT(is_pairing()); |
| ZX_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) { |
| ZX_ASSERT(is_pairing()); |
| current_pairing_ = nullptr; |
| state_ = State::kIdle; |
| } |
| if (state() == State::kIdle) { |
| ZX_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) { |
| 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(fxbug.dev/37447): Reject pairing if pairing delegate went away. |
| ZX_ASSERT(pairing_delegate()); |
| state_ = State::kWaitPairingComplete; |
| bt_log_scope("%#.4x, id: %s", handle(), bt_str(peer_id())); |
| |
| if (current_pairing_->action == PairingAction::kAutomatic) { |
| if (!outgoing_connection_) { |
| bt_log(ERROR, "gap-bredr", "automatically rejecting incoming link pairing"); |
| } else { |
| bt_log(DEBUG, "gap-bredr", "automatically confirming outgoing link pairing"); |
| } |
| cb(outgoing_connection_); |
| return; |
| } |
| auto confirm_cb = [cb = std::move(cb), log_ctx = capture_log_context(), |
| pairing = current_pairing_->GetWeakPtr()](bool confirm) mutable { |
| if (!pairing) { |
| return; |
| } |
| add_parent_context(log_ctx); |
| bt_log(DEBUG, "gap-bredr", "%sing User Confirmation Request", confirm ? "Confirm" : "Cancel"); |
| 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 { |
| ZX_PANIC("%#.4x (id: %s): unexpected action %d", handle(), bt_str(peer_id()), |
| current_pairing_->action); |
| } |
| } |
| |
| void PairingState::OnUserPasskeyRequest(UserPasskeyCallback cb) { |
| if (state() != State::kWaitUserPasskeyRequest) { |
| FailWithUnexpectedEvent(__func__); |
| cb(std::nullopt); |
| return; |
| } |
| ZX_ASSERT(is_pairing()); |
| |
| // TODO(fxbug.dev/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(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; |
| } |
| ZX_ASSERT(is_pairing()); |
| |
| // TODO(fxbug.dev/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(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(hci_spec::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 fitx::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(fxbug.dev/37447): Checking pairing_delegate() for reset like this isn't thread safe. |
| if (pairing_delegate()) { |
| pairing_delegate()->CompletePairing(peer_id(), ToResult(HostError::kFailed)); |
| } |
| state_ = State::kFailed; |
| SignalStatus(result); |
| return; |
| } |
| |
| // Handle successful Authentication Complete events that are not expected. |
| if (state() != State::kWaitPairingComplete) { |
| FailWithUnexpectedEvent(__func__); |
| return; |
| } |
| ZX_ASSERT(is_pairing()); |
| |
| pairing_delegate()->CompletePairing(peer_id(), fitx::ok()); |
| state_ = State::kWaitLinkKey; |
| } |
| |
| std::optional<hci_spec::LinkKey> PairingState::OnLinkKeyRequest() { |
| if (state() != State::kIdle && state() != State::kInitiatorWaitLinkKeyRequest) { |
| FailWithUnexpectedEvent(__func__); |
| return std::nullopt; |
| } |
| |
| ZX_ASSERT(peer_); |
| |
| 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())); |
| |
| ZX_ASSERT(peer_->bredr()->link_key().has_value()); |
| link_key = peer_->bredr()->link_key(); |
| ZX_ASSERT(link_key->security().enc_key_size() == hci_spec::kBrEdrLinkKeySize); |
| |
| const auto link_key_type = link_key->security().GetLinkKeyType(); |
| ZX_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()) { |
| ZX_ASSERT(!is_pairing()); |
| current_pairing_ = Pairing::MakeResponderForBonded(); |
| state_ = State::kWaitEncryption; |
| return link_key->key(); |
| } |
| return std::optional<hci_spec::LinkKey>(); |
| } |
| |
| ZX_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) { |
| // TODO(fxbug.dev/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_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)); |
| 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. |
| 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(ToResult(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(ToResult(HostError::kInsufficientSecurity)); |
| return; |
| } |
| |
| link_->set_link_key(hci_spec::LinkKey(link_key, 0, 0), key_type); |
| if (initiator()) { |
| state_ = State::kInitiatorWaitAuthComplete; |
| } else { |
| EnableEncryption(); |
| } |
| } |
| |
| void PairingState::OnAuthenticationComplete(hci_spec::StatusCode status_code) { |
| if (is_pairing() && peer_->bredr() && peer_->bredr()->bonded() && |
| status_code == hci_spec::StatusCode::kPinOrKeyMissing) { |
| // 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 fitx::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); |
| return; |
| } |
| |
| // Handle successful Authentication Complete events that are not expected. |
| if (state() != State::kInitiatorWaitAuthComplete) { |
| FailWithUnexpectedEvent(__func__); |
| return; |
| } |
| ZX_ASSERT(initiator()); |
| EnableEncryption(); |
| } |
| |
| void PairingState::OnEncryptionChange(hci::Result<bool> result) { |
| 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 = fitx::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<>(fitx::ok()) : result.take_error()); |
| } |
| |
| 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( |
| hci_spec::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); |
| ZX_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", local_iocap, peer_iocap, authenticated ? "" : "un", |
| 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) { |
| bt_log(INFO, "gap-bredr", "Signaling pairing listeners for %#.4x (id: %s) with %s", handle(), |
| bt_str(peer_id()), 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()) { |
| ZX_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; |
| } |
| |
| ZX_ASSERT(state_ == State::kIdle); |
| ZX_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(fxbug.dev/1249): 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)); |
| } |
| |
| 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_spec::EventCode GetExpectedEvent(IOCapability local_cap, IOCapability peer_cap) { |
| if (local_cap == IOCapability::kNoInputNoOutput || peer_cap == IOCapability::kNoInputNoOutput) { |
| return hci_spec::kUserConfirmationRequestEventCode; |
| } |
| if (local_cap == IOCapability::kKeyboardOnly) { |
| return hci_spec::kUserPasskeyRequestEventCode; |
| } |
| if (peer_cap == IOCapability::kKeyboardOnly) { |
| return hci_spec::kUserPasskeyNotificationEventCode; |
| } |
| return hci_spec::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 bt::gap |