• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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