xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/sm/phase_2_secure_connections.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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