/* 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 * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include #include #include #include #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 = -1; /* Leaf-elements */ static int hf_zb_direct_info_type = -1; static int hf_zb_direct_info_key = -1; static int hf_zb_direct_info_zdd_ieee = -1; static int hf_zb_direct_info_zvd_ieee = -1; static int hf_zb_direct_info_encryption = -1; static int hf_zb_direct_msg_type = -1; /* Commissioning */ static int hf_zb_direct_comm_permit_time = -1; static int hf_zb_direct_comm_rejoin = -1; static int hf_zb_direct_comm_rm_children = -1; static int hf_zb_direct_comm_identify_time = -1; static int hf_zb_direct_comm_fb_endpoint = -1; static int hf_zb_direct_comm_fb_initiator = -1; /* Markers (also leafs) */ static int hf_zb_direct_unrecognized_msg = -1; static int hf_zb_direct_char_info = -1; static int hf_zb_direct_char_c25519_aesmmo = -1; static int hf_zb_direct_char_c25519_sha256 = -1; static int hf_zb_direct_char_p256 = -1; static int hf_zb_direct_char_form = -1; static int hf_zb_direct_char_status = -1; static int hf_zb_direct_char_join = -1; static int hf_zb_direct_char_permit_join = -1; static int hf_zb_direct_char_leave = -1; static int hf_zb_direct_char_manage_joiners = -1; static int hf_zb_direct_char_identify = -1; static int hf_zb_direct_char_finding_binding = -1; static int hf_zb_direct_char_tunneling = -1; /* Expert items */ static expert_field ei_zb_direct_crypt_error = EI_INIT; /* Trees entitties */ static gint ett_zb_direct = -1; 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 gboolean 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-speciefic 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 Commisioning 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", "Wether 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 characterisitc */ { "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: */