GAP Bond Manager是一个可配置的模块,使用Bond manager后应用程序可以减少大部分安全机制。下表列出了术语。
术语 | 描述 |
---|---|
配对(Pairing) | 交换密钥的过程 |
加密(Encryption) | 0x02 |
认证(Authentication) | 使用中间人(MITM)保护完成的配对 |
Bonding | 将密钥存储在非易失性存储器中 |
授权(Authorization) | 除了认证之外,还需要额外的应用级密钥交换 |
OOB(Out of band) | 密钥不是通过空中交换,而是通过串行端口或NFC等其他来源进行交换。这也提供了MITM保护。 |
MITM(Man in the Middle protection) | 这可以防止攻击者收听通过空中传输的密钥来破坏加密。 |
只是工作(Just work) | 配对方法,其中密钥在没有MITM的情况下通过空中传送 |
由于蓝牙是无线空中传输,所以数据很容易被窃听,于是蓝牙在数据发送和接收过程中使用了AES加密,关于AES算法超出了本文的范围,大家只需要了解该算法是通过一组密钥(假设密钥是123456)。然后接收方和发送方都使用这个密钥对数据进行加密和解密,传输在空中的数据都是经过加密了的数据,这样安全性就大大提高。AES算法虽然目前没有暴力破解的方法,但是攻击者一旦知道了该密钥就能轻松获取数据,所以密钥的交换一般不通过空中传输,所以蓝牙设计者设计了两个解决方案:OOB和MITM,MITM其实就是在一端生成密钥并显示出来,另一端手动的输入密钥,这样完成密钥交换。OOB和MITM类似,但是密钥的交换通过NFC或串口等形式进行交换。这样攻击者就很难获取密钥进行破解了。当密钥完成交换之后会有Bonding过程,主要目的是保存密钥,下次连接的时候就不需要输入密钥从而直接连接。
所以在程序中使用GAPBondMgr实现,过程如下:
执行所有这些步骤是不必要的。例如,两个设备可以选择配对而不绑定(Bond)。
有四种类型的配对模型,每种模型都在GAPBondMgr中详细描述,用于不同配对模式:
其中Just Work 不能防止攻击者攻击,用于配对没有显示没有输入的设备,主动发起连接即可配对,用户看不到配对过程,例如连接蓝牙耳机。
PassKey Enter 配对目标输入一个在本地设备上显示的6位数字,输入正确即可配对,并可以防止攻击。使用场景:一端设备可以显示,另一端设备可以输入。
Numeric Comparison 配对双方都显示一个6位的数字,由用户来核对数字是否一致,并输入Yes/No,两端Yes表示一致即可配对,可以防止中间人攻击。使用场景:两端设备可以弹出6位十进制数,并且有yes和no按钮。
Out of Band 一般很少使用,通过串口或NFC交换密钥,例如NFC蓝牙音箱。
所以开发者需要根据自己设备IO输入输出能力选择合适的配对模式。下面给出一些配置参考。
如果两个设备都支持安全连接,使用下图决定下一步配置。
如果至少有一个设备不支持安全连接,使用下图决定下一步。
下表根据IO Capabilities再对模式进行细分
本节介绍应用程序必须做什么来配置,启动和使用GAPBondMgr。GAPRole处理一些GAPBondMgr功能。GAPBondMgr被定义在gapbondmgr.c 和gapbondmgr.h。BLE Stack API参考(GAPBondMgr部分)描述了完整的API,包括命令,可配置参数,事件和回调。
使用GAPBondMgr模块的一般步骤如下:
配置堆栈以包括GAPBondMgr功能,如果需要安全连接。在堆栈项目build_config.opt中定义以下内容:-DGAP_BOND_MGR
堆栈还必须配置为使用1或2个SNV页面,通过在堆栈项目中定义OSAL_SNV=1或OSAL_SNV=2作为预处理器定义的符号。
如果使用安全连接,则PDU大小必须大于等于69.这可以通过在应用程序项目中定义以下预处理器符号来设置MAX_PDU_SIZE=69。此外, 可用于安全连接的最小堆大小为3690。
通过根据需要初始化其参数来配置GAPBondMgr。有关具有描述功能的参数的完整列表,请参阅BLE Stack API参考(GAPBondMgr部分)。有关不同配对模式的GAPBondMgr示例中的各种配对/绑定模式的示例。
使用GAPBondMgr注册应用程序回调,以便应用程序可以与GAPBondMgr通信并通知事件。
// Register with bond manager after starting device
GAPBondMgr_Register(&bondmanager_callbacks);
一旦GAPBondMgr被配置,它主要从应用程序的角度自主运行。当建立连接时,根据初始化期间设置的配置参数启动配对和绑定,并根据需要通过定义的回调与应用程序进行通信。
可以随时从应用程序中设置一些参数和异步调用的函数。有关详细信息,请参阅BLE Stack API参考(GAPBondMgr部分)。
GAPBondMgr与此应用程序之间的大多数通信都是通过在步骤5中注册的回调发生的。 下图是GAPBondMgr的流程图示例,通知应用程序配对已经完成。对于各种其他事件的发生也是用相同的方法,并将在后面部分进行扩展。
本节提供了上节提到的几种配对模式在应用程序中怎么通过修改参数进行配置,本节提供了程序消息框图,这些模式假设安全模式具有可以接受的IO输入/输出能力。这些例子只考虑配对方面,关于bond实现请参阅下一节,这里的代码片段不是完整的功能示例,仅用于说明目的。
配对设置FALSE后,BLE堆栈会自动拒绝任何配对尝试。如下配置GAPBondMgr以禁用配对:
uint8 pairMode = GAPBOND_PAIRING_MODE_NO_PAIRING ;
GAPBondMgr_SetParameter (GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &pairMode );
Just Works配对允许加密而不需要MITM身份验证,并且容易受到攻击。Just Works配对可以是LE Legacy或Secure Connections配对。GAPBondMgr不需要任何额外的输入从应用程序对于Just Work配对。配置GAPBondMgr为Just Works配对如下。
uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE ;
uint8_t mitm = FALSE ;
GAPBondMgr_SetParameter ( GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &pairMode );
GAPBondMgr_SetParameter ( GAPBOND_MITM_PROTECTION , sizeof (uint8_t ), &mitm );
下图描述了GAPBondMgr和Just Works配对应用程序之间的交互。如图所示,应用程序GAPBOND_PAIRING_STATE_STARTED一旦发送配对请求就收到一个事件,GAPBOND_PAIRING_STATE_COMPLETE一旦配对过程完成,就会发生一个事件。此时,链接被加密。
密钥条目是一种可以防止MITM攻击的身份验证配对。它可以是LE Legacy配对或安全连接配对。在这种配对方法中,一个设备显示6位密码,另一个设备输入密码。如选择配对模式所述,IO功能决定哪个设备执行哪个角色。GAPBondMgr在启动时注册的密码回调用于输入或显示密码。以下是启动Passcode Entry配对的示例,其中显示密码。
定义密码回调
// Bond ManagerCB
static gapBondCBs_t security_examples_central_bondCB =
{
(pfnPasscodeCB_t )security_examples_central_passcodeCB , //密码回调
security_examples_central_pairStateCB //配对状态回调
};
static void security_examples_central_passcodeCB (uint8_t * deviceAddr , uint16_t connHandle , uint8_t uiInputs , uint8_t uiOutputs , uint32_t numComparison )
{
gapPasskeyNeededEvent_t * pData ; //为密码事件分配空间
if ((pData = ICall_malloc (sizeof (gapPasskeyNeededEvent_t ))))
{
memcpy (pData - > deviceAddr , deviceAddr , B_ADDR_LEN );
pData - > connectionHandle = connHandle ;
pData - > uiInputs = uiInputs ;
pData - > uiOutputs = uiOutputs ;
//排队事件
security_examples_central_enqueueMsg (SEC_PASSCODE_NEEDED_EVT , 0 , (uint8_t * ) pData );
}
}
配置GAPBondMgr
uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE ;
uint8_t mitm = TRUE ;
GAPBondMgr_SetParameter (GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE ;
GAPBondMgr_SetParameter (GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &mitm );
处理密码回调并向协议栈发送响应
static void security_examples_central_processPasscode (uint16_t connectionHandle , gapPasskeyNeededEvent_t * pData )
{
if (pData - > uiInputs ) //如果我们要输入passkey
{
passcode = 111111 ;
//发送密码响应
GAPBondMgr_PasscodeRsp (connectionHandle , SUCCESS , passcode );
}
else if (pData - > uiOutputs ) //如果我们要显示密码
{
passcode = 111111 ;
DISPLAY_WRITE_STRING_VALUE (“passcode:%d” , passcode, LCD_PAGE4 );
//发送密码响应
GAPBondMgr_PasscodeRsp (connectionHandle , SUCCESS , passcode );
}
}
根据uiInputs和uiOutputs从GAPBondMgr返回的内容,必须显示或输入密码。然后使用GAPBondMgr_PasscodeRsp()将密码发送到GAPBondMgr ,以便配对可以继续。在这种情况下,密码静态设置为111111.在实际产品中,密码很可能是随机生成的,设备必须为用户提供输入密码的方式,然后使用GAPBondMgr_PasscodeRsp()发送给GAPBondMgr 。GAPBondMgr和应用程序之间的一个示例交互如图所示。
数字比较是一种经过身份验证的配对,可以防止MITM攻击。只能作为安全连接配对; 不是LE遗产。对于数字比较配对,两个设备都显示6位数的代码。每个设备必须通过按钮或其他一些无输入来指示代码是否匹配。GAPBondMgr在启动时注册的密码回调用于显示6位数代码。以下是启动显示密码的数字比较配对的示例。必须适当设置IO功能才能选择数值比较(即Yes-No)。
定义密码回调来显示代码。
// Bond Manager回调
static gapBondCBs_t SimpleBLECentral_bondCB =
{
(pfnPasscodeCB_t )SimpleBLECentral_passcodeCB , //密码回调
SimpleBLECentral_pairStateCB //配对状态回调
};
static void SimpleBLECentral_passcodeCB (uint8_t * deviceAddr , uint16_t connHandle , uint8_t uiInputs , uint8_t uiOutputs , uint32_t numComparison )
{
gapPasskeyNeededEvent_t * pData ;
//为密码事件分配空间
if ((pData = ICall_malloc (sizeof (gapPasskeyNeededEvent_t ))))
{
memcpy (pData - > deviceAddr , deviceAddr , B_ADDR_LEN );
pData - > connectionHandle = connHandle ;
pData - > numComparison = numComparison ;
//排队事件
security_examples_central_enqueueMsg (SEC_PASSCODE_NEEDED_EVT , 0 ,
配置GAPBondMgr
uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE ;
uint8_t scMode = GAPBOND_SECURE_CONNECTION_ONLY ;
uint8_t mitm = TRUE ;
uint8_t ioCap = GAPBOND_IO_CAP_DISPLAY_YES_NO ;
GAPBondMgr_SetParameter (GAPBOND_IO_CAPABILITIES , sizeof (uint8_t ), &ioCap );
GAPBondMgr_SetParameter (GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &pairMode );
GAPBondMgr_SetParameter (GAPBOND_MITM_PROTECTION , sizeof (uint8_t ), &mitm );
GAPBondMgr_SetParameter (GAPBOND_SECURE_CONNECTION , sizeof (uint8_t ), &scMode );
处理密码回调和显示代码
static void SimpleBLECentral_processPasscode (uint16_t connectionHandle , gapPasskeyNeededEvent_t * pData )
{
if (pData - > numComparison ) //数值比较
{
//显示密码
DISPLAY_WRITE_STRING_VALUE (“Num Cmp:%d” , pData - > numComparison , LCD_PAGE4 );
}
}
接受用户输入Yes-No,并发送回应GAPBondMgr。
if (keys & KEY_RIGHT )
{
GAPBondMgr_PasscodeRsp (connHandle , SUCCESS , TRUE );
DISPLAY_WRITE_STRING (“Codes Match!” , LCD_PAGE5 );
return;
}
在这种情况下,通常接受密码的GAPBondMgr_PasscodeRsp的第三个参数被重载,以向堆栈发送TRUE,以指示代码匹配并继续配对。数字比较的过程如图56所示。
可以通过GAPBOND_BONDING_ENABLED参数对任何类型的配对启用或禁用绑定,并在配对过程完成后进行绑定 。要启用绑定,请按如下所示配置GAPBondMgr:
uint8_t bonding = TRUE ;
GAPBondMgr_SetParameter (GAPBOND_BONDING_ENABLED , sizeof (uint8_t ), &bonding );
启用绑定后,GAPBondMgr将配对过程中传输的长期密钥存储到SNV。有关详细信息,请参阅GAPBondMgr和SNV。完成后,通过GAPBOND_PAIRING_STATE_COMPLETE事件GAPBOND_PAIRING_STATE_BOND_SAVED通知应用程序。最初连接,配对和绑定时只传递给应用程序对状态回调。为了将来连接到绑定设备,安全密钥从闪存加载,从而跳过配对过程。在这种情况下,只 GAPBOND_PAIRING_STATE_BONDED传递给应用程序对状态回调。这在图57中示出。
本节介绍GAPBondMgr如何使用SNV闪存区域来存储bond信息。有关SNV本身的更多信息,请参阅内存管理 Flash部分。可以存储的bond数量由GAP_BONDINGS_MAX定义设置,默认情况下在gapbondmgr.h中设置为10。GAPBondMgr的功能在不存在可用的绑定时会有所不同。有关参数的更多信息,请参阅BLE Stack API参考(GAPBondMgr部分)。GAPBOND_LRU_BOND_REPLACEMENT:如果此参数设置为false,则不删除bond从而不能添加任何更多的bond。如果参数设置为true,则删除最近最少使用的bond为新bond腾出空间。
bond记录:这个结构体包括设备的地址,地址类型,隐私重新连接地址和状态标志
typedef struct
{
uint8 publicAddr[B_ADDR_LEN]; // Peer's address
uint8 publicAddrType; // Peer's address type
uint8 reconnectAddr[B_ADDR_LEN]; // Privacy Reconnection Address
uint8 stateFlags; // State flags: @ref GAP_BONDED_STATE_FLAGS
} gapBondRec_t;
客户端特征配置 Client Characteristic Configurations(CCC):存储在每个条目中的CCC数量由GAP_CHAR_CFG_MAXdefine 设置。默认设置为4。每个CCC由4个字节组成,定义如下:
typedef struct
{
uint16 attrHandle ; //属性句柄
uint8 value ; //该设备的属性值
} gapBondCharCfg_t ;
本地长期密钥(LTK)信息:这存储本地设备的加密信息。这包括28个字节,其组成如下:
typedef struct
{
uint8 LTK [ KEYLEN ]; //长期密钥(LTK)
uint16 div ; // lint -e754 // LTK eDiv
uint8 rand [ B_RANDOM_NUM_SIZE ]; // LTK随机数
uint8 keySize ; // LTK key size
} gapBondLTK_t ;
连接设备长期密钥信息:这将存储连接的设备的加密信息。这也是一个gapBondLTK_t,包含28个字节。
连接的设备身份解析密钥(IRK):存储配对期间生成的IRK。这是一个16字节的数组。
连接的设备符号解析密钥(SRK):这将存储配对期间生成的SRK。这是一个16字节的数组。
连接设备标志计数器:这将存储配对期间生成的符号计数器。这是一个4字节的字。
文章所有代码、工具、文档开源。加入我们QQ群 591679055获取更多支持,共同研究CC2640R2F&BLE5.0。