From 7aeb7384cdd651f17cfb2287c065fc227b76988f Mon Sep 17 00:00:00 2001
From: jhchun <jh.chun@medithings.co.kr>
Date: Tue, 4 Aug 2026 16:18:01 +0900
Subject: [PATCH] =?UTF-8?q?Allowlist=20-=20pairing=20window=20open/close?=
 =?UTF-8?q?=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 신규 페어링 제한 및 인증 실패 제한- Allowlist: 등록된 central만 페어링 기본 허용- 출고 직후 / bond 없는 상태: pairing window open  → central이 연결 후 처음 페어링 성공  → 본딩 정보 저장 : 페어링된 central 등록, pairing window close  → 이후 평상시에는 등록된 central만 연결 허용- 재등록/초기화  → 전원 버튼 15초 롱프레스로 본딩 정보 삭제  → 10분간 pairing window open  → 10분 내 페어링 완료 시 본딩 정보 저장 : central 등록, pairing window close  → 10분 내 페어링 미완료 시 : pairing window close
---
 src/ble/ble_service.c             | 124 +++++++++++++++++++++++++++++-
 src/ble/ble_service.h             |   1 +
 src/command/handlers/cmd_device.c |   2 +
 src/power/power_control.c         |   2 +
 4 files changed, 125 insertions(+), 4 deletions(-)

diff --git a/src/ble/ble_service.c b/src/ble/ble_service.c
index 44531fc..e4ecd9f 100644
--- a/src/ble/ble_service.c
+++ b/src/ble/ble_service.c
@@ -15,6 +15,7 @@
 #include <zephyr/bluetooth/conn.h>
 #include <zephyr/bluetooth/uuid.h>
 #include <zephyr/bluetooth/gatt.h>
+#include <hal/nrf_power.h>
 #if IS_ENABLED(CONFIG_SETTINGS)
 #include <zephyr/settings/settings.h>
 #endif
@@ -41,7 +42,14 @@ static uint16_t current_notify_mtu = 23U;
 
 #if IS_ENABLED(CONFIG_BT_SMP)
 #define BLE_REQUIRED_SECURITY_LEVEL    BT_SECURITY_L4
+#define PAIRING_WINDOW_TIMEOUT_MS      (10 * 60 * 1000)
+#define PAIRING_WINDOW_GPREGRET_REG    0U
+#define PAIRING_WINDOW_GPREGRET_MAGIC  0xB0U
 static uint32_t pairing_passkey = BT_PASSKEY_RAND;
+static bool bond_peer_exists;
+static bool pairing_window_open;
+static bool pairing_window_limited;
+static struct k_work_delayable pairing_window_timeout_work;
 #endif
 
 /*
@@ -132,6 +140,84 @@ static void ble_log_local_identity(void)
 
 #if IS_ENABLED(CONFIG_BT_SMP)
 /* NUS 접근 전 보안 level 확인 */
+static bool ble_conn_is_secure(const struct bt_conn *conn);
+
+static void pairing_window_close(void)
+{
+    pairing_window_open = false;
+    pairing_window_limited = false;
+    k_work_cancel_delayable(&pairing_window_timeout_work);
+    DBG_CORE("[BLE] Pairing window closed\r\n");
+}
+
+static void pairing_window_open_set(bool limited)
+{
+    pairing_window_open = true;
+    pairing_window_limited = limited;
+    if (limited)
+    {
+        k_work_schedule(&pairing_window_timeout_work, K_MSEC(PAIRING_WINDOW_TIMEOUT_MS));
+        DBG_CORE("[BLE] Pairing window opened (10min)\r\n");
+    }
+    else
+    {
+        k_work_cancel_delayable(&pairing_window_timeout_work);
+        DBG_CORE("[BLE] Pairing window opened (factory)\r\n");
+    }
+}
+
+static void pairing_window_timeout_handler(struct k_work *work)
+{
+    ARG_UNUSED(work);
+    if (pairing_window_limited)
+    {
+        pairing_window_close();
+        if (current_conn != NULL && !ble_conn_is_secure(current_conn))
+        {
+            DBG_ERR("[BLE] Pairing window timeout -> disconnect unsecured peer\r\n");
+            (void)bt_conn_disconnect(current_conn, BT_HCI_ERR_AUTH_FAIL);
+        }
+    }
+}
+
+static void bond_check_cb(const struct bt_bond_info *info, void *user_data)
+{
+    bool *found = (bool *)user_data;
+    ARG_UNUSED(info);
+    *found = true;
+}
+
+static bool ble_consume_pairing_window_reset_request(void)
+{
+#if NRF_POWER_HAS_GPREGRET
+    if (nrf_power_gpregret_get(NRF_POWER, PAIRING_WINDOW_GPREGRET_REG) == PAIRING_WINDOW_GPREGRET_MAGIC)
+    {
+        nrf_power_gpregret_set(NRF_POWER, PAIRING_WINDOW_GPREGRET_REG, 0U);
+        return true;
+    }
+#endif
+    return false;
+}
+
+static void ble_pairing_window_init_from_bonds(void)
+{
+    bool reset_request = ble_consume_pairing_window_reset_request();
+
+    bond_peer_exists = false;
+    bt_foreach_bond(BT_ID_DEFAULT, bond_check_cb, &bond_peer_exists);
+
+    DBG_CORE("[BLE] Bond peer exists=%u reset_window=%u\r\n", bond_peer_exists ? 1U : 0U, reset_request ? 1U : 0U);
+
+    if (bond_peer_exists)
+    {
+        pairing_window_close();
+    }
+    else
+    {
+        pairing_window_open_set(reset_request);
+    }
+}
+
 static bool ble_conn_is_secure(const struct bt_conn *conn)
 {
     return (conn != NULL) && (bt_conn_get_security(conn) >= BLE_REQUIRED_SECURITY_LEVEL);
@@ -190,8 +276,16 @@ static int ble_security_configure(void)
 /* Just Works/passkey pairing 요청 자동 승인 */
 static void auth_pairing_confirm(struct bt_conn *conn)
 {
-    int err = bt_conn_auth_pairing_confirm(conn);
+    int err;
 
+    if (!pairing_window_open)
+    {
+        DBG_ERR("[BLE] Pairing rejected: window closed\r\n");
+        (void)bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
+        return;
+    }
+
+    err = bt_conn_auth_pairing_confirm(conn);
     if (err)
     {
         DBG_ERR("[BLE] Pairing confirm failed err=%d\r\n", err);
@@ -214,6 +308,13 @@ static uint32_t auth_app_passkey(struct bt_conn *conn)
 {
     unsigned int passkey;
 
+    if (!pairing_window_open)
+    {
+        DBG_ERR("[BLE] Passkey rejected: pairing window closed\r\n");
+        (void)bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
+        return pairing_passkey;
+    }
+
     ARG_UNUSED(conn);
 
     if (ble_passkey_to_uint(m_static_passkey, &passkey) == 0)
@@ -241,6 +342,11 @@ 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);
+    if (bonded)
+    {
+        bond_peer_exists = true;
+        pairing_window_close();
+    }
 }
 
 /* pairing 실패 시 연결 종료 */
@@ -546,11 +652,10 @@ static void le_param_updated(struct bt_conn *conn, uint16_t interval, uint16_t l
 /* BLE security level 변경 결과 확인 */
 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);
+        DBG_ERR("[BLE] Security failed level=%u err=%u window=%u\r\n", level, err, pairing_window_open ? 1U : 0U);
+        (void)bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
         return;
     }
 
@@ -624,6 +729,9 @@ int ble_service_init(ble_data_rx_cb_t rx_cb)
     k_work_init(&adv_restart_work, adv_restart_handler);
     k_work_init_delayable(&adv_timeout_work, adv_timeout_handler);
     k_work_init_delayable(&conn_param_update_work, conn_param_update_handler);
+#if IS_ENABLED(CONFIG_BT_SMP)
+    k_work_init_delayable(&pairing_window_timeout_work, pairing_window_timeout_handler);
+#endif
     ble_tx_power_init();
     // BLE stack 활성화
     err = bt_enable(NULL);
@@ -653,6 +761,7 @@ int ble_service_init(ble_data_rx_cb_t rx_cb)
     {
         return err;
     }
+    ble_pairing_window_init_from_bonds();
 #endif
 
     // NUS service 초기화
@@ -855,6 +964,13 @@ int ble_data_send(const uint8_t *data, uint16_t len)
 }
 
 /* BLE 연결 상태 */
+void ble_pairing_window_mark_reset_open(void)
+{
+#if IS_ENABLED(CONFIG_BT_SMP) && NRF_POWER_HAS_GPREGRET
+    nrf_power_gpregret_set(NRF_POWER, PAIRING_WINDOW_GPREGRET_REG, PAIRING_WINDOW_GPREGRET_MAGIC);
+#endif
+}
+
 bool ble_is_connected(void)
 {
     return (current_conn != NULL);
diff --git a/src/ble/ble_service.h b/src/ble/ble_service.h
index 8cd566b..44ff1a8 100644
--- a/src/ble/ble_service.h
+++ b/src/ble/ble_service.h
@@ -36,5 +36,6 @@ int ble_dfu_advertising_enable(void);
 bool ble_dfu_advertising_is_enabled(void);
 bool ble_is_connected(void);
 int ble_disconnect_active(void);
+void ble_pairing_window_mark_reset_open(void);
 
 #endif /* BLE_SERVICE_H__ */
diff --git a/src/command/handlers/cmd_device.c b/src/command/handlers/cmd_device.c
index 270cd5f..646d680 100644
--- a/src/command/handlers/cmd_device.c
+++ b/src/command/handlers/cmd_device.c
@@ -76,10 +76,12 @@ int cmd_msr(const uint8_t *data, uint8_t data_len)
     {
         led_set_state(LED_STATE_BOND_DELETE);
         bond_data_delete = true;
+        ble_pairing_window_mark_reset_open();
         DBG_PRINTF("[CMD] msr bond data deleted\r\n");
     }
 #else
     bond_data_delete = true;
+    ble_pairing_window_mark_reset_open();
     DBG_PRINTF("[CMD] msr bond delete skipped (BT_SMP disabled)\r\n");
 #endif
 
diff --git a/src/power/power_control.c b/src/power/power_control.c
index 858d2b0..b5fe45a 100644
--- a/src/power/power_control.c
+++ b/src/power/power_control.c
@@ -88,10 +88,12 @@ static void bond_reset_work_handler(struct k_work *work)
     else
     {
         bond_data_delete = true;
+        ble_pairing_window_mark_reset_open();
         DBG_PRINTF("[BTN] BLE bond data deleted\r\n");
     }
 #else
     bond_data_delete = true;
+    ble_pairing_window_mark_reset_open();
     DBG_PRINTF("[BTN] BLE bond delete skipped (BT_SMP disabled)\r\n");
 #endif
 
