-
Hi @cefitzger, ~afaik Bluez is used in android~ (wrong, it used to be up until 4v). Anyways, I wonder what happens when you
NRF.setSecurity({lesc: 1});
and then manually initiate bonding? This flow (central initiated) could be different to the one that you are trying to do (when peripheral responses with an error and then central automatically initiates secure connection). I'm just trying to narrow it down, there might be nothing to do with characteristic security. -
From your WireShark trace, when secure request is made, your central provides "SecureConnection, MITM & Bonding" auth parameters. I'm just guessing that this is what your central wants to have, i.e. it is requesting to establish secure link with MITM and also store keys. I'm speculating again, this list of parameters is made by Bluez based on peripheral advertisement packages (which contains some capabilities flags). I think your central (bluez) does not specify lesc parameter just because it does not know if your peripheral supports it, It could be that you have to set up your peripheral to advertise lesc capability so that Bluez knows what to do.
Can you please try to set this?
NRF.setSecurity({lesc: 1});
I assume that this will make your peripheral to advertise that it supports lesc and give a hint for Bluez to try to use lesc algorithm.
-
Hi @fanoush,
What if I wanted to communicate with e.g. 20 sensors without pairing/bonding them all but still wish to have the link encrypted like https does, is this scenario supported in BLE?
Yes, this is a valid use case. You may establish secure connection without actually bonding (think about it as a temporary bond). This is what happens when you mark characteristics as secured, but you do not initiate bonding. Bluez in this case automatically "starts" temporary secure connection and also automatically performs failed operation (read or write) with the same payload. Bear in mind that it is not very secure anyway as centra and peripheral have to exchange secure keys every time they establish connection, hence it is prone to mitm attack.
Some good readings on that topic: https://gattack.io/
Have a look at the whitepaper that describes many possible attacks: https://github.com/securing/docs/raw/master/whitepaper.pdf
-
Hi @cefitzger thanks for your feedback.
I agree it might be confusing, but not all combinations of security params are valid, e.g. some of them are mutually exclusive. If you set "lesc" to true, then all other parameters get ignored. I've tried to describe this in the documentation here: https://www.espruino.com/Reference#l_NRF_setServices
Note: Not all combinations of security configuration values are valid, the valid combinations are: encrypted, encrypted + mitm, lesc, signed, signed + mitm.
Effectively all your tests were done using "lesc". Not all BT devices support this security protocol, I think it has been introduced in BT 4.2 specification. I assume one of your android device, ios or pixl itself does not support it, hence fails establishing security channel.
I did some test using a sniffer (nrf51822 and android 7.x), what I saw is similar to your WireShark trace, but after a successful pairing response I saw that all data packages were encrypted, I could not even see what type of packages get exchanged, e.g. the software I used could not decode radio packages.
Re rationale of having security configs per each characteristic. I agree this might be confusing, but if you consider characteristics as abstract resources, then why would not you have different levels per each? I can make up an example. Let's say you have a peripheral device that can report some data which is not secure (open). You may connect to it and read that data, you may chose not to establish or not to establish secure channel (pair or not pair), this is a valid case. But also that device provides a characteristic that is used to change some settings, this one has to be secured hence you mark it as secured, i.e. forcing central to establish secure connection just for this characteristic. I think it makes perfect sense having different levels per each characteristics (and not globally).
-
-
Just thinking about the structure more, I think it is better to have something like that:
NRF.setServices({ 0xBCDE : { 0xABCD : { value : "Hello", readable: true, writable : true, security: { read: { encrypted: false, mitm: false, lesc: false, signed: false }, write: { encrypted: true, mitm: false, lesc: false, signed: false } } } } });
Here is my branch it is still in wip and untested.
-
-
Hi all, just want to share a code snippet and some thoughts/ideas on how to interact with Xiaomi devices.
There are a few Xiaomi devices that have internal bluetooth module, e.g.:
- Mi Flora plant sensor
- Temperature and Humidity sensor
- Mi Smart Scale
- Mi Smart Kettle
Xiaomi uses its own proprietary authentication protocol in their devices. The protocol logic is encapsulated into a native shared library (Android JNI library) that is used to generate/encode/decode authentication tokens which must be exchanged with the central device. Xiaomi devices only allow a very short lived connection while they are expecting to receive an authentication token. Once authentication fails, device drops its connection. More info on the Xiaomi authentication protocol can be found here. Fortunately, Xiaomi devices actively advertise their data which can be easily received and decoded.
It turns out that all Xiaomi devices (that I'm aware of) use a common protocol/data structure for their advertisement messages that can be easily decoded, hence no need in establishing any direct connection with the device. I've decoded them and came up with some custom GATT definitions that describes the protocol and data structure here and here.
Here is a code snippet that shows how to decode Temperature and Humidity sensor data:
var temp = null; var humidity = null; var battery = null; function getSensorData() { NRF.findDevices(function(devices) { if (devices[0] && devices[0].serviceData.fe95) { var data = devices[0].serviceData.fe95; var flag = data[11]; switch (flag) { case 4: { // temp temp = (data[14] | data[15] << 8) / 10; break; } case 6: { // humidity humidity = (data[14] | data[15] << 8) / 10; break; } case 10: { // battery battery = devices[0].serviceData.fe95[14]; break; } case 13: { // temp and humidity temp = (data[14] | data[15] << 8) / 10; humidity = (data[16] | data[17] << 8) / 10; break; } default : { print("Unknown flag: " + flag); } } } for (var i = 0; i < devices.length; i++) { devices[i] = null; } devices = null; }, { filters: [ { id: "4c:65:a8:d0:7a:ee public" } ], timeout: 2000 } ); } setInterval(function () { print("Temp: " + temp + " humidity: " + humidity + " battery: " + battery); getSensorData(); }, 5000);
As I mentioned, all devices share the same notification data structure, hence it is easy to come up with very similar code that would decode advertisements from other Xiaomi devices by following the gatt specs that I provided.
PS. The provided gatt spec files is a part of Gatt Parser, Java Bluetooth Manager and Eclipse SmartHome binding that I'm working on, you may find it useful too. The official discussion thread here.
Let me know if you have any questions.
- Mi Flora plant sensor
-
Looks like similar security configs are also available for ESP boards.
-
Hi @Gordon, I think I found a better option that would be useful for all nrf boards. Just wanted to validate something with you before I create a PR.
It just turned out that it is a bit problematic to add that event. Well, it is not difficult for the peripheral mode, but quite problematic for the central mode as you need to figure out MAC address by using handle ID, so that we can pass it as a parameter to the event.
Anyway, there is what I think is a better option. When characteristics get added/configured, espruino marks them as "open to read/write" here.
Instead of making them wide open, we could come up with something like that:
if (jsvGetBoolAndUnLock(jsvObjectGetChild(charVar, "encrypted", 0))) { BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(&attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(&attr_md.write_perm); } else { BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); }
So that users can configure characteristics in JS like that (forcing encryption):
NRF.setServices({ 0xBCDE : { 0xABCD : { value : "Hello", readable: true, writable : true, encrypted: true, onWrite : function(evt) { print("Written: " + evt.data); } } } });
I've tested this, it is kind of working, in a bit an unexpected way though. I'd have thought if you configure a characteristic to be encrypted, then if the link is not encrypted it should fail, but instead it looks like bonding happening automatically. Well, it works differently for different environments:
- Both Bluez and android encrypt characteristics automatically, ie. without initiating bonding procedure manually.
- Bluegiga serial BT adapter fails to read/write, ie. it does not encrypt characteristics automatically.
I'll need to do more tests with a BT sniffer to make sure it actually encrypts, will do this over this weekend.
Now just wanted to validate the config format. Some users may want to configure read and write encryption separately, e.g. encrypt it only for write and let read operation open. Furthermore, some user may want to use different security levels, e.g. "man in the middle" or even "signed". I'm thinking to let users configure the security in this format:
NRF.setServices({ 0xBCDE : { 0xABCD : { value : "Hello", readable: true, writable : true, security: { read: "open", // default write: "encrypted" // could be also: "encrypted_signed", "mitm", "mitm_signed", "lesc_mitm" }, onWrite : function(evt) { print("Written: " + evt.data); } } } });
All those security constants correspond to these macros
Please let me know what you think.
- Both Bluez and android encrypt characteristics automatically, ie. without initiating bonding procedure manually.
-
-
Yeah, I agree, this should perfectly work for nrf52 boards. The reason why I was looking into an option with a new event is that
NRF.getSecurityStatus()
does not work for nrf51. It just seems to me that an important/little piece is missing from the nrf51 api that is not very difficult to implement (from my immature point of view) but once implemented would seriously improve security aspects. -
-
-
Just wondering @Gordon, would that be possible to add a new event for this ("bounded/encrypted")? Eg. like we have for "connect"/"disconnect". Cheers.
-
Thanks for that @Gordon, I've tried to enable this for nrf51 but it requires peer manager be enabled, and this won't fit in nrf51 flash unfortunately. Anyways thank you very much.
-
Hi @Gordon, how can I find out the connection status? I can see only a method for the case when device acts as "central": https://github.com/espruino/Espruino/blob/2f1a0e8c49f0d21c34ac745b2dabee51952f5f09/libs/bluetooth/jswrap_bluetooth.c#L2896
It looks like it is not just a problem of nrf51, but also other boards. I can't see any way to query connection status for any board...
-
Yes, it is correct. Depending on the flag field (11th element), payload contains temperature, humidity, both temp and hum, battery level.
I've decoded and described Xiaomi protocol in the Gatt Parser project here and here. Most of Xiaomi devices follow this protocol, e.g. MiFlora sensor, Mi Kettle, Mi Scales etc
-
Hi there,
I've read this great article, but I does not answer my question on how to make sure that your peripheral device require a secure connection (bonded/encrypted) while a central node connects to it and writes some data to its characteristic?
As far as I understand, the espruino API allows you to do something similar when your device acts as a central device, e.g. it can initiate bonding process etc. But how do I do similar thing for a peripheral device?
Essentially I want to protect some characteristics that are "writable". For example, let's say I've got a relay hooked up to nrf51822 module that is controlled by a characteristic. How do I make sure that only encrypted links can update that characteristic?
As far as I understand, Nordic API provides some mechanism to do so, e.g. you can mark some characteristics with some security flags that would prevent you to write into it if your link is not secure enough: https://os.mbed.com/forum/team-63-Bluetooth-Low-Energy-community/topic/5661/
-
Thanks everyone.
Just a heads up, I've tried to enable full BLE API support for nrf51822 board (e.g. that are disabled atm by
defined(NRF52)
andSAVE_ON_FLASH
- these are the APIs that allow to create GATT server, in other words to make nrf board to be "central" device), this is not possible with that small flash unfortunately:STORAGE: 247808 -> 262144 CODE: 110592 -> 256372 CODE AND STORAGE OVERLAP
Effectively nrf51822 boards are forced to be peripheral devices only. That's ok methink as to be "central" is not that important/common for this small devices.
However, I must say I was able to read some data from other BT devices by using advertised services, for example I was able to hook up to a Xiaomi Temperature and Humidity sensor:
NRF.findDevices(function(devices) { print("Found"); if (devices[0] && devices[0].serviceData.fe95) { var data = devices[0].serviceData.fe95; var flag = data[11]; print(flag); switch (flag) { case 4: { // temp temp = (data[14] | data[15] << 8) / 10; break; } case 6: { // humidity humidity = (data[14] | data[15] << 8) / 10; break; } case 10: { // battery battery = devices[0].serviceData.fe95[14]; break; } case 13: { // temp and humidity temp = (data[14] | data[15] << 8) / 10; humidity = (data[16] | data[17] << 8) / 10; break; } default : { print("Unknown flag: " + flag); } } } }, { filters: [ { id: "4c:65:a8:d0:7a:ee public" } ], timeout: 2000 } );
Perfectly working.
-
Interesting... Thank you guys for your clarification. I'm trying to come to a conclusion if it is actually rational to use internal nrf5 flash for this king of things I wanted to use it for, e.g. backing up some readings (daily or hourly).
Giving that:
- we have only 10k write/erase cycles
- I've managed to allocate only 14 pages
- there is a wear leveling mechanism so that all 14 pages are evenly used even if I write 1 word
Does it mean that I'll have about 10000 * (14 * 1024 (page size)) / 4 (bytes) = 35840000 write cycles for 1 word?
Something tells me that it is not right or very hard to achieve giving that wear leveling won't be ideal. Also, I'm not quite sure if my assumption is correct - 10000 write/erase cycles is per 1 bit (e.g. not per page or minimum write operation length).
If 10000 write/erase cycles is actually per 1 page (e.g. minimum write operation length is 1024 bytes), then it would be a different figure: 10000 * 14 = 140000 write cycles for 1 word.
I also not quite sure if flash page size on low level is the same as page size of Espruino Flash module. Looks like ppl on that nordicsemi thread mention that page size is 512.
What's your thoughts on that guys? If it is 35m write/erase cycles, then it is acceptable even if we store a figure every hour. If it is only 140000 write cycles, then it could be problematic.
- we have only 10k write/erase cycles
-
Alright... After doing this:
- Disabling
GRAPHICS
- Removing
'DEFINES += -DUSE_DEBUGGER -DUSE_TAB_COMPLETE',
Configuring
saved_code
as per this:'saved_code' : { 'address' : ((256 - 14) * 1024), 'page_size' : 1024, 'pages' : 14, 'flash_available' : (256 - 108 - 14) }
I'm getting:
In the build output:
STORAGE: 247808 -> 262144 CODE: 110592 -> 247628
In the app:
storage.getFree()
: 14336 bytes.
Great success, I think it is more than enough.
Thanks for your suggestion to look at the Flash module, I will soon.
- Disabling
-
@cefitzger, I use docker to build espruino firmware. It is a mater of 10 mins to install docker daemon and you have a proper stable environment. More details here: https://github.com/espruino/Espruino/blob/master/Dockerfile
Once you've build it first time, you can then use this docker file to speed up the process (it will just copy sources and won't install build chain):