wireshark/epan/dissectors/packet-zbee-direct.c

1920 lines
63 KiB
C

/* packet-zbee-direct.c
* Dissector routines for the ZigBee Direct
* Copyright 2021 DSR Corporation, http://dsr-wireless.com/
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include <gcrypt.h>
#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/uat.h>
#include "packet-zbee-security.h"
#include "packet-bluetooth.h"
#include "packet-ieee802154.h"
#include "packet-zbee-nwk.h"
#include "packet-zbee-tlv.h"
#include "packet-zbee-direct.h"
/*-------------------------------------
* Dissector Function Prototypes
*-------------------------------------
*/
static int dissect_zb_direct_dump_info(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_secur_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data, unsigned offset, guint msg_id);
static int dissect_zb_direct_secur_c25519_aesmmo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_secur_c25519_sha256(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_secur_p256(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_formation(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_status(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_join(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_permit_join(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_leave(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_manage_joiners(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_identify(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_finding_binding(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_tunneling(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);
static int dissect_zb_direct_common(tvbuff_t **tvb, packet_info *pinfo, proto_tree **tree, void *data, unsigned offset, const guint8 *serv_uuid, const guint8 *char_uuid);
/* Used dissectors */
static dissector_handle_t zbee_nwk_handle;
/* TLV Node-elements */
static int proto_zb_direct;
/* Leaf-elements */
static int hf_zb_direct_info_type;
static int hf_zb_direct_info_key;
static int hf_zb_direct_info_zdd_ieee;
static int hf_zb_direct_info_zvd_ieee;
static int hf_zb_direct_info_encryption;
static int hf_zb_direct_msg_type;
/* Commissioning */
static int hf_zb_direct_comm_permit_time;
static int hf_zb_direct_comm_rejoin;
static int hf_zb_direct_comm_rm_children;
static int hf_zb_direct_comm_identify_time;
static int hf_zb_direct_comm_fb_endpoint;
static int hf_zb_direct_comm_fb_initiator;
/* Markers (also leafs) */
static int hf_zb_direct_unrecognized_msg;
static int hf_zb_direct_char_info;
static int hf_zb_direct_char_c25519_aesmmo;
static int hf_zb_direct_char_c25519_sha256;
static int hf_zb_direct_char_p256;
static int hf_zb_direct_char_form;
static int hf_zb_direct_char_status;
static int hf_zb_direct_char_join;
static int hf_zb_direct_char_permit_join;
static int hf_zb_direct_char_leave;
static int hf_zb_direct_char_manage_joiners;
static int hf_zb_direct_char_identify;
static int hf_zb_direct_char_finding_binding;
static int hf_zb_direct_char_tunneling;
/* Expert items */
static expert_field ei_zb_direct_crypt_error;
/* Trees entities */
static gint ett_zb_direct;
static const guint8 serv_secur_uuid[] = { 0xe3, 0x29, 0xb4, 0x99, 0x02, 0x6d, 0xe9, 0xbf,
0x81, 0x44, 0x00, 0x00, 0xf4, 0x4a, 0x14, 0x29 };
static const guint8 char_p256_uuid[] = { 0xe3, 0x29, 0xb4, 0x99, 0x02, 0x6d, 0xe9, 0xbf,
0x81, 0x44, 0x03, 0x00, 0xf4, 0x4a, 0x14, 0x29 };
static const guint8 char_c25519_aesmmo_uuid[] = { 0xe3, 0x29, 0xb4, 0x99, 0x02, 0x6d, 0xe9, 0xbf,
0x81, 0x44, 0x01, 0x00, 0xf4, 0x4a, 0x14, 0x29 };
static const guint8 char_c25519_sha256_uuid[] = { 0xe3, 0x29, 0xb4, 0x99, 0x02, 0x6d, 0xe9, 0xbf,
0x81, 0x44, 0x02, 0x00, 0xf4, 0x4a, 0x14, 0x29 };
static const guint8 serv_comm_uuid[] = { 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0xf7, 0xff, 0x00, 0x00 };
static const guint8 char_form_uuid[] = { 0x61, 0x3a, 0x33, 0x27, 0x1c, 0x49, 0x63, 0xb1,
0x1c, 0x42, 0x01, 0x00, 0x7d, 0x37, 0x72, 0x70 };
static const guint8 char_join_uuid[] = { 0x61, 0x3a, 0x33, 0x27, 0x1c, 0x49, 0x63, 0xb1,
0x1c, 0x42, 0x02, 0x00, 0x7d, 0x37, 0x72, 0x70 };
static const guint8 char_permit_uuid[] = { 0x61, 0x3a, 0x33, 0x27, 0x1c, 0x49, 0x63, 0xb1,
0x1c, 0x42, 0x03, 0x00, 0x7d, 0x37, 0x72, 0x70 };
static const guint8 char_leave_uuid[] = { 0x61, 0x3a, 0x33, 0x27, 0x1c, 0x49, 0x63, 0xb1,
0x1c, 0x42, 0x04, 0x00, 0x7d, 0x37, 0x72, 0x70 };
static const guint8 char_status_uuid[] = { 0x61, 0x3a, 0x33, 0x27, 0x1c, 0x49, 0x63, 0xb1,
0x1c, 0x42, 0x05, 0x00, 0x7d, 0x37, 0x72, 0x70 };
static const guint8 char_identify_uuid[] = { 0x61, 0x3a, 0x33, 0x27, 0x1c, 0x49, 0x63, 0xb1,
0x1c, 0x42, 0x07, 0x00, 0x7d, 0x37, 0x72, 0x70 };
static const guint8 char_manage_joiners_uuid[] = { 0x61, 0x3a, 0x33, 0x27, 0x1c, 0x49, 0x63, 0xb1,
0x1c, 0x42, 0x06, 0x00, 0x7d, 0x37, 0x72, 0x70 };
static const guint8 char_finding_binding_uuid[] = { 0x61, 0x3a, 0x33, 0x27, 0x1c, 0x49, 0x63, 0xb1,
0x1c, 0x42, 0x08, 0x00, 0x7d, 0x37, 0x72, 0x70 };
static const guint8 serv_tunnel_uuid[] = { 0x3f, 0x31, 0xd5, 0x8b, 0x37, 0xb2, 0x20, 0x81,
0xf4, 0x45, 0x00, 0x00, 0xfd, 0x78, 0xd1, 0x8b };
static const guint8 char_tunnel_uuid[] = { 0x3f, 0x31, 0xd5, 0x8b, 0x37, 0xb2, 0x20, 0x81,
0xf4, 0x45, 0x01, 0x00, 0xfd, 0x78, 0xd1, 0x8b };
#define ZIGBEE_DIRECT_MAX_ATT_SIZE 248
#define ZIGBEE_DIRECT_AUTH_STR_SIZE (16 + 1 + 16 + 1)
#define ZIGBEE_DIRECT_SECUR_CONTROL 0x05
/* MIC length */
#define ZB_CCM_M 4
#define KEY_LEN 16
#define MAX_CONNECTIONS 2
/*****************************************************************************/
/******************************** Static Data ********************************/
/*****************************************************************************/
static uat_t *zbd_secur_key_table_uat;
/* Values in the key rings. */
typedef struct
{
guint frame_num;
guint8 zdd_ieee[8];
guint8 zvd_ieee[8];
guint8 key[KEY_LEN];
gchar *label;
} zb_direct_key_record_t;
/* UAT Key Entry */
typedef struct uat_key_record_s
{
gchar *zdd_ieee;
gchar *zvd_ieee;
gchar *key;
gchar *label;
} uat_key_record_t;
UAT_CSTRING_CB_DEF(uat_key_records, zdd_ieee, uat_key_record_t)
UAT_CSTRING_CB_DEF(uat_key_records, zvd_ieee, uat_key_record_t)
UAT_CSTRING_CB_DEF(uat_key_records, key, uat_key_record_t)
UAT_CSTRING_CB_DEF(uat_key_records, label, uat_key_record_t)
static GSList *zbee_pc_keyring = NULL;
static uat_key_record_t *uat_key_records = NULL;
static guint num_uat_key_records = 0;
/* Common data */
static guint8 g_conn_id;
static bool ignore_late_keys = true;
/* Info types */
typedef enum
{
DUMP_INFO_KEY_DEL,
DUMP_INFO_KEY_SET,
DUMP_INFO_ENCRYPTION_STATUS
} dump_info_t;
static const value_string info_type_str[] =
{
{ DUMP_INFO_KEY_DEL, "Delete CCM* key" },
{ DUMP_INFO_KEY_SET, "Set CCM* key" },
{ DUMP_INFO_ENCRYPTION_STATUS, "Set encryption status" },
{ 0, NULL }
};
/* Message types */
typedef enum
{
MSG_SE1 = 1,
MSG_SE2 = 2,
MSG_SE3 = 3,
MSG_SE4 = 4
} msg_type_t;
static const value_string msg_type_str[] =
{
{ MSG_SE1, "Message SE1" },
{ MSG_SE2, "Message SE2" },
{ MSG_SE3, "Message SE3" },
{ MSG_SE4, "Message SE4" },
{ 0, NULL }
};
#define BOOLSTR(b) ((b) ? "TRUE" : "FALSE")
/* "Cast" GSList node to zb_direct_key_record_t* */
#define keyrec(node) ((zb_direct_key_record_t*)((node)->data))
/**
* Like memcpy, but in reverse order.
*
* @param dst pointer to destination (copy to)
* @param src pointer to source (copy from)
* @param len number of bytes
*/
static inline void memcpy_reverse(guint8 *dst, const guint8 *src, gsize len)
{
len -= 1;
for (gsize i = 0; i <= len; ++i)
{
dst[i] = src[len - i];
}
}
/*****************************************************************************/
/************************************ UAT ************************************/
/*****************************************************************************/
/**
* Parses a hex string into bytes.
*
* @param str pointer to a hex string
* @param buf pointer to buffer, where to place result
* @param bytes_num number of bytes to retrive from the string
* @return success
*/
static gboolean zbd_parse_uat_hexline(const gchar *str,
guint8 *buf,
guint bytes_num)
{
gint i, j;
gchar temp;
gboolean string_mode = FALSE;
/* Clear the key. */
memset(buf, 0, bytes_num);
if (str == NULL)
{
return FALSE;
}
/**
* Attempt to parse the hex string. The hex string must
* be at least 16 pairs of hexidecimal digits with the
* following optional separators: ':', '-', " ", or 16
* alphanumeric characters after a double-quote.
*/
if ((temp = *str++) == '"')
{
string_mode = TRUE;
temp = *str++;
}
j = 0;
for (i = bytes_num - 1; i >= 0; i--)
{
if (string_mode)
{
if (g_ascii_isprint(temp))
{
buf[j] = temp;
temp = *str++;
}
else
{
return FALSE;
}
}
else
{
/* If this character is a separator, skip it. */
if (temp == ':' || temp == '-' || temp == ' ')
{
temp = *str++;
}
/* Process a nibble. */
if (g_ascii_isxdigit(temp))
{
buf[j] = g_ascii_xdigit_value(temp) << 4;
}
else
{
return FALSE;
}
/* Get the next nibble. */
temp = *str++;
/* Process another nibble. */
if (g_ascii_isxdigit(temp))
{
buf[j] |= g_ascii_xdigit_value(temp);
}
else
{
return FALSE;
}
/* Get the next nibble. */
temp = *str++;
}
/* Move buf pointer */
j++;
}
/* If we get this far, then the key was good. */
return TRUE;
}
/**
* UAT Copy callback.
*
* @param n pointer to new uat_kkey_record_t
* @param o pointer to old uat_key_record_t
* @param size unused
*/
static void *uat_key_record_copy_cb(void *n, const void *o, size_t size _U_)
{
uat_key_record_t *new_key = (uat_key_record_t *)n;
const uat_key_record_t *old_key = (const uat_key_record_t *)o;
new_key->zdd_ieee = g_strdup(old_key->zdd_ieee);
new_key->zvd_ieee = g_strdup(old_key->zvd_ieee);
new_key->key = g_strdup(old_key->key);
new_key->label = g_strdup(old_key->label);
return new_key;
}
/**
* UAT Update callback.
*
* @param r pointer to uat_kkey_record_t
* @param err pointer to error pointer
* @return success
*/
static bool uat_key_record_update_cb(void *r, char **err)
{
uat_key_record_t *rec = (uat_key_record_t *)r;
guint8 zdd_ieee[8];
guint8 zvd_ieee[8];
guint8 key[KEY_LEN];
*err = NULL;
if (rec->zdd_ieee == NULL)
{
*err = g_strdup("ZDD IEEE can't be blank");
return FALSE;
}
if (rec->zvd_ieee == NULL)
{
*err = g_strdup("ZVD IEEE can't be blank");
return FALSE;
}
if (rec->key == NULL)
{
*err = g_strdup("Key can't be blank");
return FALSE;
}
g_strstrip(rec->zdd_ieee);
g_strstrip(rec->zvd_ieee);
g_strstrip(rec->key);
if (rec->zdd_ieee[0] == 0)
{
*err = g_strdup("ZDD IEEE can't be blank");
return FALSE;
}
if (rec->zvd_ieee[0] == 0)
{
*err = g_strdup("ZVD IEEE can't be blank");
return FALSE;
}
if (rec->key[0] == 0)
{
*err = g_strdup("Key can't be blank");
return FALSE;
}
if (!zbd_parse_uat_hexline(rec->zdd_ieee, zdd_ieee, 8))
{
*err = g_strdup_printf("Expecting %d hexadecimal bytes or a %d character double-quoted string", 8, 8);
return FALSE;
}
if (!zbd_parse_uat_hexline(rec->zvd_ieee, zvd_ieee, 8))
{
*err = g_strdup_printf("Expecting %d hexadecimal bytes or a %d character double-quoted string", 8, 8);
return FALSE;
}
if (!zbd_parse_uat_hexline(rec->key, key, 16))
{
*err = g_strdup_printf("Expecting %d hexadecimal bytes or a %d character double-quoted string", 16, 16);
return FALSE;
}
return TRUE;
}
/**
* UAT Free callback.
*
* @param r pointer to a uat_key_record_t
*/
static void uat_key_record_free_cb(void *r)
{
uat_key_record_t *key = (uat_key_record_t *)r;
g_free(key->zdd_ieee);
g_free(key->zvd_ieee);
g_free(key->key);
g_free(key->label);
}
/**
* Frees zb_direct_key_record_t.
*
* @param ptr pointer to a zb_direct_key_record_t
*/
static void zbd_free_key_record(gpointer ptr)
{
zb_direct_key_record_t *k = (zb_direct_key_record_t *)ptr;
g_free(k->label);
g_free(k);
}
/**
* Deletes all existing keys in zbee_pc_keyrig and adds new ones
* from uat_key_records.
*/
static void uat_key_record_post_update(void)
{
zb_direct_key_record_t key_record;
guint8 zdd_ieee[8];
guint8 zvd_ieee[8];
guint8 key[KEY_LEN];
/* Empty UAT keys */
GSList *element = zbee_pc_keyring;
/* Find where UAT table keys begin */
while (element && keyrec(element)->frame_num > 0)
{
element = g_slist_next(element);
}
/* Delete all UAT keys */
while (element)
{
GSList *next = element->next;
zbee_pc_keyring = g_slist_remove_link(zbee_pc_keyring, element);
g_slist_free_full(element, zbd_free_key_record);
element = next;
}
/* Load the pre-configured slist from the UAT */
for (guint i = 0U; uat_key_records && i < num_uat_key_records; i++)
{
bool success = zbd_parse_uat_hexline(uat_key_records[i].zdd_ieee, zdd_ieee, sizeof(zdd_ieee))
| zbd_parse_uat_hexline(uat_key_records[i].zvd_ieee, zvd_ieee, sizeof(zvd_ieee))
| zbd_parse_uat_hexline(uat_key_records[i].key, key, sizeof(key));
if (success)
{
key_record.frame_num = 0; /* means it's a user PC key */
key_record.label = g_strdup(uat_key_records[i].label);
memcpy_reverse(key_record.zdd_ieee, zdd_ieee, 8);
memcpy_reverse(key_record.zvd_ieee, zvd_ieee, 8);
memcpy(key_record.key, key, KEY_LEN);
/* Add UAT keys to the end */
zbee_pc_keyring = g_slist_append(zbee_pc_keyring, g_memdup2(&key_record, sizeof(key_record)));
}
}
}
/*****************************************************************************/
/******************************** Decryption *********************************/
/*****************************************************************************/
#define MAX_CRYPT_TOGGLES 4096
typedef struct encryption_states_handler_s
{
/* How many toggles were performed */
guint16 counter;
/* Even entries point, where encryption enabled region starts, odd ones point, where they end */
guint32 states[MAX_CRYPT_TOGGLES];
} encryption_states_handler_t;
static encryption_states_handler_t enc_h[MAX_CONNECTIONS];
/**
* Enables encryption for packet_info if possible.
*
* @param pinfo pointer to packet
*/
static void zb_direct_encryption_enable(packet_info *pinfo)
{
encryption_states_handler_t *h = &enc_h[g_conn_id];
/* If currently enabled && was not disabled previously, exit */
if (h->counter % 2 == 1)
{
return;
}
/* If this packet was already handled, exit */
if (h->counter != 0 && pinfo->num <= h->states[h->counter - 1])
{
return;
}
if (h->counter >= MAX_CRYPT_TOGGLES)
{
return;
}
/* Enable */
h->states[h->counter++] = pinfo->num;
}
/**
* Disables encryption for packet_info if possible.
*
* @param pinfo pointer to packet
*/
static void zb_direct_encryption_disable(packet_info *pinfo)
{
encryption_states_handler_t *h = &enc_h[g_conn_id];
/* If currently enabled && was not disabled previously */
if (h->counter % 2 == 0)
{
return;
}
if (pinfo->num <= h->states[h->counter - 1])
{
return;
}
/* Enable */
h->states[h->counter++] = pinfo->num;
}
/**
* Checks if the packet must be decrypted.
*
* @param pinfo pointer to packet
* @return true, if decryption is needed, false, otherwise
*/
static gboolean zb_direct_decryption_needed(packet_info *pinfo)
{
encryption_states_handler_t *h = &enc_h[g_conn_id];
for (gint i = 0; i < h->counter; i += 2)
{
if (h->states[i] < pinfo->num)
{
/* If the packet is before the beginning of current crypted block, shutdown the search */
if (pinfo->num < h->states[i])
{
return FALSE;
}
/* If encrypted block was opened and not closed till now, or closed after current packet */
if (i == h->counter - 1 || pinfo->num < h->states[i + 1])
{
return TRUE;
}
}
}
return FALSE;
}
static gboolean decrypt_data(const guint8 *serv_uuid,
const guint8 *char_uuid,
gboolean to_zdd,
const guint8 *in,
guint8 *out,
guint16 *len,
guint8 zdd_ieee[8],
guint8 zvd_ieee[8],
guint8 key[KEY_LEN]);
/**
* Tries to decrypt packet payload as ZDD and ZVD.
*
* @param serv_uuid service UUID
* @param char_uuid characteristic UUID
* @param in pointer to encrypted payload
* @param out pointer to buffer for the result
* @param len pointer to the length of payload, outputs length of out
* @param zdd_ieee ZDD IEEE
* @param zvd_ieee ZVD IEEE
* @param key key for decryption
* @return success
*/
static gboolean try_decrypt(const guint8 *serv_uuid,
const guint8 *char_uuid,
const guint8 *in,
guint8 *out,
guint16 *len,
guint8 zdd_ieee[8],
guint8 zvd_ieee[8],
guint8 key[KEY_LEN])
{
/* As there is no reliable way known to determine,
* if the packet is from zdd or zvd, try both cases */
guint16 len_buf = *len;
gboolean success = decrypt_data(serv_uuid, char_uuid,
true,
in,
out, len,
zdd_ieee, zvd_ieee, key);
if (!success)
{
*len = len_buf;
success = decrypt_data(serv_uuid, char_uuid,
false,
in,
out, len,
zdd_ieee, zvd_ieee, key);
}
return success;
}
/**
* @brief Generates IEEE address from BLE MAC.
*
* @param mac_address BLE MAC in BE
* @param ieee generated IEEE in BE
*/
static void zb_direct_ieee_from_mac(const guint8 *mac_address, guint8 *ieee)
{
ieee[0] = mac_address[0] ^ 0x02;
ieee[1] = mac_address[1];
ieee[2] = mac_address[2];
ieee[3] = 0xFF;
ieee[4] = 0xFE;
ieee[5] = mac_address[3];
ieee[6] = mac_address[4];
ieee[7] = mac_address[5];
}
/**
* @brief Get BLE MAC address of the device which sent current packet from the packet data.
*
* @param pinfo packet info
* @param mac BLE MAC (bd_addr) corresponding to current packet sender
*/
static void zb_direct_bd_addr_from_packet_data(const packet_info *pinfo,
guint8 *mac)
{
(void)address_to_bytes(&pinfo->dl_src, mac, 6);
}
/**
* @brief Get IEEE address of the device which generated current packet from the packet data.
*
* @param pinfo packet info
* @param ieee calculated IEEE in BE
*/
static void zb_direct_ieee_from_packet_data(const packet_info *pinfo,
guint8 *ieee)
{
guint8 mac[6];
zb_direct_bd_addr_from_packet_data(pinfo, mac);
zb_direct_ieee_from_mac(mac, ieee);
}
/**
* Decrypts ZB Direct packets.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to the command subtree
* @param data raw packet private data
* @param offset offset into the tvb to begin dissection
* @param serv_uuid service UUID
* @param char_uuid characteristic UUID
* @return offset after command dissection
*/
static int zb_direct_decrypt(tvbuff_t **tvb,
packet_info *pinfo,
proto_tree *tree,
void *data _U_,
unsigned offset,
const guint8 *serv_uuid,
const guint8 *char_uuid)
{
if (zb_direct_decryption_needed(pinfo))
{
guint8 ieee[8];
gboolean success = FALSE;
guint16 size = tvb_reported_length_remaining(*tvb, offset);
guint8 *decrypted = (guint8 *)wmem_alloc(pinfo->pool, 512);
GList *pan_keyring;
GSList *i = zbee_pc_keyring;
guint16 init_size = size;
zb_direct_ieee_from_packet_data(pinfo, ieee);
if (ignore_late_keys)
{
/* Skip all keys, which were reported after current package */
while (i && (keyrec(i)->frame_num > pinfo->num))
{
i = g_slist_next(i);
}
}
/* Try potential keys from preconfigured table and dump info packets */
while (i && !success)
{
success = try_decrypt(serv_uuid,
char_uuid,
tvb_get_ptr(*tvb, offset, size),
decrypted,
&size,
keyrec(i)->zdd_ieee,
keyrec(i)->zvd_ieee,
keyrec(i)->key);
if (!success)
{
i = g_slist_next(i);
size = init_size;
}
}
/* Retrieve all pan-specific nwk keyrings from the hash table */
if (!success && zbee_table_nwk_keyring)
{
pan_keyring = (GList*)g_hash_table_get_values(zbee_table_nwk_keyring);
while (!success && pan_keyring)
{
i = *((GSList**)pan_keyring->data);
/* Iterate over keys in the keyring */
while (!success && i)
{
if (!ignore_late_keys || ((key_record_t*)i->data)->frame_num > pinfo->num)
{
success = decrypt_data(serv_uuid, char_uuid, FALSE,
tvb_get_ptr(*tvb, offset, size),
decrypted, &size,
ieee, NULL, ((key_record_t*)i->data)->key);
i = g_slist_next(i);
if (!success)
{
size = init_size;
}
}
}
pan_keyring = g_list_next(i);
}
}
if (success)
{
/* On decryption success: replace the tvb, make offset point to its beginning */
*tvb = tvb_new_child_real_data(*tvb, decrypted, size, size);
add_new_data_source(pinfo, *tvb, "CCM* decrypted payload");
offset = 0;
}
else
{
/* On decryption error: make offset point to the end of original tvb */
offset = tvb_reported_length(*tvb);
expert_add_info(pinfo, tree, &ei_zb_direct_crypt_error);
}
}
return offset;
}
/* 6.4.3. CCM Nonce */
typedef struct
#if defined(_MSC_VER)
# pragma pack(push, 1)
#else
__attribute__((__packed__))
#endif
zb_secur_ccm_nonce_s
{
guint8 source_address[8];
guint32 frame_counter;
guint8 secur_control;
} zb_secur_ccm_nonce_t;
#ifdef _MSC_VER
# pragma pack(pop)
#endif
/**
* Creates an auth string.
*
* @param serv_uuid service UUID
* @param char_uuid characteristic UUID
* @param auth_string output buffer
*/
static void create_auth_string(const guint8 serv_uuid[16],
const guint8 char_uuid[16],
guint8 auth_string[ZIGBEE_DIRECT_AUTH_STR_SIZE])
{
/* 6.4.5. Unique address */
memcpy_reverse(auth_string, serv_uuid, 16);
auth_string[16] = 0;
memcpy_reverse(&auth_string[17], char_uuid, 16);
auth_string[33] = 0;
}
/**
* Decrypts packet payload as ZDD and ZVD.
*
* @param serv_uuid service UUID
* @param char_uuid characteristic UUID
* @param to_zdd true if packet ws sent to zdd, false if to zvd (needed for nonce formation)
* @param in pointer to encrypted payload
* @param out pointer to buffer for the result
* @param len pointer to the length of payload, outputs length of out
* @param zdd_ieee ZDD IEEE
* @param zvd_ieee ZVD IEEE
* @param key key for decryption
* @return success
*/
static gboolean decrypt_data(const guint8 *serv_uuid,
const guint8 *char_uuid,
gboolean to_zdd,
const guint8 *in,
guint8 *out,
guint16 *len,
guint8 zdd_ieee[8],
guint8 zvd_ieee[8],
guint8 key[KEY_LEN])
{
gboolean success = true;
guint8 auth_str[ZIGBEE_DIRECT_AUTH_STR_SIZE];
guint8 decrypted_data[ZIGBEE_DIRECT_MAX_ATT_SIZE + 16];
guint16 decrypted_data_len = sizeof(decrypted_data);
/* Remove 32-bit counter from the beginning */
const guint8 *encrypted_data = in + sizeof(guint32);
guint16 encrypted_data_len = *len - sizeof(guint32);
/* Form the nonce */
zb_secur_ccm_nonce_t nonce = (zb_secur_ccm_nonce_t)
{
.secur_control = ZIGBEE_DIRECT_SECUR_CONTROL
};
/* Fetch counter from the packet (don't check) */
memcpy(&nonce.frame_counter, in, sizeof(guint32));
memcpy(&nonce.source_address, to_zdd ? zvd_ieee : zdd_ieee, 8);
if (*len < 8) return false;
create_auth_string(serv_uuid, char_uuid, auth_str);
success = zbee_sec_ccm_decrypt(key,
(guint8*)&nonce,
auth_str,
encrypted_data,
decrypted_data,
sizeof(auth_str),
encrypted_data_len - ZB_CCM_M,
ZB_CCM_M);
if (success)
{
decrypted_data_len = encrypted_data_len - ZB_CCM_M;
memcpy(out, decrypted_data, decrypted_data_len);
*len = decrypted_data_len;
}
else
{
*len = 0;
}
return success;
}
/*****************************************************************************/
/***************************** Dissectors Common *****************************/
/*****************************************************************************/
/**
* Common helper dissector.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to the command subtree
* @param data pointer to packet data
* @param offset offset into the tvb to begin dissection
* @param serv_uuid service UUID
* @param char_uuid characteristic UUID
* @return offset after command dissection
*/
static int dissect_zb_direct_common(tvbuff_t **tvb,
packet_info *pinfo,
proto_tree **tree,
void *data,
unsigned offset,
const guint8 *serv_uuid,
const guint8 *char_uuid)
{
proto_item *ti;
/** TODO: find a way to detect direct (master/slave) and particular connection from data, passed from Bluetooth dissector */
/* Set basic columns (proto, src, dst) */
col_set_str(pinfo->cinfo, COL_PROTOCOL, "ZBD");
/**
* Actually should think of better way to know
*
* (probably try fetch BLE data: indication would reveal ZDD for example)
*/
/* Add ZB Direct subtree */
ti = proto_tree_add_item(*tree, proto_zb_direct, *tvb, 0, -1, ENC_LITTLE_ENDIAN);
*tree = proto_item_add_subtree(ti, ett_zb_direct);
g_conn_id = 0;
proto_item_append_text(ti, " (Connection ID: %d)", (int)g_conn_id);
/* NULL uuid is for chars, which do not have to be encrypted at all (dump info) */
if (char_uuid != NULL && serv_uuid != NULL && memcmp(serv_uuid, serv_secur_uuid, sizeof(serv_secur_uuid)))
{
offset = zb_direct_decrypt(tvb, pinfo, *tree, data, offset, serv_uuid, char_uuid);
}
return offset;
}
/*****************************************************************************/
/**************************** Dump Info Dissector ****************************/
/*****************************************************************************/
typedef enum zb_dump_info_e
{
/* Clear current used key */
ZB_DUMP_INFO_CCM_KEY_DELETE,
/* Replace current key with a new one */
ZB_DUMP_INFO_CCM_KEY_SET,
/* Specify, if encryption is needed or not */
ZB_DUMP_INFO_ENCRYPTION_STATUS
} zb_dump_info_t;
/**
* Dump Info dissector.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to the command subtree
* @param data raw packet private data
* @return offset after command dissection
*/
static int dissect_zb_direct_dump_info(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
guint32 type;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, NULL, NULL);
col_set_str(pinfo->cinfo, COL_INFO, "Dump info");
ti = proto_tree_add_item(tree, hf_zb_direct_char_info, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
proto_tree_add_item_ret_uint(tree, hf_zb_direct_info_type, tvb, offset, 1, ENC_LITTLE_ENDIAN, &type);
offset += 1;
switch(type)
{
case ZB_DUMP_INFO_CCM_KEY_DELETE:
/* Obsolete option */
break;
case ZB_DUMP_INFO_CCM_KEY_SET:
{
zb_direct_key_record_t key_record;
col_append_str(pinfo->cinfo, COL_INFO, ": update key");
/**
* From the Wireshark Developer's Guide:
*
* Wireshark performs a first pass of dissecting all packets as they are loaded from the file.
* All packets are dissected sequentially...
*
* So, we can assume that keys are coming in order they will be used in file
*/
proto_tree_add_item(tree, hf_zb_direct_info_key, tvb, offset, KEY_LEN, ENC_NA);
tvb_memcpy(tvb, key_record.key, offset, KEY_LEN);
offset += KEY_LEN;
proto_tree_add_item(tree, hf_zb_direct_info_zdd_ieee, tvb, offset, 8, ENC_LITTLE_ENDIAN);
tvb_memcpy(tvb, key_record.zdd_ieee, offset, sizeof(key_record.zdd_ieee));
offset += 8;
proto_tree_add_item(tree, hf_zb_direct_info_zvd_ieee, tvb, offset, 8, ENC_LITTLE_ENDIAN);
tvb_memcpy(tvb, key_record.zvd_ieee, offset, sizeof(key_record.zdd_ieee));
offset += 8;
key_record.frame_num = pinfo->num;
key_record.label = g_strdup_printf("Key reported over air in packet #%d", pinfo->num);
/* Check if this key was already added */
if (zbee_pc_keyring == NULL || keyrec(zbee_pc_keyring)->frame_num < pinfo->num)
{
/* store the keys in order: latest <- ... <- first <- (UAT: top <- ... <- bottom) */
zbee_pc_keyring = g_slist_prepend(zbee_pc_keyring,
g_memdup2(&key_record, sizeof(zb_direct_key_record_t)));
}
break;
}
case ZB_DUMP_INFO_ENCRYPTION_STATUS:
{
gboolean is_enabled = tvb_get_guint8(tvb, offset);
if (is_enabled)
{
zb_direct_encryption_enable(pinfo);
}
else
{
zb_direct_encryption_disable(pinfo);
}
proto_tree_add_item(tree,
hf_zb_direct_info_encryption,
tvb,
offset,
1,
ENC_LITTLE_ENDIAN);
offset += 1;
if (is_enabled)
{
col_append_str(pinfo->cinfo, COL_INFO, ": encryption ON");
}
else
{
col_append_str(pinfo->cinfo, COL_INFO, ": encryption OFF");
}
break;
}
}
return offset;
}
/*****************************************************************************/
/********* Zigbee Direct Security Service Characteristics Dissectors *********/
/*****************************************************************************/
/**
* Dissector for the security packets.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to the command subtree
* @param data raw packet private data
* @param offset offset into the tvb to begin dissection.
* @param msg_id ZB Direct local Message ID
* @return offset after command dissection
*/
static int dissect_zb_direct_secur_common(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data,
unsigned offset,
guint msg_id)
{
unsigned cap_len = tvb_captured_length(tvb);
proto_item* ti;
const guint8 *decrypt_char_uuid;
switch (msg_id)
{
case ZB_DIRECT_MSG_ID_SECUR_C25519_AESMMO:
decrypt_char_uuid = char_c25519_aesmmo_uuid;
break;
case ZB_DIRECT_MSG_ID_SECUR_C25519_SHA256:
decrypt_char_uuid = char_c25519_sha256_uuid;
break;
case ZB_DIRECT_MSG_ID_SECUR_P256:
decrypt_char_uuid = char_p256_uuid;
break;
default:
DISSECTOR_ASSERT(FALSE);
break;
}
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset,
serv_secur_uuid, decrypt_char_uuid);
switch (msg_id)
{
case ZB_DIRECT_MSG_ID_SECUR_C25519_AESMMO:
ti = proto_tree_add_item(tree, hf_zb_direct_char_c25519_aesmmo, tvb, offset, 0, ENC_NA);
break;
case ZB_DIRECT_MSG_ID_SECUR_C25519_SHA256:
ti = proto_tree_add_item(tree, hf_zb_direct_char_c25519_sha256, tvb, offset, 0, ENC_NA);
break;
case ZB_DIRECT_MSG_ID_SECUR_P256:
ti = proto_tree_add_item(tree, hf_zb_direct_char_p256, tvb, offset, 0, ENC_NA);
break;
default:
DISSECTOR_ASSERT(false);
break;
}
proto_item_set_generated(ti);
/* Discover type of the message */
guint8 msg_type = tvb_get_guint8(tvb, offset);
proto_tree_add_item(tree, hf_zb_direct_msg_type, tvb, offset, 1, ENC_LITTLE_ENDIAN);
offset += 1;
if (msg_type == MSG_SE1)
{
zb_direct_encryption_disable(pinfo);
}
else if (msg_type == MSG_SE4)
{
zb_direct_encryption_enable(pinfo);
}
offset = dissect_zbee_tlvs(tvb, pinfo, tree, offset, data,
ZBEE_TLV_SRC_TYPE_ZB_DIRECT, msg_id);
if (msg_type >= MSG_SE1 && msg_type <= MSG_SE4)
{
gsize msg_type_idx = msg_type - MSG_SE1;
col_set_str(pinfo->cinfo, COL_INFO, msg_type_str[msg_type_idx].strptr);
}
else
{
proto_tree_add_item(tree, hf_zb_direct_unrecognized_msg, tvb, 0, cap_len, ENC_NA);
offset = cap_len;
col_set_str(pinfo->cinfo, COL_INFO, "Unrecognized SE message");
}
return offset;
}
/**
* Dissector for security packets authenticated with Curve25519/AESMMO.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to the command subtree
* @param data raw packet private data
* @return offset after command dissection
*/
static int dissect_zb_direct_secur_c25519_aesmmo(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
return dissect_zb_direct_secur_common(tvb, pinfo, tree, data, 0U, ZB_DIRECT_MSG_ID_SECUR_C25519_AESMMO);
}
/**
* Dissector for security packets authenticated with Curve25519/SHA256.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to the command subtree
* @param data raw packet private data
* @return offset after command dissection
*/
static int dissect_zb_direct_secur_c25519_sha256(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
return dissect_zb_direct_secur_common(tvb, pinfo, tree, data, 0U, ZB_DIRECT_MSG_ID_SECUR_C25519_SHA256);
}
/**
* Dissector for security packets authenticated with curve P-256.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to the command subtree
* @param data raw packet private data
* @return offset after command dissection
*/
static int dissect_zb_direct_secur_p256(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
return dissect_zb_direct_secur_common(tvb, pinfo, tree, data, 0U, ZB_DIRECT_MSG_ID_SECUR_P256);
}
/*****************************************************************************/
/****************** BLE Service Characteristics Dissectors *******************/
/*****************************************************************************/
/**
* Dissector for Form Network.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_formation(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, serv_comm_uuid, char_form_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_form, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
col_set_str(pinfo->cinfo, COL_INFO, "FORM Request");
if (tree)
{
offset = dissect_zbee_tlvs(tvb, pinfo, tree, offset, data,
ZBEE_TLV_SRC_TYPE_ZB_DIRECT,
ZB_DIRECT_MSG_ID_FORMATION);
}
return offset;
}
/**
* Dissector for Commissioning Status.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_status(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, serv_comm_uuid, char_status_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_status, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
col_set_str(pinfo->cinfo, COL_INFO, "COMM STATUS Notification");
offset = dissect_zbee_tlvs(tvb, pinfo, tree, offset, data,
ZBEE_TLV_SRC_TYPE_ZB_DIRECT,
ZB_DIRECT_MSG_ID_STATUS);
return offset;
}
/**
* Dissector for Join Network.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_join(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, serv_comm_uuid, char_join_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_join, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
col_set_str(pinfo->cinfo, COL_INFO, "JOIN Request");
if (tree)
{
offset = dissect_zbee_tlvs(tvb, pinfo, tree, offset, data,
ZBEE_TLV_SRC_TYPE_ZB_DIRECT,
ZB_DIRECT_MSG_ID_JOIN);
}
return offset;
}
/**
* Dissector for Permit Joining.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_permit_join(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, serv_comm_uuid, char_permit_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_permit_join, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
col_set_str(pinfo->cinfo, COL_INFO, "PERMIT JOIN Request");
if (offset < tvb_reported_length(tvb))
{
guint32 parent_time;
proto_tree_add_item_ret_uint(tree, hf_zb_direct_comm_permit_time, tvb, offset, 1, ENC_LITTLE_ENDIAN, &parent_time);
offset += 1;
if (parent_time > 0)
{
col_append_fstr(pinfo->cinfo, COL_INFO, ": open for %us", parent_time);
}
else
{
col_append_fstr(pinfo->cinfo, COL_INFO, ": close");
}
}
return offset;
}
/**
* Dissector for Leave Networ.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_leave(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, serv_comm_uuid, char_leave_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_leave, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
col_set_str(pinfo->cinfo, COL_INFO, "LEAVE Request");
if (offset < tvb_reported_length(tvb))
{
gboolean rm_children;
gboolean rejoin;
proto_tree_add_item_ret_boolean(tree, hf_zb_direct_comm_rm_children, tvb, offset, 1, ENC_LITTLE_ENDIAN, &rm_children);
offset += 1;
proto_tree_add_item_ret_boolean(tree, hf_zb_direct_comm_rejoin, tvb, offset, 1, ENC_LITTLE_ENDIAN, &rejoin);
offset += 1;
col_append_fstr(pinfo->cinfo, COL_INFO, " (remove children: %s, rejoin: %s)", BOOLSTR(rm_children), BOOLSTR(rejoin));
}
return offset;
}
/**
* Dissector for Manage Joiners.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_manage_joiners(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, serv_comm_uuid, char_manage_joiners_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_manage_joiners, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
col_set_str(pinfo->cinfo, COL_INFO, "MANAGE JOINERS Request");
if (tree)
{
offset = dissect_zbee_tlvs(tvb, pinfo, tree, offset, data,
ZBEE_TLV_SRC_TYPE_ZB_DIRECT,
ZB_DIRECT_MSG_ID_MANAGE_JOINERS);
}
return offset;
}
/**
* Dissector for Indentify.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_identify(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, serv_comm_uuid, char_identify_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_identify, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
col_set_str(pinfo->cinfo, COL_INFO, "IDENTIFY Request");
if (offset < tvb_reported_length(tvb))
{
guint32 parent_time;
proto_tree_add_item_ret_uint(tree, hf_zb_direct_comm_identify_time, tvb, offset, 2, ENC_LITTLE_ENDIAN, &parent_time);
offset += 2;
if (parent_time > 0)
{
col_append_fstr(pinfo->cinfo, COL_INFO, ": start for %us", parent_time);
}
else
{
col_append_fstr(pinfo->cinfo, COL_INFO, ": stop");
}
}
return offset;
}
/**
* Dissector for Finding & Binding.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_finding_binding(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb, pinfo, &tree, data, offset, serv_comm_uuid, char_finding_binding_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_finding_binding, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
col_set_str(pinfo->cinfo, COL_INFO, "FINDING & BINDING Request");
if (offset < tvb_reported_length(tvb))
{
guint32 endpoint;
gboolean initiator;
proto_tree_add_item_ret_uint(tree, hf_zb_direct_comm_fb_endpoint, tvb, offset, 1, ENC_LITTLE_ENDIAN, &endpoint);
offset += 1;
proto_tree_add_item_ret_boolean(tree, hf_zb_direct_comm_fb_initiator, tvb, offset, 1, ENC_LITTLE_ENDIAN, &initiator);
offset += 1;
col_append_fstr(pinfo->cinfo, COL_INFO, " (endpoint: %u, initiator: %s)", endpoint, BOOLSTR(initiator));
}
return offset;
}
/**
* Helper dissector for Tunneling.
*
* @param tvb pointer to buffer containing raw packet
* @param pinfo pointer to packet information fields
* @param tree pointer to subtree
* @param data raw packet private data
* @return offset after dissection
*/
static int dissect_zb_direct_tunneling(tvbuff_t *tvb,
packet_info *pinfo,
proto_tree *tree,
void *data)
{
proto_item* ti;
unsigned offset = 0;
offset = dissect_zb_direct_common(&tvb,
pinfo,
&tree,
data,
offset,
serv_tunnel_uuid,
char_tunnel_uuid);
ti = proto_tree_add_item(tree, hf_zb_direct_char_tunneling, tvb, offset, 0, ENC_NA);
proto_item_set_generated(ti);
offset = dissect_zbee_tlvs(tvb, pinfo, tree, offset, data,
ZBEE_TLV_SRC_TYPE_ZB_DIRECT,
ZB_DIRECT_MSG_ID_TUNNELING);
return offset;
}
/*****************************************************************************/
/******************************* Registration ********************************/
/*****************************************************************************/
/**
* ZigBee Direct initialization routine.
*/
static void zb_direct_init(void)
{
for (gint i = 0; i < MAX_CONNECTIONS; i++)
{
enc_h[i].counter = 0;
for (gint j = 0; j < MAX_CRYPT_TOGGLES && enc_h[i].states[j] != 0; j++)
{
enc_h[i].states[j] = 0;
}
}
}
/**
* ZigBee Direct clean routine.
*/
static void zb_direct_cleanup(void)
{
/* Empty temporary keys */
while (zbee_pc_keyring && keyrec(zbee_pc_keyring)->frame_num > 0)
{
GSList *element = zbee_pc_keyring;
zbee_pc_keyring = g_slist_delete_link(zbee_pc_keyring, element);
}
}
/**
* ZigBee Direct registration routine.
*/
void proto_register_zb_direct(void)
{
static hf_register_info hf[] =
{
{ &hf_zb_direct_unrecognized_msg,
{ "Unrecognized message", "zbd.unrecognized",
FT_BYTES, SEP_SPACE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_info_type,
{ "Type", "zbd.dump_info.type",
FT_UINT8, BASE_DEC,
VALS(info_type_str), 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_info_key,
{ "Key", "zbd.key",
FT_BYTES, SEP_SPACE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_info_zdd_ieee,
{ "ZDD IEEE Address", "zbd.dump_info.zdd_addr",
FT_UINT64, BASE_HEX,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_info_zvd_ieee,
{ "ZVD IEEE Address", "zbd.dump_info.zvd_addr",
FT_UINT64, BASE_HEX,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_info_encryption,
{ "Encryption enabled", "zbd.encryption_status",
FT_BOOLEAN, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
/* secur */
{ &hf_zb_direct_msg_type,
{ "Message type", "zbd.secur.msg_type",
FT_UINT8, BASE_HEX,
VALS(msg_type_str), 0x0,
NULL, HFILL }
},
/* Markers */
{ &hf_zb_direct_char_info,
{ "Dump info", "zbd.dump_info",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_c25519_aesmmo,
{ "Characteristic: Security / C25519-AES-MMO", "zbd.secur.c25519_aesmmo",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_c25519_sha256,
{ "Characteristic: Security / C25519-SHA-256", "zbd.secur.c25519_sha256",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_p256,
{ "Characteristic: Security / P-256", "zbd.secur.p256",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_form,
{ "Characteristic: Commissioning / Formation", "zbd.comm.form",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_status,
{ "Characteristic: Commissioning / Status", "zbd.comm.status",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_join,
{ "Characteristic: Commissioning / Join", "zbd.comm.join",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_permit_join,
{ "Characteristic: Commissioning / Permit Join", "zbd.comm.permit_join",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_leave,
{ "Characteristic: Commissioning / Leave", "zbd.comm.leave",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_manage_joiners,
{ "Characteristic: Commissioning / Manage Joiners", "zbd.comm.manage_joiners",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_identify,
{ "Characteristic: Commissioning / Identify", "zbd.comm.identify",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_finding_binding,
{ "Characteristic: Commissioning / Finding & Binding", "zbd.comm.finding_binding",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_char_tunneling,
{ "Characteristic: Tunneling", "zbd.comm.tunneling",
FT_NONE, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
/* Subtrees elements */
{ &hf_zb_direct_comm_permit_time,
{ "Permit time interval (sec)", "zbd.comm.permit_time",
FT_UINT8, BASE_DEC,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_comm_rejoin,
{ "Rejoin", "zbd.comm.rejoin",
FT_BOOLEAN, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_comm_rm_children,
{ "Remove children", "zbd.comm.rm_children",
FT_BOOLEAN, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_comm_identify_time,
{ "Identify time", "zbd.comm.identify_time",
FT_UINT16, BASE_DEC,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_comm_fb_endpoint,
{ "Endpoint", "zbd.comm.fb_endpoint",
FT_UINT8, BASE_DEC,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_zb_direct_comm_fb_initiator,
{ "Initiator", "zbd.comm.fb_initiator",
FT_BOOLEAN, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
};
static ei_register_info ei[] = {
{ &ei_zb_direct_crypt_error,
{ "zbd.error.decryption", PI_UNDECODED, PI_WARN,
"Decryption fail",
EXPFILL }
}
};
/* Setup protocol subtree array */
static gint *ett[] =
{
&ett_zb_direct,
};
expert_module_t *expert_zb_direct;
proto_zb_direct = proto_register_protocol("ZigBee Direct", /* name */
"ZBD", /* short_name */
"zbd"); /* filter_name */
proto_register_field_array(proto_zb_direct, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
expert_zb_direct = expert_register_protocol(proto_zb_direct);
expert_register_field_array(expert_zb_direct, ei, array_length(ei));
register_init_routine(zb_direct_init);
register_cleanup_routine(zb_direct_cleanup);
module_t *zbd_prefs = prefs_register_protocol(proto_zb_direct, NULL);
static uat_field_t key_uat_fields[] =
{
UAT_FLD_CSTRING(uat_key_records, zdd_ieee, "ZDD IEEE",
"A 8-byte address of ZDD in hexadecimal with optional "
"dash-, colon-, or space-separator characters, "
"in Big Endian"),
UAT_FLD_CSTRING(uat_key_records, zvd_ieee, "ZVD IEEE",
"A 8-byte address of ZVD in hexadecimal with optional "
"dash-, colon-, or space-separator characters, "
"in Big Endian"),
UAT_FLD_CSTRING(uat_key_records, key, "Key",
"A 16-byte session key in hexadecimal with optional "
"dash-, colon-, or space-separator characters, "
"in Big Endian"),
UAT_FLD_CSTRING(uat_key_records, label, "Label", "User comment"),
UAT_END_FIELDS
};
/* Affects dissection of packets, but not set of named fields */
guint uat_flags = UAT_AFFECTS_DISSECTION;
zbd_secur_key_table_uat = uat_new("Pre-configured Keys",
sizeof(uat_key_record_t),
"zigbee_direct_pc_keys",
TRUE,
&uat_key_records,
&num_uat_key_records,
uat_flags,
NULL, /** TODO: ptr to help manual? */
uat_key_record_copy_cb,
uat_key_record_update_cb,
uat_key_record_free_cb,
uat_key_record_post_update,
NULL,
key_uat_fields);
prefs_register_uat_preference(zbd_prefs,
"key_table",
"Pre-configured Keys",
"Pre-configured session keys",
zbd_secur_key_table_uat);
prefs_register_bool_preference(zbd_prefs,
"ignore_late_keys",
"Ignore Late Keys",
"Whether or not dissector shall ignore keys, "
"which were provided after current packet "
"during decryption",
&ignore_late_keys);
}
/**
* ZigBee Direct handoff routine.
*/
void proto_reg_handoff_zb_direct(void)
{
typedef struct
{
const char *uuid;
dissector_t dissector;
} zb_direct_service_t;
static zb_direct_service_t services[] =
{
{ "29144af4-00ff-4481-bfe9-6d0299b429e3", dissect_zb_direct_dump_info },
/* 6.5.1. Zigbee Direct Security Service characteristic */
{ "29144af4-0001-4481-bfe9-6d0299b429e3", dissect_zb_direct_secur_c25519_aesmmo },
{ "29144af4-0002-4481-bfe9-6d0299b429e3", dissect_zb_direct_secur_c25519_sha256 },
{ "29144af4-0003-4481-bfe9-6d0299b429e3", dissect_zb_direct_secur_p256 },
/* 7.7.2.3. Zigbee Direct Commissioning Service characteristics */
{ "7072377d-0001-421c-b163-491c27333a61", dissect_zb_direct_formation },
{ "7072377d-0002-421c-b163-491c27333a61", dissect_zb_direct_join },
{ "7072377d-0003-421c-b163-491c27333a61", dissect_zb_direct_permit_join },
{ "7072377d-0004-421c-b163-491c27333a61", dissect_zb_direct_leave },
{ "7072377d-0005-421c-b163-491c27333a61", dissect_zb_direct_status },
{ "7072377d-0006-421c-b163-491c27333a61", dissect_zb_direct_manage_joiners },
{ "7072377d-0007-421c-b163-491c27333a61", dissect_zb_direct_identify },
{ "7072377d-0008-421c-b163-491c27333a61", dissect_zb_direct_finding_binding },
/* 7.7.3.3. Zigbee Direct Tunnel Service characteristics */
{ "8bd178fd-0001-45f4-8120-b2378bd5313f", dissect_zb_direct_tunneling },
{ NULL, NULL },
};
for (gsize i = 0; services[i].uuid; i++)
{
dissector_handle_t handle = create_dissector_handle(services[i].dissector, proto_zb_direct);
dissector_add_string("bluetooth.uuid", services[i].uuid, handle);
}
zbee_nwk_handle = find_dissector("zbee_nwk");
}
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 4
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=4 tabstop=8 expandtab:
* :indentSize=4:tabSize=8:noTabs=true:
*/