// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/internal/host/sm/security_manager.h" #include #include #include #include #include #include #include #include "lib/fit/function.h" #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" #include "pw_bluetooth_sapphire/internal/host/common/device_address.h" #include "pw_bluetooth_sapphire/internal/host/common/log.h" #include "pw_bluetooth_sapphire/internal/host/common/random.h" #include "pw_bluetooth_sapphire/internal/host/common/uint128.h" #include "pw_bluetooth_sapphire/internal/host/gap/gap.h" #include "pw_bluetooth_sapphire/internal/host/hci-spec/link_key.h" #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connection.h" #include "pw_bluetooth_sapphire/internal/host/sm/error.h" #include "pw_bluetooth_sapphire/internal/host/sm/packet.h" #include "pw_bluetooth_sapphire/internal/host/sm/pairing_phase.h" #include "pw_bluetooth_sapphire/internal/host/sm/phase_1.h" #include "pw_bluetooth_sapphire/internal/host/sm/phase_2_legacy.h" #include "pw_bluetooth_sapphire/internal/host/sm/phase_2_secure_connections.h" #include "pw_bluetooth_sapphire/internal/host/sm/phase_3.h" #include "pw_bluetooth_sapphire/internal/host/sm/security_request_phase.h" #include "pw_bluetooth_sapphire/internal/host/sm/smp.h" #include "pw_bluetooth_sapphire/internal/host/sm/types.h" #include "pw_bluetooth_sapphire/internal/host/sm/util.h" #include "pw_bluetooth_sapphire/internal/host/transport/error.h" namespace bt::sm { namespace { using PairingToken = gap::Peer::PairingToken; SecurityProperties FeaturesToProperties(const PairingFeatures& features) { return SecurityProperties(features.method == PairingMethod::kJustWorks ? SecurityLevel::kEncrypted : SecurityLevel::kAuthenticated, features.encryption_key_size, features.secure_connections); } } // namespace class SecurityManagerImpl final : public SecurityManager, public PairingPhase::Listener, public PairingChannel::Handler { public: ~SecurityManagerImpl() override; SecurityManagerImpl(hci::LowEnergyConnection::WeakPtr low_energy_link, hci::BrEdrConnection::WeakPtr bredr_link, l2cap::Channel::WeakPtr smp, IOCapability io_capability, Delegate::WeakPtr delegate, BondableMode bondable_mode, gap::LESecurityMode security_mode, bool is_controller_remote_public_key_validation_supported, pw::async::Dispatcher& dispatcher, bt::gap::Peer::WeakPtr peer); // SecurityManager overrides: void UpgradeSecurity(SecurityLevel level, PairingCallback callback) override; void InitiateBrEdrCrossTransportKeyDerivation( CrossTransportKeyDerivationResultCallback callback) override; void Reset(IOCapability io_capability) override; void Abort(ErrorCode ecode) override; private: // Represents a pending request to update the security level. struct PendingRequest { PendingRequest(SecurityLevel level_in, PairingCallback callback_in); PendingRequest(PendingRequest&&) = default; PendingRequest& operator=(PendingRequest&&) = default; SecurityLevel level; PairingCallback callback; }; // Pseudo-phase where we are waiting for BR/EDR pairing to complete. struct WaitForBrEdrPairing {}; // Pseudo-phase that indicates that encryption is being started while no other // phase is in progress. struct StartingEncryption {}; // Called when we receive a peer security request as initiator, will start // Phase 1. void OnSecurityRequest(AuthReqField auth_req); // Called when we receive a peer pairing request as responder, will start // Phase 1. void OnPairingRequest(const PairingRequestParams& req_params); // Pulls the next PendingRequest off |request_queue_| and starts a security // upgrade to that |level| by either sending a Pairing Request as initiator or // a Security Request as responder. void UpgradeSecurityInternal(); // Creates the pairing phase responsible for sending the security upgrade // request to the peer (a PairingRequest if we are initiator, otherwise a // SecurityRequest). Returns fit::error(ErrorCode:: // kAuthenticationRequirements) if the local IOCapabilities are insufficient // for SecurityLevel, otherwise returns fit::ok(). [[nodiscard]] fit::result RequestSecurityUpgrade( SecurityLevel level); // Called when the feature exchange (Phase 1) completes and the relevant // features of both sides have been resolved into `features`. `preq` and // `pres` need to be retained for cryptographic calculations in Phase 2. // Causes a state transition from Phase 1 to Phase 2 void OnFeatureExchange(PairingFeatures features, PairingRequestParams preq, PairingResponseParams pres); // Called when Phase 2 generates an encryption key, so the link can be // encrypted with it. void OnPhase2EncryptionKey(const UInt128& new_key); // Check if encryption using `current_ltk` will satisfy the current security // requirements. static bool CurrentLtkInsufficientlySecureForEncryption( std::optional current_ltk, SecurityRequestPhase* security_request_phase, gap::LESecurityMode mode); // Called when the encryption state of the LE link changes. void OnEncryptionChange(hci::Result enabled_result); // Called when the link is encrypted at the end of pairing Phase 2. void EndPhase2(); // Cleans up pairing state, updates the current security level, and notifies // parties that requested security of the link's updated security properties. void OnLowEnergyPairingComplete(PairingData data); // Derives LE LTK, notifies clients, and resets pairing state. void OnBrEdrPairingComplete(PairingData pairing_data); // After a call to UpgradeSecurity results in an increase of the link security // level (through pairing completion or SMP Security Requested encryption), // this method notifies all the callbacks associated with SecurityUpgrade // requests. void NotifySecurityCallbacks(); // Assign the current security properties and notify the delegate of the // change. void SetSecurityProperties(const SecurityProperties& sec); // Directly assigns the current |ltk_| and the underlying |le_link_|'s link // key. This function does not initiate link layer encryption and can be // called during and outside of pairing. void OnNewLongTermKey(const LTK& ltk); // PairingPhase::Listener overrides: void OnPairingFailed(Error error) override; std::optional OnIdentityRequest() override; void ConfirmPairing(ConfirmCallback confirm) override; void DisplayPasskey(uint32_t passkey, Delegate::DisplayMethod method, ConfirmCallback cb) override; void RequestPasskey(PasskeyResponseCallback respond) override; // PairingChannel::Handler overrides. SecurityManagerImpl is only the fallback // handler, meaning these methods are only called by PairingChannel when no // security upgrade is in progress: void OnRxBFrame(ByteBufferPtr sdu) override; void OnChannelClosed() override; // Starts the SMP timer. Stops and cancels any in-progress timers. void StartNewTimer(); // Stops and resets the SMP Pairing Timer. void StopTimer(); // Called when the pairing timer expires, forcing the pairing process to stop void OnPairingTimeout(); // Returns a std::pair. Will assert if // called outside active pairing or before Phase 1 is complete. std::pair LEPairingAddresses(); // Puts the class into a non-pairing state. void ResetState(); bool InPhase1() const { return std::holds_alternative>(current_phase_); } // Returns true if the pairing state machine is currently in Phase 2 of // pairing. bool InPhase2() const { return std::holds_alternative(current_phase_) || std::holds_alternative(current_phase_); } bool SecurityUpgradeInProgress() const { return !std::holds_alternative(current_phase_); } // Validates that both SM and the link have stored LTKs, and that these values // match. Disconnects the link if it finds an issue. Should only be called // when an LTK is expected to exist. Result<> ValidateExistingLocalLtk(); // Returns true only if all security conditions are met for BR/EDR CTKD. bool IsBrEdrCrossTransportKeyDerivationAllowed(); std::optional GetExistingLtkFromPeerCache(); // The role of the local device in pairing. // LE roles are fixed for the lifetime of a connection, but BR/EDR roles can // be changed after a connection is established, so we cannot cache it during // construction. Role role() { pw::bluetooth::emboss::ConnectionRole conn_role = pw::bluetooth::emboss::ConnectionRole::CENTRAL; if (low_energy_link_.is_alive()) { conn_role = low_energy_link_->role(); } else if (bredr_link_.is_alive()) { conn_role = bredr_link_->role(); } else { PW_CRASH("no active link"); } return conn_role == pw::bluetooth::emboss::ConnectionRole::CENTRAL ? Role::kInitiator : Role::kResponder; } pw::async::Dispatcher& pw_dispatcher_; // The ID that will be assigned to the next pairing operation. PairingProcedureId next_pairing_id_; // The higher-level class acting as a delegate for operations outside of SMP. Delegate::WeakPtr delegate_; // Data for the currently registered LE-U link, if any. hci::LowEnergyConnection::WeakPtr low_energy_link_; hci::BrEdrConnection::WeakPtr bredr_link_; // Whether the controller performs remote public key validation for BR/EDR // keys. bool is_controller_remote_public_key_validation_supported_ = false; // The IO capabilities of the device IOCapability low_energy_io_cap_; // The current LTK assigned to this connection. This can be assigned directly // by calling AssignLongTermKey() or as a result of a pairing procedure. std::optional ltk_; // If a pairing is in progress and Phase 1 (feature exchange) has completed, // this will store the result of that feature exchange. Otherwise, this will // be std::nullopt. std::optional features_; // The pending security requests added via UpgradeSecurity(). std::queue request_queue_; CrossTransportKeyDerivationResultCallback bredr_cross_transport_key_derivation_callback_ = nullptr; // Fixed SMP Channel used to send/receive packets std::unique_ptr sm_chan_; SmartTask timeout_task_{pw_dispatcher_}; // Set to a PairingToken when the current phase is not monostate (always null // in monostate). std::optional pairing_token_; bt::gap::Peer::WeakPtr peer_; // The presence of a particular phase in this variant indicates that a // security upgrade is in progress at the stored phase. No security upgrade is // in progress if std::monostate is present. std::variant, Phase2Legacy, Phase2SecureConnections, Phase3> current_phase_; WeakSelf weak_self_; WeakSelf weak_listener_; WeakSelf weak_handler_; BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(SecurityManagerImpl); }; SecurityManagerImpl::PendingRequest::PendingRequest(SecurityLevel level_in, PairingCallback callback_in) : level(level_in), callback(std::move(callback_in)) {} SecurityManagerImpl::~SecurityManagerImpl() { if (low_energy_link_.is_alive()) { low_energy_link_->set_encryption_change_callback({}); } } SecurityManagerImpl::SecurityManagerImpl( hci::LowEnergyConnection::WeakPtr low_energy_link, hci::BrEdrConnection::WeakPtr bredr_link, l2cap::Channel::WeakPtr smp, IOCapability io_capability, Delegate::WeakPtr delegate, BondableMode bondable_mode, gap::LESecurityMode security_mode, bool is_controller_remote_public_key_validation_supported, pw::async::Dispatcher& dispatcher, bt::gap::Peer::WeakPtr peer) : SecurityManager(bondable_mode, security_mode), pw_dispatcher_(dispatcher), next_pairing_id_(0), delegate_(std::move(delegate)), low_energy_link_(std::move(low_energy_link)), bredr_link_(std::move(bredr_link)), is_controller_remote_public_key_validation_supported_( is_controller_remote_public_key_validation_supported), low_energy_io_cap_(io_capability), sm_chan_(std::make_unique( smp, fit::bind_member<&SecurityManagerImpl::StartNewTimer>(this))), peer_(std::move(peer)), weak_self_(this), weak_listener_(this), weak_handler_(this) { PW_CHECK(delegate_.is_alive()); PW_CHECK(smp.is_alive()); sm_chan_->SetChannelHandler(weak_handler_.GetWeakPtr()); timeout_task_.set_function( [this](pw::async::Context /*ctx*/, pw::Status status) { if (status.ok()) { OnPairingTimeout(); } }); if (smp->id() == l2cap::kLESMPChannelId) { PW_CHECK(low_energy_link_.is_alive()); PW_CHECK(low_energy_link_->handle() == smp->link_handle()); // Set up HCI encryption event. low_energy_link_->set_encryption_change_callback( fit::bind_member<&SecurityManagerImpl::OnEncryptionChange>(this)); // Obtain existing pairing data, if any. std::optional ltk = GetExistingLtkFromPeerCache(); if (ltk) { bt_log(INFO, "sm", "starting encryption with existing LTK (peer: %s, handle: %#.4x)", bt_str(peer_->identifier()), low_energy_link_->handle()); // Sets LTK in low_energy_link_ OnNewLongTermKey(*ltk); // The initiatior starts encryption when there is an LTK. if (low_energy_link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) { current_phase_ = StartingEncryption(); PW_CHECK(low_energy_link_->StartEncryption()); } } } } void SecurityManagerImpl::OnSecurityRequest(AuthReqField auth_req) { if (bredr_link_.is_alive()) { sm_chan_->SendMessageNoTimerReset(kPairingFailed, ErrorCode::kCommandNotSupported); return; } PW_CHECK(!SecurityUpgradeInProgress() || std::holds_alternative(current_phase_) || std::holds_alternative(current_phase_)); if (role() != Role::kInitiator) { bt_log( INFO, "sm", "Received spurious Security Request while not acting as SM initiator"); sm_chan_->SendMessageNoTimerReset(kPairingFailed, ErrorCode::kCommandNotSupported); return; } SecurityLevel requested_level; // inclusive-language: ignore if (auth_req & AuthReq::kMITM) { requested_level = SecurityLevel::kAuthenticated; } else { requested_level = SecurityLevel::kEncrypted; } // "If pairing has been initiated by the local device on the BR/EDR transport, // and a pairing request is received from the same remote device on the LE // transport, the LE pairing shall be rejected with SMP error code BR/EDR // Pairing in Progress if both sides support LE Secure Connections." (v6.0, // Vol. 3, Part C, Sec. 14.2) bool peer_supports_secure_connections = auth_req & kSC; bool bredr_pairing_in_progress = peer_->bredr() && peer_->bredr()->is_pairing(); if (peer_supports_secure_connections && bredr_pairing_in_progress) { bt_log(INFO, "sm", "rejecting Security Request because BREDR pairing in progress"); sm_chan_->SendMessageNoTimerReset(kPairingFailed, ErrorCode::kBREDRPairingInProgress); return; } // If we already have a LTK and its security properties satisfy the request, // then we start link layer encryption (which will either encrypt the link or // perform a key refresh). See Vol 3, Part H, Figure 2.7 for the algorithm. if (ltk_ && (ltk_->security().level() >= requested_level) && (!(auth_req & AuthReq::kSC) || ltk_->security().secure_connections())) { if (bt_is_error( ValidateExistingLocalLtk(), ERROR, "sm", "disconnecting link as it cannot be encrypted with LTK status")) { return; } current_phase_ = StartingEncryption(); PW_CHECK(low_energy_link_->StartEncryption()); return; } // V5.1 Vol. 3 Part H Section 3.4: "Upon [...] reception of the Security // Request command, the Security Manager Timer shall be [...] restarted." StartNewTimer(); if (fit::result result = RequestSecurityUpgrade(requested_level); result.is_error()) { // Per v5.3 Vol. 3 Part H 2.4.6, "When a Central receives a Security Request // command it may // [...] reject the request.", which we do here as we know we are unable to // fulfill it. sm_chan_->SendMessageNoTimerReset(kPairingFailed, result.error_value()); // If we fail to start pairing, we need to stop the timer. StopTimer(); } } void SecurityManagerImpl::UpgradeSecurity(SecurityLevel level, PairingCallback callback) { PW_CHECK(!bredr_link_.is_alive()); if (SecurityUpgradeInProgress()) { bt_log(TRACE, "sm", "LE security upgrade in progress; request for %s security queued", LevelToString(level)); request_queue_.emplace(level, std::move(callback)); return; } if (level <= security().level()) { callback(fit::ok(), security()); return; } // Secure Connections only mode only permits Secure Connections authenticated // pairing with a 128- bit encryption key, so we force all security upgrade // requests to that level. if (security_mode() == gap::LESecurityMode::SecureConnectionsOnly) { level = SecurityLevel::kSecureAuthenticated; } // |request_queue| must be empty if there is no active security upgrade // request, which is equivalent to being in idle phase with no pending // security request. PW_CHECK(request_queue_.empty()); request_queue_.emplace(level, std::move(callback)); UpgradeSecurityInternal(); } void SecurityManagerImpl::OnPairingRequest( const PairingRequestParams& req_params) { // Only the initiator may send the Pairing Request (V5.0 Vol. 3 Part H 3.5.1). if (role() != Role::kResponder) { bt_log(INFO, "sm", "rejecting \"Pairing Request\" as initiator"); sm_chan_->SendMessageNoTimerReset(kPairingFailed, ErrorCode::kCommandNotSupported); return; } // "If pairing has been initiated by the local device on the BR/EDR transport, // and a pairing request is received from the same remote device on the LE // transport, the LE pairing shall be rejected with SMP error code BR/EDR // Pairing in Progress if both sides support LE Secure Connections." (v6.0, // Vol. 3, Part C, Sec. 14.2) bool peer_supports_secure_connections = req_params.auth_req & kSC; bool le_secure_connections = low_energy_link_.is_alive() && peer_supports_secure_connections; bool bredr_pairing_in_progress = peer_->bredr() && peer_->bredr()->is_pairing(); if (le_secure_connections && bredr_pairing_in_progress) { bt_log(INFO, "sm", "LE: rejecting Pairing Request because BREDR pairing in progress"); sm_chan_->SendMessageNoTimerReset(kPairingFailed, ErrorCode::kBREDRPairingInProgress); return; } // We only require authentication as Responder if there is a pending // Security Request for it. SecurityRequestPhase* security_req_phase = std::get_if(¤t_phase_); auto required_level = security_req_phase ? security_req_phase->pending_security_request() : SecurityLevel::kEncrypted; // Secure Connections only mode only permits Secure Connections authenticated // pairing with a 128- bit encryption key, so we force all security upgrade // requests to that level. if (security_mode() == gap::LESecurityMode::SecureConnectionsOnly) { required_level = SecurityLevel::kSecureAuthenticated; } if (bredr_link_.is_alive()) { if (!IsBrEdrCrossTransportKeyDerivationAllowed()) { bt_log(INFO, "sm", "BR/EDR: rejecting \"Pairing Request\" because CTKD not allowed"); sm_chan_->SendMessageNoTimerReset( kPairingFailed, ErrorCode::kCrossTransportKeyDerivationNotAllowed); return; } } if (!pairing_token_) { if (low_energy_link_.is_alive()) { pairing_token_ = peer_->MutLe().RegisterPairing(); } else { pairing_token_ = peer_->MutBrEdr().RegisterPairing(); } } // V5.1 Vol. 3 Part H Section 3.4: "Upon [...] reception of the Pairing // Request command, the Security Manager Timer shall be reset and started." StartNewTimer(); current_phase_ = Phase1::CreatePhase1Responder( sm_chan_->GetWeakPtr(), weak_listener_.GetWeakPtr(), req_params, low_energy_io_cap_, bondable_mode(), required_level, fit::bind_member<&SecurityManagerImpl::OnFeatureExchange>(this)); std::get>(current_phase_)->Start(); } void SecurityManagerImpl::UpgradeSecurityInternal() { PW_CHECK(!bredr_link_.is_alive()); PW_CHECK( !SecurityUpgradeInProgress(), "cannot upgrade security while security upgrade already in progress!"); PW_CHECK(!request_queue_.empty()); // "If a BR/EDR/LE device supports LE Secure Connections, then it shall // initiate pairing on only one transport at a time to the same remote // device." (v6.0, Vol 3, Part C, Sec. 14.2) if (peer_->bredr() && peer_->bredr()->is_pairing()) { bt_log(DEBUG, "sm", "Delaying security upgrade until BR/EDR pairing completes"); current_phase_ = WaitForBrEdrPairing(); peer_->MutBrEdr().add_pairing_completion_callback( [self = weak_self_.GetWeakPtr()]() { if (!self.is_alive() || !std::get_if(&self->current_phase_)) { return; } self->ResetState(); if (self->request_queue_.empty()) { return; } self->UpgradeSecurityInternal(); }); return; } const PendingRequest& next_req = request_queue_.front(); // BR/EDR cross-transport key derivation could have created a new LE LTK that // meets the requirements of the next request. Only central can start // encryption, so we skip this and request a security upgrade as peripheral. if (low_energy_link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) { std::optional ltk = GetExistingLtkFromPeerCache(); // If the new LTK isn't going to satisfy the request anyway, we can ignore // it and start pairing. if (ltk && ltk != ltk_ && ltk->security().level() >= next_req.level) { bt_log(INFO, "sm", "starting encryption with LTK from PeerCache (peer: %s, handle: " "%#.4x)", bt_str(peer_->identifier()), low_energy_link_->handle()); OnNewLongTermKey(*ltk); current_phase_ = StartingEncryption(); PW_CHECK(low_energy_link_->StartEncryption()); return; } } if (fit::result result = RequestSecurityUpgrade(next_req.level); result.is_error()) { next_req.callback(ToResult(result.error_value()), security()); request_queue_.pop(); if (!request_queue_.empty()) { UpgradeSecurityInternal(); } } } fit::result SecurityManagerImpl::RequestSecurityUpgrade( SecurityLevel level) { PW_CHECK(!bredr_link_.is_alive()); if (level >= SecurityLevel::kAuthenticated && low_energy_io_cap_ == IOCapability::kNoInputNoOutput) { bt_log(WARN, "sm", "cannot fulfill authenticated security request as IOCapabilities " "are NoInputNoOutput"); return fit::error(ErrorCode::kAuthenticationRequirements); } if (!pairing_token_) { pairing_token_ = peer_->MutLe().RegisterPairing(); } if (role() == Role::kInitiator) { current_phase_ = Phase1::CreatePhase1Initiator( sm_chan_->GetWeakPtr(), weak_listener_.GetWeakPtr(), low_energy_io_cap_, bondable_mode(), level, fit::bind_member<&SecurityManagerImpl::OnFeatureExchange>(this)); std::get>(current_phase_)->Start(); } else { current_phase_.emplace( sm_chan_->GetWeakPtr(), weak_listener_.GetWeakPtr(), level, bondable_mode(), fit::bind_member<&SecurityManagerImpl::OnPairingRequest>(this)); std::get(current_phase_).Start(); } return fit::ok(); } void SecurityManagerImpl::OnFeatureExchange(PairingFeatures features, PairingRequestParams preq, PairingResponseParams pres) { PW_CHECK(std::holds_alternative>(current_phase_)); bt_log(DEBUG, "sm", "SMP feature exchange complete"); next_pairing_id_++; features_ = features; auto self = weak_listener_.GetWeakPtr(); if (bredr_link_.is_alive()) { if (!features_->generate_ct_key.has_value()) { Abort(ErrorCode::kCrossTransportKeyDerivationNotAllowed); return; } // If there are no keys to distribute, skip Phase3 if (!HasKeysToDistribute(*features_, /*is_bredr=*/true)) { OnBrEdrPairingComplete(PairingData()); return; } // We checked the ltk before Phase1. PW_CHECK(bredr_link_->ltk().has_value()); // Phase3 needs to know the security properties. SecurityProperties bredr_security_properties( bredr_link_->ltk_type().value()); current_phase_.emplace( sm_chan_->GetWeakPtr(), self, role(), *features_, bredr_security_properties, fit::bind_member<&SecurityManagerImpl::OnBrEdrPairingComplete>(this)); std::get(current_phase_).Start(); } else if (features.secure_connections) { const auto [initiator_addr, responder_addr] = LEPairingAddresses(); current_phase_.emplace( sm_chan_->GetWeakPtr(), self, role(), features, preq, pres, initiator_addr, responder_addr, fit::bind_member<&SecurityManagerImpl::OnPhase2EncryptionKey>(this)); std::get(current_phase_).Start(); } else { const auto [initiator_addr, responder_addr] = LEPairingAddresses(); auto preq_pdu = util::NewPdu(sizeof(PairingRequestParams)), pres_pdu = util::NewPdu(sizeof(PairingResponseParams)); PacketWriter preq_writer(kPairingRequest, preq_pdu.get()), pres_writer(kPairingResponse, pres_pdu.get()); *preq_writer.mutable_payload() = preq; *pres_writer.mutable_payload() = pres; current_phase_.emplace( sm_chan_->GetWeakPtr(), self, role(), features, *preq_pdu, *pres_pdu, initiator_addr, responder_addr, fit::bind_member<&SecurityManagerImpl::OnPhase2EncryptionKey>(this)); std::get(current_phase_).Start(); } } void SecurityManagerImpl::OnPhase2EncryptionKey(const UInt128& new_key) { PW_CHECK(!bredr_link_.is_alive()); PW_CHECK(low_energy_link_.is_alive()); PW_CHECK(features_); PW_CHECK(InPhase2()); // EDiv and Rand values are 0 for Phase 2 keys generated by Legacy or Secure // Connections (Vol 3, Part H, 2.4.4 / 2.4.4.1). Secure Connections generates // an LTK, while Legacy generates an STK. auto new_link_key = hci_spec::LinkKey(new_key, 0, 0); if (features_->secure_connections) { OnNewLongTermKey(LTK(FeaturesToProperties(*features_), new_link_key)); } else { // `set_le_ltk` sets the encryption key of the LE link (which is the STK for // Legacy), not the long-term key that results from pairing (which is // generated in Phase 3 for Legacy). low_energy_link_->set_ltk(new_link_key); } // If we're the initiator, we encrypt the link. If we're the responder, we // wait for the initiator to encrypt the link with the new key.|le_link_| will // respond to the HCI "LTK request" event with the `new_link_key` assigned // above, which should trigger OnEncryptionChange. if (role() == Role::kInitiator) { if (!low_energy_link_->StartEncryption()) { bt_log(ERROR, "sm", "failed to start encryption"); Abort(ErrorCode::kUnspecifiedReason); } } } bool SecurityManagerImpl::CurrentLtkInsufficientlySecureForEncryption( std::optional current_ltk, SecurityRequestPhase* security_request_phase, gap::LESecurityMode mode) { SecurityLevel current_ltk_sec = current_ltk ? current_ltk->security().level() : SecurityLevel::kNoSecurity; return (security_request_phase && security_request_phase->pending_security_request() > current_ltk_sec) || (mode == gap::LESecurityMode::SecureConnectionsOnly && current_ltk_sec != SecurityLevel::kSecureAuthenticated); } void SecurityManagerImpl::OnEncryptionChange(hci::Result enabled_result) { PW_CHECK(!bredr_link_.is_alive()); // First notify the delegate in case of failure. if (bt_is_error( enabled_result, ERROR, "sm", "link layer authentication failed")) { PW_CHECK(delegate_.is_alive()); delegate_->OnAuthenticationFailure( fit::error(enabled_result.error_value())); } if (enabled_result.is_error() || !enabled_result.value()) { bt_log(WARN, "sm", "encryption of link (handle: %#.4x) %s%s!", low_energy_link_->handle(), enabled_result.is_error() ? bt_lib_cpp_string::StringPrintf("failed with %s", bt_str(enabled_result)) .c_str() : "disabled", SecurityUpgradeInProgress() ? "" : " during security upgrade"); SetSecurityProperties(sm::SecurityProperties()); if (SecurityUpgradeInProgress()) { Abort(ErrorCode::kUnspecifiedReason); } return; } SecurityRequestPhase* security_request_phase = std::get_if(¤t_phase_); if (CurrentLtkInsufficientlySecureForEncryption( ltk_, security_request_phase, security_mode())) { bt_log(WARN, "sm", "peer encrypted link with insufficiently secure key, disconnecting"); delegate_->OnAuthenticationFailure( ToResult(HostError::kInsufficientSecurity)); sm_chan_->SignalLinkError(); return; } bool wait_for_bredr_pairing_phase = std::holds_alternative(current_phase_); bool starting_encryption_phase = std::holds_alternative(current_phase_); if (!SecurityUpgradeInProgress() || security_request_phase || starting_encryption_phase || wait_for_bredr_pairing_phase) { bt_log(DEBUG, "sm", "encryption enabled while not pairing"); if (bt_is_error( ValidateExistingLocalLtk(), ERROR, "sm", "disconnecting link as it cannot be encrypted with LTK status")) { return; } // If encryption is enabled while not pairing, we update the security // properties to those of `ltk_`. Otherwise, we let the EndPhase2 pairing // function determine the security properties. SetSecurityProperties(ltk_->security()); if (security_request_phase) { PW_CHECK(role() == Role::kResponder); PW_CHECK(!request_queue_.empty()); } if (starting_encryption_phase) { ResetState(); } NotifySecurityCallbacks(); return; } if (InPhase2()) { bt_log(DEBUG, "sm", "link encrypted with phase 2 generated key"); EndPhase2(); } } void SecurityManagerImpl::EndPhase2() { PW_CHECK(features_.has_value()); PW_CHECK(InPhase2()); PW_CHECK(!bredr_link_.is_alive()); SetSecurityProperties(FeaturesToProperties(*features_)); // If there are no keys to distribute, don't bother creating Phase 3 if (!HasKeysToDistribute(*features_, bredr_link_.is_alive())) { OnLowEnergyPairingComplete(PairingData()); return; } auto self = weak_listener_.GetWeakPtr(); current_phase_.emplace( sm_chan_->GetWeakPtr(), self, role(), *features_, security(), fit::bind_member<&SecurityManagerImpl::OnLowEnergyPairingComplete>(this)); std::get(current_phase_).Start(); } void SecurityManagerImpl::OnLowEnergyPairingComplete(PairingData pairing_data) { // We must either be in Phase3 or Phase 2 with no keys to distribute if // pairing has completed. if (!std::holds_alternative(current_phase_)) { PW_CHECK(InPhase2()); PW_CHECK(!HasKeysToDistribute(*features_, /*is_bredr=*/false)); } PW_CHECK(delegate_.is_alive()); PW_CHECK(features_.has_value()); bt_log(DEBUG, "sm", "LE pairing complete"); delegate_->OnPairingComplete(fit::ok()); // In Secure Connections, the LTK will be generated in Phase 2, not exchanged // in Phase 3, so we want to ensure that it is still put in the pairing_data. if (features_->secure_connections) { PW_CHECK(ltk_.has_value()); pairing_data.peer_ltk = pairing_data.local_ltk = ltk_; } else { // The SM-internal LTK is used to validate future encryption events on the // existing link. Encryption with LTKs generated by LE legacy pairing uses // the key received by the link-layer central - so as initiator, this is the // peer key, and as responder, this is the local key. const std::optional& new_ltk = role() == Role::kInitiator ? pairing_data.peer_ltk : pairing_data.local_ltk; if (new_ltk.has_value()) { OnNewLongTermKey(*new_ltk); } } if (features_->generate_ct_key.has_value()) { // If we are generating the CT key, we must be using secure connections, and // as such the peer and local LTKs will be equivalent. PW_CHECK(features_->secure_connections); PW_CHECK(pairing_data.peer_ltk == pairing_data.local_ltk); std::optional ct_key_value = util::LeLtkToBrEdrLinkKey( ltk_->key().value(), features_->generate_ct_key.value()); if (ct_key_value) { pairing_data.cross_transport_key = sm::LTK(ltk_->security(), hci_spec::LinkKey(*ct_key_value, 0, 0)); } else { bt_log(WARN, "sm", "failed to generate cross-transport key"); } } if (features_->will_bond) { std::optional ltk; if (pairing_data.peer_ltk) { ltk = pairing_data.peer_ltk; } else { ltk = pairing_data.local_ltk; } if (ltk.has_value()) { bt_log( INFO, "sm", "new %s pairing data: [%s%s%s%s%s%s] (peer: %s)", ltk->security().secure_connections() ? "secure connections" : "legacy", pairing_data.peer_ltk ? "peer_ltk " : "", pairing_data.local_ltk ? "local_ltk " : "", pairing_data.irk ? "irk " : "", pairing_data.cross_transport_key ? "ct_key " : "", pairing_data.identity_address ? bt_lib_cpp_string::StringPrintf( "(identity: %s) ", bt_str(*pairing_data.identity_address)) .c_str() : "", pairing_data.csrk ? "csrk " : "", bt_str(peer_->identifier())); if (!peer_->MutLe().StoreBond(pairing_data)) { bt_log(ERROR, "sm", "failed to cache bonding data (id: %s)", bt_str(peer_->identifier())); } } else { // Consider the pairing temporary if no link key was received. This // means we'll remain encrypted with the STK without creating a bond and // reinitiate pairing when we reconnect in the future. bt_log(INFO, "sm", "temporarily paired with peer (peer: %s)", bt_str(peer_->identifier())); } } else { bt_log(INFO, "gap-le", " %s pairing complete in non-bondable mode with [%s%s%s%s%s]", features_->secure_connections ? "secure connections" : "legacy", pairing_data.peer_ltk ? "peer_ltk " : "", pairing_data.local_ltk ? "local_ltk " : "", pairing_data.irk ? "irk " : "", pairing_data.identity_address ? bt_lib_cpp_string::StringPrintf( "(identity: %s) ", bt_str(*pairing_data.identity_address)) .c_str() : "", pairing_data.csrk ? "csrk " : ""); } // So we can pair again if need be. ResetState(); NotifySecurityCallbacks(); } void SecurityManagerImpl::OnBrEdrPairingComplete(PairingData pairing_data) { PW_CHECK(bredr_link_.is_alive()); // We must either be in Phase3 or Phase1 with no keys to distribute if pairing // has completed. if (!std::holds_alternative(current_phase_)) { PW_CHECK(InPhase1()); PW_CHECK(!HasKeysToDistribute(*features_, /*is_bredr=*/true)); } PW_CHECK(features_.has_value()); PW_CHECK(features_->generate_ct_key.has_value()); PW_CHECK(delegate_.is_alive()); bt_log(INFO, "sm", "BR/EDR cross-transport key derivation complete"); delegate_->OnPairingComplete(fit::ok()); std::optional ct_key_value = util::BrEdrLinkKeyToLeLtk( bredr_link_->ltk()->value(), features_->generate_ct_key.value()); if (ct_key_value) { // The LE LTK will have the same security properties as the BR/EDR key. SecurityProperties bredr_properties(bredr_link_->ltk_type().value()); sm::LTK le_ltk = sm::LTK(bredr_properties, hci_spec::LinkKey(*ct_key_value, 0, 0)); pairing_data.local_ltk = le_ltk; pairing_data.peer_ltk = le_ltk; } else { bt_log(ERROR, "sm", "BR/EDR CTKD key generation failed"); if (bredr_cross_transport_key_derivation_callback_) { bredr_cross_transport_key_derivation_callback_( ToResult(HostError::kFailed)); } ResetState(); return; } if (!peer_->MutLe().StoreBond(pairing_data)) { bt_log(ERROR, "sm", "failed to cache bonding data (id: %s)", bt_str(peer_->identifier())); } if (bredr_cross_transport_key_derivation_callback_) { bredr_cross_transport_key_derivation_callback_(fit::ok()); } ResetState(); } void SecurityManagerImpl::NotifySecurityCallbacks() { // Separate out the requests that are satisfied by the current security level // from those that require a higher level. We'll retry pairing for the latter. std::queue satisfied; std::queue unsatisfied; while (!request_queue_.empty()) { auto& request = request_queue_.front(); if (request.level <= security().level()) { satisfied.push(std::move(request)); } else { unsatisfied.push(std::move(request)); } request_queue_.pop(); } request_queue_ = std::move(unsatisfied); // Notify the satisfied requests with success. while (!satisfied.empty()) { satisfied.front().callback(fit::ok(), security()); satisfied.pop(); } if (!request_queue_.empty()) { UpgradeSecurityInternal(); } } void SecurityManagerImpl::InitiateBrEdrCrossTransportKeyDerivation( CrossTransportKeyDerivationResultCallback callback) { PW_CHECK(bredr_link_.is_alive()); PW_CHECK(role() == Role::kInitiator); if (bredr_cross_transport_key_derivation_callback_ || SecurityUpgradeInProgress()) { callback(ToResult(HostError::kInProgress)); return; } if (!IsBrEdrCrossTransportKeyDerivationAllowed()) { callback(ToResult(HostError::kInsufficientSecurity)); return; } bredr_cross_transport_key_derivation_callback_ = std::move(callback); if (!pairing_token_) { pairing_token_ = peer_->MutBrEdr().RegisterPairing(); } current_phase_ = Phase1::CreatePhase1Initiator( sm_chan_->GetWeakPtr(), weak_listener_.GetWeakPtr(), IOCapability::kDisplayOnly, // arbitrary BondableMode::Bondable, SecurityLevel::kEncrypted, // arbitrary fit::bind_member<&SecurityManagerImpl::OnFeatureExchange>(this)); std::get>(current_phase_)->Start(); } void SecurityManagerImpl::Reset(IOCapability io_capability) { Abort(ErrorCode::kUnspecifiedReason); low_energy_io_cap_ = io_capability; ResetState(); } void SecurityManagerImpl::ResetState() { StopTimer(); features_.reset(); sm_chan_->SetChannelHandler(weak_handler_.GetWeakPtr()); pairing_token_.reset(); current_phase_ = std::monostate{}; } void SecurityManagerImpl::SetSecurityProperties(const SecurityProperties& sec) { if (sec != security()) { bt_log(DEBUG, "sm", "security properties changed - handle: %#.4x, new: %s, old: %s", low_energy_link_->handle(), bt_str(sec), bt_str(security())); set_security(sec); delegate_->OnNewSecurityProperties(security()); } } void SecurityManagerImpl::Abort(ErrorCode ecode) { std::visit( [=](auto& arg) { using T = std::decay_t; if constexpr (std::is_same_v, T>) { arg->Abort(ecode); } else if constexpr (std::is_base_of_v) { arg.Abort(ecode); } else { bt_log(DEBUG, "sm", "Attempted to abort security upgrade while not in progress"); } }, current_phase_); // "Abort" should trigger OnPairingFailed. } std::optional SecurityManagerImpl::OnIdentityRequest() { // This is called by the bearer to determine if we have local identity // information to distribute. PW_CHECK(delegate_.is_alive()); return delegate_->OnIdentityInformationRequest(); } void SecurityManagerImpl::ConfirmPairing(ConfirmCallback confirm) { PW_CHECK(!bredr_link_.is_alive()); PW_CHECK(delegate_.is_alive()); delegate_->ConfirmPairing([id = next_pairing_id_, self = weak_self_.GetWeakPtr(), cb = std::move(confirm)](bool is_confirmed) { if (!self.is_alive() || self->next_pairing_id_ != id) { bt_log(TRACE, "sm", "ignoring user confirmation for expired pairing: id = %" PRIu64, id); return; } cb(is_confirmed); }); } void SecurityManagerImpl::DisplayPasskey(uint32_t passkey, Delegate::DisplayMethod method, ConfirmCallback confirm) { PW_CHECK(!bredr_link_.is_alive()); PW_CHECK(delegate_.is_alive()); delegate_->DisplayPasskey( passkey, method, [id = next_pairing_id_, self = weak_self_.GetWeakPtr(), method, cb = std::move(confirm)](bool is_confirmed) { if (!self.is_alive() || self->next_pairing_id_ != id) { bt_log(TRACE, "sm", "ignoring %s response for expired pairing: id = %" PRIu64, util::DisplayMethodToString(method).c_str(), id); return; } cb(is_confirmed); }); } void SecurityManagerImpl::RequestPasskey(PasskeyResponseCallback respond) { PW_CHECK(!bredr_link_.is_alive()); PW_CHECK(delegate_.is_alive()); delegate_->RequestPasskey([id = next_pairing_id_, self = weak_self_.GetWeakPtr(), cb = std::move(respond)](int64_t passkey) { if (!self.is_alive() || self->next_pairing_id_ != id) { bt_log( TRACE, "sm", "ignoring passkey input response for expired pairing: id = %" PRIx64, id); return; } cb(passkey); }); } void SecurityManagerImpl::OnRxBFrame(ByteBufferPtr sdu) { fit::result maybe_reader = ValidPacketReader::ParseSdu(sdu); if (maybe_reader.is_error()) { bt_log(INFO, "sm", "dropped SMP packet: %s", bt_str(ToResult(maybe_reader.error_value()))); return; } ValidPacketReader reader = maybe_reader.value(); Code smp_code = reader.code(); if (smp_code == kPairingRequest) { OnPairingRequest(reader.payload()); } else if (smp_code == kSecurityRequest) { OnSecurityRequest(reader.payload()); } else { bt_log(INFO, "sm", "dropped unexpected SMP code %#.2X when not pairing", smp_code); } } void SecurityManagerImpl::OnChannelClosed() { bt_log(DEBUG, "sm", "SMP channel closed while not pairing"); } void SecurityManagerImpl::OnPairingFailed(Error error) { std::string phase_status = std::visit( [=](auto& arg) { using T = std::decay_t; std::string s; if constexpr (std::is_same_v, T>) { s = arg->ToString(); } else if constexpr (std::is_base_of_v) { s = arg.ToString(); } else { PW_CRASH( "security upgrade cannot fail when current_phase_ is " "std::monostate!"); } return s; }, current_phase_); bt_log(ERROR, "sm", "LE pairing failed: %s. Current pairing phase: %s", bt_str(error), phase_status.c_str()); StopTimer(); // TODO(fxbug.dev/42172514): implement "waiting interval" to prevent repeated // attempts as described in Vol 3, Part H, 2.3.6. PW_CHECK(delegate_.is_alive()); delegate_->OnPairingComplete(fit::error(error)); auto requests = std::move(request_queue_); while (!requests.empty()) { requests.front().callback(fit::error(error), security()); requests.pop(); } if (SecurityUpgradeInProgress() && !bredr_link_.is_alive()) { PW_CHECK(low_energy_link_.is_alive()); low_energy_link_->set_ltk(hci_spec::LinkKey()); } if (bredr_cross_transport_key_derivation_callback_) { bredr_cross_transport_key_derivation_callback_( ToResult(HostError::kFailed)); } ResetState(); // Reset state before potentially disconnecting link to avoid causing pairing // phase to fail twice. if (error.is(HostError::kTimedOut)) { // Per v5.2 Vol. 3 Part H 3.4, after a pairing timeout "No further SMP // commands shall be sent over the L2CAP Security Manager Channel. A new // Pairing process shall only be performed when a new physical link has been // established." bt_log(WARN, "sm", "pairing timed out! disconnecting link"); sm_chan_->SignalLinkError(); } } void SecurityManagerImpl::StartNewTimer() { if (timeout_task_.is_pending()) { PW_CHECK(timeout_task_.Cancel()); } timeout_task_.PostAfter(kPairingTimeout); } void SecurityManagerImpl::StopTimer() { if (timeout_task_.is_pending() && !timeout_task_.Cancel()) { bt_log(TRACE, "sm", "smp: failed to stop timer"); } } void SecurityManagerImpl::OnPairingTimeout() { std::visit( [=](auto& arg) { using T = std::decay_t; if constexpr (std::is_same_v, T>) { arg->OnFailure(Error(HostError::kTimedOut)); } else if constexpr (std::is_base_of_v) { arg.OnFailure(Error(HostError::kTimedOut)); } else { PW_CRASH("cannot timeout when current_phase_ is std::monostate!"); } }, current_phase_); } std::pair SecurityManagerImpl::LEPairingAddresses() { PW_CHECK(!bredr_link_.is_alive()); PW_CHECK(SecurityUpgradeInProgress()); const DeviceAddress *initiator = &low_energy_link_->local_address(), *responder = &low_energy_link_->peer_address(); if (role() == Role::kResponder) { std::swap(initiator, responder); } return std::make_pair(*initiator, *responder); } void SecurityManagerImpl::OnNewLongTermKey(const LTK& ltk) { ltk_ = ltk; low_energy_link_->set_ltk(ltk.key()); } Result<> SecurityManagerImpl::ValidateExistingLocalLtk() { Result<> status = fit::ok(); if (!ltk_.has_value()) { // Should always be present when this method is called. bt_log(ERROR, "sm", "SM LTK not found"); status = fit::error(Error(HostError::kNotFound)); } else if (!low_energy_link_->ltk().has_value()) { // Should always be present when this method is called. bt_log(ERROR, "sm", "Link LTK not found"); status = fit::error(Error(HostError::kNotFound)); } else if (low_energy_link_->ltk().value() != ltk_->key()) { // As only SM should ever change the LE Link encryption key, these two // values should always be in sync, i.e. something in the system is acting // unreliably if they get out of sync. bt_log(ERROR, "sm", "SM LTK differs from LE link LTK"); status = fit::error(Error(HostError::kNotReliable)); } if (status.is_error()) { // SM does not own the link, so although the checks above should never fail, // disconnecting the link (vs. ASSERTing these checks) is safer against // non-SM code potentially touching the key. delegate_->OnAuthenticationFailure( ToResult(pw::bluetooth::emboss::StatusCode::PIN_OR_KEY_MISSING)); sm_chan_->SignalLinkError(); } return status; } bool SecurityManagerImpl::IsBrEdrCrossTransportKeyDerivationAllowed() { if (!is_controller_remote_public_key_validation_supported_) { bt_log(DEBUG, "sm", "%s: remote public key validation not supported", __FUNCTION__); return false; } if (bredr_link_->encryption_status() != pw::bluetooth::emboss::EncryptionStatus::ON_WITH_AES_FOR_BREDR) { bt_log(DEBUG, "sm", "%s: encryption status not AES", __FUNCTION__); return false; } if (!bredr_link_->ltk().has_value() || !bredr_link_->ltk_type() || !(bredr_link_->ltk_type().value() == hci_spec::LinkKeyType::kUnauthenticatedCombination256 || bredr_link_->ltk_type().value() == hci_spec::LinkKeyType::kAuthenticatedCombination256)) { bt_log(DEBUG, "sm", "%s: Link Key has insufficient security", __FUNCTION__); return false; } // Do not derive LE LTK if existing LE LTK is stronger than current // BR/EDR link key. SecurityProperties bredr_security_props(bredr_link_->ltk_type().value()); bool has_le_ltk = peer_->le() && peer_->le()->bond_data() && peer_->le()->bond_data()->local_ltk; if (has_le_ltk && !bredr_security_props.IsAsSecureAs( peer_->le()->bond_data()->local_ltk->security())) { bt_log(DEBUG, "sm", "%s: LE LTK stronger than current BR/EDR link key", __FUNCTION__); return false; } // TODO(fxbug.dev/388607971): check for LE pairing in progress return true; } std::optional SecurityManagerImpl::GetExistingLtkFromPeerCache() { if (peer_->le() && peer_->le()->bond_data()) { // Legacy pairing allows both devices to generate and exchange LTKs. "The // Central must have the security information (LTK, EDIV, and Rand) // distributed by the Peripheral in LE legacy [...] to setup an encrypted // session" (v5.3, Vol. 3 Part H 2.4.4.2). For Secure Connections peer_ltk // and local_ltk will be equal, so this check is unnecessary but correct. if (low_energy_link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) { return peer_->le()->bond_data()->peer_ltk; } return peer_->le()->bond_data()->local_ltk; } return std::nullopt; } std::unique_ptr SecurityManager::CreateLE( hci::LowEnergyConnection::WeakPtr link, l2cap::Channel::WeakPtr smp, IOCapability io_capability, Delegate::WeakPtr delegate, BondableMode bondable_mode, gap::LESecurityMode security_mode, pw::async::Dispatcher& dispatcher, bt::gap::Peer::WeakPtr peer) { PW_CHECK(link.is_alive()); return std::make_unique( std::move(link), hci::BrEdrConnection::WeakPtr(), std::move(smp), io_capability, std::move(delegate), bondable_mode, security_mode, /*is_controller_remote_public_key_validation_supported=*/false, dispatcher, std::move(peer)); } std::unique_ptr SecurityManager::CreateBrEdr( hci::BrEdrConnection::WeakPtr link, l2cap::Channel::WeakPtr smp, Delegate::WeakPtr delegate, bool is_controller_remote_public_key_validation_supported, pw::async::Dispatcher& dispatcher, bt::gap::Peer::WeakPtr peer) { PW_CHECK(smp.is_alive()); PW_CHECK(smp->id() == l2cap::kSMPChannelId); return std::make_unique( hci::LowEnergyConnection::WeakPtr(), std::move(link), std::move(smp), IOCapability::kNoInputNoOutput, // arbitrary, RFU in BR/EDR std::move(delegate), BondableMode::Bondable, // BR/EDR is always "bondable". gap::LESecurityMode::Mode1, // arbitrary, not used by // BR/EDR is_controller_remote_public_key_validation_supported, dispatcher, std::move(peer)); } SecurityManager::SecurityManager(BondableMode bondable_mode, gap::LESecurityMode security_mode) : low_energy_bondable_mode_(bondable_mode), low_energy_security_mode_(security_mode) {} } // namespace bt::sm