ESP32 GAP API |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Просмотрите содержимое папки bluetooth/bluedroid/ble каталога установки ESP-IDF [2], которая содержит следующие демонстрационные примеры и соответствующую документацию: SMP security client (находится в каталоге bluetooth/bluedroid/ble/gatt_security_client). Этот демонстрационный проект инициализирует свои параметры безопасности и работает как клиент GATT, который может посылать security request устройству пира, и затем завершает процедуру шифрования. Примечание: незнакомые термины и сокращения см. в Словариках статей [5, 6]. Здесь представлен обзор примера кода GATT Security Client для ESP32. GATT Client может сканировать ближайшие устройства, и после того, как интересующее устройство было найдено, может запросить безопасное соединение с ним. GATT client ведет себя как главное устройство (master), которое инициирует соединение с подчиненным устройством (slave, которое работает как сервер, см. ниже описание SMP security server) путем отправки Pairing Request, как это указано в стандарте Bluetooth Core Specification Version 4.2. Удаленное slave-устройство это обычно GATT Server, который показывает свои службы (Services) и характеристики (Characteristics). Устройство slave отвечает пакетом Pairing Response, за котороым идет аутентификация и обмен ключами. Если при этом также выполняется процесс привязки (bonding), то ключи (Long Term Keys) сохраняются в энергонезависимой памяти, чтобы их можно было повторно использовать в будущем при последующих соединениях. В завершение устанавливается зашифрованный канал связи, который может поддерживать защиту от атак типа "промежуточное звено" (Man-In-The-Middle, MITM) в зависимости от конфигурации безопасности (security configuration). Код реализован с использованием Application Profile, который после регистрации позволяет установить локальную конфигурацию приватности, поскольку события запускаются в течение времени работы программы. Здесь также приведено описание аспектов безопасности реализации GATT Client, чтобы дать обзор, как определить такую функциональность GATT client, как сканирование параметров и открытие соединений. [Конфигурирование Local Privacy для Security Client] В примере регистрируется Application Profile следующим образом: #define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
Эта регистрация происходит из функции app_main() путем вызова API-функции esp_ble_gattc_app_register(): ...
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
if (ret) { ESP_LOGE(GATTC_TAG, "%s gattc app register error, error code = %x\n", __func__, ret); } ... Регистрация Application Profile вызывает срабатывание события ESP_GATTC_REG_EVT, которое обрабатывается callback-функцией esp_gattc_cb(), и перенаправляется в обработчик события профиля приложения, функцию gattc_profile_event_handler(). Здесь событие используется для конфигурирования local privacy подчиненного устройства путем вызова функции esp_ble_gap_config_local_privacy(). case ESP_GATTC_REG_EVT: ESP_LOGI(GATTC_TAG, "REG_EVT"); esp_ble_gap_config_local_privacy(true); break; Эта функция принадлежит к библиотеке Bluedroid API, она предназначена для конфигурирования настроек приватности по умолчанию на локальном устройстве. Как только privacy установлена, сработает событие ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT, которая используется для установки параметров сканирования, и запускает сканирование ближайших периферийных устройств: case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(GATTC_TAG, "config local privacy failed, error code =%x", param->local_privacy_cmpl.status); break; } esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); if (scan_ret) { ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret); } break; case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: // Единицы длительности для duration указываются в секундах: uint32_t duration = 30; esp_ble_gap_start_scanning(duration); break; case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: // Событие завершения запуска сканирования показывает, был ли // запуск сканирования удачным, или нет. if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status); break; } ESP_LOGI(GATTC_TAG, "Scan start success"); break; [Конфигурирование и Bonding для подчиненного устройства] Остальная конфигурация для GATT Client обычно выполняется таким же способом, как и в примере GATT Client. Т. е. клиент находит интересующее его устройство (GATT Server) и открывает с ним соединение. В этот момент GATT client, который обычно работает как master, инициирует pairing-процесс отправкой Pairing Request к slave-устройству. Этот запрос должен быть подтвержден ответом Pairing Response. Процесс Pairing реализован в стеке автоматически, и не требует от пользователя дополнительной конфигурации. Однако в зависимости от возможностей I/O для обоих устройств, может быть сгенерирован passkey (ключ пароля) на ESP32, что представляется пользователю с событием ESP_GAP_BLE_PASSKEY_NOTIF_EVT: case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // Приложение получит это событие, когда у него имеется возможность // IO Output, и устройство пира имеет возможность IO Input. Для // пользователю показывается число passkey, чтобы его можно было // ввести для подтверждения на устройстве пира. ESP_LOGE(GATTS_TABLE_TAG, "The passkey Notify number:%d", param->ble_security.key_notif.passkey); break; Комбинация возможностей по вводу и выводу определяет, какой будет использоваться алгоритм:
В методе Just Works временный ключ (Temporary Key) устанавливается в 0. Это практический способ для аутентификации устройств, у которых нет экрана или клавиатуры, поэтому нет способа показать или ввести passkey. Однако если у ESP32 GATT Client есть LCD, он может показать passkey, сгенерированный локально, чтобы пользователь мог ввести его на другом устройстве пира, или если у GATT Client есть клавиатура, то он может ввести passkey, сгенерированный на другом устройстве пира. Дополнительно числовое сравнение может быть выполнено, если оба устройства имеют экран и кнопки подтверждения yes/no, и используется LE Secure Connections, таким способом независимо сгенерированный passkey отображается на обоих устройствах, и пользователь вручную проверяют, что оба значения из 6 цифр совпали. [Обмен ключами] Когда клиент подключается к удаленному устройству, и pairing произошел успешно, происходит обмен ключами initiator и responder. Для каждого сообщения обмена ключами срабатывает событие ESP_GAP_BLE_KEY_EVT, которое может использоваться для вывода типа полученного ключа: case ESP_GAP_BLE_KEY_EVT: // Покажет пользователю информацию ключа, которым произошел // обмен с устройством пира. ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type)); break; Когда обмен ключами прошел успешно, процесс pairing завершен, и может начаться шифрование данных полезной нагрузки, выполняемое подсистемой AES-128. Это вызовет срабатывание событие ESP_GAP_BLE_AUTH_CMPL_EVT, которое используется для вывода информации: 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; В этом описании дан обзор реализованных аспектов безопасности GATT Client. BLE security охватывает Pairing, Bonding и Encryption. Чтобы установить защищенный линк между устройствами master и slave, устанавливается local privacy для GATT client, что позволяет стеку BLE автоматически установить необходимые параметры безопасности без необходимости в дополнительной конфигурации пользователя. Комбинация существующих возможностей взаимодействующих устройств определяет выпор подходящего метода для pairing, который затем выполнит стек BLE. Сразу после этого происходит генерация ключей и обмен ими, и начинается шифрование последующих сообщений с помощью подсистемы AES-128. Эти выполняемые шаги вызывают генерацию событий, которые обрабатываются соответствующими хендлерами GATT и GAP, которые могут печатать полезную информацию, такую как типы ключей, которыми произведен обмен, и pairing статус. Остальной функционал безопасности GATT client, такой как регистрация оповещений характеристик, реализуется примерно так, как сделано в этом примере проекта. См. также GitHub репозиторий [3]. SMP security server (находится в каталоге bluetooth/bluedroid/ble/gatt_security_server). Это демо инициирует свои параметры безопасности и работает как сервер GATT, который посылает pair request устройству пира, и затем завершает процедуру шифрования. Здесь приведен обзор примера GATT Server BLE для ESP32. Конфигурация безопасности позволяет серверу GATT работать в качестве подчиненного (slave) устройства, чтобы осуществить привязку (bond) с master-устройством (клиентом GATT), и установить с ним зашифрованный канал связи. Этот функционал определен в стандарте Bluetooth Specification version 4.2, и реализован стеком ESP-IDF BLE в API-функциях Security Manager Protocol (SMP). BLE security охватывает 3 внутренне связанные друг с другом концепции: Pairing, Bonding и Encryption. Pairing заключается в обмене поддерживаемыми функциями и типами безопасности и необходимыми ключами. Дополнительно процедура pairing заботится о генерации и обмене общими ключами. Основной стандарт определяет Legacy Pairing и Secure Connections Pairing (введенный в Bluetooth 4.2), оба этих варианта поддерживаются ESP32. Как только обмен ключами завершился, устанавливается временный шифрованный линк, чтобы произошел обмен ключами short term и long term. Bonding заключается в сохранении в энергонезависимой памяти ключей, которыми прошел обмен, чтобы они при будущих соединениях не передавались повторно. И наконец, Encryption относится к шифрованию передаваемых данных с использованием подсистемы AES-128 и обмененных ключей. Также могут быть определены атрибуты сервера, чтобы можно было шифровать только сообщения записи или только сообщения чтения. В любой точке обмена данными slave-устройство может запросить начать шифрование выдачей security request другому устройству пира, которое вернет security response вызовом соответствующего API. Здесь описывается только конфигурация безопасности. Остальной функционал сервера GATT, такой как определена в таблице служб, объясняется в документации примера GATT Server. Для лучшего понимания процессов в этом примере, рекомендуется ознакомиться с описанием pairing и генерацией ключей, описанных в секции 3.5 стандарта Bluetooth Specification Version 4.2 [Vol 3, Part H]. [Установка параметров безопасности] ESP32 требует серию параметров безопасности, чтобы определить, как должны строиться pairing request и pairing response. Пакет Pairing Response, который строит GATT Server, включает такие поля, как возможности input/output, Secure Connections pairing, аутентифицированную защиту от атак Man-In-The-Middle (MITM), либо отсутствие требований по безопасности (см. Section 2.3.1 стандарта Bluetooth Specification Version 4.2 [Vol 3, Part H]). В этом примере соответствующая процедура выполняется в функции app_main(). Запрос pairing посылается инициатором (initiator), которым в нашем случае выступает удаленный GATT client (см. его описание выше). Сервер ESP32, реализованный в этом примере, принимает запрос и отвечает пакетом pairing response, который содержит те же параметры безопасности, чтобы оба устройства пришли к соглашению по доступным для них ресурсам и применимому pairing-алгоритму (Just Works или Passkey Entry). И команды запроса pairing, и команды response, обе имеют следующие параметры: IO Capability: описывает, есть ли у устройства возможности ввода/вывода, такие как экран и/или клавиатура. В коде эти параметры определены следующим образом. IO Capability: // IO capability устанавливается в No Input No Output:
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; Возможны следующие значения для IO Capabilities: ESP_IO_CAP_OUT 0 /*!< только отображение */ ESP_IO_CAP_IO 1 /*!< отображение Yes/No */ ESP_IO_CAP_IN 2 /*!< только клавиатура */ ESP_IO_CAP_NONE 3 /*!< NoInput, NoOutput: ни ввода, ни вывода */ ESP_IO_CAP_KBDISP 4 /*!< клавиатура и экран */ Authorization Request: // Привязка (bonding) с устройством пира после аутентификации:
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND; Возможные значения для Authorization Request это комбинация запросов Bonding, защиты MITM и Secure Connections: ESP_LE_AUTH_NO_BOND: без bonding. Maximum Encryption Key Size: uint8_t key_size = 16; // размер ключа должен быть 7 .. 16 байт Initiator Key Distribution/Generation: uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; Инициатор распространяет ключи LTK и IRK установкой масок EncKey и IdKey. Responder Key Distribution/Generation: uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; Устройство responder распространяет ключи LTK и IRK установкой масок EncKey и IdKey. Будучи определенными, параметры устанавливаются функцией 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, чтобы он выполнил pairing-процесс, включая подтверждение pairing и генерацию ключа. Эта процедура невидима для пользователя и выполняется стеком автоматически. [Соединение и Bonding с устройством пира] Параметры безопасности, установленные ранее, сохраняются локально, чтобы использоваться позже, когда master-устройство подключится к slave-устройству. Каждый раз, когда удаленное устройство подключается к локальному серверу GATT, генерируется событие ESP_GATTS_CONNECT_EVT. Это событие реализовано для выполнения процесса pairing и bonding путем запуска функции esp_ble_set_encryption(), которая в качестве параметра получает адрес удаленного устройства и тип шифрования, которое будет выполняться. Затем стек BLE будет выполнять реальный процесс pairing-а в фоновом режиме. В этом примере шифрование включает защиту от атаки MITM. case ESP_GATTS_CONNECT_EVT: // Запускает безопасное соединение с устройством пира, когда примет // запрос соединения, посланное устройством 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 основано на факте, что предыдущее соединение могло иметь уровень безопасности, который должен быть обновлен, что требует повторного обмена ключами. В этом примере I/O capabilities установлены в No Input No Output, так что будет применен pairing-метод Just Works, который не требует генерации случайного passkey из 6 цифр (подробности см. в таблице ниже). Пользователь может изменить этот пример, чтобы установить I/O capabilities в другие возможные варианты. Таким образом, в зависимости от I/O capabilities удаленного устройства, может быть сгенерирован passkey в устройстве ESP32, который представлен для пользователя событием ESP_GAP_BLE_PASSKEY_NOTIF_EVT: case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // Приложение получит это событие, когда IO capability имеет возможность // вывода (Output), и устройство пира обладает возможностью ввода (Input). // Отобразить число passkey для пользователя, чтобы он ввел его на // устройстве пира. ESP_LOGE(GATTS_TABLE_TAG, "The passkey Notify number:%d", param->ble_security.key_notif.passkey); break; Комбинация возможностей ввода и вывода (IO capabilities), которая определяет используемый алгоритм авторизации:
[Обмен ключами] Когда клиент подключился к серверу и произошел pairing, происходит обмен ключами, показываемыми параметрами init_key и rsp_key. В этом примере генерируются и распространяются следующие ключи: • LTK локального устройства (LTK означает Long Term Key) Обратите внимание, что только для примера здесь ключом CSRK устройства пира обмен не происходит. Для каждого сообщения обмена ключами, генерируется событие ESP_GAP_BLE_KEY_EVT, которое может использоваться для печать типа полученного ключа: case ESP_GAP_BLE_KEY_EVT: // Покажет для пользователю информацию ключа BLE, общего с устройством пира. ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type)); break; Когда обмен ключами прошел успешно, процесс pairing-а завершен. Это вызывает генерацию события ESP_GAP_BLE_AUTH_CMPL_EVT, которое используется для печати такой информации, как адрес удаленного устройства, типа адреса и pairing-статуса: 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; } [Разрешения безопасности для атрибутов] Когда определяются атрибуты сервера, могут быть установлены различные разрешения для событий записи и чтения. Разрешениями для атрибутов могут быть: ESP_GATT_PERM_READ: разрешение на чтение. Когда создается таблица служб, у каждого атрибута может быть разрешение на чтение или запись, с шифрованием или без. Когда у атрибута установлены разрешения на шифрование, и устройство пира не имеет требуемого разрешения по безопасности и пробует читать или записывать этот атрибут, локальный хост посылает ошибку недостаточной авторизации (Insufficient Authorization Error). В примере с разрешениями на шифрование определены следующие атрибуты: ... // Значение характеристики Body Sensor Location: [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: [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} }, ... [Запросы безопасности] Во время обмена между устройствами master и slave устройство slave может в любой момент запросить начать шифрование путем выдачи команды запроса безопасности (security request command). Эта команда приведет к генерации события ESP_GAP_BLE_SEC_REQ_EVT на master-устройстве, который ответит положительным (true) security response устройству пира, чтобы принять запрос, или отрицательным (false) security response, чтобы отклонить запрос. В этом примере такое событие используется для ответа запуском шифрования с использованием API-функции esp_ble_gap_security_rsp(). case ESP_GAP_BLE_SEC_REQ_EVT: // Отправка положительного (true) security response устройству пира, // чтобы принять security request. Если бы security request нельзя // принять, то второй параметр должен быть false. esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); break; В этом описании дан обзор реализованных аспектов безопасности сервера GATT. BLE security охватывает Pairing, Bonding и Encryption. Чтобы установить защищенный линк между устройствами master и slave, устанавливаются параметры безопасности, определяющие возможности и функции каждого устройства. Комбинация существующих возможностей взаимодействующих устройств определяет выбор подходящего метода для pairing, который затем выполнит стек BLE. Сразу после этого происходит генерация ключей и обмен ими, и начинается шифрование последующих сообщений с помощью подсистемы AES-128 и этих ключей. Эти выполняемые шаги вызывают генерацию различных событий, которые обрабатываются соответствующими хендлерами GATT и GAP. Обработчики событий могут печатать полезную информацию, такую как типы ключей, которыми произведен обмен, и pairing статус. Кроме того, назначаются разрешения для атрибутов, которые определяют необходимость шифрования для чтения и/или записи. Остальной функционал безопасности сервера GATT, такой как определение служб и характеристик, реализуется примерно так, как сделано в этом примере проекта. См. также GitHub репозиторий [4]. [Справочник по API-функциям GAP API] Заголовочный файл: components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h.
Примечание: подробное описание функций, их параметров, используемых определений и типов данных см. в [1]. [Ссылки] 1. ESP32 GAP API site:docs.espressif.com. |