mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-27 14:14:32 +08:00
7582f36071
BT core spec 5.3 promotes the usage of inclusive languages. This CL replaces some terms with the more appropriate counterparts, such as "central", "peripheral", "link key", and "accept list" Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
924 lines
20 KiB
C
924 lines
20 KiB
C
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2013-2014 Intel Corporation
|
|
*
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <endian.h>
|
|
#include <stdbool.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include "lib/bluetooth.h"
|
|
#include "lib/hci.h"
|
|
|
|
#include "src/shared/util.h"
|
|
#include "src/shared/crypto.h"
|
|
#include "src/shared/ecc.h"
|
|
#include "monitor/bt.h"
|
|
#include "bthost.h"
|
|
|
|
#define SMP_CID 0x0006
|
|
#define SMP_BREDR_CID 0x0007
|
|
|
|
#define L2CAP_FC_SMP_BREDR 0x80
|
|
|
|
#define SMP_PASSKEY_ENTRY_FAILED 0x01
|
|
#define SMP_OOB_NOT_AVAIL 0x02
|
|
#define SMP_AUTH_REQUIREMENTS 0x03
|
|
#define SMP_CONFIRM_FAILED 0x04
|
|
#define SMP_PAIRING_NOTSUPP 0x05
|
|
#define SMP_ENC_KEY_SIZE 0x06
|
|
#define SMP_CMD_NOTSUPP 0x07
|
|
#define SMP_UNSPECIFIED 0x08
|
|
#define SMP_REPEATED_ATTEMPTS 0x09
|
|
#define SMP_INVALID_PARAMS 0x0a
|
|
#define SMP_DHKEY_CHECK_FAILED 0x0b
|
|
#define SMP_NUMERIC_COMP_FAILED 0x0c
|
|
#define SMP_BREDR_PAIRING_IN_PROGRESS 0x0d
|
|
|
|
#define DIST_ENC_KEY 0x01
|
|
#define DIST_ID_KEY 0x02
|
|
#define DIST_SIGN 0x04
|
|
#define DIST_LINK_KEY 0x08
|
|
|
|
#define SC_NO_DIST (DIST_ENC_KEY | DIST_LINK_KEY)
|
|
|
|
#define MAX_IO_CAP 0x04
|
|
|
|
#define SMP_AUTH_NONE 0x00
|
|
#define SMP_AUTH_BONDING 0x01
|
|
#define SMP_AUTH_MITM 0x04
|
|
#define SMP_AUTH_SC 0x08
|
|
#define SMP_AUTH_KEYPRESS 0x10
|
|
|
|
struct smp {
|
|
struct bthost *bthost;
|
|
struct smp_conn *conn;
|
|
struct bt_crypto *crypto;
|
|
};
|
|
|
|
struct smp_conn {
|
|
struct smp *smp;
|
|
uint16_t handle;
|
|
uint8_t addr_type;
|
|
bool out;
|
|
bool sc;
|
|
bool initiator;
|
|
uint8_t method;
|
|
uint8_t local_key_dist;
|
|
uint8_t remote_key_dist;
|
|
uint8_t ia[6];
|
|
uint8_t ia_type;
|
|
uint8_t ra[6];
|
|
uint8_t ra_type;
|
|
uint8_t tk[16];
|
|
uint8_t prnd[16];
|
|
uint8_t rrnd[16];
|
|
uint8_t pcnf[16];
|
|
uint8_t preq[7];
|
|
uint8_t prsp[7];
|
|
uint8_t ltk[16];
|
|
|
|
uint8_t local_sk[32];
|
|
uint8_t local_pk[64];
|
|
uint8_t remote_pk[64];
|
|
uint8_t dhkey[32];
|
|
uint8_t mackey[16];
|
|
|
|
uint8_t passkey_notify;
|
|
uint8_t passkey_round;
|
|
};
|
|
|
|
enum {
|
|
JUST_WORKS,
|
|
JUST_CFM,
|
|
REQ_PASSKEY,
|
|
CFM_PASSKEY,
|
|
REQ_OOB,
|
|
DSP_PASSKEY,
|
|
OVERLAP,
|
|
};
|
|
|
|
static const uint8_t gen_method[5][5] = {
|
|
{ JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY },
|
|
{ JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY },
|
|
{ CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY },
|
|
{ JUST_WORKS, JUST_CFM, JUST_WORKS, JUST_WORKS, JUST_CFM },
|
|
{ CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, OVERLAP },
|
|
};
|
|
|
|
static const uint8_t sc_method[5][5] = {
|
|
{ JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY },
|
|
{ JUST_WORKS, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY },
|
|
{ DSP_PASSKEY, DSP_PASSKEY, REQ_PASSKEY, JUST_WORKS, DSP_PASSKEY },
|
|
{ JUST_WORKS, JUST_CFM, JUST_WORKS, JUST_WORKS, JUST_CFM },
|
|
{ DSP_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY },
|
|
};
|
|
|
|
static uint8_t get_auth_method(struct smp_conn *conn, uint8_t local_io,
|
|
uint8_t remote_io)
|
|
{
|
|
/* If either side has unknown io_caps, use JUST_CFM (which gets
|
|
* converted later to JUST_WORKS if we're initiators.
|
|
*/
|
|
if (local_io > MAX_IO_CAP || remote_io > MAX_IO_CAP)
|
|
return JUST_CFM;
|
|
|
|
if (conn->sc)
|
|
return sc_method[remote_io][local_io];
|
|
|
|
return gen_method[remote_io][local_io];
|
|
}
|
|
|
|
static uint8_t sc_select_method(struct smp_conn *conn)
|
|
{
|
|
struct bt_l2cap_smp_pairing_request *local, *remote;
|
|
uint8_t local_mitm, remote_mitm, local_io, remote_io, method;
|
|
|
|
if (conn->out) {
|
|
local = (void *) &conn->preq[1];
|
|
remote = (void *) &conn->prsp[1];
|
|
} else {
|
|
local = (void *) &conn->prsp[1];
|
|
remote = (void *) &conn->preq[1];
|
|
}
|
|
|
|
local_io = local->io_capa;
|
|
remote_io = remote->io_capa;
|
|
|
|
local_mitm = (local->auth_req & SMP_AUTH_MITM);
|
|
remote_mitm = (remote->auth_req & SMP_AUTH_MITM);
|
|
|
|
/* If either side wants MITM, look up the method from the table,
|
|
* otherwise use JUST WORKS.
|
|
*/
|
|
if (local_mitm || remote_mitm)
|
|
method = get_auth_method(conn, local_io, remote_io);
|
|
else
|
|
method = JUST_WORKS;
|
|
|
|
/* Don't confirm locally initiated pairing attempts */
|
|
if (method == JUST_CFM && conn->initiator)
|
|
method = JUST_WORKS;
|
|
|
|
return method;
|
|
}
|
|
|
|
static uint8_t key_dist(struct bthost *host)
|
|
{
|
|
if (!bthost_bredr_capable(host))
|
|
return (DIST_ENC_KEY | DIST_ID_KEY | DIST_SIGN);
|
|
|
|
return (DIST_ENC_KEY | DIST_ID_KEY | DIST_SIGN | DIST_LINK_KEY);
|
|
}
|
|
|
|
static void smp_send(struct smp_conn *conn, uint8_t smp_cmd, const void *data,
|
|
uint8_t len)
|
|
{
|
|
struct iovec iov[2];
|
|
uint16_t cid;
|
|
|
|
iov[0].iov_base = &smp_cmd;
|
|
iov[0].iov_len = 1;
|
|
|
|
iov[1].iov_base = (void *) data;
|
|
iov[1].iov_len = len;
|
|
|
|
if (conn->addr_type == BDADDR_BREDR)
|
|
cid = SMP_BREDR_CID;
|
|
else
|
|
cid = SMP_CID;
|
|
|
|
bthost_send_cid_v(conn->smp->bthost, conn->handle, cid, iov, 2);
|
|
}
|
|
|
|
static bool send_public_key(struct smp_conn *conn)
|
|
{
|
|
if (!ecc_make_key(conn->local_pk, conn->local_sk))
|
|
return false;
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_PUBLIC_KEY, conn->local_pk, 64);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void sc_dhkey_check(struct smp_conn *conn)
|
|
{
|
|
uint8_t io_cap[3], r[16], a[7], b[7], *local_addr, *remote_addr;
|
|
struct bt_l2cap_smp_dhkey_check check;
|
|
|
|
memcpy(a, conn->ia, 6);
|
|
memcpy(b, conn->ra, 6);
|
|
a[6] = conn->ia_type;
|
|
b[6] = conn->ra_type;
|
|
|
|
if (conn->out) {
|
|
local_addr = a;
|
|
remote_addr = b;
|
|
memcpy(io_cap, &conn->preq[1], 3);
|
|
} else {
|
|
local_addr = b;
|
|
remote_addr = a;
|
|
memcpy(io_cap, &conn->prsp[1], 3);
|
|
}
|
|
|
|
memset(r, 0, sizeof(r));
|
|
|
|
bt_crypto_f6(conn->smp->crypto, conn->mackey, conn->prnd, conn->rrnd,
|
|
r, io_cap, local_addr, remote_addr, check.e);
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_DHKEY_CHECK, &check, sizeof(check));
|
|
}
|
|
|
|
static void sc_mackey_and_ltk(struct smp_conn *conn)
|
|
{
|
|
uint8_t *na, *nb, a[7], b[7];
|
|
|
|
if (conn->out) {
|
|
na = conn->prnd;
|
|
nb = conn->rrnd;
|
|
} else {
|
|
na = conn->rrnd;
|
|
nb = conn->prnd;
|
|
}
|
|
|
|
memcpy(a, conn->ia, 6);
|
|
memcpy(b, conn->ra, 6);
|
|
a[6] = conn->ia_type;
|
|
b[6] = conn->ra_type;
|
|
|
|
bt_crypto_f5(conn->smp->crypto, conn->dhkey, na, nb, a, b,
|
|
conn->mackey, conn->ltk);
|
|
}
|
|
|
|
static uint8_t sc_passkey_send_confirm(struct smp_conn *conn)
|
|
{
|
|
struct bt_l2cap_smp_pairing_confirm cfm;
|
|
uint8_t r;
|
|
|
|
r = ((conn->passkey_notify >> conn->passkey_round) & 0x01);
|
|
r |= 0x80;
|
|
|
|
if (!bt_crypto_f4(conn->smp->crypto, conn->local_pk, conn->remote_pk,
|
|
conn->prnd, r, cfm.value))
|
|
return SMP_UNSPECIFIED;
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, &cfm, sizeof(cfm));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t sc_passkey_round(struct smp_conn *conn, uint8_t smp_op)
|
|
{
|
|
uint8_t cfm[16], r;
|
|
|
|
/* Ignore the PDU if we've already done 20 rounds (0 - 19) */
|
|
if (conn->passkey_round >= 20)
|
|
return 0;
|
|
|
|
switch (smp_op) {
|
|
case BT_L2CAP_SMP_PAIRING_RANDOM:
|
|
r = ((conn->passkey_notify >> conn->passkey_round) & 0x01);
|
|
r |= 0x80;
|
|
|
|
if (!bt_crypto_f4(conn->smp->crypto, conn->remote_pk,
|
|
conn->local_pk, conn->rrnd, r, cfm))
|
|
return SMP_UNSPECIFIED;
|
|
|
|
if (memcmp(conn->pcnf, cfm, 16))
|
|
return SMP_CONFIRM_FAILED;
|
|
|
|
conn->passkey_round++;
|
|
|
|
if (conn->passkey_round == 20) {
|
|
/* Generate MacKey and LTK */
|
|
sc_mackey_and_ltk(conn);
|
|
}
|
|
|
|
/* The round is only complete when the initiator
|
|
* receives pairing random.
|
|
*/
|
|
if (!conn->out) {
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM,
|
|
conn->prnd, sizeof(conn->prnd));
|
|
return 0;
|
|
}
|
|
|
|
/* Start the next round */
|
|
if (conn->passkey_round != 20)
|
|
return sc_passkey_round(conn, 0);
|
|
|
|
/* Passkey rounds are complete - start DHKey Check */
|
|
sc_dhkey_check(conn);
|
|
|
|
break;
|
|
|
|
case BT_L2CAP_SMP_PAIRING_CONFIRM:
|
|
if (conn->out) {
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM,
|
|
conn->prnd, sizeof(conn->prnd));
|
|
return 0;
|
|
}
|
|
|
|
return sc_passkey_send_confirm(conn);
|
|
|
|
case BT_L2CAP_SMP_PUBLIC_KEY:
|
|
default:
|
|
/* Initiating device starts the round */
|
|
if (!conn->out)
|
|
return 0;
|
|
|
|
return sc_passkey_send_confirm(conn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool verify_random(struct smp_conn *conn, const uint8_t rnd[16])
|
|
{
|
|
uint8_t confirm[16];
|
|
|
|
if (!bt_crypto_c1(conn->smp->crypto, conn->tk, conn->rrnd, conn->prsp,
|
|
conn->preq, conn->ia_type, conn->ia,
|
|
conn->ra_type, conn->ra, confirm))
|
|
return false;
|
|
|
|
if (memcmp(conn->pcnf, confirm, sizeof(conn->pcnf)) != 0) {
|
|
bthost_debug(conn->smp->bthost,
|
|
"Confirmation values don't match");
|
|
return false;
|
|
}
|
|
|
|
if (conn->out) {
|
|
bt_crypto_s1(conn->smp->crypto, conn->tk, conn->rrnd,
|
|
conn->prnd, conn->ltk);
|
|
bthost_le_start_encrypt(conn->smp->bthost, conn->handle,
|
|
conn->ltk);
|
|
} else {
|
|
bt_crypto_s1(conn->smp->crypto, conn->tk, conn->prnd,
|
|
conn->rrnd, conn->ltk);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void distribute_keys(struct smp_conn *conn)
|
|
{
|
|
uint8_t buf[16];
|
|
|
|
if (conn->local_key_dist & DIST_ENC_KEY) {
|
|
memset(buf, 0, sizeof(buf));
|
|
smp_send(conn, BT_L2CAP_SMP_ENCRYPT_INFO, buf, sizeof(buf));
|
|
smp_send(conn, BT_L2CAP_SMP_CENTRAL_IDENT, buf, 10);
|
|
}
|
|
|
|
if (conn->local_key_dist & DIST_ID_KEY) {
|
|
memset(buf, 0, sizeof(buf));
|
|
smp_send(conn, BT_L2CAP_SMP_IDENT_INFO, buf, sizeof(buf));
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
if (conn->out) {
|
|
buf[0] = conn->ia_type;
|
|
memcpy(&buf[1], conn->ia, 6);
|
|
} else {
|
|
buf[0] = conn->ra_type;
|
|
memcpy(&buf[1], conn->ra, 6);
|
|
}
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_IDENT_ADDR_INFO, buf, 7);
|
|
}
|
|
|
|
if (conn->local_key_dist & DIST_SIGN) {
|
|
memset(buf, 0, sizeof(buf));
|
|
smp_send(conn, BT_L2CAP_SMP_SIGNING_INFO, buf, sizeof(buf));
|
|
}
|
|
}
|
|
|
|
static void pairing_req(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
struct bthost *bthost = conn->smp->bthost;
|
|
struct bt_l2cap_smp_pairing_response rsp;
|
|
|
|
memcpy(conn->preq, data, sizeof(conn->preq));
|
|
|
|
if (conn->addr_type == BDADDR_BREDR) {
|
|
rsp.io_capa = 0x00;
|
|
rsp.oob_data = 0x00;
|
|
rsp.auth_req = 0x00;
|
|
} else {
|
|
rsp.io_capa = bthost_get_io_capability(bthost);
|
|
rsp.oob_data = 0x00;
|
|
rsp.auth_req = bthost_get_auth_req(bthost);
|
|
}
|
|
|
|
rsp.max_key_size = 0x10;
|
|
rsp.init_key_dist = conn->preq[5] & key_dist(bthost);
|
|
rsp.resp_key_dist = conn->preq[6] & key_dist(bthost);
|
|
|
|
conn->prsp[0] = BT_L2CAP_SMP_PAIRING_RESPONSE;
|
|
memcpy(&conn->prsp[1], &rsp, sizeof(rsp));
|
|
|
|
conn->local_key_dist = rsp.resp_key_dist;
|
|
conn->remote_key_dist = rsp.init_key_dist;
|
|
|
|
if (((conn->prsp[3] & 0x08) && (conn->preq[3] & 0x08)) ||
|
|
conn->addr_type == BDADDR_BREDR) {
|
|
conn->sc = true;
|
|
conn->local_key_dist &= ~SC_NO_DIST;
|
|
conn->remote_key_dist &= ~SC_NO_DIST;
|
|
}
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_RESPONSE, &rsp, sizeof(rsp));
|
|
|
|
if (conn->addr_type == BDADDR_BREDR)
|
|
distribute_keys(conn);
|
|
}
|
|
|
|
static void pairing_rsp(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
struct smp *smp = conn->smp;
|
|
uint8_t cfm[16];
|
|
|
|
memcpy(conn->prsp, data, sizeof(conn->prsp));
|
|
|
|
conn->local_key_dist = conn->prsp[5];
|
|
conn->remote_key_dist = conn->prsp[6];
|
|
|
|
if (conn->addr_type == BDADDR_BREDR) {
|
|
conn->local_key_dist &= ~SC_NO_DIST;
|
|
conn->remote_key_dist &= ~SC_NO_DIST;
|
|
distribute_keys(conn);
|
|
return;
|
|
}
|
|
|
|
if (((conn->prsp[3] & 0x08) && (conn->preq[3] & 0x08)) ||
|
|
conn->addr_type == BDADDR_BREDR) {
|
|
conn->sc = true;
|
|
conn->local_key_dist &= ~SC_NO_DIST;
|
|
conn->remote_key_dist &= ~SC_NO_DIST;
|
|
if (conn->addr_type == BDADDR_BREDR)
|
|
distribute_keys(conn);
|
|
else
|
|
send_public_key(conn);
|
|
return;
|
|
}
|
|
|
|
bt_crypto_c1(smp->crypto, conn->tk, conn->prnd, conn->prsp,
|
|
conn->preq, conn->ia_type, conn->ia,
|
|
conn->ra_type, conn->ra, cfm);
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, cfm, sizeof(cfm));
|
|
}
|
|
static void sc_check_confirm(struct smp_conn *conn)
|
|
{
|
|
if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY) {
|
|
sc_passkey_round(conn, BT_L2CAP_SMP_PAIRING_CONFIRM);
|
|
return;
|
|
}
|
|
|
|
if (conn->out)
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd,
|
|
sizeof(conn->prnd));
|
|
}
|
|
|
|
static void pairing_cfm(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
uint8_t rsp[16];
|
|
|
|
memcpy(conn->pcnf, data + 1, 16);
|
|
|
|
if (conn->sc) {
|
|
sc_check_confirm(conn);
|
|
return;
|
|
}
|
|
|
|
if (conn->out) {
|
|
memset(rsp, 0, sizeof(rsp));
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd,
|
|
sizeof(conn->prnd));
|
|
} else {
|
|
bt_crypto_c1(conn->smp->crypto, conn->tk, conn->prnd,
|
|
conn->prsp, conn->preq, conn->ia_type,
|
|
conn->ia, conn->ra_type, conn->ra, rsp);
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, rsp, sizeof(rsp));
|
|
}
|
|
}
|
|
|
|
static uint8_t sc_random(struct smp_conn *conn)
|
|
{
|
|
/* Passkey entry has special treatment */
|
|
if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY)
|
|
return sc_passkey_round(conn, BT_L2CAP_SMP_PAIRING_RANDOM);
|
|
|
|
if (conn->out) {
|
|
uint8_t cfm[16];
|
|
|
|
bt_crypto_f4(conn->smp->crypto, conn->remote_pk,
|
|
conn->local_pk, conn->rrnd, 0, cfm);
|
|
|
|
if (memcmp(conn->pcnf, cfm, 16))
|
|
return 0x04; /* Confirm Value Failed */
|
|
} else {
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, 16);
|
|
}
|
|
|
|
sc_mackey_and_ltk(conn);
|
|
|
|
if (conn->out)
|
|
sc_dhkey_check(conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pairing_rnd(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
memcpy(conn->rrnd, data + 1, 16);
|
|
|
|
if (conn->sc) {
|
|
uint8_t reason = sc_random(conn);
|
|
if (reason)
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_FAILED, &reason,
|
|
sizeof(reason));
|
|
return;
|
|
}
|
|
|
|
if (!verify_random(conn, data + 1))
|
|
return;
|
|
|
|
if (conn->out)
|
|
return;
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd,
|
|
sizeof(conn->prnd));
|
|
}
|
|
|
|
static void encrypt_info(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
}
|
|
|
|
static void central_ident(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
conn->remote_key_dist &= ~DIST_ENC_KEY;
|
|
|
|
if (conn->out && !conn->remote_key_dist)
|
|
distribute_keys(conn);
|
|
}
|
|
|
|
static void ident_addr_info(struct smp_conn *conn, const void *data,
|
|
uint16_t len)
|
|
{
|
|
}
|
|
|
|
static void ident_info(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
conn->remote_key_dist &= ~DIST_ID_KEY;
|
|
|
|
if (conn->out && !conn->remote_key_dist)
|
|
distribute_keys(conn);
|
|
}
|
|
|
|
static void signing_info(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
conn->remote_key_dist &= ~DIST_SIGN;
|
|
|
|
if (conn->out && !conn->remote_key_dist)
|
|
distribute_keys(conn);
|
|
}
|
|
|
|
static void public_key(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
struct smp *smp = conn->smp;
|
|
uint8_t buf[16];
|
|
|
|
memcpy(conn->remote_pk, data + 1, 64);
|
|
|
|
if (!conn->out) {
|
|
if (!send_public_key(conn))
|
|
return;
|
|
}
|
|
|
|
if (!ecdh_shared_secret(conn->remote_pk, conn->local_sk, conn->dhkey))
|
|
return;
|
|
|
|
conn->method = sc_select_method(conn);
|
|
|
|
if (conn->method == DSP_PASSKEY || conn->method == REQ_PASSKEY) {
|
|
sc_passkey_round(conn, BT_L2CAP_SMP_PUBLIC_KEY);
|
|
return;
|
|
}
|
|
|
|
if (conn->out)
|
|
return;
|
|
|
|
if (!bt_crypto_f4(smp->crypto, conn->local_pk, conn->remote_pk,
|
|
conn->prnd, 0, buf))
|
|
return;
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, buf, sizeof(buf));
|
|
}
|
|
|
|
static void dhkey_check(struct smp_conn *conn, const void *data, uint16_t len)
|
|
{
|
|
const struct bt_l2cap_smp_dhkey_check *cmd = data + 1;
|
|
uint8_t a[7], b[7], *local_addr, *remote_addr;
|
|
uint8_t io_cap[3], r[16], e[16];
|
|
|
|
memcpy(a, &conn->ia, 6);
|
|
memcpy(b, &conn->ra, 6);
|
|
a[6] = conn->ia_type;
|
|
b[6] = conn->ra_type;
|
|
|
|
if (conn->out) {
|
|
local_addr = a;
|
|
remote_addr = b;
|
|
memcpy(io_cap, &conn->prsp[1], 3);
|
|
} else {
|
|
local_addr = b;
|
|
remote_addr = a;
|
|
memcpy(io_cap, &conn->preq[1], 3);
|
|
}
|
|
|
|
memset(r, 0, sizeof(r));
|
|
|
|
if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY)
|
|
put_le32(conn->passkey_notify, r);
|
|
|
|
if (!bt_crypto_f6(conn->smp->crypto, conn->mackey, conn->rrnd,
|
|
conn->prnd, r, io_cap, remote_addr, local_addr, e))
|
|
return;
|
|
|
|
if (memcmp(cmd->e, e, 16)) {
|
|
uint8_t reason = 0x0b; /* DHKey Check Failed */
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_FAILED, &reason,
|
|
sizeof(reason));
|
|
}
|
|
|
|
if (conn->out)
|
|
bthost_le_start_encrypt(conn->smp->bthost, conn->handle,
|
|
conn->ltk);
|
|
else
|
|
sc_dhkey_check(conn);
|
|
}
|
|
|
|
void smp_pair(void *conn_data, uint8_t io_cap, uint8_t auth_req)
|
|
{
|
|
struct smp_conn *conn = conn_data;
|
|
struct bt_l2cap_smp_pairing_request req;
|
|
|
|
req.io_capa = io_cap;
|
|
req.oob_data = 0x00;
|
|
req.auth_req = auth_req;
|
|
req.max_key_size = 0x10;
|
|
req.init_key_dist = key_dist(conn->smp->bthost);
|
|
req.resp_key_dist = key_dist(conn->smp->bthost);
|
|
|
|
conn->preq[0] = BT_L2CAP_SMP_PAIRING_REQUEST;
|
|
memcpy(&conn->preq[1], &req, sizeof(req));
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_REQUEST, &req, sizeof(req));
|
|
}
|
|
|
|
void smp_data(void *conn_data, const void *data, uint16_t len)
|
|
{
|
|
struct smp_conn *conn = conn_data;
|
|
uint8_t opcode;
|
|
|
|
if (len < 1) {
|
|
bthost_debug(conn->smp->bthost, "Received too small SMP PDU");
|
|
return;
|
|
}
|
|
|
|
if (conn->addr_type == BDADDR_BREDR) {
|
|
bthost_debug(conn->smp->bthost,
|
|
"Received BR/EDR SMP data on LE link");
|
|
return;
|
|
}
|
|
|
|
opcode = *((const uint8_t *) data);
|
|
|
|
switch (opcode) {
|
|
case BT_L2CAP_SMP_PAIRING_REQUEST:
|
|
pairing_req(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_PAIRING_RESPONSE:
|
|
pairing_rsp(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_PAIRING_CONFIRM:
|
|
pairing_cfm(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_PAIRING_RANDOM:
|
|
pairing_rnd(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_ENCRYPT_INFO:
|
|
encrypt_info(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_CENTRAL_IDENT:
|
|
central_ident(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_IDENT_ADDR_INFO:
|
|
ident_addr_info(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_IDENT_INFO:
|
|
ident_info(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_SIGNING_INFO:
|
|
signing_info(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_PUBLIC_KEY:
|
|
public_key(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_DHKEY_CHECK:
|
|
dhkey_check(conn, data, len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void smp_bredr_data(void *conn_data, const void *data, uint16_t len)
|
|
{
|
|
struct smp_conn *conn = conn_data;
|
|
uint8_t opcode;
|
|
|
|
if (len < 1) {
|
|
bthost_debug(conn->smp->bthost, "Received too small SMP PDU");
|
|
return;
|
|
}
|
|
|
|
if (conn->addr_type != BDADDR_BREDR) {
|
|
bthost_debug(conn->smp->bthost,
|
|
"Received LE SMP data on BR/EDR link");
|
|
return;
|
|
}
|
|
|
|
opcode = *((const uint8_t *) data);
|
|
|
|
switch (opcode) {
|
|
case BT_L2CAP_SMP_PAIRING_REQUEST:
|
|
pairing_req(conn, data, len);
|
|
break;
|
|
case BT_L2CAP_SMP_PAIRING_RESPONSE:
|
|
pairing_rsp(conn, data, len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int smp_get_ltk(void *smp_data, uint64_t rand, uint16_t ediv, uint8_t *ltk)
|
|
{
|
|
struct smp_conn *conn = smp_data;
|
|
static const uint8_t no_ltk[16] = { 0 };
|
|
|
|
if (!memcmp(conn->ltk, no_ltk, 16))
|
|
return -ENOENT;
|
|
|
|
memcpy(ltk, conn->ltk, 16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void smp_conn_bredr(struct smp_conn *conn, uint8_t encrypt)
|
|
{
|
|
struct smp *smp = conn->smp;
|
|
struct bt_l2cap_smp_pairing_request req;
|
|
uint64_t fixed_chan;
|
|
|
|
if (encrypt != 0x02)
|
|
return;
|
|
|
|
conn->sc = true;
|
|
|
|
if (!conn->out)
|
|
return;
|
|
|
|
fixed_chan = bthost_conn_get_fixed_chan(smp->bthost, conn->handle);
|
|
if (!(fixed_chan & L2CAP_FC_SMP_BREDR))
|
|
return;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.max_key_size = 0x10;
|
|
req.init_key_dist = key_dist(smp->bthost);
|
|
req.resp_key_dist = key_dist(smp->bthost);
|
|
|
|
smp_send(conn, BT_L2CAP_SMP_PAIRING_REQUEST, &req, sizeof(req));
|
|
}
|
|
|
|
void smp_conn_encrypted(void *conn_data, uint8_t encrypt)
|
|
{
|
|
struct smp_conn *conn = conn_data;
|
|
|
|
if (!encrypt)
|
|
return;
|
|
|
|
if (conn->addr_type == BDADDR_BREDR) {
|
|
smp_conn_bredr(conn, encrypt);
|
|
return;
|
|
}
|
|
|
|
if (conn->out && conn->remote_key_dist)
|
|
return;
|
|
|
|
distribute_keys(conn);
|
|
}
|
|
|
|
static uint8_t type2hci(uint8_t addr_type)
|
|
{
|
|
switch (addr_type) {
|
|
case BDADDR_BREDR:
|
|
case BDADDR_LE_PUBLIC:
|
|
return LE_PUBLIC_ADDRESS;
|
|
case BDADDR_LE_RANDOM:
|
|
return LE_RANDOM_ADDRESS;
|
|
}
|
|
|
|
return 0x00;
|
|
}
|
|
|
|
void *smp_conn_add(void *smp_data, uint16_t handle,
|
|
const uint8_t *ia, uint8_t ia_type,
|
|
const uint8_t *ra, uint8_t ra_type, bool conn_init)
|
|
{
|
|
struct smp *smp = smp_data;
|
|
struct smp_conn *conn;
|
|
char ia_str[18], ra_str[18];
|
|
|
|
conn = malloc(sizeof(struct smp_conn));
|
|
if (!conn)
|
|
return NULL;
|
|
|
|
memset(conn, 0, sizeof(*conn));
|
|
|
|
conn->smp = smp;
|
|
conn->handle = handle;
|
|
conn->out = conn_init;
|
|
conn->addr_type = conn_init ? ia_type : ra_type;
|
|
|
|
conn->ia_type = type2hci(ia_type);
|
|
conn->ra_type = type2hci(ra_type);
|
|
memcpy(conn->ia, ia, 6);
|
|
memcpy(conn->ra, ra, 6);
|
|
|
|
ba2str((bdaddr_t *) ia, ia_str);
|
|
ba2str((bdaddr_t *) ra, ra_str);
|
|
|
|
bthost_debug(smp->bthost, "ia %s type 0x%02x ra %s type 0x%02x",
|
|
ia_str, ia_type, ra_str, ra_type);
|
|
|
|
bt_crypto_random_bytes(smp->crypto, conn->prnd, sizeof(conn->prnd));
|
|
|
|
return conn;
|
|
}
|
|
|
|
void smp_conn_del(void *conn_data)
|
|
{
|
|
struct smp_conn *conn = conn_data;
|
|
|
|
free(conn);
|
|
}
|
|
|
|
void *smp_start(struct bthost *bthost)
|
|
{
|
|
struct smp *smp;
|
|
|
|
smp = malloc(sizeof(struct smp));
|
|
if (!smp)
|
|
return NULL;
|
|
|
|
memset(smp, 0, sizeof(*smp));
|
|
|
|
smp->crypto = bt_crypto_new();
|
|
if (!smp->crypto) {
|
|
free(smp);
|
|
return NULL;
|
|
}
|
|
|
|
smp->bthost = bthost;
|
|
|
|
return smp;
|
|
}
|
|
|
|
void smp_stop(void *smp_data)
|
|
{
|
|
struct smp *smp = smp_data;
|
|
|
|
bt_crypto_unref(smp->crypto);
|
|
|
|
free(smp);
|
|
}
|