esp-nimble-cpp icon indicating copy to clipboard operation
esp-nimble-cpp copied to clipboard

Bonded flag not set onConnect for previously bonded device

Open outlandnish opened this issue 3 years ago • 24 comments

Hey there, thanks for creating an awesome wrapper for NimBLE on the ESP32!

I've been having an issue trying to get the setScanFilter function working. If I whitelist scanning or connections, I cannot get the device to show up (or connect) when scanning from an Android phone, even after bonding.

I set the following security setup before starting the server:

NimBLEDevice::setSecurityAuth(true, true, true);
NimBLEDevice::setSecurityCallbacks(ble);
NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
NimBLEDevice::init("");
NimBLEDevice::setMTU(512);

I'm assuming the bonded device needs to be added to the whitelist. Does this happen out of the box or is it additional functionality I need to add in the server callbacks?

Thanks for any help!

outlandnish avatar Oct 27 '20 03:10 outlandnish

Hi @outlandnish, You're welcome!

If you setup the security settings after the call to ::init() I think you will see a better result. The ::init() call will initialize the security settings to defaults, overwriting what you've set there.

When bonding with a phone and using a whitelist it will not work on the esp32 due to the controller not supporting resolvable private addresses, instead they have implemented that in the host. Unfortunately this is a limitation in bluedroid as well.

h2zero avatar Oct 27 '20 14:10 h2zero

Thanks for the quick response - I'll give that a shot with the security settings!

And that's unfortunate re: the whitelist. I remember that being an issue with Bluedroid too. So if I wanted my ESP32 to only allow bonding at certain times (i.e. when entering a pairing mode), there's no way for me to do that, is there?

outlandnish avatar Oct 27 '20 15:10 outlandnish

You can turn bonding on or off as needed there is just not a way to recognize a peer device with a random address via whitelist.

What you can do if you want is to filter out connections from non bonded peers. For instance when you have bonded with your phone, the next time it connects it will be recognized as bonded and you can check the flag. If a non-bonded peer connects you could just disconnect them in the callback. Not a great solution but it should work.

h2zero avatar Oct 27 '20 21:10 h2zero

Conceptually, that solution works well, thanks!

However, when I tested it, I found that the bonded flag would only get set after onAuthenticationComplete, not onConnect. The odd thing was that I couldn't stop other devices from bonding either. I noticed the code for onSecurityRequest doesn't fire - is that because there's no longer a corresponding event from NimBLE that triggers it?

outlandnish avatar Oct 28 '20 14:10 outlandnish

Yes onAuthenticationComplete will tell you when you've bonded, may want to set a flag here or something, that you can test in onConnect().

You can check for the bonded flag in the connection description parameter from the overloaded onConnect() callback, seen here.

NimBLE does not provide a callback for the onSecurityRequest, only when using passkey or numeric compare will it ask the app to check permission. So the best way to deal with that is to use one of those pairing methods.

h2zero avatar Oct 28 '20 16:10 h2zero

Yeah, so I use the overloaded onConnect callback and the bonded flag isn't set, even for devices that have previously been bonded.

Here's the relevant logs from my device:

ble-auth-issue

Thanks for the heads up on using the passkey / numeric compare though. I'll give that a shot!

outlandnish avatar Oct 28 '20 18:10 outlandnish

I'll play with this tonight and see if I can find a solution. Very strange that the bonded flag isn't set when reconnecting, might be a bug in NimBLE

h2zero avatar Oct 28 '20 20:10 h2zero

Does the bonding process happen automatically within NimBLE or is it initiated by ble_gap_security_initiate? If it's the latter, I could potentially call that in onConnect conditionally

outlandnish avatar Oct 28 '20 20:10 outlandnish

It can be triggered multiple ways but the whole process is handled in the stack.

You certainly can call that in the callback although it may cause another issue if already bonded to the peer.

Are you using the NimBLE stack included in IDF or my component repo?

h2zero avatar Oct 28 '20 20:10 h2zero

I'm using the NimBLE stack in included in IDF 4.1. Are there any differences in your component? I didn't see any, but I could've missed something.

outlandnish avatar Oct 29 '20 01:10 outlandnish

I'll have a look, there has been a lot of work on this area of the NimBLE stack in recent weeks to address such things. If you feel like giving it a go you could try using the master branch of esp-nimble in 4.1, It should cause no issue.

I just got home so I will be testing for a solution to this shortly.

h2zero avatar Oct 29 '20 01:10 h2zero

Sounds good, I'll see what I can find. And thanks for taking a look into it!

outlandnish avatar Oct 29 '20 02:10 outlandnish

Just did a quick test and I can confirm that NimBLE is not reporting the bonded flag correctly in the onConnect callback.

Unfortunately the proper fix for this will likely require an upstream PR. I'll see what I can come up with in the mean time.

h2zero avatar Oct 29 '20 02:10 h2zero

Ahh that's unfortunate :/

I guess a temporary workaround would be to store the peer_id_addr in NVS on successful bonding and then check against it onConnect. I imagine that's all NimBLE is doing behind the scenes anyway

outlandnish avatar Oct 29 '20 02:10 outlandnish

Also, I found this interesting PR to enable accepting / rejecting JUST_WORKS pairing requests: https://github.com/apache/mynewt-nimble/pull/540

Looks like it needs a bit of work to conform with the way the NimBLE peeps want it but it sounds like a good starting point.

outlandnish avatar Oct 29 '20 02:10 outlandnish

Yes that PR is being worked on by espressif and will hopefully get merged.

In the mean time, for your purproses I believe there is a possible work around, it's not pretty but it might work for you.

In the onConnect() callback you would need to do something like this:

    ble_addr_t peer_id_addrs[MYNEWT_VAL(BLE_STORE_MAX_BONDS)];
    int num_peers;
    int i;

    ble_store_util_bonded_peers(&peer_id_addrs[0], &num_peers, MYNEWT_VAL(BLE_STORE_MAX_BONDS));

    for (i = 0; i < num_peers; i++) {
        if (ble_addr_cmp(desc->peer_id_addr, &peer_id_addrs[i]) != 0) {
            break;
        }
    }

    if (i >= num_peers) {
        pServer->disconnect(desc->conn_handle);
    }

Not tested code but it should be close to what you need, I'll spend some more time on this and try to come up with a better solution on the weekend.

h2zero avatar Oct 29 '20 03:10 h2zero

That solution worked perfectly, thank you!

outlandnish avatar Oct 29 '20 15:10 outlandnish

Great, I'll see what can be done to simplify this going forward.

h2zero avatar Oct 29 '20 16:10 h2zero

Hi, I have the same issue as outlandnish, I guess the onConnect() solution you proposed wont work with RPA address? Is there a way to use the IRK to know if the device is bonded?

fhelie avatar Nov 06 '20 13:11 fhelie

Hi @fhelie, it should work with an RPA as the address gets resolved before the callback is called.

If that is not the case on your tests let me know.

h2zero avatar Nov 06 '20 13:11 h2zero

Thank you, I will test it soon and give back the result!

fhelie avatar Nov 06 '20 13:11 fhelie

Hi @h2zero , I had the same issue and tired the proposed solution:

    void BluetoothController::setupSecurity(const uint32_t pin)
    {
        NimBLEDevice::setSecurityAuth(true, true, true);
        NimBLEDevice::setSecurityPasskey(pin);
        NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
        NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ID | BLE_SM_PAIR_KEY_DIST_ENC);
        NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ID | BLE_SM_PAIR_KEY_DIST_ENC);
        NimBLEDevice::setMTU(512);
    }

The device bonds. After reconnecting however the bonding check failes because the addrress->type is not matching:

ServerCallbacks::isDeviceBonded() - num_peers: 1
ServerCallbacks::isDeviceBonded() - ble_addr_cmp(21921685547864, 21921685547864):  type of device address: 0 type of nvm address: 1073555579
ServerCallbacks::isDeviceBonded() - (i < num_peers): false
ServerCallbacks::onConnect() - Connection rejected because device is not bonded.

Do you think I can ignore the address type? Do you have any idea why the saved bonded device would store such a wierd number a address type?

Best Regards

sanastasiou avatar Jun 21 '23 18:06 sanastasiou

Yes you can ignore the address type. This is due to how espressif deals with bonded peers using host based instead of controller based privacy.

h2zero avatar Jun 24 '23 02:06 h2zero

Thanks, I have also found out that NimbleDevice::isBonded(...) also works. So that above solution is not needed. I worked around the rest of the issues as described in this thread. It is not optimal, but it works just fine. Thanks.

sanastasiou avatar Jun 24 '23 09:06 sanastasiou