人工智能如何赋能物联网?EDGE AI →

Bluetooth LE安全机制以及在nRF Connect SDK中的应用 2024-08-01 Gao Jun, Nordic Semiconductor

本文将详细介绍低功耗蓝牙的安全机制以及在NCS中的应用。

一. 低功耗蓝牙Bluetooth LE安全必须应对 的3 种常见攻击类型:

身份追踪,被动窃听(嗅探)和主动窃听(中间人MITM)

  • 身份跟踪:利用蓝牙地址来跟踪设备。这可以通过使用随机变化的可解析私有地址(Resolvable Random Private Address)来避免,其中只有绑定/可信设备才能解析私有地址。 IRK(身份解析密钥)用于生成和解析私有地址。

  • 被动窃听(嗅探):就是攻击者偷偷窃听设备之间的数据传输。 可以通过加密对等设备之间的通信来防止这种情况。 这里的挑战是对等设备如何生成和/或交换密钥以建立安全地加密连接。 Bluetooth LE 传统配对的密码生成和交换存在不足,然后提出了LE secure Connections。

  • 主动窃听(中间人MITM):在主动窃听(或中间人)攻击中,攻击者冒充合法设备来欺骗另外两个设备,与他们建立连接。 为了防止这种情况,我们需要确保我们正在通信的设备实际上是我们想要与之通信的设备,而不是未经身份验证的设备。可靠的身份验证过程可以有效的防止中间人攻击。中间人攻击的示例,如下图:

    Bluetooth LE安全机制

二. BLE安全相关术语:

  • 临时密钥 (Temporary Key,TK): 临时密钥的生成取决于所选的配对方法。每次配对过程发生时都会生成 TK(仅用于传统配对)。
  • 短期密钥 (Short Term Key,STK): 此密钥由设备之间交换的 TK 生成。每次配对过程发生时都会生成 STK,并用于加密当前连接中的数据(仅用于传统配对)。
  • 长期密钥 (LongTerm Key,LTK): 此密钥在传统配对的第三阶段和 LE 安全连接的第二阶段期间生成和存储,是一个 128 位密钥,用于生成session key。它存储在绑定的两个设备中的每一个上,并用于两个设备之间的后续加密连接。
  • 加密多样化(Encrypted Diversifier,EDIV)和 随机值(Random Number,Rand): 这两个值用于创建和识别 LTK。它们也会在绑定过程中储存起来。
  • 身份解析密钥 (ldentity Resolving Key,IRK): 用于生成和解析随机私有可解析地址(Resolvable Random Private Address)。该密钥对于每个设备都是唯一的,因此主设备的 IRK 将存储在从设备端,而从设备的IRK 将存储在主设备端。
  • 连接签名解析密钥(Connection Signature Resolving Key,CSRK): 如果启用,该密钥将存储在两个绑定设备中的每一个上,用于对数据进行签名并验证附加到另一端数据的签名(used in signing data sent over an unencrypted link )。
  • ECDH(Elliptic-curve Diffie–Hellman)椭圆曲线diffie-hellman密钥交换算法

三. 可解析随机私有地址(Resolvable Random Private Address)

可解析随机私有地址可以防止被未知设备扫描和追踪

Bluetooth LE安全机制

如图,由两部分组成:高位24bits是随机数部分,其中最高两个bit为"10",用于标识地址类型;低位24bits是随机数和IRK经过hash运算得到的hash值,运算的公式为hash = ah(IRK, prand)。

当对端Bluetooth LE设备扫描到该类型的蓝牙地址后,会使用保存在本机的IRK,和该地址中的prand,进行同样的hash运算,并将运算结果和地址中的hash字段比较,相同的时候,才进行后续的操作。这个过程称作resolve(解析),这也是Non-resolvable private address/Resolvable private address命名的由来。以T_GAP(private_addr_int)为周期,定时更新。哪怕在广播、扫描、已连接等过程中,也可能改变。

四. Bluetooth LE安全等级

  1. Bluetooth LE 在安全模式 1 中定义了 4 个安全级别:
  • 1 级:无安全性(开放文本,意味着无需身份验证且无需加密)
  • 2级:未经身份验证的配对加密
  • 3 级:经过身份验证的配对加密
  • 4 级:经过身份验证的 LE 安全连接配对加密(LE Secure Connections)

安全等级1代表没有加密。安全等级2,3对应传统配对(Legacy pairing),安全等级4对应LE安全连接(LE Secure Connections)

  1. 传统配对(Legacy pairing) 和LE安全连接(LE Secure Connections)

在蓝牙 v4.2 之前,传统配对是Bluetooth LE中唯一可用的配对方法。它非常简单,但存在风险,因为用于加密链接的短期密钥 (STK) 很容易被破解。在传统配对中使用 Just Works 时,TK 设置为 0,这在窃听或中间人 (MITM) 方面不提供任何保护。攻击者可以轻松暴力破解 STK 并窃听连接,而且也没有办法验证设备。

使用密钥输入会好一些,因为 TK 现在是一个 6 位数字,由用户在设备之间传递。例如,其中一个设备在其屏幕上显示该数字,用户使用键盘在其他设备上输入该数字。不幸的是,攻击者可以轻松嗅探正在交换的值,然后很容易找出 STK 并解密连接。即使无法直接确定 TK,也可以通过尝试所有 999999 种组合轻松破解密钥。

在带外配对(OOB)中,TK 通过Bluetooth LE以外的其他方式进行交换,例如使用 NFC。此方法支持使用最大 128 位的 TK,从而提高了连接的安全性。交换期间使用的 OOB 通道的安全性也决定了Bluetooth LE 连接的安全性。如果 OOB 通道受到保护以免遭受窃听和 MITM 攻击,那么Bluetooth LE链路也是如此。

蓝牙 SIG 不推荐使用传统配对,但如果必须使用,请使用 OOB 方式。OOB身份验证与传统配对配合使用可被视为安全的方法。

因此,在蓝牙 v4.2 中引入了 LE 安全连接(LE Secure Connections)。 LE 安全连接不使用 TK 和 STK,而是使用椭圆曲线 Diffie-Hellman (ECDH) 加密技术来生成公钥-私钥对。设备交换公钥。它们将使用四种配对方法之一(Just Works, Passkey entry, OOB or Numeric Comparison)来验证对等设备的真实性,并根据 Diffie-Hellman 密钥和身份验证数据生成 LTK。

尽管 Just Works 在使用 LE 安全连接时更安全,但它仍然不提供身份验证,因此不建议将其用作配对方法。密钥输入(Passkey entry)配对方式,使用密钥、ECDH 公钥和 128 位任意数字来验证连接。这意味着它比传统配对要安全得多。使用 OOB 配对仍然是推荐的选项,因为只要 OOB 通道是安全的,它就会提供保护,就像在传统配对中一样。

此外,此功能引入了一种称为数字比较(Numeric Comparison)的新配对方式。虽然它遵循与 Just Works 配对方式相同的程序,但它在最后增加了另一个步骤来防止 MITM 攻击,即让用户根据两个对等点上显示的值,进行手动检查和确认。

对等点之间交换的唯一数据是公钥。使用 ECDH 公钥加密使得破解 LTK 变得极其困难,与传统配对相比,这是一个显著的改进。

注:尽管大多数设备都支持 LE 安全连接,但仍有一些蓝牙 LE 产品仅支持传统配对。因此,除了 LE 安全连接之外,启用对传统配对的支持也是一个好主意,以便在您的应用程序中实现更好的兼容性。

五. SMP安全管理协议

在蓝牙核心规范中,有三个主要的架构层:控制器、主机和应用程序,如下图所示。在主机层,有一个名为安全管理器 (SM) 的模块,它定义了配对和密钥分发的方法和协议、相应的安全工具箱,以及安全管理器协议 (SMP)。SMP定义了配对命令帧格式、帧结构和超时限制,用于配对和传输特定的密钥分发。

Bluetooth LE安全机制

SMP具体命令如下图:

Bluetooth LE安全机制

Bluetooth LE安全机制

六. Bluetooth LE配对过程

配对是为了创建密钥,然后可以使用这些密钥来加密链接。配对过程中还可能传输特定的密钥和数据,以便双方共享一些机密信息。这些机密信息可用于在未来重新连接时加密链接、验证签名或执行随机地址解析。

Bluetooth LE配对之前需要先建立连接。配对过程可以分为三个过程,前两个过程是必须的,第三个过程是可选的。

  • Phase1 Pairing Feature Exchange: 配对特征交换
  • Phase2 (LE legacy pairing): Short Term Key (STK) Generation: STK密钥生成
  • Phase2 (LE Secure Connections): Long Term Key (LTK) Generation: LTK密钥生成
  • Phase3 Transport Specific Key Distribution: 传输特定密钥分发(在传统配对中,LTK 也在该阶段被派发)

配对过程如下图所示:

Bluetooth LE安全机制

(1) 配对特征交换(Pairing Feature Exchange)

  1. 配对命令

两个设备之间的配对信息交换是通过配对请求(pairing request)和配对响应(pairing response)数据包完成的。只有主机才能发送配对请求命令,从机发送配对响应命令进行回复。如果从机也想发起配对,那么可以发送安全请求(security request )命令,主机收到这条命令后,可以选择发送配对请求,也可以拒绝。处理逻辑如下:

Bluetooth LE安全机制

主机,从机都可以调用int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec)函数来发起配对。这个函数的处理逻辑里面会判断当前的身份是主机还是从机,从而发送不同的命令。

  1. 配对命令格式

Pairing Feature Exchange主要交换I/O能力,是否支持OOB,是否支持绑定,是否支持MITM,是否支持安全连接(SC)等,还有分发密钥的相关的信息。格式如下:

Bluetooth LE安全机制

1)I/O能力包括:

  • DisplayOnly:仅有显示能力
  • DisplayYesNo:有显示能力,并可选择“是”或“否”
  • KeyboardOnly:仅有键盘输入能力
  • NoInputNoOutput:没有输入和输出能力
  • KeyboardDisplay:有键盘输入和显示能力

I/O能力编码,如下图:

Bluetooth LE安全机制

2)OOB 数据flag如下图:

Bluetooth LE安全机制

3)AuthReq中各个字段的含义如下:

  • Bonding Flags:指示是否支持绑定
  • MITM:如果设备需要请求 MITM 保护,则必须设置此位字段。这将启用设备的身份验证。
  • SC:代表安全连接,如果支持安全连接功能,则设置此字段。如果设备支持 LE 安全连接配对,则必须将 SC 字段设置为 1,否则必须将其设置为 0。如果两个设备都支持 LE 安全连接配对,则必须使用 LE 安全连接配对,否则将使用 LE 传统配对。
  • Keypress:仅在 Passkey Entry 协议中使用,其他协议将忽略它。设置此字段后,输入 passkey 时将使用按键通知。这将一次交换一位(one bit)passkey,这是蓝牙 4.2 相对于之前的机制(蓝牙 4.1 或更早版本)的一项重要增强。蓝牙 4.2之前的处理中,整个passkey在一次确认操作中交换。这增强了passkey交换机制,现在在 MITM 攻击中预测passkey变得更加困难。
  • CT2:表示加密过程可以使用名为 h7 的安全功能。
  • RFU:保留以备将来使用。

4)最大加密密钥大小 (Maximum Encryption Key Size):由于不同设备的处理能力不同,此字节表示设备可以处理的最大密钥大小。大小可以是 7 到 16 个字节。Nordic支持16个字节。

  1. 发起方密钥分发(Initiator Key Distribution):此字段表示发起方希望在后续阶段生成和分发哪些密钥。

  2. 响应方密钥分发 (Responder Key Distribution):此字段表示发起方希望请求响应方生成和分发哪些密钥。

密钥的种类如下:

Bluetooth LE安全机制

EncKey代表LTK;IdKey代表IRK ;SignKey代表 CSRK;Bluetooth LE中LinkKey不被分发。

  1. 配对模式选择

通过综合考虑双方的OOB标志、MITM标志和I/O能力,来决定使用哪种配对方法。OOB和MITM标志首先确定是直接使用OOB配对方法还是根据I/O能力确定配对方法。如下图所示:

Bluetooth LE安全机制

Bluetooth LE安全机制

1) Initiator和Responder两方都设定了OOB flag的情况下,选用OOB

2) Initiator和Responder一方设定了OOB flag的情况下,需要判断SC flag

  • Secure connection的情况下,选用OOB。
  • Legacy的情况下,需要去判断MITM flag,只要一方设定了MITM flag,就要通过I/O能力去选择pairing mothed。如果两方都没有设定MITM flag,那么直接用Just works

3) Initiator和Responder都没有设OOB的情况下,判断MITM flag

  • 只要一方设定了MITM,需要去判断I/O能力
  • 两方都没设定MITM的情况下,使用Just Works

(2) 密钥生成阶段

  1. 传统配对方式,生成STK,流程如下:

Bluetooth LE安全机制

传统配对方式中,首先交换临时密钥 (TK),用于生成短期密钥 (STK),然后使用短期密钥 (STK) 加密链路,传输长期密钥(LTK)。

1.1传统配对定义了三种不同的方法来交互 TK,它们是Just works,Passkey Entry和Out of Band (OOB)。

  • Just Works:TK默认为0,不提供身份验证,因此无法防御 MITM 攻击。
  • Passkey Entry :一台设备上显示 6 位数字密码(passkey),另一台设备上需要输入。设备的 I/O 能力决定了哪台设备显示数字以及哪台设备输入数字。用户输入的6位数字passkey,用前导零填充以得出 128 位TK值。
  • Out of Band:在设备之间通过带外通信,传递完整 128 位TK值,例如使用 NFC传输。

注:Just Works 配对不提供身份验证,无法防御 MITM 攻击。

1.2 传统配对的认证(authentication)过程:

首先,每个设备计算一个 128 位的确认值(confirm value)。确认值是使用名为 c1 的函数计算的,该函数接受多个参数,包括 TK。除了 TK 值之外,还包括两个设备都知道的字段以及一个随机数,在这个阶段,只有创建确认值的设备知道这个随机数。在主机上,这个值称为LP_RAND_I。在从机上,称为LP_RAND_R。

Bluetooth LE安全机制

然后,主机将其确认值 (LP_CONFIRM_I) 发送到从机,从机以其确认值 (LP_CONFIRM_R) 进行响应。

收到 LP_CONFIRM_R 后,主机发送自己的随机数 LP_RAND_I给从机。从机计算出LP_CONFIRM_I 值,并将其与主机端发送过来的 LP_CONFIRM_I 值进行比较。如果它们匹配,则从机已对主机进行了身份验证,因此它将发送自己的随机数LP_RAND_R进行响应。主机计算 LP_CONFIRM_R 值并将其与之前收到的值进行比较。如果它们匹配,则主机完成了对从机的身份验证。

以Passkey为例,认证流程如下:

Bluetooth LE安全机制

1.3传统配对中,STK的生成公式如下:

STK=s1(TK,LP_RAND_R,LP_RAND_I)

注意:TK值是不会被蓝牙链路传输的,这确保了一定的安全性。TK的密码强度直接影响了STK的密码强度,显然OOB要好一些。

2.安全配对方式, 生成LTK,流程如下:

Bluetooth LE安全机制

LE 安全连接使用椭圆曲线 Diffie-Hellman (ECDH) 密钥交换算法交换数据,然后使用数据创建对称密钥,称为长期密钥 (LTK)。

2.1 Public Key Exchange

发起配对的设备(发起者)将其公钥发送给另一台设备(响应者),后者用其公钥进行回复。公钥在收到后进行检验,以检查它们是否在正确的椭圆曲线上。注意,LE 安全连接仅使用 P-256 曲线。

2.2 Calculate DHKey

每个设备都会计算一个称为 Diffie-Hellman key (DHKey) 的密钥。假设一方是Alice,一方是Bob,Alice 和 Bob 这两个设备按以下方式计算 DHKey:

Alice: DHKey = P256(SKa, PKb)
Bob: DHKey = P256(SKb, PKa)

两个设备 A 和 B 的私钥分别表示为 SKa 和 SKb。PKa和PKb是它们的公钥,交换之后,双方都知道对方的公钥。基于公钥和私钥作为参数,两边能计算出相同的DHkey。DHKey 是一个对称共享密钥。

上述过程如下图所示(图一描述过程,图二描述协议):

Bluetooth LE安全机制

Bluetooth LE安全机制

2.3 Authentication Stage 1

不同的配对模式,对应的算法略有不同。LE安全配对有4种模式,除了前面提到的传统配对中的3种模式以外,还引入了一种新模式,称为数字比较(Numeric Comparison)

1) Just Works 和Numeric Comparison身份验证阶段1:

首先,设备 B 生成一个伪随机数 Nb,并将其与两个设备的公钥一起,作为 f4 (函数)的参数,来计算确认值Cb 。Cb=f4(PKb, PKa, Nb, 0),然后将 Cb 发送到设备 A。由于设备 A 不知道 Nb 的值,因此它无法在此阶段对确认值执行任何操作。

设备 A 现在将其自己的伪随机数 Na 发送到设备 B,设备 B 则用其随机数 Nb 回复。现在,设备 A 拥有 f4 函数的所有参数,因此它使用在公钥交换期间获取的公钥和刚刚收到的另一台设备的随机数 Nb 重新计算确认值。它将计算出的确认值与从设备 B 收到的 Cb 值进行比较。如果它们不相同,则配对中止。

如果使用 Just Works,则身份验证第 1 阶段已完成。如果使用Numeric Comparison,则还需要执行一步。

每个设备使用两个公钥 PKa 和 PKb 以及两个伪随机数 Na 和 Nb,通过g2 函数计算出一个六位数字。然后,每个设备显示其计算出的数字。用户被邀请确认两个设备是否显示相同的六位数字,也可以通过按下特定按钮来实现。通过指示两个数字相同,用户确认参与通信的两个设备确实是用户尝试配对的设备。换句话说,用户参与了配对设备的身份验证。对设备进行身份验证后,就会进入身份验证阶段 2。

另一方面,如果用户发现两个数字不相同,则身份验证阶段 1 失败,并且终止该过程。

具体流程如下图所示:

Bluetooth LE安全机制

2) Passkey Entry身份验证阶段1:

用户输入密码(Passkey)后,设备会计算、交换和检查确认值 Ca 和 Cb。但是,与 Just Works/Numeric Comparison 中的单次确认不同,使用密码(Passkey)输入,确认过程是迭代和增量的。

在每次迭代过程中,每个设备都会生成一个 128 位随机数 Na 或 Nb,并且每次将密钥值的一位纳入确认值的计算中。确认值函数 f4 的输入包括两个公钥、本次迭代的随机数以及从本次迭代的密码位值派生的值。

每次迭代都会交换和检查确认值。Cai=f4(Pka, PKb, Nai, rbi),Cbi=f4(PKb, PKa, Nbi, rbi)(i=0 to 19)(ra=rb=passkey)rai和rbi是passkey二进制值对应的每一位。

六位密码(passkey)对应 20 位二进制数字(999999=0xF423F),因此计算、交换和检查完整确认值的过程需要迭代20次,每次迭代都会合并或披露密码的新位。这种方法称为“逐步披露”,其优点是 MITM 攻击在实际应用中更加困难,大多数攻击在攻击者尚未看到完整和最终确认值的情况下就会提前失败。缺点是计算过程比较复杂,数据交互的次数较多,耗时耗电。

具体流程如下图所示:

Bluetooth LE安全机制

3) OOB身份验证阶段1:

使用 OOB 方式,整个过程使用非蓝牙的数据交换机制进行的。

具体流程如下图所示:

Bluetooth LE安全机制

Bluetooth LE安全机制

使用带内接收的公钥和带外接收的随机数 ra 和 rb 重新计算接收到的确认值 Ca 和 Cb,这个是基于OOB双向数据传输的情况。如果 OOB 通信只能在一个方向上进行(单方向传输数据),则接收 OOB 数据的设备的认证将基于该设备知道通过 OOB 发送的随机数 r(ra或rb)。在这种情况下,r 必须是保密的:r 可以每次重新创建,或者必须限制对发送 r 的设备的访问。如果设备未发送 r,则设备会将其假定为 0。计算出的确认值必须与接收到的 OOB 值匹配。如果两者之一不匹配,配对将中止。

2.4 Authentication Stage 2

身份验证阶段 2 有四个步骤。

1)使用函数 f5 计算长期密钥 (LTK) 和 MacKey ,MacKey || LTK = f5(DHKey, Na, Nb, BD_ADDR_C, BD_ADDR_P)

2)设备 A 计算称为 Ea 的检查值并将其发送到设备 B,在设备 B 中重新计算该值并将其与收到的值进行比较

3)设备 B 计算称为 Eb 的检查值并将其发送到设备 A,在设备 A 中重新计算该值并将其与收到的值进行比较

4)使用计算出的 LTK 在链路上启动加密,为阶段 3 做好准备

Ea,Eb的计算公式如下表:

Bluetooth LE安全机制

如果步骤 2 或 3 中的任何一个检查失败,配对将中止。具体流程如下图所示:

Bluetooth LE安全机制

(3) 密钥分发

对于传统配对和LE安全连接,它们分发的内容是有区别的。另外,传统配对密钥分发的链路是通过STK加密的;LE安全连接的密钥分发是通过LTK加密的。传统配对可能分发的内容如下:

  1. LTK by the Peripheral
  2. EDIV AND Rand by the Peripheral
  3. IRK by the Peripheral
  4. BD_ADDR by the Peripheral
  5. CSRK by the Peripheral
  6. LTK by the Central
  7. EDIV and Rand by the Central
  8. IRK by the Central
  9. BD_ADDR by the Central
  10. CSRK by the Central

LE安全连接可能分发的内容如下:

  1. IRK by the Peripheral
  2. BD_ADDR by the Peripheral
  3. CSRK by the Peripheral
  4. IRK by the Central
  5. BD_ADDR by the Central
  6. CSRK by the Central

七. Bluetooth LE绑定

创建绑定后,每个设备都会存储已分发的密钥信息,并在下一次连接中使用这些密钥,这样就可以跳过配对过程,从而节省能源并减少用户操作设备时必须等待的时间。

LTK 存储在安全数据库中,并通过 EDIV 和 Rand 值进行标识。执行绑定后,每次后续重新连接都将使用已分发的 LTK 生成的密钥来加密链接。

NCS中可以通过配置编译选项使能绑定功能(CONFIG_BT_BONDABLE=y)。注意,绑定信息存储在settings区,更具体的是bt settings区域,所以,使能绑定功能的同时也要使能bt settings区。具体信息如下:

Bluetooth LE安全机制

Bluetooth LE安全机制

Bluetooth LE安全机制

如果要删除绑定信息,可以调用int bt_unpair(uint8_t id, const bt_addr_le_t *addr)函数,函数说明和使用示例如下:

/**
* @brief Clear pairing information.
*
* @param id  Local identity (mostly just BT_ID_DEFAULT).
* @param addr Remote address, NULL or BT_ADDR_LE_ANY to clear all remote devices.
*
* @return 0 on success or negative error value on failure.
*/
int bt_unpair(unit8_t id, const bt_addr_le_t *addr);
void button_changed(unit32_t button_state,unit32_t has_changed)
{
     int err;
     unit32_t buttons = button_state & has_changed;

     if(buttons & KEY_BOND_REMOVE_MASK) {
        err = bt_unpair(BT_ID_DEFAULT,NULL);
        if (err) {
          printk("Bond remove failed err: %d\n", err);
        } else {
            printk("All bond removed\n");
        }
     }
}
static int remove_peers(unit8_t identity)
{
    LOG_INF("Remove peer on identity %u", identity);

    int err = bt_unpair(get_bt_stack_peer_id(identity), BT_ADDR_LE_ANY);
    if (err) {
        LOG_ERR("Failed to remove");
    }
    return err;
}

八. NCS中如何开启和使用SMP相关功能

(1) 开启SMP功能

SMP协议对应的代码主要位于zephyr\subsys\bluetooth\host\smp.c

设置CONFIG_BT_SMP=y可以加入smp.c到当前工程。

config BT_SMP
    bool "Security Manager Protocol support"
    select BT_CRYPTO
    select BT_RPA
    select BT_ECC
    help
      This option enables support for the Security Manager Protocol
      (SMP), making it possible to pair devices over LE.
if(CONFIG_BT_CONN)
  zephyr_library_sources(
    conn.c
    12cap.c
    att.c
    gatt.c
    )
  if(CONFIG_BT_SMP)
    zephyr_library_sources(
      smp.c
      keys.c
      )
  else()
    zephyr_library_sources(
      smp_null.c
      )
  endif()
endif()

NCS中SMP的主要例子包括:

  • nrf\samples\bluetooth\central_hids
  • nrf\samples\bluetooth\peripheral_uart
  • nrf\samples\bluetooth\peripheral_mds
  • nrf\samples\bluetooth\peripheral_nfc_pairing
  • nrf\samples\bluetooth\central_nfc_pairing
  • zephyr\samples\bluetooth\peripheral_sc_only
  • zephyr\subsys\bluetooth\shell

Bluetooth LE例子里,只要设置了CONFIG_BT_SMP=y,至少支持just works

(2) 如何设置SC(安全连接)标志

NCS中使能SMP的Nordic的例子里,默认都是SC。SC需要用到很多安全算法,Nordic的芯片都能支持。为了保证Bluetooth LE通信的安全性,我们建议用SC的方式。但如果客户资源(RAM,flash)不够用,且对Bluetooth LE安全要求不高的情况下,可以关闭SC,使用传统配对。

SC设置相关代码段:

int bt_smp_init(void)
{
	static struct bt_pub_key_cb pub_key_cb = {
		.func           = bt_smp_pkey_ready,
	};

	sc_supported = le_sc_supported();
	if (IS_ENABLED(CONFIG_BT_SMP_SC_PAIR_ONLY) && !sc_supported) {
		LOG_ERR("SC Pair Only Mode selected but LE SC not supported");
		return -ENOENT;
	}

	if (IS_ENABLED(CONFIG_BT_SMP_USB_HCI_CTLR_WORKAROUND)) {
		LOG_WRN("BT_SMP_USB_HCI_CTLR_WORKAROUND is enabled, which "
			"exposes a security vulnerability!");
	}

	LOG_DBG("LE SC %s", sc_supported ? "enabled" : "disabled");

	if (!IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY)) {
		bt_pub_key_gen(&pub_key_cb);
	}

	return smp_self_test();
}
static bool le_sc_supported(void)
{
	/*
	 * If controller based ECC is to be used it must support
	 * "LE Read Local P-256 Public Key" and "LE Generate DH Key" commands.
	 * Otherwise LE SC are not supported.
	 */
	if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY)) {
		return false;
	}

	return BT_CMD_TEST(bt_dev.supported_commands, 34, 1) &&
	       BT_CMD_TEST(bt_dev.supported_commands, 34, 2);
}
static void read_supported_commands_complete(struct net_buf *buf)
{
	struct bt_hci_rp_read_supported_commands *rp = (void *)buf->data;

	LOG_DBG("status 0x%02x", rp->status);

	memcpy(bt_dev.supported_commands, rp->commands,
	       sizeof(bt_dev.supported_commands));

	/* Report additional HCI commands used for ECDH as
	 * supported if TinyCrypt ECC is used for emulation.
	 */
	if (IS_ENABLED(CONFIG_BT_TINYCRYPT_ECC)) {
		bt_hci_ecc_supported_commands(bt_dev.supported_commands);
	}
}
void bt_hci_ecc_supported_commands(uint8_t *supported_commands)
{
	/* LE Read Local P-256 Public Key */
	supported_commands[34] |= BIT(1);
	/* LE Generate DH Key v1 */
	supported_commands[34] |= BIT(2);
	/* LE Generate DH Key v2 */
	supported_commands[41] |= BIT(2);
}
#if defined(CONFIG_BT_CTLR_ECDH)
	case BT_HCI_OP_LE_P256_PUBLIC_KEY:
		return hci_cmd_le_read_local_p256_public_key();
	case BT_HCI_OP_LE_GENERATE_DHKEY:
		return hci_cmd_le_generate_dhkey((void *)cmd_params);
	case BT_HCI_OP_LE_GENERATE_DHKEY_V2:
		return hci_cmd_le_generate_dhkey_v2((void *)cmd_params);
#endif

1) 使用OOB的情况下,通过设置CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY=y去指定非SC。

config BT_SMP_OOB_LEGACY_PAIR_ONLY
    bool "Force Out Of Band Legacy pairing"
    depends on !(BT_SMP_SC_PAIR_ONLY || BT_SMP_SC_ONLY)
    help
      This option disables Legacy and LE SC pairing and forces legacy OOB.

2) 不使用OOB的情况下,设置CONFIG_BT_CTLR_ECDH=n和CONFIG_BT_TINYCRYPT_ECC=n。关闭安全算法的情况下,系统会使用传统配对。

config BT_CTLR_ECDH
    bool "Elliptic Curve Diffie-Hellman (ECDH)"
    depends on BT_CTLR_ECDH_SUPPORT
    default y
    help
      Enable support for Bluetooth v4.2 Elliptic Curve Diffie-Hellman
      feature in the controller.
config BT_TINYCRYPT_ECDH
    bool "Emulate ECDH in the Host using TinyCrypt library"
    select TINYCRYPT
    select TINYCRYPT_ECC_DH
    select BT_LONG_WQ
    depends on BT_ECC && (BT_HCI_RAW || BT_HCI_HOST)
    default y if BT_CTLR && !BT_CTLR_ECDH
    help
    If this option is set TinyCrypt library is used for emulating the
    ECDH HCI commands and events need by e.g LE Secure Connections.
    In builds including the BLE Host, if not set the controller crypto is
    used for ECDH and if the controller doesn't support the required HCI
    commands the LE Secure Connections support will be disable.
    In builds including the HCI RAW interface and the BLE Controller, this
    option injects support for the 2 HCI commands required for LE Secure
    Connections so that Hosts can make use of those. The option defaults
    to enable for a combined build with Zephyr's own controller, since it
    does not have any special ECC support itself (at least not currently).

另外,调用int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec)函数时,第二个参数如果是BT_SECURITY_L4,且I/O能力满足条件,则会发起SC;否则,使用传统配对。

/** @brief Set security level for a connection.
 *
 *  This function enable security (encryption) for a connection. If the device
 *  has bond information for the peer with sufficiently strong key encryption
 *  will be enabled. If the connection is already encrypted with sufficiently
 *  strong key this function does nothing.
 *
 *  If the device has no bond information for the peer and is not already paired
 *  then the pairing procedure will be initiated. Note that @p sec has no effect
 *  on the security level selected for the pairing process. The selection is
 *  instead controlled by the values of the registered @ref bt_conn_auth_cb. If
 *  the device has bond information or is already paired and the keys are too
 *  weak then the pairing procedure will be initiated.
 *
 *  This function may return error if required level of security is not possible
 *  to achieve due to local or remote device limitation (e.g., input output
 *  capabilities), or if the maximum number of paired devices has been reached.
 *
 *  This function may return error if the pairing procedure has already been
 *  initiated by the local device or the peer device.
 *
 *  @note When @kconfig{CONFIG_BT_SMP_SC_ONLY} is enabled then the security
 *        level will always be level 4.
 *
 *  @note When @kconfig{CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY} is enabled then the
 *        security level will always be level 3.
 *
 *  @note When @ref BT_SECURITY_FORCE_PAIR within @p sec is enabled then the pairing
 *        procedure will always be initiated.
 *
 *  @param conn Connection object.
 *  @param sec Requested security level.
 *
 *  @return 0 on success or negative error
 */
int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec);

注:是否使用SC,需要两方协商,一方不能决定。

(3) 如何设置MITM标志

通过设定CONFIG_BT_SMP_ENFORCE_MITM=y编译选项,可以强制设置MITM标志。

config BT_SMP_ENFORCE_MITM
    bool "Enforce MITM protection"
    default y
    help
      with this option enabled, the Security Manager will set MITM option in
      the Authentication Requirements Flags whenever local IO Capabilities
      allow the generated key to be authenticated.
static uint8_t get_auth(struct bt_smp *smp, uint8_t auth)
{
	struct bt_conn *conn = smp->chan.chan.conn;

	if (sc_supported) {
		auth &= BT_SMP_AUTH_MASK_SC;
	} else {
		auth &= BT_SMP_AUTH_MASK;
	}

	if ((get_io_capa(smp) == BT_SMP_IO_NO_INPUT_OUTPUT) ||
	    (!IS_ENABLED(CONFIG_BT_SMP_ENFORCE_MITM) &&
	    (conn->required_sec_level < BT_SECURITY_L3))) {
		auth &= ~(BT_SMP_AUTH_MITM);
	} else {
		auth |= BT_SMP_AUTH_MITM;
	}

	if (latch_bondable(smp)) {
		auth |= BT_SMP_AUTH_BONDING;
	} else {
		auth &= ~BT_SMP_AUTH_BONDING;
	}

	if (IS_ENABLED(CONFIG_BT_PASSKEY_KEYPRESS)) {
		auth |= BT_SMP_AUTH_KEYPRESS;
	} else {
		auth &= ~BT_SMP_AUTH_KEYPRESS;
	}

	return auth;
}

当CONFIG_BT_SMP_ENFORCE_MITM=n时,SC flag和I/O能力共同决定了MITM flag。

下图中Authenticated对应的MITM=1,Unauthenticated对应的MITM=0。

Bluetooth LE安全机制

(4) 如何设置OOB数据 flag

准备好OOB数据后,通过调用API函数设置此flag。SC和非SC的设置函数分别如下:

/** @brief Allow/disallow remote LE SC OOB data to be used for pairing.
 *
 *  Set/clear the OOB data flag for LE SC SMP Pairing Request/Response data.
 *
 *  @param enable Value allowing/disallowing remote LE SC OOB data.
 */
void bt_le_oob_set_sc_flag(bool enable);
/** @brief Allow/disallow remote legacy OOB data to be used for pairing.
 *
 *  Set/clear the OOB data flag for legacy SMP Pairing Request/Response data.
 *
 *  @param enable Value allowing/disallowing remote legacy OOB data.
 */
void bt_le_oob_set_legacy_flag(bool enable);

/** @brief Set OOB Temporary Key to be used for pairing
 *
 *  This function allows to set OOB data for the LE legacy pairing procedure.
 *  The function should only be called in response to the oob_data_request()
 *  callback provided that the legacy method is user pairing.
 *
 *  @param conn Connection object
 *  @param tk Pointer to 16 byte long TK array
 *
 *  @return Zero on success or -EINVAL if NULL
 */

OOB数据 flag设置相关的代码片段:

void bt_le_oob_set_sc_flag(bool enable)
{
	sc_oobd_present = enable;
}

void bt_le_oob_set_legacy_flag(bool enable)
{
	legacy_oobd_present = enable;
}
	/* At this point is it unknown if pairing will be legacy or LE SC so
	 * set OOB flag if any OOB data is present and assume to peer device
	 * provides OOB data that will match it's pairing type.
	 */
	req->oob_flag = (legacy_oobd_present || sc_oobd_present) ?
				BT_SMP_OOB_PRESENT : BT_SMP_OOB_NOT_PRESENT;
	if (atomic_test_bit(smp->flags, SMP_FLAG_SC)) {
		rsp->oob_flag = sc_oobd_present ? BT_SMP_OOB_PRESENT :
				BT_SMP_OOB_NOT_PRESENT;
	} else {
		rsp->oob_flag = legacy_oobd_present ? BT_SMP_OOB_PRESENT :
				BT_SMP_OOB_NOT_PRESENT;
	}

从配对的过程来看,OOB的数据在两个阶段中使用,一个阶段是带外数据交互,另一个阶段是身份验证的过程中充当参数。

非SC的OOB带外需要交互TK值。TK值实际上也是一个Random Number。

SC 的OOB带外需要交互Random Number和Confirm Value。

另外还有一些附加的辅助信息。我们peripheral_nfc_pairing和central_nfc_pairing例子里面,NFC的信息如下(兼容支持SC和非SC):

/**
 * @brief LE OOB record payload descriptor.
 */
struct nfc_ndef_le_oob_rec_payload_desc {
	bt_addr_le_t *addr;
	enum nfc_ndef_le_oob_rec_le_role *le_role;
	struct bt_le_oob_sc_data *le_sc_data;
	uint8_t *tk_value;
	uint16_t *appearance;
	uint8_t *flags;
	const char *local_name;
};
/** .. include_startingpoint_nfc_tnep_ch_tag_rst */
static int carrier_prepare(void)
{
	static struct nfc_ndef_le_oob_rec_payload_desc rec_payload;

	NFC_NDEF_LE_OOB_RECORD_DESC_DEF(oob_rec, '0', &rec_payload);
	NFC_NDEF_CH_AC_RECORD_DESC_DEF(oob_ac, NFC_AC_CPS_ACTIVE, 1, "0", 0);

	memset(&rec_payload, 0, sizeof(rec_payload));

	rec_payload.addr = &oob_local.addr;
	rec_payload.le_sc_data = &oob_local.le_sc_data;
	rec_payload.tk_value = tk_value;
	rec_payload.local_name = bt_get_name();
	rec_payload.le_role = NFC_NDEF_LE_OOB_REC_LE_ROLE(
		NFC_NDEF_LE_OOB_REC_LE_ROLE_PERIPH_ONLY);
	rec_payload.appearance = NFC_NDEF_LE_OOB_REC_APPEARANCE(
		CONFIG_BT_DEVICE_APPEARANCE);
	rec_payload.flags = NFC_NDEF_LE_OOB_REC_FLAGS(BT_LE_AD_NO_BREDR);

	return nfc_tnep_ch_carrier_set(&NFC_NDEF_CH_AC_RECORD_DESC(oob_ac),
				       &NFC_NDEF_LE_OOB_RECORD_DESC(oob_rec),
				       1);
}
/** LE Secure Connections pairing Out of Band data. */
struct bt_le_oob_sc_data {
	/** Random Number. */
	uint8_t r[16];

	/** Confirm Value. */
	uint8_t c[16];
};

/** LE Out of Band information. */
struct bt_le_oob {
	/** LE address. If privacy is enabled this is a Resolvable Private
	 *  Address.
	 */
	bt_addr_le_t addr;

	/** LE Secure Connections pairing Out of Band data. */
	struct bt_le_oob_sc_data le_sc_data;
};

通过int bt_le_oob_get_local(uint8_t id, struct bt_le_oob *oob)或int bt_le_ext_adv_oob_get_local(struct bt_le_ext_adv *adv, struct bt_le_oob *oob)可以从controller获取本地生成的SC OOB数据。这个数据需要写入带外设备,如NFC 设备。

/**
 * @brief Get local LE Out of Band (OOB) information.
 *
 * This function allows to get local information that are useful for
 * Out of Band pairing or connection creation.
 *
 * If privacy @kconfig{CONFIG_BT_PRIVACY} is enabled this will result in
 * generating new Resolvable Private Address (RPA) that is valid for
 * @kconfig{CONFIG_BT_RPA_TIMEOUT} seconds. This address will be used for
 * advertising started by @ref bt_le_adv_start, active scanning and
 * connection creation.
 *
 * @note If privacy is enabled the RPA cannot be refreshed in the following
 *       cases:
 *       - Creating a connection in progress, wait for the connected callback.
 *      In addition when extended advertising @kconfig{CONFIG_BT_EXT_ADV} is
 *      not enabled or not supported by the controller:
 *       - Advertiser is enabled using a Random Static Identity Address for a
 *         different local identity.
 *       - The local identity conflicts with the local identity used by other
 *         roles.
 *
 * @param[in]  id  Local identity, in most cases BT_ID_DEFAULT.
 * @param[out] oob LE OOB information
 *
 * @return Zero on success or error code otherwise, positive in case of
 *         protocol error or negative (POSIX) in case of stack internal error.
 */
int bt_le_oob_get_local(uint8_t id, struct bt_le_oob *oob);
/**
 * @brief Get local LE Out of Band (OOB) information.
 *
 * This function allows to get local information that are useful for
 * Out of Band pairing or connection creation.
 *
 * If privacy @kconfig{CONFIG_BT_PRIVACY} is enabled this will result in
 * generating new Resolvable Private Address (RPA) that is valid for
 * @kconfig{CONFIG_BT_RPA_TIMEOUT} seconds. This address will be used by the
 * advertising set.
 *
 * @note When generating OOB information for multiple advertising set all
 *       OOB information needs to be generated at the same time.
 *
 * @note If privacy is enabled the RPA cannot be refreshed in the following
 *       cases:
 *       - Creating a connection in progress, wait for the connected callback.
 *
 * @param[in]  adv The advertising set object
 * @param[out] oob LE OOB information
 *
 * @return Zero on success or error code otherwise, positive in case
 * of protocol error or negative (POSIX) in case of stack internal error.
 */
int bt_le_ext_adv_oob_get_local(struct bt_le_ext_adv *adv,
				struct bt_le_oob *oob);

通过调用int bt_le_oob_set_sc_data(struct bt_conn *conn, const struct bt_le_oob_sc_data *oobd_local, const struct bt_le_oob_sc_data *oobd_remote)可以设置SC OOB配对过程中,身份认证阶段所需要的数据。

/** @brief Set OOB data during LE Secure Connections (SC) pairing procedure
 *
 *  This function allows to set OOB data during the LE SC pairing procedure.
 *  The function should only be called in response to the oob_data_request()
 *  callback provided that LE SC method is used for pairing.
 *
 *  The user should submit OOB data according to the information received in the
 *  callback. This may yield three different configurations: with only local OOB
 *  data present, with only remote OOB data present or with both local and
 *  remote OOB data present.
 *
 *  @param conn Connection object
 *  @param oobd_local Local OOB data or NULL if not present
 *  @param oobd_remote Remote OOB data or NULL if not present
 *
 *  @return Zero on success or error code otherwise, positive in case of
 *          protocol error or negative (POSIX) in case of stack internal error.
 */
int bt_le_oob_set_sc_data(struct bt_conn *conn,
			  const struct bt_le_oob_sc_data *oobd_local,
			  const struct bt_le_oob_sc_data *oobd_remote);

非SC的OOB,通过int bt_le_oob_set_legacy_tk(struct bt_conn *conn, const uint8_t *tk)设置身份认证阶段所需要的tk。

/** @brief Set OOB Temporary Key to be used for pairing
 *
 *  This function allows to set OOB data for the LE legacy pairing procedure.
 *  The function should only be called in response to the oob_data_request()
 *  callback provided that the legacy method is user pairing.
 *
 *  @param conn Connection object
 *  @param tk Pointer to 16 byte long TK array
 *
 *  @return Zero on success or -EINVAL if NULL
 */
int bt_le_oob_set_legacy_tk(struct bt_conn *conn, const uint8_t *tk);

(5) 如何设置IO能力

  1. IO能力有如下5种:

Bluetooth LE安全机制

#define BT_SMP_IO_DISPLAY_ONLY			0x00
#define BT_SMP_IO_DISPLAY_YESNO			0x01
#define BT_SMP_IO_KEYBOARD_ONLY			0x02
#define BT_SMP_IO_NO_INPUT_OUTPUT		0x03
#define BT_SMP_IO_KEYBOARD_DISPLAY		0x04

首先看看获取IO能力函数uint8_t get_io_capa(struct bt_smp *smp)的实现,如下图:

static uint8_t get_io_capa(struct bt_smp *smp)
{
	const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp);

	if (!smp_auth_cb) {
		goto no_callbacks;
	}

	/* Passkey Confirmation is valid only for LE SC */
	if (smp_auth_cb->passkey_display && smp_auth_cb->passkey_entry &&
	    (smp_auth_cb->passkey_confirm || !sc_supported)) {
		return BT_SMP_IO_KEYBOARD_DISPLAY;
	}

	/* DisplayYesNo is useful only for LE SC */
	if (sc_supported && smp_auth_cb->passkey_display &&
	    smp_auth_cb->passkey_confirm) {
		return BT_SMP_IO_DISPLAY_YESNO;
	}

	if (smp_auth_cb->passkey_entry) {
		if (IS_ENABLED(CONFIG_BT_FIXED_PASSKEY) &&
		    fixed_passkey != BT_PASSKEY_INVALID) {
			return BT_SMP_IO_KEYBOARD_DISPLAY;
		} else {
			return BT_SMP_IO_KEYBOARD_ONLY;
		}
	}

	if (smp_auth_cb->passkey_display) {
		return BT_SMP_IO_DISPLAY_ONLY;
	}

no_callbacks:
	if (IS_ENABLED(CONFIG_BT_FIXED_PASSKEY) &&
	    fixed_passkey != BT_PASSKEY_INVALID) {
		return BT_SMP_IO_DISPLAY_ONLY;
	} else {
		return BT_SMP_IO_NO_INPUT_OUTPUT;
	}
}

上面的代码能看到一个关键的结构体struct bt_conn_auth_cb,结构体的定义如下图:

Bluetooth LE安全机制

 

结构体对应变量的成员都是callback函数。部分callback函数定义与否,决定了本设备的IO能力。

1)no callbacks的情况下,如果没有设置CONFIG_BT_FIXED_PASSKEY,那么IO能力就是BT_SMP_IO_NO_INPUT_OUTPUT;如果设置CONFIG_BT_FIXED_PASSKEY=y且fixed_passkey != BT_PASSKEY_INVALID,那么IO能力就是BT_SMP_IO_DISPLAY_ONLY

config BT_FOEXED_PASSKEY
    bool "Use a fixed passkey for pairing"
    help
      With this option enabled, the application will be able to call the
      bt_passley_set() API to set a fixed passkey. If set, the
      pairing_confirm() callback will be called for all incoming pairings.

注:如果使用fixed passkey,应用需要调用bt_passkey_set()函数去设置passkey。没有IO能力的设备,通过使用fixed passkey,可以伪装成有display能力的设备走passkey entry的配对流程。本设备和对方设备需要共同知道这个passkey,因为它没有真正被显示出来(对方也需要有输入passkey的能力),且passkey不能暴露给不可信赖的第三方。

2)非no callbacks的情况下,如果定义了passkey_display,passkey_entry和passkey_confirm三个 callbacks,那么IO能力是BT_SMP_IO_KEYBOARD_DISPLAY;如果定义了passkey_display和passkey_entry两个callbacks且非SC, 那么IO能力也是BT_SMP_IO_KEYBOARD_DISPLAY

3)非no callbacks的情况下,如果定义了passkey_display和passkey_confirm两个callbacks且SC, 那么IO能力是BT_SMP_IO_DISPLAY_YESNO

4)非no callbacks的情况下,如果只定义了passkey_entry一个callback,当CONFIG_BT_FIXED_PASSKEY=y且fixed_passkey != BT_PASSKEY_INVALID时,IO能力是BT_SMP_IO_KEYBOARD_DISPLAY,否则,IO能力是BT_SMP_IO_ KEYBOARD _ONLY

注:如果使用fixed passkey,那么相当与只有KEYBOARD输入的设备,伪装成了有KEYBOARD输入和DISPLAY输出能力的设备。

5)非no callbacks的情况下,如果只定义了passkey_display一个callback,IO能力是BT_SMP_IO_DISPLAY_ONLY

注意,最终的配对方式和行为是本设备的综合能力和对方设备的综合能力共同决定的。用户在设计自己应用的时候需要根据自己设备实际的IO能力和设备角色(central/peripheral)去做配置和编写相应的代码,同时也要考虑安全性和兼容性。

  1. 如何配置IO能力

定义和实现好相关的callback函数就可以控制IO能力了。IO能力越强的设备,如果要做好兼容性,代码的复杂性会更高一些。拿从机来举例,如果没有输入输出能力,那么只能支持Jusk works(fixed passkey除外),也只需要支持Jusk works;如果只有display的能力,如下图,它需要兼容Jusk works和 passkey entry(passkey display)

Bluetooth LE安全机制

如果有display和Keyboard的能力,如下图,它需要兼容Jusk works, passkey entry(passkey input,passkey display)和Numeric Comparison

Bluetooth LE安全机制

(6)结构体struct bt_conn_auth_cb callback主要函数的作用

  1. passkey_display callback的作用是从底层向应用层提供passkey。Passkey entry配对过程中,这个passkey需要通过本机的显示接口展示给用户。用户再将这个passkey通过对端设备的输入接口输入给对端。正常情况下,用户只会给他信任的设备输入passkey,passkey起到了身份验证的作用。nrf\samples\bluetooth\peripheral_uart例子演示了passkey_display callback的使用方法。
	/** @brief Display a passkey to the user.
	 *
	 *  When called the application is expected to display the given
	 *  passkey to the user, with the expectation that the passkey will
	 *  then be entered on the peer device. The passkey will be in the
	 *  range of 0 - 999999, and is expected to be padded with zeroes so
	 *  that six digits are always shown. E.g. the value 37 should be
	 *  shown as 000037.
	 *
	 *  This callback may be set to NULL, which means that the local
	 *  device lacks the ability do display a passkey. If set
	 *  to non-NULL the cancel callback must also be provided, since
	 *  this is the only way the application can find out that it should
	 *  stop displaying the passkey.
	 *
	 *  @param conn Connection where pairing is currently active.
	 *  @param passkey Passkey to show to the user.
	 */
	void (*passkey_display)(struct bt_conn *conn, unsigned int passkey);

#if defined(CONFIG_BT_PASSKEY_KEYPRESS)
	/** @brief Receive Passkey Keypress Notification during pairing
	 *
	 *  This allows the remote device to use the local device to give users
	 *  feedback on the progress of entering the passkey over there. This is
	 *  useful when the remote device itself has no display suitable for
	 *  showing the progress.
	 *
	 *  The handler of this callback is expected to keep track of the number
	 *  of digits entered and show a password-field-like feedback to the
	 *  user.
	 *
	 *  This callback is only relevant while the local side does Passkey
	 *  Display.
	 *
	 *  The event type is verified to be in range of the enum. No other
	 *  sanitization has been done. The remote could send a large number of
	 *  events of any type in any order.
	 *
	 *  @param conn The related connection.
	 *  @param type Type of event. Verified in range of the enum.
	 */
	void (*passkey_display_keypress)(struct bt_conn *conn,
					 enum bt_conn_auth_keypress type);
#endif
  1. passkey_entry callback的作用是提示用户输入passkey。Passkey entry配对过程中,等待用户输入passkey的时候,这个callback函数被调用。当passkey被用户输入后,应用需要调用bt_conn_auth_passkey_entry()API,将passkey从应用层传到底层。nrf\applications\nrf_desktop\src\modules\ble_passkey.c例子演示了passkey_entry callback的使用方法。
	/** @brief Request the user to enter a passkey.
	 *
	 *  When called the user is expected to enter a passkey. The passkey
	 *  must be in the range of 0 - 999999, and should be expected to
	 *  be zero-padded, as that's how the peer device will typically be
	 *  showing it (e.g. 37 would be shown as 000037).
	 *
	 *  Once the user has entered the passkey its value should be given
	 *  to the stack using the bt_conn_auth_passkey_entry() API.
	 *
	 *  This callback may be set to NULL, which means that the local
	 *  device lacks the ability to enter a passkey. If set to non-NULL
	 *  the cancel callback must also be provided, since this is the
	 *  only way the application can find out that it should stop
	 *  requesting the user to enter a passkey.
	 *
	 *  @param conn Connection where pairing is currently active.
	 */
	void (*passkey_entry)(struct bt_conn *conn);
  1. passkey_confirm callback的作用是从底层向应用层提供 passkey(user confirm value)并提示用户是否确认。Numeric Comparison方式的配对过程中,两边的设备都会计算出一个数字(user confirm value),需要给用户确认,确认它们是否相同。如果两边的数字相同,说明这两个设备是可以被用户信任的。用户确认后,配对才能继续。passkey_display提供的passkey实际上是user confirm value。另外,如果用户确认两边的数字相同,应用需要调用bt_conn_auth_passkey_confirm()API去通知底层确认值匹配;如果不同,应用需要调用bt_conn_auth_cancel()API去通知底层确认值不匹配。nrf\samples\bluetooth\peripheral_uart例子演示了passkey_confirm callback的使用方法。
	/** @brief Request the user to confirm a passkey.
	 *
	 *  When called the user is expected to confirm that the given
	 *  passkey is also shown on the peer device.. The passkey will
	 *  be in the range of 0 - 999999, and should be zero-padded to
	 *  always be six digits (e.g. 37 would be shown as 000037).
	 *
	 *  Once the user has confirmed the passkey to match, the
	 *  bt_conn_auth_passkey_confirm() API should be called. If the
	 *  user concluded that the passkey doesn't match the
	 *  bt_conn_auth_cancel() API should be called.
	 *
	 *  This callback may be set to NULL, which means that the local
	 *  device lacks the ability to confirm a passkey. If set to non-NULL
	 *  the cancel callback must also be provided, since this is the
	 *  only way the application can find out that it should stop
	 *  requesting the user to confirm a passkey.
	 *
	 *  @param conn Connection where pairing is currently active.
	 *  @param passkey Passkey to be confirmed.
	 */
	void (*passkey_confirm)(struct bt_conn *conn, unsigned int passkey);
  1. pairing_confirm callback和IO能力没有关系,它用来通知应用,收到了pairing request请求。这里留出的接口,可以让用户做出是否同意配对的选择。如果同意进行配对,那么应用需要调用bt_conn_auth_pairing_confirm()API表示同意;如果用户不同意进行配对,应用可以调用bt_conn_auth_cancel()取消配对。当使用fixed key或just works配对方式时,可以定义这个callback函数,提示用户是否同意配对,添加用户的控制权。nrf\samples\bluetooth\peripheral_mds例子演示了pairing_confirm callback的使用方法。
	/** @brief Request confirmation for an incoming pairing.
	 *
	 *  This callback will be called to confirm an incoming pairing
	 *  request where none of the other user callbacks is applicable.
	 *
	 *  If the user decides to accept the pairing the
	 *  bt_conn_auth_pairing_confirm() API should be called. If the
	 *  user decides to reject the pairing the bt_conn_auth_cancel() API
	 *  should be called.
	 *
	 *  This callback may be set to NULL, which means that the local
	 *  device lacks the ability to confirm a pairing request. If set
	 *  to non-NULL the cancel callback must also be provided, since
	 *  this is the only way the application can find out that it should
	 *  stop requesting the user to confirm a pairing request.
	 *
	 *  @param conn Connection where pairing is currently active.
	 */
	void (*pairing_confirm)(struct bt_conn *conn);
  1. oob_data_request callback和IO能力也没有关系,它用于OOB配对,通知应用,需要提供OOB数据了。当应用层准备好这些数据后,需要调用bt_le_oob_set_sc_data() API(SC OOB)或bt_le_oob_set_legacy_tk() API(Legacy OOB)将数据传给底层。nrf\samples\bluetooth\central_nfc_pairing例子演示了oob_data_request callback的使用方法。

	/** @brief Request the user to provide Out of Band (OOB) data.
	 *
	 *  When called the user is expected to provide OOB data. The required
	 *  data are indicated by the information structure.
	 *
	 *  For LE Secure Connections OOB pairing, the user should provide
	 *  local OOB data, remote OOB data or both depending on their
	 *  availability. Their value should be given to the stack using the
	 *  bt_le_oob_set_sc_data() API.
	 *
	 *  This callback must be set to non-NULL in order to support OOB
	 *  pairing.
	 *
	 *  @param conn Connection where pairing is currently active.
	 *  @param info OOB pairing information.
	 */
	void (*oob_data_request)(struct bt_conn *conn,
				 struct bt_conn_oob_info *info);
  1. pairing_accept callback只有在设置了CONFIG_BT_SMP_APP_PAIRING_ACCEPT=y的情况下,才能使用。pairing_accept和pairing_confirm的功能上有点重合。pairing_confirm收到pairing req会被调用;pairing_accept收到pairing req或rsp都会被调用。用户通过它们可以去干预与和控制配对过程。nrf\applications\nrf_desktop\src\modules\fast_pair.c例子演示了pairing_accept callback的使用方法。
	 *  On any incoming pairing req/rsp this callback will be called for
	 *  the application to decide whether to allow for the pairing to
	 *  continue.
	 *
	 *  The pairing info received from the peer is passed to assist
	 *  making the decision.
	 *
	 *  As this callback is synchronous the application should return
	 *  a response value immediately. Otherwise it may affect the
	 *  timing during pairing. Hence, this information should not be
	 *  conveyed to the user to take action.
	 *
	 *  The remaining callbacks are not affected by this, but do notice
	 *  that other callbacks can be called during the pairing. Eg. if
	 *  pairing_confirm is registered both will be called for Just-Works
	 *  pairings.
	 *
	 *  This callback may be unregistered in which case pairing continues
	 *  as if the Kconfig flag was not set.
	 *
	 *  This callback is not called for BR/EDR Secure Simple Pairing (SSP).
	 *
	 *  @param conn Connection where pairing is initiated.
	 *  @param feat Pairing req/resp info.
	 */
	enum bt_security_err (*pairing_accept)(struct bt_conn *conn,
			      const struct bt_conn_pairing_feat *const feat);
  1. cancel callback通知应用,之前的用户请求(密码显示、输入 或确认)被取消了。应用通过cancel callback函数可以提示用户,配对被取消了。只要定义了

passkey_display、passkey_entry、 passkey_confirm 或 pairing_confirm,那么就必须定义cancel callback。

	/** @brief Cancel the ongoing user request.
	 *
	 *  This callback will be called to notify the application that it
	 *  should cancel any previous user request (passkey display, entry
	 *  or confirmation).
	 *
	 *  This may be set to NULL, but must always be provided whenever the
	 *  passkey_display, passkey_entry passkey_confirm or pairing_confirm
	 *  callback has been provided.
	 *
	 *  @param conn Connection where pairing is currently active.
	 */
	void (*cancel)(struct bt_conn *conn);

应用使用bt_conn_auth_cb_register()函数注册这些callback。例子片段如下:

//display only
static struct bt_conn_auth_cb conn_auth_callbacks = {
	.passkey_display = auth_passkey_display,
	.cancel = auth_cancel,
};
//Keyboard only
static struct bt_conn_auth_cb conn_auth_callbacks = {
	.passkey_entry = auth_passkey_entry,
	.cancel = auth_cancel,
};
//display yes no
static struct bt_conn_auth_cb conn_auth_callbacks = {
	.passkey_display = auth_passkey_display,
	.passkey_confirm = auth_passkey_confirm,
	.cancel = auth_cancel,
};
//Keyboard display
static struct bt_conn_auth_cb conn_auth_callbacks = {
	.passkey_display = auth_passkey_display,
	.passkey_entry = auth_passkey_entry,
	.passkey_confirm = auth_passkey_confirm,
	.cancel = auth_cancel,
};
err = bt_conn_auth_cb_register(&conn_auth_callbacks);

(7) 结构体struct bt_conn_auth_info_cb用于通知应用,配对结果(成功或失败)以及绑定信息删除结果。如果应用对这些信息感兴趣可以定义相应的callback函数。


/** Authenticated pairing information callback structure */
struct bt_conn_auth_info_cb {
	/** @brief notify that pairing procedure was complete.
	 *
	 *  This callback notifies the application that the pairing procedure
	 *  has been completed.
	 *
	 *  @param conn Connection object.
	 *  @param bonded Bond information has been distributed during the
	 *                pairing procedure.
	 */
	void (*pairing_complete)(struct bt_conn *conn, bool bonded);

	/** @brief notify that pairing process has failed.
	 *
	 *  @param conn Connection object.
	 *  @param reason Pairing failed reason
	 */
	void (*pairing_failed)(struct bt_conn *conn,
			       enum bt_security_err reason);

	/** @brief Notify that bond has been deleted.
	 *
	 *  This callback notifies the application that the bond information
	 *  for the remote peer has been deleted
	 *
	 *  @param id   Which local identity had the bond.
	 *  @param peer Remote address.
	 */
	void (*bond_deleted)(uint8_t id, const bt_addr_le_t *peer);

	/** Internally used field for list handling */
	sys_snode_t node;
};

应用使用bt_conn_auth_info_cb_register()函数注册这些callback。例子片段如下:

static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
	.pairing_complete = pairing_complete,
	.pairing_failed = pairing_failed
};
err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);

注:本文中的图表主要摘自Bluetooth Core Specification,Bluetooth LE Security Study Guide以及Nordic官网资料;代码摘自nRF Connect SDK 2.6.0

订阅Nordic新闻简报

了解最新信息!订阅后即可获取最新Nordic及物联网资讯

立即订阅