1 简介
为了安全连接,必不可少的修改蓝牙的配对方式,提高产品的安全性。安全配置使充当从设备的GATT服务器能够与主设备绑定,并在它们之间建立加密链接。此功能由[蓝牙规范4.2版]定义(https://www.bluetooth.com/specifications/bluetooth-core-specification)并在ESP-IDF BLE堆栈上实现,特别是在安全管理器协议(SMP)API上实现。
BLE安全涉及三个相互关联的概念:配对、绑定和加密。
与交换所需的安全功能和密钥类型有关的配对问题。此外,配对过程还负责生成和交换共享密钥。核心规范定义了ESP32支持的传统配对和安全连接配对(在蓝牙4.2中引入)。一旦共享密钥的交换完成,就建立临时加密链路来交换短期和长期密钥。绑定是指为后续连接存储交换的密钥,这样它们就不必再次传输。最后,加密涉及使用AES-128引擎和共享密钥对纯文本数据进行加密。服务器属性也可以被定义为只允许加密的写入和读取消息。在通信的任何时候,从设备总是可以通过向另一个对等设备发出安全请求来请求开始加密,该对等设备通过调用API返回安全响应。
2 修改案例
2.1 创建移植工程
例程使用VS Code创建gatt_server例程,并在此例程上修改
2.2 添加安全绑定功能
2.2.1 设置安全参数
ESP32需要一系列安全参数,以定义如何构建配对请求和响应。GATT Server构建的配对响应数据包包括输入/输出功能、安全连接配对、经过身份验证的中间人(MITM)保护或无安全要求等字段(参见[Bluetooth Specification Version 4.2]的第2.3.1节)(https://www.bluetooth.com/specifications/bluetooth-core-specification)[第3卷,第H部分])。在本例中,此过程在app_main()
函数中完成。配对请求由启动器发送,在这种情况下,启动器是远程GATT客户端。在此示例中实现的ESP32服务器接收此请求并使用配对响应进行回复,该配对响应包含相同的安全参数,以便两个设备商定可用的资源和适用的配对算法(Just Works或Passkey Entry)。配对请求和响应命令都具有以下参数:
- IO功能:描述设备是否具有输入/输出功能,如显示器或键盘。
-
OOB标志:描述设备是否支持带外密钥交换,例如使用NFC或Wi-Fi将密钥交换为TK。
-
授权请求:表示请求的安全属性,如绑定、安全连接(SC)、MITM保护或不存在于配对请求和响应数据包中。
-
最大加密密钥大小:以八位字节为单位的最大加密密钥尺寸。
-
启动器密钥分发/生成:指示启动器在传输特定密钥分发阶段请求分发/生成或使用的密钥。在配对请求中,请求这些密钥,而在配对响应中,确认这些密钥被分发。
-
响应程序密钥分发/生成:指示发起者在传输特定密钥分发阶段请求响应程序分发/生成或使用的密钥。在配对请求中,请求这些密钥,而在配对响应中,确认这些密钥被分发。
在app_main中添加如下:
- IO Capability:
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;//set the IO capability to No Input No Output
IO Capability包括如下几种
ESP_IO_CAP_OUT 0 /*!< DisplayOnly */
ESP_IO_CAP_IO 1 /*!< DisplayYesNo */
ESP_IO_CAP_IN 2 /*!< KeyboardOnly */
ESP_IO_CAP_NONE 3 /*!< NoInputNoOutput */
ESP_IO_CAP_KBDISP 4 /*!< Keyboard display */
- Authorization Request:
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND; //bonding with peer device after authentication
Authorization Request的可能值是绑定、MITM保护和安全连接请求的组合:
ESP_LE_AUTH_NO_BOND: No bonding.
ESP_LE_AUTH_BOND: Bonding is performed.
ESP_LE_AUTH_REQ_MITM: MITM Protection is enabled.
ESP_LE_AUTH_REQ_SC_ONLY: Secure Connections without bonding enabled.
ESP_LE_AUTH_REQ_SC_BOND: Secure Connections with bonding enabled.
ESP_LE_AUTH_REQ_SC_MITM: Secure Connections with MITM Protection and no bonding enabled.
ESP_LE_AUTH_REQ_SC_MITM_BOND: Secure Connections with MITM Protection and bonding enabled.
- Maximum Encryption Key Size:
uint8_t key_size = 16; //the key size should be 7~16 bytes
- Initiator Key Distribution/Generation:
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
启动器通过设置EncKey和IdKey掩码来分发LTK和IRK密钥。
- Responder Key Distribution/Generation:
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
响应程序通过设置EncKey和IdKey掩码来分发LTK和IRK密钥。
定义后,使用“esp_ble_gap_set_security_param()”函数设置参数。此函数用于设置参数类型、参数值和参数长度:
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
这些信息足以让BLE堆栈执行配对过程,包括配对确认和密钥生成。该过程对用户不可见,并由堆栈自动执行。
2.2.2 连接和绑定到对等设备
先前设置的安全参数存储在本地,以便稍后在主设备连接到从设备时使用。每次远程设备连接到本地GATT服务器时,都会触发连接事件“ESP_GATTS_CONNECT_EVT”。此事件用于通过调用“esp_ble_set_encryption()”函数来执行配对和绑定过程,该函数将远程设备地址和将要执行的加密类型作为参数。BLE堆栈然后在后台执行实际的配对过程。在本例中,加密包括MITM保护,此权限可根据gatt 特征的权限进行修改。
将下列代码添加至一个gatt服务的handle里面,如gatts_profile_b_event_handler()等。
case ESP_GATTS_CONNECT_EVT:
//start security connect with peer device when receive the connect event sent by the master.
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
break;
可用的加密类型有:
ESP_BLE_SEC_NONE
ESP_BLE_SEC_ENCRYPT
ESP_BLE_SEC_ENCRYPT_NO_MITM
ESP_BLE_SEC_ENCRYPT_MITM
“ESP_BLE_SEC_ENCRYPT”和“ESP_BLE _SEC_ENCRYPT_NO_MITM”之间的区别在于,以前的连接可能具有需要升级的安全级别,因此需要再次交换密钥。
在本例中将“IO Capability”设置为“仅显示”,因此需要生成6位密钥。因此,根据远程设备的I/O能力,可以在ESP32上生成密钥,该密钥以“ESP_GAP_BLE_passkey_NOTIF_EVT”呈现给用户:
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
///show the passkey number to the user to input it in the peer device.
ESP_LOGE(GATTS_TABLE_TAG, "The passkey Notify number:%d", param->ble_security.key_notif.passkey);
break;
决定使用哪种算法的输入和输出能力的组合是:
Display Only | Display Yes/No | Keyboard Only | No Input No Output | Keyboard Display | |
---|---|---|---|---|---|
Display Only | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry |
Display Yes/No | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry |
Keyboard Only | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry |
No Input No Output | Just Works | Just Works | Just Works | Just Works | Just Works |
Keyboard Display | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry |
2.2.3 交换密钥
当客户端连接到服务器并且配对成功完成时,交换由“init_key”和“rsp_key”安全参数指示的密钥。在本例中,将生成并分发以下密钥:
- Local device LTK
- Local device IRK
- Local device CSRK
- Peer device LTK
- Peer device IRK
注意:
仅对于该示例,不交换对等设备CSRK。对于每个密钥交换消息,都会触发一个“ESP_GAP_BLE_KEY_EVT”事件,该事件可用于打印接收到的密钥类型:
case ESP_GAP_BLE_KEY_EVT:
//shows the ble key info share with peer device to the user.
ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
break;
当密钥交换成功后,配对过程就完成了。这会触发“ESP_GAP_BLE_AUTH_CMPL_EVET”事件,该事件用于打印远程设备、地址类型和配对状态等信息:
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr,
sizeof(esp_bd_addr_t));
ESP_LOGI(GATTS_TABLE_TAG, "remote BD_ADDR: %08x%04x",\
(bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) +
bd_addr[3],
(bd_addr[4] << 8) + bd_addr[5]);
ESP_LOGI(GATTS_TABLE_TAG, "address type = %d",
param->ble_security.auth_cmpl.addr_type);
ESP_LOGI(GATTS_TABLE_TAG, "pair status = %s",
param->ble_security.auth_cmpl.success ? "success" : "fail");
break;
}
2.2.4 属性的安全权限
在定义服务器的属性时,可以对写入和读取事件设置不同的权限。属性权限可以是:
- Permission to read
- Permission to read with encryption
- Permission to read with encryption and MITM protection
- Permission to write
- Permission to write with encryption
- Permission to write with encryption and MITM protection
- Permission to signed write
- Permission to signed write with MITM protection
这些权限在API中定义为:
ESP_GATT_PERM_READ
ESP_GATT_PERM_READ_ENCRYPTED
ESP_GATT_PERM_READ_ENC_MITM
ESP_GATT_PERM_WRITE
ESP_GATT_PERM_WRITE_ENCRYPTED
ESP_GATT_PERM_WRITE_ENC_MITM
ESP_GATT_PERM_WRITE_SIGNED
ESP_GATT_PERM_WRITE_SIGNED_MITM
创建服务表时,无论是否加密,每个属性都可以具有读取或写入权限。当某个属性具有加密权限,而不具有所需安全权限的对等设备试图读取或写入该属性时,本地主机会发送“授权不足错误”。在其他新的例子中,以下属性是使用加密权限定义的,本例无需添加:
…
// Body Sensor Location Characteristic Value
[HRS_IDX_BOBY_SENSOR_LOC_VAL] = {
{ESP_GATT_AUTO_RSP},
{ESP_UUID_LEN_16,
(uint8_t *)&body_sensor_location_uuid,
ESP_GATT_PERM_READ_ENCRYPTED,
sizeof(uint8_t),
sizeof(body_sensor_loc_val),
(uint8_t *)body_sensor_loc_val}
},
…
// Heart Rate Control Point Characteristic Value
[HRS_IDX_HR_CTNL_PT_VAL] = {
{ESP_GATT_AUTO_RSP},
{ESP_UUID_LEN_16,
(uint8_t *)&heart_rate_ctrl_point,
ESP_GATT_PERM_WRITE_ENCRYPTED|ESP_GATT_PERM_READ_ENCRYPTED,
sizeof(uint8_t),
sizeof(heart_ctrl_point),
(uint8_t *)heart_ctrl_point}
},
…
2.2.5 安全请求
在主设备和从设备之间的通信过程中,从设备可能随时通过发出安全请求命令来请求开始加密。此命令将在主机上触发“ESP_GAP_BLE_SEC_REQ_EVT”事件,该事件将向对等设备回复肯定(真)安全响应以接受请求,或回复否定(假)安全响应来拒绝请求。在本例中,此事件用于通过使用esp_ble_gap_security_rsp()
neneneba API调用来回复启动加密响应。需添加至gap_event_handler()中。
case ESP_GAP_BLE_SEC_REQ_EVT:
/* send the positive (true) security response to the peer device to accept the security request.
If not accept the security request, should send the security response with negative(false) accept value*/
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
2.2.6 总结
2.2部分在全新工程需要添加的内容,此工程只需要将2.1和2.5部分添加至工程即可。