BLE 보안, 패스키 등

This commit is contained in:
2026-06-19 11:08:19 +09:00
parent 87d3e1972e
commit 65b42df182
4 changed files with 292 additions and 33 deletions
+254 -9
View File
@@ -15,8 +15,12 @@
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#if IS_ENABLED(CONFIG_SETTINGS)
#include <zephyr/settings/settings.h>
#endif
#include <zephyr/mgmt/mcumgr/transport/smp_bt.h>
#include <bluetooth/services/nus.h>
#include <errno.h>
#include "ble_service.h"
#include "main.h"
@@ -30,6 +34,12 @@ LOG_MODULE_REGISTER(ble_svc, LOG_LEVEL_INF);
*============================================================================*/
static struct bt_conn *current_conn;
static ble_data_rx_cb_t rx_callback;
static bool dfu_advertising_mode;
#if IS_ENABLED(CONFIG_BT_SMP)
#define BLE_REQUIRED_SECURITY_LEVEL BT_SECURITY_L4
static uint32_t pairing_passkey = BT_PASSKEY_RAND;
#endif
/*
* NUS 전송은 "보냈다" 호출만 한다고 바로 끝나는 게 아니다.
@@ -118,6 +128,142 @@ static int ble_configure_fixed_identity(void)
return 0;
}
#if IS_ENABLED(CONFIG_BT_SMP)
static bool ble_conn_is_secure(const struct bt_conn *conn)
{
return (conn != NULL) && (bt_conn_get_security(conn) >= BLE_REQUIRED_SECURITY_LEVEL);
}
static int ble_passkey_to_uint(const char *passkey, unsigned int *value)
{
unsigned int parsed = 0U;
if ((passkey == NULL) || (value == NULL))
{
return -EINVAL;
}
for (size_t i = 0; i < PASSKEY_LENGTH; i++)
{
if ((passkey[i] < '0') || (passkey[i] > '9'))
{
return -EINVAL;
}
parsed = (parsed * 10U) + (unsigned int)(passkey[i] - '0');
}
*value = parsed;
return 0;
}
static int ble_security_configure(void)
{
unsigned int passkey;
int err = ble_passkey_to_uint(m_static_passkey, &passkey);
if (err)
{
DBG_ERR("[BLE] Invalid stored passkey, using default\r\n");
memset(m_static_passkey, 0, sizeof(m_static_passkey));
memcpy(m_static_passkey, DEFAULT_PASSKEY, PASSKEY_LENGTH);
err = ble_passkey_to_uint(m_static_passkey, &passkey);
if (err)
{
return err;
}
}
pairing_passkey = passkey;
DBG_CORE("[BLE] Fixed passkey configured\r\n");
return 0;
}
static void auth_pairing_confirm(struct bt_conn *conn)
{
int err = bt_conn_auth_pairing_confirm(conn);
if (err)
{
DBG_ERR("[BLE] Pairing confirm failed err=%d\r\n", err);
return;
}
DBG_CORE("[BLE] Pairing confirmed\r\n");
}
static void auth_cancel(struct bt_conn *conn)
{
ARG_UNUSED(conn);
DBG_ERR("[BLE] Pairing cancelled\r\n");
}
#if IS_ENABLED(CONFIG_BT_APP_PASSKEY)
static uint32_t auth_app_passkey(struct bt_conn *conn)
{
unsigned int passkey;
ARG_UNUSED(conn);
if (ble_passkey_to_uint(m_static_passkey, &passkey) == 0)
{
return passkey;
}
DBG_ERR("[BLE] Invalid runtime passkey, using configured fallback\r\n");
return pairing_passkey;
}
#endif
static struct bt_conn_auth_cb auth_cb =
{
.pairing_confirm = auth_pairing_confirm,
.cancel = auth_cancel,
#if IS_ENABLED(CONFIG_BT_APP_PASSKEY)
.app_passkey = auth_app_passkey,
#endif
};
static void auth_pairing_complete(struct bt_conn *conn, bool bonded)
{
ARG_UNUSED(conn);
DBG_CORE("[BLE] Pairing complete bonded=%u\r\n", bonded ? 1U : 0U);
}
static void auth_pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
{
DBG_ERR("[BLE] Pairing failed reason=%u\r\n", reason);
(void)bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
}
static struct bt_conn_auth_info_cb auth_info_cb =
{
.pairing_complete = auth_pairing_complete,
.pairing_failed = auth_pairing_failed,
};
static int ble_security_init(void)
{
int err = bt_conn_auth_cb_register(&auth_cb);
if (err)
{
DBG_ERR("[BLE] auth cb register failed err=%d\r\n", err);
return err;
}
err = bt_conn_auth_info_cb_register(&auth_info_cb);
if (err)
{
DBG_ERR("[BLE] auth info cb register failed err=%d\r\n", err);
return err;
}
return ble_security_configure();
}
#endif
static void adv_restart_handler(struct k_work *work)
{
ARG_UNUSED(work);
@@ -136,13 +282,14 @@ static const struct bt_data ad[] =
static const struct bt_data sd[] =
{
/*
* Legacy NUS + DFU(SMP) UUIDs together exceed the 31-byte scan response
* limit for legacy advertising. Keep the device name in the advertising
* packet and expose only SMP here so DFU remains discoverable.
BT_DATA_BYTES(BT_DATA_UUID128_SOME, SMP_BT_SVC_UUID_VAL), // BLE DFU 동작을 위한 SMP UUDI 광고 */
BT_DATA_BYTES(BT_DATA_UUID128_SOME, BT_UUID_NUS_VAL), // UUID에 NUS 안 넣으면 Web bluetooth 앱에서 안 보임
/* 평상시 Web Bluetooth 앱이 찾을 수 있도록 NUS UUID 광고 */
BT_DATA_BYTES(BT_DATA_UUID128_SOME, BT_UUID_NUS_VAL),
};
static const struct bt_data sd_dfu[] =
{
/* DFU 진입 후 mcumgr 앱이 찾을 수 있도록 SMP UUID 광고 */
BT_DATA_BYTES(BT_DATA_UUID128_SOME, SMP_BT_SVC_UUID_VAL),
};
/*==============================================================================
@@ -175,6 +322,17 @@ static void connected(struct bt_conn *conn, uint8_t err)
};
bt_conn_le_param_update(conn, &conn_param);
#if IS_ENABLED(CONFIG_BT_SMP)
err = bt_conn_set_security(conn, BLE_REQUIRED_SECURITY_LEVEL);
if (err)
{
DBG_ERR("[BLE] Security request failed err=%d\r\n", err);
(void)bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
return;
}
DBG_CORE("[BLE] Security requested level=%u\r\n", BLE_REQUIRED_SECURITY_LEVEL);
#endif
led_set_state(LED_STATE_OFF);
DBG_CORE("[BLE] Connected\r\n");
}
@@ -201,11 +359,30 @@ static void le_param_updated(struct bt_conn *conn, uint16_t interval,
interval, latency, timeout);
}
#if IS_ENABLED(CONFIG_BT_SMP)
static void security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err err)
{
ARG_UNUSED(conn);
if (err)
{
DBG_ERR("[BLE] Security failed level=%u err=%u\r\n", level, err);
return;
}
DBG_CORE("[BLE] Security ready level=%u\r\n", level);
}
#endif
BT_CONN_CB_DEFINE(conn_callbacks) =
{
.connected = connected,
.disconnected = disconnected,
.le_param_updated = le_param_updated,
#if IS_ENABLED(CONFIG_BT_SMP)
.security_changed = security_changed,
#endif
};
/*==============================================================================
@@ -213,7 +390,15 @@ BT_CONN_CB_DEFINE(conn_callbacks) =
*============================================================================*/
static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len)
{
#if IS_ENABLED(CONFIG_BT_SMP)
if (!ble_conn_is_secure(conn))
{
DBG_ERR("[NUS RX] blocked until BLE security is ready\r\n");
return;
}
#else
ARG_UNUSED(conn);
#endif
DBG_CORE("[NUS RX] len=%u\r\n", len);
if (rx_callback)
@@ -264,8 +449,26 @@ int ble_service_init(ble_data_rx_cb_t rx_cb)
}
DBG_CORE("[BLE] Stack enabled\r\n");
#if IS_ENABLED(CONFIG_BT_SETTINGS)
err = settings_load_subtree("bt");
if (err)
{
DBG_ERR("[BLE] BT settings load failed err=%d\r\n", err);
return err;
}
DBG_CORE("[BLE] BT settings loaded\r\n");
#endif
ble_log_local_identity();
#if IS_ENABLED(CONFIG_BT_SMP)
err = ble_security_init();
if (err)
{
return err;
}
#endif
/* Initialize NUS */
err = bt_nus_init(&nus_cb);
if (err)
@@ -281,6 +484,8 @@ int ble_service_init(ble_data_rx_cb_t rx_cb)
int ble_advertising_start(void)
{
int err;
const struct bt_data *scan_data = dfu_advertising_mode ? sd_dfu : sd;
size_t scan_data_count = dfu_advertising_mode ? ARRAY_SIZE(sd_dfu) : ARRAY_SIZE(sd);
struct bt_le_adv_param adv_param = BT_LE_ADV_PARAM_INIT(
BT_LE_ADV_OPT_CONN,
@@ -289,14 +494,14 @@ int ble_advertising_start(void)
NULL /* undirected */
);
err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), scan_data, scan_data_count);
if (err)
{
DBG_ERR("[BLE] Adv start failed (err %d)\r\n", err);
return err;
}
DBG_PRINTF("[BLE] Advertising started\r\n");
DBG_PRINTF("[BLE] Advertising started (%s)\r\n", dfu_advertising_mode ? "SMP" : "NUS");
return 0;
}
@@ -313,6 +518,38 @@ int ble_advertising_stop(void)
return 0;
}
int ble_dfu_advertising_enable(void)
{
if (dfu_advertising_mode)
{
DBG_ERR("[BLE] DFU advertising already enabled\r\n");
return -EALREADY;
}
/* mdf? 응답 전송 후 현재 연결을 끊고 SMP UUID 광고로 재시작 */
dfu_advertising_mode = true;
DBG_PRINTF("[BLE] DFU advertising mode enabled\r\n");
if (current_conn)
{
int err = bt_conn_disconnect(current_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err)
{
DBG_ERR("[BLE] DFU disconnect failed err=%d\r\n", err);
return err;
}
return 0;
}
(void)ble_advertising_stop();
return ble_advertising_start();
}
bool ble_dfu_advertising_is_enabled(void)
{
return dfu_advertising_mode;
}
int ble_data_send(const uint8_t *data, uint16_t len)
{
int err;
@@ -323,6 +560,14 @@ int ble_data_send(const uint8_t *data, uint16_t len)
return -ENOTCONN;
}
#if IS_ENABLED(CONFIG_BT_SMP)
if (!ble_conn_is_secure(current_conn))
{
DBG_ERR("[NUS TX] blocked until BLE security is ready\r\n");
return -EACCES;
}
#endif
/*
* NUS는 한 번에 한 패킷씩 질서 있게 보내는 편이 안전하다.
* 특히 mbb?는 rbb + reb x6 + raa처럼 여러 응답을 연속 전송하므로