| // Copyright 2020 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/sm/phase_2_secure_connections.h" |
| |
| #include <cstdint> |
| #include <memory> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/device_address.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/random.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/uint128.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/hci/connection.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/ecdh_key.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/fake_phase_listener.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/packet.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/smp.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/types.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/testing/test_helpers.h" |
| |
| namespace bt::sm { |
| namespace { |
| using ConfirmCallback = FakeListener::ConfirmCallback; |
| using PasskeyResponseCallback = FakeListener::PasskeyResponseCallback; |
| |
| const PairingFeatures kDefaultFeatures = { |
| .initiator = true, |
| .secure_connections = true, |
| .will_bond = true, |
| .generate_ct_key = std::optional<CrossTransportKeyAlgo>{std::nullopt}, |
| .method = PairingMethod::kJustWorks, |
| .encryption_key_size = kMaxEncryptionKeySize, |
| .local_key_distribution = KeyDistGen::kIdKey, |
| .remote_key_distribution = KeyDistGen::kIdKey | KeyDistGen::kEncKey}; |
| |
| const PairingRequestParams kDefaultPreq{ |
| .io_capability = IOCapability::kNoInputNoOutput, |
| .oob_data_flag = OOBDataFlag::kNotPresent, |
| .auth_req = AuthReq::kSC | AuthReq::kBondingFlag, |
| .max_encryption_key_size = kMaxEncryptionKeySize, |
| .initiator_key_dist_gen = KeyDistGen::kIdKey, |
| .responder_key_dist_gen = KeyDistGen::kIdKey | KeyDistGen::kEncKey}; |
| |
| const PairingResponseParams kDefaultPres{ |
| .io_capability = IOCapability::kNoInputNoOutput, |
| .oob_data_flag = OOBDataFlag::kNotPresent, |
| .auth_req = AuthReq::kSC | AuthReq::kBondingFlag, |
| .max_encryption_key_size = kMaxEncryptionKeySize, |
| .initiator_key_dist_gen = KeyDistGen::kIdKey, |
| .responder_key_dist_gen = KeyDistGen::kIdKey | KeyDistGen::kEncKey}; |
| |
| const DeviceAddress kAddr1(DeviceAddress::Type::kLEPublic, |
| {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}); |
| const DeviceAddress kAddr2(DeviceAddress::Type::kLEPublic, |
| {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}); |
| const LocalEcdhKey kDefaultEcdhKey = LocalEcdhKey::Create().value(); |
| |
| using util::PacketSize; |
| |
| class Phase2SecureConnectionsTest : public l2cap::testing::FakeChannelTest { |
| public: |
| Phase2SecureConnectionsTest() = default; |
| ~Phase2SecureConnectionsTest() override = default; |
| |
| protected: |
| void SetUp() override { NewPhase2SecureConnections(); } |
| |
| void TearDown() override { phase_2_sc_ = nullptr; } |
| |
| void NewPhase2SecureConnections( |
| Role local_role = Role::kInitiator, |
| PairingMethod method = PairingMethod::kJustWorks, |
| uint16_t mtu = kLeSecureConnectionsMtu) { |
| features_.initiator = (local_role == Role::kInitiator); |
| features_.method = method; |
| ChannelOptions options(l2cap::kLESMPChannelId, mtu); |
| options.link_type = bt::LinkType::kLE; |
| fake_sm_chan_ = CreateFakeChannel(options); |
| sm_chan_ = std::make_unique<PairingChannel>(fake_sm_chan_->GetWeakPtr()); |
| |
| listener_ = std::make_unique<FakeListener>(); |
| phase_2_sc_ = |
| std::make_unique<Phase2SecureConnections>(sm_chan_->GetWeakPtr(), |
| listener_->as_weak_ptr(), |
| local_role, |
| features_, |
| preq_, |
| pres_, |
| initiator_addr_, |
| responder_addr_, |
| [this](const UInt128& ltk) { |
| phase_2_complete_count_++; |
| ltk_ = ltk; |
| }); |
| } |
| |
| template <typename T> |
| void ReceiveCmd(Code cmd_code, const T& value) { |
| fake_chan()->Receive(MakeCmd(cmd_code, value)); |
| } |
| |
| template <typename T> |
| StaticByteBuffer<PacketSize<T>()> MakeCmd(Code cmd_code, const T& value) { |
| StaticByteBuffer<PacketSize<T>()> buffer; |
| PacketWriter writer(cmd_code, &buffer); |
| *writer.mutable_payload<T>() = value; |
| return buffer; |
| } |
| |
| static std::pair<Code, UInt128> ExtractCodeAnd128BitCmd(ByteBufferPtr sdu) { |
| BT_ASSERT_MSG(sdu, "Tried to ExtractCodeAnd128BitCmd from nullptr in test"); |
| auto maybe_reader = ValidPacketReader::ParseSdu(sdu); |
| BT_ASSERT_MSG(maybe_reader.is_ok(), |
| "Tried to ExtractCodeAnd128BitCmd from invalid SMP packet"); |
| return {maybe_reader.value().code(), |
| maybe_reader.value().payload<UInt128>()}; |
| } |
| |
| std::optional<PairingConfirmValue> FastForwardPublicKeyExchange() { |
| std::optional<PairingConfirmValue> responder_jw_nc_confirm = std::nullopt; |
| fake_chan()->SetSendCallback( |
| [&responder_jw_nc_confirm, this](ByteBufferPtr sdu) { |
| auto reader = ValidPacketReader::ParseSdu(sdu).value(); |
| if (reader.code() == kPairingPublicKey) { |
| local_key_ = EcdhKey::ParseFromPublicKey( |
| reader.payload<PairingPublicKeyParams>()); |
| } else if (reader.code() == kPairingConfirm) { |
| // Confirm must come after ECDH Pub Key if it comes now |
| ASSERT_TRUE(local_key_.has_value()); |
| responder_jw_nc_confirm = reader.payload<PairingConfirmValue>(); |
| } else { |
| ADD_FAILURE() << "unexpected packet code " << reader.code(); |
| } |
| }, |
| dispatcher()); |
| |
| if (phase_2_sc_->role() == Role::kInitiator) { |
| phase_2_sc_->Start(); |
| RunUntilIdle(); |
| BT_ASSERT_MSG(local_key_.has_value(), |
| "initiator did not send ecdh key upon starting"); |
| ReceiveCmd(kPairingPublicKey, peer_key_.GetSerializedPublicKey()); |
| RunUntilIdle(); |
| } else { |
| phase_2_sc_->Start(); |
| ReceiveCmd(kPairingPublicKey, peer_key_.GetSerializedPublicKey()); |
| RunUntilIdle(); |
| BT_ASSERT_MSG(local_key_.has_value(), |
| "responder did not send ecdh key upon peer key"); |
| if (features_.method == PairingMethod::kJustWorks || |
| features_.method == PairingMethod::kNumericComparison) { |
| BT_ASSERT(responder_jw_nc_confirm.has_value()); |
| return responder_jw_nc_confirm; |
| } |
| } |
| // We should only send confirm value immediately after ECDH key as responder |
| // in Numeric Comparison/Just Works pairing, in which case we would've |
| // already returned. |
| BT_ASSERT(!responder_jw_nc_confirm.has_value()); |
| return responder_jw_nc_confirm; |
| } |
| |
| UInt128 GenerateConfirmValue(const UInt128& random, |
| bool gen_initiator_confirm, |
| uint8_t r = 0) const { |
| BT_ASSERT_MSG(local_key_.has_value(), |
| "cannot compute confirm, missing key!"); |
| UInt256 pka = local_key_->GetPublicKeyX(), pkb = peer_key_.GetPublicKeyX(); |
| if (phase_2_sc_->role() == Role::kResponder) { |
| std::swap(pka, pkb); |
| } |
| return gen_initiator_confirm ? util::F4(pka, pkb, random, r).value() |
| : util::F4(pkb, pka, random, r).value(); |
| } |
| |
| struct MatchingPair { |
| UInt128 confirm; |
| UInt128 random; |
| }; |
| MatchingPair GenerateMatchingConfirmAndRandom(uint8_t r = 0) const { |
| MatchingPair pair{.confirm = {}, .random = {1, 2, 3, 4}}; |
| pair.confirm = GenerateConfirmValue( |
| pair.random, phase_2_sc_->role() == Role::kResponder, r); |
| return pair; |
| } |
| |
| struct LtkAndChecks { |
| UInt128 ltk; |
| UInt128 dhkey_check_a; |
| UInt128 dhkey_check_b; |
| }; |
| LtkAndChecks GenerateLtkAndChecks(const UInt128& initiator_rand, |
| const UInt128& responder_rand, |
| uint64_t r = 0) { |
| LtkAndChecks vals; |
| util::F5Results f5 = *util::F5(peer_key_.CalculateDhKey(*local_key_), |
| initiator_rand, |
| responder_rand, |
| initiator_addr_, |
| responder_addr_); |
| vals.ltk = f5.ltk; |
| |
| UInt128 r_array{0}; |
| // Copy little-endian uint64 r to the UInt128 array needed for Stage 2 |
| std::memcpy(r_array.data(), &r, sizeof(uint64_t)); |
| vals.dhkey_check_a = *util::F6(f5.mac_key, |
| initiator_rand, |
| responder_rand, |
| r_array, |
| preq_.auth_req, |
| preq_.oob_data_flag, |
| preq_.io_capability, |
| initiator_addr_, |
| responder_addr_); |
| vals.dhkey_check_b = *util::F6(f5.mac_key, |
| responder_rand, |
| initiator_rand, |
| r_array, |
| pres_.auth_req, |
| pres_.oob_data_flag, |
| pres_.io_capability, |
| responder_addr_, |
| initiator_addr_); |
| return vals; |
| } |
| |
| LtkAndChecks FastForwardToDhKeyCheck() { |
| BT_ASSERT_MSG( |
| features_.method == PairingMethod::kJustWorks, |
| "Fast forward to DHKey check only implemented for JustWorks method"); |
| return phase_2_sc_->role() == Role::kInitiator |
| ? FastForwardToDhKeyCheckInitiatorJustWorks() |
| : FastForwardToDhKeyCheckResponderJustWorks(); |
| } |
| |
| void DestroyPhase2() { phase_2_sc_.reset(nullptr); } |
| Phase2SecureConnections* phase_2_sc() { return phase_2_sc_.get(); } |
| FakeListener* listener() { return listener_.get(); } |
| const LocalEcdhKey& peer_key() { return peer_key_; } |
| const std::optional<EcdhKey>& local_key() { return local_key_; } |
| std::optional<EcdhKey>& mut_local_key() { return local_key_; } |
| int phase_2_complete_count() const { return phase_2_complete_count_; } |
| UInt128 ltk() const { return ltk_; } |
| |
| private: |
| LtkAndChecks FastForwardToDhKeyCheckInitiatorJustWorks() { |
| FastForwardPublicKeyExchange(); |
| Code sent_code = kPairingFailed; |
| PairingRandomValue initiator_rand; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, initiator_rand) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| MatchingPair stage1_vals = GenerateMatchingConfirmAndRandom(); |
| ReceiveCmd<PairingConfirmValue>(kPairingConfirm, stage1_vals.confirm); |
| RunUntilIdle(); |
| BT_ASSERT_MSG(kPairingRandom == sent_code, |
| "did not send pairing random when expected!"); |
| |
| ReceiveCmd(kPairingRandom, stage1_vals.random); |
| RunUntilIdle(); |
| return GenerateLtkAndChecks(initiator_rand, stage1_vals.random); |
| } |
| |
| LtkAndChecks FastForwardToDhKeyCheckResponderJustWorks() { |
| UInt128 rsp_confirm = *FastForwardPublicKeyExchange(); |
| Code sent_code = kPairingFailed; |
| UInt128 rsp_rand; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, rsp_rand) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| PairingRandomValue initiator_rand{1}; |
| ReceiveCmd(kPairingRandom, initiator_rand); |
| RunUntilIdle(); |
| |
| BT_ASSERT_MSG(kPairingRandom == sent_code, |
| "did not send pairing random when expected!"); |
| BT_ASSERT_MSG(GenerateConfirmValue( |
| rsp_rand, /*gen_initiator_confirm=*/false) == rsp_confirm, |
| "send invalid confirm value as JustWorks responder"); |
| return GenerateLtkAndChecks(initiator_rand, rsp_rand); |
| } |
| |
| std::unique_ptr<FakeListener> listener_; |
| std::unique_ptr<l2cap::testing::FakeChannel> fake_sm_chan_; |
| std::unique_ptr<PairingChannel> sm_chan_; |
| std::unique_ptr<Phase2SecureConnections> phase_2_sc_; |
| // Key of the internal bt-host SMP stack |
| std::optional<EcdhKey> local_key_; |
| // Key of the unit test peer we're mocking |
| const LocalEcdhKey& peer_key_ = kDefaultEcdhKey; |
| |
| PairingFeatures features_ = kDefaultFeatures; |
| PairingRequestParams preq_ = kDefaultPreq; |
| PairingResponseParams pres_ = kDefaultPres; |
| DeviceAddress initiator_addr_ = kAddr1; |
| DeviceAddress responder_addr_ = kAddr2; |
| int phase_2_complete_count_ = 0; |
| UInt128 ltk_; |
| |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Phase2SecureConnectionsTest); |
| }; |
| |
| using Phase2SecureConnectionsDeathTest = Phase2SecureConnectionsTest; |
| |
| TEST_F(Phase2SecureConnectionsDeathTest, MtuTooSmallDies) { |
| ASSERT_DEATH_IF_SUPPORTED( |
| NewPhase2SecureConnections( |
| Role::kInitiator, PairingMethod::kJustWorks, kNoSecureConnectionsMtu), |
| ".*SecureConnections.*"); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceivePairingFailed) { |
| phase_2_sc()->Start(); |
| fake_chan()->Receive(StaticByteBuffer<PacketSize<ErrorCode>()>{ |
| kPairingFailed, ErrorCode::kPairingNotSupported}); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(Error(ErrorCode::kPairingNotSupported), listener()->last_error()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceiveMalformedPacket) { |
| phase_2_sc()->Start(); |
| // PairingPublicKeyParams is expected to have both an X and Y value, not just |
| // an X. |
| const UInt256 kX = peer_key().GetPublicKeyX(); |
| const auto kPairingPublicKeyCmd = MakeCmd(kPairingPublicKey, kX); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kInvalidParameters}; |
| |
| EXPECT_TRUE(ReceiveAndExpect(kPairingPublicKeyCmd, kExpectedFailure)); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceiveUnexpectedPacket) { |
| phase_2_sc()->Start(); |
| // Pairing Responses should only be sent during Phase 1 of pairing. |
| const auto kPairingResponseCmd = |
| MakeCmd(kPairingResponse, PairingResponseParams()); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| |
| EXPECT_TRUE(ReceiveAndExpect(kPairingResponseCmd, kExpectedFailure)); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, InitiatorPubKeyOutOfOrder) { |
| NewPhase2SecureConnections(Role::kInitiator); |
| |
| const auto kPairingPublicKeyCmd = |
| MakeCmd(kPairingPublicKey, peer_key().GetSerializedPublicKey()); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingPublicKeyCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, RejectsPublicKeyOffCurve) { |
| phase_2_sc()->Start(); |
| const auto kPairingPublicKeyCmd = MakeCmd( |
| kPairingPublicKey, PairingPublicKeyParams{.x = {0x01}, .y = {0x02}}); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kInvalidParameters}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingPublicKeyCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, RejectsPublicKeyIdenticalToLocalKey) { |
| // Read local key sent to peer |
| fake_chan()->SetSendCallback( |
| [this](ByteBufferPtr sdu) { |
| auto reader = ValidPacketReader::ParseSdu(sdu).value(); |
| if (reader.code() == kPairingPublicKey) { |
| mut_local_key() = EcdhKey::ParseFromPublicKey( |
| reader.payload<PairingPublicKeyParams>()); |
| } |
| }, |
| dispatcher()); |
| |
| phase_2_sc()->Start(); |
| RunUntilIdle(); |
| ASSERT_TRUE(local_key().has_value()); |
| |
| // Mirror back local key as the peer's public key |
| const auto kPairingPublicKeyCmd = |
| MakeCmd(kPairingPublicKey, local_key()->GetSerializedPublicKey()); |
| const StaticByteBuffer kExpectedFailure{kPairingFailed, |
| ErrorCode::kInvalidParameters}; |
| EXPECT_TRUE(ReceiveAndExpect(kPairingPublicKeyCmd, kExpectedFailure)); |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceivePeerPublicKeyTwice) { |
| phase_2_sc()->Start(); |
| const auto kPairingPublicKeyCmd = |
| MakeCmd(kPairingPublicKey, peer_key().GetSerializedPublicKey()); |
| fake_chan()->Receive(kPairingPublicKeyCmd); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingPublicKeyCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceiveConfirmValueBeforeStage1Fails) { |
| phase_2_sc()->Start(); |
| const auto kPairingConfirmCmd = |
| MakeCmd(kPairingConfirm, PairingConfirmValue{1}); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingConfirmCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceiveRandomValueBeforeStage1Fails) { |
| phase_2_sc()->Start(); |
| const auto kPairingRandomCmd = MakeCmd(kPairingRandom, PairingRandomValue{1}); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingRandomCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceiveDhKeyCheckValueBeforeStage1Fails) { |
| phase_2_sc()->Start(); |
| const auto kPairingDHKeyCheckCmd = |
| MakeCmd(kPairingDHKeyCheck, PairingDHKeyCheckValueE{1}); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingDHKeyCheckCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceiveConfirmValueAfterStage1Fails) { |
| FastForwardToDhKeyCheck(); |
| const auto kPairingConfirmCmd = |
| MakeCmd(kPairingConfirm, PairingConfirmValue{1}); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingConfirmCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ReceiveRandomValueAfterStage1Fails) { |
| FastForwardToDhKeyCheck(); |
| const auto kPairingRandomCmd = MakeCmd(kPairingRandom, PairingRandomValue{1}); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingRandomCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, |
| InitiatorReceiveDhKeyCheckWhileWaitingForConfirmFails) { |
| NewPhase2SecureConnections(Role::kInitiator); |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_confirm_delegate( |
| [&](ConfirmCallback cb) { confirm_cb = std::move(cb); }); |
| LtkAndChecks expected_stage2_vals = FastForwardToDhKeyCheck(); |
| ASSERT_TRUE(confirm_cb); |
| // Receiving the peer DHKey check before user confirmation should fail as |
| // initiator. |
| const auto kPairingDHKeyCheckCmd = |
| MakeCmd(kPairingDHKeyCheck, expected_stage2_vals.dhkey_check_b); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kUnspecifiedReason}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingDHKeyCheckCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, Stage1JustWorksErrorPropagates) { |
| NewPhase2SecureConnections(Role::kInitiator, PairingMethod::kJustWorks); |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_confirm_delegate( |
| [&](ConfirmCallback cb) { confirm_cb = std::move(cb); }); |
| FastForwardPublicKeyExchange(); |
| |
| Code sent_code = kPairingFailed; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, std::ignore) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| MatchingPair stage1_vals = GenerateMatchingConfirmAndRandom(); |
| ReceiveCmd<PairingConfirmValue>(kPairingConfirm, stage1_vals.confirm); |
| RunUntilIdle(); |
| ASSERT_EQ(kPairingRandom, sent_code); |
| UInt128 mismatched_random = stage1_vals.random; |
| mismatched_random[0] += 1; |
| const auto kMismatchedPairingRandomCmd = |
| MakeCmd(kPairingRandom, mismatched_random); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kConfirmValueFailed}; |
| ASSERT_TRUE(ReceiveAndExpect(kMismatchedPairingRandomCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, Stage1PasskeyErrorPropagates) { |
| NewPhase2SecureConnections(Role::kInitiator, |
| PairingMethod::kPasskeyEntryDisplay); |
| uint32_t passkey; |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_display_delegate([&](uint32_t disp_passkey, |
| Delegate::DisplayMethod method, |
| ConfirmCallback cb) { |
| ASSERT_EQ(Delegate::DisplayMethod::kPeerEntry, method); |
| confirm_cb = std::move(cb); |
| passkey = disp_passkey; |
| }); |
| FastForwardPublicKeyExchange(); |
| |
| ASSERT_TRUE(confirm_cb); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kPasskeyEntryFailed}; |
| bool failure_sent = false; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| ASSERT_TRUE(ContainersEqual(kExpectedFailure, *sdu)); |
| failure_sent = true; |
| }, |
| dispatcher()); |
| confirm_cb(false); |
| RunUntilIdle(); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| ASSERT_TRUE(failure_sent); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, InitiatorReceiveWrongDhKeyCheckFails) { |
| NewPhase2SecureConnections(Role::kInitiator); |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_confirm_delegate( |
| [&](ConfirmCallback cb) { confirm_cb = std::move(cb); }); |
| LtkAndChecks expected_stage2_vals = FastForwardToDhKeyCheck(); |
| ASSERT_TRUE(confirm_cb); |
| Code sent_code = kPairingFailed; |
| UInt128 sent_payload; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, sent_payload) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| confirm_cb(true); |
| RunUntilIdle(); |
| ASSERT_EQ(kPairingDHKeyCheck, sent_code); |
| // As initiator, we expect the dhkey_check_b value, not the a. |
| const auto kPairingDHKeyCheckCmd = |
| MakeCmd(kPairingDHKeyCheck, expected_stage2_vals.dhkey_check_a); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kDHKeyCheckFailed}; |
| ASSERT_TRUE(ReceiveAndExpect(kPairingDHKeyCheckCmd, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| ASSERT_EQ(0, phase_2_complete_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, InitiatorFlowSuccessJustWorks) { |
| NewPhase2SecureConnections(Role::kInitiator, PairingMethod::kJustWorks); |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_confirm_delegate( |
| [&](ConfirmCallback cb) { confirm_cb = std::move(cb); }); |
| FastForwardPublicKeyExchange(); |
| |
| Code sent_code = kPairingFailed; |
| UInt128 sent_payload; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, sent_payload) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| MatchingPair stage1_vals = GenerateMatchingConfirmAndRandom(); |
| ReceiveCmd<PairingConfirmValue>(kPairingConfirm, stage1_vals.confirm); |
| RunUntilIdle(); |
| ASSERT_EQ(kPairingRandom, sent_code); |
| UInt128 local_rand = sent_payload; |
| ASSERT_FALSE(confirm_cb); |
| ReceiveCmd(kPairingRandom, stage1_vals.random); |
| RunUntilIdle(); |
| ASSERT_TRUE(confirm_cb); |
| |
| LtkAndChecks expected_stage2_vals = |
| GenerateLtkAndChecks(local_rand, stage1_vals.random); |
| confirm_cb(true); |
| RunUntilIdle(); |
| // After receiving user confirmation, we should send (the correct) DHKey Check |
| // Ea |
| ASSERT_EQ(kPairingDHKeyCheck, sent_code); |
| ASSERT_EQ(expected_stage2_vals.dhkey_check_a, sent_payload); |
| ReceiveCmd(kPairingDHKeyCheck, expected_stage2_vals.dhkey_check_b); |
| RunUntilIdle(); |
| ASSERT_EQ(1, phase_2_complete_count()); |
| // We should generate the same LTK on "both sides" |
| ASSERT_EQ(expected_stage2_vals.ltk, ltk()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, InitiatorFlowSuccessNumericComparison) { |
| NewPhase2SecureConnections(Role::kInitiator, |
| PairingMethod::kNumericComparison); |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_display_delegate( |
| [&](uint32_t, Delegate::DisplayMethod method, ConfirmCallback cb) { |
| ASSERT_EQ(Delegate::DisplayMethod::kComparison, method); |
| confirm_cb = std::move(cb); |
| }); |
| FastForwardPublicKeyExchange(); |
| |
| Code sent_code = kPairingFailed; |
| UInt128 sent_payload; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, sent_payload) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| MatchingPair stage1_vals = GenerateMatchingConfirmAndRandom(); |
| ReceiveCmd(kPairingConfirm, stage1_vals.confirm); |
| RunUntilIdle(); |
| EXPECT_EQ(kPairingRandom, sent_code); |
| UInt128 initiator_random = sent_payload; |
| ASSERT_FALSE(confirm_cb); |
| |
| ReceiveCmd(kPairingRandom, stage1_vals.random); |
| RunUntilIdle(); |
| ASSERT_TRUE(confirm_cb); |
| confirm_cb(true); |
| RunUntilIdle(); |
| LtkAndChecks vals = |
| GenerateLtkAndChecks(initiator_random, stage1_vals.random); |
| EXPECT_EQ(kPairingDHKeyCheck, sent_code); |
| EXPECT_EQ(vals.dhkey_check_a, sent_payload); |
| ReceiveCmd(kPairingDHKeyCheck, vals.dhkey_check_b); |
| RunUntilIdle(); |
| ASSERT_EQ(vals.ltk, ltk()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, InitiatorFlowSuccessPasskeyEntryDisplay) { |
| NewPhase2SecureConnections(Role::kInitiator, |
| PairingMethod::kPasskeyEntryDisplay); |
| uint32_t passkey; |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_display_delegate([&](uint32_t disp_passkey, |
| Delegate::DisplayMethod method, |
| ConfirmCallback cb) { |
| ASSERT_EQ(Delegate::DisplayMethod::kPeerEntry, method); |
| confirm_cb = std::move(cb); |
| passkey = disp_passkey; |
| }); |
| FastForwardPublicKeyExchange(); |
| ASSERT_TRUE(confirm_cb); |
| Code sent_code = kPairingFailed; |
| UInt128 sent_payload; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, sent_payload) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| confirm_cb(true); |
| MatchingPair stage1_vals; |
| UInt128 last_sent_rand; |
| for (size_t i = 0; i < 20; ++i) { |
| const uint8_t r = (passkey & (1 << i)) ? 0x81 : 0x80; |
| stage1_vals = GenerateMatchingConfirmAndRandom(r); |
| RunUntilIdle(); |
| |
| ASSERT_EQ(kPairingConfirm, sent_code); |
| PairingConfirmValue init_confirm = sent_payload; |
| ReceiveCmd(kPairingConfirm, stage1_vals.confirm); |
| RunUntilIdle(); |
| |
| ASSERT_EQ(kPairingRandom, sent_code); |
| last_sent_rand = sent_payload; |
| EXPECT_EQ( |
| GenerateConfirmValue(sent_payload, /*gen_initiator_confirm=*/true, r), |
| init_confirm); |
| ReceiveCmd(kPairingRandom, stage1_vals.random); |
| } |
| LtkAndChecks vals = GenerateLtkAndChecks( |
| last_sent_rand, stage1_vals.random, uint64_t{passkey}); |
| RunUntilIdle(); |
| EXPECT_EQ(kPairingDHKeyCheck, sent_code); |
| EXPECT_EQ(vals.dhkey_check_a, sent_payload); |
| ReceiveCmd(kPairingDHKeyCheck, vals.dhkey_check_b); |
| RunUntilIdle(); |
| ASSERT_EQ(vals.ltk, ltk()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, InitiatorFlowSuccessPasskeyEntryInput) { |
| NewPhase2SecureConnections(Role::kInitiator, |
| PairingMethod::kPasskeyEntryInput); |
| PasskeyResponseCallback passkey_cb = nullptr; |
| listener()->set_request_passkey_delegate( |
| [&](PasskeyResponseCallback cb) { passkey_cb = std::move(cb); }); |
| FastForwardPublicKeyExchange(); |
| ASSERT_TRUE(passkey_cb); |
| Code sent_code = kPairingFailed; |
| UInt128 sent_payload; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, sent_payload) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| const int64_t passkey = 123456; |
| passkey_cb(passkey); |
| MatchingPair stage1_vals; |
| UInt128 last_sent_rand; |
| for (size_t i = 0; i < 20; ++i) { |
| const uint8_t r = (passkey & (1 << i)) ? 0x81 : 0x80; |
| stage1_vals = GenerateMatchingConfirmAndRandom(r); |
| RunUntilIdle(); |
| |
| ASSERT_EQ(kPairingConfirm, sent_code); |
| PairingConfirmValue init_confirm = sent_payload; |
| ReceiveCmd(kPairingConfirm, stage1_vals.confirm); |
| RunUntilIdle(); |
| |
| ASSERT_EQ(kPairingRandom, sent_code); |
| last_sent_rand = sent_payload; |
| EXPECT_EQ( |
| GenerateConfirmValue(sent_payload, /*gen_initiator_confirm=*/true, r), |
| init_confirm); |
| ReceiveCmd(kPairingRandom, stage1_vals.random); |
| } |
| LtkAndChecks vals = GenerateLtkAndChecks( |
| last_sent_rand, stage1_vals.random, uint64_t{passkey}); |
| RunUntilIdle(); |
| EXPECT_EQ(kPairingDHKeyCheck, sent_code); |
| EXPECT_EQ(vals.dhkey_check_a, sent_payload); |
| ReceiveCmd(kPairingDHKeyCheck, vals.dhkey_check_b); |
| RunUntilIdle(); |
| ASSERT_EQ(vals.ltk, ltk()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ResponderReceiveWrongDhKeyCheckFails) { |
| NewPhase2SecureConnections(Role::kResponder); |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_confirm_delegate( |
| [&](ConfirmCallback cb) { confirm_cb = std::move(cb); }); |
| LtkAndChecks expected_stage2_vals = FastForwardToDhKeyCheck(); |
| ASSERT_TRUE(confirm_cb); |
| Code sent_code = kPairingFailed; |
| UInt128 sent_payload; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, sent_payload) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| confirm_cb(true); |
| RunUntilIdle(); |
| // As responder, we expect the dhkey_check_a value, not the b. |
| const auto kPairingDHKeyCheckCmdWithBValue = |
| MakeCmd(kPairingDHKeyCheck, expected_stage2_vals.dhkey_check_b); |
| const StaticByteBuffer<PacketSize<ErrorCode>()> kExpectedFailure{ |
| kPairingFailed, ErrorCode::kDHKeyCheckFailed}; |
| ASSERT_TRUE( |
| ReceiveAndExpect(kPairingDHKeyCheckCmdWithBValue, kExpectedFailure)); |
| ASSERT_EQ(1, listener()->pairing_error_count()); |
| ASSERT_EQ(0, phase_2_complete_count()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, ResponderFlowSuccessJustWorks) { |
| NewPhase2SecureConnections(Role::kResponder, PairingMethod::kJustWorks); |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_confirm_delegate( |
| [&](ConfirmCallback cb) { confirm_cb = std::move(cb); }); |
| PairingConfirmValue responder_confirm = *FastForwardPublicKeyExchange(); |
| |
| Code sent_code = kPairingFailed; |
| UInt128 sent_payload; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, sent_payload) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| ASSERT_FALSE(confirm_cb); |
| const PairingRandomValue kInitiatorRand{1}; |
| ReceiveCmd(kPairingRandom, kInitiatorRand); |
| RunUntilIdle(); |
| |
| ASSERT_EQ(kPairingRandom, sent_code); |
| UInt128 responder_rand = sent_payload; |
| ASSERT_EQ( |
| GenerateConfirmValue(responder_rand, /*gen_initiator_confirm=*/false), |
| responder_confirm); |
| ASSERT_TRUE(confirm_cb); |
| LtkAndChecks expected_stage2_vals = |
| GenerateLtkAndChecks(kInitiatorRand, responder_rand); |
| // After receiving user confirmation & the peer dhkey check, we should send |
| // the DHKey Check Eb. |
| confirm_cb(true); |
| ReceiveCmd(kPairingDHKeyCheck, expected_stage2_vals.dhkey_check_a); |
| RunUntilIdle(); |
| ASSERT_EQ(kPairingDHKeyCheck, sent_code); |
| ASSERT_EQ(expected_stage2_vals.dhkey_check_b, sent_payload); |
| ASSERT_EQ(1, phase_2_complete_count()); |
| // We should generate the same LTK on "both sides" |
| ASSERT_EQ(expected_stage2_vals.ltk, ltk()); |
| } |
| |
| TEST_F(Phase2SecureConnectionsTest, |
| ResponderReceiveDhKeyCheckWhileWaitingForConfirmSuccess) { |
| NewPhase2SecureConnections(Role::kResponder, PairingMethod::kJustWorks); |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_confirm_delegate( |
| [&](ConfirmCallback cb) { confirm_cb = std::move(cb); }); |
| LtkAndChecks expected_stage2_vals = FastForwardToDhKeyCheck(); |
| |
| Code sent_code; |
| PairingDHKeyCheckValueE sent_dhkey_check; |
| fake_chan()->SetSendCallback( |
| [&](ByteBufferPtr sdu) { |
| std::tie(sent_code, sent_dhkey_check) = |
| ExtractCodeAnd128BitCmd(std::move(sdu)); |
| }, |
| dispatcher()); |
| ASSERT_TRUE(confirm_cb); |
| // Receiving the peer DHKey check before user confirmation should work as |
| // responder. |
| ReceiveCmd(kPairingDHKeyCheck, expected_stage2_vals.dhkey_check_a); |
| RunUntilIdle(); |
| confirm_cb(true); |
| RunUntilIdle(); |
| |
| ASSERT_EQ(kPairingDHKeyCheck, sent_code); |
| ASSERT_EQ(expected_stage2_vals.dhkey_check_b, sent_dhkey_check); |
| ASSERT_EQ(1, phase_2_complete_count()); |
| } |
| |
| } // namespace |
| } // namespace bt::sm |