-
• #3
There isn't anything at the moment I believe - but since JS is executed on a write it may be possible to just ignore the write if the connection isn't secure.
However either way I think you're not going to have any success with nRF51 - I don't think link security is implemented on that because there isn't enough space.
-
• #4
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...
-
• #5
I think you're right - I don't believe there is a function to get the status.
I just committed code that adds
NRF.getSecurityStatus
which works in a similar way to the one you found previously. -
• #9
Technically encryption of the link won't solve characteristic authorisation issue, however it will provide a way to implement some basic app-level authorisation mechanisms, e.g. pin code / password authorisation (as you can exchange secure tokens freely).
-
• #10
Events might make sense, but I don't understand why the changes I just made aren't ok for you?
NRF.setServices({ 0xBCDE : { 0xABCD : { writable : true, onWrite : function(evt) { if (NRF.getSecurityStatus().bonded) // do stuff } } } });
Realistically that's less code than if you had to handle the event as well.
-
• #11
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. -
• #12
Ahh - does bonding even work properly without peer manager? I'm not so sure since presumably you need to be able to store the encryption keys somewhere so it still works after a power cycle?
If you did me a PR on GitHub with the event added then I'd pull it in - but at the end of the day I have to run a business and spending my time adding features for users of boards that I'm not producing isn't really a good use of my time.
I added
NRF.getSecurityStatus()
because I thought it would help you (and because it's also useful for paying customers), but having abonded
event whenNRF.getSecurityStatus()
is now available seems of minimal use to most people. -
• #13
Yep, afaik exchanging/storing encryption keys is done by Nordik SDK as a part of connection procedure, meaning that you don't do it manually, it is done behind the scenes.
Yeah, no worries, thank you for your help, it makes sense. I'll try to add that even and if it works I'll create a PR.
-
• #14
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.
-
• #16
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.
-
• #17
Thanks - this looks good. Yes, I think letting the softdevice handle permissions is definitely a really nice addition.
The write causing bonding to initiate definitely seems what I'd expect based on how I've seen other devices working.
Your branch looks great as well - although it might be good to add a check for
readVar!=NULL
and same for writeVar (maybe just in set_security_mode?). I'm pretty sure this'll work with what you've done, but it'd be nice if you could do just something like:security: { write: { encrypted: true } },
as a shorthand - so you don't have to mention everything.
-
• #19
@Vlad, nice, very powerful, versatile, easy to use and understand. - Even though moving (horizontal) concerns into system layer is best practices, having a more concise, flexible and easy to use solution in 'application' layer (with inclusion of elements available in system layer) is in the light of the (memory) resource constraints a very good solution.
After all, Espruino firmware is 'on top' of BLE can be seen as a system layer (or a 'system' application) from point of view of ('user') application - 'user' application being uploaded from Espruino IDE (including uploaded modules; modules can though be in a gray zone... because some or already included in the Espruino firmware...).
-
• #20
That looks brilliant - thanks! Just merged.
-
• #21
Interesting topic. I tried it with my SDK11 based port to DS-D6 tracker and found out that NRF.getSecurityStatus() call fails with error 'Peer Manager not compiled in'. Maybe the getSecurityStatus() can return false values to report that connection is not secure if peer manager is not in, instead of throwing exception, does it make sense? Or can I even have encrypted connection without adding Peer Manager and this pstorage nordic thing that saves bonding into flash somewhere? Something like https vs http so that two can communicate over encrypted connection without this bonding and passkey authentication stuff?
As for Peer Manager I found the right define to add and linked also micro-ecc and other stuff and then found that peer manager api related to whitelists has changed a bit between SDK 11 and 12, which is all fixable and is not a problem/question but do I really need it?
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?
-
• #22
I'm having some problems with this in practice using 2v02. What I'm seeing with a Pixl as a peripheral is that the it (the Pixl) ignores the security values in the services when negotiating with the central. It manages to bond with the central but without the security association required by the characteristic. As a result, subsequent reads or writes fail due to insufficient authentication.
Here are the test configs and traces. The JS is below is the only config on the Pixl and I hard reset between test. I also delete bonding information and turn Bluetooth off/on to reset the phones before each test.
Config 1: No security on read, security on write.
NRF.setServices({ 0xBCDE : { 0xABCD : { value : "Hello", // optional maxLen : 5, // optional (otherwise is length of initial value) readable : true, // optional, default is false writable : true, // optional, default is false security: { // optional read: { // optional encrypted: false, // optional, default is false mitm: false, // optional, default is false lesc: false, // optional, default is false signed: false // optional, default is false }, write: { // optional encrypted: true, // optional, default is false mitm: true, // optional, default is false lesc: true, // optional, default is false signed: false // optional, default is false } }, onWrite : function(evt) { // optional print("Got ", evt.data); // an ArrayBuffer } } // more characteristics allowed } // more services allowed });
I tested with both Android (Oreo 8.1) and iOS (12.2) and had the same results. When trying to write, they get a failure due to insufficient authorisation (as you would expect) and so requests to bond (SecureConnection, MITM & Bonding parameters). The Pixl replies omitting MITM and SecureConnection and the bonding completes without these.
Another write request is then attempted but again fails due to insufficient auth. The WireShark trace for the Android test is included below. Read works fine before and after bonding.
Config 2: No security on write, security on read.
NRF.setServices({ 0xBCDE : { 0xABCD : { value : "Hello", // optional maxLen : 5, // optional (otherwise is length of initial value) readable : true, // optional, default is false writable : true, // optional, default is false // notify : true, // optional, default is false security: { // optional read: { // optional encrypted: true, // optional, default is false mitm: true, // optional, default is false lesc: true, // optional, default is false signed: false // optional, default is false }, write: { // optional encrypted: false, // optional, default is false mitm: false, // optional, default is false lesc: false, // optional, default is false signed: false // optional, default is false } }, onWrite : function(evt) { // optional print("Got ", evt.data); // an ArrayBuffer } } // more characteristics allowed } // more services allowed });
With this config, the write works without security and the read fails (as expected). This failure leads to bonding but then fails again due to the insufficient authentication. The trace for this is also below. I include a screenshot of the negotiation trace for ease of access to show the Pixl failing to match the Pairing Request parameters.
I've also tried adding
NRF.setSecurity({passkey:"123456", mitm:1, display:1});
but it doesn't make any difference. In fact it doesn't seem to register in the config at all after being pushed to the Pixl (see screen shot of IDE).
I believe that the Pixl is expected to propose it's security parameters in the Pairing Response frame. Having different potential security associations for each characteristic seems like a problematic approach to me. The bonding is for the device and must converge on a single common set of security associations so you couldn't have one characteristic that requires MITM and another that doesn't want it - they either all get it or none do.
It might work in cases where the device only pairs (effectively forgetting the security association after each connection), but that would still pose a problem until the connection is torn down. I think the security association needs to be at a system level. Each characteristic can then either implement security or not, which works fine as the traces show.
Edit 1: Just to confirm, if I leave the MITM and LESC set to false, everything works fine.
4 Attachments
-
• #23
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).
-
• #24
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
-
• #25
Hi @Vlad,
I assumed that this was to indicate capabilities as is with network protocols - my mistake. I've re-run with this config though and get exactly the same result:
security: { // optional read: { // optional encrypted: true, // optional, default is false mitm: true, // optional, default is false lesc: false, // optional, default is false signed: false // optional, default is false },
The Encrypted and MITM options are ignored.
The iPhone and Android indicate support for LESC with MITM (see this article for a discussion of the weakness of LESC without MITM Protection - https://devzone.nordicsemi.com/f/nordic-q-a/35856/questions-about-lesc-mitm-and-passkey/138216#138216 .)
I had also assumed that, as this is in the Espruino API documentation, the Pixl supported the options available. @Gordon can you comment on the features that should work with the Pixl?
As to the comment on the use case, I can agree that a device could be required to support protected and un-protected characteristics. So the be able to say per characteristic and per action (read/write) whether it is secure or not seems like a great feature to have.
However, this still seems to be something that should be device wide, not per characteristic. It would not work to require encrypted+mitm for one characteristic and LESC for another - the bonding process can only result in one outcome.
On the issue of reading encrypted packets, any device that is eavesdropping during the bonding process can get the keys and decrypt the packets, especially if it is LESC without a Random PIN and MITM. Even with MITM protection and a static PIN, the sniffer can easily decode the packets (you can see the passkey/OOB Key field in the screenshot I sent earlier).
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/