Creating a Heart Rate Service Peripheral Example Application That Requires Mode 1 Level 3 Security

I am using SDK v15.3 and would like to modify the peripheral heart rate service example to require the Central to establish mode 1, level 3 security. I am undecided on whether it will be numeric comparison, passkey or OOB, but would like to evaluate each.

If there is another example I could reference to help with the changes to the heart rate service example, please advise.

Parents
  • Hi,

    In Bluetooth, the security level is set per characteristic. Referring to the Heart Rate example in nRF5 SDK 15.3, you can see that the security level of the heart rate characteristics is set in services_init():

        // Here the sec level for the Heart Rate Service can be changed/increased.
        hrs_init.hrm_cccd_wr_sec = SEC_OPEN;
        hrs_init.bsl_rd_sec      = SEC_OPEN;

    If you want to use level 3, change this to SEC_MITM intend of SEC_OPEN. With this, security level 3 is required in order to access the specific characteristics. Note that the mode essentially means that MITM or OOB is used, so you do not need to do other changes in this part regardless of if you are using numeric comparison, passkey or OOB.

    The second part is to expand the example to use the MITM protection method you want to use/test. For that, you can refer to other examples and copy-past in relevant parts. For instance, ble_app_gls use passkey (with the nRF being the display), printing the passkey over UART. Which MITM method that works is depending on the I/O capabilities of your product. If it has both a keyboard and a display, you can use numeric comparison (only with LE secure connections and provided the peer also supports it). If it has a keyboard or a display, it can use passkey, provided the peer has the opposite (or both). For OOB you would typically need something application specific.

  • I found the hrm_ccd_wr_sec setting and changed it to SEC_JUST_WORKS as a start. This resulted in an "Insuffcient Authentication" error when the Central attempted to enable the characteristic notification.

    I then enabled the peer manager functionality in the Central to manage the bonding process. That all worked as expected. I am now struggling with the reconnection after both Central and Peripheral have stored the bonding information. I can see that the Central successfully reaches the sd_ble_gap_encrypt() function; however, the peripheral is sending an ATTMTU request prior to the Central enabling encryption and the Central attempts to reply but receives an NRF_ERROR_INVALID_STATE error in response to sd_ble_gatts_exchange_mtu_reply().

    I haven't  changed any of the Central logic which was in place prior to supporting security, so typically the nrf_ble_gatt_on_ble_evt() would handle the ATT requests/responses and my application event handler would just start service discovery on the CONNECT event. I suspect this has to change somehow.

    Can you point me to a sequence diagram that demonstrates a reconnection between bonded Central and Peripheral (and an example)?

  • Regarding database caching, that is supported in the SDK, and makes it so that repeated discovery is not needed. There are two examples in the SDK that demonstrate it: Apple Notification Center Service (ANCS) Client Application and GATT Service Client Example Application. Search for "remote_db" in the main.c to see most of the relevant code. (These happen to be peripheral and not centrals, but they key point here is that they are GATT clients).

  • I assume there is nothing that precludes repeated discovery if you choose not to implement the "remote_db" functionality. If repeated discovery is used, is the link encrypted first before discovery or is discovery in the clear and when encryption is enabled doesn't matter?

    Also, is there a peripheral example that supports mode 1, level 2 security (no MITM) out-of-the-box...preferably heart rate?

  • Hi,

    mlyonupdesigns said:
    I assume there is nothing that precludes repeated discovery if you choose not to implement the "remote_db" functionality.

    No, that is right. If you don't store the remote database, you have to do discovery every time.

    mlyonupdesigns said:
    If repeated discovery is used, is the link encrypted first before discovery or is discovery in the clear and when encryption is enabled doesn't matter?

    It should not matter if you do service discovery before or after the link is encrypted.

    mlyonupdesigns said:
    Also, is there a peripheral example that supports mode 1, level 2 security (no MITM) out-of-the-box...preferably heart rate?

    Not out of the box, but you only need to change the security level for the parameter(s) you don't want to be accessible without mode 1 level 2 or higher, as I suggested in my initial post.

  • OK....thanks.

    This is the BT capture when I used nRF Connect (Android) to first bond to ble_app_hrs app (with mode 1, level 2 security required for the measurement data) and then reconnect after bonding.

    and this is my application (instead of nRF Connect) trying to reconnect to the ble_app_hrs app...obviously I have something wrong in  my app, but not sure what. Any ideas?

    I note that there is no "feature exchange" from my app...is this necessary?

    Also, the "More Data" flag is set in the example from my app (see Empty PDUs) which looks incorrect and may be keeping the peripheral from behaving properly. After 6 seconds the peripherals disconnects and begins advertising again.

    Above is the initial bonding capture

  • Hi,

    I see from the second screenshot that the central is re transmitting the LL_ENC_REQ packet, so it seems that something happened to the peripheral when you are using your central and not nRF Connect. Do you have logs from the peripheral that can tell us what happened there?

    Other than that, the peripheral sent the LL_LENGTH_REQ right before the central sent LL_ENC_REQ, but that should be OK. There are few limitations on the order of procedures other than that only one procedure can be initiated from each side at a single time, and as I see LL_ENC_REQ here right away, so a bond already exist. A feature exchange is normally needed for using any feature that is not mandatory, but up to this point the only non-mandatory feature is encrypting the link, but if a bond already existed that should be no problem. Do you have a sniffer trace from the first connection when the bond was established? (Or if this is the first connection, then there is something fundamentally wrong with the handling of the security as the central at least acts as if it has an existing bond).

    I am not able to say much more based on these  screenshots. Can you share logs from both the peripheral and central side, and your central implementation?

Reply
  • Hi,

    I see from the second screenshot that the central is re transmitting the LL_ENC_REQ packet, so it seems that something happened to the peripheral when you are using your central and not nRF Connect. Do you have logs from the peripheral that can tell us what happened there?

    Other than that, the peripheral sent the LL_LENGTH_REQ right before the central sent LL_ENC_REQ, but that should be OK. There are few limitations on the order of procedures other than that only one procedure can be initiated from each side at a single time, and as I see LL_ENC_REQ here right away, so a bond already exist. A feature exchange is normally needed for using any feature that is not mandatory, but up to this point the only non-mandatory feature is encrypting the link, but if a bond already existed that should be no problem. Do you have a sniffer trace from the first connection when the bond was established? (Or if this is the first connection, then there is something fundamentally wrong with the handling of the security as the central at least acts as if it has an existing bond).

    I am not able to say much more based on these  screenshots. Can you share logs from both the peripheral and central side, and your central implementation?

Children
  • The third screen shot (which I added after the original reply) shows the initial bonding capture. The first screen shot is nRF connect and the second screen shot is my application.

    Can you comment on the "More Data" flag being set in the second screen shot?

  • Here are two example logs (and call stack screen shots) from the peripheral.

    # SEGGER J-Link RTT Viewer V7.80c Terminal Log File
    # Compiled: 16:09:36 on Sep 27 2022
    # Logging started @ 26 May 2023 08:18:31
    00> <debug> nrf_ble_lesc: Initialized nrf_crypto.
    00> 
    00> <debug> nrf_ble_lesc: Initialized nrf_ble_lesc.
    00> 
    00> <debug> nrf_ble_lesc: Generating ECC key pair
    00> 
    00> <info> app: Heart Rate Sensor example started.
    00> 
    00> <info> app: Fast advertising.
    00> 
    00> <debug> nrf_ble_gatt: Requesting to update ATT MTU to 247 bytes on connection 0x0.
    00> 
    00> <debug> nrf_ble_gatt: Updating data length to 251 on connection 0x0.
    00> 
    00> <info> app: Connected.
    00> 
    00> <error> app: Fatal error
    00> 
    
    # Logging stopped @ 26 May 2023 08:19:53
    
    # SEGGER J-Link RTT Viewer V7.80c Terminal Log File
    # Compiled: 16:09:36 on Sep 27 2022
    # Logging started @ 26 May 2023 08:20:47
    00> <debug> nrf_ble_lesc: Initialized nrf_crypto.
    00> 
    00> <debug> nrf_ble_lesc: Initialized nrf_ble_lesc.
    00> 
    00> <debug> nrf_ble_lesc: Generating ECC key pair
    00> 
    00> <info> app: Heart Rate Sensor example started.
    00> 
    00> <info> app: Fast advertising.
    00> 
    00> <debug> nrf_ble_gatt: Requesting to update ATT MTU to 247 bytes on connection 0x0.
    00> 
    00> <debug> nrf_ble_gatt: Updating data length to 251 on connection 0x0.
    00> 
    00> <info> app: Connected.
    00> 
    00> <debug> nrf_ble_gatt: ATT MTU updated to 247 bytes on connection 0x0 (response).
    00> 
    00> <info> app: GATT ATT MTU on connection 0x0 changed to 247.
    00> 
    00> <debug> nrf_ble_gatt: Data length updated to 251 on connection 0x0.
    00> 
    00> <debug> nrf_ble_gatt: max_rx_octets: 251
    00> 
    00> <debug> nrf_ble_gatt: max_tx_octets: 251
    00> 
    00> <debug> nrf_ble_gatt: max_rx_time: 2120
    00> 
    00> <debug> nrf_ble_gatt: max_tx_time: 2120
    00> 
    00> <debug> app: BLE_GAP_EVT_SEC_PARAMS_REQUEST
    00> 
    00> <error> app: Fatal error
    00> 
    

    The error being returned from heart_rate_meas_timeout_handler() is NRF_ERROR_FORBIDDEN.

  • Can you share code also? Regarding the more data bit in the last screenshot from the previous post I am not able to see much from the screenshot. Can you upload the actual trace file? Generally though, if there are empty packets with MD set, that seems odd.

  • Once I fixed the behavior of the ble_app_hrs example so it doesn't start the application timers (application_timers_start) at start-up, but rather waits for BLE_GAP_EVT_AUTH_STATUS event on the initial bonding or NRF_BLE_GATT_EVT_ATT_MTU_UPDATED event on reconnection, then all worked as expected.

    Also stopping the application timers on a disconnect was necessary so that repeated connect/disconnect operations would work.

    So I think I'm all set. Thanks for your attention.

  • I did have a follow up question related to central and peripheral bonding data becoming of "lost". If the central has it's bonding information, but the peripheral no longer does.

    From my BT capture I see that the central sends a LL_ENC_REQ and the peripheral responds with LL_ENC_RSP (which must have something in it that doesn't match. The peripheral then responds with LL_REJECT_IND. I don't see this handled in the SDK and wonder what is the correct action?

    Similarly, if the central no longer has the bonding information, but the peripheral does what is the expected behavior?

Related