blob: 61311432de634518ebf3cc8973ca918f2b4b143d [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/sc_stage_1_just_works_numeric_comparison.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/random.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/uint128.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/smp.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/util.h"
namespace bt::sm {
ScStage1JustWorksNumericComparison::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)
: listener_(std::move(listener)),
role_(role),
local_public_key_x_(local_pub_key_x),
peer_public_key_x_(peer_pub_key_x),
method_(method),
sent_pairing_confirm_(false),
local_rand_(Random<UInt128>()),
sent_local_rand_(false),
peer_rand_(),
sm_chan_(std::move(sm_chan)),
on_complete_(std::move(on_complete)),
weak_self_(this) {
BT_ASSERT(method == PairingMethod::kJustWorks ||
method == PairingMethod::kNumericComparison);
}
void ScStage1JustWorksNumericComparison::Run() {
// The responder sends the Pairing Confirm message to start Stage 1, so as
// initiator there is nothing to do besides wait for the peer's Confirm value
// (Vol 3, Part H, 2.3.5.6.2).
if (role_ == Role::kResponder) {
std::optional<UInt128> maybe_confirm =
util::F4(local_public_key_x_, peer_public_key_x_, local_rand_, 0);
if (!maybe_confirm.has_value()) {
bt_log(WARN, "sm", "unable to calculate confirm value in SC Phase 1");
on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
return;
}
responder_confirm_ = *maybe_confirm;
sm_chan_->SendMessage(kPairingConfirm, *responder_confirm_);
sent_pairing_confirm_ = true;
}
}
void ScStage1JustWorksNumericComparison::OnPairingConfirm(
PairingConfirmValue confirm) {
// Only the responder can send the confirm value to the initiator (Vol 3, Part
// H, 2.3.5.6.2).
if (role_ == Role::kResponder) {
bt_log(WARN,
"sm",
"cannot accept pairing confirm in SC Numeric Comparison/Just Works "
"responder mode");
on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
return;
}
if (responder_confirm_.has_value()) {
bt_log(WARN,
"sm",
"received multiple Pairing Confirm values in SC Numeric "
"Comparison/Just Works");
on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
return;
}
responder_confirm_ = confirm;
// We already know that we're the initiator at this point, which sends the
// Random immediately after receiving the Confirm.
SendPairingRandom();
}
void ScStage1JustWorksNumericComparison::SendPairingRandom() {
// The random value is always sent after the confirm exchange (Vol 3, Part
// H, 2.3.5.6.2).
BT_ASSERT(responder_confirm_.has_value());
BT_ASSERT(!sent_local_rand_);
if (role_ == Role::kResponder) {
BT_ASSERT(peer_rand_.has_value());
}
sm_chan_->SendMessage(kPairingRandom, local_rand_);
sent_local_rand_ = true;
if (role_ == Role::kResponder) {
CompleteStage1();
}
}
void ScStage1JustWorksNumericComparison::OnPairingRandom(
PairingRandomValue rand) {
if (!responder_confirm_.has_value()) {
bt_log(WARN,
"sm",
"received Pairing Random before the confirm value was exchanged");
on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
return;
}
if (peer_rand_.has_value()) {
bt_log(WARN, "sm", "received multiple Pairing Random values from peer");
on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
return;
}
peer_rand_ = rand;
if (role_ == Role::kResponder) {
SendPairingRandom();
return;
}
// Otherwise, we're the initiator & we must validate the |responder_confirm_|
// with |rand|.
if (!sent_local_rand_) {
bt_log(
WARN, "sm", "received peer random before sending our own as initiator");
on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
return;
}
std::optional<UInt128> maybe_confirm_check =
util::F4(peer_public_key_x_, local_public_key_x_, rand, 0);
if (!maybe_confirm_check.has_value()) {
bt_log(WARN, "sm", "unable to calculate SC confirm check value");
on_complete_(fit::error(ErrorCode::kConfirmValueFailed));
return;
}
if (*maybe_confirm_check != *responder_confirm_) {
bt_log(WARN, "sm", "peer SC confirm value did not match check, aborting");
on_complete_(fit::error(ErrorCode::kConfirmValueFailed));
return;
}
CompleteStage1();
}
void ScStage1JustWorksNumericComparison::CompleteStage1() {
BT_ASSERT(responder_confirm_.has_value());
BT_ASSERT(peer_rand_.has_value());
BT_ASSERT(sent_local_rand_);
const auto& [initiator_rand, responder_rand] =
util::MapToRoles(local_rand_, *peer_rand_, role_);
const auto& [initiator_pub_key_x, responder_pub_key_x] =
util::MapToRoles(local_public_key_x_, peer_public_key_x_, role_);
auto results = Output{.initiator_r = {0},
.responder_r = {0},
.initiator_rand = initiator_rand,
.responder_rand = responder_rand};
auto self = weak_self_.GetWeakPtr();
if (method_ == PairingMethod::kNumericComparison) {
std::optional<uint32_t> g2_result = util::G2(initiator_pub_key_x,
responder_pub_key_x,
initiator_rand,
responder_rand);
if (!g2_result.has_value()) {
bt_log(WARN, "sm", "unable to calculate numeric comparison user check");
on_complete_(fit::error(ErrorCode::kNumericComparisonFailed));
return;
}
// The code displayed to the user is the least significant 6 digits of the
// G2 function.
uint32_t comparison_code = *g2_result % 1000000;
listener_->DisplayPasskey(
comparison_code,
Delegate::DisplayMethod::kComparison,
[self, results](bool passkey_confirmed) {
bt_log(INFO,
"sm",
"PairingDelegate %s SC numeric display pairing",
passkey_confirmed ? "accepted" : "rejected");
if (self.is_alive()) {
passkey_confirmed ? self->on_complete_(fit::ok(results))
: self->on_complete_(fit::error(
ErrorCode::kNumericComparisonFailed));
}
});
} else { // method == kJustWorks
listener_->ConfirmPairing([self, results](bool user_confirmed) {
bt_log(INFO,
"sm",
"PairingDelegate %s SC just works pairing",
user_confirmed ? "accepted" : "rejected");
if (self.is_alive()) {
user_confirmed
? self->on_complete_(fit::ok(results))
: self->on_complete_(fit::error(ErrorCode::kUnspecifiedReason));
}
});
}
}
} // namespace bt::sm