Bridging the GAP: Bluetooth LE Security

Peripheral Implementation | 2nd of a 5 Part Series

Halo Sport is a Bluetooth 4.0 capable peripheral that leverages Bluetooth LE Security

When interacting with a Bluetooth LE peripheral, you’re almost certain to leverage Android or iOS as your central device. As a peripheral developer, it is imperative that your peripheral plays nicely with both iOS and Android devices in a manner that keeps connections reliable and your customer’s actions and data secure. Regardless of your peripheral’s form factor, one constant is the need to correctly implement the Bluetooth 4.0 Specification.

Part 2 of our 5 part series on Bluetooth LE Security focuses on implementing the Bluetooth specification on a peripheral using the Stonestreet One Bluetopia stack. We will focus on key integration points in a way that, hopefully, can be applied to any peripheral implementation.


The Bluetopia stack contains an immense codebase that abstracts the complexities of Bluetooth communication and provides an incredible number of points to extend its functionality. To leverage this code properly, it is critical to understand the key integration points to implement and validate a solution that works with both Android and iOS central devices.

This is not a time to assume boilerplate example code handles all needed scenarios. It is immensely difficult to update peripherals in the field that experience connectivity issues. This guide will help identify and understand the required callbacks and events for pairing with a central device — it is a tool for guiding debugging and testing efforts related to connections and pairing.


Peripheral Security

Security on your peripheral is centered around access to your peripheral’s characteristics. The idea is that some subset (potentially a complete subset) of your characteristics require authentication and subsequent encryption to be accessed. For example, your peripheral may allow anyone to connect and read sensor data from it (e.g. read from a subset of read-only characteristics), but may require authentication to configure it (e.g. read/ write to another subset of characteristics). Pairing is the process that provides this authentication and encryption.

It is up to you, the peripheral developer, to decide which characteristics require authentication and design your system appropriately. Once authenticated, the central device should be granted access to all characteristics across all services.

Additionally, the concept of master and slave devices is important. The master device is the device responsible for initiating the pairing process. Android and iOS expect to be the master device in the relationship between central and peripheral.

Let’s look at Bluetopia’s key integration points used to pair a peripheral slave with a central master.

GAP & GATT

There are two key profiles for pairing and communication between a peripheral and central — the Generic Access Profile (GAP) and Generic Attribute Profile (GATT). We rely on events routed through these profiles and handled by two callbacks to facilitate pairing:

  • GAP Callback: Handles events triggered as the central device establishes a connection to your peripheral, and subsequent events as pairing capabilities are shared and a bond is established.
  • GATT Callback: Handles events triggered as the central device accesses characteristics defined by your peripheral’s services.

A connection between your peripheral and a central device is required to pair. Bluetopia will handle the I/O and logic required to establish a connection. Begin advertising and you’ll receive a GAP callback event when a connection is established.

After establishing a connection with your peripheral, the central may begin the pairing process at any time by communicating with your peripheral’s GAP interface. In many cases, however, the central device will wait to initiate pairing until it reads or writes a protected characteristic on your GATT Server. If an unpaired central attempts to access a protected characteristic, the peripheral should respond with an insufficient authentication error, and the central will begin the pairing process upon receiving the error.

GAP: Connection Establishment

To establish a connection, your peripheral first must begin advertising. Start advertising and register a GAP event callback via the GAP_LE_Advertising_Enable function:

int BTPSAPI GAP_LE_Advertising_Enable(
unsigned int BluetoothStackID,
Boolean_t EnableScanResponse,
GAP_LE_Advertising_Parameters_t *GAP_LE_Advertising_Parameters, GAP_LE_Connectability_Parameters_t *GAP_LE_Connectability_Parameters,
GAP_LE_Event_Callback_t GAP_LE_Event_Callback,
unsigned long CallbackParameter);

The second to last parameter is your GAP_LE_Event_Callback function, which will be called on each step for connecting and pairing:

void (BTPSAPI *GAP_LE_Event_Callback_t)(
unsigned int BluetoothStackID,
GAP_LE_Event_Data_t *GAP_LE_Event_Data,
unsigned long CallbackParameter)

Events handled by the callback are specified by:

GAP_LE_Event_Data->Event_Data_Type

There are two related to your logical connection:

  • etLE_Connection_Complete: This is our first opportunity to identify the central device that connected to our peripheral. This provides the opportunity to either allow or reject the connection based on the bond data in our security database.
  • etLE_Disconnection_Complete: This is the last point where we may perform logic related to a connection. It is called after disconnection occurs for any reason and is a good place to clean up and prepare to start advertising again.

GATT: Authentication Response

After a connection is established, a peripheral should expect a central device to query its GATT server for services and characteristics, and subsequently access those characteristics. The peripheral will inform the central device if authentication is required to access a given characteristic. This is considered a protected characteristic.

To handle events related to your characteristics, you must register a GATT Server event callback via the GATT_Register_Service function:

int BTPSAPI GATT_Register_Service(
unsigned int BluetoothStackID,
Byte_t ServiceFlags,
unsigned int NumberOfServiceAttributeEntries,
GATT_Service_Attribute_Entry_t *ServiceTable,
GATT_Attribute_Handle_Group_t *ServiceHandleGroupResult,
GATT_Server_Event_Callback_t ServerEventCallback,
unsigned long CallbackParameter)

The second to last parameter is your GATT_Server_Event_Callback_t function, which is called each time an event occurs on the registered service:

void (BTPSAPI * GATT_Server_Event_Callback_t)(
unsigned int BluetoothStackID,
GATT_Server_Event_Data_t *GATT_Server_Event_Data,
unsigned long CallbackParameter)

Events handled by the callback are specified by:

GATT_Server_Event_Data->Event_Data_Type

We are primarily interested in two event types, etGATT_Server_Read_Request and etGATT_Server_Write_Request.

If the characteristic in question requires authentication for read/write access, your peripheral should respond by calling GATT_Error_Response:

int BTPSAPI GATT_Error_Response(
unsigned int BluetoothStackID,
unsigned int TransactionID,
Word_t AttributeOffset,
Byte_t ErrorCode)

Use the error code ATT_PROTOCOL_ERROR_CODE_INSUFFICIENT_AUTHENTICATION to inform the central authentication is required and it needs to begin the pairing process.

Note that iOS requires accessing a protected characteristic and receiving the subsequent insufficient authentication response to begin the pairing process. This workflow is only weakly supported by various Android manufacturers.

GAP: Pairing & Authentication

When a central device begins the pairing process, we handle another series of events in the GAP event callback, under the parenting event type etLE_Authentication. When receiving an etLE_Authentication event, we can access the type of authentication event via:

GAP_LE_Event_Data->Event_Data.GAP_LE_Authentication_Event_Data->GAP_LE_Authentication_Event_Type

The critical authentication events we are concerned with are (in the order they are typically received when pairing with Android and iOS):

  • latPairingRequest: A Central is requesting to pair; respond with the Peripheral’s pairing capabilities.
  • latConfirmationRequest: Confirm the pairing technique. The Central, based on the capabilities returned for the latPairingRequest, requests the appropriate type of pairing (e.g. Just Works). Respond in kind if the requested pairing type is supported.
  • latEncryptionInformationRequest: Generate and respond with your LTK, EDIV, and Rand used for encryption.
  • latIdentityInformationRequest: Generate and respond with your IRK.
  • latIdentityInformation: Receive and store the IRK for the pairing Central, allowing you to re-establish a bond later.
  • latPairingStatus: The final event indicating if pairing was a success or failure.

For each of the above events, if a response is necessary, it is sent back to the central device by calling:

int BTPSAPI GAP_LE_Authentication_Response(
unsigned int BluetoothStackID,
BD_ADDR_t BD_ADDR,
GAP_LE_Authentication_Response_Information_t *GAP_LE_Authentication_Information);

Calling this with appropriate response information will allow the central to move forward with the pairing process and trigger your next event.

Note that Android can (and should) start this process directly without interacting with your GATT Server. As such, it is important not to rely on GATT Server characteristic read / writes as a step in preparing your peripheral for pairing.

GAP: Re-establish a Bond

If we identify a Central device as being previously bonded during the etLE_Connection_Complete event, we can trigger a process to re-establish that bond by calling GAP_LE_Reestablish_Security:

int BTPSAPI GAP_LE_Reestablish_Security(
unsigned int BluetoothStackID,
BD_ADDR_t BD_ADDR,
GAP_LE_Security_Information_t *SecurityInformation,
GAP_LE_Event_Callback_t GAP_LE_Event_Callback,
unsigned long CallbackParameter);

Note that the callback is the same GAP event callback we’re already using. The important event to handle here is etLE_Authentication’s latLongTermKeyRequest authentication event. Here we regenerate our LTK based on the IRK we have stored for the connecting Central device and proceed to use that LTK to encrypt all communications.

This event is triggered internally by the Bluetopia stack. No communication is made with the Central device to re-establish the bond, as previously exchanged identity information is used instead. This is Bluetooth LE Security at work!

What’s Next?

Now that we have a Bluetooth 4.0 LE compliant peripheral, we’re ready to explore how both iOS and Android connect, pair, and bond to it.

While iOS makes it easy with explicit guidelines and a stable Bluetooth LE stack in CoreBluetooth, Android provides no clear guidelines and a Bluetooth stack that has bolted-on LE specific features and numerous stability problems, along with an evolving API that seems to change on every major Android release.

We’ll explore how to leverage each and achieve optimal connectivity, along with identifying some gotchas that require changes to your peripheral code for each platform.

Until then, may all your connections be stable and secure!


This series is based on Bluetooth 4.0, which is aging rapidly, but is not going away anytime soon. The 4.2 spec is generally passed over and 5.0 won’t be on a majority of mobile devices for at least a few more years. The Samsung Galaxy S8 is the only mobile device available with Bluetooth 5 compatible hardware as of August 2017 — and it has no software support to enable this hardware yet!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.