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