| // 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 |