xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/sm/sc_stage_1_passkey.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 #include "pw_bluetooth_sapphire/internal/host/sm/sc_stage_1_passkey.h"
15 
16 #include <pw_bytes/endian.h>
17 
18 #include <cinttypes>
19 #include <optional>
20 
21 #include "lib/fit/function.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/random.h"
23 #include "pw_bluetooth_sapphire/internal/host/common/uint128.h"
24 #include "pw_bluetooth_sapphire/internal/host/sm/delegate.h"
25 #include "pw_bluetooth_sapphire/internal/host/sm/sc_stage_1.h"
26 #include "pw_bluetooth_sapphire/internal/host/sm/smp.h"
27 #include "pw_bluetooth_sapphire/internal/host/sm/util.h"
28 
29 namespace bt::sm {
30 
31 namespace {
32 const size_t kMaxPasskeyBitLocation = 19;
33 
GetPasskeyBit(uint32_t passkey,size_t passkey_bit_location)34 uint8_t GetPasskeyBit(uint32_t passkey, size_t passkey_bit_location) {
35   uint32_t masked_passkey = passkey & (1 << passkey_bit_location);
36   // These values come from the spec f4 section (V5.1 Vol. 3 Part H
37   // Section 2.2.7)
38   return (masked_passkey == 0) ? 0x80 : 0x81;
39 }
40 
41 }  // namespace
42 
ScStage1Passkey(PairingPhase::Listener::WeakPtr listener,Role role,UInt256 local_pub_key_x,UInt256 peer_pub_key_x,PairingMethod method,PairingChannel::WeakPtr sm_chan,Stage1CompleteCallback on_complete)43 ScStage1Passkey::ScStage1Passkey(PairingPhase::Listener::WeakPtr listener,
44                                  Role role,
45                                  UInt256 local_pub_key_x,
46                                  UInt256 peer_pub_key_x,
47                                  PairingMethod method,
48                                  PairingChannel::WeakPtr sm_chan,
49                                  Stage1CompleteCallback on_complete)
50     : listener_(std::move(listener)),
51       role_(role),
52       local_public_key_x_(local_pub_key_x),
53       peer_public_key_x_(peer_pub_key_x),
54       method_(method),
55       passkey_bit_location_(0),
56       sent_local_confirm_(false),
57       peer_confirm_(std::nullopt),
58       sent_local_rand_(false),
59       peer_rand_(std::nullopt),
60       sm_chan_(std::move(sm_chan)),
61       on_complete_(std::move(on_complete)),
62       weak_self_(this) {
63   PW_CHECK(method == PairingMethod::kPasskeyEntryDisplay ||
64            method == PairingMethod::kPasskeyEntryInput);
65 }
66 
Run()67 void ScStage1Passkey::Run() {
68   auto self = weak_self_.GetWeakPtr();
69   auto passkey_responder = [self](std::optional<uint32_t> passkey) {
70     if (!self.is_alive()) {
71       bt_log(TRACE, "sm", "passkey for expired callback");
72       return;
73     }
74     if (!passkey.has_value()) {
75       self->on_complete_(fit::error(ErrorCode::kPasskeyEntryFailed));
76       return;
77     }
78     self->passkey_ = passkey;
79     self->StartBitExchange();
80   };
81   if (method_ == PairingMethod::kPasskeyEntryDisplay) {
82     // Randomly generate a 6 digit passkey.
83     uint32_t passkey;
84     random_generator()->GetInt<uint32_t>(passkey,
85                                          /*exclusive_upper_bound=*/1'000'000);
86     listener_->DisplayPasskey(
87         passkey,
88         Delegate::DisplayMethod::kPeerEntry,
89         [responder = std::move(passkey_responder), passkey](bool confirm) {
90           std::optional<uint32_t> passkey_response = passkey;
91           if (!confirm) {
92             bt_log(WARN, "sm", "passkey entry display rejected by user");
93             passkey_response = std::nullopt;
94           }
95           bt_log(INFO, "sm", "SC passkey entry display accepted by user");
96           responder(passkey_response);
97         });
98   } else {  // method_ == kPasskeyEntryInput
99     listener_->RequestPasskey([responder = std::move(passkey_responder)](
100                                   int64_t passkey) {
101       std::optional<uint32_t> passkey_response = passkey;
102       if (passkey >= 1000000 || passkey < 0) {
103         bt_log(WARN,
104                "sm",
105                "rejecting passkey entry input: %s",
106                passkey >= 1000000 ? "passkey has > 6 digits" : "user rejected");
107         passkey_response = std::nullopt;
108       }
109       bt_log(INFO,
110              "sm",
111              "SC passkey entry display (passkey: %" PRId64 ") accepted by user",
112              passkey);
113       responder(passkey_response);
114     });
115   }
116 }
117 
StartBitExchange()118 void ScStage1Passkey::StartBitExchange() {
119   PW_CHECK(passkey_.has_value());
120   // The passkey is 6 digits i.e. representable in 2^20 bits. Attempting to
121   // exchange > 20 bits indicates a programmer error.
122   PW_CHECK(passkey_bit_location_ <= kMaxPasskeyBitLocation);
123   local_rand_ = Random<UInt128>();
124   sent_local_confirm_ = sent_local_rand_ = false;
125 
126   if (role_ == Role::kInitiator || peer_confirm_.has_value()) {
127     // The initiator always sends the pairing confirm first. The only situation
128     // where we should have received a peer confirm before StartBitExchange is
129     // if, as responder in the first bit exchange, we receive the peer
130     // initiator's confirm while waiting for local user input.
131     PW_CHECK((role_ == Role::kInitiator && !peer_confirm_.has_value()) ||
132              passkey_bit_location_ == 0);
133     SendPairingConfirm();
134   }
135   // As responder, we wait for the peer confirm before taking any action.
136 }
137 
SendPairingConfirm()138 void ScStage1Passkey::SendPairingConfirm() {
139   PW_CHECK(!sent_local_confirm_);
140   PW_CHECK(passkey_.has_value());
141   if (role_ == Role::kResponder) {
142     PW_CHECK(peer_confirm_.has_value());
143   }
144 
145   uint8_t current_passkey_bit = GetPasskeyBit(*passkey_, passkey_bit_location_);
146   std::optional<UInt128> maybe_confirm = util::F4(local_public_key_x_,
147                                                   peer_public_key_x_,
148                                                   local_rand_,
149                                                   current_passkey_bit);
150   if (!maybe_confirm.has_value()) {
151     bt_log(WARN,
152            "sm",
153            "could not calculate confirm value in SC Stage 1 Passkey Entry");
154     on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
155     return;
156   }
157   local_confirm_ = *maybe_confirm;
158   sm_chan_->SendMessage(kPairingConfirm, local_confirm_);
159   sent_local_confirm_ = true;
160 }
161 
OnPairingConfirm(PairingConfirmValue confirm)162 void ScStage1Passkey::OnPairingConfirm(PairingConfirmValue confirm) {
163   if (peer_confirm_.has_value()) {
164     bt_log(WARN,
165            "sm",
166            "received multiple Pairing Confirm values in one SC Passkey Entry "
167            "cycle");
168     on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
169     return;
170   }
171   if (sent_local_rand_ || peer_rand_.has_value() ||
172       (!sent_local_confirm_ && role_ == Role::kInitiator)) {
173     bt_log(WARN,
174            "sm",
175            "received Pairing Confirm out of order in SC Passkey Entry");
176     on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
177     return;
178   }
179   peer_confirm_ = confirm;
180   if (role_ == Role::kInitiator) {
181     SendPairingRandom();
182   } else if (passkey_.has_value()) {
183     // As responder, it's possible to receive a confirm value while waiting for
184     // the passkey. If that occurs, the local confirm will be sent by
185     // StartBitExchange.
186     SendPairingConfirm();
187   }
188 }
189 
SendPairingRandom()190 void ScStage1Passkey::SendPairingRandom() {
191   PW_CHECK(sent_local_confirm_ && peer_confirm_.has_value());
192   PW_CHECK(!sent_local_rand_);
193   if (role_ == Role::kResponder) {
194     PW_CHECK(peer_rand_.has_value());
195   }
196   sm_chan_->SendMessage(kPairingRandom, local_rand_);
197   sent_local_rand_ = true;
198 }
199 
OnPairingRandom(PairingRandomValue rand)200 void ScStage1Passkey::OnPairingRandom(PairingRandomValue rand) {
201   if (!sent_local_confirm_ || !peer_confirm_.has_value()) {
202     bt_log(WARN, "sm", "received Pairing Random before confirm value sent");
203     on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
204     return;
205   }
206   if (peer_rand_.has_value()) {
207     bt_log(WARN,
208            "sm",
209            "received multiple Pairing Random values in one SC Passkey Entry "
210            "cycle");
211     on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
212     return;
213   }
214   if (role_ == Role::kInitiator && !sent_local_rand_) {
215     bt_log(WARN, "sm", "received peer random out of order");
216     on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
217     return;
218   }
219 
220   uint8_t current_passkey_bit = GetPasskeyBit(*passkey_, passkey_bit_location_);
221   std::optional<PairingConfirmValue> maybe_confirm_check = util::F4(
222       peer_public_key_x_, local_public_key_x_, rand, current_passkey_bit);
223   if (!maybe_confirm_check.has_value()) {
224     bt_log(WARN, "sm", "unable to calculate SC confirm check value");
225     on_complete_(fit::error(ErrorCode::kConfirmValueFailed));
226     return;
227   }
228   if (*maybe_confirm_check != *peer_confirm_) {
229     bt_log(WARN, "sm", "peer SC confirm value did not match check, aborting");
230     on_complete_(fit::error(ErrorCode::kConfirmValueFailed));
231     return;
232   }
233   peer_rand_ = rand;
234   if (role_ == Role::kResponder) {
235     SendPairingRandom();
236   }
237   // After the random exchange completes, this round of passkey bit agreement is
238   // over.
239   FinishBitExchange();
240 }
241 
FinishBitExchange()242 void ScStage1Passkey::FinishBitExchange() {
243   PW_CHECK(sent_local_confirm_);
244   PW_CHECK(peer_confirm_.has_value());
245   PW_CHECK(sent_local_rand_);
246   PW_CHECK(peer_rand_.has_value());
247   passkey_bit_location_++;
248   if (passkey_bit_location_ <= kMaxPasskeyBitLocation) {
249     peer_confirm_ = std::nullopt;
250     peer_rand_ = std::nullopt;
251     StartBitExchange();
252     return;
253   }
254   // If we've exchanged bits 0-19 (20 bits), stage 1 is complete and we notify
255   // the callback.
256   auto [initiator_rand, responder_rand] =
257       util::MapToRoles(local_rand_, *peer_rand_, role_);
258   UInt128 passkey_array{0};
259   // Copy little-endian uint32 passkey to the UInt128 array needed for Stage 2
260   auto little_endian_passkey =
261       pw::bytes::ConvertOrderTo(cpp20::endian::little, *passkey_);
262   std::memcpy(passkey_array.data(), &little_endian_passkey, sizeof(uint32_t));
263   on_complete_(fit::ok(Output{.initiator_r = passkey_array,
264                               .responder_r = passkey_array,
265                               .initiator_rand = initiator_rand,
266                               .responder_rand = responder_rand}));
267 }
268 
269 }  // namespace bt::sm
270