| /****************************************************************************** |
| * |
| * 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 <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <memory> |
| |
| #include "os/log.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::security::CommandView; |
| |
| namespace bluetooth { |
| namespace security { |
| |
| namespace { |
| |
| CommandView BuilderToView(std::unique_ptr<BasePacketBuilder> builder) { |
| std::shared_ptr<std::vector<uint8_t>> packet_bytes = std::make_shared<std::vector<uint8_t>>(); |
| BitInserter it(*packet_bytes); |
| builder->Serialize(it); |
| PacketView<kLittleEndian> packet_bytes_view(packet_bytes); |
| auto temp_cmd_view = CommandView::Create(packet_bytes_view); |
| return CommandView::Create(temp_cmd_view); |
| } |
| |
| std::condition_variable outgoing_l2cap_blocker_; |
| std::optional<bluetooth::security::CommandView> outgoing_l2cap_packet_; |
| |
| bool WaitForOutgoingL2capPacket() { |
| std::mutex mutex; |
| std::unique_lock<std::mutex> lock(mutex); |
| if (outgoing_l2cap_blocker_.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) { |
| return false; |
| } |
| return true; |
| } |
| |
| class PairingResultHandlerMock { |
| public: |
| MOCK_CONST_METHOD1(OnPairingFinished, void(PairingResultOrFailure)); |
| }; |
| |
| std::unique_ptr<PairingResultHandlerMock> pairingResult; |
| LeSecurityInterfaceMock leSecurityMock; |
| UIMock uiMock; |
| |
| void OnPairingFinished(PairingResultOrFailure r) { |
| if (std::holds_alternative<PairingResult>(r)) { |
| LOG(INFO) << "pairing with " << std::get<PairingResult>(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<common::BidiQueue<packet::PacketView<packet::kLittleEndian>, packet::BasePacketBuilder>>(10); |
| up_buffer_ = std::make_unique<os::EnqueueBuffer<packet::BasePacketBuilder>>(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<packet::BasePacketBuilder> builder = bidi_queue_->GetDownEnd()->TryDequeue(); |
| |
| outgoing_l2cap_packet_ = BuilderToView(std::move(builder)); |
| outgoing_l2cap_packet_->IsValid(); |
| |
| outgoing_l2cap_blocker_.notify_one(); |
| } |
| |
| public: |
| os::Thread* thread_; |
| os::Handler* handler_; |
| std::unique_ptr<common::BidiQueue<packet::PacketView<packet::kLittleEndian>, packet::BasePacketBuilder>> bidi_queue_; |
| std::unique_ptr<os::EnqueueBuffer<packet::BasePacketBuilder>> up_buffer_; |
| }; |
| |
| InitialInformations initial_informations{ |
| .my_role = hci::Role::MASTER, |
| .my_connection_address = {{}, hci::AddressType::PUBLIC_DEVICE_ADDRESS}, |
| |
| .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}, |
| .ui_handler = &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_; |
| |
| std::unique_ptr<PairingHandlerLe> pairing_handler = |
| std::make_unique<PairingHandlerLe>(PairingHandlerLe::PHASE1, initial_informations); |
| |
| EXPECT_TRUE(WaitForOutgoingL2capPacket()); |
| EXPECT_EQ(outgoing_l2cap_packet_->GetCode(), Code::PAIRING_REQUEST); |
| |
| EXPECT_CALL(*pairingResult, OnPairingFinished(VariantWith<PairingFailure>(_))).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); |
| |
| EXPECT_TRUE(WaitForOutgoingL2capPacket()); |
| EXPECT_EQ(outgoing_l2cap_packet_->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_; |
| |
| // we keep the pairing_handler as unique_ptr to better mimick how it's used |
| // in the real world |
| std::unique_ptr<PairingHandlerLe> pairing_handler = |
| std::make_unique<PairingHandlerLe>(PairingHandlerLe::PHASE1, initial_informations); |
| |
| EXPECT_TRUE(WaitForOutgoingL2capPacket()); |
| EXPECT_EQ(outgoing_l2cap_packet_->GetCode(), Code::PAIRING_REQUEST); |
| CommandView pairing_request = outgoing_l2cap_packet_.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 |
| EXPECT_TRUE(WaitForOutgoingL2capPacket()); |
| EXPECT_EQ(Code::PAIRING_PUBLIC_KEY, outgoing_l2cap_packet_->GetCode()); |
| EcdhPublicKey my_public_key; |
| auto ppkv = PairingPublicKeyView::Create(outgoing_l2cap_packet_.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<uint8_t, 32> dhkey = ComputeDHKey(private_key, my_public_key); |
| |
| // Phasae 2 Stage 1 start |
| Octet16 ra, rb; |
| ra = rb = {0}; |
| |
| Octet16 Nb = PairingHandlerLe::GenerateRandom<16>(); |
| |
| // Compute confirm |
| Octet16 Cb = crypto_toolbox::f4((uint8_t*)my_public_key.x.data(), (uint8_t*)public_key.x.data(), Nb, 0); |
| |
| pairing_handler->OnCommandView(BuilderToView(PairingConfirmBuilder::Create(Cb))); |
| |
| // random |
| EXPECT_TRUE(WaitForOutgoingL2capPacket()); |
| EXPECT_EQ(Code::PAIRING_RANDOM, outgoing_l2cap_packet_->GetCode()); |
| auto prv = PairingRandomView::Create(outgoing_l2cap_packet_.value()); |
| prv.IsValid(); |
| Octet16 Na = prv.GetRandomValue(); |
| |
| // Compute Ca, compare |
| Octet16 Ca = crypto_toolbox::f4((uint8_t*)my_public_key.x.data(), (uint8_t*)public_key.x.data(), Na, 0); |
| |
| EXPECT_EQ(Ca, Cb); |
| |
| 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().address, 6); |
| b[6] = (uint8_t)initial_informations.remote_connection_address.GetAddressType(); |
| memcpy(a, initial_informations.my_connection_address.GetAddress().address, 6); |
| 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<uint8_t, 3> iocapA{static_cast<uint8_t>(preqv.GetIoCapability()), |
| static_cast<uint8_t>(preqv.GetOobDataFlag()), preqv.GetAuthReq()}; |
| std::array<uint8_t, 3> iocapB{static_cast<uint8_t>(prspv.GetIoCapability()), |
| static_cast<uint8_t>(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); |
| |
| EXPECT_TRUE(WaitForOutgoingL2capPacket()); |
| EXPECT_EQ(Code::PAIRING_DH_KEY_CHECK, outgoing_l2cap_packet_->GetCode()); |
| auto pdhkcv = PairingDhKeyCheckView::Create(outgoing_l2cap_packet_.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::MASTER, |
| .my_connection_address = hci::AddressWithType(), |
| |
| .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(), |
| .ui_handler = &uiMock, |
| .le_security_interface = &leSecurityMock, |
| .OnPairingFinished = OnPairingFinished, |
| }; |
| |
| /* This test verifies that when remote slave device sends security request , and user |
| * does accept the prompt, we do send pairing request */ |
| TEST_F(PairingHandlerUnitTest, test_remote_slave_initiating) { |
| initial_informations_trsi.proper_l2cap_interface = up_buffer_.get(); |
| initial_informations_trsi.l2cap_handler = handler_; |
| |
| std::unique_ptr<PairingHandlerLe> pairing_handler = |
| std::make_unique<PairingHandlerLe>(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 */); |
| |
| EXPECT_TRUE(WaitForOutgoingL2capPacket()); |
| EXPECT_EQ(Code::PAIRING_REQUEST, outgoing_l2cap_packet_->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::SLAVE, |
| .my_connection_address = hci::AddressWithType(), |
| |
| .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))), |
| .ui_handler = &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_master_initiating) { |
| initial_informations_trmi.proper_l2cap_interface = up_buffer_.get(); |
| initial_informations_trmi.l2cap_handler = handler_; |
| |
| std::unique_ptr<PairingHandlerLe> pairing_handler = |
| std::make_unique<PairingHandlerLe>(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 */); |
| |
| EXPECT_TRUE(WaitForOutgoingL2capPacket()); |
| EXPECT_EQ(Code::PAIRING_RESPONSE, outgoing_l2cap_packet_->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 |