blob: 1e106569e2fd0ae79b61e24cd0b70de7ed28be3a [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.
#ifndef SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_GAP_PAIRING_STATE_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_GAP_PAIRING_STATE_H_
#include <list>
#include <optional>
#include <vector>
#include <fbl/macros.h>
#include "src/connectivity/bluetooth/core/bt-host/common/identifier.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/pairing_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/types.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/connection.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/smp.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/types.h"
namespace bt::gap {
// Represents the local user interaction that will occur, as inferred from Core
// Spec v5.0 Vol 3, Part C, Sec 5.2.2.6 (Table 5.7). This is not directly
// coupled to the reply action for the HCI "User" event for pairing; e.g.
// kDisplayPasskey may mean automatically confirming User Confirmation Request
// or displaying the value from User Passkey Notification.
enum class PairingAction {
// Don't involve the user.
kAutomatic,
// Request yes/no consent.
kGetConsent,
// Display 6-digit value with "cancel."
kDisplayPasskey,
// Display 6-digit value with "yes/no."
kComparePasskey,
// Request a 6-digit value entry.
kRequestPasskey,
};
// Tracks the pairing state of a peer's BR/EDR link. This drives HCI
// transactions and user interactions for pairing in order to obtain the highest
// possible level of link security given the capabilities of the controllers
// and hosts participating in the pairing.
//
// This implements Core Spec v5.0 Vol 2, Part F, Sec 4.2 through Sec 4.4, per
// logic requirements in Vol 3, Part C, Sec 5.2.2.
//
// This tracks both the bonded case (both hosts furnish their Link Keys to their
// controllers) and the unbonded case (both controllers perform Secure Simple
// Pairing and deliver the resulting Link Keys to their hosts).
//
// Pairing is considered complete when the Link Keys have been used to
// successfully encrypt the link, at which time pairing may be restarted (e.g.
// with different capabilities).
//
// This state machine navigates the following HCI message sequences, in which
// both the host subsystem and the Link Manager use knowledge of both peers' IO
// Capabilities and Authentication Requirements to decide on the same
// association model.
// ▶ means command.
// ◀ means event.
//
// Initiator flow
// --------------
// Authentication Requested▶
// ◀ Link Key Request
// Link Key Request Reply▶ (skip to "Authentication Complete")
// or
// Link Key Request Negative Reply▶ (continue with pairing)
// ◀ Command Complete
// ◀ IO Capability Request
// IO Capability Request Reply▶
// or
// IO Capability Request Negative Reply▶ (reject pairing)
// ◀ Command Complete
// ◀ IO Capability Response
// ◀ User Confirmation Request
// or
// ◀ User Passkey Request
// or
// ◀ User Passkey Notification
// or
// ◀ Remote OOB Data Request
// User Confirmation Request Reply▶
// or
// User Confirmation Request Negative Reply▶ (reject pairing)
// or
// User Passkey Request Reply▶
// or
// User Passkey Request Negative Reply▶ (reject pairing)
// or
// Remote OOB Data Request Reply▶
// or
// Remote OOB Extended Data Request Reply▶
// or
// Remote OOB Data Request Negative Reply▶ (reject pairing)
// ◀ Simple Pairing Complete (status may be error)
// ◀ Link Key Notification (key may be insufficient)
// ◀ Authentication Complete (status may be error)
// Set Connection Encryption▶
// ◀ Command Status
// ◀ Encryption Change (status may be error or encryption may be disabled)
//
// Responder flow
// --------------
// If initiator has key:
// ◀ Link Key Request
// Link Key Request Reply▶ (skip to "Encryption Change")
// or
// Link Key Request Negative Reply▶ (Authentication failed, skip pairing)
//
// If initiator doesn't have key:
// ◀ IO Capability Response
// ◀ IO Capability Request
// IO Capability Request Reply▶
// or
// IO Capability Request Negative Reply▶ (reject pairing)
// ◀ Command Complete
// Pairing
// ◀ User Confirmation Request
// or
// ◀ User Passkey Request
// or
// ◀ User Passkey Notification
// or
// ◀ Remote OOB Data Request
// User Confirmation Request Reply▶
// or
// User Confirmation Request Negative Reply▶ (reject pairing)
// or
// User Passkey Request Reply▶
// or
// User Passkey Request Negative Reply▶ (reject pairing)
// or
// Remote OOB Data Request Reply▶
// or
// Remote OOB Extended Data Request Reply▶
// or
// Remote OOB Data Request Negative Reply▶ (reject pairing)
// ◀ Simple Pairing Complete (status may contain error)
// ◀ Link Key Notification (key may be insufficient)
// Set Connection Encryption▶
// ◀ Command Status
// ◀ Encryption Change (status may be error or encryption may be disabled)
//
// This class is not thread-safe and should only be called on the thread on
// which it was created.
class PairingState final {
public:
// Used to report the status of each pairing procedure on this link. |status|
// will contain HostError::kNotSupported if the pairing procedure does not
// proceed in the order of events expected.
using StatusCallback = fit::function<void(hci::ConnectionHandle, hci::Status)>;
// Constructs a PairingState for the ACL connection |link| to |peer_id|. This
// object will receive its "encryption change" callbacks. Successful pairing
// is reported through |status_cb| after encryption is enabled. When errors
// occur, this object will be put in a "failed" state and the owner shall
// disconnect the link and destroy its PairingState. When destroyed, status
// callbacks for any waiting pairings are called. |status_cb| is not called
// on destruction.
//
// |auth_cb| will be called to indicate that the caller should send an Authentication Request for
// this peer.
//
// |link| must be valid for the lifetime of this object.
PairingState(PeerId peer_id, hci::Connection* link, PeerCache* peer_cache, fit::closure auth_cb,
StatusCallback status_cb);
PairingState(PairingState&&) = default;
PairingState& operator=(PairingState&&) = default;
~PairingState();
// True if there is currently a pairing procedure in progress that the local
// device initiated.
bool initiator() const { return is_pairing() ? current_pairing_->initiator : false; }
// Set a handler for user-interactive authentication challenges. If not set or
// set to nullptr, all pairing requests will be rejected, but this does not
// cause a fatal error and should not result in link disconnection.
//
// If the delegate indicates passkey display capabilities, then it will always
// be asked to confirm pairing, even when Core Spec v5.0, Vol 3, Part C,
// Section 5.2.2.6 indicates "automatic confirmation."
void SetPairingDelegate(fxl::WeakPtr<PairingDelegate> pairing_delegate) {
pairing_delegate_ = std::move(pairing_delegate);
}
// Starts pairing against the peer, if pairing is not already in progress.
// If not, this device becomes the pairing initiator. If pairing is in progress, the request will
// be queued until the current pairing completes or an additional pairing that upgrades the link
// key succeeds or fails.
//
// If no PairingDelegate is available, |status_cb| is immediately called with
// HostError::kNotReady, but the PairingState status callback (provided in the
// ctor) is not called.
//
// When pairing completes or errors out, the |status_cb| of each call to this
// function will be invoked with the result.
void InitiatePairing(BrEdrSecurityRequirements security_requirements, StatusCallback status_cb);
// Event handlers. Caller must ensure that the event is addressed to the link
// for this PairingState.
// Returns value for IO Capability Request Reply, else std::nullopt for IO
// Capability Negative Reply.
//
// TODO(fxbug.dev/601): Indicate presence of out-of-band (OOB) data.
[[nodiscard]] std::optional<hci::IOCapability> OnIoCapabilityRequest();
// Caller is not expected to send a response.
void OnIoCapabilityResponse(hci::IOCapability peer_iocap);
// |cb| is called with: true to send User Confirmation Request Reply, else
// for to send User Confirmation Request Negative Reply. It may be called from
// a different thread than the one that called OnUserConfirmationRequest.
using UserConfirmationCallback = fit::callback<void(bool confirm)>;
void OnUserConfirmationRequest(uint32_t numeric_value, UserConfirmationCallback cb);
// |cb| is called with: passkey value to send User Passkey Request Reply, else
// std::nullopt to send User Passkey Request Negative Reply. It may not be
// called from the same thread that called OnUserPasskeyRequest.
using UserPasskeyCallback = fit::callback<void(std::optional<uint32_t> passkey)>;
void OnUserPasskeyRequest(UserPasskeyCallback cb);
// Caller is not expected to send a response.
void OnUserPasskeyNotification(uint32_t numeric_value);
// Caller is not expected to send a response.
void OnSimplePairingComplete(hci::StatusCode status_code);
// Caller should send the returned link key in a Link Key Request Reply (or Link Key Request
// Negative Reply if the returned value is null).
[[nodiscard]] std::optional<hci::LinkKey> OnLinkKeyRequest(DeviceAddress address);
// Caller is not expected to send a response.
void OnLinkKeyNotification(const UInt128& link_key, hci::LinkKeyType key_type);
// Caller is not expected to send a response.
void OnAuthenticationComplete(hci::StatusCode status_code);
// Handler for hci::Connection::set_encryption_change_callback.
void OnEncryptionChange(hci::Status status, bool enabled);
private:
enum class State {
// Wait for initiator's IO Capability Response, Link Key Request, or for locally-initiated
// pairing.
kIdle,
// As initiator, wait for Link Key Request.
kInitiatorWaitLinkKeyRequest,
// As initiator, wait for IO Capability Request.
kInitiatorWaitIoCapRequest,
// As initiator, wait for IO Capability Response.
kInitiatorWaitIoCapResponse,
// As responder, wait for IO Capability Request.
kResponderWaitIoCapRequest,
// Wait for controller event for pairing action. Only one of these will occur in a given pairing
// (see class documentation for pairing flow).
kWaitUserConfirmationRequest,
kWaitUserPasskeyRequest,
kWaitUserPasskeyNotification,
// Wait for Simple Pairing Complete.
kWaitPairingComplete,
// Wait for Link Key Notification.
kWaitLinkKey,
// As initiator, wait for Authentication Complete.
kInitiatorWaitAuthComplete,
// Wait for Encryption Change.
kWaitEncryption,
// Error occurred; wait for link closure and ignore events.
kFailed,
};
// Extra information for pairing constructed when a pairing procedure begins and destroyed when
// the pairing procedure is reset or errors out.
//
// Instances must be heap allocated so that they can be moved without destruction, preserving
// their WeakPtr holders. WeakPtrs are vended to PairingDelegate callbacks to uniquely identify
// each attempt to pair because |current_pairing_| is not synchronized to the user's actions
// through PairingDelegate.
class Pairing final {
public:
static std::unique_ptr<Pairing> MakeInitiator(BrEdrSecurityRequirements security_requirements);
static std::unique_ptr<Pairing> MakeResponder(hci::IOCapability peer_iocap);
// For a Pairing whose |initiator|, |local_iocap|, and |peer_iocap| are already set, compute and
// set |action|, |expected_event|, |authenticated|, and |security_properties| for the pairing
// procedure and bonding data that we expect.
void ComputePairingData();
// Used to prevent PairingDelegate callbacks from using captured stale pointers.
fxl::WeakPtr<Pairing> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
// True if the local device initiated pairing.
bool initiator;
// IO Capability obtained from the pairing delegate.
hci::IOCapability local_iocap;
// IO Capability from peer through IO Capability Response.
hci::IOCapability peer_iocap;
// User interaction to perform after receiving HCI user event.
PairingAction action;
// HCI event to respond to in order to complete or reject pairing.
hci::EventCode expected_event;
// True if this pairing is expected to be resistant to MITM attacks.
bool authenticated;
// Security properties of the link key received from the controller.
std::optional<sm::SecurityProperties> security_properties;
// If the preferred security is greater than the existing link key, a new link key will be
// negotiated (which may still have insufficient security properties).
BrEdrSecurityRequirements preferred_security;
private:
Pairing() : weak_ptr_factory_(this) {}
fxl::WeakPtrFactory<Pairing> weak_ptr_factory_;
};
static const char* ToString(State state);
// Returns state for the three pairing action events, kFailed otherwise.
static State GetStateForPairingEvent(hci::EventCode event_code);
// Peer for this pairing.
PeerId peer_id() const { return peer_id_; }
State state() const { return state_; }
bool is_pairing() const { return current_pairing_ != nullptr; }
hci::ConnectionHandle handle() const { return link_->handle(); }
// Returns nullptr if the delegate is not set or no longer alive.
PairingDelegate* pairing_delegate() { return pairing_delegate_.get(); }
// Call the permanent status callback this object was created with as well as any completed
// request callbacks from local initiators. Resets the current pairing and may initiate a new
// pairing if any requests have not been completed.
void SignalStatus(hci::Status status);
// Determines which pairing requests have been completed by the current link key and/or status and
// removes them from the queue. If any pairing requests were not completed, starts a new pairing
// procedure. Returns a list of closures that call the status callbacks of completed pairing
// requests.
std::vector<fit::closure> CompletePairingRequests(hci::Status status);
// Starts the pairing procedure for the next queued pairing request, if any.
void InitiateNextPairingRequest();
// Called to enable encryption on the link for this peer. Sets |state_| to
// kWaitEncryption.
void EnableEncryption();
// Called when an event is received while in a state that doesn't expect that
// event. Invokes |status_callback_| with HostError::kNotSupported and sets
// |state_| to kFailed. Logs an error using |handler_name| for identification.
void FailWithUnexpectedEvent(const char* handler_name);
// Compute the expected pairing event and state to occur after receiving the peer IO Capability
// and write it to |current_pairing_| (which must exist).
void WritePairingData();
PeerId peer_id_;
// The BR/EDR link whose pairing is being driven by this object.
hci::Connection* link_;
// Used to restore link keys.
PeerCache* peer_cache_;
fxl::WeakPtr<PairingDelegate> pairing_delegate_;
// State machine representation.
State state_;
std::unique_ptr<Pairing> current_pairing_;
struct PairingRequest {
// Security properties required by the pairing initiator for pairing to be considered a success.
BrEdrSecurityRequirements security_requirements;
// Callback called when the pairing procedure is complete.
StatusCallback status_callback;
};
// Represents ongoing and queued pairing requests. Will contain a value when the state isn't
// kIdle or kFailed. Requests may be completed out-of-order as their security requirements are
// satisfied.
std::list<PairingRequest> request_queue_;
// Callback used to indicate an Authentication Request for this peer should be sent.
fit::closure send_auth_request_callback_;
// Callback that status of this pairing is reported back through.
StatusCallback status_callback_;
// Cleanup work that should occur only once per connection; uniqueness is guaranteed by being
// moved with PairingState. |self| shall be a pointer to the moved-to instance being cleaned up.
fit::callback<void(PairingState* self)> cleanup_cb_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(PairingState);
};
PairingAction GetInitiatorPairingAction(hci::IOCapability initiator_cap,
hci::IOCapability responder_cap);
PairingAction GetResponderPairingAction(hci::IOCapability initiator_cap,
hci::IOCapability responder_cap);
hci::EventCode GetExpectedEvent(hci::IOCapability local_cap, hci::IOCapability peer_cap);
bool IsPairingAuthenticated(hci::IOCapability local_cap, hci::IOCapability peer_cap);
// Get the Authentication Requirements for a locally-initiated pairing according
// to Core Spec v5.0, Vol 2, Part E, Sec 7.1.29.
//
// Non-Bondable Mode and Dedicated Bonding over BR/EDR are not supported and
// this always returns kMITMGeneralBonding if |local_cap| is not
// kNoInputNoOutput, kGeneralBonding otherwise. This requests authentication
// when possible (based on IO Capabilities), as we don't know the peer's
// authentication requirements yet.
hci::AuthRequirements GetInitiatorAuthRequirements(hci::IOCapability local_cap);
// Get the Authentication Requirements for a peer-initiated pairing. This will
// request MITM protection whenever possible to obtain an "authenticated" link
// encryption key.
//
// Local service requirements and peer authentication bonding type should be
// available by the time this is called, but Non-Bondable Mode and Dedicated
// Bonding over BR/EDR are not supported, so this always returns
// kMITMGeneralBonding if this pairing can result in an authenticated link key,
// kGeneralBonding otherwise.
hci::AuthRequirements GetResponderAuthRequirements(hci::IOCapability local_cap,
hci::IOCapability remote_cap);
} // namespace bt::gap
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_GAP_PAIRING_STATE_H_