/****************************************************************************** * * Copyright 2019 The Android Open Source Project * * 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: * * http://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 #include #include #include "os/log.h" #include "os/rand.h" #include "security/pairing_handler_le.h" #include "security/test/mocks.h" using ::testing::_; using ::testing::Eq; using ::testing::Field; using ::testing::VariantWith; using bluetooth::os::GenerateRandom; using bluetooth::security::CommandView; namespace bluetooth { namespace security { namespace { CommandView BuilderToView(std::unique_ptr builder) { std::shared_ptr> packet_bytes = std::make_shared>(); BitInserter it(*packet_bytes); builder->Serialize(it); PacketView packet_bytes_view(packet_bytes); auto temp_cmd_view = CommandView::Create(packet_bytes_view); return CommandView::Create(temp_cmd_view); } class PairingResultHandlerMock { public: MOCK_CONST_METHOD1(OnPairingFinished, void(PairingResultOrFailure)); }; std::unique_ptr pairingResult; LeSecurityInterfaceMock leSecurityMock; UIMock uiMock; void OnPairingFinished(PairingResultOrFailure r) { if (std::holds_alternative(r)) { LOG(INFO) << "pairing with " << std::get(r).connection_address << " finished successfully!"; } else { LOG(INFO) << "pairing with ... failed!"; } pairingResult->OnPairingFinished(r); } } // namespace class PairingHandlerUnitTest : public testing::Test { protected: void SetUp() { thread_ = new os::Thread("test_thread", os::Thread::Priority::NORMAL); handler_ = new os::Handler(thread_); bidi_queue_ = std::make_unique, packet::BasePacketBuilder>>(10); up_buffer_ = std::make_unique>(bidi_queue_->GetUpEnd()); bidi_queue_->GetDownEnd()->RegisterDequeue( handler_, common::Bind(&PairingHandlerUnitTest::L2CAP_SendSmp, common::Unretained(this))); pairingResult.reset(new PairingResultHandlerMock); } void TearDown() { pairingResult.reset(); bidi_queue_->GetDownEnd()->UnregisterDequeue(); handler_->Clear(); delete handler_; delete thread_; ::testing::Mock::VerifyAndClearExpectations(&leSecurityMock); ::testing::Mock::VerifyAndClearExpectations(&uiMock); } void L2CAP_SendSmp() { std::unique_ptr builder = bidi_queue_->GetDownEnd()->TryDequeue(); outgoing_l2cap_packet_ = BuilderToView(std::move(builder)); outgoing_l2cap_packet_->IsValid(); outgoing_l2cap_blocker_.notify_one(); } std::optional WaitForOutgoingL2capPacket() { std::mutex mutex; std::unique_lock lock(mutex); // It is possible that we lost wakeup from condition_variable, check if data is already waiting to be processed if (outgoing_l2cap_packet_ != std::nullopt) { std::optional tmp = std::nullopt; outgoing_l2cap_packet_.swap(tmp); return tmp; } // Data not ready yet, wait for it. if (outgoing_l2cap_blocker_.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) { return std::nullopt; } std::optional tmp = std::nullopt; outgoing_l2cap_packet_.swap(tmp); return tmp; } public: os::Thread* thread_; os::Handler* handler_; std::unique_ptr, packet::BasePacketBuilder>> bidi_queue_; std::unique_ptr> up_buffer_; std::condition_variable outgoing_l2cap_blocker_; std::optional outgoing_l2cap_packet_ = std::nullopt; }; InitialInformations initial_informations{ .my_role = hci::Role::CENTRAL, .my_connection_address = {{}, hci::AddressType::PUBLIC_DEVICE_ADDRESS}, .my_identity_address = {{}, hci::AddressType::PUBLIC_DEVICE_ADDRESS}, .my_identity_resolving_key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, .myPairingCapabilities = {.io_capability = IoCapability::NO_INPUT_NO_OUTPUT, .oob_data_flag = OobDataFlag::NOT_PRESENT, .auth_req = AuthReqMaskBondingFlag | AuthReqMaskMitm | AuthReqMaskSc, .maximum_encryption_key_size = 16, .initiator_key_distribution = 0x03, .responder_key_distribution = 0x03}, .remotely_initiated = false, .remote_connection_address = {{}, hci::AddressType::RANDOM_DEVICE_ADDRESS}, .user_interface = &uiMock, .le_security_interface = &leSecurityMock, .OnPairingFinished = OnPairingFinished, }; TEST_F(PairingHandlerUnitTest, test_phase_1_failure) { initial_informations.proper_l2cap_interface = up_buffer_.get(); initial_informations.l2cap_handler = handler_; initial_informations.user_interface_handler = handler_; std::unique_ptr pairing_handler = std::make_unique(PairingHandlerLe::PHASE1, initial_informations); std::optional pairing_request = WaitForOutgoingL2capPacket(); EXPECT_TRUE(pairing_request.has_value()); EXPECT_EQ(pairing_request->GetCode(), Code::PAIRING_REQUEST); EXPECT_CALL(*pairingResult, OnPairingFinished(VariantWith(_))).Times(1); // SMP will waith for Pairing Response, once bad packet is received, it should stop the Pairing CommandView bad_pairing_response = BuilderToView(PairingRandomBuilder::Create({})); bad_pairing_response.IsValid(); pairing_handler->OnCommandView(bad_pairing_response); std::optional pairing_failure = WaitForOutgoingL2capPacket(); EXPECT_TRUE(pairing_failure.has_value()); EXPECT_EQ(pairing_failure->GetCode(), Code::PAIRING_FAILED); } TEST_F(PairingHandlerUnitTest, test_secure_connections_just_works) { initial_informations.proper_l2cap_interface = up_buffer_.get(); initial_informations.l2cap_handler = handler_; initial_informations.user_interface_handler = handler_; // we keep the pairing_handler as unique_ptr to better mimick how it's used // in the real world std::unique_ptr pairing_handler = std::make_unique(PairingHandlerLe::PHASE1, initial_informations); std::optional pairing_request_pkt = WaitForOutgoingL2capPacket(); EXPECT_TRUE(pairing_request_pkt.has_value()); EXPECT_EQ(pairing_request_pkt->GetCode(), Code::PAIRING_REQUEST); CommandView pairing_request = pairing_request_pkt.value(); auto pairing_response = BuilderToView( PairingResponseBuilder::Create(IoCapability::KEYBOARD_DISPLAY, OobDataFlag::NOT_PRESENT, AuthReqMaskBondingFlag | AuthReqMaskMitm | AuthReqMaskSc, 16, 0x03, 0x03)); pairing_handler->OnCommandView(pairing_response); // Phase 1 finished. // pairing public key std::optional public_key_pkt = WaitForOutgoingL2capPacket(); EXPECT_TRUE(public_key_pkt.has_value()); EXPECT_EQ(Code::PAIRING_PUBLIC_KEY, public_key_pkt->GetCode()); EcdhPublicKey my_public_key; auto ppkv = PairingPublicKeyView::Create(public_key_pkt.value()); ppkv.IsValid(); my_public_key.x = ppkv.GetPublicKeyX(); my_public_key.y = ppkv.GetPublicKeyY(); const auto [private_key, public_key] = GenerateECDHKeyPair(); pairing_handler->OnCommandView(BuilderToView(PairingPublicKeyBuilder::Create(public_key.x, public_key.y))); // DHKey exchange finished std::array dhkey = ComputeDHKey(private_key, my_public_key); // Phasae 2 Stage 1 start Octet16 ra, rb; ra = rb = {0}; Octet16 Nb = GenerateRandom<16>(); // Compute confirm Octet16 Cb = crypto_toolbox::f4((uint8_t*)public_key.x.data(), (uint8_t*)my_public_key.x.data(), Nb, 0); pairing_handler->OnCommandView(BuilderToView(PairingConfirmBuilder::Create(Cb))); // random std::optional random_pkt = WaitForOutgoingL2capPacket(); EXPECT_TRUE(random_pkt.has_value()); EXPECT_EQ(Code::PAIRING_RANDOM, random_pkt->GetCode()); auto prv = PairingRandomView::Create(random_pkt.value()); prv.IsValid(); Octet16 Na = prv.GetRandomValue(); pairing_handler->OnCommandView(BuilderToView(PairingRandomBuilder::Create(Nb))); // Start of authentication stage 2 uint8_t a[7]; uint8_t b[7]; memcpy(b, initial_informations.remote_connection_address.GetAddress().data(), hci::Address::kLength); b[6] = (uint8_t)initial_informations.remote_connection_address.GetAddressType(); memcpy(a, initial_informations.my_connection_address.GetAddress().data(), hci::Address::kLength); a[6] = (uint8_t)initial_informations.my_connection_address.GetAddressType(); Octet16 ltk, mac_key; crypto_toolbox::f5(dhkey.data(), Na, Nb, a, b, &mac_key, <k); PairingRequestView preqv = PairingRequestView::Create(pairing_request); PairingResponseView prspv = PairingResponseView::Create(pairing_response); preqv.IsValid(); prspv.IsValid(); std::array iocapA{static_cast(preqv.GetIoCapability()), static_cast(preqv.GetOobDataFlag()), preqv.GetAuthReq()}; std::array iocapB{static_cast(prspv.GetIoCapability()), static_cast(prspv.GetOobDataFlag()), prspv.GetAuthReq()}; Octet16 Ea = crypto_toolbox::f6(mac_key, Na, Nb, rb, iocapA.data(), a, b); Octet16 Eb = crypto_toolbox::f6(mac_key, Nb, Na, ra, iocapB.data(), b, a); std::optional dh_key_pkt = WaitForOutgoingL2capPacket(); EXPECT_TRUE(dh_key_pkt.has_value()); EXPECT_EQ(Code::PAIRING_DH_KEY_CHECK, dh_key_pkt->GetCode()); auto pdhkcv = PairingDhKeyCheckView::Create(dh_key_pkt.value()); pdhkcv.IsValid(); EXPECT_EQ(pdhkcv.GetDhKeyCheck(), Ea); pairing_handler->OnCommandView(BuilderToView(PairingDhKeyCheckBuilder::Create(Eb))); // Phase 2 finished // We don't care for the rest of the flow, let it die. } InitialInformations initial_informations_trsi{ .my_role = hci::Role::CENTRAL, .my_connection_address = hci::AddressWithType(), .my_identity_address = {{}, hci::AddressType::PUBLIC_DEVICE_ADDRESS}, .my_identity_resolving_key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, .myPairingCapabilities = {.io_capability = IoCapability::NO_INPUT_NO_OUTPUT, .oob_data_flag = OobDataFlag::NOT_PRESENT, .auth_req = AuthReqMaskBondingFlag | AuthReqMaskMitm | AuthReqMaskSc, .maximum_encryption_key_size = 16, .initiator_key_distribution = 0x03, .responder_key_distribution = 0x03}, .remotely_initiated = true, .remote_connection_address = hci::AddressWithType(), .user_interface = &uiMock, .le_security_interface = &leSecurityMock, .OnPairingFinished = OnPairingFinished, }; /* This test verifies that when remote peripheral device sends security request , and user * does accept the prompt, we do send pairing request */ TEST_F(PairingHandlerUnitTest, test_remote_peripheral_initiating) { initial_informations_trsi.proper_l2cap_interface = up_buffer_.get(); initial_informations_trsi.l2cap_handler = handler_; initial_informations_trsi.user_interface_handler = handler_; std::unique_ptr pairing_handler = std::make_unique(PairingHandlerLe::ACCEPT_PROMPT, initial_informations_trsi); // Simulate user accepting the pairing in UI pairing_handler->OnUiAction(PairingEvent::PAIRING_ACCEPTED, 0x01 /* Non-zero value means success */); std::optional pairing_request_pkt = WaitForOutgoingL2capPacket(); EXPECT_TRUE(pairing_request_pkt.has_value()); EXPECT_EQ(Code::PAIRING_REQUEST, pairing_request_pkt->GetCode()); // We don't care for the rest of the flow, let it die. pairing_handler.reset(); } InitialInformations initial_informations_trmi{ .my_role = hci::Role::PERIPHERAL, .my_connection_address = hci::AddressWithType(), .my_identity_address = {{}, hci::AddressType::PUBLIC_DEVICE_ADDRESS}, .my_identity_resolving_key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, .myPairingCapabilities = {.io_capability = IoCapability::NO_INPUT_NO_OUTPUT, .oob_data_flag = OobDataFlag::NOT_PRESENT, .auth_req = AuthReqMaskBondingFlag | AuthReqMaskMitm | AuthReqMaskSc, .maximum_encryption_key_size = 16, .initiator_key_distribution = 0x03, .responder_key_distribution = 0x03}, .remotely_initiated = true, .remote_connection_address = hci::AddressWithType(), .pairing_request = PairingRequestView::Create(BuilderToView(PairingRequestBuilder::Create( IoCapability::NO_INPUT_NO_OUTPUT, OobDataFlag::NOT_PRESENT, AuthReqMaskBondingFlag | AuthReqMaskMitm | AuthReqMaskSc, 16, 0x03, 0x03))), .user_interface = &uiMock, .le_security_interface = &leSecurityMock, .OnPairingFinished = OnPairingFinished, }; /* This test verifies that when remote device sends pairing request, and user does accept the prompt, we do send proper * reply back */ TEST_F(PairingHandlerUnitTest, test_remote_central_initiating) { initial_informations_trmi.proper_l2cap_interface = up_buffer_.get(); initial_informations_trmi.l2cap_handler = handler_; initial_informations_trmi.user_interface_handler = handler_; std::unique_ptr pairing_handler = std::make_unique(PairingHandlerLe::ACCEPT_PROMPT, initial_informations_trmi); // Simulate user accepting the pairing in UI pairing_handler->OnUiAction(PairingEvent::PAIRING_ACCEPTED, 0x01 /* Non-zero value means success */); std::optional pairing_response_pkt = WaitForOutgoingL2capPacket(); EXPECT_TRUE(pairing_response_pkt.has_value()); EXPECT_EQ(Code::PAIRING_RESPONSE, pairing_response_pkt->GetCode()); // Phase 1 finished. // We don't care for the rest of the flow, it's handled in in other tests. let it die. pairing_handler.reset(); } } // namespace security } // namespace bluetooth