blob: c9e2a4a7c262cf52d3a6412e9f94d7a20ad129d1 [file] [log] [blame]
/*
* Copyright 2017 The Chromium OS 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "newblue-macros.h"
#include "bt.h"
#include "config.h"
#include "l2cap.h"
#include "log.h"
#include "mt.h"
#include "multiNotif.h"
#include "persist.h"
#include "sendQ.h"
#include "sg.h"
#include "timer.h"
#include "types.h"
#include "util.h"
#include "workQueue.h"
#include "sm.h"
/* definition of structs and macros based on Bluetooth spec v4.0 */
/* packet types */
#define SM_PAIRING_REQ 0x01 /* struct smPairReqResp */
#define SM_PAIRING_RSP 0x02 /* struct smPairReqResp */
#define SM_PAIRING_CONF 0x03 /* struct smPairConf */
#define SM_PAIRING_RAND 0x04 /* struct smPairRand */
#define SM_PAIRING_FAIL 0x05 /* struct smPairFailed */
#define SM_ENCR_INFO 0x06 /* struct smKey => LTK */
#define SM_MASTER_INFO 0x07 /* struct smMasterInfo */
#define SM_IDENTITY_INFO 0x08 /* struct smKey => IRK */
#define SM_IDENTITY_ADDR_INFO 0x09 /* struct smIdentityAddrInfo */
#define SM_SIGNING_INFO 0x0A /* struct smKey => CSRK */
#define SM_SECURITY_REQUEST 0x0B /* struct smAuthReq */
struct smHdr {
uint8_t typ;
} __packed;
struct smPairReqResp {
uint8_t ioCap;
uint8_t oobDataFlag;
uint8_t authReq; /* SM_AUTH_REQ_* */
uint8_t maxKeySz;
uint8_t initiatorKeyDistr; /* bitfield of SM_KEY_DISTR_* */
uint8_t responderKeyDistr; /* bitfield of SM_KEY_DISTR_* */
} __packed;
struct smPairConf {
uint8_t confirmVal[SM_BLOCK_LEN]; /* [0]->[15] : MSO->LSO */
} __packed;
struct smPairRand {
uint8_t rand[SM_BLOCK_LEN]; /* [0]->[15] : MSO->LSO */
} __packed;
struct smPairFailed {
uint8_t reason; /* SM_ERR_* */
} __packed;
struct smKey {
uint8_t key[HCI_LE_KEY_LEN]; /* [0]->[15] : MSO->LSO */
} __packed;
struct smMasterInfo {
uint16_t ediv;
uint64_t rand;
} __packed;
struct smIdentityAddrInfo {
uint8_t addrType;
uint8_t mac[BT_MAC_LEN];
} __packed;
struct smAuthReq {
uint8_t authReq; /* SM_AUTH_REQ_* */
} __packed;
/* fail codes */
#define SM_ERR_PASSKEY_ENTRY_FAILED 0x01 /* user cancelled or failed to input key */
#define SM_ERR_OOB_NOT_AVAIL 0x02 /* no OOB data */
#define SM_ERR_AUTH_REQMENTS 0x03 /* auth requirements cannot be met due to IO caps */
#define SM_ERR_CONF_VAL_FAILED 0x04 /* confirm value does not match calculated value */
#define SM_ERR_PAIRING_NOT_SUPPORTED 0x05 /* pairing not supported by this device */
#define SM_ERR_ENCR_KEY_SZ 0x06 /* resulting key size is not long enough */
#define SM_ERR_CMD_NOT_SUPP 0x07 /* received command not supported by this device */
#define SM_ERR_UNSPECCED_REASON 0x08 /* pairing failed due to an unspecified reason */
#define SM_ERR_REPEATED_ATTEMPTS 0x09 /* too rapid an attempt to pair after a failure */
#define SM_ERR_INVALID_PARAMS 0x0A /* unparsable command or invalid params in command */
/* things for authReq fields */
#define SM_AUTH_REQ_BOND_MASK 0x03
#define SM_AUTH_REQ_BOND_NO 0x00
#define SM_AUTH_REQ_BOND_BOND 0x01
#define SM_AUTH_REQ_MITM_FLAG 0x04
#define SM_AUTH_REQ_SC_FLAG 0x08
#define SM_AUTH_REQ_KEYPRESS_FLAG 0x10
/* things for key distr fields */
#define SM_KEY_DISTR_LTK 0x01
#define SM_KEY_DISTR_IRK 0x02
#define SM_KEY_DISTR_CSRK 0x04
/* misc defines */
#define SM_MIN_KEY_LEN 7 /* bytes */
#define SM_MAX_KEY_LEN 16 /* bytes */
/* definition of structs and macros based on our implementation */
#define SM_PHASE_START 0 /* nothing happened yet. */
#define SM_PHASE_SEC_REQ_SENT 1 /* we sent security req. waiting for pair req. */
#define SM_PHASE_REQ_SENT 2 /* we sent pair req. waiting for pair resp. */
#define SM_PHASE_REQ_RESP 3 /* the peer has sent us a pair request, we replied. waiting for Mconfirm. OR we sent req and got resp */
#define SM_PHASE_MCONF_SENT 4 /* we sent Mconfirm. waiting for Sconfirm */
#define SM_PHASE_MCONF_RECEIVED 5 /* the peer has sent us Mconfirm */
#define SM_PHASE_SCONF_READY 6 /* Sconfirm is generated and ready to be sent, Sconfirm can be generated before or after receiving Mconfirm */
#define SM_PHASE_SCONF_SENT 7 /* we replied with Sconfirm. waiting for Mrand.*/
#define SM_PHASE_SCONF_RECEIVED 8 /* we Sconfirm */
#define SM_PHASE_RAND_SENT 9 /* we sent Mrand. waiting for Srand. */
#define SM_PHASE_RAND_RESP 10 /* the peer has sent us Mrand, we replied with Srand. OR we sent Mrand and got Srand. */
#define SM_PHASE_STK_SENT 11 /* the controller asked us for LTK, we replied with STK. OR we started encryption with STK. */
#define SM_PHASE_STK_ENCRYPTED 12 /* we received encryption changed event with STK sent previously */
#define SM_PHASE_ENCRYPT_INFO_SENT 13 /* we sent our LTK via encryption information */
#define SM_PHASE_ENCRYPT_INFO_RECEIVED 14 /* we received peer's LTK via encryption information*/
#define SM_PHASE_MASTER_ID_SENT 15 /* we sent our EDIV and Rand via master identification */
#define SM_PHASE_MASTER_ID_RECEIVED 16 /* we received peer's EDIV and Rand via master identification */
#define SM_PHASE_ID_INFO_SENT 17 /* we sent our IRK via identity information */
#define SM_PHASE_ID_INFO_RECEIVED 18 /* we received peer's IRK via identity information */
#define SM_PHASE_ID_ADDR_INFO_SENT 19 /* we sent our public address via identity address information */
#define SM_PHASE_ID_ADDR_INFO_RECEIVED 20 /* we received peer's address via identity address information */
#define SM_PHASE_SIGN_INFO_SENT 21 /* we sent our CSRK via signing information */
#define SM_PHASE_SIGN_INFO_RECEIVED 22 /* we received peer's CSRK via signing information */
#define SM_PHASE_DONE 23 /* pairing is done. OR the peer was paired previously. */
#define SM_PHASE_LTK_SENT 24 /* the controller asked us for LTK, we replied with LTK. OR we started encryption with LTK. */
#define SM_PHASE_LTK_ENCRYPTED 25 /* pairing is done and we received encryption changed event with LTK sent previously */
#define SM_WAIT_INTVL_FACTOR 2 /* the wait interval grows exponentially with base 2 */
#define SM_DEF_WAIT_INTVL 0 /* 0 sec in msec - default wait interval */
#define SM_MIN_WAIT_INTVL 4000 /* 4 sec in msec - minimum wait interval */
#define SM_MAX_WAIT_INTVL 64000 /* 64 sec in msec - maximum wait interval */
#define SM_STALL_INTVL 30000 /* 30 sec in msec - timeout of a pairing process */
#define SM_UNAUTH_NO_MITM_PROTECT 0 /* Unauthenticated no MITM protection */
#define SM_AUTH_MITM_PROTECT 1 /* Authenticated MITM protection */
#define SM_WORK_NOTIFY_PAIR_STATE_CHG 0 /* work type of notifying upper layer about the pairing state change */
#define SM_WORK_NOTIFY_PASSKEY_DISPLAY 1 /* work type of notifying upper layer about the passkey display */
#define SM_WORK_NOTIFY_PASSKEY_REQUEST 2 /* work type of notifying upper layer about the passkey request */
struct smInstance {
struct smInstance* prev;
struct smInstance* next;
struct bt_addr peerAddr;
struct bt_addr myAddr;
l2c_handle_t conn;
bool isInitiator; /* whether we are the initiator */
uint8_t phase;
uniq_t stallTimer; /* the timer tracking whether a pairing session is stalled */
uint8_t alg; /* the SM_ALG_* selected */
struct smKey tk;
struct smKey stk;
bool isMitmSafe; /* the security property decided when the pairing algorithm is chosen */
uint8_t peerIoCap;
bool peerHasOob;
uint8_t peerAuthReq;
uint8_t peerMaxKeySz;
uint8_t peerKeyDistrReq; /* the key distribution in pairing request */
uint8_t peerKeyDistr;
struct smPairRand peerRandNum;
struct smPairConf peerConfVal;
uint8_t myAuthReq;
uint8_t myMaxKeySz;
uint8_t myKeyDistrReq; /* the key distribution in pairing request */
uint8_t myKeyDistr;
struct smPairRand myRandNum;
struct smPairConf myConfVal;
};
struct smDevNode {
struct smDevNode *prev;
struct smDevNode *next;
struct bt_addr addr; /* the address of the banned device */
uniq_t devId; /* an unique ID associated with the device */
uint64_t waitInvl; /* the wait interval of initiating/responding the pairing */
uniq_t waitTimer; /* the timer tracking the wait interval of a banned device */
};
struct smNumEnumCbkData {
bool wasFound; /* output: whether a peer device was found */
struct bt_addr peerAddr; /* output: peer device where the random number matched */
uint8_t numType; /* input: the target number type associated with num */
uint64_t num; /* input: the target number */
};
struct smIrkEnumCbkData {
bool wasFound; /* output: whether a peer device was found */
struct bt_addr peerAddr; /* output: peer device where an existing IRK can resolved the random
address */
uint8_t keyType; /* input: the target key type */
struct bt_addr resolAddr; /* input: the address to be resolved */
};
/* mask applied to the MSB of a address (addr[5] in this case) for checking the address type */
#define SM_RAND_ADDR_MASK 0xC0
#define SM_RAND_ADDR_STATIC 0xC0
#define SM_RAND_ADDR_PRI_NONRESOL 0x00
#define SM_RAND_ADDR_PRI_RESOL 0x40
/* address type */
enum smAddrType {addrTypeUnknown, addrTypePubStatic, addrTypeRandStatic, addrTypeRandPriNonResol,
addrTypeRandPriResol};
struct smPasskeyDisplayObserver {
uniq_t id;
smPasskeyDisplayCbk cbk;
void *data; /* observer data */
};
struct smPasskeyRequestObserver {
uniq_t id;
smPasskeyRequestCbk cbk;
void *data; /* observer data */
};
/* IO capability in terms of output and input */
#define SM_OUT_CAP_NONE 0x00
#define SM_OUT_CAP_NUMERIC 0x01
#define SM_IN_CAP_NONE 0x00
#define SM_IN_CAP_YES_NO 0x01
#define SM_IN_CAP_KBD 0x02
/* local settings and state */
static uint8_t mInCap = SM_IN_CAP_NONE; /* input capability */
static uint8_t mOutCap = SM_OUT_CAP_NONE; /* output capability */
static uint8_t mIoCap = HCI_DISP_CAP_NONE; /* IO capability based on mIoCaps[mInCap][mOutCap] */
static bool mHasOob = false; /* whether we support OOB data */
static pthread_mutex_t mInstLock = PTHREAD_MUTEX_INITIALIZER; /* lock for the active instance list */
static struct smInstance *mActInstList = NULL; /* the active instances */
static pthread_mutex_t mDevLock = PTHREAD_MUTEX_INITIALIZER; /* lock for the banned device list */
static struct smDevNode *mBannedDevList = NULL; /* the device banned on pairing */
static struct multiNotifList *mPairStateObservers = NULL; /* the observers of pairing state changes */
static pthread_mutex_t mObvrLock = PTHREAD_MUTEX_INITIALIZER; /* lock for the smObservers */
static struct smPasskeyDisplayObserver *mPasskeyDisplayObserver = NULL; /* the observer of pairing passkey display */
static struct smPasskeyRequestObserver *mPasskeyRequestObserver = NULL; /* the observer of pairing passkey request */
static pthread_t mNotifWorker; /* the worker thread for notifying applications */
static struct workQueue *mNotifWork = NULL; /* the work queue of notification works */
/* fwd decls */
static bool smGenSTK(struct smInstance *inst);
static bool smGenTK(struct smInstance *inst);
static bool smGenConfVal(struct smPairConf *conf, const struct smInstance *inst, bool check);
static bool smGenRandNum(uint8_t *out, uint8_t numLen);
static bool smGenLtk(struct smInstance *inst);
static bool smGenEdiv(uint16_t *out, const struct bt_addr *peerAddr, uint16_t div, uint64_t rand,
bool check);
static void smEncrypt(uint8_t* dst, const uint8_t *src, const uint8_t *key);
static void smNotifyPairStateObserver(const struct bt_addr *addr, SmPairState state, SmPairErr err);
static void smNotifyPasskeyDisplayObserver(const struct bt_addr *addr, bool valid,
uint32_t passkey);
static void smNotifyPasskeyRequestObserver(const struct bt_addr *addr, bool valid);
static uint32_t smAddressHash(uint32_t r, const uint8_t *key);
static bool smResolveAddr(struct bt_addr *addr, const struct bt_addr *peerAddr);
/* sendQ for packet transmissions */
static struct sendQ *mSmSendQ;
/* table of IO capability mapping */
static const uint8_t mIoCaps[3][2] = {
{HCI_DISP_CAP_NONE, HCI_DISP_CAP_DISP_ONLY},
{HCI_DISP_CAP_NONE, HCI_DISP_CAP_DISP_YES_NO},
{HCI_DISP_CAP_KBD_ONLY, HCI_DISP_CAP_KBD_DISP},
};
/* pairing algorithms */
#define SM_ALG_JUST_WORK 0x00
#define SM_ALG_PASS_KEY 0x01
#define SM_ALG_OOB 0x02
#define SM_ALG_UNKNOWN 0xFF
/* table of pairing algorithms - mPairAlgs[responder][initiator] */
static const uint8_t mPairAlgs[5][5] = {
{SM_ALG_JUST_WORK, SM_ALG_JUST_WORK, SM_ALG_PASS_KEY, SM_ALG_JUST_WORK, SM_ALG_PASS_KEY },
{SM_ALG_JUST_WORK, SM_ALG_JUST_WORK, SM_ALG_PASS_KEY, SM_ALG_JUST_WORK, SM_ALG_PASS_KEY },
{SM_ALG_PASS_KEY, SM_ALG_PASS_KEY, SM_ALG_PASS_KEY, SM_ALG_JUST_WORK, SM_ALG_PASS_KEY },
{SM_ALG_JUST_WORK, SM_ALG_JUST_WORK, SM_ALG_JUST_WORK, SM_ALG_JUST_WORK, SM_ALG_JUST_WORK},
{SM_ALG_PASS_KEY, SM_ALG_PASS_KEY, SM_ALG_PASS_KEY, SM_ALG_JUST_WORK, SM_ALG_PASS_KEY },
};
/* work struct of notifying application(s) */
struct smNotifWork {
int type;
union {
struct {
struct bt_addr addr;
SmPairState state;
SmPairErr err;
} pairStateChg;
struct {
struct bt_addr addr;
bool valid;
uint32_t passkey;
} passkeyDisp;
struct {
struct bt_addr addr;
bool valid;
} passkeyReq;
};
};
/*
* FUNCTION: smNotifWorkFree
* USE: Clean up a SM notification work in mNotifWork
* PARAMS: work - a work item
* RETURN:
* NOTES:
*/
static void smNotifWorkFree(void *work)
{
struct smNotifWork *w = (struct smNotifWork *)work;
free(w);
}
/*
* FUNCTION: smNotifWorker
* USE: Worker thread to notify application layer without blocking any lower layer thread
* PARAMS: unused - not used
* RETURN:
* NOTES:
*/
static void* smNotifWorker(void *unused)
{
struct smNotifWork *work;
struct smPairStateChange pairStateChg;
struct smPasskeyDisplay passkeyDisp;
struct smPasskeyRequest passkeyReq;
int status;
pthread_setname_np(pthread_self(), "bt_sm_worker");
while (1) {
status = workQueueGet(mNotifWork, (void**)&work);
if (status)
break;
switch(work->type) {
case SM_WORK_NOTIFY_PAIR_STATE_CHG:
pairStateChg.peerAddr = work->pairStateChg.addr;
pairStateChg.pairState = work->pairStateChg.state;
pairStateChg.pairErr = work->pairStateChg.err;
multiNotifNotify(mPairStateObservers, &pairStateChg);
break;
case SM_WORK_NOTIFY_PASSKEY_DISPLAY:
passkeyDisp.peerAddr = work->passkeyDisp.addr;
passkeyDisp.valid = work->passkeyDisp.valid;
passkeyDisp.passkey = work->passkeyDisp.passkey;
pthread_mutex_lock(&mObvrLock);
if (mPasskeyDisplayObserver) {
mPasskeyDisplayObserver->cbk(
mPasskeyDisplayObserver->data, &passkeyDisp, mPasskeyDisplayObserver->id);
}
pthread_mutex_unlock(&mObvrLock);
break;
case SM_WORK_NOTIFY_PASSKEY_REQUEST:
passkeyReq.peerAddr = work->passkeyReq.addr;
passkeyReq.valid = work->passkeyReq.valid;
pthread_mutex_lock(&mObvrLock);
if (mPasskeyRequestObserver) {
mPasskeyRequestObserver->cbk(
mPasskeyRequestObserver->data, &passkeyReq, mPasskeyRequestObserver->id);
}
pthread_mutex_unlock(&mObvrLock);
break;
default:
logd("Unknown SM work type %d\n", work->type);
break;
}
free(work);
}
return NULL;
}
/*
* FUNCTION: smSameAddr
* USE: Given two struct bt_addr, tell whether they are the same
* PARAMS: a - a device address
* b - the other device address
* RETURN: true if they are the same; false otherwise
* NOTES:
*/
static bool smSameAddr(const struct bt_addr *a, const struct bt_addr *b)
{
if (!a || !b)
return false;
return !memcmp(a, b, sizeof(struct bt_addr));
}
/*
* FUNCTION: smBannedDevFree
* USE: Free the given device node
* PARAMS: dev - the to-be-freed device node
* RETURN: NONE
* NOTES:
*/
static void smBannedDevFree(struct smDevNode *dev)
{
if (!dev)
return;
if (dev->waitTimer)
timerCancel(dev->waitTimer);
free(dev);
}
/*
* FUNCTION: smBannedDevFindByAddr
* USE: Find a device node by the address of the device on the banned device list
* PARAMS: addr - the device address
* RETURN: the pointer to the device node if matched; NULL otherwise
* NOTES: call with mDevLock held
*/
static struct smDevNode* smBannedDevFindByAddr(const struct bt_addr *addr)
{
struct smDevNode *n = mBannedDevList;
if (!addr)
return NULL;
while (n) {
if (smSameAddr(addr, &n->addr))
return n;
n = n->next;
}
return NULL;
}
/*
* FUNCTION: smBannedDevFindById
* USE: Find a device node by the device ID on the banned device list
* PARAMS: id - the device ID
* RETURN: the pointer to the device node if matched; NULL otherwise
* NOTES: call with mDevLock held
*/
static struct smDevNode* smBannedDevFindById(uniq_t id)
{
struct smDevNode *n = mBannedDevList;
if (!id)
return NULL;
while (n) {
if (n->devId == id)
return n;
n = n->next;
}
return NULL;
}
/*
* FUNCTION: smBannedDevDel
* USE: Find device node by Device ID and delete it from the banned device list if matched
* PARAMS: id - the device ID
* RETURN: NONE
* NOTES: call with mDevLock held
*/
static void smBannedDevDel(uniq_t id)
{
struct smDevNode *n;
if (!id)
return;
n = smBannedDevFindById(id);
if (!n)
return;
logd("%s(): Remove device "MACFMT" from the banned list\n", __func__, MACCONV(n->addr.addr));
if (n->next)
n->next->prev = n->prev;
if (n->prev)
n->prev->next = n->next;
else
mBannedDevList = n->next;
smBannedDevFree(n);
}
/*
* FUNCTION: smWaitTimerCb
* USE: Called when the timer with wait interval expires to allow sending a Pairing request, a
* Security request or a Pairing response for a banned device
* PARAMS: timerId - an unique ID of a timer
* devId - a device ID
* RETURN: NONE
* NOTES:
*/
static void smWaitTimerCb(uniq_t timerId, uint64_t devId)
{
pthread_mutex_lock(&mDevLock);
smBannedDevDel(devId);
pthread_mutex_unlock(&mDevLock);
}
/*
* FUNCTION: smBannedDevAdd
* USE: Add a device to the banned device list
* PARAMS: addr - the device address
* RETURN: the pointer to the device node; NULL otherwise
* NOTES: call with mDevLock held
*/
static struct smDevNode* smBannedDevAdd(const struct bt_addr *addr)
{
struct smDevNode *n = NULL;
if (!addr)
return NULL;
n = (struct smDevNode*)calloc(1, sizeof(struct smDevNode));
if (!n)
return NULL;
n->addr = *addr;
n->devId = uniqGetNext();
n->waitInvl = SM_MIN_WAIT_INTVL;
n->waitTimer = timerSet(n->waitInvl, smWaitTimerCb, n->devId);
if (!n->waitTimer)
goto fail;
if (mBannedDevList) {
n->next = mBannedDevList;
mBannedDevList->prev = n;
}
mBannedDevList = n;
logd("%s(): Add device "ADDRFMT" to the banned list\n", __func__, ADDRCONV(*addr));
return n;
fail:
logw("%s(): Failed to add device "ADDRFMT" to the banned list\n", __func__, ADDRCONV(*addr));
smBannedDevFree(n);
return NULL;
}
/*
* FUNCTION: smBannedDevFreeAll
* USE: Free all nodes of the banned device list
* PARAMS: NONE
* RETURN: NONE
* NOTES:
*/
static void smBannedDevFreeAll(void)
{
struct smDevNode *n;
pthread_mutex_lock(&mDevLock);
while(mBannedDevList) {
n = mBannedDevList->next;
smBannedDevFree(mBannedDevList);
mBannedDevList = n;
}
pthread_mutex_unlock(&mDevLock);
}
/*
* FUNCTION: smBanDeviceForTime
* USE: Start or update the wait interval timer to prevent from sending a Pairing request, a
* Security request or a Pairing response before the wait interval expires
* PARAMS: addr - the address of the device
* RETURN: NONE
* NOTES:
*/
static void smBanDeviceForTime(const struct bt_addr *addr)
{
struct smDevNode *n;
if (!addr)
return;
pthread_mutex_lock(&mDevLock);
/* add the newly failed device in the banned device list */
if (!(n = smBannedDevFindByAddr(addr))) {
n = smBannedDevAdd(addr);
logd("%s(): Add device "MACFMT" to banned list and start wait timer with "FMT64"u secs\n",
__func__, MACCONV(n->addr.addr), CNV64(n->waitInvl / 1000));
} else {
/* if the failed device was previously added to the banned device list, increase the wait
* interval and update timers
*/
if (n->waitTimer)
timerCancel(n->waitTimer);
if (n->waitInvl == SM_DEF_WAIT_INTVL)
n->waitInvl = SM_MIN_WAIT_INTVL;
else
n->waitInvl = n->waitInvl * SM_WAIT_INTVL_FACTOR > SM_MAX_WAIT_INTVL ?
SM_MAX_WAIT_INTVL : n->waitInvl * SM_WAIT_INTVL_FACTOR;
n->waitTimer = timerSet(n->waitInvl, smWaitTimerCb, n->devId);
logd("%s(): Update device "ADDRFMT" and start wait timer with "FMT64"u secs\n", __func__,
ADDRCONV(n->addr), CNV64(n->waitInvl / 1000));
}
pthread_mutex_unlock(&mDevLock);
}
/*
* FUNCTION: smIsDevBanned
* USE: Start or update the wait interval timer to prevent from sending a Pairing request, a
* Security request or a Pairing response before the wait interval expires
* PARAMS: addr - the address of the device
* RETURN: is banned or not
* NOTES:
*/
static bool smIsDevBanned(const struct bt_addr *addr)
{
struct smDevNode *n;
pthread_mutex_lock(&mDevLock);
n = smBannedDevFindByAddr(addr);
if (n)
logd("%s(): Device "MACFMT" is banned currently for "FMT64"u secs\n", __func__,
MACCONV(addr->addr), CNV64(n->waitInvl / 1000));
pthread_mutex_unlock(&mDevLock);
return n;
}
/*
* FUNCTION: smSelPairAlg
* USE: Select the pairing algorithm base on IO capability, OOB data and
* Authentication request
* PARAMS: inst - the instance
* RETURN: pairing algorithm used
* NOTES:
*/
static uint8_t smSelPairAlg(const struct smInstance *inst)
{
if (!inst)
return SM_ALG_UNKNOWN;
if ((mIoCap != HCI_DISP_CAP_DISP_ONLY && mIoCap != HCI_DISP_CAP_DISP_YES_NO &&
mIoCap != HCI_DISP_CAP_KBD_ONLY && mIoCap != HCI_DISP_CAP_NONE &&
mIoCap != HCI_DISP_CAP_KBD_DISP)||
(inst->peerIoCap != HCI_DISP_CAP_DISP_ONLY && inst->peerIoCap != HCI_DISP_CAP_DISP_YES_NO &&
inst->peerIoCap != HCI_DISP_CAP_KBD_ONLY && inst->peerIoCap != HCI_DISP_CAP_NONE &&
inst->peerIoCap != HCI_DISP_CAP_KBD_DISP))
return SM_ALG_UNKNOWN;
/* check OOB support */
if (inst->peerHasOob && mHasOob)
return SM_ALG_OOB;
/* check MITM flag */
/* TODO(mcchou): we use what ever requested by the peer device */
if (!(inst->peerAuthReq & SM_AUTH_REQ_MITM_FLAG))
return SM_ALG_JUST_WORK;
/* check IO capability*/
return inst->isInitiator ? mPairAlgs[inst->peerIoCap][mIoCap] :
mPairAlgs[mIoCap][inst->peerIoCap];
}
/*
* FUNCTION: smIsEncryptExpected
* USE: Check if the link is encrypted as negotiated given the encryption
* changed state
* PARAMS: state - the new l2cap encryption state
* RETURN: true if the link encrypted as negotiated
* NOTES:
*/
static bool smIsEncryptExpected(const struct l2cEncrState *state, const struct smInstance *inst)
{
if (!inst || !state)
return false;
return state->isEncr && state->isMitmSafe == inst->isMitmSafe;
}
/*
* FUNCTION: smGetLeAddrType
* USE: Check the type of the address
* PARAMS: addr - the address
* RETURN: enum smAddrType
* NOTES:
*/
static enum smAddrType smGetLeAddrType(const struct bt_addr *addr)
{
uint8_t msb;
if (!addr)
return addrTypeUnknown;
switch (addr->type) {
case BT_ADDR_TYPE_LE_PUBLIC:
return addrTypePubStatic;
case BT_ADDR_TYPE_LE_RANDOM:
msb = addr->addr[5] & SM_RAND_ADDR_MASK;
switch(msb) {
case SM_RAND_ADDR_STATIC:
return addrTypeRandStatic;
case SM_RAND_ADDR_PRI_NONRESOL:
return addrTypeRandPriNonResol;
case SM_RAND_ADDR_PRI_RESOL:
return addrTypeRandPriResol;
default:
logw("%s(): Unknown LE random address type 0x%02X\n", __func__, msb);
return addrTypeUnknown;
}
default:
logw("%s(): Unexpected address type %02X\n", addr->type);
return addrTypeUnknown;
}
}
/*
* FUNCTION: smTx
* USE: Send a packet to the peer
* PARAMS: inst - the instance
* pktTyp - packet type
* data - the data to send
* len - length of said data
* RETURN: success
* NOTES:
*/
static bool smTx(const struct smInstance *inst, uint8_t pktTyp, const void *data, uint8_t len)
{
sg tmpData = NULL;
struct smHdr hdr;
utilSetLE8(&hdr.typ, pktTyp);
tmpData = sgNewWithCopyData(data, len);
if (!tmpData) {
logw("%s(): Failed to alloc SG packet\n", __func__);
return false;
}
if (!sgConcatFrontCopy(tmpData, &hdr, sizeof(hdr))) {
logw("%s(): Failed to copy SG header data\n", __func__);
goto tx_err;
}
if (!sendQueueTx(mSmSendQ, inst->conn, tmpData)) {
logw("%s(): Failed to SG packet to sendQueue\n", __func__);
goto tx_err;
}
logd("%s(): Packet successfully sent\n", __func__);
return true;
tx_err:
sgFree(tmpData);
return false;
}
/*
* FUNCTION: smParsePairReqResp
* USE: Parse a struct smPairReqResp into relevant instance variables
* PARAMS: err - the output pairing error if there is any
* inst - the instance
* pairReqResp - the struct
* RETURN: return 0 if all values were read & found valid; SM_ERR_* otherwise
* NOTES:
*/
static uint8_t smParsePairReqResp(SmPairState *err, struct smInstance *inst,
const struct smPairReqResp *pairReqResp)
{
uint8_t tmp, initKeyDistr, rspKeyDistr;
tmp = utilGetLE8(&pairReqResp->ioCap);
if (tmp != HCI_DISP_CAP_DISP_ONLY && tmp != HCI_DISP_CAP_DISP_YES_NO &&
tmp != HCI_DISP_CAP_KBD_ONLY && tmp != HCI_DISP_CAP_NONE &&
tmp != HCI_DISP_CAP_KBD_DISP) {
logd("Invalid io caps RXed 0x%02X\n", tmp);
if (err)
*err = SM_PAIR_ERR_INVALID_PARAM;
return SM_ERR_INVALID_PARAMS;
}
inst->peerIoCap = tmp;
tmp = utilGetLE8(&pairReqResp->oobDataFlag);
if (tmp != 0 && tmp != 1) {
logd("Invalid oob flag RXed 0x%02X\n", tmp);
if (err)
*err = SM_PAIR_ERR_INVALID_PARAM;
return SM_ERR_INVALID_PARAMS;
}
inst->peerHasOob = !!tmp;
tmp = utilGetLE8(&pairReqResp->authReq);
if ((tmp & SM_AUTH_REQ_BOND_MASK) != SM_AUTH_REQ_BOND_NO &&
(tmp & SM_AUTH_REQ_BOND_MASK) != SM_AUTH_REQ_BOND_BOND) {
logd("Invalid authReq RXed 0x%02X\n", tmp);
if (err)
*err = SM_PAIR_ERR_INVALID_PARAM;
return SM_ERR_INVALID_PARAMS;
}
inst->peerAuthReq = tmp;
if ((inst->peerAuthReq & SM_AUTH_REQ_MITM_FLAG) && inst->peerIoCap == HCI_DISP_CAP_NONE) {
logd("Infeasible authReq RXed 0x%02X due to io caps 0x%02X\n", tmp, inst->peerIoCap);
if (err)
*err = SM_PAIR_ERR_AUTH_REQ_INFEASIBLE;
return SM_ERR_AUTH_REQMENTS;
}
tmp = utilGetLE8(&pairReqResp->maxKeySz);
if (tmp < SM_MIN_KEY_LEN || tmp > SM_MAX_KEY_LEN) {
logd("Invalid maxKeySize RXed 0x%02X\n", tmp);
if (err)
*err = SM_PAIR_ERR_ENCR_KEY_SIZE;
/* the invalid key size in pairing request will be handled when receiving confirm value */
if (!inst->isInitiator)
return SM_ERR_ENCR_KEY_SZ;
}
inst->peerMaxKeySz = tmp;
initKeyDistr = utilGetLE8(&pairReqResp->initiatorKeyDistr);
rspKeyDistr = utilGetLE8(&pairReqResp->responderKeyDistr);
if (inst->isInitiator) { /* must be a response. accept as many as given */
inst->myKeyDistr = inst->myKeyDistrReq & initKeyDistr;
inst->peerKeyDistr = inst->peerKeyDistrReq & rspKeyDistr;
} else { /* must be a request - agree to send all keys they want, accept whatever was given */
inst->myKeyDistr = inst->myKeyDistrReq = rspKeyDistr;
inst->peerKeyDistr = inst->peerKeyDistrReq = initKeyDistr;
}
return 0;
}
/*
* FUNCTION: smReadReversedBytes
* USE: Read the bytes stream of LSB first order and store the bytes stream to out in MSB
* first order
* PARAMS: out - the output byte array
* outLen - the length of out
* in - the input byte array
* InLen - the length of in
* RETURN: success
* NOTES: Used for reading fields of SM commands or L2CAP event
*/
static bool smReadReversedBytes(uint8_t *out, int outLen, const uint8_t *in, int inLen)
{
int i;
if (outLen <= 0 || inLen <= 0 || outLen != inLen)
return false;
for (i = 0; i < inLen; ++i)
out[i] = utilGetLE8(&in[inLen - i - 1]);
return true;
}
/*
* FUNCTION: smWriteReversedBytes
* USE: Read the bytes stream of MSB first order and store the bytes stream to out in LSB
* first order
* PARAMS: out - the output byte array
* outLen - the length of out
* in - the input byte array
* InLen - the length of in
* RETURN: success
* NOTES: Used for writing fields of SM commands
*/
static bool smWriteReversedBytes(uint8_t *out, int outLen, const uint8_t *in, int inLen)
{
int i;
if (outLen <= 0 || inLen <= 0 || outLen != inLen)
return false;
for (i = 0; i < inLen; ++i)
utilSetLE8(&out[i], in[inLen - i - 1]);
return true;
}
/*
* FUNCTION: smSendSecurityReq
* USE: Send security request
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendSecurityReq(struct smInstance *inst)
{
struct smAuthReq req;
inst->isInitiator = false;
utilSetLE8(&req.authReq, inst->myAuthReq);
if (!smTx(inst, SM_SECURITY_REQUEST, &req, sizeof(req))) {
logw("%s(): Failed to send security request command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendPairReq
* USE: Send pairing request
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendPairReq(struct smInstance *inst)
{
struct smPairReqResp req;
if (!inst->isInitiator)
return false;
utilSetLE8(&req.ioCap, mIoCap);
/* TODO(mcchou): this needs to be changed accordingly for OOB support */
utilSetLE8(&req.oobDataFlag, mHasOob);
utilSetLE8(&req.authReq, inst->myAuthReq);
utilSetLE8(&req.maxKeySz, inst->myMaxKeySz);
utilSetLE8(&req.initiatorKeyDistr, inst->myKeyDistr);
utilSetLE8(&req.responderKeyDistr, inst->myKeyDistr);
if (!smTx(inst, SM_PAIRING_REQ, &req, sizeof(req))) {
logw("%s(): Failed to send pairing request command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendPairResp
* USE: Send pairing response
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendPairResp(const struct smInstance *inst)
{
struct smPairReqResp resp;
if (inst->isInitiator) {
logw("%s(): Unexpected to send pairing response\n", __func__);
return false;
}
utilSetLE8(&resp.ioCap, mIoCap);
/* TODO(mcchou): this needs to be changed accordingly for OOB support */
utilSetLE8(&resp.oobDataFlag, mHasOob);
utilSetLE8(&resp.authReq, inst->myAuthReq);
utilSetLE8(&resp.maxKeySz, inst->myMaxKeySz);
utilSetLE8(&resp.initiatorKeyDistr, inst->peerKeyDistr); /* accept whatever they promised us */
utilSetLE8(&resp.responderKeyDistr, inst->myKeyDistr); /* give up whatever they asked for */
if (!smTx(inst, SM_PAIRING_RSP, &resp, sizeof(resp))) {
logw("%s(): Failed to send pairing response command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendPairConf
* USE: Send pairing confirm value
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendPairConf(const struct smInstance *inst)
{
struct smPairConf conf;
smWriteReversedBytes(conf.confirmVal, sizeof(conf.confirmVal), inst->myConfVal.confirmVal,
sizeof(inst->myConfVal.confirmVal));
if (!smTx(inst, SM_PAIRING_CONF, &conf, sizeof(conf))) {
logw("%s(): Failed to send pairing confirm command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendPairRand
* USE: Send pairing Random number
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendPairRand(const struct smInstance *inst)
{
struct smPairRand rand;
smWriteReversedBytes(rand.rand, sizeof(rand.rand), inst->myRandNum.rand,
sizeof(inst->myRandNum.rand));
if (!smTx(inst, SM_PAIRING_RAND, &rand, sizeof(rand))) {
logw("%s(): Failed to send pairing random command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendEncryptInfo
* USE: Send LTK via Encryption Information
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendEncryptInfo(const struct smInstance *inst)
{
uint8_t key[HCI_LE_KEY_LEN] = {0,};
struct smKey ltk;
if (!persistGetDevKey(&inst->peerAddr, KEY_TYPE_MY_LTK, key))
return false;
smWriteReversedBytes(ltk.key, sizeof(ltk.key), key, sizeof(key));
if (!smTx(inst, SM_ENCR_INFO, &ltk, sizeof(ltk))) {
logw("%s(): Failed to send encryption information command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendMasterId
* USE: Send EDIV and random number
* PARAMS: inst - the instance
* ediv - our EDIV associated with the peer device
* rand - our random number associated with the peer device
* RETURN: success
* NOTES:
*/
static bool smSendMasterId(const struct smInstance *inst, uint16_t ediv, uint64_t rand)
{
struct smMasterInfo info;
utilSetLE16(&info.ediv, ediv);
utilSetLE64(&info.rand, rand);
if (!smTx(inst, SM_MASTER_INFO, &info, sizeof(info))) {
logw("%s(): Failed to send master identification command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendIdInfo
* USE: Send IRK
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendIdInfo(const struct smInstance *inst)
{
uint8_t key[HCI_LE_KEY_LEN] = {0,};
struct smKey irk;
if (!persistGetDevKey(NULL, KEY_TYPE_IRK, key))
return false;
smWriteReversedBytes(irk.key, sizeof(irk.key), key, sizeof(key));
if (!smTx(inst, SM_IDENTITY_INFO, &irk, sizeof(irk))) {
logw("%s(): Failed to send identity information command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendIdAddrInfo
* USE: Send public device address or static random address
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendIdAddrInfo(const struct smInstance *inst)
{
uint16_t at;
enum smAddrType addrType;
struct smIdentityAddrInfo addr;
struct bt_addr selfAddr;
/* send the random resolvable address used during advertising */
l2cApiGetSelfBtAddr(inst->conn, &selfAddr);
memcpy(addr.mac, selfAddr.addr, sizeof(addr.mac));
addrType = smGetLeAddrType(&selfAddr);
switch (addrType) {
case addrTypePubStatic:
case addrTypeRandStatic:
at = 0x00; /* static address */
break;
case addrTypeRandPriNonResol:
case addrTypeRandPriResol:
at = 0x01; /* random private address */
break;
default:
logw("%s(): Failed to send identity address information command, address type %02X\n",
__func__, addrType);
return false;
}
utilSetLE8(&addr.addrType, at);
if (!smTx(inst, SM_IDENTITY_ADDR_INFO, &addr, sizeof(addr))) {
logw("%s(): Failed to send identity address information command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smSendSignInfo
* USE: Send CSRK
* PARAMS: inst - the instance
* RETURN: success
* NOTES:
*/
static bool smSendSignInfo(const struct smInstance *inst)
{
uint8_t key[HCI_LE_KEY_LEN] = {0,};
struct smKey csrk;
if (!persistGetDevKey(NULL, KEY_TYPE_CSRK, key))
return false;
smWriteReversedBytes(csrk.key, sizeof(csrk.key), key, sizeof(key));
if (!smTx(inst, SM_SIGNING_INFO, &csrk, sizeof(csrk))) {
logw("%s(): Failed to send signing information command\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smTransitPhase
* USE: Log and transit the phase of pairing process
* PARAMS: inst - the instance
* phase - the phase to transit to
* RETURN: NONE
* NOTES:
*/
static void smTransitPhase(struct smInstance *inst, uint8_t phase) {
if (phase < SM_PHASE_START || phase > SM_PHASE_LTK_ENCRYPTED) {
logd("Invalid phase to transit, phase %d\n", phase);
return;
}
if (inst->phase == phase) {
logd("Phase transition skipped, phase %d\n", phase);
return;
}
if ((phase == SM_PHASE_DONE || phase == SM_PHASE_LTK_ENCRYPTED)
&& inst->stallTimer) {
timerCancel(inst->stallTimer);
inst->stallTimer = 0;
}
logd("Transit from phase %d to phase %d\n", inst->phase, phase);
inst->phase = phase;
if (inst->phase == SM_PHASE_DONE)
smNotifyPairStateObserver(&inst->peerAddr, SM_PAIR_STATE_PAIRED, SM_PAIR_ERR_NONE);
}
/*
* FUNCTION: smDistributeKeys
* USE: Distribute our LTK, EDIV, Rand, IRK, Addr, CSRK based on the key distribution field
* PARAMS: inst - the instance
* RETURN: success
* NOTES: the phase will be transited based on the key distribution field
*/
static bool smDistributeKeys(struct smInstance *inst)
{
uint16_t ediv;
uint64_t rand;
if (inst->myKeyDistr & SM_KEY_DISTR_LTK) {
if (!smGenLtk(inst) || !smSendEncryptInfo(inst))
goto fail;
smTransitPhase(inst, SM_PHASE_ENCRYPT_INFO_SENT);
if (!smGenRandNum((uint8_t *)&rand, sizeof(rand)) ||
!persistAddDevNumber(&inst->peerAddr, PERSIST_NUM_TYPE_SM_MY_RANDOM, rand) ||
!smGenEdiv(&ediv, &inst->peerAddr, 0, rand, false))
goto fail;
if (!smSendMasterId(inst, ediv, rand))
goto fail;
smTransitPhase(inst, SM_PHASE_MASTER_ID_SENT);
}
if (inst->myKeyDistr & SM_KEY_DISTR_IRK) {
if (!smSendIdInfo(inst))
goto fail;
smTransitPhase(inst, SM_PHASE_ID_INFO_SENT);
if (!smSendIdAddrInfo(inst))
goto fail;
smTransitPhase(inst, SM_PHASE_ID_ADDR_INFO_SENT);
}
if (inst->myKeyDistr & SM_KEY_DISTR_CSRK) {
if (!smSendSignInfo(inst))
goto fail;
smTransitPhase(inst, SM_PHASE_SIGN_INFO_SENT);
}
return true;
fail:
logw("Failed to distribute keys when we are the %s\n",
inst->isInitiator ? "initiator" : "responder");
return false;
}
/*
* FUNCTION: smSendPairFail
* USE: Send a pairing error to the peer
* PARAMS: inst - the instance
* reason - the error reason
* RETURN: success
* NOTES:
*/
static bool smSendPairFail(const struct smInstance *inst, uint8_t reason)
{
struct smPairFailed fail;
/* delete keys and numbers associated with the peer device */
persistDelDevKeys(&inst->peerAddr);
persistDelDevNumbers(&inst->peerAddr);
smBanDeviceForTime(&inst->peerAddr);
utilSetLE8(&fail.reason, reason);
return smTx(inst, SM_PAIRING_FAIL, &fail, sizeof(fail));
}
/*
* FUNCTION: smIsPaired
* USE: check if the peer device is previously paired by checking the exchanged
* LTK, EDIV and random number
* PARAMS: mitm - indicate whether the existing pairing is MITM protected; valid if paired
* bonded - indicate whether the existing pairing is bonded; valid if paired
* addr - the address of the peer device
* RETURN: true if previously paired; false otherwise
* NOTES: The address for checking is either the given addr or the address used during the last
* pairing if there is one.
*/
static bool smIsPaired(bool *mitm, bool *bonded, const struct bt_addr *addr)
{
uint8_t key[HCI_LE_KEY_LEN] = {0,};
uint64_t num;
bool paired;
struct bt_addr prevAddr; /* previous address associated with IRK which can */
const struct bt_addr *a;
if (!addr)
return false;
if (smResolveAddr(&prevAddr, addr))
a = &prevAddr;
else
a = addr;
paired = persistGetDevKey(a, KEY_TYPE_LTK, key) &&
persistGetDevKey(a, KEY_TYPE_MY_LTK, key) &&
persistGetDevNumber(a, PERSIST_NUM_TYPE_SM_EDIV, &num) &&
persistGetDevNumber(a, PERSIST_NUM_TYPE_SM_RANDOM, &num) &&
persistGetDevNumber(a, PERSIST_NUM_TYPE_SM_MY_RANDOM, &num) &&
persistGetDevNumber(a, PERSIST_NUM_TYPE_SM_MITM_PROTECT, &num);
if (!paired)
return false;
if (mitm)
*mitm = !!num;
if (bonded) {
*bonded = false;
if (persistGetDevNumber(a, PERSIST_NUM_TYPE_SM_BOND, &num) && !!num &&
persistGetDevKey(a, KEY_TYPE_IRK, key)) {
*bonded = true;
}
}
return true;
}
/*
* FUNCTION: smLtkEncrypt
* USE: (Re-)encrypt the link using peer's random number, EVID and LTK
* PARAMS: inst - the instance
* RETURN: true if encryption request sent
* NOTES: This should be called when we are the initiator of the link
*/
static bool smLtkEncrypt(struct smInstance *inst)
{
uint64_t rand, ediv;
uint8_t ltk[HCI_LE_KEY_LEN] = {0,};
uint8_t rawLtk[HCI_LE_KEY_LEN] = {0,};
if (!inst)
return false;
if (!persistGetDevNumber(&inst->peerAddr, PERSIST_NUM_TYPE_SM_EDIV, &ediv) ||
!persistGetDevNumber(&inst->peerAddr, PERSIST_NUM_TYPE_SM_RANDOM, &rand) ||
!persistGetDevKey(&inst->peerAddr, KEY_TYPE_LTK, ltk)) {
logw("Failed to retrieve encryption info for encryption using LTK\n");
return false;
} else if (!smReadReversedBytes(rawLtk, sizeof(rawLtk), ltk, sizeof(ltk)) ||
!l2cApiLeEncryptConn(inst->conn, rand, ediv, rawLtk)) {
logw("Failed to encrypt using LTK\n");
return false;
} else {
logi("Encrypt using LTK\n");
smTransitPhase(inst, SM_PHASE_LTK_SENT);
}
return true;
}
/*
* FUNCTION: smRx
* USE: Data arrived - handle it
* PARAMS: inst - the instance
* packet - the data that arrived
* RETURN: NONE
* NOTES:
*/
static void smRx(struct smInstance *inst, sg packet)
{
struct smHdr hdr;
struct smPairReqResp pairReqResp;
struct smPairConf pairConf;
struct smPairRand pairRand;
struct smPairFailed pairFailed;
struct smKey k, key;
struct smMasterInfo masterInfo;
struct smIdentityAddrInfo addrInfo;
struct smAuthReq authReq;
SmPairErr pairErr = SM_PAIR_ERR_UNKNOWN;
uint8_t typ, err, auth, prevPhase;
uint8_t stk[HCI_LE_KEY_LEN] = {0,};
uint64_t rand, ediv;
if (!sgSerializeCutFront(packet, &hdr, sizeof(hdr))) {
logw("Incoming SM packet header extraction failed\n");
sgFree(packet);
return;
}
typ = utilGetLE8(&hdr.typ);
switch (typ) {
case SM_PAIRING_REQ:
if (!sgSerializeCutFront(packet, &pairReqResp, sizeof(pairReqResp))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (smIsDevBanned(&inst->peerAddr)) {
smSendPairFail(inst, SM_ERR_REPEATED_ATTEMPTS);
smBanDeviceForTime(&inst->peerAddr);
pairErr = SM_PAIR_ERR_REPEATED_ATTEMPT;
break;
}
if ((inst->phase == SM_PHASE_SEC_REQ_SENT && inst->isInitiator) ||
(inst->phase != SM_PHASE_START) || inst->isInitiator) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
inst->isInitiator = false;
if ((pairErr = smParsePairReqResp(&err, inst, &pairReqResp))) {
smSendPairFail(inst, err);
break;
}
if (inst->peerHasOob && !mHasOob) {
smSendPairFail(inst, SM_ERR_OOB_NOT_AVAIL);
pairErr = SM_PAIR_ERR_OOB_NOT_AVAILABLE;
break;
}
/* as a responsder, we set our authentication requirement based on our IO capabilities and
* peer's requirement */
inst->myAuthReq = inst->peerAuthReq;
if (mIoCap == HCI_DISP_CAP_NONE)
inst->myAuthReq &= ~SM_AUTH_REQ_MITM_FLAG;
inst->myMaxKeySz = SM_MAX_KEY_LEN;
if (!smGenRandNum(inst->myRandNum.rand, sizeof(inst->myRandNum.rand)))
break;
if (!smSendPairResp(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_REQ_RESP);
if (!smGenTK(inst))
break;
/* for Just Work pairing, there is no authentication steps, so generate Sconfirm */
if (inst->alg == SM_ALG_JUST_WORK) {
smGenConfVal(&inst->myConfVal, inst, false);
smTransitPhase(inst, SM_PHASE_SCONF_READY);
}
goto done;
case SM_PAIRING_RSP:
if (!sgSerializeCutFront(packet, &pairReqResp, sizeof(pairReqResp))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (inst->phase != SM_PHASE_REQ_SENT || !inst->isInitiator) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
if ((pairErr = smParsePairReqResp(&err, inst, &pairReqResp))) {
smSendPairFail(inst, err);
break;
}
/* fail the pairing if peer does not distribute IRK when bonding is requested by us */
if ((inst->myAuthReq & SM_AUTH_REQ_BOND_MASK) == SM_AUTH_REQ_BOND_BOND &&
!(inst->peerKeyDistr & SM_KEY_DISTR_IRK)) {
smSendPairFail(inst, SM_ERR_AUTH_REQMENTS);
pairErr = SM_PAIR_ERR_AUTH_REQ_INFEASIBLE;
break;
}
if (!smGenRandNum(inst->myRandNum.rand, sizeof(inst->myRandNum.rand)) ||
!smGenTK(inst) ||
!smGenConfVal(&inst->myConfVal, inst, false))
break;
if (!smSendPairConf(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_MCONF_SENT);
goto done;
case SM_PAIRING_CONF:
if (!sgSerializeCutFront(packet, &pairConf, sizeof(pairConf))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if ((inst->isInitiator && inst->phase != SM_PHASE_MCONF_SENT) ||
(!inst->isInitiator && inst->phase != SM_PHASE_REQ_RESP &&
inst->phase != SM_PHASE_SCONF_READY)) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
prevPhase = inst->phase;
if (!smReadReversedBytes(inst->peerConfVal.confirmVal, sizeof(inst->peerConfVal.confirmVal),
pairConf.confirmVal, sizeof(pairConf.confirmVal)))
break;
if (inst->isInitiator) {
/* once received conf value from responder, the passkey display is done */
if (inst->alg == SM_ALG_PASS_KEY)
smNotifyPasskeyDisplayObserver(&inst->peerAddr, false, 0);
smTransitPhase(inst, SM_PHASE_SCONF_RECEIVED);
if (!smSendPairRand(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_RAND_SENT);
} else {
smTransitPhase(inst, SM_PHASE_MCONF_RECEIVED);
if (inst->peerMaxKeySz < SM_MIN_KEY_LEN || inst->peerMaxKeySz > SM_MAX_KEY_LEN) {
smSendPairFail(inst, SM_ERR_ENCR_KEY_SZ);
pairErr = SM_PAIR_ERR_ENCR_KEY_SIZE;
break;
}
/* if the passkey is already provided before receiving Mconfirm, we send Sconfirm */
if (prevPhase == SM_PHASE_SCONF_READY) {
if (!smSendPairConf(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_SCONF_SENT);
}
}
goto done;
case SM_PAIRING_RAND:
if (!sgSerializeCutFront(packet, &pairRand, sizeof(pairRand))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if ((inst->isInitiator && inst->phase != SM_PHASE_RAND_SENT) ||
(!inst->isInitiator && inst->phase != SM_PHASE_SCONF_SENT))
break;
if (!smReadReversedBytes(inst->peerRandNum.rand, sizeof(inst->peerRandNum.rand),
pairRand.rand, sizeof(pairRand.rand)))
break;
/* check if the peer confirm value generated from the random value matches */
smGenConfVal(&pairConf, inst, true);
if (memcmp(pairConf.confirmVal, inst->peerConfVal.confirmVal,
sizeof(pairConf.confirmVal)) != 0) {
smSendPairFail(inst, SM_ERR_CONF_VAL_FAILED);
pairErr = SM_PAIR_ERR_CONF_VALUE_MISMATCHED;
break;
}
if (!inst->isInitiator && !smSendPairRand(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_RAND_RESP);
if (!smGenSTK(inst)) {
if ((inst->peerAuthReq & SM_AUTH_REQ_MITM_FLAG) && inst->alg == SM_ALG_JUST_WORK) {
logw("%s(): Chosen pairing algorithm 0x%02X failed to meet MITM\n", __func__,
inst->alg);
smSendPairFail(inst, SM_ERR_AUTH_REQMENTS);
pairErr = SM_PAIR_ERR_AUTH_REQ_INFEASIBLE;
}
break;
}
/* Rand value and EDIV should be set to 0 when setting up encryption with STK */
if (inst->isInitiator) {
if (!smReadReversedBytes(stk, sizeof(stk), inst->stk.key, sizeof(inst->stk.key))) {
break;
} else if (!l2cApiLeEncryptConn(inst->conn, 0, 0, stk)) {
pairErr = SM_PAIR_ERR_ENCR_CONN;
break;
} else {
smTransitPhase(inst, SM_PHASE_STK_SENT);
}
}
goto done;
case SM_PAIRING_FAIL:
if (!sgSerializeCutFront(packet, &pairFailed, sizeof(pairFailed))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
switch(pairFailed.reason) {
case SM_ERR_PASSKEY_ENTRY_FAILED:
pairErr = SM_PAIR_ERR_PASSKEY_FAILED;
break;
case SM_ERR_OOB_NOT_AVAIL:
pairErr = SM_PAIR_ERR_OOB_NOT_AVAILABLE;
break;
case SM_ERR_AUTH_REQMENTS:
pairErr = SM_PAIR_ERR_AUTH_REQ_INFEASIBLE;
break;
case SM_ERR_CONF_VAL_FAILED:
pairErr = SM_PAIR_ERR_CONF_VALUE_MISMATCHED;
break;
case SM_ERR_PAIRING_NOT_SUPPORTED:
pairErr = SM_PAIR_ERR_PAIRING_NOT_SUPPORTED;
break;
case SM_ERR_ENCR_KEY_SZ:
pairErr = SM_PAIR_ERR_ENCR_KEY_SIZE;
break;
case SM_ERR_CMD_NOT_SUPP:
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
case SM_ERR_REPEATED_ATTEMPTS:
pairErr = SM_PAIR_ERR_REPEATED_ATTEMPT;
break;
case SM_ERR_INVALID_PARAMS:
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
case SM_ERR_UNSPECCED_REASON:
default:
break;
}
smBanDeviceForTime(&inst->peerAddr);
break;
case SM_ENCR_INFO:
if (!sgSerializeCutFront(packet, &k, sizeof(k))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (inst->isInitiator) {
if (inst->phase != SM_PHASE_STK_ENCRYPTED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else {
/* the phase check can differ based on the responder key distribution field */
if (inst->myKeyDistr & SM_KEY_DISTR_CSRK) {
if (inst->phase != SM_PHASE_SIGN_INFO_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->myKeyDistr & SM_KEY_DISTR_IRK) {
if (inst->phase != SM_PHASE_ID_ADDR_INFO_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->myKeyDistr & SM_KEY_DISTR_LTK) {
if (inst->phase != SM_PHASE_MASTER_ID_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->phase != SM_PHASE_STK_ENCRYPTED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
}
/* process and store peer's LTK */
if (!smReadReversedBytes(key.key, sizeof(key.key), k.key, sizeof(k.key)))
break;
if (!persistAddDevKey(&inst->peerAddr, KEY_TYPE_LTK, key.key))
break;
smTransitPhase(inst, SM_PHASE_ENCRYPT_INFO_RECEIVED);
goto done;
case SM_MASTER_INFO:
if (!sgSerializeCutFront(packet, &masterInfo, sizeof(masterInfo))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (inst->phase != SM_PHASE_ENCRYPT_INFO_RECEIVED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
/* process and store peer's EDIV and random number */
ediv = utilGetLE16(&masterInfo.ediv);
rand = utilGetLE64(&masterInfo.rand);
if (!persistAddDevNumber(&inst->peerAddr, PERSIST_NUM_TYPE_SM_EDIV, ediv) ||
!persistAddDevNumber(&inst->peerAddr, PERSIST_NUM_TYPE_SM_RANDOM, rand)) {
break;
}
smTransitPhase(inst, SM_PHASE_MASTER_ID_RECEIVED);
if (!(inst->peerKeyDistr & (SM_KEY_DISTR_IRK | SM_KEY_DISTR_CSRK))) {
/* as the initiator, if we don't expect more keys, we start key distribution. according
* to the spec, a device "may" re-encrypt the link using LTK, but we don't until the
* next re-connection.
*/
if (inst->isInitiator && !smDistributeKeys(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_DONE);
}
goto done;
case SM_IDENTITY_INFO:
if (!sgSerializeCutFront(packet, &k, sizeof(k))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
/* the phase check can differ based on the key distribution fields */
if (inst->isInitiator) {
if (inst->peerKeyDistr & SM_KEY_DISTR_LTK) {
if (inst->phase != SM_PHASE_MASTER_ID_RECEIVED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->phase != SM_PHASE_STK_ENCRYPTED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else {
if (inst->peerKeyDistr & SM_KEY_DISTR_LTK) {
if (inst->phase != SM_PHASE_MASTER_ID_RECEIVED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->myKeyDistr & SM_KEY_DISTR_CSRK) {
if (inst->phase != SM_PHASE_SIGN_INFO_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->myKeyDistr & SM_KEY_DISTR_IRK) {
if (inst->phase != SM_PHASE_ID_ADDR_INFO_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->myKeyDistr & SM_KEY_DISTR_LTK) {
if (inst->phase != SM_PHASE_MASTER_ID_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->phase != SM_PHASE_STK_ENCRYPTED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
}
/* process and store peer's IRK */
if (!smReadReversedBytes(key.key, sizeof(key.key), k.key, sizeof(k.key)))
break;
if (!persistAddDevKey(&inst->peerAddr, KEY_TYPE_IRK, key.key))
break;
smTransitPhase(inst, SM_PHASE_ID_INFO_RECEIVED);
goto done;
case SM_IDENTITY_ADDR_INFO:
if (!sgSerializeCutFront(packet, &addrInfo, sizeof(addrInfo))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (inst->phase != SM_PHASE_ID_INFO_RECEIVED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
/* TODO(mcchou): store the address as a property in persist, note that according to spec,
* the address is considered valid after reconnecting using previously-exchanged LTK */
smTransitPhase(inst, SM_PHASE_ID_ADDR_INFO_RECEIVED);
if (!(inst->peerKeyDistr & SM_KEY_DISTR_CSRK)) {
/* as the initiator, if we don't expect more keys, we start key distribution. according
* to the spec, a device "may" re-encrypt the link using LTK, but we don't until the
* next re-connection.
*/
if (inst->isInitiator && !smDistributeKeys(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_DONE);
}
goto done;
case SM_SIGNING_INFO:
if (!sgSerializeCutFront(packet, &k, sizeof(k))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
/* the phase check can differ based on the key distribution fields */
if (inst->isInitiator) {
if (inst->peerKeyDistr & SM_KEY_DISTR_IRK) {
if (inst->phase != SM_PHASE_ID_ADDR_INFO_RECEIVED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->peerKeyDistr & SM_KEY_DISTR_LTK) {
if (inst->phase != SM_PHASE_MASTER_ID_RECEIVED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->phase != SM_PHASE_STK_ENCRYPTED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else {
if (inst->peerKeyDistr & SM_KEY_DISTR_IRK) {
if (inst->phase != SM_PHASE_ID_ADDR_INFO_RECEIVED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->peerKeyDistr & SM_KEY_DISTR_LTK) {
if (inst->phase != SM_PHASE_MASTER_ID_RECEIVED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->myKeyDistr & SM_KEY_DISTR_CSRK) {
if (inst->phase != SM_PHASE_SIGN_INFO_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->myKeyDistr & SM_KEY_DISTR_IRK) {
if (inst->phase != SM_PHASE_ID_ADDR_INFO_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->myKeyDistr & SM_KEY_DISTR_LTK) {
if (inst->phase != SM_PHASE_MASTER_ID_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
} else if (inst->phase != SM_PHASE_STK_ENCRYPTED) {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
}
/* process and store peer's CSRK */
if (!smReadReversedBytes(key.key, sizeof(key.key), k.key, sizeof(k.key)))
break;
if (!persistAddDevKey(&inst->peerAddr, KEY_TYPE_CSRK, key.key))
break;
smTransitPhase(inst, SM_PHASE_SIGN_INFO_RECEIVED);
/* as the initiator, if we start key distribution. according to the spec, a device "may"
* re-encrypt the link using LTK, but we don't until the next re-connection.
*/
if (inst->isInitiator && !smDistributeKeys(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_DONE);
goto done;
case SM_SECURITY_REQUEST:
if (!sgSerializeCutFront(packet, &authReq, sizeof(authReq))) {
logw("Incoming SM packet data extraction failed\n");
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (sgLength(packet)) {
logw("Residual data in packet: %u\n", sgLength(packet));
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
if (smIsDevBanned(&inst->peerAddr)) {
smSendPairFail(inst, SM_ERR_REPEATED_ATTEMPTS);
smBanDeviceForTime(&inst->peerAddr);
pairErr = SM_PAIR_ERR_REPEATED_ATTEMPT;
break;
}
if ((inst->phase == SM_PHASE_DONE || inst->phase == SM_PHASE_LTK_ENCRYPTED) &&
inst->isInitiator) {
inst->isInitiator = true; /* for re-establishment, we are the initiator for sure */
if (!smLtkEncrypt(inst)) {
pairErr = SM_PAIR_ERR_ENCR_CONN;
break;
}
} else if (inst->phase == SM_PHASE_START) {
auth = utilGetLE8(&authReq.authReq);
if ((auth & SM_AUTH_REQ_BOND_MASK) != SM_AUTH_REQ_BOND_NO &&
(auth & SM_AUTH_REQ_BOND_MASK) != SM_AUTH_REQ_BOND_BOND) {
logw("Invalid authReq in security request\n");
smSendPairFail(inst, SM_ERR_INVALID_PARAMS);
pairErr = SM_PAIR_ERR_INVALID_PARAM;
break;
}
/* Use authentication requirements received from the responder to initiate the
* pairing */
inst->isInitiator = true;
inst->myAuthReq = auth;
inst->myMaxKeySz = SM_MAX_KEY_LEN;
inst->myKeyDistrReq = SM_KEY_DISTR_LTK | SM_KEY_DISTR_CSRK |
((auth & SM_AUTH_REQ_BOND_MASK) == SM_AUTH_REQ_BOND_BOND ? SM_KEY_DISTR_IRK : 0x00);
inst->myKeyDistr = inst->myKeyDistrReq;
inst->peerKeyDistrReq = inst->myKeyDistrReq;
inst->peerKeyDistr = inst->myKeyDistrReq;
/* all fields required were set when the instance was allocated */
if (!smSendPairReq(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
break;
}
smTransitPhase(inst, SM_PHASE_REQ_SENT);
} else {
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
goto done;
default:
logw("Unexpected packet type 0x%02X in SM. Dropping\n", typ);
smSendPairFail(inst, SM_ERR_CMD_NOT_SUPP);
pairErr = SM_PAIR_ERR_UNEXPECTED_SM_CMD;
break;
}
logd("Got packet type 0x%02X in phase %u when we are the %s\n", typ, inst->phase,
inst->isInitiator ? "initiator" : "responder");
smNotifyPairStateObserver(&inst->peerAddr, SM_PAIR_STATE_FAILED, pairErr);
smTransitPhase(inst, SM_PHASE_START);
done:
sgFree(packet);
return;
}
/*
* FUNCTION: smInstFindByConn
* USE: Find the instance node by the connection ID
* PARAMS: conn - the connection ID
* RETURN: the instance if matched; NULL otherwise
* NOTES: call with mInstLock held
*/
static struct smInstance* smInstFindByConn(l2c_handle_t conn)
{
struct smInstance *inst = mActInstList;
if (!conn)
return NULL;
while(inst) {
if (inst->conn == conn)
return inst;
inst = inst->next;
}
return NULL;
}
/*
* FUNCTION: smInstFindByAddr
* USE: Find the instance node by the peer address
* PARAMS: addr
* RETURN: the instance if matched; NULL otherwise
* NOTES: call with mInstLock held
*/
static struct smInstance* smInstFindByAddr(const struct bt_addr *addr)
{
struct smInstance *inst = mActInstList;
if (!addr)
return NULL;
while(inst) {
if (smSameAddr(addr, &inst->peerAddr))
return inst;
inst = inst->next;
}
return NULL;
}
/*
* FUNCTION: smStallTimerCb
* USE: Called when the stallTimer of an instance is fired to terminate a stall pairing session
* PARAMS: timerId - an unique ID of a timer
* conn - a connection handle
* RETURN: NONE
* NOTES:
*/
static void smStallTimerCb(uniq_t timerId, uint64_t conn)
{
struct smInstance *inst;
pthread_mutex_lock(&mInstLock);
inst = smInstFindByConn(conn);
if (!inst) {
pthread_mutex_unlock(&mInstLock);
return;
}
if (inst->alg == SM_ALG_PASS_KEY) {
/* as an initiator, if we never send/receive conf value to/from responder, the passkey
* display becomes invalid */
if (inst->isInitiator && (inst->phase == SM_PHASE_REQ_RESP ||
inst->phase == SM_PHASE_MCONF_SENT))
smNotifyPasskeyDisplayObserver(&inst->peerAddr, false, 0);
/* as a responder, if we never receive passkey from user, the passkey request becomes
* invalid */
if (!inst->isInitiator && (inst->phase == SM_PHASE_REQ_RESP ||
inst->phase == SM_PHASE_MCONF_RECEIVED))
smNotifyPasskeyRequestObserver(&inst->peerAddr, false);
}
inst->stallTimer = 0;
logd("%s(): Pairing with device "MACFMT" is stalled\n", __func__,
MACCONV(inst->peerAddr.addr));
smBanDeviceForTime(&inst->peerAddr);
pthread_mutex_unlock(&mInstLock);
l2cApiDisconnect(conn);
smNotifyPairStateObserver(&inst->peerAddr, SM_PAIR_STATE_FAILED, SM_PAIR_ERR_STALLED);
}
/*
* FUNCTION: smUpdateIoCap
* USE: Update the IO capability based on the value of mInCap and mOutCap if there is no
* ongoing pairings
* PARAMS: inCap - input capability
* outCap - output capability
* RETURN: success
* NOTES: call with mInstLock held
*/
static bool smUpdateIoCap(uint8_t inCap, uint8_t outCap)
{
if ((inCap != SM_IN_CAP_NONE && inCap != SM_IN_CAP_YES_NO && inCap != SM_IN_CAP_KBD) ||
(outCap != SM_OUT_CAP_NONE && outCap != SM_OUT_CAP_NUMERIC))
return false;
mInCap = inCap;
mOutCap = outCap;
// postpone the update if there is any ongoing pairing
if (mActInstList)
return true;
mIoCap = mIoCaps[mInCap][mOutCap];
return true;
}
/*
* FUNCTION: smInstDeinit
* USE: Delete an instance from the active instance list, cancel the pairing timer and free it
* PARAMS: inst - the instance
* RETURN: NONE
* NOTES: This MUST be called only by smFixedChState when the state is L2C_STATE_CLOSED to avoid
* potential race and freeing already-freed instance
*/
static void smInstDeinit(struct smInstance* inst)
{
pthread_mutex_lock(&mInstLock);
if (!inst)
return;
if (inst->next)
inst->next->prev = inst->prev;
if (inst->prev)
inst->prev->next = inst->next;
else
mActInstList = inst->next;
if (inst->stallTimer)
timerCancel(inst->stallTimer);
smUpdateIoCap(mInCap, mOutCap);
free(inst);
pthread_mutex_unlock(&mInstLock);
}
/*
* FUNCTION: smInstInit
* USE: Set default values for repeated attempt fields of the instance, add it to the
* active instance list and keep start the timer for pairing process if an instance is
* not yet in the active instance list
* PARAMS: inst - the instance
* RETURN: NONE
* NOTES:
*/
static void smInstInit(struct smInstance *inst)
{
if (!inst)
return;
pthread_mutex_lock(&mInstLock);
if (!smInstFindByAddr(&inst->peerAddr)) {
inst->prev = NULL;
inst->next = mActInstList;
if (mActInstList)
mActInstList->prev = inst;
mActInstList = inst;
}
/* TODO(mcchou): fix stall timer according to Volume 3, Part H, 3.4 SMP TIMEOUT */
inst->stallTimer = timerSet(SM_STALL_INTVL, smStallTimerCb, inst->conn);
pthread_mutex_unlock(&mInstLock);
}
/*
* FUNCTION: smFixedChState
* USE: Connection event happened
* PARAMS: userData - unused
* instance - per-connection instance allocated by smFixedChAlloc()
* evt - what happened
* data - the pertinent data
* len - length of said data
* RETURN: SVC_ALLOC_*
* NOTES:
*/
static void smFixedChState(void *userData, void *instance, uint8_t evt, const void *data,
uint32_t len)
{
struct smInstance *inst = (struct smInstance*)instance;
struct l2cEncrState *encrState = NULL;
struct l2cKeyReqState *keyReqState = NULL;
struct bt_addr peerAddr;
struct bt_addr myAddr;
struct bt_addr resolAddr;
SmPairErr pairErr = SM_PAIR_ERR_UNKNOWN;;
l2c_handle_t conn;
sg s;
uint16_t myEdiv = 0;
uint64_t myRand = 0, myDiv = 0;
uint8_t key[HCI_LE_KEY_LEN] = {0,};
uint8_t rawKey[HCI_LE_KEY_LEN] = {0,};
uint8_t phase = inst->phase;
switch (evt) {
case L2C_STATE_OPEN:
if (len != sizeof(l2c_handle_t)) {
loge("invalid length for open event\n");
goto fail;
}
conn = *(l2c_handle_t*)data;
/* when we support outbound connections, this code will need to evolve */
if (!inst->isInitiator && inst->conn != conn) {
loge("Handle change!!!\n");
pairErr = SM_PAIR_ERR_UNEXPECTED_L2C_EVT;
goto reset;
}
/* assign the connection, our address and peer address if smPair was called to start the
* pairing as an initiator
*/
if (inst->isInitiator) {
if (inst->phase != SM_PHASE_START) {
pairErr = SM_PAIR_ERR_UNEXPECTED_L2C_EVT;
goto reset;
}
inst->conn = conn;
if (!l2cApiGetBtAddr(inst->conn, &peerAddr) ||
!l2cApiGetSelfBtAddr(inst->conn, &myAddr)) {
loge("Failed to get peer address or local address\n");
goto reset;
}
if (memcmp(&inst->peerAddr, &peerAddr, sizeof(struct bt_addr))) {
loge("Peer address mismatched\n");
goto reset;
}
inst->myAddr = myAddr;
}
smInstInit(inst);
if (inst->isInitiator) {
if (!smSendPairReq(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
goto reset;
}
smTransitPhase(inst, SM_PHASE_REQ_SENT);
}
break;
case L2C_STATE_ENCR:
if (len != sizeof(struct l2cEncrState)) {
loge("invalid length for encryption event\n");
goto fail;
}
encrState = (struct l2cEncrState *)data;
if (inst->phase != SM_PHASE_LTK_SENT && inst->phase != SM_PHASE_STK_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_L2C_EVT;
goto reset;
}
if (!smIsEncryptExpected(encrState, inst)) {
logw("Failed to encrypt the connection %llu\n", inst->conn);
pairErr = SM_PAIR_ERR_ENCR_CONN;
goto reset;
}
/* if the encryption of re-establishment went well, no further action is needed for either
* initiator or responder
*/
if (inst->phase == SM_PHASE_LTK_SENT) {
smTransitPhase(inst, SM_PHASE_LTK_ENCRYPTED);
break;
}
if (inst->phase == SM_PHASE_STK_SENT)
smTransitPhase(inst, SM_PHASE_STK_ENCRYPTED);
/* if we are the initiator, we wait for key distribution from the peer if any */
if (inst->isInitiator &&
(inst->peerKeyDistr & (SM_KEY_DISTR_LTK | SM_KEY_DISTR_IRK | SM_KEY_DISTR_CSRK)))
break;
if (!smDistributeKeys(inst)) {
pairErr = SM_PAIR_ERR_SEND_SM_CMD;
goto reset;
}
/* if we are responder, we wait for key distribution from the peer if any */
if (!inst->isInitiator &&
!!(inst->peerKeyDistr & (SM_KEY_DISTR_LTK | SM_KEY_DISTR_IRK | SM_KEY_DISTR_CSRK)))
break;
smTransitPhase(inst, SM_PHASE_DONE);
break;
case L2C_STATE_RX:
if (len != sizeof(sg)) {
loge("invalid length for RX event\n");
goto fail;
}
s = *(sg*)data;
smRx(inst, s);
break;
case L2C_STATE_KEY_REQ:
if (len != sizeof(struct l2cKeyReqState)) {
loge("invalid length for key request event\n");
goto fail;
}
keyReqState = (struct l2cKeyReqState *)data;
if (inst->isInitiator) {
logw("received key request when we are the initiator\n");
pairErr = SM_PAIR_ERR_UNEXPECTED_L2C_EVT;
goto reset;
}
/* handle the case where the peer device try to re-establish security after exchanging LTK,
* by checking if the EDIV and random number in key request state match with the numbers
* exchanged in previous pairing session. In this case, we are the responder where the peer
* will use our LTK for security re-establishment.
*/
if (inst->phase == SM_PHASE_DONE || inst->phase == SM_PHASE_LTK_ENCRYPTED) {
logd("Received key request for connection re-establishment, phase %u\n", inst->phase);
/* if peer address can be resolved, use its previous address to retrieve pairing info */
if (!smResolveAddr(&resolAddr, &inst->peerAddr))
resolAddr = inst->peerAddr;
if (!persistGetDevNumber(&resolAddr, PERSIST_NUM_TYPE_SM_MY_RANDOM, &myRand)) {
logw("Failed to retrieve random number to verify connection re-establishment\n");
goto fail;
} else if(!persistGetDevNumber(&resolAddr, PERSIST_NUM_TYPE_SM_MY_DIV, &myDiv)) {
logw("Failed to retrieve DIV to verify connection re-establishment\n");
goto fail;
} else if (!smGenEdiv(&myEdiv, &resolAddr, (uint16_t)myDiv, myRand, true) ||
myEdiv != keyReqState->ediv) {
logw("Failed to verify EDIV for connection re-establishment\n");
goto fail;
} else if (!persistGetDevKey(&resolAddr, KEY_TYPE_MY_LTK, key) ||
!smReadReversedBytes(rawKey, sizeof(rawKey), key, sizeof(key))) {
logw("Failed to retrieve LTK for connection re-establishment\n");
goto fail;
} else {
inst->isInitiator = false; /* for re-establishment, we are the responder for sure */
l2cApiLeProvideLtk(inst->conn, rawKey);
smTransitPhase(inst, SM_PHASE_LTK_SENT);
}
break;
}
if (inst->phase != SM_PHASE_RAND_RESP) {
l2cApiLeProvideLtk(inst->conn, NULL); /* reply with no key */
pairErr = SM_PAIR_ERR_UNEXPECTED_L2C_EVT;
goto reset;
}
/* the controller takes the key in little endian order, so reverse it before sending */
if (!smReadReversedBytes(rawKey, sizeof(rawKey), inst->stk.key, sizeof(inst->stk.key)) ||
!l2cApiLeProvideLtk(inst->conn, rawKey)) {
pairErr = SM_PAIR_ERR_ENCR_CONN;
goto reset;
}
smTransitPhase(inst, SM_PHASE_STK_SENT);
break;
case L2C_STATE_ENCR_KEY_REF:
if (len != sizeof(struct l2cEncrState)) {
loge("invalid length for encryption event\n");
goto fail;
}
encrState = (struct l2cEncrState *)data;
if (inst->phase != SM_PHASE_LTK_SENT) {
pairErr = SM_PAIR_ERR_UNEXPECTED_L2C_EVT;
goto reset;
}
if (!smIsEncryptExpected(encrState, inst)) {
logw("Failed to encrypt the connection %llu\n", inst->conn);
pairErr = SM_PAIR_ERR_ENCR_CONN;
goto reset;
}
/* if the encryption of key refreshing went well, no further action is needed for either
* initiator or responder
*/
smTransitPhase(inst, SM_PHASE_LTK_ENCRYPTED);
break;
case L2C_STATE_CLOSED:
smInstDeinit(inst);
break;
default:
logd("unknown L2C event %u\n", evt);
goto fail;
}
return;
reset:
smTransitPhase(inst, SM_PHASE_START);
fail:
logd("Got l2cap event 0x%02X in phase %u when we are the %s\n", evt, phase,
inst->isInitiator ? "initiator" : "responder");
smNotifyPairStateObserver(&inst->peerAddr, SM_PAIR_STATE_FAILED, pairErr);
}
/*
* FUNCTION: smFixedChAlloc
* USE: Connection request: if accepted, alloc a per-connection instance of the SM connection
* structure
* PARAMS: userData - unused
* conn - l2c conn handle
* instanceP - we store instance here if we allocate one
* RETURN: SVC_ALLOC_*
* NOTES:
*/
static uint8_t smFixedChAlloc(void *userData, l2c_handle_t conn, void **instanceP)
{
struct smInstance *inst;
struct bt_addr peerAddr;
struct bt_addr myAddr;
if (!l2cApiGetBtAddr(conn, &peerAddr)) {
loge("Failed to get peer address\n");
return SVC_ALLOC_FAIL_OTHER;
}
if(!l2cApiGetSelfBtAddr(conn, &myAddr)) {
logd("Failed to get local address\n");
return SVC_ALLOC_FAIL_OTHER;
}
if (!BT_ADDR_IS_LE(peerAddr)) {
logd("Refusing SM connection for EDR\n");
return SVC_ALLOC_FAIL_OTHER;
}
if (smIsDevBanned(&peerAddr)) {
logd("Refusing SM connection for banned device\n");
return SVC_ALLOC_FAIL_OTHER;
}
inst = (struct smInstance*)calloc(1, sizeof(struct smInstance));
if (!inst)
return SVC_ALLOC_FAIL_OTHER;
inst->peerAddr = peerAddr;
inst->myAddr = myAddr;
inst->conn = conn;
inst->phase = SM_PHASE_START;
/* if the peer is previously paired, set the phase to done */
if (smIsPaired(NULL, NULL, &inst->peerAddr))
inst->phase = SM_PHASE_DONE;
*instanceP = inst;
l2cApiLeSetSecurityManagerForAclConn(conn);
logd("%s() sm channel assigned\n", __func__);
return SVC_ALLOC_SUCCESS;
}
/*
* FUNCTION: smNumEnumF
* USE: Find the previously-paired peer info in DB by matching the number
* PARAMS: cbkData - the callback data to the caller function to reveal the result of enumeration
* addr - the peer address
* name - device name if known
* nameLen - length of the name
* devCls - the device class
* haveKeys - the bit mask of keys we have
* wantedKey - NULL if unused
* haveNums - the bit mask of numbers we have
* wantedNum - NULL if unused
* RETURN: success
* NOTES:
*/
static bool smNumEnumF(void *cbkData, const struct bt_addr *addr, const void *name,
uint32_t nameLen, uint32_t devCls, uint32_t haveKeys,
const uint8_t *wantedKey, uint32_t haveNums, const uint64_t *wantedNum)
{
struct smNumEnumCbkData *data = NULL;
if (!cbkData)
return true;
data = (struct smNumEnumCbkData *)cbkData;
if (wantedNum) {
if (!(haveNums & (1 << data->numType)))
return true;
else if (*wantedNum != data->num)
return true;
}
if (!addr)
return true;
data->peerAddr = *addr;
data->wasFound = true;
return false;
}
/*
* FUNCTION: smReestablishSecurity
* USE: Re-establish the security of a connection with previously-exchanged pairing info if any
* PARAMS: hciConn - HCI connection handle
* randomNum - the random number associated with a set of pairing info
* diversifier - the EDIV associated with a set of pairing info
* RETURN: success
* NOTES:
*/
static bool smReestablishSecurity(hci_conn_t hciConn, uint64_t randomNum, uint16_t diversifier)
{
struct smNumEnumCbkData enumData;
uint8_t numType = PERSIST_NUM_TYPE_SM_MY_RANDOM;
uint8_t keyType = KEY_TYPE_LTK;
uint8_t myLtk[HCI_LE_KEY_LEN] = {0,};
uint8_t rawLtk[HCI_LE_KEY_LEN] = {0,};
uint16_t ediv = 0;
uint64_t myEdiv = 0, myDiv = 0;
bool err = true;
enumData.numType = numType;
enumData.num = randomNum;
/* enumerate DB to find the peer device with matched randomNum and EDIV */
persistEnumKnownDevs(smNumEnumF, (void *)&enumData, NULL, &numType);
if (!enumData.wasFound) {
logd("Failed to find the peer device with a random number\n");
goto out;
}
/* retrieve EDIV and check if EDIVs match */
if (!persistGetDevNumber(&enumData.peerAddr, PERSIST_NUM_TYPE_SM_MY_DIV, &myDiv)) {
logd("Failed to retrieve DIV for verifying EDIV\n");
goto out;
} else if (!persistGetDevNumber(&enumData.peerAddr, numType, &myEdiv)) {
logd("Failed to retrieve EDIV for verifying EDIV\n");
goto out;
} else if (!smGenEdiv(&ediv, &enumData.peerAddr, myDiv, randomNum, true) || ediv != myEdiv) {
logd("EDIV mismatched for re-establishing security\n");
goto out;
}
/* retrieve LTK */
if (!persistGetDevKey(&enumData.peerAddr, keyType, myLtk)) {
logd("Failed to retrieve LTK for re-establishing security\n");
goto out;
} else if (!smReadReversedBytes(rawLtk, sizeof(rawLtk), myLtk, sizeof(myLtk))) {
goto out;
}
err = false;
out:
hciLeProvideLtk(hciConn, err ? NULL : rawLtk);
return !err;
}
/*
* FUNCTION: smInit
* USE: Init the security manager
* PARAMS: NONE
* RETURN: success
* NOTES:
*/
bool smInit(void)
{
static const struct l2cServiceFixedChDescriptor descr = {
.serviceFixedChAlloc = smFixedChAlloc,
.serviceFixedChStateCbk = smFixedChState,
};
pthread_mutex_lock(&mInstLock);
smUpdateIoCap(SM_IN_CAP_NONE, SM_OUT_CAP_NONE);
pthread_mutex_unlock(&mInstLock);
/* TODO(mcchou): this needs to be changed accordingly for the OOB support */
mHasOob = false;
if (!(mSmSendQ = sendQueueAlloc(MAX_PACKETS_SM_SEND_QUEUE))) {
logw("%s(): Error allocating SM sendQ.\n", __func__);
return false;
}
mPairStateObservers = multiNotifCreate();
if (!mPairStateObservers) {
logw("%s(): Error creating pairing state observer list\n", __func__);
goto pair_state_notif_fail;
}
mNotifWork = workQueueAlloc(SM_NUM_OUTSTANDING_NOTIFY);
if (!mNotifWork) {
logw("%s(): Error allocating SM notification work queue\n", __func__);
goto work_fail;
}
if (pthread_create(&mNotifWorker, NULL, smNotifWorker, NULL)) {
logw("%s(): Error creating SM notification worker\n", __func__);
goto thread_fail;
}
if(!l2cApiServiceFixedChRegister(L2C_FIXED_CH_NUM_SM, &descr)) {
logw("%s(): Error registering L2CAP service.\n", __func__);
goto register_fail;
}
l2cApiLeSetDefaultSecurityManager(smReestablishSecurity);
/* if DB was removed before, new CSRK, IRK and DHK are generated automatically on load */
persistLoad();
logd("%s(): smInit finished correctly\n", __func__);
return true;
register_fail:
workQueueWakeAll(mNotifWork, 1);
pthread_join(mNotifWorker, NULL);
thread_fail:
workQueueFree(mNotifWork, smNotifWorkFree);
mNotifWork = NULL;
work_fail:
multiNotifDestroy(mPairStateObservers);
mPairStateObservers = NULL;
pair_state_notif_fail:
sendQueueFree(mSmSendQ);
mSmSendQ = NULL;
return false;
}
/*
* FUNCTION: smDeinit
* USE: Deinit the security manager
* PARAMS: NONE
* RETURN: NONE
* NOTES:
*/
void smDeinit(void)
{
l2cApiServiceFixedChUnregister(L2C_FIXED_CH_NUM_SM, NULL, NULL);
if (mNotifWork) {
workQueueWakeAll(mNotifWork, 1);
pthread_join(mNotifWorker, NULL);
workQueueFree(mNotifWork, smNotifWorkFree);
}
if (mPasskeyDisplayObserver) {
free(mPasskeyDisplayObserver);
mPasskeyDisplayObserver = NULL;
}
if (mPasskeyRequestObserver) {
free(mPasskeyRequestObserver);
mPasskeyRequestObserver = NULL;
}
if (mPairStateObservers)
multiNotifDestroy(mPairStateObservers);
if (mSmSendQ)
sendQueueFree(mSmSendQ);
smBannedDevFreeAll();
persistStore();
logd("%s(): smDeinit finished correctly\n", __func__);
}
/*
* FUNCTION: smRegisterPairStateObserver
* USE: Register as an observer if interested in receiving notifications about pairing state
* changes with remote LE devices
* PARAMS: observerData - observer's own data passed when the callback is invoked
* cbk - the callback invoked if there is any pairing state change
* RETURN: a none-zero unique observer ID in success case; 0 otherwise
* NOTES:
*/
uniq_t smRegisterPairStateObserver(void *observerData, smPairStateChangeCbk cbk)
{
if (!cbk)
return 0;
return multiNotifRegister(mPairStateObservers, cbk, observerData);
}
/*
* FUNCTION: smUnregisterPairStateObserver
* USE: Unregister as an observer if no longer interested in receiving notifications about
* pairing state changes with remote LE devices
* PARAMS: observerId - an unique observer ID
* RETURN: NONE
* NOTES:
*/
void smUnregisterPairStateObserver(uniq_t observerId)
{
multiNotifUnregister(mPairStateObservers, observerId);
}
/*
* FUNCTION: smNotifyPairStateObserver
* USE: Notify the observers about the state change of a device
* PARAMS: addr - the address of the peer device
* state - the new pairing state
* RETURN: NONE
* NOTES:
*/
static void smNotifyPairStateObserver(const struct bt_addr *addr, SmPairState state, SmPairErr err)
{
struct smNotifWork *work;
switch(state) {
case SM_PAIR_STATE_NOT_PAIRED:
case SM_PAIR_STATE_START:
case SM_PAIR_STATE_PAIRED:
case SM_PAIR_STATE_CANCELED:
case SM_PAIR_STATE_FAILED:
break;
default:
logd("Drop unknown pair state %u with err %u\n", state, err);
return;
}
switch(err) {
case SM_PAIR_ERR_NONE:
if (state == SM_PAIR_STATE_FAILED) {
logd("Drop unexpected pair state %u with err %u\n", state, err);
return;
}
break;
case SM_PAIR_ERR_ALREADY_PAIRED:
case SM_PAIR_ERR_IN_PROGRESS:
case SM_PAIR_ERR_NO_SUCH_DEVICE:
case SM_PAIR_ERR_INVALID_PAIR_REQ:
case SM_PAIR_ERR_PASSKEY_FAILED:
case SM_PAIR_ERR_OOB_NOT_AVAILABLE:
case SM_PAIR_ERR_AUTH_REQ_INFEASIBLE:
case SM_PAIR_ERR_CONF_VALUE_MISMATCHED:
case SM_PAIR_ERR_PAIRING_NOT_SUPPORTED:
case SM_PAIR_ERR_ENCR_KEY_SIZE:
case SM_PAIR_ERR_REPEATED_ATTEMPT:
case SM_PAIR_ERR_INVALID_PARAM:
case SM_PAIR_ERR_MEMORY:
case SM_PAIR_ERR_L2C_CONN:
case SM_PAIR_ERR_UNEXPECTED_SM_CMD:
case SM_PAIR_ERR_SEND_SM_CMD:
case SM_PAIR_ERR_ENCR_CONN:
case SM_PAIR_ERR_UNEXPECTED_L2C_EVT:
case SM_PAIR_ERR_STALLED:
case SM_PAIR_ERR_UNKNOWN:
if (state != SM_PAIR_STATE_FAILED) {
logd("Drop unexpected pair state %u with err %u\n", state, err);
return;
}
break;
default:
logd("Drop unknown pair error %u with state %u\n", err, state);
return;
}
work = (struct smNotifWork *)calloc(1, sizeof(struct smNotifWork));
if (!work) {
loge("Failed to allocate SM work item\n");
return;
}
work->type = SM_WORK_NOTIFY_PAIR_STATE_CHG;
work->pairStateChg.addr = *addr;
work->pairStateChg.state = state;
work->pairStateChg.err = err;
if (!workQueuePut(mNotifWork, work)) {
loge("Failed to enqueue SM work for passkey display\n");
free(work);
}
}
/*
* FUNCTION: smRegisterPasskeyDisplayObserver
* USE: Register as the observer if interested in receiving notifications about the passkey
* used during the pairing with remote LE devices.
* PARAMS: observerData - observer's own data passed when the callback is invoked
* cbk - the callback invoked if there is a passkey to be displayed
* RETURN: a none-zero unique observer ID in success case; 0 otherwise
* NOTES: there can be only one observer of passkey display at a time
*/
uniq_t smRegisterPasskeyDisplayObserver(void *observerData, smPasskeyDisplayCbk cbk)
{
uniq_t ret = 0;
pthread_mutex_lock(&mObvrLock);
if (!cbk || mPasskeyDisplayObserver)
goto out;
mPasskeyDisplayObserver = (struct smPasskeyDisplayObserver *)
calloc(1, sizeof(struct smPasskeyDisplayObserver));
if (!mPasskeyDisplayObserver)
goto out;
mPasskeyDisplayObserver->id = ret = uniqGetNext();
mPasskeyDisplayObserver->cbk = cbk;
mPasskeyDisplayObserver->data = observerData;
smUpdateIoCap(mInCap, SM_OUT_CAP_NUMERIC);
out:
pthread_mutex_unlock(&mObvrLock);
return ret;
}
/*
* FUNCTION: smUnregisterPasskeyDisplayObserver
* USE: Unregister as an observer if no longer interested in receiving notifications about
* the passkey display
* PARAMS: observerId - an unique observer ID
* RETURN: NONE
* NOTES:
*/
void smUnregisterPasskeyDisplayObserver(uniq_t observerId)
{
pthread_mutex_lock(&mObvrLock);
if (!mPasskeyDisplayObserver || observerId != mPasskeyDisplayObserver->id) {
pthread_mutex_unlock(&mObvrLock);
return;
}
free(mPasskeyDisplayObserver);
mPasskeyDisplayObserver = NULL;
smUpdateIoCap(mInCap, SM_OUT_CAP_NONE);
pthread_mutex_unlock(&mObvrLock);
}
/*
* FUNCTION: smNotifyPasskeyDisplayObserver
* USE: Notify the observer about the passkey display
* PARAMS: addr - the address of the peer device
* valid - a switch to notify on starting or stopping the passkey display
* valid - indicate whether a passkey is valid; if true, the passkey is new and
* should be displayed; if false, the passkey value is not valid, since the passkey is
* either done with display or expired
* passkey - the passkey, if display is true, then passkey is a valid number; false
* otherwise
* RETURN: NONE
* NOTES:
*/
static void smNotifyPasskeyDisplayObserver(const struct bt_addr *addr, bool valid, uint32_t passkey)
{
struct smNotifWork *work;
if (!addr)
return;
work = (struct smNotifWork *)calloc(1, sizeof(struct smNotifWork));
if (!work) {
loge("Failed to allocate SM work item\n");
return;
}
work->type = SM_WORK_NOTIFY_PASSKEY_DISPLAY;
work->passkeyDisp.addr = *addr;
work->passkeyDisp.valid = valid;
if (valid)
work->passkeyDisp.passkey = passkey;
if (!workQueuePut(mNotifWork, work)) {
loge("Failed to enqueue SM work for pairing state change\n");
free(work);
}
}
/*
* FUNCTION: smRegisterPasskeyRequestObserver
* USE: Register as the observer if interested in receiving the passkey request during the
* pairing with remote LE devices.
* PARAMS: observerData - observer's own data passed when the callback is invoked
* cbk - the callback invoked if there is a passkey request
* RETURN: a none-zero unique observer ID in success case; 0 otherwise
* NOTES: there can be only one observer of passkey request at a time
*/
uniq_t smRegisterPasskeyRequestObserver(void *observerData, smPasskeyRequestCbk cbk)
{
uniq_t ret = 0;
pthread_mutex_lock(&mObvrLock);
if (!cbk || mPasskeyRequestObserver)
goto out;
mPasskeyRequestObserver = (struct smPasskeyRequestObserver *)
calloc(1, sizeof(struct smPasskeyRequestObserver));
if (!mPasskeyRequestObserver)
goto out;
mPasskeyRequestObserver->id = ret = uniqGetNext();
mPasskeyRequestObserver->cbk = cbk;
mPasskeyRequestObserver->data = observerData;
smUpdateIoCap(SM_IN_CAP_KBD, mOutCap);
out:
pthread_mutex_unlock(&mObvrLock);
return ret;
}
/*
* FUNCTION: smUnregisterPasskeyRequestObserver
* USE: Unregister as an observer if no longer interested in receiving notifications about
* the passkey request
* PARAMS: observerId - an unique observer ID
* RETURN: NONE
* NOTES:
*/
void smUnregisterPasskeyRequestObserver(uniq_t observerId)
{
pthread_mutex_lock(&mObvrLock);
if (!mPasskeyRequestObserver || observerId != mPasskeyRequestObserver->id) {
pthread_mutex_unlock(&mObvrLock);
return;
}
free(mPasskeyRequestObserver);
mPasskeyRequestObserver = NULL;
smUpdateIoCap(SM_IN_CAP_NONE, mOutCap);
pthread_mutex_unlock(&mObvrLock);
}
/*
* FUNCTION: smNotifyPasskeyRequestObserver
* USE: Notify the observer about the passkey request
* PARAMS: addr - the address of the peer device
* valid - indicate whether a passkey request is valid; if true, the request is new and
* should be displayed; if false, the passkey request is either done or canceled
* RETURN: NONE
* NOTES:
*/
static void smNotifyPasskeyRequestObserver(const struct bt_addr *addr, bool valid)
{
struct smNotifWork *work;
if (!addr)
return;
work = (struct smNotifWork *)calloc(1, sizeof(struct smNotifWork));
if (!work) {
loge("Failed to allocate SM work item\n");
return;
}
work->type = SM_WORK_NOTIFY_PASSKEY_REQUEST;
work->passkeyReq.addr = *addr;
work->passkeyReq.valid = valid;
if (!workQueuePut(mNotifWork, work)) {
loge("Failed to enqueue SM work for passkey request\n");
free(work);
}
}
/*
* FUNCTION: smPair
* USE: Pair with a peer device with security requirements enforced
* PARAMS: addr - the address of the peer device
* secReqs - the security requirements of the pairing
* RETURN: NONE
* NOTES: The outcome will be notified via smPairStateChangeCbk once a client registered as an
* observer of pairing state changes
*/
void smPair(const struct bt_addr *addr, const struct smPairSecurityRequirements *secReqs)
{
bool mitm = false, bonded = false;
struct smInstance *inst = NULL;
SmPairErr pairErr = SM_PAIR_ERR_UNKNOWN;
if (!addr || !secReqs) {
logd("%s(): Invalid peer address or security requirements\n", __func__);
pairErr = SM_PAIR_ERR_INVALID_PAIR_REQ;
goto fail;
}
logd("%s(): "ADDRFMT"\n", __func__, ADDRCONV(*addr));
pthread_mutex_lock(&mInstLock);
/* check if there is an existing instance with the same peer device */
if ((inst = smInstFindByAddr(addr))) {
logd("%s(): Pairing in progress with "ADDRFMT"\n", __func__, ADDRCONV(*addr));
pairErr = SM_PAIR_ERR_IN_PROGRESS;
goto fail;
}
/* check if the device was previously paired with the security requirements met */
if (smIsPaired(&mitm, &bonded, addr) && (secReqs->bond ? bonded : true) &&
(secReqs->mitm ? mitm : true)) {
logd("%s(): Already paired with "ADDRFMT"\n", __func__, ADDRCONV(*addr));
pairErr = SM_PAIR_ERR_ALREADY_PAIRED;
goto fail;
}
/* clear the previously pairing info and start the new pairing */
logi("%s(): Clear pairing info if any to start pairing with higher security requirements "
ADDRFMT"\n", __func__, ADDRCONV(*addr));
persistDelDevKeys(addr);
persistDelDevNumbers(addr);
inst = (struct smInstance*)calloc(1, sizeof(struct smInstance));
if (!inst) {
logd("%s(): Failed to create instance for pairing with "ADDRFMT"\n", __func__,
ADDRCONV(*addr));
pairErr = SM_PAIR_ERR_MEMORY;
goto fail;
}
inst->peerAddr = *addr;
inst->isInitiator = true; /* we are the initiator of pairing */
inst->phase = SM_PHASE_START;
inst->myAuthReq = (secReqs->bond ? SM_AUTH_REQ_BOND_BOND : 0x00) |
(secReqs->mitm ? SM_AUTH_REQ_MITM_FLAG : 0x00);
inst->myMaxKeySz = SM_MAX_KEY_LEN;
/* TODO(mcchou): change the distribution of IRK if the privacy feature is not supported when
* moving to Bluetooth spec v5.0
* the current implementation is based on Bluetooth spec v4.0 (see Table 10.2: Requirements
* related to privacy feature). it is mandatory for the central role (the initiator of pairing)
* to support resolvable address generation, and the distribution of IRK comes along with the
* bonding. on the other hand, the remote device may or may not support privacy feature, so it
* is optional for the remote device to support resolvable address generation, but it is
* mandatory to support resolvable address resolution.
*/
inst->myKeyDistrReq = (SM_KEY_DISTR_LTK | (secReqs->bond ? SM_KEY_DISTR_IRK : 0x00) |
SM_KEY_DISTR_CSRK);
inst->myKeyDistr = inst->myKeyDistrReq;
inst->peerKeyDistrReq = inst->myKeyDistrReq;
inst->peerKeyDistr = inst->myKeyDistrReq;
if (!l2cApiCreateFixedChConnection(L2C_FIXED_CH_NUM_SM, addr, smFixedChState, NULL, inst)) {
logw("%s(): Error creating a fixed channel l2cap connection\n", __func__);
pairErr = SM_PAIR_ERR_L2C_CONN;
goto fail;
}
smNotifyPairStateObserver(&inst->peerAddr, SM_PAIR_STATE_START, SM_PAIR_ERR_NONE);
pthread_mutex_unlock(&mInstLock);
return;
fail:
smNotifyPairStateObserver(addr, SM_PAIR_STATE_FAILED, pairErr);
pthread_mutex_unlock(&mInstLock);
}
/*
* FUNCTION: smUnpair
* USE: Unpair with a peer device by removing all stored pairing info from the database OR
* cancel the pairing with a peer device
* PARAMS: addr - the address of the peer device
* RETURN: None
* NOTES: The outcome will be notified via smPairStateChangeCbk once a client registered as an
* observer of pairing state changes
*/
void smUnpair(const struct bt_addr *addr)
{
struct smInstance *inst;
bool cancelled = false;
if (!addr || !persistGetKnownDev(addr, NULL, NULL, NULL)) {
smNotifyPairStateObserver(addr, SM_PAIR_STATE_FAILED, SM_PAIR_ERR_NO_SUCH_DEVICE);
return;
}
logd("%s(): "ADDRFMT"\n", __func__, ADDRCONV(*addr));
pthread_mutex_lock(&mInstLock);
/* check if there is an existing instance with the peer device */
inst = smInstFindByAddr(addr);
if (inst) {
/* Let L2CAP handle the removal of the instance to avoid race condition */
if (inst->conn)
l2cApiDisconnect(inst->conn);
cancelled = inst->phase != SM_PHASE_DONE && inst->phase != SM_PHASE_LTK_ENCRYPTED;
}
pthread_mutex_unlock(&mInstLock);
persistDelDevKeys(addr);
persistDelDevNumbers(addr);
logd("%s(): Pairing with "ADDRFMT" is %s\n", __func__, ADDRCONV(*addr),
cancelled ? "cancelled" : "forgotten");
smNotifyPairStateObserver(addr, cancelled ? SM_PAIR_STATE_CANCELED : SM_PAIR_STATE_NOT_PAIRED,
SM_PAIR_ERR_NONE);
}
/*
* FUNCTION: smGenResolvableAddr
* USE: Generate a resolvable private address based on our IRK
* PARAMS: addr - the output resolvable private address
* RETURN: true if addr is a valid address; false otherwise
* NOTES:
*/
bool smGenResolvableAddr(struct bt_addr *addr)
{
uint32_t rand, hash; /* only the 24 LSBs of rand and hash will be used */
uint8_t irk[HCI_LE_KEY_LEN] = {0,};
int8_t i;
if (!addr)
return false;
do {
if (!smGenRandNum((uint8_t *)&rand, sizeof(rand)))
return false;
rand = (rand & 0x003FFFFF) | (1 << 22); /* set 2 MSBs of the 24 LSBs to be 01 */
} while (rand == 0x00400000 || rand == 0x007FFFFF); /* make sure there is at least one bit is
0/1 in 22 LSBs of the 24 LSBs */
if (!persistGetDevKey(NULL, KEY_TYPE_IRK, irk)) {
logd("%s(): Failed to retrieve IRK for generating address\n", __func__);
return false;
}
hash = smAddressHash(rand, irk);
addr->type = BT_ADDR_TYPE_LE_RANDOM;
for (i = 0; i < 3; ++i, hash >>= 8)
addr->addr[i] = hash; /* addr->addr is stored in [0]->[5] : LSO -> MSO order */
for (i = 3; i < BT_MAC_LEN; ++i, rand >>= 8)
addr->addr[i] = rand;
return true;
}
/*
* FUNCTION: smGenNonResolvableAddr
* USE: Generate a non-resolvable private address
* PARAMS: addr - the output non-resolvable private address
* RETURN: true if addr is a valid address; false otherwise
* NOTES:
*/
bool smGenNonResolvableAddr(struct bt_addr *addr)
{
uint8_t i;
uint64_t rand, m;
uint8_t mac[BT_MAC_LEN] = {0,};
if (!addr)
return false;
hciGetLocalAddress(mac);
for (i = 0; i < BT_MAC_LEN; ++i)
m = m << 8 | mac[BT_MAC_LEN - 1 - i];
do {
if (!smGenRandNum((uint8_t *)&rand, sizeof(rand)))
return false;
rand = rand & 0x00003FFFFFFFFFFF;
} while (rand == 0x0000000000000000 || rand == 0x00003FFFFFFFFFFF || rand == m);
addr->type = BT_ADDR_TYPE_LE_RANDOM;
for (i = 0; i < BT_MAC_LEN; ++i, rand >>= 8)
addr->addr[i] = rand; /* addr->addr is stored in [0]->[5] : LSO -> MSO order */
return true;
}
/*
* FUNCTION: smProvidePasskey
* USE: The observer of passkey request should call this to provide the passkey displayed on
* on the peer device
* PARAMS: addr - the peer address
* entered - indicate whether the user provide a passkey or not
* passkey - the input passkey
* RETURN: true if addr is a valid address; false otherwise
* NOTES: if there is no observer registered to handle to passkey input, or the observer fails to
* provide the passkey input in time, pairing will fail due to timeout
*/
void smProvidePasskey(const struct bt_addr *addr, bool provided, uint32_t passkey)
{
uint8_t i, prevPhase;
struct smInstance *inst = NULL;
pthread_mutex_lock(&mInstLock);
if (!(inst = smInstFindByAddr(addr)))
goto out;
if (inst->isInitiator || (inst->phase != SM_PHASE_REQ_RESP &&
inst->phase != SM_PHASE_MCONF_RECEIVED))
goto out;
prevPhase = inst->phase;
smNotifyPasskeyRequestObserver(addr, false);
for (i = sizeof(inst->tk.key) - 1; i > sizeof(inst->tk.key) - 4; --i, passkey >>= 8)
inst->tk.key[i] = passkey;
smGenConfVal(&inst->myConfVal, inst, false);
smTransitPhase(inst, SM_PHASE_SCONF_READY);
/* a passkey can be provided before or after receiving Mconfirm. if it is provided after
* Mconfirm, we send Sconfirm right away */
if (prevPhase == SM_PHASE_MCONF_RECEIVED) {
if (!smSendPairConf(inst)) {
smNotifyPairStateObserver(&inst->peerAddr, SM_PAIR_STATE_FAILED,
SM_PAIR_ERR_SEND_SM_CMD);
smTransitPhase(inst, SM_PHASE_START);
goto out;
}
smTransitPhase(inst, SM_PHASE_SCONF_SENT);
}
out:
pthread_mutex_unlock(&mInstLock);
}
/*
* FUNCTION: smCheckResolvableAddrIrkMatch
* USE: Determine whether a address is resolvable with a IRK
* PARAMS: addr - the address
* irk - the IRK
* RETURN: true if the address is resolvable; false otherwise
* NOTES:
*/
static bool smCheckResolvableAddrIrkMatch(const struct bt_addr *addr, const uint8_t *irk)
{
int8_t i;
uint32_t rand = 0, hash = 0;
if (!addr || !irk)
return false;
for (i = BT_MAC_LEN - 1; i > 2; --i)
rand = rand << 8 | addr->addr[i]; /* addr->addr is stored in [0]->[5] : LSO -> MSO order */
for (i = 2; i >= 0; --i)
hash = hash << 8 | addr->addr[i];
return smAddressHash(rand, irk) == hash;
}
/*
* FUNCTION: smIrkEnumF
* USE: Find the previously-paired peer IRK in DB which can resolve the random private address
* PARAMS: cbkData - the callback data to the caller function to reveal the result of enumeration
* addr - the peer address
* name - device name if known
* nameLen - length of the name
* devCls - the device class
* haveKeys - the bit mask of keys we have
* wantedKey - NULL if unused
* haveNums - the bit mask of numbers we have
* wantedNum - NULL if unused
* RETURN: success
* NOTES:
*/
static bool smIrkEnumF(void *cbkData, const struct bt_addr *addr, const void *name,
uint32_t nameLen, uint32_t devCls, uint32_t haveKeys,
const uint8_t *wantedKey, uint32_t haveNums, const uint64_t *wantedNum)
{
struct smIrkEnumCbkData *data;
if (!cbkData)
return true;
data = (struct smIrkEnumCbkData *)cbkData;
if (wantedKey) {
if (!(haveKeys &(1 << data->keyType)))
return true;
else if (!smCheckResolvableAddrIrkMatch(&data->resolAddr, wantedKey))
return true;
}
if (!addr)
return true;
data->peerAddr = *addr;
data->wasFound = true;
return false;
}
/*
* FUNCTION: smResolveAddr
* USE: Try to resolve a address with all the IRK in DB
* PARAMS: addr - the output address associated with the IRK which can resolve peerAddr
* peerAddr - the address to be resolved
* RETURN: true if peerAddr can be resolved with a stored IRK and addr is valid; false otherwise
* NOTES:
*/
static bool smResolveAddr(struct bt_addr *addr, const struct bt_addr *peerAddr)
{
struct smIrkEnumCbkData enumData;
uint8_t keyType = KEY_TYPE_IRK;
bool err = true;
if (!addr || !peerAddr)
return false;
enumData.keyType = keyType;
enumData.resolAddr = *peerAddr;
/* enumerate DB to find the peer address with IRK which can resolve peerAddr */
persistEnumKnownDevs(smIrkEnumF, (void *)&enumData, NULL, &keyType);
if (!enumData.wasFound) {
logi("No matching IRK\n");
goto out;
}
*addr = enumData.peerAddr;
err = false;
out:
return !err;
}
/*
* FUNCTION: smCmacDo
* USE: Perform the CMAC calculation
* PARAMS: dst - where the store the result
* macLen - how long of a digest do we want (in bytes)
* m - the message to digest
* k - the key to use
* k1 - subkey 1 (from smCmacPrepare)
* k2 - subkey 2 (from smCmacPrepare)
* RETURN: NONE
* NOTES:
*/
static void smCmacDo(uint8_t *dst, uint8_t macLen, sg m, const uint8_t *k, const uint8_t *k1,
const uint8_t *k2)
{
uint32_t len = sgLength(m), pos = 0;
uint32_t i, nblocks = len ? (len + SM_BLOCK_LEN - 1) / SM_BLOCK_LEN : 1;
uint8_t tmp1[SM_BLOCK_LEN] = {0,}, tmp2[SM_BLOCK_LEN], j, *in = tmp1, *out = tmp2, *swap;
for (i = 0; i < nblocks; i++) {
for(j = 0; j < len && j < SM_BLOCK_LEN; j++) {
uint8_t byte;
sgSerialize(m, pos++, sizeof(uint8_t),&byte);
in[j] ^= byte;
}
len -= j;
if (!len) {
if (j == SM_BLOCK_LEN) {
for(j = 0; j < SM_BLOCK_LEN; j++)
in[j] ^= *k1++;
} else {
in[j] ^= 0x80;
for(j = 0; j < SM_BLOCK_LEN; j++)
in[j] ^= *k2++;
}
}
smEncrypt(out, in, k);
swap = in;
in = out;
out = swap;
}
while (macLen--)
*dst++ = *in++;
}
/*
* FUNCTION: smCmacPrepare
* USE: Perform the pre-calculation of K1 & K2 for the CMAC
* PARAMS: k1 - where to store k1
* k2 - where to store k2
* k - the k input value
* RETURN: NONE
* NOTES:
*/
static void smCmacPrepare(uint8_t *k1, uint8_t *k2, const uint8_t *k)
{
uint8_t tmp[SM_BLOCK_LEN] = {0,}, *out = k1, i, j;
smEncrypt(k1, tmp, k);
for (i = 0; i < 2; i++) {
bool shiftOut;
shiftOut = !!(k1[0] & 0x80);
for (j = 0; j < 15; j++)
out[j] = (k1[j] << 1) | (k1[j + 1] >> 7);
out[15] = k1[15] << 1;
if (shiftOut)
out[15] ^= 0x87;
out = k2;
}
}
/*
* FUNCTION: smSignatureCalc
* USE: Perform a CMAC calculation in its entirety
* PARAMS: data - the data to checksum
* len - length of said data
* key - the key
* sig - sig gets stored here
* RETURN: NONE
* NOTES:
*/
void smSignatureCalc(sg m, const uint8_t *key, uint8_t *sig)
{
uint8_t k1[SM_BLOCK_LEN], k2[SM_BLOCK_LEN];
smCmacPrepare(k1, k2, key);
smCmacDo(sig, SM_SIG_LEN, m, key, k1, k2);
}
/*
* FUNCTION: smCalcKey
* USE: Perform the calculation of a key
* PARAMS: out - store the result here
* k - the k value as per spec
* p1 - the p1 value as per spec
* p2 - the p2 calue as per spec
* RETURN: NONE
* NOTES: The SM docs call this the s1() function
*/
static void smCalcKey(uint8_t *out, const uint8_t *k, const uint8_t *r1, const uint8_t *r2)
{
uint8_t t[SM_BLOCK_LEN];
uint8_t i;
for (i = 0; i < 8; i++) {
t[i + 0] = r1[i + 8];
t[i + 8] = r2[i + 8];
}
smEncrypt(out, t, k);
}
/*
* FUNCTION: smCalcConfVal
* USE: Perform the calculation of the confirmation value
* PARAMS: out - store the result here
* k - the k value as per spec
* r - the r value as per spec
* pres - the pres value as per spec, only bottom 56 bit used
* preq - the pres value as per spec, only bottom 56 bit used
* iat - the iat value as per spec
* ia - the ia value as per spec, only bottom 48 bits used
* rat - the rat value as per spec
* ra - the ra value as per spec, only bottom 48 bits used
* RETURN: NONE
* NOTES: The SM docs call this the c1() function
*/
static void smCalcConfVal(uint8_t* out, const uint8_t *k, const uint8_t *r, uint64_t pres,
uint64_t preq, bool iat, uint64_t ia, bool rat, uint64_t ra)
{
uint8_t p1[SM_BLOCK_LEN];
uint8_t p2[SM_BLOCK_LEN] = {0,};
uint8_t i;
/* create p1 */
for (i = 0; i < 7; i++) {
p1[6 - i] = pres;
p1[13 - i] = preq;
pres >>= 8;
preq >>= 8;
}
p1[14] = rat ? 1 : 0;
p1[15] = iat ? 1 : 0;
/* create p2 */
for (i = 0; i < 6; i++) {
p2[9 - i] = ia;
p2[15 - i] = ra;
ia >>= 8;
ra >>= 8;
}
/* p1 ^= r */
for (i = 0; i < SM_BLOCK_LEN; i++)
p1[i] ^= r[i];
/* out = e(k, p1) */
smEncrypt(out, p1, k);
/* p2 ^= out */
for (i = 0; i < SM_BLOCK_LEN; i++)
p2[i] ^= out[i];
/* out = e(k, p2) */
smEncrypt(out, p2, k);
}
/*
* FUNCTION: smCalcDivMask
* USE: Perform the calculation of the DIV mask value
* PARAMS: mask - a 16-bit DIV mask value
* k - the 128-bit DHK, k value as per spec
* r - the 64-bit Rand, r value as per spec
* RETURN: NONE
* NOTES: The SM docs call this the dm() function
*/
static void smCalcDivMask(uint16_t *mask, const uint8_t *k, uint64_t r)
{
uint8_t out[SM_BLOCK_LEN] = {0,};
uint8_t paddingR[SM_BLOCK_LEN] = {0,};
uint8_t i;
if (!mask || !k)
return;
for (i = SM_BLOCK_LEN - 1; i >= SM_BLOCK_LEN - sizeof(r); --i) {
paddingR[i] = r;
r >>= 8;
}
smEncrypt(out, k, paddingR);
/* truncate to 16 bits by taking the least significant 16 bits of the output of e() */
*mask = 0;
*mask = out[SM_BLOCK_LEN - 2];
*mask <<= 8;
*mask |= out[SM_BLOCK_LEN - 1];
}
/*
* FUNCTION: smPrepPreqPrsp
* USE: Prepare the pairing request command or the pairing response
* command by reconstructing fields into a uint64_t value
* PARAS: cmd - output
* reqRsp - command type
* ioCap - IO capability
* hasOob - OOB data flag
* authReq - security properties
* maxKeySz - maximum key size
* initKeyDistr - initiator key distribution
* rspKeyDistr - responder key distribution
* RETURN: success
* NOTES:
*/
static bool smPrepPreqPrsp(
uint64_t *cmd, uint8_t reqRsp, uint8_t ioCap, bool hasOob, uint8_t authReq, uint8_t maxKeySz,
uint8_t initKeyDistr, uint8_t rspKeyDistr)
{
uint8_t oob = hasOob;
if (!cmd)
return false;
if (reqRsp != SM_PAIRING_REQ && reqRsp != SM_PAIRING_RSP)
return false;
*cmd = utilGetBE8(&rspKeyDistr);
*cmd = *cmd << 8 | utilGetBE8(&initKeyDistr);
*cmd = *cmd << 8 | utilGetBE8(&maxKeySz);
*cmd = *cmd << 8 | utilGetBE8(&authReq);
*cmd = *cmd << 8 | utilGetBE8(&oob);
*cmd = *cmd << 8 | utilGetBE8(&ioCap);
*cmd = *cmd << 8 | reqRsp;
return true;
}
/*
* FUNCTION: smPrepInitRspAddr
* USE: Prepare the initiator device address or the responding device
* address by reconstructing a struct bt_addr into an uint64_t value
* PARAS: a - output uint64_t
* at - output address types (public or random)
* addr - address
* RETURN: success
* NOTES:
*/
static bool smPrepInitRspAddr(uint64_t *a, uint8_t *at, const struct bt_addr *addr)
{
int i;
if (!addr)
return false;
*at = (addr->type == BT_ADDR_TYPE_LE_RANDOM) ? 1 : 0;
for (i = sizeof(addr->addr) - 1, *a = 0; i >= 0; --i)
*a = ((*a) << 8) | addr->addr[i]; /* addr is stored in [0]->[5] : LSO -> MSO */
return true;
}
/*
* FUNCTION: smGenSTK
* USE: Generate a 128-bit Short Term Key, mask it with the negotiated key size and store it
* in the given instance
* PARAS: inst - instance
* RETURN: success
* NOTES:
*/
static bool smGenSTK(struct smInstance *inst)
{
uint8_t i, maskLen;
if (!inst) {
logw("%s(): Invalid instance\n", __func__);
return false;
}
smCalcKey((uint8_t *)inst->stk.key, (uint8_t *)inst->tk.key,
inst->isInitiator ? inst->peerRandNum.rand : inst->myRandNum.rand,
inst->isInitiator ? inst->myRandNum.rand : inst->peerRandNum.rand);
maskLen = SM_MAX_KEY_LEN -
(inst->myMaxKeySz > inst->peerMaxKeySz ? inst->peerMaxKeySz : inst->myMaxKeySz);
for (i = 0; i < maskLen; ++i)
inst->stk.key[i] = 0x00;
return true;
}
/*
* FUNCTION: smGenTK
* USE: Generate a 128-bit Temporary Key and store it in the given instance
* PARAS: inst - instance
* RETURN: success
* NOTES:
*/
static bool smGenTK(struct smInstance *inst)
{
uint8_t i;
uint32_t passkey, p;
if (!inst) {
logw("%s(): Invalid instance\n", __func__);
return false;
}
if ((inst->alg = smSelPairAlg(inst)) == SM_ALG_UNKNOWN)
return false;
logd("Pairing method chosen: 0x%02X\n", inst->alg);
/* generate TK */
switch(inst->alg) {
case SM_ALG_JUST_WORK:
memset(inst->tk.key, 0, sizeof(inst->tk.key));
inst->isMitmSafe = false;
break;
case SM_ALG_PASS_KEY:
if (inst->isInitiator) {
smGenRandNum((uint8_t *)&passkey, sizeof(passkey));
passkey %= 1000000; /* the range is between 000,000 and 999,999 */
for (i = sizeof(inst->tk.key) - 1, p = passkey; i > sizeof(inst->tk.key) - 4; --i, p >>= 8)
inst->tk.key[i] = p;
smNotifyPasskeyDisplayObserver(&inst->peerAddr, true, passkey);
} else {
smNotifyPasskeyRequestObserver(&inst->peerAddr, true);
}
break;
case SM_ALG_OOB: /* TODO(mcchou): retrieve OOB data from OOB source */
logw("%s(): Pairing method %d not implemented\n", __func__, inst->alg);
inst->isMitmSafe = true;
return false;
default:
logw("%s(): Unknown pairing algorithm 0x%02X", __func__, inst->alg);
return false;
}
if(!persistAddDevNumber(&inst->peerAddr, PERSIST_NUM_TYPE_SM_MITM_PROTECT,
inst->isMitmSafe ? SM_AUTH_MITM_PROTECT : SM_UNAUTH_NO_MITM_PROTECT)) {
logw("%s(): Failed to store MITM security property\n", __func__);
return false;
}
return true;
}
/*
* FUNCTION: smGenConfVal
* USE: Generate a 128-bit random number and store it in the given instance. This should be
* called after calling smGenTK.
* PARAS: rand - the output random number
* inst - instance
* check - indicate whether this is confirm value check or not, if true, peer random
* value will be used as the input of confirm value calculation; otherwise the
* local random value will be used.
* RETURN: success
* NOTES:
*/
static bool smGenConfVal(struct smPairConf *conf, const struct smInstance *inst, bool check)
{
uint64_t pReqCmd = 0, pRspCmd = 0, ia = 0, ra = 0;
uint8_t iat = 0, rat = 0;
if (!inst) {
logw("%s(): Invalid instance\n", __func__);
return false;
}
/* prepare params for calculation of confirm value */
if (inst->isInitiator) {
if (!smPrepPreqPrsp(&pReqCmd, SM_PAIRING_REQ, mIoCap, mHasOob, inst->myAuthReq,
inst->myMaxKeySz, inst->myKeyDistrReq, inst->peerKeyDistrReq) ||
!smPrepPreqPrsp(&pRspCmd, SM_PAIRING_RSP, inst->peerIoCap, inst->peerHasOob,
inst->peerAuthReq, inst->peerMaxKeySz, inst->myKeyDistr,
inst->peerKeyDistr) ||
!smPrepInitRspAddr(&ia, &iat, &inst->myAddr) ||
!smPrepInitRspAddr(&ra, &rat, &inst->peerAddr))
goto fail;
} else {
if (!smPrepPreqPrsp(&pReqCmd, SM_PAIRING_REQ, inst->peerIoCap, inst->peerHasOob,
inst->peerAuthReq, inst->peerMaxKeySz, inst->myKeyDistrReq,
inst->peerKeyDistrReq) ||
!smPrepPreqPrsp(&pRspCmd, SM_PAIRING_RSP, mIoCap, mHasOob, inst->myAuthReq,
inst->myMaxKeySz, inst->myKeyDistr, inst->peerKeyDistr) ||
!smPrepInitRspAddr(&ia, &iat, &inst->peerAddr) ||
!smPrepInitRspAddr(&ra, &rat, &inst->myAddr))
goto fail;
}
smCalcConfVal(conf->confirmVal, (uint8_t *)inst->tk.key,
check ? inst->peerRandNum.rand : inst->myRandNum.rand,
pRspCmd, pReqCmd, iat, ia, rat, ra);
return true;
fail:
logw("%s(): Preparation error\n", __func__);
return false;
}
/*
* FUNCTION: smGenRandNum
* USE: Generate a random number
* PARAS: out - the output random number
* numLen - the wanted byte length of the random number where the max length is 16
* RETURN: success
* NOTES:
*/
static bool smGenRandNum(uint8_t *out, uint8_t numLen)
{
int r = 0;
uint16_t i, rSz, cp, rem;
struct timespec ts;
if (!out) {
logw("%s(): Invalid destination for random number\n", __func__);
return false;
}
if (!timespec_get(&ts, TIME_UTC)) {
logd("%s(): Filed to retrieve time\n", __func__);
return false;
}
srandom(ts.tv_nsec ^ ts.tv_sec);
rem = numLen > SM_BLOCK_LEN ? SM_BLOCK_LEN : numLen;
for (i = 0, rSz = sizeof(r); rem > 0; i += cp, rem -= cp) {
r = random();
cp = rem > rSz ? rSz : rem;
memcpy(&out[i], &r, cp);
}
return true;
}
/*
* FUNCTION: smGenLtk
* USE: Generate a 128-bit Long Term Key and store it in database
* PARAS: inst - the instance
* RETURN: success if LTK is valid and stored
* NOTES: We use Database Lookup approach for key management.
*/
static bool smGenLtk(struct smInstance *inst)
{
uint8_t key[HCI_LE_KEY_LEN] = {0,};
if (!inst || !smGenRandNum(key, HCI_LE_KEY_LEN))
return false;
return persistAddDevKey(&inst->peerAddr, KEY_TYPE_MY_LTK, key);
}
/*
* FUNCTION: smGenEdiv
* USE: Generate a 16-bit EDIV
* PARAS: out - where EDIV is stored
* peerAddr - the peer adress
* div - if this is a EDIV value check, DIV needs to be a valid number; otherwise a random
* DIV value is generated and stored.
* rand - random number
* check - indicate whether this is an EDIV value check or not. if true, we use
* previously stored DIV value to calculate EDIV; otherwise a random DIV value
* is generated and stored.
* RETURN: true if EDIV is valid; false otherwise
* NOTES: We use Database Lookup approach for key management.
*/
static bool smGenEdiv(uint16_t *out, const struct bt_addr *peerAddr, uint16_t div, uint64_t rand,
bool check)
{
uint8_t dhk[SM_BLOCK_LEN] = {0,};
uint16_t mask = 0;
if (!check) {
/* generate a 16-bit DIV value */
if (!smGenRandNum((uint8_t *)&div, sizeof(div)) ||
!persistAddDevNumber(peerAddr, PERSIST_NUM_TYPE_SM_MY_DIV, div))
return false;
}
/* generate a 16-bit DIV mask value */
if (!persistGetDevKey(NULL, KEY_TYPE_DHK, dhk))
return false;
smCalcDivMask(&mask, dhk, rand);
*out = mask^div;
return true;
}
/*
* FUNCTION: smAddressHash
* USE: Perform 24bit->24bit hash for address resolution
* PARAMS: r - the 24 bits to hash
* key - the key
* RETURN: the 24-bit hash
* NOTES: The SM docs call this the ah() function
*/
static uint32_t smAddressHash(uint32_t r, const uint8_t *key)
{
uint8_t in[SM_BLOCK_LEN] = {0,}, out[SM_BLOCK_LEN];
utilSetBE24(in + 13, r);
smEncrypt(out, in, key);
return utilGetBE24(out + 13);
}
/* a simple AES-128 implementation - we use it rarely enough that even a nonoptimal version will do */
static void smEncryptSubBytes(uint8_t *bytes, uint8_t num)
{
static const uint8_t sbox[] = {
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
};
uint8_t i;
for (i = 0; i < num; i++)
bytes[i] = sbox[bytes[i]];
}
static void smEncryptShiftRows(uint8_t *state)
{
uint8_t i, j;
uint8_t tmp[4];
for (i = 0; i < 4; i++) {
for(j = 0; j < 4; j++)
tmp[j] = state[i * 4 + j];
for(j = 0; j < 4; j++)
state[i * 4 + j] = tmp[(i + j) & 3];
}
}
static uint8_t smEncryptMul(uint8_t vin, uint8_t by)
{
uint16_t v = vin;
switch(by){
case 1:
break;
case 2:
v <<= 1;
break;
case 3:
v = (v << 1) ^ v;
break;
}
if (v & 0x100)
v ^= 0x1B;
return v;
}
static void smEncryptMixColumns(uint8_t *state)
{
uint8_t i, j, k, ret[SM_BLOCK_LEN];
static const uint8_t matrix[] = {2,3,1,1,1,2,3,1,1,1,2,3,3,1,1,2};
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
ret[i * 4 + j] = 0;
for(k = 0; k < 4; k++)
ret[i * 4 + j] ^= smEncryptMul(state[k * 4 + j], matrix[i * 4 + k]);
}
}
for (i = 0; i <SM_BLOCK_LEN; i++)
state[i] = ret[i];
}
static void smEncryptAddRoundKey(uint8_t *state, const uint8_t *keyPos)
{
uint8_t i, j;
for (i = 0; i < 4; i++, keyPos += 40)
for(j = 0; j < 4; j++)
*state++ ^= *keyPos++;
}
static void smEncryptExpandKey(uint8_t *expanded, const uint8_t *key)
{
static const uint8_t rcon[] = {1,0,0,0, 2,0,0,0, 4,0,0,0, 8,0,0,0, 16,0,0,0, 32,0,0,0, 64,0,0,0,
128,0,0,0, 27,0,0,0, 54,0,0,0};
uint8_t i, j, temp[4];
for(i = 0; i < 4; i++)
for(j = 0; j < 4; j++)
expanded[i + 44 * j] = key[i * 4 + j];
for (i = 4; i < 44; i++) {
for(j = 0; j < 4; j++)
temp[j] = expanded[i - 1 + 44 * j];
if (!(i & 3)) {
j = temp[0];
temp[0] = temp[1];
temp[1] = temp[2];
temp[2] = temp[3];
temp[3] = j;
smEncryptSubBytes(temp, 4);
for (j = 0; j < 4; j++)
temp[j] ^= rcon[(i &~ 3) - 4 + j];
}
for(j = 0; j < 4; j++)
expanded[i + 44 * j] = expanded[i - 4 + 44 * j] ^ temp[j];
}
}
/*
* FUNCTION: smEncrypt
* USE: Perform an AES-128 encryption
* PARAMS: dst - place to store result (SM_ENCRYPT_DATA_LEN bytes)
* src - the plain text (SM_ENCRYPT_DATA_LEN bytes)
* key - the key (SM_ENCRYPT_DATA_LEN bytes)
* RETURN: NONE
* NOTES: The SM docs call this the e() function
*/
static void smEncrypt(uint8_t* dst, const uint8_t *src, const uint8_t *key)
{
uint8_t expandedKey[176];
uint8_t state[SM_BLOCK_LEN];
uint8_t i, j;
for(i = 0; i < 4; i++)
for(j = 0; j < 4; j++)
state[i + 4 * j] = *src++;
smEncryptExpandKey(expandedKey, key);
smEncryptAddRoundKey(state, expandedKey + 0);
for (i = 1; i < 10; i++) {
smEncryptSubBytes(state, SM_BLOCK_LEN);
smEncryptShiftRows(state);
smEncryptMixColumns(state);
smEncryptAddRoundKey(state, expandedKey + i * 4);
}
smEncryptSubBytes(state, SM_BLOCK_LEN);
smEncryptShiftRows(state);
smEncryptAddRoundKey(state, expandedKey + i * 4);
for(i = 0; i < 4; i++)
for(j = 0; j < 4; j++)
*dst++ = state[i + 4 * j];
}
/*
* FUNCTION: smEncryptForTest
* USE: Perform an AES-128 encryption
* PARAMS: dst - place to store result (SM_ENCRYPT_DATA_LEN bytes)
* src - the plain text (SM_ENCRYPT_DATA_LEN bytes)
* key - the key (SM_ENCRYPT_DATA_LEN bytes)
* RETURN: NONE
* NOTES: The SM docs call this the e() function
*/
void smEncryptForTest(uint8_t* dst, const uint8_t *src, const uint8_t *key)
{
smEncrypt(dst, src, key);
}
/*
* FUNCTION: smCalcConfValForTest
* USE: Perform the calculation of the confirmation value
* PARAMS: out - store the result here
* k - the k value as per spec
* r - the r value as per spec
* pres - the pres value as per spec, only bottom 56 bit used
* preq - the pres value as per spec, only bottom 56 bit used
* iat - the iat value as per spec
* ia - the ia value as per spec, only bottom 48 bits used
* rat - the rat value as per spec
* ra - the ra value as per spec, only bottom 48 bits used
* RETURN: NONE
* NOTES: The SM docs call this the c1() function
*/
void smCalcConfValForTest(uint8_t* out, const uint8_t *k, const uint8_t *r, uint64_t pres,
uint64_t preq, bool iat, uint64_t ia, bool rat, uint64_t ra)
{
smCalcConfVal(out, k, r, pres, preq, iat, ia, rat, ra);
}
/*
* FUNCTION: smCalcKeyForTest
* USE: Perform the calculation of a key
* PARAMS: out - store the result here
* k - the k value as per spec
* p1 - the p1 value as per spec
* p2 - the p2 calue as per spec
* RETURN: NONE
* NOTES: The SM docs call this the s1() function
*/
void smCalcKeyForTest(uint8_t *out, const uint8_t *k, const uint8_t *r1, const uint8_t *r2)
{
smCalcKey(out, k, r1, r2);
}
/*
* FUNCTION: smAddressHashForTest
* USE: Perform 24bit->24bit hash for address resolution
* PARAMS: r - the 24 bits to hash
* key - the key
* RETURN: the 24-bit hash
* NOTES: The SM docs call this the ah() function
*/
uint32_t smAddressHashForTest(uint32_t r, const uint8_t *key)
{
return smAddressHash(r, key);
}