blob: fa34d16420122b829a8394504637ed6037ea2a9f [file] [log] [blame]
// Copyright 2018 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 "util.h"
#include <openssl/aes.h>
#include "garnet/drivers/bluetooth/lib/hci/util.h"
#include "lib/fxl/logging.h"
namespace btlib {
using common::BufferView;
using common::ByteBuffer;
using common::DeviceAddress;
using common::UInt128;
namespace sm {
namespace util {
namespace {
constexpr size_t kPreqSize = 7;
// Swap the endianness of a 128-bit integer. |in| and |out| should not be backed
// by the same buffer.
void Swap128(const UInt128& in, UInt128* out) {
FXL_DCHECK(out);
for (size_t i = 0; i < in.size(); ++i) {
(*out)[i] = in[in.size() - i - 1];
}
}
// XOR two 128-bit integers and return the result in |out|. It is possible to
// pass a pointer to one of the inputs as |out|.
void Xor128(const UInt128& int1, const UInt128& int2, UInt128* out) {
FXL_DCHECK(out);
uint64_t lower1 = *reinterpret_cast<const uint64_t*>(int1.data());
uint64_t upper1 = *reinterpret_cast<const uint64_t*>(int1.data() + 8);
uint64_t lower2 = *reinterpret_cast<const uint64_t*>(int2.data());
uint64_t upper2 = *reinterpret_cast<const uint64_t*>(int2.data() + 8);
uint64_t lower_res = lower1 ^ lower2;
uint64_t upper_res = upper1 ^ upper2;
std::memcpy(out->data(), &lower_res, 8);
std::memcpy(out->data() + 8, &upper_res, 8);
}
} // namespace
std::string IOCapabilityToString(IOCapability capability) {
switch (capability) {
case IOCapability::kDisplayOnly:
return "Display Only";
case IOCapability::kDisplayYesNo:
return "Display w/ Confirmation";
case IOCapability::kKeyboardOnly:
return "Keyboard";
case IOCapability::kNoInputNoOutput:
return "No I/O";
case IOCapability::kKeyboardDisplay:
return "Keyboard w/ Display";
default:
break;
}
return "(unknown)";
};
std::string PairingMethodToString(PairingMethod method) {
switch (method) {
case PairingMethod::kJustWorks:
return "Just Works";
case PairingMethod::kPasskeyEntryInput:
return "Passkey Entry (input)";
case PairingMethod::kPasskeyEntryDisplay:
return "Passkey Entry (display)";
case PairingMethod::kNumericComparison:
return "Numeric Comparison";
case PairingMethod::kOutOfBand:
return "OOB";
default:
break;
}
return "(unknown)";
}
PairingMethod SelectPairingMethod(bool sec_conn, bool local_oob, bool peer_oob,
bool mitm_required, IOCapability local_ioc,
IOCapability peer_ioc, bool local_initiator) {
if ((sec_conn && (local_oob || peer_oob)) ||
(!sec_conn && local_oob && peer_oob)) {
return PairingMethod::kOutOfBand;
}
// If neither device requires MITM protection or if the peer has not I/O
// capable, we select Just Works.
if (!mitm_required || peer_ioc == IOCapability::kNoInputNoOutput) {
return PairingMethod::kJustWorks;
}
// Select the pairing method by comparing I/O capabilities. The switch
// statement will return if an authenticated entry method is selected.
// Otherwise, we'll break out and default to Just Works below.
switch (local_ioc) {
case IOCapability::kNoInputNoOutput:
break;
case IOCapability::kDisplayOnly:
switch (peer_ioc) {
case IOCapability::kKeyboardOnly:
case IOCapability::kKeyboardDisplay:
return PairingMethod::kPasskeyEntryDisplay;
default:
break;
}
break;
case IOCapability::kDisplayYesNo:
switch (peer_ioc) {
case IOCapability::kDisplayYesNo:
return sec_conn ? PairingMethod::kNumericComparison
: PairingMethod::kJustWorks;
case IOCapability::kKeyboardDisplay:
return sec_conn ? PairingMethod::kNumericComparison
: PairingMethod::kPasskeyEntryDisplay;
case IOCapability::kKeyboardOnly:
return PairingMethod::kPasskeyEntryDisplay;
default:
break;
}
break;
case IOCapability::kKeyboardOnly:
return PairingMethod::kPasskeyEntryInput;
case IOCapability::kKeyboardDisplay:
switch (peer_ioc) {
case IOCapability::kKeyboardOnly:
return PairingMethod::kPasskeyEntryDisplay;
case IOCapability::kDisplayOnly:
return PairingMethod::kPasskeyEntryInput;
case IOCapability::kDisplayYesNo:
return sec_conn ? PairingMethod::kNumericComparison
: PairingMethod::kPasskeyEntryInput;
default:
break;
}
// If both devices have KeyboardDisplay then use Numeric Comparison
// if S.C. is supported. Otherwise, the initiator always displays and the
// responder inputs a passkey.
if (sec_conn) {
return PairingMethod::kNumericComparison;
}
return local_initiator ? PairingMethod::kPasskeyEntryDisplay
: PairingMethod::kPasskeyEntryInput;
}
return PairingMethod::kJustWorks;
}
void Encrypt(const common::UInt128& key, const common::UInt128& plaintext_data,
common::UInt128* out_encrypted_data) {
// Swap the bytes since "the most significant octet of key corresponds to
// key[0], the most significant octet of plaintextData corresponds to in[0]
// and the most significant octet of encryptedData corresponds to out[0] using
// the notation specified in FIPS-197" for the security function "e" (Vol 3,
// Part H, 2.2.1).
UInt128 be_k, be_pt, be_enc;
Swap128(key, &be_k);
Swap128(plaintext_data, &be_pt);
AES_KEY k;
AES_set_encrypt_key(be_k.data(), 128, &k);
AES_encrypt(be_pt.data(), be_enc.data(), &k);
Swap128(be_enc, out_encrypted_data);
}
void C1(const UInt128& tk, const UInt128& rand, const ByteBuffer& preq,
const ByteBuffer& pres, const DeviceAddress& initiator_addr,
const DeviceAddress& responder_addr, UInt128* out_confirm_value) {
FXL_DCHECK(preq.size() == kPreqSize);
FXL_DCHECK(pres.size() == kPreqSize);
FXL_DCHECK(out_confirm_value);
UInt128 p1, p2;
// Calculate p1 = pres || preq || rat’ || iat’
hci::LEAddressType iat = hci::AddressTypeToHCI(initiator_addr.type());
hci::LEAddressType rat = hci::AddressTypeToHCI(responder_addr.type());
p1[0] = static_cast<uint8_t>(iat);
p1[1] = static_cast<uint8_t>(rat);
std::memcpy(p1.data() + 2, preq.data(), preq.size()); // Bytes [2-8]
std::memcpy(p1.data() + 2 + preq.size(), pres.data(), pres.size()); // [9-15]
// Calculate p2 = padding || ia || ra
BufferView ia = initiator_addr.value().bytes();
BufferView ra = responder_addr.value().bytes();
std::memcpy(p2.data(), ra.data(), ra.size()); // Lowest 6 bytes
std::memcpy(p2.data() + ra.size(), ia.data(), ia.size()); // Next 6 bytes
std::memset(p2.data() + ra.size() + ia.size(), 0,
p2.size() - ra.size() - ia.size()); // Pad 0s for the remainder
// Calculate the confirm value: e(tk, e(tk, rand XOR p1) XOR p2)
UInt128 tmp;
Xor128(rand, p1, &p1);
Encrypt(tk, p1, &tmp);
Xor128(tmp, p2, &tmp);
Encrypt(tk, tmp, out_confirm_value);
}
void S1(const UInt128& tk, const UInt128& r1, const UInt128& r2,
UInt128* out_stk) {
FXL_DCHECK(out_stk);
UInt128 r_prime;
// Take the lower 64-bits of r1 and r2 and concatanate them to produce
// r’ = r1’ || r2’, where r2' contains the LSB and r1' the MSB.
constexpr size_t kHalfSize = sizeof(UInt128) / 2;
std::memcpy(r_prime.data(), r2.data(), kHalfSize);
std::memcpy(r_prime.data() + kHalfSize, r1.data(), kHalfSize);
// Calculate the STK: e(tk, r’)
Encrypt(tk, r_prime, out_stk);
}
} // namespace util
} // namespace sm
} // namespace btlib