This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

How to add just works security to Battery Service (BAS) with nRF Connect SDK(NCS)

I would like to add Just Works security to our battery service (BAS). However, I could not find such a setting anywhere, so I looked into it and found that in bas.c
BT_GATT_SERVICE_DEFINE(bas,
BT_GATT_PRIMARY_SERVICE(BT_UUID_BAS),
BT_GATT_CHARACTERISTIC(BT_UUUID_BAS_BATTERY_LEVEL,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ, read_blvl, NULL,
&battery_level),
BT_GATT_CCC(blvl_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);
I found that it is defined as I understand that I should change this BT_GATT_PERM_READ to BT_GATT_PERM_READ_ENCRYPT or BT_GATT_PERM_READ_AUTHEN, but I do not know how exactly to make that change.
Also, this bas.c file is automatically added to the project with CONFIG_BT_BAS=y in prj.conf and I could not remove it from the project tree.

Translated with www.DeepL.com/Translator (free version) *sigh*

  • Hi Yoshihiro, 

    When you define BT_GATT_PERM_READ_ENCRYPT (instead of BT_GATT_PERM_READ)  it means the characteristic requires encryption for reading the value. If you choose BT_GATT_PERM_READ_AUTHEN the characteristic requires encryption with MITM protection. 

    In your case if you want Just Work, you can use BT_GATT_PERM_READ_ENCRYPT. 

    Make sure you don't declare any passkey_display and passkey_confirm callbacks in bt_conn_auth_cb(). Declaring these will enable the capability of your device to do MITM. 

  • Hi Hung,

    Thank you for the information. I tried it but I look like that security system doesn't work.

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

    bas_init.evt_handler = NULL;
    bas_init.support_notification = true;
    bas_init.p_report_ref = NULL;
    bas_init.initial_batt_level = 100;

    bas_init.bl_rd_sec = SEC_JUST_WORKS;
    bas_init.bl_cccd_wr_sec = SEC_JUST_WORKS;
    bas_init.bl_report_rd_sec = SEC_JUST_WORKS;

    err_code = ble_bas_init(&m_bas, &bas_init);
    APP_ERROR_CHECK(err_code);


    I had written this in the nRF SDK. I would like to implement this in the nRF Connect SDK as well.

  • Hi, 
    Please show how you configured your characteristics. Please use BT_GATT_PERM_READ_ENCRYPT  instead of BT_GATT_PERM_READ

  • Hmmm, I'm not sure, are you suggesting that I change the bas.c setting? The bas.c is a file provided by the system, right? Can the user edit it?

    My source code is here. There is nowhere to set BT_GATT_PERM_READ_ENCRYPT.

    /*
     * Copyright (c) 2012-2014 Wind River Systems, Inc.
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr.h>
    #include <logging/log.h>
    #include <sys/printk.h>
    #include <settings/settings.h>
    
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/gatt.h>
    #include <dk_buttons_and_leds.h>
    
    // LOG
    LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
    
    #define DEVICE_NAME CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    
    #if CONFIG_BT_EXT_ADV
    static const struct bt_data ad[] = {
        BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
        BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_BAS_VAL), BT_UUID_16_ENCODE(BT_UUID_DIS_VAL)),
        BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    static const struct bt_data sd[] = {
    };
    #else
    static const struct bt_data ad[] = {
        BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
        BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_BAS_VAL), BT_UUID_16_ENCODE(BT_UUID_DIS_VAL)),
    };
    static const struct bt_data sd[] = {
        BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    #endif
    
    static unsigned int retry_count;
    static struct bt_conn *pairing_confirmation_conn;
    
    static struct k_work pairing_work;
    struct pairing_data_mitm {
    	struct bt_conn *conn;
    	unsigned int passkey;
    };
    
    K_MSGQ_DEFINE(mitm_queue,
    	      sizeof(struct pairing_data_mitm),
    	      1,
    	      4);
    
    /* Bonded address queue. */
    K_MSGQ_DEFINE(bonds_queue, sizeof(bt_addr_le_t), CONFIG_BT_MAX_PAIRED, 4);
    
    static void bond_find(const struct bt_bond_info *info, void *user_data)
    {
        int err;
    
        err = k_msgq_put(&bonds_queue, (void *) &info->addr, K_NO_WAIT);
        if (err)
        {
            LOG_ERR("No space in the queue for the bond.");
        }
    }
    
    static void advertising_start_handler(struct k_work *work)
    {
        int err;
        bt_addr_le_t addr;
        struct bt_le_adv_param *adv_param;
        struct bt_le_ext_adv *ext_adv;
        char addr_buf[BT_ADDR_LE_STR_LEN];
    
        k_msgq_purge(&bonds_queue);
        bt_foreach_bond(BT_ID_DEFAULT, bond_find, NULL);
    
        if (!k_msgq_get(&bonds_queue, &addr, K_NO_WAIT))
        {
            adv_param = BT_LE_ADV_CONN_DIR(&addr);
    
            err = bt_le_adv_start(adv_param, NULL, 0, NULL, 0);
            if (err)
            {
                LOG_ERR("Directed advertising failed to start");
            }
            else
            {
                bt_addr_le_to_str(&addr, addr_buf, BT_ADDR_LE_STR_LEN);
                LOG_INF("Direct advertising to %s started", log_strdup(addr_buf));
            }
        }
        else if (IS_ENABLED(CONFIG_BT_EXT_ADV))
        {
            // Advertising with extension
            adv_param = BT_LE_ADV_PARAM(
                BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_EXT_ADV,
                BT_GAP_ADV_FAST_INT_MIN_2,
                BT_GAP_ADV_FAST_INT_MAX_2,
                NULL
            );
    
            err = bt_le_ext_adv_create(adv_param, NULL, &ext_adv);
            if (!err)
            {
                err = bt_le_ext_adv_set_data(ext_adv, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
                if (!err)
                {
                    err = bt_le_ext_adv_start(ext_adv, NULL);
                    if (!err)
                    {
                        LOG_INF("Start advertising with extension.");
                    }
                    else
                    {
                        LOG_ERR("Failed to start advertising set (%d)", err);
                    }
                }
                else
                {
                    LOG_ERR("Failed to set advertising data (%d)", err);
                }
            }
            else
            {
                LOG_ERR("Failed to create advertiser set (%d)", err);
            }
        }
        else
        {
            adv_param = BT_LE_ADV_CONN;
            err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
            if (err)
            {
                LOG_ERR("Advertising failed to start (err %d)", err);
            }
            else
            {
                LOG_INF("Regular advertising started.");
            }
        }
    }
    K_WORK_DEFINE(work_advertising_start, advertising_start_handler);
    
    static void connected(struct bt_conn *conn, uint8_t err)
    {
        char addr[BT_ADDR_LE_STR_LEN];
    
        if (err)
        {
            bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
            if (err == BT_HCI_ERR_ADV_TIMEOUT)
            {
                LOG_WRN("Direct advertising to %s timed out", log_strdup(addr));
            }
            else
            {
                LOG_ERR("Failed to connect to %s (%u)", log_strdup(addr), err);
            }
    
            // Advertising again
            k_work_submit(&work_advertising_start);
        }
        else
        {
            LOG_INF("Connected");
        }
    }
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
        LOG_INF("Disconnected(reason %u)", reason);
    
        // Advertising again
        k_work_submit(&work_advertising_start);
    }
    
    static struct bt_conn_cb conn_callbacks = {
        .connected    = connected,
        .disconnected = disconnected,
    };
    
    static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Passkey for %s: %06u\n", addr, passkey);
    }
    
    
    static void auth_passkey_confirm(struct bt_conn *conn, unsigned int passkey)
    {
    	int err;
    
    	struct pairing_data_mitm pairing_data;
    
    	pairing_data.conn    = bt_conn_ref(conn);
    	pairing_data.passkey = passkey;
    
    	err = k_msgq_put(&mitm_queue, &pairing_data, K_NO_WAIT);
    	if (err) {
    		printk("Pairing queue is full. Purge previous data.\n");
    	}
    
    	/* In the case of multiple pairing requests, trigger
    	 * pairing confirmation which needed user interaction only
    	 * once to avoid display information about all devices at
    	 * the same time. Passkey confirmation for next devices will
    	 * be proccess from queue after handling the earlier ones.
    	 */
    	if (k_msgq_num_used_get(&mitm_queue) == 1) {
    		k_work_submit(&pairing_work);
    	}
    }
    
    
    static void auth_cancel(struct bt_conn *conn)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing cancelled: %s\n", addr);
    }
    
    static void pairing_confirm(struct bt_conn *conn)
    {
            int err;
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	pairing_confirmation_conn = bt_conn_ref(conn);
    
    	if (pairing_confirmation_conn)
            {
                struct bt_conn *conn = pairing_confirmation_conn;
    
                pairing_confirmation_conn = NULL;
    
                err = bt_conn_auth_pairing_confirm(conn);
                if (err) {
                        printk("Failed to confirm the pairing: %d\n", err);
                } else {
                        printk("Pairing confirmed\n");
                }
    
                bt_conn_unref(conn);
            }
    
    	printk("Pairing confirmation required for %s\n", addr);
    	printk("Press Button 1 to confirm, Button 2 to reject.\n");
    }
    
    static void pairing_complete(struct bt_conn *conn, bool bonded)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing completed: %s, bonded: %d\n", addr, bonded);
    }
    
    static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing failed conn: %s, reason %d\n", addr, reason);
    
    	if (pairing_confirmation_conn) {
    		pairing_confirmation_conn = NULL;
    		bt_conn_unref(pairing_confirmation_conn);
    	}
    }
    
    static struct bt_conn_auth_cb conn_auth_callbacks = {
        .passkey_display = auth_passkey_display,
        .passkey_confirm = auth_passkey_confirm,
        .bond_deleted = NULL,
        .cancel = auth_cancel,
        .pairing_confirm = pairing_confirm,
        .pairing_complete = pairing_complete,
        .pairing_failed = pairing_failed
    };
    
    static void button_changed(uint32_t button_state, uint32_t has_changed)
    {
        int err;
        uint32_t buttons = button_state & has_changed;
    }
    
    void main(void)
    {
        int err = 0;
    
        // Init
        retry_count = 0;
    
        err = dk_buttons_init(button_changed);
        if (err)
        {
            LOG_ERR("Cannot init buttons (err: %d)", err);
            return;
        }
    
        // Callback
        bt_conn_cb_register(&conn_callbacks);
        err = bt_conn_auth_cb_register(&conn_auth_callbacks);
        if (err)
        {
            LOG_INF("Failed to register authorization callbacks.\n");
            return;
        }
    
        err = bt_enable(NULL);
        if (err)
        {
            LOG_ERR("Blutooth failed to start (err %d)", err);
            return;
        }
    
        if (IS_ENABLED(CONFIG_SETTINGS))
        {
            settings_load();
        }
    
        // Advertising
        k_work_submit(&work_advertising_start);
    }
    

  • Hi Yoshihiro, 

    As far as I can see there is no requirement for the BAS service to be open or encrypted in Bluetooth specification here. So the easiest solution is to modify the bas.c file for your need. 

    Another option is to create your own bas.c so you don't need to modify the original file in the SDK. In that case, don't included CONFIG_BT_BAS=y in your prj.conf. 

    You can also think of requiring encryption from the peripheral side when connected. But the central can reject that request and still can access the battery service. Also it's not recommended to request encryption from the peripheral side , especially when you connect to Apple product (iPhone). 

Related