When a Bluetooth LE Central and Peripheral devices are in connection, data within the payload can be encrypted. Encryption of the channel can be achieved through pairing with others. Once the communication has been encrypted, the Bluetooth LE devices could distribute the keys to save it for future connections. The last is better known as bonding. When two Bluetooth LE devices are bonded, in a future connection, they do not need to exchange the keys since they already know the shared secret, thus, they can encrypt the channel directly, saving time and power.
However, if an attacker is listening to the first time that both (Central and Peripheral) Bluetooth LE devices enter into a connection state, the security of the link could be vulnerated, since the attacker could decipher the original message. Fortunately, Out Of Band (OOB) provides the ability (obviously, if both devices support it) to share the keys on an unknown medium for an attacker listening Bluetooth LE (for instance, NFC, SPI, UART, CAN, etc), increasing the security of the communication.
This document explains how to enable OOB pairing on Bluetooth LE connectivity examples, basing on FRDM-KW36 SDK HID Host and HID Device examples.
The connectivity software stack contains macros and APIs that developers should implement to interact with the host stack and handle the events necessary for OOB. The following sections explain the main macros, variables, and APIs that manage OOB in our software.
It is used to enable or disable pairing to encrypt the link.
Values | Result |
---|---|
0 | Pairing Disabled |
1 | Pairing Enabled |
It is used to enable or disable bonding to request and save the keys for future connections.
Values | Result |
---|---|
0 | Bonding Disabled |
1 | Bonding Enabled |
This flag must be set if the application requires Man In the Middle protection, in other words, if the link must be authenticated. You can determine whether your software needs to set or clear this flag from the GAP Security Mode and Level. Red instances of the following table indicate that gBleLeScOobHasMitmProtection_c must be set to 1.
This struct contains the pairing request or the pairing response (depending on the device's GAP role) payload. To enable and configure OOB pairing, oobAvailable field of the struct must be set to 1.
This API must be implemented in response of gConnEvtOobRequest_c event in BleConnManager_GapPeripheralEvent or BleConnManager_GapCentralEvent functions (depending of the GAP role). This event only will be triggered if OOB is enabled and LE Legacy pairing is used. The gConnEvtOobRequest_c event occurs when the stack request the OOB data received from the peer device just after the gConnEvtPairingRequest_c or gConnEvtPairingResponse_c (depending of the GAP role). This API is valid only for LE Legacy pairing.
Name of the Parameter | Input/Output | Description |
---|---|---|
deviceId | Input | ID of the peer device |
aOob | Input | Pointer to OOB data previously received from the peer. |
This API must be implemented either in response of gConnEvtPairingRequest_c or gConnEvtPairingResponse_c events in BleConnManager_GapPeripheralEvent or BleConnManager_GapCentralEvent functions (depending of the GAP role) to get the local OOB data generated from the controller and in response of gLeScPublicKeyRegenerated_c event at BleConnManager_GenericEvent. Each time that Gap_LeScGetLocalOobData is executed in the application to obtain the OOB data, it triggers the gLeScLocalOobData_c generic event to inform that OOB data must be read from pGenericEvent->eventData.localOobData to send it to the peer device. This API is valid only for LE Secure Connections pairing.
This API must be implemented in response of gConnEvtLeScOobDataRequest_c event in BleConnManager_GapPeripheralEvent or BleConnManager_GapCentralEvent functions(depending of the GAP role). This event occurs when the stack requires the OOB data previously recieved from the peer. This API is valid only for LE Secure Connections pairing.
Name of the Parameter | Input/Output | Description |
---|---|---|
deviceId | Input | ID of the peer device |
aOob | Input | Pointer to gapLeScOobData_t struct that contains the OOB data received from the peer. |
The following example is based on the HID Device software included in the FRDM-KW36 SDK. It explains the minimum code needed to enable OOB. In the following sections, brown color indicates that such definition or API takes part in the stack and violet color indicates that such definition does not take part in the stack and its use is only for explanation purposes in this document.
The app_preinclude.h header file contains definitions for the management of the application. To enable OOB pairing, you must ensure that gAppUseBonding_d and gAppUsePairing_d are set to 1.
You can also set the value of the gBleLeScOobHasMitmProtection_c in this file, depending on the security mode and level needed in your application.
This example makes use of two custom definitions: gAppUseOob_d and gAppUseSecureConnections_d. Such definitions are used to explain how to enable/disable OOB and, if OOB is enabled, how to switch between LE Secure Connections pairing or LE Legacy paring.
/*! Enable/disable use of bonding capability */
#define gAppUseBonding_d 1
/*! Enable/disable use of pairing procedure */
#define gAppUsePairing_d 1
/*! Enable/disable use of privacy */
#define gAppUsePrivacy_d 0
#define gPasskeyValue_c 999999
/*! Enable/disable use of OOB pairing */
#define gAppUseOob_d 1
/*! Enable MITM protection when using OOB pairing */
#if (gAppUseOob_d)
#define gBleLeScOobHasMitmProtection_c TRUE
#endif
/*! Enable/disable Secure Connections */
#define gAppUseSecureConnections_d 1
Using the code above, you can enable or disable OOB using gAppUseOob_d, also you can decide whether to use LE Secure Connections (gAppUseSecureConnections_d = 1) or LE Legacy (gAppUseSecureConnections_d = 0)
The following portion fo code depicts how to fill gPairingParameters struct depending on which pairing method is used by the application.
/* SMP Data */
gapPairingParameters_t gPairingParameters = {
.withBonding = (bool_t)gAppUseBonding_d,
/* If Secure Connections pairing is supported, then set Security Mode 1 Level 4 */
/* If Legacy pairing is supported, then set Security Mode 1 Level 3 */
#if (gAppUseSecureConnections_d)
.securityModeAndLevel = gSecurityMode_1_Level_4_c,
#else
.securityModeAndLevel = gSecurityMode_1_Level_3_c,
#endif
.maxEncryptionKeySize = mcEncryptionKeySize_c,
.localIoCapabilities = gIoKeyboardDisplay_c,
/* OOB Available enabled when app_preinclude.h file gAppUseOob_d macro is true */
.oobAvailable = (bool_t)gAppUseOob_d,
#if (gAppUseSecureConnections_d)
.centralKeys = (gapSmpKeyFlags_t) (gIrk_c),
.peripheralKeys = (gapSmpKeyFlags_t) (gIrk_c),
#else
.centralKeys = (gapSmpKeyFlags_t) (gLtk_c | gIrk_c),
.peripheralKeys = (gapSmpKeyFlags_t) (gLtk_c | gIrk_c),
#endif
/* Secure Connections enabled when app_preinclude.h file gAppUseSecureConnections_d macro is true */
.leSecureConnectionSupported = (bool_t)gAppUseSecureConnections_d,
.useKeypressNotifications = FALSE,
};
Additionally, the serviceSecurity struct registers which are the security mode and level of each Bluetooth LE service, so if Secure Connections is selected (gAppUseSecureConnections_d = 1), mode = 1 level = 4.
static const gapServiceSecurityRequirements_t serviceSecurity[3] = {
{
.requirements = {
#if (gAppUseSecureConnections_d)
.securityModeLevel = gSecurityMode_1_Level_4_c,
#else
.securityModeLevel = gSecurityMode_1_Level_3_c,
#endif
.authorization = FALSE,
.minimumEncryptionKeySize = gDefaultEncryptionKeySize_d
},
.serviceHandle = (uint16_t)service_hid
},
{
.requirements = {
#if (gAppUseSecureConnections_d)
.securityModeLevel = gSecurityMode_1_Level_4_c,
#else
.securityModeLevel = gSecurityMode_1_Level_3_c,
#endif
.authorization = FALSE,
.minimumEncryptionKeySize = gDefaultEncryptionKeySize_d
},
.serviceHandle = (uint16_t)service_battery
},
{
.requirements = {
#if (gAppUseSecureConnections_d)
.securityModeLevel = gSecurityMode_1_Level_4_c,
#else
.securityModeLevel = gSecurityMode_1_Level_3_c,
#endif
.authorization = FALSE,
.minimumEncryptionKeySize = gDefaultEncryptionKeySize_d
},
.serviceHandle = (uint16_t)service_device_info
}
};
If your application will use LE Legacy Pairing, then you have to implement Gap_ProvideOob in response to the gConnEvtOobRequest_c event at the BleConnManager_GapPeripheralEvent function. In this example, gOobReceivedTKDataFromPeer is an array that stores the data previously received OOB from the peer device (using SPI, UART, I2C, etc), therefore, the procedure to fill this array with the data received from the peer depends entirely on your application. Notice that gOobReceivedTKDataFromPeer must contain the data received from the peer before to execute Gap_ProvideOob.
static uint8_t gOobReceivedTKDataFromPeer[16];
void BleConnManager_GapPeripheralEvent(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent)
{
switch (pConnectionEvent->eventType)
{
case gConnEvtConnected_c:
{
...
...
...
}
break;
...
...
...
#if (gAppUseOob_d && !gAppUseSecureConnections_d)
case gConnEvtOobRequest_c:
{
/* The stack has requested the LE Legacy OOB data*/
(void)Gap_ProvideOob(peerDeviceId, &gOobReceivedTKDataFromPeer[0]);
}
break;
#endif
...
...
...
}
}
When using Secure Connections Pairing, the application must handle two events at the BleConnManager_GapPeripheralEvent function. In gConnEvtPairingRequest_c event, you must implement Gap_LeScGetLocalOobData API to generate the local (r, Cr) values. The gConnEvtLeScOobDataRequest_c event indicates that the application is requesting the (r, Cr) values previously received OOB from the peer device (using SPI, UART, I2C, etc). Such values are contained into gOobReceivedRandomValueFromPeer and gOobReceivedConfirmValueFromPeer buffers. You must implement Gap_LeScSetPeerOobData in response to gConnEvtLeScOobDataRequest_c, This function has two parameters, the device ID of the peer and a pointer to a gapLeScOobData_t type struct. This struct is filled with the data contained in gOobReceivedRandomValueFromPeer and gOobReceivedConfirmValueFromPeer buffers.
gapLeScOobData_t gPeerOobData;
static uint8_t gOobReceivedRandomValueFromPeer[gSmpLeScRandomValueSize_c]; /*!< LE SC OOB r (Random value) */
static uint8_t gOobReceivedConfirmValueFromPeer[gSmpLeScRandomConfirmValueSize_c]; /*!< LE SC OOB Cr (Random Confirm value) */
void BleConnManager_GapPeripheralEvent(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent)
{
switch (pConnectionEvent->eventType)
{
case gConnEvtConnected_c:
{
...
...
...
}
break;
case gConnEvtPairingRequest_c:
{
#if (defined(gAppUsePairing_d) && (gAppUsePairing_d == 1U))
gPairingParameters.centralKeys = pConnectionEvent->eventData.pairingEvent.centralKeys;
(void)Gap_AcceptPairingRequest(peerDeviceId, &gPairingParameters);
#if (gAppUseOob_d && gAppUseSecureConnections_d)
/* The central has requested pairing, get local LE Secure Connections OOB data */
(void)Gap_LeScGetLocalOobData();
#endif
#else
(void)Gap_RejectPairing(peerDeviceId, gPairingNotSupported_c);
#endif
}
break;
...
...
...
#if (gAppUseOob_d && gAppUseSecureConnections_d)
case gConnEvtLeScOobDataRequest_c:
{
/* The stack has requested the peer LE Secure Connections OOB data. Fill the gPeerOobData struct and provide it to the stack */
FLib_MemCpy(gPeerOobData.randomValue, &gOobReceivedRandomValueFromPeer[0], gSmpLeScRandomValueSize_c);
FLib_MemCpy(gPeerOobData.confirmValue, &gOobReceivedConfirmValueFromPeer[0], gSmpLeScRandomConfirmValueSize_c);
Gap_LeScSetPeerOobData(peerDeviceId, &gPeerOobData);
}
break;
#endif
...
...
...
}
}
The gLeScPublicKeyRegenerated_c event in the BleConnManager_GenericEvent function must be handled using the Gap_LeScGetLocalOobData API as depicted below. Each time that Gap_LeScGetLocalOobData is executed by the software, it generates, asynchronously, the gLeScLocalOobData_c event (also handled in the BleConnManager_GenericEvent function) indicating that the local (r, Cr) values were successfully generated and you can read them using the pGenericEvent->eventData.localOobData pointer to send it OOB to the peer device. In this example, Oob_SendLocalRandomValueToPeer and Oob_SendLocalConfirmValueToPeer are custom synchronous functions that demonstrate how you can implement a custom API that sends the local (r, Cr) read from pGenericEvent->eventData.localOobData pointer to the peer device using other protocols (SPI, UART, I2C, etc).
void BleConnManager_GenericEvent(gapGenericEvent_t* pGenericEvent)
{
switch (pGenericEvent->eventType)
{
case gInitializationComplete_c:
{
...
...
...
}
break;
...
...
...
#if (defined(gAppUsePairing_d) && (gAppUsePairing_d == 1U))
case gLeScPublicKeyRegenerated_c:
{
/* Key pair regenerated -> reset pairing counters */
mFailedPairings = mSuccessfulPairings = 0;
/* Local Secure Connections OOB data must be refreshed whenever this event occurs */
#if (gAppUseOob_d && gAppUseSecureConnections_d)
(void)Gap_LeScGetLocalOobData();
#endif
}
break;
#endif
...
...
...
#if (gAppUseOob_d && gAppUseSecureConnections_d)
case gLeScLocalOobData_c:
{
/* Get the local Secure Connections OOB data and send to the peer */
Oob_SendLocalRandomValueToPeer((uint8_t*)pGenericEvent->eventData.localOobData.randomValue);
Oob_SendLocalConfirmValueToPeer((uint8_t*)pGenericEvent->eventData.localOobData.confirmValue);
}
break;
#endif
...
...
...
}
}
The following example is based on the HID Host software included in the FRDM-KW36 SDK. It explains the minimum code needed to enable OOB. In the following sections, brown color indicates that such definition or API takes part in the stack and violet color indicates that such definition does not take part in the stack and its use is only for explanation purposes in this document.
The app_preinclude.h header file contains definitions for the management of the application. To enable OOB pairing, you must ensure that gAppUseBonding_d and gAppUsePairing_d are set to 1.
You can also set the value of the gBleLeScOobHasMitmProtection_c in this file, depending on the security mode and level needed in your application.
This example makes use of two custom definitions: gAppUseOob_d and gAppUseSecureConnections_d. Such definitions are used to explain how to enable/disable OOB and, if OOB is enabled, how to switch between LE Secure Connections pairing or LE Legacy paring.
/*! Enable/disable use of bonding capability */
#define gAppUseBonding_d 1
/*! Enable/disable use of pairing procedure */
#define gAppUsePairing_d 1
/*! Enable/disable use of privacy */
#define gAppUsePrivacy_d 0
#define gPasskeyValue_c 999999
/*! Enable/disable use of OOB pairing */
#define gAppUseOob_d 1
/*! Enable MITM protection when using OOB pairing */
#if (gAppUseOob_d)
#define gBleLeScOobHasMitmProtection_c TRUE
#endif
/*! Enable/disable Secure Connections */
#define gAppUseSecureConnections_d 1
Using the code above, you can enable or disable OOB using gAppUseOob_d, also you can decide whether to use LE Secure Connections (gAppUseSecureConnections_d = 1) or LE Legacy (gAppUseSecureConnections_d = 0)
The following portion fo code depicts how to fill gPairingParameters struct depending on which pairing method is used by the application.
/* SMP Data */
gapPairingParameters_t gPairingParameters = {
.withBonding = (bool_t)gAppUseBonding_d,
/* If Secure Connections pairing is supported, then set Security Mode 1 Level 4 */
/* If Legacy pairing is supported, then set Security Mode 1 Level 3 */
#if (gAppUseSecureConnections_d)
.securityModeAndLevel = gSecurityMode_1_Level_4_c,
#else
.securityModeAndLevel = gSecurityMode_1_Level_3_c,
#endif
.maxEncryptionKeySize = mcEncryptionKeySize_c,
.localIoCapabilities = gIoKeyboardDisplay_c,
/* OOB Available enabled when app_preinclude.h file gAppUseOob_d macro is true */
.oobAvailable = (bool_t)gAppUseOob_d,
#if (gAppUseSecureConnections_d)
.centralKeys = (gapSmpKeyFlags_t) (gIrk_c),
.peripheralKeys = (gapSmpKeyFlags_t) (gIrk_c),
#else
.centralKeys = (gapSmpKeyFlags_t) (gLtk_c | gIrk_c),
.peripheralKeys = (gapSmpKeyFlags_t) (gLtk_c | gIrk_c),
#endif
/* Secure Connections enabled when app_preinclude.h file gAppUseSecureConnections_d macro is true */
.leSecureConnectionSupported = (bool_t)gAppUseSecureConnections_d,
.useKeypressNotifications = FALSE,
};
If your application will use LE Legacy Pairing, then you have to implement Gap_ProvideOob in response to the gConnEvtOobRequest_c event at the BleConnManager_GapCentralEvent function. In this example, gOobOwnTKData is an array that stores the TK data which will be sent OOB to the peer device (using SPI, UART, I2C, etc) and, at the same time, is the TK data that will be provided to the stack using Gap_ProvideOob. This data must be common on both Central and Peripheral devices, so the procedure to share the TK depends entirely on your application. Oob_SendLocalTKValueToPeer is a custom synchronous function that demonstrates how you can implement a custom API that sends the local TK to the peer device using other protocols (SPI, UART, I2C, etc).
static uint8_t gOobOwnTKData[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
void BleConnManager_GapCentralEvent(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent)
{
switch (pConnectionEvent->eventType)
{
case gConnEvtConnected_c:
{
...
...
...
}
break;
...
...
...
case gConnEvtPairingResponse_c:
{
/* Send Legacy OOB data to the peer */
#if (gAppUseOob_d & !gAppUseSecureConnections_d)
Oob_SendLocalTKValueToPeer(&gOobOwnTKData[0]);
#endif
}
break;
...
...
...
#if (gAppUseOob_d && !gAppUseSecureConnections_d)
case gConnEvtOobRequest_c:
{
/* The stack has requested the LE Legacy OOB data*/
(void)Gap_ProvideOob(peerDeviceId, &gOobOwnTKData[0]);
}
break;
#endif
...
...
...
}
}
When using Secure Connections Pairing, the application must handle two events at the BleConnManager_GapCentralEvent function. In gConnEvtPairingResponse_c event, you must implement Gap_LeScGetLocalOobData API to generate the local (r, Cr) values. The gConnEvtLeScOobDataRequest_c event indicates that the application is requesting the (r, Cr) values previously received OOB from the peer device (using SPI, UART, I2C, etc). Such values are contained into gOobReceivedRandomValueFromPeer and gOobReceivedConfirmValueFromPeer buffers. You must implement Gap_LeScSetPeerOobData in response to gConnEvtLeScOobDataRequest_c, This function has two parameters, the device ID of the peer and a pointer to a gapLeScOobData_t type struct. This struct is filled with the data contained in gOobReceivedRandomValueFromPeer and gOobReceivedConfirmValueFromPeer buffers.
gapLeScOobData_t gPeerOobData;
static uint8_t gOobReceivedRandomValueFromPeer[gSmpLeScRandomValueSize_c]; /*!< LE SC OOB r (Random value) */
static uint8_t gOobReceivedConfirmValueFromPeer[gSmpLeScRandomConfirmValueSize_c]; /*!< LE SC OOB Cr (Random Confirm value) */
void BleConnManager_GapCentralEvent(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent)
{
switch (pConnectionEvent->eventType)
{
case gConnEvtConnected_c:
{
...
...
...
}
break;
...
...
...
case gConnEvtPairingResponse_c:
{
/* The peripheral has acepted pairing, get local LE Secure Connections OOB data */
#if (gAppUseOob_d && gAppUseSecureConnections_d)
(void)Gap_LeScGetLocalOobData();
#endif
}
break;
...
...
...
#if (gAppUseOob_d && gAppUseSecureConnections_d)
case gConnEvtLeScOobDataRequest_c:
{
/* The stack has requested the peer LE Secure Connections OOB data. Fill the gPeerOobData struct and provide it to the stack */
FLib_MemCpy(gPeerOobData.randomValue, &gOobReceivedRandomValueFromPeer[0], gSmpLeScRandomValueSize_c);
FLib_MemCpy(gPeerOobData.confirmValue, &gOobReceivedConfirmValueFromPeer[0], gSmpLeScRandomConfirmValueSize_c);
Gap_LeScSetPeerOobData(peerDeviceId, &gPeerOobData);
}
break;
#endif
...
...
...
}
}
The gLeScPublicKeyRegenerated_c event in the BleConnManager_GenericEvent function must be handled using the Gap_LeScGetLocalOobData API as depicted below. Each time that Gap_LeScGetLocalOobData is executed by the software, it generates, asynchronously, the gLeScLocalOobData_c event (also handled in the BleConnManager_GenericEvent function) indicating that the local (r, Cr) values were successfully generated and you can read them using the pGenericEvent->eventData.localOobData pointer to send it OOB to the peer device. In this example, Oob_SendLocalRandomValueToPeer and Oob_SendLocalConfirmValueToPeer are custom synchronous functions that demonstrate how you can implement a custom API that sends the local (r, Cr) read from pGenericEvent->eventData.localOobData pointer to the peer device using other protocols (SPI, UART, I2C, etc).
void BleConnManager_GenericEvent(gapGenericEvent_t* pGenericEvent)
{
switch (pGenericEvent->eventType)
{
case gInitializationComplete_c:
{
...
...
...
}
break;
...
...
...
#if (defined(gAppUsePairing_d) && (gAppUsePairing_d == 1U))
case gLeScPublicKeyRegenerated_c:
{
/* Key pair regenerated -> reset pairing counters */
mFailedPairings = mSuccessfulPairings = 0;
/* Local LE Secure Connections OOB data must be refreshed whenever this event occurs */
#if (gAppUseOob_d && gAppUseSecureConnections_d)
(void)Gap_LeScGetLocalOobData();
#endif
}
break;
#endif
...
...
...
#if (gAppUseOob_d && gAppUseSecureConnections_d)
case gLeScLocalOobData_c:
{
/* Get the local LE Secure Connections OOB data and send to the peer */
Oob_SendLocalRandomValueToPeer((uint8_t*)pGenericEvent->eventData.localOobData.randomValue);
Oob_SendLocalConfirmValueToPeer((uint8_t*)pGenericEvent->eventData.localOobData.confirmValue);
}
break;
#endif
...
...
...
}
}
The following figure shows a simplified flow diagram of the LE Legacy OOB pairing example in this document. The LE Central device is the one that contains the OOB TK data that will be shared OOB using the custom Oob_SendLocalTKValueToPeer function. It must be implemented at the gConnEvtPairingResponse_c event to ensure that both devices know the OOB TK before to execute Gap_ProvideOob since this function requests this data. If the OOB data is correct on both sides, the pairing procedure ends, and it is noticed through gConnEvtPairingComplete_c.
The following figure shows a simplified flow diagram of the LE Secure Connections OOB pairing example in this document. After both devices enter in connection, the data that will be shared OOB using the custom Oob_SendLocalRandomValueToPeer and Oob_SendLocalConfirmValueToPeer functions is yielded by Gap_LeScGetLocalOobData on both sides. The last one must be implemented at gConnEvtPairingResponse_c and gConnEvtPairingRequest_c events to ensure that both devices know the Peripheral and Central (r, Cr) OOB data before to execute Gap_LeScSetPeerOobData since this function requests this data. If the OOB data is correct on both sides, the pairing procedure ends, and it is noticed through gConnEvtPairingComplete_c.
This is how OOB pairing can be implemented in your project. I hope this document will be useful to you. Please, let us know any questions or comments.
Hi EdgarLomeli,
Could you share a demo project, I tested with legacy pairing, but no gConnEvtOobRequest_c callback, I used SDK_2.2.7_FRDM-KW36.