Pairing/Bonding errors with a nRF MCU to a Windows laptop

I've written a GUI in python on a windows laptop which communicates fine with an nRF MCU when using open broadcast and no security. I'm using python's bleak library to connect and read/write gatt characteristics. I now want to implement security. I have copied much of the code from lesson 2 in the sdk fundamentals. I've modified it to use a static passkey for security level 4 connections. It still uses the filter accept list and pairing button. 

It works fine on the nRF connect app. I connect, then write to one of the characteristics, which then prompts an input for the static passkey. However, on windows, this is not the case. I'm doing pairing outside of the python program as I don't believe bleak supports that functionality. I intend to use the GUI after pairing. When connecting, it says "connecting..." on windows, the 'on_connected' callback executes on the MCU, then it errors out on both the laptop and mcu after about 15 seconds. No bonding occurs and no messages are sent/received.

Errors seen:

[00:09:16.687,164] <inf> BLE_Main: Security failed: 58:A0:23:A6:35:0A (public) level 1 err 9

[00:09:16.687,377] <inf> BLE_Main: Disconnected (reason 8)

My Bluetooth drivers:

Error 9 on security fail is BT_SECURITY_ERR_UNSPECIFIED. Is the issue that the windows laptop has no means of entering the static passkey? Or could it be that LE Secure Connections is not natively supported?

Help is much appreciated.

  • Some clarifications:

    1. Passkeys offer MITM protection if used correctly. What I wrote was "assuming no input/output capabilities or OOB", you get no MITM protection, because then you have to use JustWorks. If you have input/output capabilities, then you per the standard generate a passkey that is shown on the display and is inputed on the other device.

    2. In the wireshark log, you can see the central sending LL_FEATURE_REQ. This contains the feature set from the central. There is no point after receiving this packet to send LL_PERIPHERAL_FEATURE_REQ because the response from the central will be the same content as in the previous LL_FEATURE_REQ.

  • Hi Emil, 
    1. Thank for the clarification. You are right. I was misreading the part when you mentioned "assuming no input/output capabilities or OOB" sorry about that. 

    2. I assume our controller would send LL_PERIPHERAL_FEATURE_REQ automatically. I will need to check internally. But from my understanding the spec doesn't prohibit that. And by sending that you can confirm that the central has updated the feature set according to the LL_FEATURE_RSP from the peripheral send earlier.  

  • Correct. The spec allows performing the feature negotiation several times per connection, but there is no point doing so, as long as you can set aside 8 bytes of data in the connection structure to keep this information. It explicitly recommends doing this only once: "The FeatureSet information may be cached either during a connection or between connections."

    I would say "confirming that the central has updated the feature set" doesn't make much sense, since the LL_FEATURE_RSP is defined exactly what it should contain, namely that the first octet is a bitwise-and of the first octet of the local feature set and the first octet received in the corresponding request and the rest of the octets are octet 2-8 of the local feature set unmodified.

    Note that the version negotiation (LL_VERSION_IND) is however only permitted to perform once per connection.

  • Thanks Emil,

    It seems these are my options for pairing at this point to get MITM protection:

    1. QR Code

    2. NFC

    3. Some custom encryption??

    QR code seems like the least amount of overhead in terms of hardware, firmware, and software. How exactly does this provide more security than a static code? Can't the QR also be brute-forced? What information is encoded? Where can I read about how to implement this?

    Regarding the PHY updates, I captured the packets for pairing 3 more times. Out of the four total, two fail after the LL_PHY_REQ (peripheral -> central) and two fail after LL_PHY_UPDATE_IND (central -> peripheral) so it appears it's not just an issue with the central. 

    BLE_Windows_Secure_Error_2.pcapngBLE_Windows_Secure_Error_3.pcapngBLE_Windows_Secure_Error_4.pcapng

  • In the LL_PHY_UPDATE_IND, we can see that the Instant is set to 0, which is not valid (or at least not a sane value, since it represents an event in the past). It feels like they forgot to assign the value a proper value. So please report this bug to Intel who makes the Bluetooth chip in your computer. Or maybe see if there is a firmware update for your Bluetooth chip on your PC?

    A QR code typically just contains a static passkey as well, but for another protocol than the standard BLE passkey pairing protocol. BLE's passkey protocol is really stupid an inefficient but on the other hand only requires the AES-ECB and ECDH (P-256 curve) crypto primitives, which makes it relatively simple to implement. The reason it doesn't work for static passkeys is because after a failed attempt, you get to know which bit of the 20 bits in the passkey that was wrong. That way you can flip that bit in your attempted passkey and try again, max 20 tries to crack and 10 on average. If you use a random passkey for every attempt this does not cause any security issues since even if you learn a particular bit that was wrong after a failed attempt, it's already too late since that passkey is now discarded.

    Contrary to what BLE is using, there are properly designed pairing protocols for the use case where a short shared secret is used (like a 6-digit passkey or a password), see https://en.wikipedia.org/wiki/Password-authenticated_key_agreement. The Matter smart home standard for example uses SPAKE2+ which you will find in that list. The idea of a PAKE is that it should require one real online attempt to test whether a passkey is correct. Even if an attacker sniffs a session between two legitimate parties successfully performing pairing, it should not be possible within reasonable time to brute-force the passkey used using the packet logs. This is specifically not the case for BLE's passkey protocol since an eavesdropper immediately learns the passkey that was used after a successful pairing attempt. Note that a solution based on a PAKE is not trivial to implement from scratch since you must both implement the PAKE protocol itself as well as the subsequent encryption and authentication signatures of packets using the established secret as key with e.g. AES-CCM as cipher, so I wouldn't recommend anyone doing that unless that person knows exactly what he/she is doing. If you're interested, read e.g. §3.10 of the Matter standard (https://leconiot.com/matter/1.2/index.html#ref_Pake).

    I guess in the real world, product designers just take the simple route and use JustWorks pairing with BLE since the probability that an attacker would be around that has the capabilities doing a MITM attack is expected to be quite low, and users prefer simple pairing than scanning QR codes. After all, the Bluetooth spec designers thought JustWorks was enough and didn't bother to add any static passkey option using a proper PAKE.

Related