diff --git a/doc/connectivity/bluetooth/bluetooth-shell.rst b/doc/connectivity/bluetooth/bluetooth-shell.rst index f639cb0b030..2b38f4c7b6f 100644 --- a/doc/connectivity/bluetooth/bluetooth-shell.rst +++ b/doc/connectivity/bluetooth/bluetooth-shell.rst @@ -228,6 +228,90 @@ Let's now have a look at some extended advertising features. To enable extended This will create an extended advertiser, that is connectable and non-scannable. +Encrypted Advertising Data +========================== + +Zephyr has support for the Encrypted Advertising Data feature. The :code:`bt encrypted-ad` +sub-commands allow managing the advertising data of a given advertiser. + +To encrypt the advertising data, key materials need to be provided, that can be done with :code:`bt +encrypted-ad set-keys `. The session key is 16 bytes long and the +initialisation vector is 8 bytes long. + +You can add advertising data by using :code:`bt encrypted-ad add-ad` and :code:`bt encrypted-ad +add-ead`. The former will take add one advertising data structure (as defined in the Core +Specification), when the later will read the given data, encrypt them and then add the generated +encrypted advertising data structure. It's possible to mix encrypted and non-encrypted data, when +done adding advertising data, :code:`bt encrypted-ad commit-ad` can be used to apply the change to +the data to the selected advertiser. After that the advertiser can be started as described +previously. It's possible to clear the advertising data by using :code:`bt encrypted-ad clear-ad`. + +On the Central side, it's possible to decrypt the received encrypted advertising data by setting the +correct keys material as described earlier and then enabling the decrypting of the data with +:code:`bt encrypted-ad decrypt-scan on`. + +.. note:: + + To see the advertising data in the scan report :code:`bt scan-verbose-output` need to be + enabled. + +.. note:: + + It's possible to increase the length of the advertising data by increasing the value of + :kconfig:option:`CONFIG_BT_CTLR_ADV_DATA_LEN_MAX` and + :kconfig:option:`CONFIG_BT_CTLR_SCAN_DATA_LEN_MAX`. + +Here is a simple example demonstrating the usage of EAD: + +.. tabs:: + + .. group-tab:: Peripheral + + .. code-block:: console + + uart:~$ bt init + ... + uart:~$ bt adv-create conn-nscan ext-adv + Created adv id: 0, adv: 0x81769a0 + uart:~$ bt encrypted-ad set-keys 9ba22d3824efc70feb800c80294cba38 2e83f3d4d47695b6 + session key set to: + 00000000: 9b a2 2d 38 24 ef c7 0f eb 80 0c 80 29 4c ba 38 |..-8$... ....)L.8| + initialisation vector set to: + 00000000: 2e 83 f3 d4 d4 76 95 b6 |.....v.. | + uart:~$ bt encrypted-ad add-ad 06097368656C6C + uart:~$ bt encrypted-ad add-ead 03ffdead03ffbeef + uart:~$ bt encrypted-ad commit-ad + Advertising data for Advertiser[0] 0x81769a0 updated. + uart:~$ bt adv-start + Advertiser[0] 0x81769a0 set started + + .. group-tab:: Central + + .. code-block:: console + + uart:~$ bt init + ... + uart:~$ bt scan-verbose-output on + uart:~$ bt encrypted-ad set-keys 9ba22d3824efc70feb800c80294cba38 2e83f3d4d47695b6 + session key set to: + 00000000: 9b a2 2d 38 24 ef c7 0f eb 80 0c 80 29 4c ba 38 |..-8$... ....)L.8| + initialisation vector set to: + 00000000: 2e 83 f3 d4 d4 76 95 b6 |.....v.. | + uart:~$ bt encrypted-ad decrypt-scan on + Received encrypted advertising data will now be decrypted using provided key materials. + uart:~$ bt scan on + Bluetooth active scan enabled + [DEVICE]: 68:49:30:68:49:30 (random), AD evt type 5, RSSI -59 shell C:1 S:0 D:0 SR:0 E:1 Prim: LE 1M, Secn: LE 2M, Interval: 0x0000 (0 us), SID: 0x0 + [SCAN DATA START - EXT_ADV] + Type 0x09: shell + Type 0x31: Encrypted Advertising Data: 0xe2, 0x17, 0xed, 0x04, 0xe7, 0x02, 0x1d, 0xc9, 0x40, 0x07, uart:~0x18, 0x90, 0x6c, 0x4b, 0xfe, 0x34, 0xad + [START DECRYPTED DATA] + Type 0xff: 0xde, 0xad + Type 0xff: 0xbe, 0xef + [END DECRYPTED DATA] + [SCAN DATA END] + ... + Filter Accept List ****************** diff --git a/subsys/bluetooth/shell/bt.c b/subsys/bluetooth/shell/bt.c index ac1d78c44df..3566e0f31b8 100644 --- a/subsys/bluetooth/shell/bt.c +++ b/subsys/bluetooth/shell/bt.c @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -216,6 +217,30 @@ static struct bt_scan_filter { static const char scan_response_label[] = "[DEVICE]: "; static bool scan_verbose_output; +#if defined(CONFIG_BT_EAD) +static uint8_t bt_shell_ead_session_key[BT_EAD_KEY_SIZE] = {0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, + 0xCC, 0xCD, 0xCE, 0xCF}; +static uint8_t bt_shell_ead_iv[BT_EAD_IV_SIZE] = {0xFB, 0x56, 0xE1, 0xDA, 0xDC, 0x7E, 0xAD, 0xF5}; + +/* this is the number of ad struct allowed */ +#define BT_SHELL_EAD_MAX_AD 10 +static size_t bt_shell_ead_ad_len; + +#if defined(CONFIG_BT_CTLR_ADV_DATA_LEN_MAX) +/* this is the maximum total size of the ad data */ +#define BT_SHELL_EAD_DATA_MAX_SIZE CONFIG_BT_CTLR_ADV_DATA_LEN_MAX +#else +#define BT_SHELL_EAD_DATA_MAX_SIZE 31 +#endif +static size_t bt_shell_ead_data_size; +static uint8_t bt_shell_ead_data[BT_SHELL_EAD_DATA_MAX_SIZE] = {0}; + +int ead_update_ad(void); +#endif + +static bool bt_shell_ead_decrypt_scan; + /** * @brief Compares two strings without case sensitivy * @@ -385,6 +410,36 @@ static bool data_verbose_cb(struct bt_data *data, void *user_data) case BT_DATA_CSIS_RSI: print_data_set(3, data->data, data->data_len); break; + case BT_DATA_ENCRYPTED_AD_DATA: + shell_fprintf(ctx_shell, SHELL_INFO, "Encrypted Advertising Data: "); + print_data_set(1, data->data, data->data_len); + + if (bt_shell_ead_decrypt_scan) { +#if defined(CONFIG_BT_EAD) + shell_fprintf(ctx_shell, SHELL_INFO, "\n%*s[START DECRYPTED DATA]\n", + strlen(scan_response_label), ""); + + int ead_err; + struct net_buf_simple decrypted_buf; + size_t decrypted_data_size = BT_EAD_DECRYPTED_PAYLOAD_SIZE(data->data_len); + uint8_t decrypted_data[decrypted_data_size]; + + ead_err = bt_ead_decrypt(bt_shell_ead_session_key, bt_shell_ead_iv, + data->data, data->data_len, decrypted_data); + if (ead_err) { + shell_error(ctx_shell, "Error during decryption (err %d)", ead_err); + } + + net_buf_simple_init_with_data(&decrypted_buf, &decrypted_data[0], + decrypted_data_size); + + bt_data_parse(&decrypted_buf, &data_verbose_cb, user_data); + + shell_fprintf(ctx_shell, SHELL_INFO, "%*s[END DECRYPTED DATA]", + strlen(scan_response_label), ""); +#endif + } + break; default: print_data_set(1, data->data, data->data_len); } @@ -574,6 +629,13 @@ static bool adv_rpa_expired(struct bt_le_ext_adv *adv) adv_index, adv, keep_rpa ? "not expired" : "expired"); +#if defined(CONFIG_BT_EAD) + /* EAD must be updated each time the RPA is updated */ + if (!keep_rpa) { + ead_update_ad(); + } +#endif + return keep_rpa; } #endif /* defined(CONFIG_BT_PRIVACY) */ @@ -4111,6 +4173,267 @@ static int cmd_auth_oob_tk(const struct shell *sh, size_t argc, char *argv[]) #endif /* !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) */ #endif /* CONFIG_BT_SMP) || CONFIG_BT_BREDR */ +#if defined(CONFIG_BT_EAD) +static int cmd_encrypted_ad_set_keys(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + + const char *session_key = argv[1]; + const char *iv = argv[2]; + + len = hex2bin(session_key, strlen(session_key), bt_shell_ead_session_key, BT_EAD_KEY_SIZE); + if (len != BT_EAD_KEY_SIZE) { + shell_error(sh, "Failed to set session key"); + return -ENOEXEC; + } + + len = hex2bin(iv, strlen(iv), bt_shell_ead_iv, BT_EAD_IV_SIZE); + if (len != BT_EAD_IV_SIZE) { + shell_error(sh, "Failed to set initialisation vector"); + return -ENOEXEC; + } + + shell_info(sh, "session key set to:"); + shell_hexdump(sh, bt_shell_ead_session_key, BT_EAD_KEY_SIZE); + shell_info(sh, "initialisation vector set to:"); + shell_hexdump(sh, bt_shell_ead_iv, BT_EAD_IV_SIZE); + + return 0; +} + +int encrypted_ad_store_ad(const struct shell *sh, uint8_t type, const uint8_t *data, + uint8_t data_len) +{ + /* data_len is the size of the data, add two bytes for the size of the type + * and the length that will be stored with the data + */ + uint8_t new_data_size = data_len + 2; + + if (bt_shell_ead_data_size + new_data_size > BT_SHELL_EAD_DATA_MAX_SIZE) { + shell_error(sh, "Failed to add data (trying to add %d but %d already used on %d)", + new_data_size, bt_shell_ead_data_size, BT_SHELL_EAD_DATA_MAX_SIZE); + return -ENOEXEC; + } + + /* the length is the size of the data + the size of the type */ + bt_shell_ead_data[bt_shell_ead_data_size] = data_len + 1; + bt_shell_ead_data[bt_shell_ead_data_size + 1] = type; + memcpy(&bt_shell_ead_data[bt_shell_ead_data_size + 2], data, data_len); + + bt_shell_ead_data_size += new_data_size; + bt_shell_ead_ad_len += 1; + + return 0; +} + +bool is_payload_valid_ad(uint8_t *payload, size_t payload_size) +{ + size_t idx = 0; + bool is_valid = true; + + uint8_t ad_len; + + while (idx < payload_size) { + ad_len = payload[idx]; + + if (payload_size <= ad_len) { + is_valid = false; + break; + } + + idx += ad_len + 1; + } + + if (idx != payload_size) { + is_valid = false; + } + + return is_valid; +} + +static int cmd_encrypted_ad_add_ead(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + + char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + uint8_t payload[BT_SHELL_EAD_DATA_MAX_SIZE] = {0}; + uint8_t payload_size = hex_payload_size / 2 + hex_payload_size % 2; + + uint8_t ead_size = BT_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size); + + if (ead_size > BT_SHELL_EAD_DATA_MAX_SIZE) { + shell_error(sh, + "Failed to add data. Maximum AD size is %d, passed data size after " + "encryption is %d", + BT_SHELL_EAD_DATA_MAX_SIZE, ead_size); + return -ENOEXEC; + } + + len = hex2bin(hex_payload, hex_payload_size, payload, BT_SHELL_EAD_DATA_MAX_SIZE); + if (len != payload_size) { + shell_error(sh, "Failed to add data"); + return -ENOEXEC; + } + + /* check that the given advertising data structures are valid before encrypting them */ + if (!is_payload_valid_ad(payload, payload_size)) { + shell_error(sh, "Failed to add data. Advertising structure are malformed."); + return -ENOEXEC; + } + + /* store not-yet encrypted AD but claim the expected size of encrypted AD */ + return encrypted_ad_store_ad(sh, BT_DATA_ENCRYPTED_AD_DATA, payload, ead_size); +} + +static int cmd_encrypted_ad_add_ad(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + uint8_t ad_len; + uint8_t ad_type; + + char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + uint8_t payload[BT_SHELL_EAD_DATA_MAX_SIZE] = {0}; + uint8_t payload_size = hex_payload_size / 2 + hex_payload_size % 2; + + len = hex2bin(hex_payload, hex_payload_size, payload, BT_SHELL_EAD_DATA_MAX_SIZE); + if (len != payload_size) { + shell_error(sh, "Failed to add data"); + return -ENOEXEC; + } + + /* the length received is the length of ad data + the length of the data + * type but `bt_data` struct `data_len` field is only the size of the + * data + */ + ad_len = payload[0] - 1; + ad_type = payload[1]; + + /* if the ad data is malformed or there is more than 1 ad data passed we + * fail + */ + if (len != ad_len + 2) { + shell_error(sh, + "Failed to add data. Data need to be formated as specified in the " + "Core Spec. Only one non-encrypted AD payload can be added at a time."); + return -ENOEXEC; + } + + return encrypted_ad_store_ad(sh, ad_type, payload, payload_size); +} + +static int cmd_encrypted_ad_clear_ad(const struct shell *sh, size_t argc, char *argv[]) +{ + memset(bt_shell_ead_data, 0, BT_SHELL_EAD_DATA_MAX_SIZE); + + bt_shell_ead_ad_len = 0; + bt_shell_ead_data_size = 0; + + shell_info(sh, "Advertising data has been cleared."); + + return 0; +} + +int ead_encrypt_ad(const uint8_t *payload, uint8_t payload_size, uint8_t *encrypted_payload) +{ + int err; + + err = bt_ead_encrypt(bt_shell_ead_session_key, bt_shell_ead_iv, payload, payload_size, + encrypted_payload); + if (err != 0) { + shell_error(ctx_shell, "Failed to encrypt AD."); + return -1; + } + + return 0; +} + +int ead_update_ad(void) +{ + int err; + size_t idx = 0; + struct bt_le_ext_adv *adv = adv_sets[selected_adv]; + + struct bt_data *ad; + size_t ad_structs_idx = 0; + struct bt_data ad_structs[BT_SHELL_EAD_MAX_AD] = {0}; + + size_t encrypted_data_buf_len = 0; + uint8_t encrypted_data_buf[BT_SHELL_EAD_DATA_MAX_SIZE] = {0}; + + while (idx < bt_shell_ead_data_size && ad_structs_idx < BT_SHELL_EAD_MAX_AD) { + ad = &ad_structs[ad_structs_idx]; + + /* the data_len from bt_data struct doesn't include the size of the type */ + ad->data_len = bt_shell_ead_data[idx] - 1; + + if (ad->data_len < 0) { + /* if the len is less than 0 that mean there is not even a type field */ + shell_error(ctx_shell, "Failed to update AD due to malformed AD."); + return -ENOEXEC; + } + + ad->type = bt_shell_ead_data[idx + 1]; + + if (ad->data_len > 0) { + if (ad->type == BT_DATA_ENCRYPTED_AD_DATA) { + /* for EAD the size used to store the non-encrypted data + * is the size of those data when encrypted + */ + ead_encrypt_ad(&bt_shell_ead_data[idx + 2], + BT_EAD_DECRYPTED_PAYLOAD_SIZE(ad->data_len), + &encrypted_data_buf[encrypted_data_buf_len]); + + ad->data = &encrypted_data_buf[encrypted_data_buf_len]; + encrypted_data_buf_len += ad->data_len; + } else { + ad->data = &bt_shell_ead_data[idx + 2]; + } + } + + ad_structs_idx += 1; + idx += ad->data_len + 2; + } + + err = bt_le_ext_adv_set_data(adv, ad_structs, bt_shell_ead_ad_len, NULL, 0); + if (err != 0) { + shell_error(ctx_shell, "Failed to set advertising data (err %d)", err); + return -ENOEXEC; + } + + shell_info(ctx_shell, "Advertising data for Advertiser[%d] %p updated.", selected_adv, adv); + + return 0; +} + +static int cmd_encrypted_ad_commit_ad(const struct shell *sh, size_t argc, char *argv[]) +{ + return ead_update_ad(); +} + +static int cmd_encrypted_ad_decrypt_scan(const struct shell *sh, size_t argc, char *argv[]) +{ + const char *action = argv[1]; + + if (strcmp(action, "on") == 0) { + bt_shell_ead_decrypt_scan = true; + shell_info(sh, "Received encrypted advertising data will now be decrypted using " + "provided key materials."); + } else if (strcmp(action, "off") == 0) { + bt_shell_ead_decrypt_scan = false; + shell_info(sh, "Received encrypted advertising data will now longer be decrypted."); + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + return 0; +} +#endif + static int cmd_default_handler(const struct shell *sh, size_t argc, char **argv) { if (argc == 1) { @@ -4158,6 +4481,19 @@ SHELL_STATIC_SUBCMD_SET_CREATE(bt_scan_filter_clear_cmds, ); #endif /* CONFIG_BT_OBSERVER */ +#if defined(CONFIG_BT_EAD) +SHELL_STATIC_SUBCMD_SET_CREATE( + bt_encrypted_ad_cmds, + SHELL_CMD_ARG(set-keys, NULL, " ", cmd_encrypted_ad_set_keys, 3, + 0), + SHELL_CMD_ARG(add-ead, NULL, "", cmd_encrypted_ad_add_ead, 2, 0), + SHELL_CMD_ARG(add-ad, NULL, "", cmd_encrypted_ad_add_ad, 2, 0), + SHELL_CMD(clear-ad, NULL, HELP_NONE, cmd_encrypted_ad_clear_ad), + SHELL_CMD(commit-ad, NULL, HELP_NONE, cmd_encrypted_ad_commit_ad), + SHELL_CMD_ARG(decrypt-scan, NULL, HELP_ONOFF, cmd_encrypted_ad_decrypt_scan, 2, 0), + SHELL_SUBCMD_SET_END); +#endif + SHELL_STATIC_SUBCMD_SET_CREATE(bt_cmds, SHELL_CMD_ARG(init, NULL, "[no-settings-load], [sync]", cmd_init, 1, 2), @@ -4246,6 +4582,10 @@ SHELL_STATIC_SUBCMD_SET_CREATE(bt_cmds, cmd_per_adv_sync_delete, 1, 1), SHELL_CMD_ARG(per-adv-sync-select, NULL, "[adv]", cmd_per_adv_sync_select, 1, 1), #endif /* defined(CONFIG_BT_PER_ADV_SYNC) */ +#if defined(CONFIG_BT_EAD) + SHELL_CMD(encrypted-ad, &bt_encrypted_ad_cmds, "Manage advertiser with encrypted data", + cmd_default_handler), +#endif /* CONFIG_BT_EAD */ #if defined(CONFIG_BT_CONN) #if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER) SHELL_CMD_ARG(past-subscribe, NULL, "[conn] [skip ] " diff --git a/tests/bluetooth/shell/prj.conf b/tests/bluetooth/shell/prj.conf index 168ccf62a7f..185167de1d1 100644 --- a/tests/bluetooth/shell/prj.conf +++ b/tests/bluetooth/shell/prj.conf @@ -46,6 +46,8 @@ CONFIG_BT_PER_ADV_SYNC=y CONFIG_BT_USER_DATA_LEN_UPDATE=y CONFIG_BT_AUTO_DATA_LEN_UPDATE=y +CONFIG_BT_EAD=y + CONFIG_BT_USER_PHY_UPDATE=y CONFIG_BT_AUTO_PHY_UPDATE=y