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/sc_stage_1_just_works_numeric_comparison.h"
16
17 #include "pw_bluetooth_sapphire/internal/host/common/random.h"
18 #include "pw_bluetooth_sapphire/internal/host/common/uint128.h"
19 #include "pw_bluetooth_sapphire/internal/host/sm/delegate.h"
20 #include "pw_bluetooth_sapphire/internal/host/sm/smp.h"
21 #include "pw_bluetooth_sapphire/internal/host/sm/util.h"
22
23 namespace bt::sm {
24
ScStage1JustWorksNumericComparison(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)25 ScStage1JustWorksNumericComparison::ScStage1JustWorksNumericComparison(
26 PairingPhase::Listener::WeakPtr listener,
27 Role role,
28 UInt256 local_pub_key_x,
29 UInt256 peer_pub_key_x,
30 PairingMethod method,
31 PairingChannel::WeakPtr sm_chan,
32 Stage1CompleteCallback on_complete)
33 : listener_(std::move(listener)),
34 role_(role),
35 local_public_key_x_(local_pub_key_x),
36 peer_public_key_x_(peer_pub_key_x),
37 method_(method),
38 sent_pairing_confirm_(false),
39 local_rand_(Random<UInt128>()),
40 sent_local_rand_(false),
41 peer_rand_(),
42 sm_chan_(std::move(sm_chan)),
43 on_complete_(std::move(on_complete)),
44 weak_self_(this) {
45 PW_CHECK(method == PairingMethod::kJustWorks ||
46 method == PairingMethod::kNumericComparison);
47 }
48
Run()49 void ScStage1JustWorksNumericComparison::Run() {
50 // The responder sends the Pairing Confirm message to start Stage 1, so as
51 // initiator there is nothing to do besides wait for the peer's Confirm value
52 // (Vol 3, Part H, 2.3.5.6.2).
53 if (role_ == Role::kResponder) {
54 std::optional<UInt128> maybe_confirm =
55 util::F4(local_public_key_x_, peer_public_key_x_, local_rand_, 0);
56 if (!maybe_confirm.has_value()) {
57 bt_log(WARN, "sm", "unable to calculate confirm value in SC Phase 1");
58 on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
59 return;
60 }
61 responder_confirm_ = *maybe_confirm;
62 sm_chan_->SendMessage(kPairingConfirm, *responder_confirm_);
63 sent_pairing_confirm_ = true;
64 }
65 }
66
OnPairingConfirm(PairingConfirmValue confirm)67 void ScStage1JustWorksNumericComparison::OnPairingConfirm(
68 PairingConfirmValue confirm) {
69 // Only the responder can send the confirm value to the initiator (Vol 3, Part
70 // H, 2.3.5.6.2).
71 if (role_ == Role::kResponder) {
72 bt_log(WARN,
73 "sm",
74 "cannot accept pairing confirm in SC Numeric Comparison/Just Works "
75 "responder mode");
76 on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
77 return;
78 }
79 if (responder_confirm_.has_value()) {
80 bt_log(WARN,
81 "sm",
82 "received multiple Pairing Confirm values in SC Numeric "
83 "Comparison/Just Works");
84 on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
85 return;
86 }
87 responder_confirm_ = confirm;
88 // We already know that we're the initiator at this point, which sends the
89 // Random immediately after receiving the Confirm.
90 SendPairingRandom();
91 }
92
SendPairingRandom()93 void ScStage1JustWorksNumericComparison::SendPairingRandom() {
94 // The random value is always sent after the confirm exchange (Vol 3, Part
95 // H, 2.3.5.6.2).
96 PW_CHECK(responder_confirm_.has_value());
97 PW_CHECK(!sent_local_rand_);
98 if (role_ == Role::kResponder) {
99 PW_CHECK(peer_rand_.has_value());
100 }
101 sm_chan_->SendMessage(kPairingRandom, local_rand_);
102 sent_local_rand_ = true;
103 if (role_ == Role::kResponder) {
104 CompleteStage1();
105 }
106 }
107
OnPairingRandom(PairingRandomValue rand)108 void ScStage1JustWorksNumericComparison::OnPairingRandom(
109 PairingRandomValue rand) {
110 if (!responder_confirm_.has_value()) {
111 bt_log(WARN,
112 "sm",
113 "received Pairing Random before the confirm value was exchanged");
114 on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
115 return;
116 }
117 if (peer_rand_.has_value()) {
118 bt_log(WARN, "sm", "received multiple Pairing Random values from peer");
119 on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
120 return;
121 }
122 peer_rand_ = rand;
123 if (role_ == Role::kResponder) {
124 SendPairingRandom();
125 return;
126 }
127 // Otherwise, we're the initiator & we must validate the |responder_confirm_|
128 // with |rand|.
129 if (!sent_local_rand_) {
130 bt_log(
131 WARN, "sm", "received peer random before sending our own as initiator");
132 on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
133 return;
134 }
135 std::optional<UInt128> maybe_confirm_check =
136 util::F4(peer_public_key_x_, local_public_key_x_, rand, 0);
137 if (!maybe_confirm_check.has_value()) {
138 bt_log(WARN, "sm", "unable to calculate SC confirm check value");
139 on_complete_(fit::error(ErrorCode::kConfirmValueFailed));
140 return;
141 }
142 if (*maybe_confirm_check != *responder_confirm_) {
143 bt_log(WARN, "sm", "peer SC confirm value did not match check, aborting");
144 on_complete_(fit::error(ErrorCode::kConfirmValueFailed));
145 return;
146 }
147 CompleteStage1();
148 }
149
CompleteStage1()150 void ScStage1JustWorksNumericComparison::CompleteStage1() {
151 PW_CHECK(responder_confirm_.has_value());
152 PW_CHECK(peer_rand_.has_value());
153 PW_CHECK(sent_local_rand_);
154 const auto& [initiator_rand, responder_rand] =
155 util::MapToRoles(local_rand_, *peer_rand_, role_);
156 const auto& [initiator_pub_key_x, responder_pub_key_x] =
157 util::MapToRoles(local_public_key_x_, peer_public_key_x_, role_);
158 auto results = Output{.initiator_r = {0},
159 .responder_r = {0},
160 .initiator_rand = initiator_rand,
161 .responder_rand = responder_rand};
162 auto self = weak_self_.GetWeakPtr();
163 if (method_ == PairingMethod::kNumericComparison) {
164 std::optional<uint32_t> g2_result = util::G2(initiator_pub_key_x,
165 responder_pub_key_x,
166 initiator_rand,
167 responder_rand);
168 if (!g2_result.has_value()) {
169 bt_log(WARN, "sm", "unable to calculate numeric comparison user check");
170 on_complete_(fit::error(ErrorCode::kNumericComparisonFailed));
171 return;
172 }
173
174 // The code displayed to the user is the least significant 6 digits of the
175 // G2 function.
176 uint32_t comparison_code = *g2_result % 1000000;
177 listener_->DisplayPasskey(
178 comparison_code,
179 Delegate::DisplayMethod::kComparison,
180 [self, results](bool passkey_confirmed) {
181 bt_log(INFO,
182 "sm",
183 "PairingDelegate %s SC numeric display pairing",
184 passkey_confirmed ? "accepted" : "rejected");
185 if (self.is_alive()) {
186 passkey_confirmed ? self->on_complete_(fit::ok(results))
187 : self->on_complete_(fit::error(
188 ErrorCode::kNumericComparisonFailed));
189 }
190 });
191 } else { // method == kJustWorks
192 listener_->ConfirmPairing([self, results](bool user_confirmed) {
193 bt_log(INFO,
194 "sm",
195 "PairingDelegate %s SC just works pairing",
196 user_confirmed ? "accepted" : "rejected");
197 if (self.is_alive()) {
198 user_confirmed
199 ? self->on_complete_(fit::ok(results))
200 : self->on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
201 }
202 });
203 }
204 }
205
206 } // namespace bt::sm
207