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