blob: 9f95496a7877f02eceb9815b32f4be89c66f2991 [file] [log] [blame]
/*
* Copyright (c) 2024 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*
* Proxy Solicitation test
*/
#include "mesh_test.h"
#include "mesh/access.h"
#include "mesh/settings.h"
#include "mesh/net.h"
#include "mesh/crypto.h"
#include "mesh/proxy.h"
#include <string.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/uuid.h>
LOG_MODULE_REGISTER(test_proxy_sol, LOG_LEVEL_INF);
#define WAIT_TIME 60 /*seconds*/
#define SEM_TIMEOUT 6 /*seconds*/
#define BEACON_TYPE_NET_ID 0
#define BEACON_TYPE_PRIVATE_NET_ID 2
#define BEACON_NET_ID_LEN 20
#define OTHER_ADV_TYPES_LEN 28
static struct bt_mesh_prov prov;
static struct bt_mesh_cfg_cli cfg_cli;
static struct bt_mesh_priv_beacon_cli priv_beacon_cli;
static struct bt_mesh_od_priv_proxy_cli od_priv_proxy_cli;
static struct k_sem beacon_sem;
static const struct bt_mesh_test_cfg tester_cfg = {
.addr = 0x0001,
.dev_key = {0x01},
};
static const struct bt_mesh_test_cfg iut_cfg = {
.addr = 0x0002,
.dev_key = {0x02},
};
static struct bt_mesh_model models[] = {
BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli),
BT_MESH_MODEL_PRIV_BEACON_SRV,
BT_MESH_MODEL_PRIV_BEACON_CLI(&priv_beacon_cli),
BT_MESH_MODEL_OD_PRIV_PROXY_SRV,
BT_MESH_MODEL_OD_PRIV_PROXY_CLI(&od_priv_proxy_cli)
};
static struct bt_mesh_elem elems[] = {
BT_MESH_ELEM(0, models, BT_MESH_MODEL_NONE),
};
static const struct bt_mesh_comp comp = {
.cid = TEST_VND_COMPANY_ID,
.vid = 0xaaaa,
.pid = 0xbbbb,
.elem = elems,
.elem_count = ARRAY_SIZE(elems),
};
static bool is_tester_address(void)
{
return bt_mesh_primary_addr() == tester_cfg.addr;
}
static uint8_t proxy_adv_type_get(uint8_t adv_type, struct net_buf_simple *buf)
{
uint8_t type;
uint8_t len = buf->len;
if (adv_type != BT_GAP_ADV_TYPE_ADV_IND || len < 12) {
return 0xFF;
}
(void)net_buf_simple_pull_mem(buf, 11);
type = net_buf_simple_pull_u8(buf);
if (len != ((type == BEACON_TYPE_NET_ID) ? BEACON_NET_ID_LEN : OTHER_ADV_TYPES_LEN)) {
return 0xFF;
}
return type;
}
static void scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad)
{
uint8_t type = proxy_adv_type_get(info->adv_type, ad);
if (is_tester_address() && type == BEACON_TYPE_PRIVATE_NET_ID) {
k_sem_give(&beacon_sem);
}
}
static struct bt_le_scan_cb scan_cb = {
.recv = scan_recv,
};
static void tester_configure(void)
{
uint8_t err, status;
k_sem_init(&beacon_sem, 0, 1);
bt_le_scan_cb_register(&scan_cb);
bt_mesh_test_cfg_set(NULL, WAIT_TIME);
bt_mesh_device_setup(&prov, &comp);
ASSERT_OK_MSG(bt_mesh_provision(test_net_key, 0, 0, 0, tester_cfg.addr, tester_cfg.dev_key),
"Node failed to provision");
err = bt_mesh_cfg_cli_app_key_add(0, tester_cfg.addr, 0, 0, test_app_key, &status);
if (err || status) {
FAIL("AppKey add failed (err %d, status %u)", err, status);
}
}
static void iut_configure(void)
{
uint8_t err, status;
bt_mesh_test_cfg_set(NULL, WAIT_TIME);
bt_mesh_device_setup(&prov, &comp);
/* Configurations stored in flash during immediate_replay_attack */
if (!bt_mesh_is_provisioned()) {
ASSERT_OK_MSG(
bt_mesh_provision(test_net_key, 0, 0, 0, iut_cfg.addr, iut_cfg.dev_key),
"Node failed to provision");
err = bt_mesh_cfg_cli_app_key_add(0, iut_cfg.addr, 0, 0, test_app_key, &status);
if (err || status) {
FAIL("AppKey add failed (err %d, status %u)", err, status);
}
err = bt_mesh_cfg_cli_gatt_proxy_set(0, iut_cfg.addr, BT_MESH_GATT_PROXY_DISABLED,
&status);
if (err || status) {
FAIL("Proxy state disable failed (err %d, status %u)", err, status);
}
err = bt_mesh_priv_beacon_cli_gatt_proxy_set(0, iut_cfg.addr,
BT_MESH_GATT_PROXY_DISABLED, &status);
if (err || status) {
FAIL("Private proxy state disable failed (err %d, status %u)", err, status);
}
err = bt_mesh_od_priv_proxy_cli_set(0, iut_cfg.addr, BT_MESH_FEATURE_ENABLED,
&status);
if (err || !status) {
FAIL("On-Demand Private Proxy enable failed (err %d, status %u)", err,
status);
}
}
}
static void sol_fixed_pdu_create(struct bt_mesh_subnet *sub, struct net_buf_simple *pdu)
{
uint32_t fixed_seq_n = 2;
net_buf_simple_add_u8(pdu, sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.nid);
net_buf_simple_add_u8(pdu, 0x80);
net_buf_simple_add_le24(pdu, sys_cpu_to_be24(fixed_seq_n));
net_buf_simple_add_le16(pdu, sys_cpu_to_be16(bt_mesh_primary_addr()));
net_buf_simple_add_le16(pdu, 0x0000);
ASSERT_OK(bt_mesh_net_encrypt(&sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.enc, pdu, 0,
BT_MESH_NONCE_SOLICITATION));
ASSERT_OK(bt_mesh_net_obfuscate(pdu->data, 0,
&sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.privacy));
net_buf_simple_push_u8(pdu, 0);
net_buf_simple_push_le16(pdu, BT_UUID_MESH_PROXY_SOLICITATION_VAL);
}
static int sol_fixed_pdu_send(void)
{
NET_BUF_SIMPLE_DEFINE(pdu, 20);
net_buf_simple_init(&pdu, 3);
struct bt_mesh_subnet *sub = bt_mesh_subnet_find(NULL, NULL);
uint16_t adv_int = BT_MESH_TRANSMIT_INT(CONFIG_BT_MESH_SOL_ADV_XMIT);
sol_fixed_pdu_create(sub, &pdu);
struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL,
BT_UUID_16_ENCODE(BT_UUID_MESH_PROXY_SOLICITATION_VAL)),
BT_DATA(BT_DATA_SVC_DATA16, pdu.data, pdu.size),
};
return bt_mesh_adv_bt_data_send(CONFIG_BT_MESH_SOL_ADV_XMIT, adv_int, ad, 3);
}
static void test_tester_beacon_rcvd(void)
{
tester_configure();
/* Check that no beacons are currently being picked up by the scanner */
ASSERT_EQUAL(-EAGAIN, k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)));
ASSERT_OK(bt_mesh_proxy_solicit(0));
ASSERT_OK_MSG(k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)),
"No beacon (proxy advs) reveiced.");
PASS();
}
static void test_tester_immediate_replay_attack(void)
{
tester_configure();
/* Check that no beacons are currently being picked up by the scanner */
ASSERT_EQUAL(-EAGAIN, k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)));
/* Send initial solicitation PDU with fixed sequence number */
ASSERT_OK(sol_fixed_pdu_send());
ASSERT_OK_MSG(k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)),
"No beacon (proxy advs) reveiced.");
k_sem_reset(&beacon_sem);
/* Wait for iut proxy advs to time out */
k_sleep(K_MSEC(200));
ASSERT_EQUAL(-EAGAIN, k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)));
/* Replay attack */
ASSERT_OK(sol_fixed_pdu_send());
ASSERT_EQUAL(-EAGAIN, k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)));
PASS();
}
static void test_tester_power_replay_attack(void)
{
tester_configure();
/* Check that no beacons are currently being picked up by the scanner */
ASSERT_EQUAL(-EAGAIN, k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)));
/* Replay attack, using standard api, starting with sseq = 0 < fixed sseq */
for (size_t i = 0; i < 3; i++) {
k_sleep(K_MSEC(100));
ASSERT_OK(bt_mesh_proxy_solicit(0));
}
ASSERT_EQUAL(-EAGAIN, k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)));
/* Send a sol pdu with sseq = 3 > fixed sseq (2) */
ASSERT_OK(bt_mesh_proxy_solicit(0));
ASSERT_OK_MSG(k_sem_take(&beacon_sem, K_SECONDS(SEM_TIMEOUT)),
"No beacon (proxy advs) reveiced.");
PASS();
}
static void test_iut_beacon_send(void)
{
iut_configure();
k_sleep(K_SECONDS(3 * SEM_TIMEOUT));
PASS();
}
static void test_iut_immediate_replay_attack(void)
{
iut_configure();
k_sleep(K_SECONDS(5 * SEM_TIMEOUT));
PASS();
}
static void test_iut_power_replay_attack(void)
{
iut_configure();
k_sleep(K_SECONDS(4 * SEM_TIMEOUT));
PASS();
}
#define TEST_CASE(role, name, description) \
{ \
.test_id = "proxy_sol_" #role "_" #name, \
.test_descr = description, \
.test_tick_f = bt_mesh_test_timeout, \
.test_main_f = test_##role##_##name, \
}
static const struct bst_test_instance test_proxy_sol[] = {
TEST_CASE(tester, beacon_rcvd, "Check for beacon after solicitation"),
TEST_CASE(tester, immediate_replay_attack, "Perform replay attack immediately"),
TEST_CASE(tester, power_replay_attack, "Perform replay attack after power cycle of iut"),
TEST_CASE(iut, beacon_send, "Respond with beacon after solicitation"),
TEST_CASE(iut, immediate_replay_attack, "Device is under immediate replay attack"),
TEST_CASE(iut, power_replay_attack, "Device is under power cycle replay attack"),
BSTEST_END_MARKER};
struct bst_test_list *test_proxy_sol_install(struct bst_test_list *tests)
{
tests = bst_add_tests(tests, test_proxy_sol);
return tests;
}