At the end of the knowledge-base article at http://community.silabs.com/t5/Bluetooth-Wi-Fi-Knowledge-Base/BT121-Legacy-pairing-and-fixed-PIN-code-or-Passkey/ta-p/173703 the lack of a mechanism in the Secure Simple Pairing (SSP) framework to always force a fixed/known PIN code to be entered at both sides during the pairing procedure was discussed. Conclusion was that, with a device with no input nor output capability having Just-works as the only possible pairing mechanism, the only way to have authentication/authorization is by mean of security at the application level.
In this knowledge-base article we are presenting an idea of how to implement some security at the application level (in a BGScript in particular) for both BLE and the Serial Port Profile in the Bluetooth Classic. In the latter case this will be a BGScript version of the C implementation seen in the main.c of the host example program under the directory \host_example\spp_server\ of the SDK (since version 1.1.1 build 168) where the first incoming string over the RFCOMM link is parsed and checked against a secret string in the code before the data exchange can continue.
The starting point is the script in the example project under the directory \example\bgdemo\ of the SDK. This is being modified to provide the authentication/authorization at the application level which is missing in the original example, where the Just-works mode is configured with the “call sm_configure(0,3)” under the system_initialized event, and where security is never increased with the command sm_increase_security while a BLE connection is ongoing. The resulting script is called "bgdemo_enh.bgs" and is found inside the zip file attached to this case. The rest of the article gives a short description of the main changes and added parts enabling the additional security.
At first, the script introduces a couple of constants (holding the secret code and the length of the secret code) and a couple of variables (a buffer to compare the incoming SPP data with the constant buffer, and an index to take into account that it might take multiple events to get the code from the remote party):
const secret_code() = "12345" const secret_code_len = 5 dim secret_compare(260) dim secret_compare_index
Starting with the Bluetooth Classic part, the first major change happens under the system_initialized event where the streaming_destination parameter of the command bt_rfcomm_start_server is changed to be the script itself:
The impact of this modification is that, upon any new RFCOMM-based SPP connection, all the incoming data from the remote party is directed to the script for being processed by the script itself. In this way, we can control the incoming data and for example arrange so that the very first incoming bytes will have to match those of the secret code for the connection and other operations to then proceed as in the original script. This is possible thanks to the code below being added to the original script:
event endpoint_data(endpoint, data_len, data_data) # Let's capture the incoming data just after the connection is established and until the secret code can be read if (endpoint = rep) then memcpy(secret_compare(secret_compare_index), data_data(0), data_len) # Let's fill the variable buffer with data from the remote party - This is the data that will be compared with the secret code secret_compare_index = secret_compare_index + data_len if (secret_compare_index < secret_code_len) then # We have not yet received enough data to compare with the secret code call endpoint_send(0,,"More characters needed to verify secret code...\r\n") else if memcmp(secret_compare(0), secret_code(0), secret_code_len) then # We have enough data in the variable buffer to compare with the constant buffer holding the secret code call endpoint_send(0,,"Secret code match, transparent SPP comunication can continue...\r\n") if (secret_compare_index != secret_code_len) then # We happen to have more bytes in the variable buffer than in the secret code, so the rest of the bytes are user data that we must send out of the UART call endpoint_send(0, secret_compare_index - secret_code_len, secret_compare(secret_code_len:secret_compare_index - secret_code_len)) # Does not send the secret code #call endpoint_send(0, secret_compare_index, secret_compare(0:secret_compare_index)) # Sends also the secret code if desired end if call endpoint_set_streaming_destination(endpoint,0) # Let's map the data from the remote side directly to the UART for the full transparent data in both directions from now on, like in the original bgdemo call hardware_set_soft_timer(1000,0,0) # Start the repeating timer that in the original bgdemo is always started at boot (under the system_initialized event) and continue like in the original bgdemo example else call endpoint_send(0,,"Secret code mismatch or not enough data, communication will be closed in 1s!\r\n") call hardware_set_soft_timer(1000,1,1) # Timer needed here because it is not allowed to use endpoint_close under the endpoint_data event end if secret_compare_index = 0 end if end if end
By the endpoint_send commands the script is sending status messages out of the UART interface until the secret code is verified, and the transparent data exchange over UART can re-start, as in the original script, following here the “call endpoint_set_streaming_destination(endpoint,0)”. Please, follow the comments in the code and the UART status messages to realize what is going on. In case of a code mismatch the script will close the connection without allowing further data exchange. In the ideal case the endpoint_close command would be issued together with the code above, but since it is not possible for such command to appear under the endpoint_data event according to the API reference, a single-shot timer is started with handle of 1 and the SPP connection will be closed when the timer times out, under the event hardware_soft_timer as seen below:
event hardware_soft_timer(handle) if handle = 1 then call endpoint_close(rep) # This command is used to close both le and br/edr connections (using connection ID or RFCOMM endpoint respectively - we are using the variable rep for both for simplicity) rep = -1 return end if […] end
Let’s see now how authentication/authorization is implemented for the BLE part. In the approach proposed here, a custom Service and a custom Characteristic are added to the GATT database via the gatt.xml project file, as seen below:
<!-- Custom Service - Security--> <service uuid="11111111-1111-1111-1111-111111111111"> <characteristic uuid="22222222-2222-2222-2222-222222222222" id="security"> <properties write="true" /> <value length="5" /> </characteristic> </service>
The characteristic with an arbitrary 128-bit UUID, of fixed length equal to the length of the secret code, whose handle number is held in the BGScript by the constant called “security”, is to be written by the remote GATT Client with the exact secret code within a desired timeout since the connection is opened, otherwise the connection is automatically closed. As in the case of the Classic connection, here is the added code with the comments and status messages explaining the functionality:
event le_connection_opened(address, address_type, master, connection, bonding) rep = connection # Let's reuse the rep variable for simplicity, assuming there will not be le and br/edr connections at the same time call endpoint_send(0,,"BLE connection opened. 5s to execute SSP pairing and/or security increase...\r\n") # Debug message out of UART call hardware_set_soft_timer(5000,1,1) # Let's launch a 5s one-shot timer with handle of 1 - At timeout we will be closing the connection if the normal Bluetooth Just-works pairing has not happened call sm_increase_security(connection) # Start encryption to protect the secret code written by the other side - Once connection is secured, the event le_connection_parameters with security_mode set to 1 will be triggered (see below) end event le_connection_parameters(connection, interval, latency, timeout, security_mode, address) if connection = rep && security_mode = 1 then # If pairing has happened and encrypted connection is started call endpoint_send(0,,"Just-works SSP pairing successful. 10s more to write secret code...\r\n") # Debug message out of UART call hardware_set_soft_timer(0,1,1) # Stop the running 5s one-shot timer with handle of 1 call hardware_set_soft_timer(10000,1,1) # Let's launch a 10s one-shot timer with handle of 1 - At timeout we will be closing the connection if the intended characteristic for security will not be written by the remote application with the expected secret code end if end event gatt_server_attribute_value(connection, attribute, att_opcode, offset, value_len, value_data) if connection = rep && attribute = security && memcmp(value_data(0), secret_code(0), secret_code_len) then # If the correct secret code was written to the intended characteristic within 10s from opening the connection call endpoint_send(0,,"Correct secret code written to intended characteristic. Communication can continue...\r\n") # Debug message out of UART call hardware_set_soft_timer(0,1,1) # Stop the 10s one-shot timer with handle of 1 call hardware_set_soft_timer(1000,0,0) # Start the repeating timer that in the original bgdemo is always started at boot (under the system_initialized event) and continue just like in the original bgdemo example end if end
As can be noticed, as soon as the BLE connection is opened the sm_increase_security command is launched to get the Just-works pairing to happen within 5 seconds. This is the first security step for all the GATT transactions to become encrypted. After pairing and bonding, another timer (also with handle of 1) is started and the remote GATT Client is given a limited interval (10 second here) to write the secret code to the dedicated Characteristic. Upon the GATT Server communicating to the script via the event gatt_server_attribute_value the received code, this is compared against the secret code which is hardcoded in the script, and only in case of a match the timer is stopped, otherwise it will naturally expire and the timer timeout will trigger the same endpoint_close as in the RFCOMM-based case (where the “rep” variable in this case is not the RFCOMM endpoint but is being re-used as the BLE connection handle.)