1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connection.h"
16
17 #include <pw_assert/check.h>
18 #include <pw_bytes/endian.h>
19
20 #include <cinttypes>
21
22 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
23
24 namespace bt::hci {
25
LowEnergyConnection(hci_spec::ConnectionHandle handle,const DeviceAddress & local_address,const DeviceAddress & peer_address,const hci_spec::LEConnectionParameters & params,pw::bluetooth::emboss::ConnectionRole role,const Transport::WeakPtr & hci)26 LowEnergyConnection::LowEnergyConnection(
27 hci_spec::ConnectionHandle handle,
28 const DeviceAddress& local_address,
29 const DeviceAddress& peer_address,
30 const hci_spec::LEConnectionParameters& params,
31 pw::bluetooth::emboss::ConnectionRole role,
32 const Transport::WeakPtr& hci)
33 : AclConnection(handle, local_address, peer_address, role, hci),
34 WeakSelf(this),
35 parameters_(params) {
36 PW_CHECK(local_address.type() != DeviceAddress::Type::kBREDR);
37 PW_CHECK(peer_address.type() != DeviceAddress::Type::kBREDR);
38 PW_CHECK(hci.is_alive());
39
40 le_ltk_request_id_ = hci->command_channel()->AddLEMetaEventHandler(
41 hci_spec::kLELongTermKeyRequestSubeventCode,
42 fit::bind_member<&LowEnergyConnection::OnLELongTermKeyRequestEvent>(
43 this));
44 }
45
~LowEnergyConnection()46 LowEnergyConnection::~LowEnergyConnection() {
47 // Unregister HCI event handlers.
48 if (hci().is_alive()) {
49 hci()->command_channel()->RemoveEventHandler(le_ltk_request_id_);
50 }
51 }
52
StartEncryption()53 bool LowEnergyConnection::StartEncryption() {
54 if (state() != Connection::State::kConnected) {
55 bt_log(DEBUG, "hci", "connection closed; cannot start encryption");
56 return false;
57 }
58 if (role() != pw::bluetooth::emboss::ConnectionRole::CENTRAL) {
59 bt_log(DEBUG, "hci", "only the central can start encryption");
60 return false;
61 }
62 if (!ltk().has_value()) {
63 bt_log(DEBUG, "hci", "connection has no LTK; cannot start encryption");
64 return false;
65 }
66
67 auto cmd = CommandPacket::New<
68 pw::bluetooth::emboss::LEEnableEncryptionCommandWriter>(
69 hci_spec::kLEStartEncryption);
70 auto params = cmd.view_t();
71 params.connection_handle().Write(handle());
72 params.random_number().Write(ltk()->rand());
73 params.encrypted_diversifier().Write(ltk()->ediv());
74 params.long_term_key().CopyFrom(
75 pw::bluetooth::emboss::LinkKeyView(<k()->value()));
76
77 auto event_cb = [self = GetWeakPtr(), handle = handle()](
78 auto, const EventPacket& event) {
79 if (!self.is_alive()) {
80 return;
81 }
82
83 Result<> result = event.ToResult();
84 if (bt_is_error(result,
85 ERROR,
86 "hci-le",
87 "could not set encryption on link %#.04x",
88 handle)) {
89 if (self->encryption_change_callback()) {
90 self->encryption_change_callback()(result.take_error());
91 }
92 return;
93 }
94 bt_log(DEBUG, "hci-le", "requested encryption start on %#.04x", handle);
95 };
96 if (!hci().is_alive()) {
97 return false;
98 }
99 return hci()->command_channel()->SendCommand(
100 std::move(cmd), std::move(event_cb), hci_spec::kCommandStatusEventCode);
101 }
102
HandleEncryptionStatus(Result<bool> result,bool)103 void LowEnergyConnection::HandleEncryptionStatus(Result<bool> result,
104 bool /*key_refreshed*/) {
105 // "On an authentication failure, the connection shall be automatically
106 // disconnected by the Link Layer." (HCI_LE_Start_Encryption, Vol 2, Part E,
107 // 7.8.24). We make sure of this by telling the controller to disconnect.
108 if (result.is_error()) {
109 Disconnect(pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE);
110 }
111
112 if (!encryption_change_callback()) {
113 bt_log(DEBUG,
114 "hci",
115 "%#.4x: no encryption status callback assigned",
116 handle());
117 return;
118 }
119 encryption_change_callback()(result);
120 }
121
122 CommandChannel::EventCallbackResult
OnLELongTermKeyRequestEvent(const EventPacket & event)123 LowEnergyConnection::OnLELongTermKeyRequestEvent(const EventPacket& event) {
124 auto view = event.unchecked_view<
125 pw::bluetooth::emboss::LELongTermKeyRequestSubeventView>();
126 if (!view.IsComplete()) {
127 bt_log(WARN, "hci", "malformed LE LTK request event");
128 return CommandChannel::EventCallbackResult::kContinue;
129 }
130
131 hci_spec::ConnectionHandle handle = view.connection_handle().Read();
132
133 // Silently ignore the event as it isn't meant for this connection.
134 if (handle != this->handle()) {
135 return CommandChannel::EventCallbackResult::kContinue;
136 }
137
138 uint64_t rand = view.random_number().Read();
139 uint16_t ediv = view.encrypted_diversifier().Read();
140
141 bt_log(DEBUG,
142 "hci",
143 "LE LTK request - ediv: %#.4x, rand: %#.16" PRIx64,
144 ediv,
145 rand);
146
147 if (!hci().is_alive()) {
148 return CommandChannel::EventCallbackResult::kRemove;
149 }
150
151 auto status_cb = [](auto, const EventPacket& status_event) {
152 HCI_IS_ERROR(
153 status_event, TRACE, "hci-le", "failed to reply to LTK request");
154 };
155
156 // TODO(fxbug.dev/388607971): The LTK may be stale if BR/EDR cross-transport
157 // key derivation was performed. Maybe move this method to
158 // sm::SecurityManager.
159 if (ltk() && ltk()->rand() == rand && ltk()->ediv() == ediv) {
160 auto cmd = CommandPacket::New<
161 pw::bluetooth::emboss::LELongTermKeyRequestReplyCommandWriter>(
162 hci_spec::kLELongTermKeyRequestReply);
163 auto cmd_view = cmd.view_t();
164 cmd_view.connection_handle().Write(handle);
165 cmd_view.long_term_key().CopyFrom(
166 pw::bluetooth::emboss::LinkKeyView(<k()->value()));
167 hci()->command_channel()->SendCommand(cmd, std::move(status_cb));
168 } else {
169 bt_log(DEBUG, "hci-le", "LTK request rejected");
170
171 auto cmd = CommandPacket::New<
172 pw::bluetooth::emboss::LELongTermKeyRequestNegativeReplyCommandWriter>(
173 hci_spec::kLELongTermKeyRequestNegativeReply);
174 auto cmd_view = cmd.view_t();
175 cmd_view.connection_handle().Write(handle);
176 hci()->command_channel()->SendCommand(cmd, std::move(status_cb));
177 }
178
179 return CommandChannel::EventCallbackResult::kContinue;
180 }
181
182 } // namespace bt::hci
183