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