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/sm/phase_2_secure_connections.h"
16
17 #include <memory>
18 #include <optional>
19 #include <type_traits>
20
21 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
23 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
24 #include "pw_bluetooth_sapphire/internal/host/common/uint256.h"
25 #include "pw_bluetooth_sapphire/internal/host/hci/connection.h"
26 #include "pw_bluetooth_sapphire/internal/host/sm/ecdh_key.h"
27 #include "pw_bluetooth_sapphire/internal/host/sm/error.h"
28 #include "pw_bluetooth_sapphire/internal/host/sm/packet.h"
29 #include "pw_bluetooth_sapphire/internal/host/sm/pairing_phase.h"
30 #include "pw_bluetooth_sapphire/internal/host/sm/sc_stage_1_just_works_numeric_comparison.h"
31 #include "pw_bluetooth_sapphire/internal/host/sm/sc_stage_1_passkey.h"
32 #include "pw_bluetooth_sapphire/internal/host/sm/smp.h"
33 #include "pw_bluetooth_sapphire/internal/host/sm/types.h"
34 #include "pw_bluetooth_sapphire/internal/host/sm/util.h"
35
36 namespace bt::sm {
37
Phase2SecureConnections(PairingChannel::WeakPtr chan,Listener::WeakPtr listener,Role role,PairingFeatures features,PairingRequestParams preq,PairingResponseParams pres,const DeviceAddress & initiator_addr,const DeviceAddress & responder_addr,OnPhase2KeyGeneratedCallback cb)38 Phase2SecureConnections::Phase2SecureConnections(
39 PairingChannel::WeakPtr chan,
40 Listener::WeakPtr listener,
41 Role role,
42 PairingFeatures features,
43 PairingRequestParams preq,
44 PairingResponseParams pres,
45 const DeviceAddress& initiator_addr,
46 const DeviceAddress& responder_addr,
47 OnPhase2KeyGeneratedCallback cb)
48 : PairingPhase(std::move(chan), std::move(listener), role),
49 sent_local_ecdh_(false),
50 local_ecdh_(),
51 peer_ecdh_(),
52 stage_1_results_(),
53 sent_local_dhkey_check_(false),
54 features_(features),
55 preq_(preq),
56 pres_(pres),
57 initiator_addr_(initiator_addr),
58 responder_addr_(responder_addr),
59 weak_self_(this),
60 on_ltk_ready_(std::move(cb)) {
61 PW_CHECK(features_.secure_connections);
62 local_ecdh_ = LocalEcdhKey::Create();
63 PW_CHECK(local_ecdh_.has_value(), "failed to generate ecdh key");
64 PW_CHECK(sm_chan().SupportsSecureConnections());
65 SetPairingChannelHandler(*this);
66 }
67
Start()68 void Phase2SecureConnections::Start() {
69 PW_CHECK(!has_failed());
70 if (role() == Role::kInitiator) {
71 SendLocalPublicKey();
72 }
73 }
74
SendLocalPublicKey()75 void Phase2SecureConnections::SendLocalPublicKey() {
76 PW_CHECK(!sent_local_ecdh_);
77 // If in the responder role (i.e. not in the initiator role), attempting to
78 // send our Public Key before we've received the peer's is a programmer error.
79 PW_CHECK(role() == Role::kInitiator || peer_ecdh_.has_value());
80
81 sm_chan().SendMessage(kPairingPublicKey,
82 local_ecdh_->GetSerializedPublicKey());
83 sent_local_ecdh_ = true;
84 bt_log(DEBUG, "sm", "sent ecdh public key to peer");
85 if (role() == Role::kResponder) {
86 PW_CHECK(ecdh_exchange_complete());
87 StartAuthenticationStage1();
88 }
89 }
90
CanReceivePeerPublicKey() const91 fit::result<ErrorCode> Phase2SecureConnections::CanReceivePeerPublicKey()
92 const {
93 // Only allowed on the LE transport.
94 if (sm_chan().link_type() != bt::LinkType::kLE) {
95 bt_log(DEBUG, "sm", "cannot accept peer ecdh key value over BR/EDR");
96 return fit::error(ErrorCode::kCommandNotSupported);
97 }
98 if (peer_ecdh_.has_value()) {
99 bt_log(WARN, "sm", "received peer ecdh key twice!");
100 return fit::error(ErrorCode::kUnspecifiedReason);
101 }
102 if (role() == Role::kInitiator && !sent_local_ecdh_) {
103 bt_log(WARN,
104 "sm",
105 "received peer ecdh key before sending local key as initiator!");
106 return fit::error(ErrorCode::kUnspecifiedReason);
107 }
108 return fit::ok();
109 }
110
OnPeerPublicKey(PairingPublicKeyParams peer_pub_key)111 void Phase2SecureConnections::OnPeerPublicKey(
112 PairingPublicKeyParams peer_pub_key) {
113 if (fit::result result = CanReceivePeerPublicKey(); result.is_error()) {
114 Abort(result.error_value());
115 return;
116 }
117 std::optional<EcdhKey> maybe_peer_key =
118 EcdhKey::ParseFromPublicKey(peer_pub_key);
119 if (!maybe_peer_key.has_value()) {
120 bt_log(WARN, "sm", "unable to validate peer public ECDH key");
121 Abort(ErrorCode::kInvalidParameters);
122 return;
123 }
124
125 EcdhKey peer_key = std::move(*maybe_peer_key);
126 PW_CHECK(local_ecdh_.has_value());
127 if (peer_key.GetPublicKeyX() == local_ecdh_->GetPublicKeyX() &&
128 peer_key.GetPublicKeyY() == local_ecdh_->GetPublicKeyY()) {
129 // NOTE(fxbug.dev/42161018): When passkey entry is used, the non-initiating
130 // device can reflect our public key (which we send in plaintext). The
131 // inputs to the hash that we disclose bit- by-bit in ScStage1Passkey are
132 // the two public keys, our nonce, and one bit of our passkey, so if the
133 // peer uses the same public key then it can easily brute force for the
134 // passkey bit.
135 bt_log(WARN,
136 "sm",
137 "peer public ECDH key mirrors local public ECDH key "
138 "(sent_local_ecdh_: %d)",
139 sent_local_ecdh_);
140 Abort(ErrorCode::kInvalidParameters);
141 return;
142 }
143 peer_ecdh_ = std::move(peer_key);
144
145 if (role() == Role::kResponder) {
146 SendLocalPublicKey();
147 } else {
148 PW_CHECK(ecdh_exchange_complete());
149 StartAuthenticationStage1();
150 }
151 }
152
StartAuthenticationStage1()153 void Phase2SecureConnections::StartAuthenticationStage1() {
154 PW_CHECK(peer_ecdh_);
155 auto self = weak_self_.GetWeakPtr();
156 auto complete_cb = [self](fit::result<ErrorCode, ScStage1::Output> result) {
157 if (self.is_alive()) {
158 self->OnAuthenticationStage1Complete(result);
159 }
160 };
161 if (is_just_works_or_numeric_comparison()) {
162 bt_log(DEBUG, "sm", "Starting SC Stage 1 Numeric Comparison/Just Works");
163 stage_1_ = std::make_unique<ScStage1JustWorksNumericComparison>(
164 listener(),
165 role(),
166 local_ecdh_->GetPublicKeyX(),
167 peer_ecdh_->GetPublicKeyX(),
168 features_.method,
169 sm_chan().GetWeakPtr(),
170 std::move(complete_cb));
171 } else if (is_passkey_entry()) {
172 bt_log(DEBUG, "sm", "Starting SC Stage 1 Passkey Entry");
173 stage_1_ = std::make_unique<ScStage1Passkey>(listener(),
174 role(),
175 local_ecdh_->GetPublicKeyX(),
176 peer_ecdh_->GetPublicKeyX(),
177 features_.method,
178 sm_chan().GetWeakPtr(),
179 std::move(complete_cb));
180 } else { // method == kOutOfBand
181 // TODO(fxbug.dev/42138242): OOB would require significant extra plumbing &
182 // add security exposure not necessary for current goals. This is not
183 // spec-compliant but should allow us to pass PTS.
184 bt_log(WARN, "sm", "Received unsupported request for OOB pairing");
185 Abort(ErrorCode::kCommandNotSupported);
186 return;
187 }
188 stage_1_->Run();
189 }
190
OnAuthenticationStage1Complete(fit::result<ErrorCode,ScStage1::Output> result)191 void Phase2SecureConnections::OnAuthenticationStage1Complete(
192 fit::result<ErrorCode, ScStage1::Output> result) {
193 PW_CHECK(peer_ecdh_.has_value());
194 PW_CHECK(stage_1_);
195 PW_CHECK(!ltk_.has_value());
196 PW_CHECK(!expected_peer_dhkey_check_.has_value());
197 PW_CHECK(!local_dhkey_check_.has_value());
198 // The presence of Stage 1 determines whether to accept PairingConfirm/Random
199 // packets, so as it is now over, it should be reset.
200 stage_1_ = nullptr;
201
202 if (result.is_error()) {
203 Abort(result.error_value());
204 return;
205 }
206 stage_1_results_ = result.value();
207 StartAuthenticationStage2();
208 }
209
StartAuthenticationStage2()210 void Phase2SecureConnections::StartAuthenticationStage2() {
211 PW_CHECK(stage_1_results_.has_value());
212 std::optional<util::F5Results> maybe_f5 =
213 util::F5(local_ecdh_->CalculateDhKey(peer_ecdh_.value()),
214 stage_1_results_->initiator_rand,
215 stage_1_results_->responder_rand,
216 initiator_addr_,
217 responder_addr_);
218 if (!maybe_f5.has_value()) {
219 bt_log(WARN, "sm", "unable to calculate local LTK/MacKey");
220 Abort(ErrorCode::kUnspecifiedReason);
221 return;
222 }
223
224 // Ea & Eb are the DHKey Check values used/defined in V5.0 Vol. 3 Part H
225 // Section 2.3.5.6.5/3.5.7. (Ea/Eb) is sent by the (initiator/responder) to be
226 // verified by the (responder/initiator). This exchange verifies that each
227 // device knows the private key associated with its public key.
228 std::optional<PairingDHKeyCheckValueE> ea =
229 util::F6(maybe_f5->mac_key,
230 stage_1_results_->initiator_rand,
231 stage_1_results_->responder_rand,
232 stage_1_results_->responder_r,
233 preq_.auth_req,
234 preq_.oob_data_flag,
235 preq_.io_capability,
236 initiator_addr_,
237 responder_addr_);
238 std::optional<PairingDHKeyCheckValueE> eb =
239 util::F6(maybe_f5->mac_key,
240 stage_1_results_->responder_rand,
241 stage_1_results_->initiator_rand,
242 stage_1_results_->initiator_r,
243 pres_.auth_req,
244 pres_.oob_data_flag,
245 pres_.io_capability,
246 responder_addr_,
247 initiator_addr_);
248 if (!eb.has_value() || !ea.has_value()) {
249 bt_log(WARN, "sm", "unable to calculate dhkey check \"E\"");
250 Abort(ErrorCode::kUnspecifiedReason);
251 return;
252 }
253 local_dhkey_check_ = ea;
254 expected_peer_dhkey_check_ = eb;
255 if (role() == Role::kResponder) {
256 std::swap(local_dhkey_check_, expected_peer_dhkey_check_);
257 }
258 ltk_ = maybe_f5->ltk;
259
260 if (role() == Role::kInitiator) {
261 SendDhKeyCheckE();
262 } else if (actual_peer_dhkey_check_.has_value()) {
263 // As responder, it's possible the initiator sent us the DHKey check while
264 // we waited for user input. In that case, check it now instead of when we
265 // receive it.
266 ValidatePeerDhKeyCheck();
267 }
268 }
269
SendDhKeyCheckE()270 void Phase2SecureConnections::SendDhKeyCheckE() {
271 PW_CHECK(stage_1_results_.has_value());
272 PW_CHECK(!sent_local_dhkey_check_);
273 PW_CHECK(ltk_.has_value());
274 PW_CHECK(local_dhkey_check_.has_value());
275
276 // Send local DHKey Check
277 sm_chan().SendMessage(kPairingDHKeyCheck, *local_dhkey_check_);
278 sent_local_dhkey_check_ = true;
279 if (role() == Role::kResponder) {
280 // As responder, we should only send the local DHKey check after receiving
281 // and validating the peer's. The presence of `peer_dhkey_check` verifies
282 // this invariant.
283 PW_CHECK(actual_peer_dhkey_check_.has_value());
284 on_ltk_ready_(ltk_.value());
285 }
286 }
287
CanReceiveDhKeyCheck() const288 fit::result<ErrorCode> Phase2SecureConnections::CanReceiveDhKeyCheck() const {
289 // Only allowed on the LE transport.
290 if (sm_chan().link_type() != bt::LinkType::kLE) {
291 bt_log(WARN, "sm", "cannot accept peer ecdh key check over BR/EDR (SC)");
292 return fit::error(ErrorCode::kCommandNotSupported);
293 }
294 if (!stage_1_results_.has_value() && !stage_1_) {
295 bt_log(WARN,
296 "sm",
297 "received peer ecdh check too early! (before stage 1 started)");
298 return fit::error(ErrorCode::kUnspecifiedReason);
299 }
300 if (actual_peer_dhkey_check_.has_value()) {
301 bt_log(WARN, "sm", "received peer ecdh key check twice (SC)");
302 return fit::error(ErrorCode::kUnspecifiedReason);
303 }
304 if (role() == Role::kInitiator && !sent_local_dhkey_check_) {
305 bt_log(WARN,
306 "sm",
307 "received peer ecdh key check as initiator before sending local "
308 "ecdh key check (SC)");
309 return fit::error(ErrorCode::kUnspecifiedReason);
310 }
311 return fit::ok();
312 }
313
OnDhKeyCheck(PairingDHKeyCheckValueE check)314 void Phase2SecureConnections::OnDhKeyCheck(PairingDHKeyCheckValueE check) {
315 if (fit::result result = CanReceiveDhKeyCheck(); result.is_error()) {
316 Abort(result.error_value());
317 return;
318 }
319 actual_peer_dhkey_check_ = check;
320 // As responder, it's possible to receive the DHKey check from the peer while
321 // waiting for user input in Stage 1 - if that happens, we validate the peer
322 // DhKey check when Stage 1 completes.
323 if (!stage_1_results_.has_value()) {
324 PW_CHECK(role() == Role::kResponder);
325 PW_CHECK(stage_1_);
326 return;
327 }
328 ValidatePeerDhKeyCheck();
329 }
330
ValidatePeerDhKeyCheck()331 void Phase2SecureConnections::ValidatePeerDhKeyCheck() {
332 PW_CHECK(actual_peer_dhkey_check_.has_value());
333 PW_CHECK(expected_peer_dhkey_check_.has_value());
334 if (*expected_peer_dhkey_check_ != *actual_peer_dhkey_check_) {
335 bt_log(WARN,
336 "sm",
337 "DHKey check value failed - possible attempt to hijack pairing!");
338 Abort(ErrorCode::kDHKeyCheckFailed);
339 return;
340 }
341 if (role() == Role::kInitiator) {
342 bt_log(INFO, "sm", "completed secure connections Phase 2 of pairing");
343 on_ltk_ready_(ltk_.value());
344 } else {
345 SendDhKeyCheckE();
346 }
347 }
348
OnPairingConfirm(PairingConfirmValue confirm)349 void Phase2SecureConnections::OnPairingConfirm(PairingConfirmValue confirm) {
350 if (!stage_1_) {
351 bt_log(WARN,
352 "sm",
353 "received pairing confirm in SC outside of authentication stage 1");
354 Abort(ErrorCode::kUnspecifiedReason);
355 return;
356 }
357 stage_1_->OnPairingConfirm(confirm);
358 }
359
OnPairingRandom(PairingRandomValue rand)360 void Phase2SecureConnections::OnPairingRandom(PairingRandomValue rand) {
361 if (!stage_1_) {
362 bt_log(WARN,
363 "sm",
364 "received pairing random in SC outside of authentication stage 1");
365 Abort(ErrorCode::kUnspecifiedReason);
366 return;
367 }
368 stage_1_->OnPairingRandom(rand);
369 }
370
OnRxBFrame(ByteBufferPtr sdu)371 void Phase2SecureConnections::OnRxBFrame(ByteBufferPtr sdu) {
372 fit::result<ErrorCode, ValidPacketReader> maybe_reader =
373 ValidPacketReader::ParseSdu(sdu);
374 if (maybe_reader.is_error()) {
375 Abort(maybe_reader.error_value());
376 return;
377 }
378 ValidPacketReader reader = maybe_reader.value();
379 Code smp_code = reader.code();
380
381 if (smp_code == kPairingFailed) {
382 OnFailure(Error(reader.payload<ErrorCode>()));
383 } else if (smp_code == kPairingPublicKey) {
384 OnPeerPublicKey(reader.payload<PairingPublicKeyParams>());
385 } else if (smp_code == kPairingConfirm) {
386 OnPairingConfirm(reader.payload<PairingConfirmValue>());
387 } else if (smp_code == kPairingRandom) {
388 OnPairingRandom(reader.payload<PairingRandomValue>());
389 } else if (smp_code == kPairingDHKeyCheck) {
390 OnDhKeyCheck(reader.payload<PairingDHKeyCheckValueE>());
391 } else {
392 bt_log(
393 INFO,
394 "sm",
395 "received unexpected code %d when in Pairing SecureConnections Phase 2",
396 smp_code);
397 Abort(ErrorCode::kUnspecifiedReason);
398 }
399 }
400
401 } // namespace bt::sm
402