Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to open/close a BLE pairing&bonding window #243

Closed
igiona opened this issue Mar 11, 2024 · 4 comments
Closed

Allow to open/close a BLE pairing&bonding window #243

igiona opened this issue Mar 11, 2024 · 4 comments

Comments

@igiona
Copy link
Contributor

igiona commented Mar 11, 2024

It is a common use case to have a gesture that enables a "pairing window" with the device.
The goal is to control who can have remote access to the device, by forcing some kind of physical access.
This is especially important in devices with limited UI capabilities that need to use the rather poor justworks security.

It seems that peripheral::advertise_pairable vs peripheral::advertise_connectable suggest this feature, but it either doesn't work properly or I misinterpret it.

In both cases, pairing (key exchange) still happens successfully, only bonding is not performed when peripheral::advertise_connectable is used.
This means that, at any time, a central device can access the encrypted GATT services and characteristics without ever having to access the device physically.

As far I could test, the current implementation does not allow to enable pairing only in a certain window.
After digging into the code, I think that maybe it comes down to simply setting the security params as follow when the BLE_GAP_EVTS_BLE_GAP_EVT_SEC_PARAMS_REQUEST is received and no security handler is provided:

      let mut sec_params: raw::ble_gap_sec_params_t = core::mem::zeroed();
      sec_params.min_key_size = 7;
      sec_params.max_key_size = 16;
      sec_params.set_io_caps(raw::BLE_GAP_IO_CAPS_NONE as u8);

Any thoughts on this? Am I missing something?

@alexmoon
Copy link
Contributor

#320 adds a method to SecurityHandler that allows you to fully customize the ble_gap_sec_params_t that should be used. That should cover your use case. It's a bit odd to me though that you would accept connections from an unknown central when you're not willing to pair with them.

@igiona
Copy link
Contributor Author

igiona commented Mar 11, 2024

#320

I guess 320 is a typo? => #230

I want to accept connections and pairing for already known devices, but I want only to allow connections & bonding requests when explicitly requested.
My understanding is that in order to allow to connect with an already bonded peer I require peripheral::advertise_pairable , but this allows every peer to bond.
Is there a way to prevent this?
Maybe I simply have to change the directed/undirected field in the adv packet? But then I can accept only a connection from the last known peer the device was connected to.

Probably the best would be to check upon connection if the cebtral is already bonded, and disconnected if it is not?

@alexmoon
Copy link
Contributor

I guess 320 is a typo? => #230

Yes, oops.

Probably the best would be to check upon connection if the cebtral is already bonded, and disconnected if it is not?

The best way to do this is with a whitelist. Use ble::set_whitelist with the list of your peers then set ble::peripheral::Config.filter_policy to define which requests you want to filter. If any of your peers use private resolvable addresses, you'll need to call ble::set_device_identities_list before calling set_whitelist to give the Softdevice the identity resolution keys.

@igiona
Copy link
Contributor Author

igiona commented Mar 11, 2024

I guess 320 is a typo? => #230

Yes, oops.

Probably the best would be to check upon connection if the cebtral is already bonded, and disconnected if it is not?

The best way to do this is with a whitelist. Use ble::set_whitelist with the list of your peers then set ble::peripheral::Config.filter_policy to define which requests you want to filter. If any of your peers use private resolvable addresses, you'll need to call ble::set_device_identities_list before calling set_whitelist to give the Softdevice the identity resolution keys.

Many thanks for the advice! It works like a charm!
This is what I had to do:

    pub(crate) fn whitelist_known_peers(&self, sd: &Softdevice) -> Result<(), RawError> {
        const MAX_LEN: usize = nrf_softdevice::raw::BLE_GAP_WHITELIST_ADDR_MAX_COUNT as usize;
        assert!(self.bond_info_map.borrow().len() <= MAX_LEN);

        let mut addresses : [Address; MAX_LEN] = unsafe { core::mem::zeroed() };
        let mut id_keys : [IdentityKey; MAX_LEN] = unsafe { core::mem::zeroed() };
    
        let mut valid_id_keys : usize = 0;
        for (i, bond) in self.bond_info_map.borrow().iter() {
            addresses[*i as usize] = bond.peer.peer_id.addr;

            warn!("Whitelisting {}", bond.peer.peer_id.addr);
            let already_exist = id_keys.iter().find(|idk| idk.is_match(bond.peer.peer_id.addr)).is_some();
            if !already_exist {
                id_keys[*i as usize] = bond.peer.peer_id;
                valid_id_keys += 1;
            }
        }

        ble::set_device_identities_list(sd, &id_keys[..valid_id_keys], None)?;
        ble::set_whitelist(sd, &addresses[..self.bond_info_map.borrow().len()])?;
        Ok(())
    }

I added this function the Bonder struct that was discussed here #237 (comment)

Then to allow pairing, in my BLE task I do something like

let mut config = peripheral::Config::default();

match bonder.whitelist_known_peers(sd) {
    Ok(_) => info!("Whitelisting stored successfully"),
    Err(e) => error!("Unable to configure whitelisting {}", e),
}

if pairing_allowed {
  let adv_data: LegacyAdvertisementPayload = LegacyAdvertisementBuilder::new()
                          .flags(&[Flag::GeneralDiscovery, Flag::LE_Only])
                          .full_name(name)
                          //add more settings if needed
                          .build();
  let adv = peripheral::ConnectableAdvertisement::ScannableUndirected {
      adv_data: &adv_data,
      scan_data: &SCAN_DATA,
  };
  config.filter_policy = FilterPolicy::Any;
  unwrap!(peripheral::advertise_pairable(sd, adv, &config, bonder).await)
}
else {
  let adv_data: LegacyAdvertisementPayload = LegacyAdvertisementBuilder::new()
                          .flags(&[Flag::LE_Only])
                          .build();
  let adv = peripheral::ConnectableAdvertisement::ScannableUndirected {
      adv_data: &adv_data,
      scan_data: &SCAN_DATA,
  };
  config.filter_policy = FilterPolicy::Both;
  unwrap!(peripheral::advertise_pairable(sd, adv, &config, bonder).await)
}

Every suggestion for improvement in performance, style or anything is very welcome :)
I'm not really happy about having to create a copy of rather "big objects" like IdentityKey and Address... but rust has a quite steep learning curve so I'll live with it for now 😄

@igiona igiona closed this as completed Mar 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants