// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/internal/host/sm/security_request_phase.h" #include #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" #include "pw_bluetooth_sapphire/internal/host/common/macros.h" #include "pw_bluetooth_sapphire/internal/host/hci/connection.h" #include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h" #include "pw_bluetooth_sapphire/internal/host/sm/fake_phase_listener.h" #include "pw_bluetooth_sapphire/internal/host/sm/packet.h" #include "pw_bluetooth_sapphire/internal/host/sm/smp.h" #include "pw_bluetooth_sapphire/internal/host/sm/types.h" #include "pw_bluetooth_sapphire/internal/host/sm/util.h" #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" #include "pw_unit_test/framework.h" namespace bt::sm { namespace { struct SecurityRequestOptions { SecurityLevel requested_level = SecurityLevel::kEncrypted; BondableMode bondable = BondableMode::Bondable; }; class SecurityRequestPhaseTest : public l2cap::testing::FakeChannelTest { public: SecurityRequestPhaseTest() = default; ~SecurityRequestPhaseTest() override = default; protected: void SetUp() override { NewSecurityRequestPhase(); } void TearDown() override { security_request_phase_ = nullptr; } pw::async::HeapDispatcher& heap_dispatcher() { return heap_dispatcher_; } void NewSecurityRequestPhase( SecurityRequestOptions opts = SecurityRequestOptions(), bt::LinkType ll_type = bt::LinkType::kLE) { l2cap::ChannelId cid = ll_type == bt::LinkType::kLE ? l2cap::kLESMPChannelId : l2cap::kSMPChannelId; ChannelOptions options(cid); options.link_type = ll_type; fake_chan_ = CreateFakeChannel(options); sm_chan_ = std::make_unique(fake_chan_->GetWeakPtr()); fake_listener_ = std::make_unique(); security_request_phase_ = std::make_unique( sm_chan_->GetWeakPtr(), fake_listener_->as_weak_ptr(), opts.requested_level, opts.bondable, [this](PairingRequestParams preq) { last_pairing_req_ = preq; }); } l2cap::testing::FakeChannel* fake_chan() const { return fake_chan_.get(); } SecurityRequestPhase* security_request_phase() { return security_request_phase_.get(); } std::optional last_pairing_req() { return last_pairing_req_; } private: std::unique_ptr fake_chan_; std::unique_ptr sm_chan_; std::unique_ptr fake_listener_; std::unique_ptr security_request_phase_; std::optional last_pairing_req_; pw::async::HeapDispatcher heap_dispatcher_{dispatcher()}; BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(SecurityRequestPhaseTest); }; using SMP_SecurityRequestPhaseDeathTest = SecurityRequestPhaseTest; TEST_F(SecurityRequestPhaseTest, MakeEncryptedBondableSecurityRequest) { NewSecurityRequestPhase( SecurityRequestOptions{.requested_level = SecurityLevel::kEncrypted, .bondable = BondableMode::Bondable}); StaticByteBuffer kExpectedReq(kSecurityRequest, AuthReq::kBondingFlag); (void)heap_dispatcher().Post( [this](pw::async::Context /*ctx*/, pw::Status status) { if (status.ok()) { security_request_phase()->Start(); } }); ASSERT_TRUE(Expect(kExpectedReq)); EXPECT_EQ(SecurityLevel::kEncrypted, security_request_phase()->pending_security_request()); } TEST_F(SecurityRequestPhaseTest, MakeAuthenticatedNonBondableSecurityRequest) { NewSecurityRequestPhase( SecurityRequestOptions{.requested_level = SecurityLevel::kAuthenticated, .bondable = BondableMode::NonBondable}); // inclusive-language: ignore StaticByteBuffer kExpectedReq(kSecurityRequest, AuthReq::kMITM); (void)heap_dispatcher().Post( [this](pw::async::Context /*ctx*/, pw::Status status) { if (status.ok()) { security_request_phase()->Start(); } }); ASSERT_TRUE(Expect(kExpectedReq)); EXPECT_EQ(SecurityLevel::kAuthenticated, security_request_phase()->pending_security_request()); } TEST_F(SecurityRequestPhaseTest, MakeSecureAuthenticatedBondableSecurityRequest) { NewSecurityRequestPhase(SecurityRequestOptions{ .requested_level = SecurityLevel::kSecureAuthenticated}); // inclusive-language: disable StaticByteBuffer kExpectedReq( kSecurityRequest, AuthReq::kBondingFlag | AuthReq::kMITM | AuthReq::kSC); // inclusive-language: enable (void)heap_dispatcher().Post( [this](pw::async::Context /*ctx*/, pw::Status status) { if (status.ok()) { security_request_phase()->Start(); } }); ASSERT_TRUE(Expect(kExpectedReq)); EXPECT_EQ(SecurityLevel::kSecureAuthenticated, security_request_phase()->pending_security_request()); } TEST_F(SecurityRequestPhaseTest, HandlesChannelClosedGracefully) { fake_chan()->Close(); RunUntilIdle(); } TEST_F(SecurityRequestPhaseTest, PairingRequestAsResponderPassedThrough) { StaticByteBuffer()> preq_packet; PacketWriter writer(kPairingRequest, &preq_packet); PairingRequestParams generic_preq{.io_capability = IOCapability::kDisplayOnly, .oob_data_flag = OOBDataFlag::kNotPresent, .auth_req = AuthReq::kBondingFlag, .max_encryption_key_size = 0, .initiator_key_dist_gen = 0, .responder_key_dist_gen = 0}; *writer.mutable_payload() = generic_preq; ASSERT_FALSE(last_pairing_req().has_value()); fake_chan()->Receive(preq_packet); RunUntilIdle(); ASSERT_TRUE(last_pairing_req().has_value()); PairingRequestParams last_preq = last_pairing_req().value(); ASSERT_EQ(0, memcmp(&last_preq, &generic_preq, sizeof(PairingRequestParams))); } TEST_F(SecurityRequestPhaseTest, InboundSecurityRequestFails) { StaticByteBuffer()> pres_packet; PacketWriter writer(kPairingResponse, &pres_packet); *writer.mutable_payload() = PairingResponseParams(); bool message_sent = false; fake_chan()->SetSendCallback( [&message_sent](ByteBufferPtr sdu) { ValidPacketReader reader = ValidPacketReader::ParseSdu(sdu).value(); ASSERT_EQ(reader.code(), kPairingFailed); message_sent = true; }, dispatcher()); fake_chan()->Receive(pres_packet); RunUntilIdle(); ASSERT_FALSE(last_pairing_req().has_value()); ASSERT_TRUE(message_sent); } TEST_F(SecurityRequestPhaseTest, DropsInvalidPacket) { StaticByteBuffer bad_packet(0xFF); // 0xFF is not a valid SMP header code bool message_sent = false; fake_chan()->SetSendCallback( [&message_sent](ByteBufferPtr sdu) { ValidPacketReader reader = ValidPacketReader::ParseSdu(sdu).value(); ASSERT_EQ(reader.code(), kPairingFailed); message_sent = true; }, dispatcher()); fake_chan()->Receive(bad_packet); RunUntilIdle(); ASSERT_FALSE(last_pairing_req().has_value()); ASSERT_TRUE(message_sent); } } // namespace } // namespace bt::sm