COSE dissector from dtn-wireshark project

This commit is contained in:
Brian Sipos 2021-05-10 23:50:41 -04:00 committed by Wireshark GitLab Utility
parent 4010502a82
commit abd0f1183f
28 changed files with 3060 additions and 0 deletions

View File

@ -3572,6 +3572,7 @@ add_custom_target(test-programs
reassemble_test
tvbtest
wmem_test
wscbor_test
test_wsutil
COMMENT "Building unit test programs and wrapper"
)

View File

@ -1221,6 +1221,14 @@ libwireshark.so.0 libwireshark0 #MINVER#
proto_tree_add_bytes_format_value@Base 1.9.1
proto_tree_add_bytes_item@Base 1.12.0~rc1
proto_tree_add_bytes_with_length@Base 1.99.3
proto_tree_add_cbor_bitmask@Base 3.5.1
proto_tree_add_cbor_boolean@Base 3.5.1
proto_tree_add_cbor_bstr@Base 3.5.1
proto_tree_add_cbor_container@Base 3.5.1
proto_tree_add_cbor_ctrl@Base 3.5.1
proto_tree_add_cbor_int64@Base 3.5.1
proto_tree_add_cbor_tstr@Base 3.5.1
proto_tree_add_cbor_uint64@Base 3.5.1
proto_tree_add_checksum@Base 2.1.1
proto_tree_add_debug_text@Base 1.9.1
proto_tree_add_double@Base 1.9.1
@ -2026,6 +2034,24 @@ libwireshark.so.0 libwireshark0 #MINVER#
ws_find_media_type_parameter@Base 2.2.0
ws_strdup_escape_char@Base 1.9.1
ws_strdup_unescape_char@Base 1.9.1
wscbor_chunk_free@Base 3.5.1
wscbor_chunk_mark_errors@Base 3.5.1
wscbor_chunk_read@Base 3.5.1
wscbor_error_new@Base 3.5.1
wscbor_has_errors@Base 3.5.1
wscbor_init@Base 3.5.1
wscbor_is_indefinite_break@Base 3.5.1
wscbor_require_array@Base 3.5.1
wscbor_require_array_size@Base 3.5.1
wscbor_require_boolean@Base 3.5.1
wscbor_require_bstr@Base 3.5.1
wscbor_require_int64@Base 3.5.1
wscbor_require_major_type@Base 3.5.1
wscbor_require_map@Base 3.5.1
wscbor_require_tstr@Base 3.5.1
wscbor_require_uint64@Base 3.5.1
wscbor_skip_if_errors@Base 3.5.1
wscbor_skip_next_item@Base 3.5.1
wslua_count_plugins@Base 1.12.0~rc1
wslua_plugin_type_name@Base 2.5.0
wslua_plugins_dump_all@Base 1.12.0~rc1

View File

@ -118,6 +118,7 @@ Vector Informatik Binary Log File (BLF)
[commaize]
--
Bluetooth Link Manager Protocol (BT LMP)
CBOR Object Signing and Encryption (COSE)
E2 Application Protocol (E2AP)
Event Tracing for Windows (ETW)
High-Performance Connectivity Tracer (HiPerConTracer)

View File

@ -161,6 +161,7 @@ set(LIBWIRESHARK_PUBLIC_HEADERS
unit_strings.h
value_string.h
wmem_scopes.h
wscbor.h
x264_prt_id.h
xdlc.h
)
@ -257,6 +258,7 @@ set(LIBWIRESHARK_NONGENERATED_FILES
tvbuff_lznt1.c
uat.c
value_string.c
wscbor.c
unit_strings.c
wmem_scopes.c
xdlc.c
@ -417,6 +419,13 @@ set_target_properties(tvbtest PROPERTIES
COMPILE_DEFINITIONS "WS_BUILD_DLL"
)
add_executable(wscbor_test EXCLUDE_FROM_ALL wscbor_test.c)
target_link_libraries(wscbor_test epan)
set_target_properties(wscbor_test PROPERTIES
FOLDER "Tests"
EXCLUDE_FROM_DEFAULT_BUILD True
)
CHECKAPI(
NAME
epan

View File

@ -294,6 +294,7 @@ set(DISSECTOR_PUBLIC_HEADERS
packet-cmp.h
packet-cms.h
packet-coap.h
packet-cose.h
packet-credssp.h
packet-crmf.h
packet-csn1.h
@ -852,6 +853,7 @@ set(DISSECTOR_SRC
${CMAKE_CURRENT_SOURCE_DIR}/packet-cops.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-corosync-totemnet.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-corosync-totemsrp.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-cose.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-cosine.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-couchbase.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-cp2179.c

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
/* packet-cose.h
* Definitions for CBOR Object Signing and Encryption (COSE) dissection
* References:
* RFC 8152: https://tools.ietf.org/html/rfc8152
*
* Copyright 2019-2021, Brian Sipos <brian.sipos@gmail.com>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __PACKET_COSE_H__
#define __PACKET_COSE_H__
#include <glib.h>
/**
* COSE message dissectors are registered multiple ways:
* 1. The unit-keyed dissector table "cose.msgtag" with keys being
* IANA-registered CBOR tag values (e.g., 18 is COSE_Sign1).
* 2. The string-keyed dissector table "media_type" with keys being
* IANA-registered media type IDs
* (e.g., application/cose; cose-type="cose-sign1" is COSE_Sign1).
* 3. The registered dissectors for names "cose" and message names in
* all-lowercase form (e.g., "cose_sign1").
* There is currently no CoAP dissector table to register with.
*
* COSE message dissectors use the tag (wscbor_tag_t *) value, if used to
* discriminate the message type, as the user data pointer.
*
* COSE header label dissectors are registered with the dissector table
* "cose.header" and key parameter dissectors with the table "cose.keyparam"
* both with cose_param_key_t* keys.
* The header/parameter dissectors use a cose_header_context_t* as the user
* data pointer.
*
* An additional dissector "cose.msg.headers" will dissect an individual
* header map structure outside of a COSE message.
*/
// A header parameter or key-type parameter key
typedef struct {
/// The Algorithm or Key Type context or NULL for
/// all-context keys.
GVariant *principal;
/// Label simple value (int or tstr) as variant.
/// Object owned by this struct.
GVariant *label;
} cose_param_key_t;
/** Compatible with GHashFunc signature.
*/
guint cose_param_key_hash(gconstpointer ptr);
/** Compatible with GEqualFunc signature.
*/
gboolean cose_param_key_equal(gconstpointer a, gconstpointer b);
/// User data for header/key-parameter dissectors
typedef struct {
/// Principal value (alg or kty) of the map, if defined.
GVariant *principal;
/// Current label being processed
GVariant *label;
} cose_header_context_t;
#endif /* __PACKET_COSE_H__ */

View File

@ -58,6 +58,7 @@
#include "stats_tree.h"
#include "secrets.h"
#include "funnel.h"
#include "wscbor.h"
#include <dtd.h>
#ifdef HAVE_PLUGINS
@ -303,6 +304,7 @@ epan_init(register_cb cb, gpointer client_data, gboolean load_plugins)
g_slist_foreach(epan_plugins, epan_plugin_register_all_tap_listeners, NULL);
packet_cache_proto_handles();
dfilter_init();
wscbor_init();
final_registration_all_protocols();
print_cache_field_handles();
expert_packet_init();

546
epan/wscbor.c Normal file
View File

@ -0,0 +1,546 @@
/* wscbor.c
* Wireshark CBOR item decoding API.
* References:
* RFC 8949: https://tools.ietf.org/html/rfc8949
*
* Copyright 2019-2021, Brian Sipos <brian.sipos@gmail.com>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <epan/packet.h>
#include <epan/exceptions.h>
#include <epan/expert.h>
#include <stdio.h>
#include <inttypes.h>
#include "wscbor.h"
/// Pseudo-protocol to register expert info
static int proto_wscbor = -1;
static expert_field ei_cbor_invalid = EI_INIT;
static expert_field ei_cbor_overflow = EI_INIT;
static expert_field ei_cbor_wrong_type = EI_INIT;
static expert_field ei_cbor_array_wrong_size = EI_INIT;
static ei_register_info expertitems[] = {
{&ei_cbor_invalid, {"_ws.wscbor.cbor_invalid", PI_MALFORMED, PI_ERROR, "CBOR cannot be decoded", EXPFILL}},
{&ei_cbor_overflow, {"_ws.wscbor.cbor_overflow", PI_UNDECODED, PI_ERROR, "CBOR overflow of Wireshark value", EXPFILL}},
{&ei_cbor_wrong_type, {"_ws.wscbor.cbor_wrong_type", PI_MALFORMED, PI_ERROR, "CBOR is wrong type", EXPFILL}},
{&ei_cbor_array_wrong_size, {"_ws.wscbor.array_wrong_size", PI_MALFORMED, PI_WARN, "CBOR array is the wrong size", EXPFILL}},
};
/// The basic header structure of CBOR encoding
typedef struct {
/// The start offset of this header
gint start;
/// The length of just this header
gint length;
/// The expert info object (if error)
expert_field *error;
/// Major type of this item (cbor_type)
guint8 type_major;
/// Minor type of this item
guint8 type_minor;
/// Raw head "value" which may be from the @c type_minor
guint64 rawvalue;
} wscbor_head_t;
/** Read the raw value from a CBOR head.
* @param[in,out] head The head to read into.
* @param tvb The buffer to read from.
*/
static void wscbor_read_unsigned(wscbor_head_t *head, tvbuff_t *tvb) {
switch (head->type_minor) {
case 0x18:
head->rawvalue = tvb_get_guint8(tvb, head->start + head->length);
head->length += 1;
break;
case 0x19:
head->rawvalue = tvb_get_guint16(tvb, head->start + head->length, ENC_BIG_ENDIAN);
head->length += 2;
break;
case 0x1A:
head->rawvalue = tvb_get_guint32(tvb, head->start + head->length, ENC_BIG_ENDIAN);
head->length += 4;
break;
case 0x1B:
head->rawvalue = tvb_get_guint64(tvb, head->start + head->length, ENC_BIG_ENDIAN);
head->length += 8;
break;
default:
if (head->type_minor <= 0x17) {
head->rawvalue = head->type_minor;
}
break;
}
}
/** Read just the CBOR head octet.
* @post Will throw wireshark exception if read fails.
*/
static wscbor_head_t * wscbor_head_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint start) {
wscbor_head_t *head = wmem_new0(alloc, wscbor_head_t);
head->start = start;
const guint8 first = tvb_get_guint8(tvb, head->start);
head->length += 1;
// Match libcbor enums
head->type_major = (first & 0xe0) >> 5;
head->type_minor = (first & 0x1f);
switch ((cbor_type)(head->type_major)) {
case CBOR_TYPE_UINT:
case CBOR_TYPE_NEGINT:
case CBOR_TYPE_TAG:
wscbor_read_unsigned(head, tvb);
if (head->type_minor > 0x1B) {
head->error = &ei_cbor_invalid;
}
break;
case CBOR_TYPE_BYTESTRING:
case CBOR_TYPE_STRING:
case CBOR_TYPE_ARRAY:
case CBOR_TYPE_MAP:
case CBOR_TYPE_FLOAT_CTRL:
wscbor_read_unsigned(head, tvb);
if ((head->type_minor > 0x1B) && (head->type_minor < 0x1F)) {
head->error = &ei_cbor_invalid;
}
break;
default:
head->error = &ei_cbor_invalid;
break;
}
return head;
}
/** Force a head to be freed.
*/
static void wscbor_head_free(wmem_allocator_t *alloc, wscbor_head_t *head) {
wmem_free(alloc, head);
}
/** Get a clamped string length suitable for tvb functions.
* @param[in,out] chunk The chunk to read and set errors on.
* @return The clamped length value.
*/
static gint wscbor_get_length(const wscbor_chunk_t *chunk) {
gint length;
if (chunk->head_value > G_MAXINT) {
wmem_list_append(chunk->errors, wscbor_error_new(
chunk->_alloc, &ei_cbor_overflow,
NULL
));
length = G_MAXINT;
}
else {
length = (gint) chunk->head_value;
}
return length;
}
wscbor_error_t * wscbor_error_new(wmem_allocator_t *alloc, expert_field *ei, const char *format, ...) {
wscbor_error_t *err = wmem_new0(alloc, wscbor_error_t);
err->ei = ei;
if (format) {
wmem_strbuf_t *buf = wmem_strbuf_new(alloc, "");
va_list ap;
va_start(ap, format);
wmem_strbuf_append_vprintf(buf, format, ap);
va_end(ap);
err->msg = wmem_strbuf_finalize(buf);
}
return err;
}
wscbor_chunk_t * wscbor_chunk_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
DISSECTOR_ASSERT(alloc != NULL);
DISSECTOR_ASSERT(offset != NULL);
DISSECTOR_ASSERT(tvb != NULL);
wscbor_chunk_t *chunk = wmem_new0(alloc, wscbor_chunk_t);
chunk->_alloc = alloc;
chunk->errors = wmem_list_new(alloc);
chunk->tags = wmem_list_new(alloc);
chunk->start = *offset;
// Read a sequence of tags followed by an item header
while (TRUE) {
// This will break out of the loop if it runs out of buffer
wscbor_head_t *head = wscbor_head_read(alloc, tvb, *offset);
*offset += head->length;
chunk->head_length += head->length;
if (head->error) {
wmem_list_append(chunk->errors, wscbor_error_new(alloc, head->error, NULL));
}
if (head->type_major == CBOR_TYPE_TAG) {
wscbor_tag_t *tag = wmem_new(alloc, wscbor_tag_t);
tag->start = head->start;
tag->length = head->length;
tag->value = head->rawvalue;
wmem_list_append(chunk->tags, tag);
// same chunk, next part
wscbor_head_free(alloc, head);
continue;
}
// An actual (non-tag) header
chunk->type_major = (cbor_type)head->type_major;
chunk->type_minor = head->type_minor;
chunk->head_value = head->rawvalue;
chunk->data_length = chunk->head_length;
switch ((cbor_type)(head->type_major)) {
case CBOR_TYPE_BYTESTRING:
case CBOR_TYPE_STRING:
if (chunk->type_minor != 31) {
const gint datalen = wscbor_get_length(chunk);
// skip over definite data
*offset += datalen;
chunk->data_length += datalen;
}
break;
default:
break;
}
wscbor_head_free(alloc, head);
break;
}
return chunk;
}
static void wscbor_subitem_free(gpointer data, gpointer userdata) {
wmem_allocator_t *alloc = (wmem_allocator_t *) userdata;
wmem_free(alloc, data);
}
void wscbor_chunk_free(wscbor_chunk_t *chunk) {
DISSECTOR_ASSERT(chunk);
wmem_allocator_t *alloc = chunk->_alloc;
wmem_list_foreach(chunk->errors, wscbor_subitem_free, alloc);
wmem_destroy_list(chunk->errors);
wmem_list_foreach(chunk->tags, wscbor_subitem_free, alloc);
wmem_destroy_list(chunk->tags);
wmem_free(alloc, chunk);
}
guint64 wscbor_chunk_mark_errors(packet_info *pinfo, proto_item *item, const wscbor_chunk_t *chunk) {
for (wmem_list_frame_t *it = wmem_list_head(chunk->errors); it;
it = wmem_list_frame_next(it)) {
wscbor_error_t *err = (wscbor_error_t *) wmem_list_frame_data(it);
if (err->msg) {
expert_add_info_format(pinfo, item, err->ei, "%s", err->msg);
}
else {
expert_add_info(pinfo, item, err->ei);
}
}
return wmem_list_count(chunk->errors);
}
guint wscbor_has_errors(const wscbor_chunk_t *chunk) {
return wmem_list_count(chunk->errors);
}
gboolean wscbor_is_indefinite_break(const wscbor_chunk_t *chunk) {
return (
(chunk->type_major == CBOR_TYPE_FLOAT_CTRL)
&& (chunk->type_minor == 31)
);
}
gboolean wscbor_skip_next_item(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
wscbor_chunk_t *chunk = wscbor_chunk_read(alloc, tvb, offset);
switch (chunk->type_major) {
case CBOR_TYPE_UINT:
case CBOR_TYPE_NEGINT:
case CBOR_TYPE_TAG:
case CBOR_TYPE_FLOAT_CTRL:
break;
case CBOR_TYPE_BYTESTRING:
case CBOR_TYPE_STRING:
if (chunk->type_minor == 31) {
// wait for indefinite break
while (!wscbor_skip_next_item(alloc, tvb, offset)) {}
}
// wscbor_read_chunk() sets offset past definite value
break;
case CBOR_TYPE_ARRAY: {
if (chunk->type_minor == 31) {
// wait for indefinite break
while (!wscbor_skip_next_item(alloc, tvb, offset)) {}
}
else {
const guint64 count = chunk->head_value;
for (guint64 ix = 0; ix < count; ++ix) {
wscbor_skip_next_item(alloc, tvb, offset);
}
}
break;
}
case CBOR_TYPE_MAP: {
if (chunk->type_minor == 31) {
// wait for indefinite break
while (!wscbor_skip_next_item(alloc, tvb, offset)) {}
}
else {
const guint64 count = chunk->head_value;
for (guint64 ix = 0; ix < count; ++ix) {
wscbor_skip_next_item(alloc, tvb, offset);
wscbor_skip_next_item(alloc, tvb, offset);
}
}
break;
}
}
const gboolean is_break = wscbor_is_indefinite_break(chunk);
wscbor_chunk_free(chunk);
return is_break;
}
gboolean wscbor_skip_if_errors(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset, const wscbor_chunk_t *chunk) {
if (wscbor_has_errors(chunk) == 0) {
return FALSE;
}
*offset = chunk->start;
wscbor_skip_next_item(alloc, tvb, offset);
return TRUE;
}
void wscbor_init(void) {
proto_wscbor = proto_register_protocol(
"CBOR Item Decoder",
"CBOR Item Decoder",
"_ws.wscbor"
);
expert_module_t *expert_wscbor = expert_register_protocol(proto_wscbor);
/* This isn't really a protocol, it's an error indication;
disabling them makes no sense. */
proto_set_cant_toggle(proto_wscbor);
expert_register_field_array(expert_wscbor, expertitems, array_length(expertitems));
}
gboolean wscbor_require_major_type(wscbor_chunk_t *chunk, cbor_type major) {
if (chunk->type_major == major) {
return TRUE;
}
wmem_list_append(chunk->errors, wscbor_error_new(
chunk->_alloc, &ei_cbor_wrong_type,
"Item has major type %d, should be %d",
chunk->type_major, major
));
return FALSE;
}
gboolean wscbor_require_array(wscbor_chunk_t *chunk) {
return wscbor_require_major_type(chunk, CBOR_TYPE_ARRAY);
}
gboolean wscbor_require_array_size(wscbor_chunk_t *chunk, guint64 count_min, guint64 count_max) {
if (!wscbor_require_array(chunk)) {
return FALSE;
}
if ((chunk->head_value < count_min) || (chunk->head_value > count_max)) {
wmem_list_append(chunk->errors, wscbor_error_new(
chunk->_alloc, &ei_cbor_array_wrong_size,
"Array has %" PRId64 " items, should be within [%"PRId64", %"PRId64"]",
chunk->head_value, count_min, count_max
));
return FALSE;
}
return TRUE;
}
gboolean wscbor_require_map(wscbor_chunk_t *chunk) {
return wscbor_require_major_type(chunk, CBOR_TYPE_MAP);
}
gboolean * wscbor_require_boolean(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
if (!wscbor_require_major_type(chunk, CBOR_TYPE_FLOAT_CTRL)) {
return NULL;
}
switch (chunk->type_minor) {
case CBOR_CTRL_TRUE:
case CBOR_CTRL_FALSE: {
gboolean *value = NULL;
value = wmem_new(alloc, gboolean);
*value = (chunk->type_minor == CBOR_CTRL_TRUE);
return value;
}
default:
wmem_list_append(chunk->errors, wscbor_error_new(
chunk->_alloc, &ei_cbor_wrong_type,
"Item has minor type %d, should be %d or %d",
chunk->type_minor, CBOR_CTRL_TRUE, CBOR_CTRL_FALSE
));
break;
}
return NULL;
}
guint64 * wscbor_require_uint64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
if (!wscbor_require_major_type(chunk, CBOR_TYPE_UINT)) {
return NULL;
}
guint64 *result = wmem_new(alloc, guint64);
*result = chunk->head_value;
return result;
}
gint64 * wscbor_require_int64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
gint64 *result = NULL;
switch (chunk->type_major) {
case CBOR_TYPE_UINT:
case CBOR_TYPE_NEGINT: {
gint64 clamped;
if (chunk->head_value > INT64_MAX) {
clamped = INT64_MAX;
wmem_list_append(chunk->errors, wscbor_error_new(
chunk->_alloc, &ei_cbor_overflow,
NULL
));
}
else {
clamped = chunk->head_value;
}
result = wmem_new(alloc, gint64);
if (chunk->type_major == CBOR_TYPE_NEGINT) {
*result = -clamped - 1;
}
else {
*result = clamped;
}
break;
}
default:
wmem_list_append(chunk->errors, wscbor_error_new(
chunk->_alloc, &ei_cbor_wrong_type,
"Item has major type %d, should be %d or %d",
chunk->type_major, CBOR_TYPE_UINT, CBOR_TYPE_NEGINT
));
break;
}
return result;
}
char * wscbor_require_tstr(wmem_allocator_t *alloc, tvbuff_t *parent, wscbor_chunk_t *chunk) {
if (!wscbor_require_major_type(chunk, CBOR_TYPE_STRING)) {
return NULL;
}
return (char *)tvb_get_string_enc(alloc, parent, chunk->start + chunk->head_length, wscbor_get_length(chunk), ENC_UTF_8);
}
tvbuff_t * wscbor_require_bstr(tvbuff_t *parent, wscbor_chunk_t *chunk) {
if (!wscbor_require_major_type(chunk, CBOR_TYPE_BYTESTRING)) {
return NULL;
}
return tvb_new_subset_length(parent, chunk->start + chunk->head_length, wscbor_get_length(chunk));
}
proto_item * proto_tree_add_cbor_container(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
const header_field_info *hfinfo = proto_registrar_get_nth(hfindex);
proto_item *item;
if (IS_FT_UINT(hfinfo->type)) {
item = proto_tree_add_uint64(tree, hfindex, tvb, chunk->start, chunk->head_length, chunk->head_value);
}
else if (IS_FT_INT(hfinfo->type)) {
item = proto_tree_add_int64(tree, hfindex, tvb, chunk->start, chunk->head_length, chunk->head_value);
}
else {
item = proto_tree_add_item(tree, hfindex, tvb, chunk->start, -1, 0);
}
wscbor_chunk_mark_errors(pinfo, item, chunk);
return item;
}
proto_item * proto_tree_add_cbor_ctrl(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
proto_item *item = proto_tree_add_item(tree, hfindex, tvb, chunk->start, chunk->head_length, 0);
wscbor_chunk_mark_errors(pinfo, item, chunk);
return item;
}
proto_item * proto_tree_add_cbor_boolean(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const gboolean *value) {
proto_item *item = proto_tree_add_boolean(tree, hfindex, tvb, chunk->start, chunk->data_length, value ? *value : FALSE);
wscbor_chunk_mark_errors(pinfo, item, chunk);
return item;
}
proto_item * proto_tree_add_cbor_uint64(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const guint64 *value) {
proto_item *item = proto_tree_add_uint64(tree, hfindex, tvb, chunk->start, chunk->head_length, value ? *value : 0);
wscbor_chunk_mark_errors(pinfo, item, chunk);
return item;
}
proto_item * proto_tree_add_cbor_int64(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const gint64 *value) {
proto_item *item = proto_tree_add_int64(tree, hfindex, tvb, chunk->start, chunk->head_length, value ? *value : 0);
wscbor_chunk_mark_errors(pinfo, item, chunk);
return item;
}
proto_item * proto_tree_add_cbor_bitmask(proto_tree *tree, int hfindex, const gint ett, int *const *fields, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const guint64 *value) {
header_field_info *field = proto_registrar_get_nth(hfindex);
gint flagsize = 0;
switch (field->type) {
case FT_UINT8:
flagsize = 1;
break;
case FT_UINT16:
flagsize = 2;
break;
case FT_UINT32:
flagsize = 4;
break;
case FT_UINT64:
flagsize = 8;
break;
default:
fprintf(stderr, "Unhandled bitmask size: %d", field->type);
return NULL;
}
// Fake TVB data for these functions
guint8 *flags = (guint8 *) wmem_alloc0(wmem_packet_scope(), flagsize);
{ // Inject big-endian value directly
guint64 buf = (value ? *value : 0);
for (gint ix = flagsize - 1; ix >= 0; --ix) {
flags[ix] = buf & 0xFF;
buf >>= 8;
}
}
tvbuff_t *tvb_flags = tvb_new_child_real_data(tvb, flags, flagsize, flagsize);
proto_item *item = proto_tree_add_bitmask_value(tree, tvb_flags, 0, hfindex, ett, fields, value ? *value : 0);
wscbor_chunk_mark_errors(pinfo, item, chunk);
return item;
}
proto_item * proto_tree_add_cbor_tstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
proto_item *item = proto_tree_add_item(tree, hfindex, tvb, chunk->start + chunk->head_length, wscbor_get_length(chunk), 0);
wscbor_chunk_mark_errors(pinfo, item, chunk);
return item;
}
proto_item * proto_tree_add_cbor_bstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
proto_item *item = proto_tree_add_item(tree, hfindex, tvb, chunk->start + chunk->head_length, wscbor_get_length(chunk), 0);
wscbor_chunk_mark_errors(pinfo, item, chunk);
return item;
}

295
epan/wscbor.h Normal file
View File

@ -0,0 +1,295 @@
/* wscbor.h
* Definitions for the Wireshark CBOR item decoding API.
* References:
* RFC 8949: https://tools.ietf.org/html/rfc8949
*
* Copyright 2019-2021, Brian Sipos <brian.sipos@gmail.com>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WSCBOR_H__
#define __WSCBOR_H__
#include <ws_symbol_export.h>
#include <epan/tvbuff.h>
#include <epan/proto.h>
#include <epan/expert.h>
#include <wsutil/wmem/wmem_list.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Register expert info and other wireshark data.
*/
WS_DLL_PUBLIC
void wscbor_init(void);
/// The same enumeration from libcbor-0.5
typedef enum cbor_type {
CBOR_TYPE_UINT = 0, ///< positive integers
CBOR_TYPE_NEGINT = 1, ///< negative integers
CBOR_TYPE_BYTESTRING = 2, ///< byte strings
CBOR_TYPE_STRING = 3, ///< text strings
CBOR_TYPE_ARRAY = 4, ///< arrays
CBOR_TYPE_MAP = 5, ///< maps
CBOR_TYPE_TAG = 6, ///< tags
CBOR_TYPE_FLOAT_CTRL = 7, ///< decimals and special values (true, false, nil, ...)
} cbor_type;
/// The same enumeration from libcbor-0.5
typedef enum {
CBOR_CTRL_NONE = 0,
CBOR_CTRL_FALSE = 20,
CBOR_CTRL_TRUE = 21,
CBOR_CTRL_NULL = 22,
CBOR_CTRL_UNDEF = 23
} _cbor_ctrl;
/// Decoding or require_* error
typedef struct {
/// The associated expert info
expert_field *ei;
/// Optional specific text
const char *msg;
} wscbor_error_t;
/** Construct a new error object.
*
* @param alloc The allocator to use.
* @param ei The specific error type.
* @param format If non-NULL, a message format string.
* @return The new object.
*/
WS_DLL_PUBLIC
wscbor_error_t * wscbor_error_new(wmem_allocator_t *alloc, expert_field *ei, const char *format, ...);
/// Tag metadata and value
typedef struct {
/// The start offset of this tag head
gint start;
/// The length of just this tag head
gint length;
/// The tag value
guint64 value;
} wscbor_tag_t;
/// A data-containing, optionally-tagged chunk of CBOR
typedef struct {
/// The allocator used for #errors and #tags
wmem_allocator_t *_alloc;
/// The start offset of this chunk
gint start;
/// The length of just this header any any preceding tags
gint head_length;
/// The length of this chunk and its immediate definite data (i.e. strings)
gint data_length;
/// Errors processing this chunk (type wscbor_error_t*)
wmem_list_t *errors;
/// Tags on this chunk, in encoded order (type wscbor_tag_t*)
wmem_list_t *tags;
/// Major type of this block.
/// This will be one of the cbor_type values.
cbor_type type_major;
/// Minor type of this item
guint8 type_minor;
/// The header-encoded value
guint64 head_value;
} wscbor_chunk_t;
/** Scan for a tagged chunk of headers.
* The chunk of byte string and text string items includes the data content
* in its @c offset.
*
* @param alloc The allocator to use.
* @param tvb The TVB to read from.
* @param[in,out] offset The offset with in @c tvb.
* @return The chunk of data found, including any errors.
* This never returns NULL.
* @post This can throw ReportedBoundsError if the read itself ran out of data.
*/
WS_DLL_PUBLIC
wscbor_chunk_t * wscbor_chunk_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset);
/** Free a chunk and its lists.
*/
WS_DLL_PUBLIC
void wscbor_chunk_free(wscbor_chunk_t *chunk);
/** After both reading and decoding a chunk, report on any errors found.
* @param pinfo The associated packet.
* @param item The associated tree item.
* @param chunk The chunk with possible errors.
* @return The error count.
*/
WS_DLL_PUBLIC
guint64 wscbor_chunk_mark_errors(packet_info *pinfo, proto_item *item, const wscbor_chunk_t *chunk);
/** Determine if a chunk has errors.
* @param chunk The chunk with possible errors.
* @return The error count.
*/
WS_DLL_PUBLIC
guint wscbor_has_errors(const wscbor_chunk_t *chunk);
/** Determine if an indefinite break is present.
*
* @param chunk The chunk to check.
* @return True if it's an indefinite break.
*/
WS_DLL_PUBLIC
gboolean wscbor_is_indefinite_break(const wscbor_chunk_t *chunk);
/** Recursively skip items from a stream.
*
* @param alloc The allocator to use.
* @param tvb The data buffer.
* @param[in,out] offset The initial offset to read and skip over.
* @return True if the skipped item was an indefinite break.
*/
WS_DLL_PUBLIC
gboolean wscbor_skip_next_item(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset);
/** Skip over an item if a chunk has errors.
* This allows skipping an entire array or map if the major type or size is
* not as expected.
*
* @param alloc The allocator to use.
* @param tvb The data buffer.
* @param[in,out] offset The initial offset to read and skip over.
* @param chunk The chunk with possible errors.
* @return True if there were errors and the item skipped.
*/
WS_DLL_PUBLIC
gboolean wscbor_skip_if_errors(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset, const wscbor_chunk_t *chunk);
/** Require a specific item major type.
*
* @param[in,out] chunk The chunk to read from and write errors on.
* @param major The required major type.
* @return True if the item is that type.
*/
WS_DLL_PUBLIC
gboolean wscbor_require_major_type(wscbor_chunk_t *chunk, cbor_type major);
/** Require an array item.
*
* @param[in,out] chunk The chunk to read from and write errors on.
* @return True if the item is an array.
*/
WS_DLL_PUBLIC
gboolean wscbor_require_array(wscbor_chunk_t *chunk);
/** Require an array have a specific ranged size.
*
* @param[in,out] chunk The chunk to read from and write errors on.
* @param count_min The minimum acceptable size.
* @param count_max The maximum acceptable size.
* @return True if the size is acceptable.
*/
WS_DLL_PUBLIC
gboolean wscbor_require_array_size(wscbor_chunk_t *chunk, guint64 count_min, guint64 count_max);
/** Require a map item.
*
* @param[in,out] chunk The chunk to read from and write errors on.
* @return True if the item is a map.
*/
WS_DLL_PUBLIC
gboolean wscbor_require_map(wscbor_chunk_t *chunk);
/** Require a CBOR item to have a boolean value.
*
* @param alloc The allocator to use.
* @param[in,out] chunk The chunk to read from and write errors on.
* @return Pointer to the boolean value, if the item was boolean.
* The value can be deleted with wscbor_require_delete().
*/
WS_DLL_PUBLIC
gboolean * wscbor_require_boolean(wmem_allocator_t *alloc, wscbor_chunk_t *chunk);
/** Require a CBOR item to have an unsigned-integer value.
* @note This reader will clip the most significant bit of the value.
*
* @param alloc The allocator to use.
* @param[in,out] chunk The chunk to read from and write errors on.
* @return Pointer to the boolean value, if the item was an integer.
* The value can be deleted with wscbor_require_delete().
*/
WS_DLL_PUBLIC
guint64 * wscbor_require_uint64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk);
/** Require a CBOR item to have an signed- or unsigned-integer value.
* @note This reader will clip the most significant bit of the value.
*
* @param alloc The allocator to use.
* @param[in,out] chunk The chunk to read from and write errors on.
* @return Pointer to the value, if the item was an integer.
* The value can be deleted with wscbor_require_delete().
*/
WS_DLL_PUBLIC
gint64 * wscbor_require_int64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk);
/** Require a CBOR item to have a text-string value.
*
* @param alloc The allocator to use.
* @param parent The containing buffer.
* @param[in,out] chunk The chunk to read from and write errors on.
* @return Pointer to the null-terminated UTF-8, if the item was a tstr.
*/
WS_DLL_PUBLIC
char * wscbor_require_tstr(wmem_allocator_t *alloc, tvbuff_t *parent, wscbor_chunk_t *chunk);
/** Require a CBOR item to have a byte-string value.
*
* @param parent The containing buffer.
* @param[in,out] chunk The chunk to read from and write errors on.
* @return Pointer to the value, if the item was an string.
* The value is memory managed by wireshark.
*/
WS_DLL_PUBLIC
tvbuff_t * wscbor_require_bstr(tvbuff_t *parent, wscbor_chunk_t *chunk);
/** Add an item representing an array or map container.
* If the item is type FT_UINT* or FT_INT* the count of (array) items
* or map (pairs) is used as the iterm value.
*/
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_container(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);
/** Add an item representing a non-boolean, non-float control value.
*/
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_ctrl(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_boolean(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const gboolean *value);
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_uint64(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const guint64 *value);
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_int64(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const gint64 *value);
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_bitmask(proto_tree *tree, int hfindex, const gint ett, int *const *fields, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const guint64 *value);
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_tstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_bstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);
#ifdef __cplusplus
}
#endif
#endif /* __WSCBOR_H__ */

551
epan/wscbor_test.c Normal file
View File

@ -0,0 +1,551 @@
/* wscbor_test.c
* Wireshark CBOR API tests
* Copyright 2021, Brian Sipos <brian.sipos@gmail.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"
#undef G_DISABLE_ASSERT
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include "wscbor.h"
#include <epan/wmem_scopes.h>
#include <epan/exceptions.h>
#include <wsutil/wmem/wmem_list.h>
#include <ws_diag_control.h>
static wmem_allocator_t *test_scope;
typedef struct
{
// Raw bytes
gint enc_len;
const guint8 *enc;
// Members of cbor_chunk_t
gint head_length;
gint data_length;
guint8 type_major;
guint64 head_value;
} example_s;
DIAG_OFF_PEDANTIC
example_s ex_uint = {1, (const guint8 *)"\x01", 1, 1, CBOR_TYPE_UINT, 1};
example_s ex_nint = {1, (const guint8 *)"\x20", 1, 1, CBOR_TYPE_NEGINT, 0};
example_s ex_bstr = {3, (const guint8 *)"\x42\x68\x69", 1, 3, CBOR_TYPE_BYTESTRING, 2};
example_s ex_tstr = {3, (const guint8 *)"\x62\x68\x69", 1, 3, CBOR_TYPE_STRING, 2};
example_s ex_false = {1, (const guint8 *)"\xF4", 1, 1, CBOR_TYPE_FLOAT_CTRL, CBOR_CTRL_FALSE};
example_s ex_true = {1, (const guint8 *)"\xF5", 1, 1, CBOR_TYPE_FLOAT_CTRL, CBOR_CTRL_TRUE};
example_s ex_null = {1, (const guint8 *)"\xF6", 1, 1, CBOR_TYPE_FLOAT_CTRL, CBOR_CTRL_NULL};
example_s ex_undef = {1, (const guint8 *)"\xF7", 1, 1, CBOR_TYPE_FLOAT_CTRL, CBOR_CTRL_UNDEF};
example_s ex_break = {1, (const guint8 *)"\xFF", 1, 1, CBOR_TYPE_FLOAT_CTRL, 0};
example_s ex_uint_overflow = {9, (const guint8 *)"\x1B\x80\x00\x00\x00\x00\x00\x00\x00", 1, 9, CBOR_TYPE_UINT, 0x8000000000000000};
example_s ex_nint_overflow = {9, (const guint8 *)"\x3B\x80\x00\x00\x00\x00\x00\x00\x00", 1, 9, CBOR_TYPE_NEGINT, 0x8000000000000000};
example_s ex_bstr_overflow = {11, (const guint8 *)"\x5B\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00", 1, 9, CBOR_TYPE_BYTESTRING, 0x8000000000000000};
example_s ex_bstr_short = {2, (const guint8 *)"\x42\x68", 1, 3, CBOR_TYPE_BYTESTRING, 2};
example_s ex_tstr_short = {2, (const guint8 *)"\x62\x68", 1, 3, CBOR_TYPE_STRING, 2};
DIAG_ON_PEDANTIC
static const example_s * all_examples[] = {
&ex_uint, &ex_nint,
&ex_bstr, &ex_bstr_short, &ex_tstr,
&ex_false, &ex_true, &ex_null, &ex_undef, &ex_break
};
/*
* These test are organized in order of the appearance, in wscbor.h, of
* the basic functions that they test. This makes it easier to
* get a quick understanding of both the testing and the organization
* of the header.
*/
/* WSCBOR TESTING FUNCTIONS (/wscbor/) */
static void
wscbor_test_read_simple(void)
{
for (size_t ex_ix = 0; ex_ix < (sizeof(all_examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = all_examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpuint(chunk->head_length, ==, ex->head_length);
g_assert_cmpuint(chunk->data_length, ==, ex->data_length);
g_assert_cmpuint(wmem_list_count(chunk->tags), ==, 0);
g_assert_cmpuint(chunk->type_major, ==, ex->type_major);
g_assert_cmpuint(chunk->head_value, ==, ex->head_value);
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_read_simple_tags(void)
{
const guint8 *const tags = (const guint8 *)"\xC1\xD8\xC8";
tvbuff_t *tvb_tags = tvb_new_real_data(tags, 3, 3);
for (size_t ex_ix = 0; ex_ix < (sizeof(all_examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = all_examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb_item = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
tvbuff_t *tvb = tvb_new_composite();
tvb_composite_append(tvb, tvb_tags);
tvb_composite_append(tvb, tvb_item);
tvb_composite_finalize(tvb);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpuint(chunk->head_length, ==, ex->head_length + 3);
g_assert_cmpuint(chunk->data_length, ==, ex->data_length + 3);
g_assert_cmpuint(wmem_list_count(chunk->tags), ==, 2);
{
wmem_list_frame_t *frm = wmem_list_head(chunk->tags);
g_assert(frm);
{
const wscbor_tag_t *tag = wmem_list_frame_data(frm);
g_assert_cmpuint(tag->value, ==, 1);
}
frm = wmem_list_frame_next(frm);
g_assert(frm);
{
const wscbor_tag_t *tag = wmem_list_frame_data(frm);
g_assert_cmpuint(tag->value, ==, 200);
}
frm = wmem_list_frame_next(frm);
g_assert(!frm);
}
g_assert_cmpuint(chunk->type_major, ==, ex->type_major);
g_assert_cmpuint(chunk->head_value, ==, ex->head_value);
wscbor_chunk_free(chunk);
tvb_free(tvb_item);
}
tvb_free(tvb_tags);
}
static void
wscbor_test_read_invalid(void)
{
tvbuff_t *tvb = tvb_new_real_data((const guint8 *)"\x00\x01\x02\xC1", 4, 2);
gint offset = 2;
{ // last valid item
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpuint(chunk->type_major, ==, CBOR_TYPE_UINT);
g_assert_cmpuint(chunk->head_value, ==, 2);
wscbor_chunk_free(chunk);
}
g_assert_cmpint(offset, ==, 3);
{ // Tag without item
guint caught = 0;
TRY {
wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(FALSE);
}
CATCH(ReportedBoundsError) {
caught = ReportedBoundsError;
}
ENDTRY;
g_assert_cmpuint(caught, ==, ReportedBoundsError);
}
g_assert_cmpint(offset, ==, 4);
{ // Read past the end
guint caught = 0;
TRY {
wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(FALSE);
}
CATCH(ReportedBoundsError) {
caught = ReportedBoundsError;
}
ENDTRY;
g_assert_cmpuint(caught, ==, ReportedBoundsError);
}
g_assert_cmpint(offset, ==, 4);
tvb_free(tvb);
}
static void
wscbor_test_is_indefinite_break(void)
{
for (size_t ex_ix = 0; ex_ix < (sizeof(all_examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = all_examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
// this test never modifies the chunk
const gboolean val = wscbor_is_indefinite_break(chunk);
if (memcmp(ex->enc, "\xFF", 1) == 0) {
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
}
else {
g_assert(!val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
}
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_skip_next_item(void)
{
tvbuff_t *tvb = tvb_new_real_data((const guint8 *)"\x00\x01\x02\x03", 4, 4);
gint offset = 3;
gboolean res = wscbor_skip_next_item(test_scope, tvb, &offset);
g_assert(!res);
g_assert_cmpint(offset, ==, 4);
{ // Read past the end
guint caught = 0;
TRY {
wscbor_skip_next_item(test_scope, tvb, &offset);
g_assert(FALSE);
}
CATCH(ReportedBoundsError) {
caught = ReportedBoundsError;
}
ENDTRY;
g_assert_cmpuint(caught, ==, ReportedBoundsError);
}
g_assert_cmpint(offset, ==, 4);
tvb_free(tvb);
}
static void
wscbor_test_require_major_type(void)
{
for (size_t ex_ix = 0; ex_ix < (sizeof(all_examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = all_examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert(wscbor_require_major_type(chunk, ex->type_major));
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
// any other type
g_assert(!wscbor_require_major_type(chunk, ex->type_major + 1));
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_require_boolean(void)
{
for (size_t ex_ix = 0; ex_ix < (sizeof(all_examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = all_examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
const gboolean *val = wscbor_require_boolean(test_scope, chunk);
if ((ex->type_major == CBOR_TYPE_FLOAT_CTRL)
&& ((ex->head_value == CBOR_CTRL_FALSE) || (ex->head_value == CBOR_CTRL_TRUE))) {
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpint(*val, ==, ex->head_value == CBOR_CTRL_TRUE);
}
else {
g_assert(!val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
}
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_require_int64(void)
{
for (size_t ex_ix = 0; ex_ix < (sizeof(all_examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = all_examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
const gint64 *val = wscbor_require_int64(test_scope, chunk);
if (ex->type_major == CBOR_TYPE_UINT) {
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpint(*val, ==, ex->head_value);
}
else if (ex->type_major == CBOR_TYPE_NEGINT) {
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpint(*val, ==, -1 - ex->head_value);
}
else {
g_assert(!val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
}
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_require_int64_overflow(void)
{
const example_s * examples[] = {
&ex_uint_overflow, &ex_nint_overflow,
};
for (size_t ex_ix = 0; ex_ix < (sizeof(examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpuint(chunk->type_major, ==, ex->type_major);
g_assert_cmpuint(chunk->head_value, ==, ex->head_value);
const gint64 *val = wscbor_require_int64(test_scope, chunk);
if (ex->type_major == CBOR_TYPE_UINT) {
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
g_assert_cmpint(*val, ==, G_MAXINT64);
}
else if (ex->type_major == CBOR_TYPE_NEGINT) {
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
g_assert_cmpint(*val, ==, G_MININT64);
}
else {
g_assert(!val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
}
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_require_tstr(void)
{
for (size_t ex_ix = 0; ex_ix < (sizeof(all_examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = all_examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
const char *val = wscbor_require_tstr(test_scope, tvb, chunk);
if (ex->type_major == CBOR_TYPE_STRING) {
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
// only works because this is Latin-1 text
g_assert_cmpint(strlen(val), ==, ex->head_value);
}
else {
g_assert(!val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
}
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_require_tstr_short(void)
{
const example_s * examples[] = {
&ex_tstr_short,
};
for (size_t ex_ix = 0; ex_ix < (sizeof(examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpuint(chunk->type_major, ==, ex->type_major);
g_assert_cmpuint(chunk->head_value, ==, ex->head_value);
guint caught = 0;
TRY {
wscbor_require_tstr(test_scope, tvb, chunk);
g_assert(FALSE);
}
CATCH(ReportedBoundsError) {
caught = ReportedBoundsError;
}
ENDTRY;
g_assert_cmpuint(caught, ==, ReportedBoundsError);
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_require_bstr(void)
{
for (size_t ex_ix = 0; ex_ix < (sizeof(all_examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = all_examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
const tvbuff_t *val = wscbor_require_bstr(tvb, chunk);
if (ex->type_major == CBOR_TYPE_BYTESTRING) {
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 0);
g_assert_cmpint(tvb_reported_length(val), ==, ex->head_value);
g_assert_cmpuint(tvb_captured_length(val), <=, ex->head_value);
}
else {
g_assert(!val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
}
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
static void
wscbor_test_require_bstr_overflow(void)
{
const example_s * examples[] = {
&ex_bstr_overflow,
};
for (size_t ex_ix = 0; ex_ix < (sizeof(examples) / sizeof(example_s*)); ++ex_ix) {
const example_s *ex = examples[ex_ix];
printf("simple #%zu\n", ex_ix);
tvbuff_t *tvb = tvb_new_real_data(ex->enc, ex->enc_len, ex->enc_len);
gint offset = 0;
wscbor_chunk_t *chunk = wscbor_chunk_read(test_scope, tvb, &offset);
g_assert(chunk);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 1);
g_assert_cmpuint(chunk->type_major, ==, ex->type_major);
g_assert_cmpuint(chunk->head_value, ==, ex->head_value);
const tvbuff_t *val = wscbor_require_bstr(tvb, chunk);
g_assert(val);
g_assert_cmpuint(wscbor_has_errors(chunk), ==, 2);
g_assert_cmpuint(tvb_reported_length(val), ==, G_MAXINT);
g_assert_cmpuint(tvb_captured_length(val), ==, 2);
wscbor_chunk_free(chunk);
tvb_free(tvb);
}
}
int
main(int argc, char **argv)
{
int result;
g_test_init(&argc, &argv, NULL);
g_test_add_func("/wscbor/read/simple", wscbor_test_read_simple);
g_test_add_func("/wscbor/read/simple_tags", wscbor_test_read_simple_tags);
g_test_add_func("/wscbor/read/invalid", wscbor_test_read_invalid);
g_test_add_func("/wscbor/is_indefinite_break", wscbor_test_is_indefinite_break);
g_test_add_func("/wscbor/skip_next_item", wscbor_test_skip_next_item);
g_test_add_func("/wscbor/require_major_type", wscbor_test_require_major_type);
g_test_add_func("/wscbor/require_boolean", wscbor_test_require_boolean);
g_test_add_func("/wscbor/require_int64", wscbor_test_require_int64);
g_test_add_func("/wscbor/require_int64_overflow", wscbor_test_require_int64_overflow);
g_test_add_func("/wscbor/require_tstr", wscbor_test_require_tstr);
g_test_add_func("/wscbor/require_tstr_short", wscbor_test_require_tstr_short);
g_test_add_func("/wscbor/require_bstr", wscbor_test_require_bstr);
g_test_add_func("/wscbor/require_bstr_overflow", wscbor_test_require_bstr_overflow);
wmem_init_scopes();
test_scope = wmem_allocator_new(WMEM_ALLOCATOR_STRICT);
//cannot use: wscbor_init();
result = g_test_run();
//none needed: wscbor_cleanup();
wmem_destroy_allocator(test_scope);
wmem_cleanup_scopes();
return result;
}
/*
* 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:
*/

View File

@ -0,0 +1,12 @@
16(
[
/ protected h'a1010a' / << {
/ alg / 1:10 / AES-CCM-16-64-128 /
} >> ,
/ unprotected / {
/ iv / 5:h'89f52f65a1c580933b5261a78c'
},
/ ciphertext / h'5974e1b99a3a4cc09a659aa2e9e7fff161d38ce71cb45ce
460ffb569'
]
)

Binary file not shown.

View File

@ -0,0 +1,24 @@
96(
[
/ protected h'a1010a' / << {
/ alg / 1:10 / AES-CCM-16-64-128 /
} >>,
/ unprotected / {
/ iv / 5:h'89f52f65a1c580933b5261a76c'
},
/ ciphertext / h'753548a19b1307084ca7b2056924ed95f2e3b17006dfe93
1b687b847',
/ recipients / [
[
/ protected h'a10129' / << {
/ alg / 1:-10
} >>,
/ unprotected / {
/ salt / -20:'aabbccddeeffgghh',
/ kid / 4:'our-secret'
},
/ ciphertext / h''
]
]
]
)

Binary file not shown.

View File

@ -0,0 +1,19 @@
[
{
1:2,
2:'meriadoc.brandybuck@buckland.example',
-1:1,
-2:h'65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c0
8551d',
-3:h'1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd008
4d19c',
-4:h'aff907c99f9ad3aae6c4cdf21122bce2bd68b5283e6907154ad911840fa
208cf'
},
{
1:4,
2:'018c0ae5-4d9b-471b-bfd6-eef314bc7037',
-1:h'849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c4
27188'
}
]

Binary file not shown.

View File

@ -0,0 +1,10 @@
17(
[
/ protected h'a1010f' / << {
/ alg / 1:15 / AES-CBC-MAC-256//64 /
} >> ,
/ unprotected / {},
/ payload / 'This is the content.',
/ tag / h'726043745027214f'
]
)

Binary file not shown.

View File

@ -0,0 +1,40 @@
97(
[
/ protected h'a10105' / << {
/ alg / 1:5 / HMAC 256//256 /
} >> ,
/ unprotected / {},
/ payload / 'This is the content.',
/ tag / h'bf48235e809b5c42e995f2b7d5fa13620e7ed834e337f6aa43df16
1e49e9323e',
/ recipients / [
[
/ protected h'a101381c' / << {
/ alg / 1:-29 / ECHD-ES+A128KW /
} >> ,
/ unprotected / {
/ ephemeral / -1:{
/ kty / 1:2,
/ crv / -1:3,
/ x / -2:h'0043b12669acac3fd27898ffba0bcd2e6c366d53bc4db
71f909a759304acfb5e18cdc7ba0b13ff8c7636271a6924b1ac63c02688075b55ef2
d613574e7dc242f79c3',
/ y / -3:true
},
/ kid / 4:'bilbo.baggins@hobbiton.example'
},
/ ciphertext / h'339bc4f79984cdc6b3e6ce5f315a4c7d2b0ac466fce
a69e8c07dfbca5bb1f661bc5f8e0df9e3eff5'
],
[
/ protected / h'',
/ unprotected / {
/ alg / 1:-5 / A256KW /,
/ kid / 4:'018c0ae5-4d9b-471b-bfd6-eef314bc7037'
},
/ ciphertext / h'0b2c7cfce04e98276342d6476a7723c090dfdd15f9a
518e7736549e998370695e6d6a83b4ae507bb'
]
]
]
)

Binary file not shown.

View File

@ -0,0 +1,14 @@
18(
[
/ protected h'a10126' / << {
/ alg / 1:-7 / ECDSA 256 /
} >>,
/ unprotected / {
/ kid / 4:'11'
},
/ payload / 'This is the content.',
/ signature / h'8eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4
d25a91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b0916e5
a4c345cacb36'
]
)

Binary file not shown.

View File

@ -0,0 +1,39 @@
98(
[
/ protected h'a2687265736572766564f40281687265736572766564' /
<< {
"reserved":false,
/ crit / 2:[
"reserved"
]
} >>,
/ unprotected / {},
/ payload / 'This is the content.',
/ signatures / [
[
/ protected h'a10126' / << {
/ alg / 1:-7 / ECDSA 256 /
} >>,
/ unprotected / {
/ kid / 4:'11'
},
/ signature / h'e2aeafd40d69d19dfe6e52077c5d7ff4e408282cbefb
5d06cbf414af2e19d982ac45ac98b8544c908b4507de1e90b717c3d34816fe926a2b
98f53afd2fa0f30a'
],
[
/ protected h'a1013823' / << {
/ alg / 1:-36 / ECDSA 521 /
} >> ,
/ unprotected / {
/ kid / 4:'bilbo.baggins@hobbiton.example'
},
/ signature / h'00a2d28a7c2bdb1587877420f65adf7d0b9a06635dd1
de64bb62974c863f0b160dd2163734034e6ac003b01e8705524c5c4ca479a952f024
7ee8cb0b4fb7397ba08d009e0c8bf482270cc5771aa143966e5a469a09f613488030
c5b07ec6d722e3835adb5b2d8c44e95ffb13877dd2582866883535de3bb03d01753f
83ab87bb4f7a0297'
]
]
]
)

Binary file not shown.

View File

@ -14,6 +14,63 @@ import unittest
import fixtures
import sys
@fixtures.mark_usefixtures('test_env')
@fixtures.uses_fixtures
class case_dissect_cose(subprocesstest.SubprocessTestCase):
'''
These test captures were generated from the COSE example files with command:
for FN in test/captures/cose*.cbordiag; do python3 tools/generate_cbor_pcap.py --content-type 'application/cose' --infile $FN --outfile ${FN%.cbordiag}.pcap; done
'''
def test_cose_sign_tagged(self, cmd_tshark, features, dirs, capture_file):
self.assertRun((cmd_tshark,
'-r', capture_file('cose_sign_tagged.pcap'),
'-Tfields', '-ecose.msg.signature',
))
self.assertTrue(self.grepOutput('e2aeafd40d69d19dfe6e52077c5d7ff4e408282cbefb5d06cbf414af2e19d982ac45ac98b8544c908b4507de1e90b717c3d34816fe926a2b98f53afd2fa0f30a'))
def test_cose_sign1_tagged(self, cmd_tshark, features, dirs, capture_file):
self.assertRun((cmd_tshark,
'-r', capture_file('cose_sign1_tagged.pcap'),
'-Tfields', '-ecose.msg.signature',
))
self.assertTrue(self.grepOutput('8eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4d25a91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b0916e5a4c345cacb36'))
def test_cose_encrypt_tagged(self, cmd_tshark, features, dirs, capture_file):
self.assertRun((cmd_tshark,
'-r', capture_file('cose_encrypt_tagged.pcap'),
'-Tfields', '-ecose.kid',
))
self.assertTrue(self.grepOutput('6f75722d736563726574'))
def test_cose_encrypt0_tagged(self, cmd_tshark, features, dirs, capture_file):
self.assertRun((cmd_tshark,
'-r', capture_file('cose_encrypt0_tagged.pcap'),
'-Tfields', '-ecose.iv',
))
self.assertTrue(self.grepOutput('89f52f65a1c580933b5261a78c'))
def test_cose_mac_tagged(self, cmd_tshark, features, dirs, capture_file):
self.assertRun((cmd_tshark,
'-r', capture_file('cose_mac_tagged.pcap'),
'-Tfields', '-ecose.kid',
))
self.assertTrue(self.grepOutput('30313863306165352d346439622d343731622d626664362d656566333134626337303337'))
def test_cose_mac0_tagged(self, cmd_tshark, features, dirs, capture_file):
self.assertRun((cmd_tshark,
'-r', capture_file('cose_mac0_tagged.pcap'),
'-Tfields', '-ecose.msg.mac_tag',
))
self.assertTrue(self.grepOutput('726043745027214f'))
def test_cose_keyset(self, cmd_tshark, features, dirs, capture_file):
self.assertRun((cmd_tshark,
'-r', capture_file('cose_keyset.pcap'),
'-Tfields', '-ecose.key.k',
))
self.assertTrue(self.grepOutput('849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188'))
@fixtures.mark_usefixtures('test_env')
@fixtures.uses_fixtures
class case_dissect_grpc(subprocesstest.SubprocessTestCase):

View File

@ -41,6 +41,10 @@ class case_unittests(subprocesstest.SubprocessTestCase):
'--verbose'
), env=base_env)
def test_unit_wscbor_test(self, program, base_env):
'''wscbor_test'''
self.assertRun(program('wscbor_test'), env=base_env)
def test_unit_wsutil(self, program, base_env):
'''wsutil unit tests'''
self.assertRun((program('test_wsutil'),

View File

@ -0,0 +1,63 @@
#!/usr/bin/env python3
'''
Convert a CBOR diagnostic notation file into an HTTP request
for the encoded cbor.
This allows straightforward test and debugging of simple pcap files.
Copyright 2021 Brian Sipos <brian.sipos@gmail.com>
SPDX-License-Identifier: LGPL-2.1-or-later
'''
from argparse import ArgumentParser
from io import BytesIO
import scapy
from scapy.layers.l2 import Ether
from scapy.layers.inet import IP, TCP
from scapy.layers.http import HTTP, HTTPRequest
from scapy.packet import Raw
from scapy.utils import wrpcap
from subprocess import check_output
import sys
def main():
parser = ArgumentParser()
parser.add_argument('--content-type', default='application/cbor',
help='The request content-type header')
parser.add_argument('--infile', default='-',
help='The diagnostic text input file, or "-" for stdin')
parser.add_argument('--outfile', default='-',
help='The PCAP output file, or "-" for stdout')
args = parser.parse_args()
# First get the CBOR data itself
infile_name = args.infile.strip()
if infile_name != '-':
infile = open(infile_name, 'r')
else:
infile = sys.stdin
cbordata = check_output('diag2cbor.rb', stdin=infile)
# Now synthesize an HTTP request with that body
req = HTTPRequest(
Method='POST',
Host='example.com',
User_Agent='scapy',
Content_Type=args.content_type,
Content_Length=str(len(cbordata)),
) / Raw(cbordata)
# Write the request directly into pcap
outfile_name = args.outfile.strip()
if outfile_name != '-':
outfile = open(outfile_name, 'wb')
else:
outfile = sys.stdout.buffer
pkt = Ether()/IP()/TCP()/HTTP()/req
wrpcap(outfile, pkt)
if __name__ == '__main__':
sys.exit(main())