This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

BLE-STACK: Ensuring read/write only with MITM protection

Part Number: BLE-STACK

Hello,

I'm implementing a BLE peripheral base on BLE-STACK v2.2.2.
This peripheral exposes custom services which include sensitive data so I would like them to only be accessed via encrypted connections that have MITM protection.
The device has a factory-assigned pairing code that is printed on sticker attached to it and is configured as display only.

It exposes a few "standard" services (e.g. device info service) that are used as is from the SDK) and my custom profiles. The latter have GATT_PERMIT_AUTHEN_READ and/or GATT_PERMIT_AUTHEN_WRITE for the value characteristic.
Testing is done via the LightBlue app on an iPhone. When selecting the characteristic to read, a pop-up is shown requesting a pairing code. If I close the window without providing a code, reading fails (as expected) and no value is displayed, however, I could still register for notifications and get the value.
To overcome this, I also added GATT_PERMIT_AUTHEN_READ and GATT_PERMIT_AUTHEN_WRITE to the relevant clientCharCfgUUID of said characteristic.
Now when I try to register for notifications, the app behaves as if it fails (the "listen for notifications" button doesn't change to "stop listening to notifications", but notifications still come through.

The bond manager is configured as follows:

  // Setup the GAP Bond Manager
  {
    uint32_t passkey = 123456;
    uint8_t pairMode = GAPBOND_PAIRING_MODE_WAIT_FOR_REQ;
    uint8_t mitm = TRUE;
    uint8_t ioCap = GAPBOND_IO_CAP_DISPLAY_ONLY;
    uint8_t bonding = TRUE;

    GAPBondMgr_SetParameter(GAPBOND_DEFAULT_PASSCODE, sizeof(uint32_t),
                            &passkey);
    GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &pairMode);
    GAPBondMgr_SetParameter(GAPBOND_MITM_PROTECTION, sizeof(uint8_t), &mitm);
    GAPBondMgr_SetParameter(GAPBOND_IO_CAPABILITIES, sizeof(uint8_t), &ioCap);
    GAPBondMgr_SetParameter(GAPBOND_BONDING_ENABLED, sizeof(uint8_t), &bonding);
  }

I thought GATT_PERMIT_AUTHEN_READ/GATT_PERMIT_AUTHEN_WRITE are meant to allow access only with MITM protection, but it doesn't seem to be the case.

What is the correct procedure to allow only MITM-protected connections? Does it make sense for the standard profiles (e.g. device manager) to not be protected while others are or is there a way to globally allow only MITM-protected connections?

Thanks in advance,
Assaf

  • Hi Assaf,

    Can you show me how you added GATT_PERMIT_AUTHEN_READ/GATT_PERMIT_AUTHEN_WRITE to your code? What do you set your characteristic properties to? Also, what do you set the attribute access permissions to for each attribute of the characteristic? It might be helpful if you could send me the whole GATT table that you are using.

    -Jessica
  • Hey Jessica,

    Please find below an example of one of the characteristics. Some are also writable, but other than that, they're all very similar:

          // Temperature Declaration
          {
            { ATT_BT_UUID_SIZE, characterUUID },
            GATT_PERMIT_READ,
            0,
            &temperatureProps
          },
    
            // Temperature Value
            {
              { ATT_BT_UUID_SIZE, temperatureUUID },
              GATT_PERMIT_AUTHEN_READ,
              0,
              (uint8_t *)&temperatureValue
            },
    
            // Temperature Client Characteristic Configuration
            {
              { ATT_BT_UUID_SIZE, clientCharCfgUUID },
              GATT_PERMIT_READ | GATT_PERMIT_AUTHEN_WRITE,
              0,
              (uint8_t *)&temperatureClientCharCfg
            },
    
            // Temperature User Description
            {
              { ATT_BT_UUID_SIZE, charUserDescUUID },
              GATT_PERMIT_READ,
              0,
              temperatureUserDesc
            },

    Thanks for your help.

    Good day,
    Assaf

  • Assaf, 

    Is there a reason you used "GATT_PERMIT_READ | GATT_PERMIT_AUTHEN_WRITE" instead of "GATT_PERMIT_AUTHEN_READ | GATT_PERMIT_AUTHEN_WRITE" for your client characteristic configuration? I tried reproducing your error with our simple_peripheral example and was able to get it to work. I added a password to my GAP Bond Manager like you did and changed the notify characteristic  configuration to use "GATT_PERMIT_AUTHEN_READ | GATT_PERMIT_AUTHEN_WRITE." 

    -Jessica

  • Hey Jessica,

    Jessica H. said:
    Is there a reason you used "GATT_PERMIT_READ | GATT_PERMIT_AUTHEN_WRITE" instead of "GATT_PERMIT_AUTHEN_READ | GATT_PERMIT_AUTHEN_WRITE" for your client characteristic configuration?


    Not really, just didn't think it was needed.

    Sadly, however, adding GATT_PERMIT_AUTHEN_READ does not solve the issue.
    First off, I'll note that reading the characteristic when it requires authentication and not supplying a pass key is indeed blocked. What I saw earlier is probably some iOS caching values after they were received via notification. I needed to turn Bluetooth off and on via iOS settings for it to be cleared. Receiving still works even when authentication is needed.

    I too was unable to reproduce the issue with the simple peripheral example. It seems weird since the only difference is that the simple peripheral uses a timer and I rely on an interrupt. Other than that the profile code and, specifically, sending the notification uses the same APIs.

    However, I did notice that the GATTServApp_ProcessCharCfg() function for sending the notification has an "authenticated" parameter. Setting that to TRUE, does not sent notifications anymore. So that's a good enough solution for me.

    With all this in mind, I was wondering what the best approach to protecting information is with BLE.
    Should I only require authentication for the "sensitive" characteristics or should I also modify the SDK's standard profiles?
    Do I need to protect the client characteristic configuration if the value itself requires authentication?
    Perhaps it's better to initiate pairing with GAPBOND_PAIRING_MODE_INITIATE and somehow detect if a passkey was used and close the connection if not.

    Thanks,
    Assaf

  • Hello Assaf,

    In theory (and probably in practice based on your testing with simple_peripheral), GATT_PERMIT_AUTHEN_WRITE permission on the associated Client Characteristic Configuration Descriptor (CCCD) would only be required to ensure an authenticated link before sending a Notification. This is because the CCCD should be set to 01 (via a GATT Write) before the stack will send a Notification - assuming you are using the recommended APIs. A couple of things could be going on, you could have manually set the CCCD or somehow bypassing this check when sending the Notification. The simple GATT profile performs the CCCD checks as prescribed by the BT spec, although we do see some applications that want to "pre-set" the CCCD. Also, you could be sending a Notification on a different characteristic...

    Another way to ensure a MITM pairing is to check the bit in the authReq when the pairing request is received in the GAP Bond Manager. See the processing for the GAPBOND_SECURE_CONNECTION_ONLY param in gapbondmgr.c as an example for parsing the auth request (authReq) when the pairing request is received:

    GAPBondMgr_ProcessGAPMsg

     if ( gapBond_secureConnection == GAPBOND_SECURE_CONNECTION_ONLY )
              {
                if ( !(pPkt->pairReq.authReq & SM_AUTH_STATE_SECURECONNECTION) )
                {
                  // We are not interested in pairing with this device.
                  VOID GAP_TerminateAuth( pPkt->connectionHandle, SMP_PAIRING_FAILED_AUTH_REQ );
    

    You could extend this logic to also check to include the SM_AUTH_STATE_AUTHENTICATED bit from the authReq and terminate the connection accordingly if the peer device is not requesting MITM pairing.

    Best wishes 

  • Hey JXS,

    Thank you for your input.

    The client configuration characteristic was supposedly protected with GATT_PERMIT_AUTHEN_WRITE as the LightBlue app behaved differently with it (the "listen for notifications" button didn't change to "stop listening").

    As for sending the notifications themselves, I'm using GATTServApp_ProcessCharCfg() when the value is changed internally (via the XXX_SetParamer() function). This seems to be on par with the simple profile.

    Finally, what did make the difference in my case, was setting the "authenticated" parameter for the above function to TRUE (i.e. requiring an authenticated session before sending a notification).

    When set to FALSE, notifications were sent. When it was set to TRUE, notifications were received only when I provided a passkey for pairing. To me it seems that the stack was aware of the authentication status.

    I can try to grab a network dump if that's of interest to get a better understanding of what's going on.

    As for terminating the connection when MITM protection is not used. I understand how to implement it, but was wondering what's the common practice to protect information. Require authentication per characteristic or initiate pairing immediately and terminated the connection if it wasn't authenticated. More to the point, does it make sense to expose the device info service (which doesn't require authentication by default) or should everything be protected?

    Thanks again,

    Assaf

  • Hello Assaf,

    I can't speak to the internal workings of the LightBlue app / CoreBluetooth w.r.t listening for Notifications, but glad to see you have a path forward. The stack does load the previous CCCD values for bonded connections so this is how it can interpret the TRUE/FALSE authenticated parameter.

    In terms of protecting information in BLE, it's important to remember all connections are established without security enabled, regardless of whether the devices have previously paired/bonded. Encryption is enabled (optionally) by the master during the connection. Also, certain adopted GATT services, such as the Device Info service do not impose any security permissions. For this reason, devices typically require encryption/authentication at the GATT level for "protected" services, that is services that the application wishes to protect. You can, as you state, terminate the connection if the peer device does not establish encryption within a specified time.

    It's also worth noting that some applications elect to authenticate peer devices and encrypt/protect information at the application layer endpoints vs. using BLE Link Layer security. These implementations are application specific though and not provided by the core spec.

    Best wishes
  • Hey JXS,

    JXS said:
    The stack does load the previous CCCD values for bonded connections


    This is the missing piece of the puzzle!
    Now I can explain my original issue, if others run into something similar. When testing my characteristics I entered the passkey for pairing and saw that everything worked. I then deleted the peripheral device from my iPhone in order to loose the bonding information so I could verify that now I won't have any access. When I connected to the peripheral this time, I didn't provide a pass key but 2 caches were at play here. The first is by iOS caching the last read value from my characteristic (this cache can be removed by disabling and enabling Bluetooth in the iOS settings). Then, when trying to enable notifications, writing to the CCCD failed (as it should since I wasn't authenticated), but the BLE stack had cached the configuration from my previous connections so it sent notifications anyway (this cache can be cleared by erasing the entire flash and re-flashing the stack + app).

    When I loaded the simple peripheral example, I actually erased all flash to make sure I have a clean slate and that's why I wasn't able to reproduce it there.
    I now have a better understanding of the scenarios at play here and I don't expect this to actually be an issue as the central would still need to know the passkey at one point.

    I understand that caching the CCCD is part of the specifications, but I wonder if it should be cleared when bonding is done again for the same client (as is the case when removing the peripheral from the iOS settings).

    Anyways, I'll mark this issue as resolved. Thanks Jessica and JXS for all your help.

    Good day,
    Assaf