blob: 483b6101c9dc01790f499b079d24a30d285df6f0 [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/phase_2_legacy.h"
#include <optional>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.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/hci/connection.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/packet.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/pairing_phase.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/types.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/util.h"
namespace bt::sm {
namespace {
// We do not support OOB pairing & Legacy pairing does not permit Numeric
// Comparison.
bool IsSupportedLegacyMethod(PairingMethod method) {
return (method == PairingMethod::kJustWorks ||
method == PairingMethod::kPasskeyEntryDisplay ||
method == PairingMethod::kPasskeyEntryInput);
}
} // namespace
Phase2Legacy::Phase2Legacy(PairingChannel::WeakPtr chan,
Listener::WeakPtr listener,
Role role,
PairingFeatures features,
const ByteBuffer& preq,
const ByteBuffer& pres,
const DeviceAddress& initiator_add,
const DeviceAddress& responder_add,
OnPhase2KeyGeneratedCallback cb)
: PairingPhase(std::move(chan), std::move(listener), role),
sent_local_confirm_(false),
sent_local_rand_(false),
tk_(std::nullopt),
local_confirm_(std::nullopt),
peer_confirm_(std::nullopt),
local_rand_(std::nullopt),
peer_rand_(std::nullopt),
features_(features),
initiator_addr_(initiator_add),
responder_addr_(responder_add),
on_stk_ready_(std::move(cb)),
weak_self_(this) {
// Cache |preq| and |pres|. These are used for confirm value generation.
BT_ASSERT(preq.size() == preq_.size());
BT_ASSERT(pres.size() == pres_.size());
BT_ASSERT_MSG(IsSupportedLegacyMethod(features.method),
"unsupported legacy pairing method!");
preq.Copy(&preq_);
pres.Copy(&pres_);
SetPairingChannelHandler(*this);
}
void Phase2Legacy::Start() {
BT_ASSERT(!has_failed());
BT_ASSERT(!features_.secure_connections);
BT_ASSERT(!tk_.has_value());
BT_ASSERT(!peer_confirm_.has_value());
BT_ASSERT(!peer_rand_.has_value());
BT_ASSERT(!sent_local_confirm_);
BT_ASSERT(!sent_local_rand_);
MakeTemporaryKeyRequest();
}
void Phase2Legacy::MakeTemporaryKeyRequest() {
bt_log(DEBUG,
"sm",
"TK request - method: %s",
sm::util::PairingMethodToString(features_.method).c_str());
BT_ASSERT(listener().is_alive());
auto self = weak_self_.GetWeakPtr();
if (features_.method == sm::PairingMethod::kPasskeyEntryInput) {
// The TK will be provided by the user.
listener()->RequestPasskey([self](int64_t passkey) {
if (!self.is_alive()) {
return;
}
bool success = passkey >= 0;
self->HandleTemporaryKey(success ? std::optional<uint32_t>(passkey)
: std::nullopt);
});
return;
}
if (features_.method == sm::PairingMethod::kPasskeyEntryDisplay) {
// Randomly generate a 6 digit passkey.
uint32_t passkey;
random_generator()->GetInt<uint32_t>(passkey,
/*exclusive_upper_bound=*/1'000'000);
listener()->DisplayPasskey(
passkey,
Delegate::DisplayMethod::kPeerEntry,
[passkey, self](bool confirm) {
if (!self.is_alive()) {
return;
}
self->HandleTemporaryKey(confirm ? std::optional<uint32_t>(passkey)
: std::nullopt);
});
return;
}
// TODO(https://fxbug.dev/42138242): Support providing a TK out of band.
BT_ASSERT(features_.method == sm::PairingMethod::kJustWorks);
listener()->ConfirmPairing([self](bool confirm) {
if (!self.is_alive()) {
return;
}
self->HandleTemporaryKey(confirm ? std::optional<uint32_t>(0)
: std::nullopt);
});
}
void Phase2Legacy::HandleTemporaryKey(std::optional<uint32_t> maybe_tk) {
if (!maybe_tk.has_value()) {
bt_log(INFO, "sm", "temporary key listener responded with error; aborting");
if (features_.method == PairingMethod::kPasskeyEntryInput) {
Abort(ErrorCode::kPasskeyEntryFailed);
} else {
Abort(ErrorCode::kUnspecifiedReason);
}
return;
}
uint32_t tk = *maybe_tk;
tk_ = UInt128{0};
// Set the lower bits to |tk|.
tk = htole32(tk);
std::memcpy(tk_.value().data(), &tk, sizeof(tk));
// We have TK so we can generate the confirm value now.
local_rand_ = Random<UInt128>();
local_confirm_ = UInt128();
util::C1(tk_.value(),
local_rand_.value(),
preq_,
pres_,
initiator_addr_,
responder_addr_,
&(local_confirm_.value()));
// If we are the initiator then we just generated the "Mconfirm" value. We
// start the exchange by sending this value to the peer. Otherwise this is the
// "Sconfirm" value and we either:
// a. send it now if the peer has sent us its confirm value while we were
// waiting for the TK.
// b. send it later when we receive Mconfirm.
if (role() == Role::kInitiator || peer_confirm_.has_value()) {
SendConfirmValue();
}
}
void Phase2Legacy::SendConfirmValue() {
BT_ASSERT(!sent_local_confirm_);
BT_ASSERT(local_confirm_.has_value());
// Only allowed on the LE transport.
if (sm_chan().link_type() != bt::LinkType::kLE) {
bt_log(DEBUG,
"sm",
"attempted to send confirm value over BR/EDR, not sending");
return;
}
sm_chan().SendMessage(kPairingConfirm, *local_confirm_);
sent_local_confirm_ = true;
}
void Phase2Legacy::OnPairingConfirm(PairingConfirmValue confirm) {
if (fit::result result = CanReceivePairingConfirm(); result.is_error()) {
Abort(result.error_value());
return;
}
peer_confirm_ = confirm;
if (role() == Role::kInitiator) {
// We MUST have a TK and have previously generated an Mconfirm - this was
// implicitly checked in CanReceivePairingConfirm by checking whether we've
// sent the confirm value.
BT_ASSERT(tk_.has_value());
BT_ASSERT(sent_local_confirm_);
// We have sent Mconfirm and just received Sconfirm. We now send Mrand for
// the peer to compare.
SendRandomValue();
} else if (tk_.has_value()) {
// We are the responder and have just received Mconfirm. If we already have
// a TK, we now send the local confirm to the peer. If not,
// HandleTemporaryKey will take care of that.
SendConfirmValue();
}
}
void Phase2Legacy::SendRandomValue() {
BT_ASSERT(!sent_local_rand_);
// This is always generated in the TK callback, which must have been called by
// now as the random are sent after the confirm values, and the TK must exist
// in order to send the confirm.
BT_ASSERT(local_rand_.has_value());
// Only allowed on the LE transport.
if (sm_chan().link_type() != bt::LinkType::kLE) {
bt_log(
WARN, "sm", "attempted to send confirm value over BR/EDR, not sending");
return;
}
sm_chan().SendMessage(kPairingRandom, *local_rand_);
sent_local_rand_ = true;
}
void Phase2Legacy::OnPairingRandom(PairingRandomValue rand) {
if (fit::result result = CanReceivePairingRandom(); result.is_error()) {
Abort(result.error_value());
return;
}
// These should have been checked in CanReceivePairingRandom
BT_ASSERT(local_rand_.has_value());
BT_ASSERT(tk_.has_value());
BT_ASSERT(peer_confirm_.has_value());
peer_rand_ = rand;
// We have the peer's confirm and rand. Verify the peer confirm to validate
// the authentication.
UInt128 peer_confirm_check;
util::C1(tk_.value(),
peer_rand_.value(),
preq_,
pres_,
initiator_addr_,
responder_addr_,
&peer_confirm_check);
if (peer_confirm_check != peer_confirm_) {
bt_log(WARN,
"sm",
"%sconfirm value does not match!",
role() == Role::kInitiator ? "S" : "M");
Abort(ErrorCode::kConfirmValueFailed);
return;
}
// Generate the STK.
UInt128 stk;
auto [initiator_rand, responder_rand] =
util::MapToRoles(*local_rand_, *peer_rand_, role());
util::S1(tk_.value(), responder_rand, initiator_rand, &stk);
// Mask the key based on the requested encryption key size.
uint8_t key_size = features_.encryption_key_size;
if (key_size < kMaxEncryptionKeySize) {
MutableBufferView view(stk.data() + key_size,
kMaxEncryptionKeySize - key_size);
view.SetToZeros();
}
// We've generated the STK, so Phase 2 is now over if we're the initiator.
on_stk_ready_(stk);
// As responder, we choose to notify the STK to the higher layer before
// sending our SRand. We expect the peer initiator to request encryption
// immediately after receiving SRand, and we want to ensure the STK is
// available at the hci::Connection layer when this occurs.
if (role() == Role::kResponder) {
SendRandomValue();
}
}
fit::result<ErrorCode> Phase2Legacy::CanReceivePairingConfirm() const {
// Only allowed on the LE transport.
if (sm_chan().link_type() != bt::LinkType::kLE) {
bt_log(DEBUG, "sm", "\"Confirm value\" over BR/EDR not supported!");
return fit::error(ErrorCode::kCommandNotSupported);
}
// Per the message sequence charts in V5.1 Vol. 3 Part H Appendix C.2.1,
// reject the pairing confirm value and abort if
// a. we are the initiator, and have not yet sent our confirm value.
// b. we are the responder, and have already sent our confirm value.
if ((role() == Role::kInitiator && !sent_local_confirm_) ||
(role() == Role::kResponder && sent_local_confirm_)) {
bt_log(
WARN, "sm", "abort pairing due to confirm value received out of order");
return fit::error(ErrorCode::kUnspecifiedReason);
}
// Legacy pairing only allows for one confirm/random exchange per pairing.
if (peer_confirm_.has_value()) {
bt_log(WARN, "sm", "already received confirm value! aborting");
return fit::error(ErrorCode::kUnspecifiedReason);
}
// The confirm value shouldn't be sent after the random value. (See spec V5.0
// Vol 3, Part H, 2.3.5.5 and Appendix C.2.1.1 for the specific order of
// events).
if (peer_rand_.has_value() || sent_local_rand_) {
bt_log(
WARN, "sm", "\"Pairing Confirm\" must come before \"Pairing Random\"");
return fit::error(ErrorCode::kUnspecifiedReason);
}
return fit::ok();
}
fit::result<ErrorCode> Phase2Legacy::CanReceivePairingRandom() const {
// Only allowed on the LE transport.
if (sm_chan().link_type() != bt::LinkType::kLE) {
bt_log(DEBUG, "sm", "\"Random value\" over BR/EDR not supported!");
return fit::error(ErrorCode::kCommandNotSupported);
}
if (!tk_.has_value()) {
bt_log(
WARN, "sm", "abort pairing, random value received before user input");
return fit::error(ErrorCode::kUnspecifiedReason);
}
// V5.0 Vol 3, Part H, 2.3.5.5 dictates that there should be exactly one
// pairing random value received by each peer in Legacy Pairing Phase 2.
if (peer_rand_.has_value()) {
bt_log(WARN, "sm", "already received random value! aborting");
return fit::error(ErrorCode::kUnspecifiedReason);
}
// The random value shouldn't be sent before the confirm value. See V5.0 Vol
// 3, Part H, 2.3.5.5 and Appendix C.2.1.1 for the specific order of events.
if (!peer_confirm_.has_value()) {
bt_log(WARN, "sm", "\"Pairing Rand\" expected after \"Pairing Confirm\"");
return fit::error(ErrorCode::kUnspecifiedReason);
}
if (role() == Role::kInitiator) {
// The initiator distributes both values before the responder sends Srandom.
if (!sent_local_rand_ || !sent_local_confirm_) {
bt_log(WARN, "sm", "\"Pairing Random\" received in wrong order!");
return fit::error(ErrorCode::kUnspecifiedReason);
}
} else {
// We know we have not received Mrand, and should not have sent Srand
// without receiving Mrand.
BT_ASSERT(!sent_local_rand_);
// We need to send Sconfirm before the initiator sends Mrand.
if (!sent_local_confirm_) {
bt_log(WARN, "sm", "\"Pairing Random\" received in wrong order!");
return fit::error(ErrorCode::kUnspecifiedReason);
}
}
return fit::ok();
}
void Phase2Legacy::OnRxBFrame(ByteBufferPtr sdu) {
fit::result<ErrorCode, ValidPacketReader> maybe_reader =
ValidPacketReader::ParseSdu(sdu);
if (maybe_reader.is_error()) {
Abort(maybe_reader.error_value());
return;
}
ValidPacketReader reader = maybe_reader.value();
Code smp_code = reader.code();
if (smp_code == kPairingFailed) {
OnFailure(Error(reader.payload<ErrorCode>()));
} else if (smp_code == kPairingConfirm) {
OnPairingConfirm(reader.payload<PairingConfirmValue>());
} else if (smp_code == kPairingRandom) {
OnPairingRandom(reader.payload<PairingRandomValue>());
} else {
bt_log(INFO,
"sm",
"received unexpected code %#.2X when in Pairing Legacy Phase 2",
smp_code);
Abort(ErrorCode::kUnspecifiedReason);
}
}
} // namespace bt::sm