| // 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_1.h" |
| |
| #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/macros.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/l2cap_defs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/mock_channel_test.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/testing/test_helpers.h" |
| |
| namespace bt::sm { |
| namespace { |
| |
| struct Phase1Args { |
| PairingRequestParams preq = PairingRequestParams(); |
| IOCapability io_capability = IOCapability::kNoInputNoOutput; |
| BondableMode bondable_mode = BondableMode::Bondable; |
| SecurityLevel level = SecurityLevel::kEncrypted; |
| bool sc_supported = false; |
| }; |
| |
| class Phase1Test : public l2cap::testing::MockChannelTest { |
| public: |
| Phase1Test() = default; |
| ~Phase1Test() override = default; |
| |
| protected: |
| void SetUp() override { NewPhase1(); } |
| |
| void TearDown() override { phase_1_ = nullptr; } |
| |
| void NewPhase1(Role role = Role::kInitiator, |
| Phase1Args phase_args = Phase1Args(), |
| bt::LinkType ll_type = bt::LinkType::kLE) { |
| l2cap::ChannelId cid = ll_type == bt::LinkType::kLE ? l2cap::kLESMPChannelId |
| : l2cap::kSMPChannelId; |
| uint16_t mtu = |
| phase_args.sc_supported ? l2cap::kMaxMTU : kNoSecureConnectionsMtu; |
| ChannelOptions options(cid, mtu); |
| options.link_type = ll_type; |
| |
| listener_ = std::make_unique<FakeListener>(); |
| l2cap::testing::FakeChannel::WeakPtr fake_chan = CreateFakeChannel(options); |
| sm_chan_ = std::make_unique<PairingChannel>(fake_chan->GetWeakPtr()); |
| auto complete_cb = [this](PairingFeatures features, |
| PairingRequestParams preq, |
| PairingResponseParams pres) { |
| feature_exchange_count_++; |
| features_ = features; |
| last_pairing_req_ = util::NewPdu(sizeof(PairingRequestParams)); |
| last_pairing_res_ = util::NewPdu(sizeof(PairingResponseParams)); |
| PacketWriter preq_writer(kPairingRequest, last_pairing_req_.get()); |
| PacketWriter pres_writer(kPairingResponse, last_pairing_res_.get()); |
| *preq_writer.mutable_payload<PairingRequestParams>() = preq; |
| *pres_writer.mutable_payload<PairingResponseParams>() = pres; |
| }; |
| if (role == Role::kInitiator) { |
| phase_1_ = Phase1::CreatePhase1Initiator(sm_chan_->GetWeakPtr(), |
| listener_->as_weak_ptr(), |
| phase_args.io_capability, |
| phase_args.bondable_mode, |
| phase_args.level, |
| std::move(complete_cb)); |
| } else { |
| phase_1_ = Phase1::CreatePhase1Responder(sm_chan_->GetWeakPtr(), |
| listener_->as_weak_ptr(), |
| phase_args.preq, |
| phase_args.io_capability, |
| phase_args.bondable_mode, |
| phase_args.level, |
| std::move(complete_cb)); |
| } |
| } |
| |
| Phase1* phase_1() { return phase_1_.get(); } |
| FakeListener* listener() { return listener_.get(); } |
| |
| int feature_exchange_count() { return feature_exchange_count_; } |
| PairingFeatures features() { return features_; } |
| ByteBuffer* last_preq() { return last_pairing_req_.get(); } |
| ByteBuffer* last_pres() { return last_pairing_res_.get(); } |
| |
| private: |
| std::unique_ptr<FakeListener> listener_; |
| std::unique_ptr<PairingChannel> sm_chan_; |
| std::unique_ptr<Phase1> phase_1_; |
| |
| int feature_exchange_count_ = 0; |
| PairingFeatures features_; |
| MutableByteBufferPtr last_pairing_req_; |
| MutableByteBufferPtr last_pairing_res_; |
| |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Phase1Test); |
| }; |
| |
| TEST_F(Phase1Test, FeatureExchangeStartDefaultParams) { |
| const StaticByteBuffer kRequest( |
| 0x01, // code: "Pairing Request" |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeStartCustomParams) { |
| auto phase_args = Phase1Args{.io_capability = IOCapability::kDisplayYesNo, |
| .bondable_mode = BondableMode::NonBondable, |
| .level = SecurityLevel::kAuthenticated, |
| .sc_supported = true}; |
| NewPhase1(Role::kInitiator, phase_args); |
| |
| const StaticByteBuffer kRequest( |
| 0x01, // code: "Pairing Request" |
| 0x01, // IO cap.: DisplayYesNo |
| 0x00, // OOB: not present |
| AuthReq::kMITM | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none - non-bondable mode |
| 0x00 // responder keys: none - non-bondable mode |
| ); |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeInitiatorWithIdentityInfo) { |
| listener()->set_identity_info(IdentityInfo()); |
| |
| const StaticByteBuffer kRequest( |
| 0x01, // code: "Pairing Request" |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| EXPECT_EQ(1, listener()->identity_info_count()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangePairingFailed) { |
| fake_chan()->Receive(StaticByteBuffer(0x05, // code: Pairing Failed |
| 0x05 // reason: Pairing Not Supported |
| )); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(Error(ErrorCode::kPairingNotSupported), listener()->last_error()); |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeLocalRejectsUnsupportedInitiatorKeys) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const auto kResponse = StaticByteBuffer( |
| 0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| 0x00, |
| 0x07, // encr. key size: 7 (default min) |
| KeyDistGen::kIdKey, // initiator keys - not listed in kRequest |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const StaticByteBuffer kFailure(0x05, // code: Pairing Failed |
| 0x0A // reason: Invalid Parameters |
| ); |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| |
| // We should receive a pairing response and reply back with Pairing Failed. |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| EXPECT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kInvalidParameters), listener()->last_error()); |
| EXPECT_EQ(0, feature_exchange_count()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeLocalRejectsUnsupportedResponderKeys) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const auto kResponse = StaticByteBuffer( |
| 0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| 0x00, |
| 0x07, // encr. key size: 7 (default min) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kSignKey // responder keys - kSignKey not in kRequest |
| ); |
| const StaticByteBuffer kFailure(0x05, // code: Pairing Failed |
| 0x0A // reason: Invalid Parameters |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| RunUntilIdle(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| // We should receive a pairing response and reply back with Pairing Failed. |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kInvalidParameters), listener()->last_error()); |
| EXPECT_EQ(0, feature_exchange_count()); |
| } |
| |
| // Pairing should fail if MITM is required but the I/O capabilities cannot |
| // provide it |
| TEST_F(Phase1Test, FeatureExchangeFailureAuthenticationRequirements) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kMITM, |
| 0x07, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys: it's |
| // OK to use fewer keys |
| // than in kRequest |
| ); |
| const StaticByteBuffer kFailure(0x05, // code: Pairing Failed |
| 0x03 // reason: Authentication requirements |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| // We should receive a pairing response and reply back with Pairing Failed. |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| EXPECT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kAuthenticationRequirements), |
| listener()->last_error()); |
| EXPECT_EQ(0, feature_exchange_count()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeFailureMalformedRequest) { |
| const auto kMalformedResponse = StaticByteBuffer( |
| 0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| 0x00, // AuthReq: empty |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey // initiator key dist.: encr. key only |
| // Missing last byte, responder key dist. |
| ); |
| const StaticByteBuffer kFailure(0x05, // code: Pairing Failed |
| 0x0A // reason: Invalid Parameters |
| ); |
| |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kMalformedResponse); |
| RunUntilIdle(); |
| EXPECT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kInvalidParameters), listener()->last_error()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeBothSupportSCFeaturesHaveSC) { |
| Phase1Args args; |
| args.sc_supported = true; |
| NewPhase1(Role::kInitiator, args); |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC, |
| 0x07, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| |
| EXPECT_TRUE(features().initiator); |
| EXPECT_TRUE(features().secure_connections); |
| ASSERT_TRUE(last_preq()); |
| ASSERT_TRUE(last_pres()); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeScIgnoresEncKeyBit) { |
| Phase1Args args; |
| args.sc_supported = true; |
| NewPhase1(Role::kInitiator, args); |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| const auto kResponse = |
| StaticByteBuffer(0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC, |
| 0x07, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| |
| EXPECT_TRUE(features().initiator); |
| EXPECT_TRUE(features().secure_connections); |
| // Even though both the pairing request and response had the EncKey bit set, |
| // because we resolved the features to secure connections, we zero the bit |
| // out. |
| EXPECT_FALSE(features().remote_key_distribution & KeyDistGen::kEncKey); |
| EXPECT_FALSE(features().remote_key_distribution & KeyDistGen::kEncKey); |
| ASSERT_TRUE(last_preq()); |
| ASSERT_TRUE(last_pres()); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeLocalSCRemoteNoSCFeaturesNoSc) { |
| Phase1Args args; |
| args.sc_supported = true; |
| NewPhase1(Role::kInitiator, args); |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| const auto kResponse = |
| StaticByteBuffer(0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag, |
| 0x07, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| |
| EXPECT_TRUE(features().initiator); |
| EXPECT_FALSE(features().secure_connections); |
| ASSERT_TRUE(last_preq()); |
| ASSERT_TRUE(last_pres()); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangePairingResponseLegacyJustWorks) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag, |
| 0x07, // encr. key size: 7 (default min) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| |
| EXPECT_TRUE(features().initiator); |
| EXPECT_EQ(PairingMethod::kJustWorks, features().method); |
| EXPECT_EQ(7, features().encryption_key_size); |
| EXPECT_TRUE(KeyDistGen::kEncKey & features().local_key_distribution); |
| EXPECT_TRUE(KeyDistGen::kEncKey & features().remote_key_distribution); |
| ASSERT_TRUE(last_preq()); |
| ASSERT_TRUE(last_pres()); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangePairingResponseLegacyMITM) { |
| auto phase_args = Phase1Args{.io_capability = IOCapability::kDisplayYesNo}; |
| NewPhase1(Role::kInitiator, phase_args); |
| |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x01, // IO cap.: DisplayYesNo |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x02, // IO cap.: KeyboardOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kMITM, |
| 0x07, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| |
| EXPECT_TRUE(features().initiator); |
| EXPECT_FALSE(features().secure_connections); |
| EXPECT_EQ(PairingMethod::kPasskeyEntryDisplay, features().method); |
| EXPECT_EQ(7, features().encryption_key_size); |
| EXPECT_FALSE(features().local_key_distribution); |
| EXPECT_TRUE(KeyDistGen::kEncKey & features().remote_key_distribution); |
| ASSERT_TRUE(last_preq() && last_pres()); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeEncryptionKeySize) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kMITM, |
| 0x02, // encr. key size: 2 (too small) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| const StaticByteBuffer kFailure(0x05, // code: Pairing Failed |
| 0x06 // reason: Encryption Key Size |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(0, feature_exchange_count()); |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kEncryptionKeySize), listener()->last_error()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeSecureAuthenticatedEncryptionKeySize) { |
| auto phase_args = Phase1Args{.io_capability = IOCapability::kKeyboardDisplay, |
| .level = SecurityLevel::kSecureAuthenticated, |
| .sc_supported = true}; |
| NewPhase1(Role::kInitiator, phase_args); |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kMITM | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| const StaticByteBuffer kResponse( |
| 0x02, // code: Pairing Response |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kMITM | AuthReq::kSC, |
| 0x0F, // encr. key size: 15, i.e. one byte less than max |
| // possible encryption key size. |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| const StaticByteBuffer kFailure(kPairingFailed, |
| ErrorCode::kEncryptionKeySize); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| // We should receive a pairing response and reply back with Pairing Failed; we |
| // enforce that all encryption keys are 16 bytes when `level` is set to |
| // SecureAuthenticated. |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(0, feature_exchange_count()); |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kEncryptionKeySize), listener()->last_error()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeSecureConnectionsRequiredNotPresent) { |
| auto phase_args = Phase1Args{.io_capability = IOCapability::kKeyboardDisplay, |
| .level = SecurityLevel::kSecureAuthenticated, |
| .sc_supported = true}; |
| NewPhase1(Role::kInitiator, phase_args); |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kMITM | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kMITM, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| const auto kFailure = |
| StaticByteBuffer(kPairingFailed, ErrorCode::kAuthenticationRequirements); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| // We should receive a pairing response and reply back with Pairing Failed; we |
| // enforce that Secure Connections is used when `level` is set to |
| // SecureAuthenticated. |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(0, feature_exchange_count()); |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kAuthenticationRequirements), |
| listener()->last_error()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeBothSupportScLinkKeyAndCt2GenerateH7CtKey) { |
| auto phase_args = Phase1Args{.sc_supported = true}; |
| NewPhase1(Role::kInitiator, phase_args); |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| IOCapability::kNoInputNoOutput, |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| const auto kResponse = |
| StaticByteBuffer(0x02, // code: Pairing Response |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| EXPECT_EQ(1, feature_exchange_count()); |
| ASSERT_TRUE(features().generate_ct_key.has_value()); |
| EXPECT_EQ(CrossTransportKeyAlgo::kUseH7, features().generate_ct_key); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangePeerDoesntSupportCt2GenerateH6CtKey) { |
| auto phase_args = Phase1Args{.sc_supported = true}; |
| NewPhase1(Role::kInitiator, phase_args); |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| IOCapability::kNoInputNoOutput, |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| EXPECT_EQ(1, feature_exchange_count()); |
| ASSERT_TRUE(features().generate_ct_key.has_value()); |
| EXPECT_EQ(CrossTransportKeyAlgo::kUseH6, features().generate_ct_key); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangePeerDoesntSupportScDoNotGenerateCtKey) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| IOCapability::kNoInputNoOutput, |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| // Although the peer supports CTKG through the hci_spec::LinkKey field and SC, |
| // locally we do not support SC, so CTKG is not allowed (v5.2 Vol. 3 Part |
| // H 3.6.1). |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().generate_ct_key.has_value()); |
| } |
| |
| TEST_F(Phase1Test, |
| FeatureExchangePeerSupportsCt2ButNotLinkKeyDoNotGenerateCtKey) { |
| auto phase_args = Phase1Args{.sc_supported = true}; |
| NewPhase1(Role::kInitiator, phase_args); |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| IOCapability::kNoInputNoOutput, |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| // The peer indicates support for the Link Key (CTKG) on only one of the |
| // Initiator/Responder Key Dist./Gen. fields, as such we do not generate the |
| // CT key. |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys - none |
| 0x00 // responder keys - none |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().generate_ct_key.has_value()); |
| } |
| |
| TEST_F(Phase1Test, |
| FeatureExchangePeerOnlyIndicatesOneLinkKeyDoNotGenerateCtKey) { |
| auto phase_args = Phase1Args{.sc_supported = true}; |
| NewPhase1(Role::kInitiator, phase_args); |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| IOCapability::kNoInputNoOutput, |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| // The peer indicates support for the Link Key (CTKG) on only one of the |
| // Initiator/Responder Key Dist./Gen. fields, as such we do not generate the |
| // CT key. |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x04, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kLinkKey, // initiator keys |
| 0x00 // responder keys - none |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().generate_ct_key.has_value()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderErrorCentral) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| 0x00, // AuthReq: no auth. request by default |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator key dist.: encr. key only |
| KeyDistGen::kEncKey // responder key dist.: encr. key only |
| ); |
| const StaticByteBuffer kFailure(0x05, // code: Pairing Failed |
| ErrorCode::kUnspecifiedReason); |
| |
| NewPhase1(Role::kInitiator); |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kRequest); |
| RunUntilIdle(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| // Verify that Pairing Requests are rejected by Phase1 - these are handled |
| // elsewhere in our stack. |
| TEST_F(Phase1Test, Phase1ResponderRejectsPairingRequest) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| 0x00, // AuthReq: no auth. request by default |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator key dist.: encr. key only |
| KeyDistGen::kEncKey // responder key dist.: encr. key only |
| ); |
| const StaticByteBuffer kFailure(0x05, // code: Pairing Failed |
| ErrorCode::kUnspecifiedReason); |
| |
| NewPhase1(Role::kResponder); |
| EXPECT_PACKET_OUT(kFailure); |
| fake_chan()->Receive(kRequest); |
| RunUntilIdle(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderBothSupportSCFeaturesHaveSC) { |
| const auto kResponse = |
| StaticByteBuffer(0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| Phase1Args args{.preq = |
| PairingRequestParams{ |
| .io_capability = IOCapability::kNoInputNoOutput, |
| .oob_data_flag = OOBDataFlag::kNotPresent, |
| .auth_req = AuthReq::kBondingFlag | AuthReq::kSC, |
| .max_encryption_key_size = 0x10, // 16, default max |
| .initiator_key_dist_gen = 0x00, |
| .responder_key_dist_gen = 0x01 // enc key |
| }, |
| .sc_supported = true}; |
| NewPhase1(Role::kResponder, args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| |
| EXPECT_FALSE(features().initiator); |
| EXPECT_TRUE(features().secure_connections); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderLocalSCRemoteNoSCFeaturesNoSC) { |
| const auto kResponse = |
| StaticByteBuffer(0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| Phase1Args args{ |
| .preq = |
| PairingRequestParams{ |
| .io_capability = IOCapability::kNoInputNoOutput, |
| .oob_data_flag = OOBDataFlag::kNotPresent, |
| .auth_req = AuthReq::kBondingFlag, |
| .max_encryption_key_size = 0x10, // 16, default max |
| .initiator_key_dist_gen = 0x00, |
| .responder_key_dist_gen = KeyDistGen::kEncKey // enc key |
| }, |
| .sc_supported = true}; |
| NewPhase1(Role::kResponder, args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| |
| EXPECT_FALSE(features().initiator); |
| EXPECT_FALSE(features().secure_connections); |
| } |
| |
| // Tests that the local responder does not request keys that the initiator |
| // cannot distribute. |
| TEST_F(Phase1Test, FeatureExchangeLocalResponderDoesNotRequestUnsupportedKeys) { |
| auto phase_args = |
| Phase1Args{.preq = PairingRequestParams{ |
| .io_capability = IOCapability::kNoInputNoOutput, |
| .oob_data_flag = OOBDataFlag::kNotPresent, |
| .auth_req = AuthReq::kBondingFlag, |
| .max_encryption_key_size = 16, |
| .initiator_key_dist_gen = 0x04, // sign key only |
| .responder_key_dist_gen = 0x00 // none |
| }}; |
| const StaticByteBuffer kResponse( |
| 0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none - we shouldn't request the SignKey |
| // as we don't support it |
| 0x00 // responder keys: none |
| ); |
| |
| NewPhase1(Role::kResponder, phase_args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().initiator); |
| EXPECT_FALSE(features().local_key_distribution); |
| EXPECT_FALSE(features().remote_key_distribution); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| } |
| |
| // Tests that we (as the responder) request to distribute identity information |
| // if available. |
| TEST_F(Phase1Test, FeatureExchangeResponderDistributesIdKey) { |
| auto phase_args = |
| Phase1Args{.preq = PairingRequestParams{ |
| .io_capability = IOCapability::kNoInputNoOutput, |
| .oob_data_flag = OOBDataFlag::kNotPresent, |
| .auth_req = 0x01, // bondable mode |
| .max_encryption_key_size = 0x10, // 16, default max |
| .initiator_key_dist_gen = 0x00, |
| .responder_key_dist_gen = KeyDistGen::kIdKey}}; |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| KeyDistGen::kIdKey // responder keys |
| ); |
| |
| NewPhase1(Role::kResponder, phase_args); |
| listener()->set_identity_info(IdentityInfo()); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().initiator); |
| EXPECT_TRUE(features().local_key_distribution & KeyDistGen::kIdKey); |
| EXPECT_FALSE(features().remote_key_distribution); |
| } |
| |
| // Tests that local responder doesn't respond with distribute ID info if |
| // available but not requested by the initiator. |
| TEST_F(Phase1Test, FeatureExchangeResponderRespectsInitiatorForIdKey) { |
| auto phase_args = |
| Phase1Args{.preq = PairingRequestParams{ |
| .io_capability = IOCapability::kNoInputNoOutput, |
| .oob_data_flag = OOBDataFlag::kNotPresent, |
| .auth_req = 0x01, // bondable mode |
| .max_encryption_key_size = 0x10, // 16, default max |
| .initiator_key_dist_gen = 0x00, |
| .responder_key_dist_gen = |
| 0x00 // Initiator explicitly does not request ID key |
| }}; |
| const StaticByteBuffer kResponse( |
| 0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| 0x00 // responder keys: none - we shouldn't distribute IdKey |
| // even though we have it |
| ); |
| |
| NewPhase1(Role::kResponder, phase_args); |
| listener()->set_identity_info(IdentityInfo()); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().initiator); |
| EXPECT_FALSE(features().local_key_distribution); |
| EXPECT_FALSE(features().remote_key_distribution); |
| } |
| |
| // Pairing should fail if MITM is required but the pairing method cannot provide |
| // it (due to I/O capabilities). |
| TEST_F(Phase1Test, FeatureExchangeResponderFailedAuthenticationRequirements) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kMITM, |
| 0x07, // encr. key size: 7 (default min) |
| KeyDistGen::kEncKey, // initiator key dist.: encr. key only |
| KeyDistGen::kEncKey // responder key dist.: encr. key only |
| ); |
| const StaticByteBuffer kFailure(0x05, // code: Pairing Failed |
| 0x03 // reason: Authentication requirements |
| ); |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .io_capability = IOCapability::kNoInputNoOutput}; |
| NewPhase1(Role::kResponder, phase_args); |
| |
| EXPECT_PACKET_OUT(kFailure); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kAuthenticationRequirements), |
| listener()->last_error()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderJustWorks) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag, |
| 0x07, // encr. key size: 7 (default min) |
| KeyDistGen::kIdKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kIdKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .io_capability = IOCapability::kNoInputNoOutput}; |
| |
| NewPhase1(Role::kResponder, phase_args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().initiator); |
| EXPECT_FALSE(features().secure_connections); |
| EXPECT_EQ(PairingMethod::kJustWorks, features().method); |
| EXPECT_EQ(7, features().encryption_key_size); |
| ASSERT_TRUE(last_preq()); |
| ASSERT_TRUE(last_pres()); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| |
| // We send the LTK when we are the responder. |
| EXPECT_TRUE(KeyDistGen::kEncKey & features().local_key_distribution); |
| |
| // The remote should send us identity information since we requested it and it |
| // promised it. |
| EXPECT_TRUE(KeyDistGen::kIdKey & features().remote_key_distribution); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderRequestInitiatorEncKey) { |
| const StaticByteBuffer kRequest(0x01, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag, |
| 0x07, // encr. key size: 7 (default min) |
| KeyDistGen::kEncKey, // initiator keys |
| 0x03 // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .io_capability = IOCapability::kNoInputNoOutput}; |
| |
| NewPhase1(Role::kResponder, phase_args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().initiator); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| |
| // We send the LTK as the initiator requested it. We also request the LTK from |
| // the initiator, which indicated it was capable of distributing it. |
| EXPECT_TRUE(KeyDistGen::kEncKey & features().local_key_distribution); |
| EXPECT_TRUE(KeyDistGen::kEncKey & features().remote_key_distribution); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderSendsOnlyRequestedKeys) { |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag, |
| 0x07, // encr. key size: 7 (default min) |
| KeyDistGen::kIdKey, // initiator keys |
| KeyDistGen::kIdKey // responder keys - responder |
| // doesn't have ID info, so |
| // won't send it |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kIdKey, // initiator keys |
| 0x00 // responder keys: none |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .io_capability = IOCapability::kNoInputNoOutput}; |
| NewPhase1(Role::kResponder, phase_args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| ASSERT_EQ(1, feature_exchange_count()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderMITM) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x02, // IO cap.: KeyboardOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kMITM, |
| 0x07, // encr. key size: 7 (default min) |
| KeyDistGen::kIdKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| 0x01, // IO cap.: DisplayYesNo |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kIdKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .io_capability = IOCapability::kDisplayYesNo}; |
| NewPhase1(Role::kResponder, phase_args); |
| |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().initiator); |
| EXPECT_FALSE(features().secure_connections); |
| EXPECT_EQ(PairingMethod::kPasskeyEntryDisplay, features().method); |
| EXPECT_EQ(7, features().encryption_key_size); |
| ASSERT_TRUE(last_preq()); |
| ASSERT_TRUE(last_pres()); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| |
| // We send the LTK when we are the responder. |
| EXPECT_TRUE(KeyDistGen::kEncKey & features().local_key_distribution); |
| |
| // The remote should send us identity information since we requested it and it |
| // promised it. |
| EXPECT_TRUE(KeyDistGen::kIdKey & features().remote_key_distribution); |
| |
| // We should have set bondable mode as both sides enabled it |
| EXPECT_TRUE(features().will_bond); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderRespectsDesiredLevel) { |
| const StaticByteBuffer kRequest(0x01, // code: Pairing Response |
| 0x01, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| const auto kResponse = StaticByteBuffer( |
| 0x02, // code: Pairing Response |
| 0x01, // IO cap.: KeyboardDisplay |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kMITM | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .io_capability = IOCapability::kDisplayYesNo, |
| .level = SecurityLevel::kAuthenticated, |
| .sc_supported = true}; |
| |
| NewPhase1(Role::kResponder, phase_args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(0, listener()->pairing_error_count()); |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_TRUE(features().secure_connections); |
| // We requested authenticated security, which led to Numeric Comparison as the |
| // pairing method. |
| EXPECT_EQ(PairingMethod::kNumericComparison, features().method); |
| ASSERT_TRUE(last_preq()); |
| ASSERT_TRUE(last_pres()); |
| EXPECT_TRUE(ContainersEqual(kRequest, *last_preq())); |
| EXPECT_TRUE(ContainersEqual(kResponse, *last_pres())); |
| } |
| |
| TEST_F(Phase1Test, |
| FeatureExchangeResponderRejectsMethodOfInsufficientSecurity) { |
| const StaticByteBuffer kRequest(0x01, // code: Pairing Response |
| 0x01, // IO cap.: DisplayYesNo |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey // responder keys |
| ); |
| const auto kFailure = |
| StaticByteBuffer(kPairingFailed, ErrorCode::kAuthenticationRequirements); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .io_capability = IOCapability::kDisplayYesNo, |
| .level = SecurityLevel::kAuthenticated, |
| .sc_supported = true}; |
| |
| NewPhase1(Role::kResponder, phase_args); |
| // Both devices have DisplayYesNo IOCap, but the initiator does not support |
| // Secure Connections so Numeric Comparison cannot be used. Neither device has |
| // a keyboard, so Passkey Entry cannot be used. Thus authenticated pairing, |
| // the desired level, cannot be met and we fail. |
| EXPECT_PACKET_OUT(kFailure); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kAuthenticationRequirements), |
| listener()->last_error()); |
| } |
| |
| TEST_F(Phase1Test, |
| FeatureExchangeResponderSecureAuthenticatedInitiatorNoInputNoOutput) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const auto kFailure = |
| StaticByteBuffer(kPairingFailed, ErrorCode::kAuthenticationRequirements); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .io_capability = IOCapability::kDisplayYesNo, |
| .level = SecurityLevel::kSecureAuthenticated, |
| .sc_supported = true}; |
| NewPhase1(Role::kResponder, phase_args); |
| |
| // Cannot perform SecureAuthenticated pairing with the peer's NoInputNoOutput |
| // IOCapabilities. |
| EXPECT_PACKET_OUT(kFailure); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kAuthenticationRequirements), |
| listener()->last_error()); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderDoesntSupportScDoNotGenerateCtKey) { |
| const StaticByteBuffer kRequest( |
| 0x01, // code: Pairing Request |
| IOCapability::kNoInputNoOutput, |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey | |
| KeyDistGen::kLinkKey // responder keys |
| ); |
| // Although the peer supports CTKG through the hci_spec::LinkKey field and SC, |
| // locally we do not support support SC, so CTKG is not allowed (v5.2 Vol. 3 |
| // Part H 3.6.1). |
| const StaticByteBuffer kResponse(0x02, // code: Pairing Response |
| IOCapability::kNoInputNoOutput, |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey // responder keys |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .sc_supported = false}; |
| NewPhase1(Role::kResponder, phase_args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().generate_ct_key.has_value()); |
| } |
| |
| TEST_F(Phase1Test, UnsupportedCommandDuringPairing) { |
| const StaticByteBuffer kRequest( |
| 0x01, // code: "Pairing Request" |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| |
| const StaticByteBuffer kFailed(0x05, // code: Pairing Failed |
| 0x07 // reason: Command Not Supported |
| ); |
| EXPECT_PACKET_OUT(kFailed); |
| fake_chan()->Receive(StaticByteBuffer(0xFF)); |
| RunUntilIdle(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kCommandNotSupported), listener()->last_error()); |
| } |
| |
| TEST_F(Phase1Test, OnSecurityRequestWhilePairing) { |
| const StaticByteBuffer kPairingRequest( |
| 0x01, // code: "Pairing Request" |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| EXPECT_PACKET_OUT(kPairingRequest); |
| phase_1()->Start(); |
| |
| const StaticByteBuffer kSecurityRequest(0x0B, // code: Security Request |
| 0x00 // auth_req |
| ); |
| |
| const StaticByteBuffer kFailed(0x05, // code: Pairing Failed |
| 0x08 // reason: unspecified reason |
| ); |
| EXPECT_PACKET_OUT(kFailed); |
| fake_chan()->Receive(kSecurityRequest); |
| RunUntilIdle(); |
| |
| // The security request while pairing should cause pairing to fail. |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| } |
| |
| // Tests whether a request from a device with bondable mode enabled to a peer |
| // with non-bondable mode enabled will return a PairingFeatures with |
| // non-bondable mode enabled, the desired result. |
| TEST_F(Phase1Test, FeatureExchangeInitiatorReqBondResNoBond) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag | AuthReq::kCT2, |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const auto kResponse = |
| StaticByteBuffer(0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| 0x00, |
| 0x07, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none |
| 0x00 // responder keys: none due to non-bondable mode |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| |
| // Should be in non-bondable mode even though the Initiator specifies bonding, |
| // as kResponse indicated that the peer follower does not support bonding. |
| EXPECT_FALSE(features().will_bond); |
| EXPECT_EQ(features().local_key_distribution, 0u); |
| EXPECT_EQ(features().remote_key_distribution, 0u); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeInitiatorReqNoBondResBond) { |
| auto phase_args = Phase1Args{.bondable_mode = BondableMode::NonBondable}; |
| NewPhase1(Role::kInitiator, phase_args); |
| const StaticByteBuffer kRequest(0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kCT2, // AuthReq: non-bondable |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| 0x00 // responder keys: none |
| ); |
| const auto kResponse = StaticByteBuffer( |
| 0x02, // code: Pairing Response |
| 0x00, // IO cap.: DisplayOnly |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag, |
| 0x07, // encr. key size: 7 (default min) |
| 0x00, // initiator keys: none - should not change request field |
| 0x00 // responder keys: none - should not change request field |
| ); |
| |
| EXPECT_PACKET_OUT(kRequest); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| fake_chan()->Receive(kResponse); |
| RunUntilIdle(); |
| |
| // Although kResponse is bondable, features should not bond as local device is |
| // non-bondable. |
| ASSERT_EQ(1, feature_exchange_count()); |
| EXPECT_FALSE(features().will_bond); |
| EXPECT_EQ(features().local_key_distribution, 0u); |
| EXPECT_EQ(features().remote_key_distribution, 0u); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderReqBondResNoBond) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kBondingFlag, |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| const auto kResponse = StaticByteBuffer( |
| 0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kCT2, // AuthReq: non-bondable to match local mode, |
| // even though kRequest was bondable |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none - should not change request field |
| 0x00 // responder keys: none - should not change request field |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>(), |
| .bondable_mode = BondableMode::NonBondable}; |
| NewPhase1(Role::kResponder, phase_args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| // Should be in non-bondable mode even though the peer requested bondable, as |
| // the Bearer was created in non-bondable mode. |
| EXPECT_FALSE(features().will_bond); |
| EXPECT_EQ(features().local_key_distribution, 0u); |
| EXPECT_EQ(features().remote_key_distribution, 0u); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderReqNoBondResNoBond) { |
| const StaticByteBuffer kRequest(0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| 0x00, // AuthReq: non-bondable |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| 0x00 // responder keys: none |
| ); |
| const auto kResponse = StaticByteBuffer( |
| 0x02, // code: Pairing Response |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kCT2, // AuthReq: non-bondable to match peer mode, even |
| // though Phase1 is bondable. |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none - should not change request field |
| 0x00 // responder keys: none - should not change request field |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{ |
| .preq = reader.payload<PairingRequestParams>(), |
| .bondable_mode = BondableMode::Bondable, // local mode is bondable, |
| // although peer is not |
| }; |
| NewPhase1(Role::kResponder, phase_args); |
| EXPECT_PACKET_OUT(kResponse); |
| phase_1()->Start(); |
| ASSERT_TRUE(AllExpectedPacketsSent()); |
| |
| // Should be in non-bondable mode even though Bearer was created in bondable |
| // mode as kRequest indicated that peer does not support bonding. |
| EXPECT_FALSE(features().will_bond); |
| EXPECT_EQ(features().local_key_distribution, 0u); |
| EXPECT_EQ(features().remote_key_distribution, 0u); |
| } |
| |
| TEST_F(Phase1Test, FeatureExchangeResponderReqNoBondWithKeys) { |
| const auto kRequest = StaticByteBuffer( |
| 0x01, // code: Pairing Request |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| AuthReq::kCT2, // AuthReq: non-bondable |
| 0x10, // encr. key size: 16 (default max) |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey, // initiator keys |
| KeyDistGen::kEncKey | KeyDistGen::kIdKey // responder keys |
| ); |
| |
| auto reader = PacketReader(&kRequest); |
| auto phase_args = Phase1Args{.preq = reader.payload<PairingRequestParams>()}; |
| NewPhase1(Role::kResponder, phase_args); |
| |
| const StaticByteBuffer kFailed(0x05, // code: Pairing Failed |
| 0x0A // reason: invalid parameters |
| ); |
| EXPECT_PACKET_OUT(kFailed); |
| phase_1()->Start(); |
| RunUntilIdle(); |
| |
| // Check that we fail with invalid parameters when a peer requests nonbondable |
| // mode with a non-zero KeyDistGen field |
| EXPECT_EQ(1, listener()->pairing_error_count()); |
| EXPECT_EQ(Error(ErrorCode::kInvalidParameters), listener()->last_error()); |
| } |
| |
| } // namespace |
| } // namespace bt::sm |