diff --git a/CMakeLists.txt b/CMakeLists.txt index 13e015aa82..b11b11d1bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" ) diff --git a/debian/libwireshark0.symbols b/debian/libwireshark0.symbols index c89534bb47..40dd889c39 100644 --- a/debian/libwireshark0.symbols +++ b/debian/libwireshark0.symbols @@ -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 diff --git a/docbook/release-notes.adoc b/docbook/release-notes.adoc index d9edaab833..ec7dc9810c 100644 --- a/docbook/release-notes.adoc +++ b/docbook/release-notes.adoc @@ -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) diff --git a/epan/CMakeLists.txt b/epan/CMakeLists.txt index f63009771a..2839e57f1f 100644 --- a/epan/CMakeLists.txt +++ b/epan/CMakeLists.txt @@ -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 diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 735cecb9ab..37dd6865bb 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -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 diff --git a/epan/dissectors/packet-cose.c b/epan/dissectors/packet-cose.c new file mode 100644 index 0000000000..c80689bd68 --- /dev/null +++ b/epan/dissectors/packet-cose.c @@ -0,0 +1,1275 @@ +/* packet-cose.c + * Routines for CBOR Object Signing and Encryption (COSE) dissection + * References: + * RFC 8152: https://tools.ietf.org/html/rfc8152 + * RFC 8949: https://tools.ietf.org/html/rfc8949 + * + * Copyright 2019-2021, Brian Sipos + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +#include "packet-cose.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/// Glib logging "domain" name +static const char *LOG_DOMAIN = "COSE"; +/// Protocol column name +static const char *const proto_name_cose = "COSE"; + +/// Protocol preferences and defaults + +/// Protocol handles +static int proto_cose = -1; + +/// Dissect opaque CBOR data +static dissector_handle_t handle_cbor = NULL; +/// Dissector handles +static dissector_handle_t handle_cose_msg_hdr = NULL; +static dissector_handle_t handle_cose_msg_tagged = NULL; +static dissector_handle_t handle_cose_sign = NULL; +static dissector_handle_t handle_cose_sign1 = NULL; +static dissector_handle_t handle_cose_encrypt = NULL; +static dissector_handle_t handle_cose_encrypt0 = NULL; +static dissector_handle_t handle_cose_mac = NULL; +static dissector_handle_t handle_cose_mac0 = NULL; +static dissector_handle_t handle_cose_key = NULL; +static dissector_handle_t handle_cose_key_set = NULL; + +/// Dissect opaque data +static dissector_table_t table_media = NULL; +/// Dissect extension items +static dissector_table_t table_cose_msg_tag = NULL; +static dissector_table_t table_header = NULL; +static dissector_table_t table_keyparam = NULL; + +static const val64_string alg_vals[] = { + {-65535, "RS1"}, + {-259, "RS512"}, + {-258, "RS384"}, + {-257, "RS256"}, + {-47, "ES256K"}, + {-45, "SHAKE256"}, + {-44, "SHA-512"}, + {-43, "SHA-384"}, + {-39, "PS512"}, + {-38, "PS384"}, + {-37, "PS256"}, + {-36, "ES512"}, + {-35, "ES384"}, + {-34, "ECDH-SS + A256KW"}, + {-33, "ECDH-SS + A192KW"}, + {-32, "ECDH-SS + A128KW"}, + {-31, "ECDH-ES + A256KW"}, + {-30, "ECDH-ES + A192KW"}, + {-29, "ECDH-ES + A128KW"}, + {-28, "ECDH-SS + HKDF-512"}, + {-27, "ECDH-SS + HKDF-256"}, + {-26, "ECDH-ES + HKDF-512"}, + {-25, "ECDH-ES + HKDF-256"}, + {-18, "SHAKE128"}, + {-17, "SHA-512/256"}, + {-16, "SHA-256"}, + {-15, "SHA-256/64"}, + {-14, "SHA-1"}, + {-13, "direct+HKDF-AES-256"}, + {-12, "direct+HKDF-AES-128"}, + {-11, "direct+HKDF-SHA-512"}, + {-10, "direct+HKDF-SHA-256"}, + {-8, "EdDSA"}, + {-7, "ES256"}, + {-6, "direct"}, + {-5, "A256KW"}, + {-4, "A192KW"}, + {-3, "A128KW"}, + {0, "Reserved"}, + {1, "A128GCM"}, + {2, "A192GCM"}, + {3, "A256GCM"}, + {4, "HMAC 256/64"}, + {5, "HMAC 256/256"}, + {6, "HMAC 384/384"}, + {7, "HMAC 512/512"}, + {10, "AES-CCM-16-64-128"}, + {11, "AES-CCM-16-64-256"}, + {12, "AES-CCM-64-64-128"}, + {13, "AES-CCM-64-64-256"}, + {14, "AES-MAC 128/64"}, + {15, "AES-MAC 256/64"}, + {24, "ChaCha20/Poly1305"}, + {25, "AES-MAC 128/128"}, + {26, "AES-MAC 256/128"}, + {30, "AES-CCM-16-128-128"}, + {31, "AES-CCM-16-128-256"}, + {32, "AES-CCM-64-128-128"}, + {33, "AES-CCM-64-128-256"}, + {34, "IV-GENERATION"}, + {0, NULL}, +}; + +static const val64_string kty_vals[] = { + {0, "Reserved"}, + {1, "OKP"}, + {2, "EC2"}, + {3, "RSA"}, + {4, "Symmetric"}, + {5, "HSS-LMS"}, + {0, NULL}, +}; + +static const val64_string keyops_vals[] = { + {1, "sign"}, + {2, "verify"}, + {3, "encrypt"}, + {4, "decrypt"}, + {5, "key wrap"}, + {6, "key unwrap"}, + {7, "derive key"}, + {8, "derive bits"}, + {9, "MAC create"}, + {10, "MAC verify"}, + {0, NULL}, +}; + +static const val64_string crv_vals[] = { + {0, "Reserved"}, + {1, "P-256"}, + {2, "P-384"}, + {3, "P-521"}, + {4, "X25519"}, + {5, "X448"}, + {6, "Ed25519"}, + {7, "Ed448"}, + {0, NULL}, +}; + +static int hf_msg_tag = -1; +static int hf_hdr_prot_bstr = -1; +static int hf_hdr_unprot = -1; +static int hf_payload_null = -1; +static int hf_payload_bstr = -1; +static int hf_cose_signature_list = -1; +static int hf_cose_signature = -1; +static int hf_signature = -1; +static int hf_ciphertext_null = -1; +static int hf_ciphertext_bstr = -1; +static int hf_cose_recipient_list = -1; +static int hf_cose_recipient = -1; +static int hf_tag = -1; + +static int hf_hdr_label_int = -1; +static int hf_hdr_label_tstr = -1; + +static int hf_hdr_salt = -1; +static int hf_hdr_static_key = -1; +static int hf_hdr_ephem_key = -1; +static int hf_hdr_alg_int = -1; +static int hf_hdr_alg_tstr = -1; +static int hf_hdr_crit_list = -1; +static int hf_hdr_ctype_uint = -1; +static int hf_hdr_ctype_tstr = -1; +static int hf_hdr_kid = -1; +static int hf_hdr_iv = -1; +static int hf_hdr_piv = -1; +static int hf_hdr_x5bag = -1; +static int hf_hdr_x5chain = -1; +static int hf_hdr_x5t = -1; +static int hf_hdr_x5t_hash = -1; +static int hf_hdr_x5u = -1; + +static int hf_key = -1; + +static int hf_keyparam_kty_int = -1; +static int hf_keyparam_kty_tstr = -1; +static int hf_keyparam_keyops_list = -1; +static int hf_keyparam_keyops_int = -1; +static int hf_keyparam_keyops_tstr = -1; +static int hf_keyparam_baseiv = -1; +static int hf_keyparam_crv_int = -1; +static int hf_keyparam_crv_tstr = -1; +static int hf_keyparam_xcoord = -1; +static int hf_keyparam_ycoord = -1; +static int hf_keyparam_dcoord = -1; +static int hf_keyparam_k = -1; + +/// Field definitions +static hf_register_info fields[] = { + {&hf_msg_tag, {"Message type tag", "cose.msgtag", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_prot_bstr, {"Protected Headers (bstr)", "cose.msg.prot_bstr", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_unprot, {"Unprotected Headers", "cose.msg.unprot", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + + {&hf_payload_null, {"Payload Detached", "cose.msg.detached_payload", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_payload_bstr, {"Payload", "cose.msg.payload", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_signature, {"Signature", "cose.msg.signature", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_cose_signature_list, {"Signature List, Count", "cose.msg.signature_list", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL}}, + {&hf_cose_signature, {"COSE_Signature", "cose.msg.cose_signature", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_ciphertext_null, {"Ciphertext Detached", "cose.msg.detached_ciphertext", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_ciphertext_bstr, {"Ciphertext", "cose.msg.ciphertext", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_cose_recipient_list, {"Recipient List, Count", "cose.msg.recipient_list", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL}}, + {&hf_cose_recipient, {"COSE_Recipient", "cose.msg.cose_recipient", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_tag, {"Tag", "cose.msg.mac_tag", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + + {&hf_hdr_label_int, {"Label", "cose.header_label.int", FT_INT64, BASE_DEC, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_label_tstr, {"Label", "cose.header_label.tstr", FT_STRING, STR_UNICODE, NULL, 0x0, NULL, HFILL}}, + + {&hf_hdr_salt, {"Salt", "cose.salt", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_static_key, {"Static Key", "cose.static_key", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_ephem_key, {"Ephemeral Key", "cose.ephem_key", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_alg_int, {"Algorithm", "cose.alg.int", FT_INT64, BASE_DEC | BASE_VAL64_STRING, VALS64(alg_vals), 0x0, NULL, HFILL}}, + {&hf_hdr_alg_tstr, {"Algorithm", "cose.alg.tstr", FT_STRING, STR_UNICODE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_crit_list, {"Critical Headers, Count", "cose.crit", FT_INT64, BASE_DEC, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_ctype_uint, {"Content-Format", "cose.content-type.uint", FT_INT64, BASE_DEC, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_ctype_tstr, {"Content-Type", "cose.content-type.tstr", FT_STRING, STR_UNICODE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_kid, {"Key identifier", "cose.kid", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_iv, {"IV", "cose.iv", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_piv, {"Partial IV", "cose.piv", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + + {&hf_hdr_x5bag, {"X509 Bag (x5bag)", "cose.x5bag", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_x5chain, {"X509 Chain (x5chain)", "cose.x5chain", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_x5t, {"X509 Thumbprint (x5t)", "cose.x5t", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_x5t_hash, {"Hash Value", "cose.x5t.hash", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_hdr_x5u, {"X509 URI (x5u)", "cose.x5u", FT_STRING, STR_UNICODE, NULL, 0x0, NULL, HFILL}}, + + {&hf_key, {"COSE_Key", "cose.key", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + + {&hf_keyparam_kty_int, {"Key Type", "cose.kty.int", FT_INT64, BASE_DEC | BASE_VAL64_STRING, VALS64(kty_vals), 0x0, NULL, HFILL}}, + {&hf_keyparam_kty_tstr, {"Key Type", "cose.kty.tstr", FT_STRING, STR_UNICODE, NULL, 0x0, NULL, HFILL}}, + {&hf_keyparam_keyops_list, {"Key Operations", "cose.keyops", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_keyparam_keyops_int, {"Operation", "cose.keyops.int", FT_INT64, BASE_DEC | BASE_VAL64_STRING, VALS64(keyops_vals), 0x0, NULL, HFILL}}, + {&hf_keyparam_keyops_tstr, {"Operation", "cose.keyops.tstr", FT_STRING, STR_UNICODE, NULL, 0x0, NULL, HFILL}}, + {&hf_keyparam_baseiv, {"Base IV", "cose.baseiv", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_keyparam_crv_int, {"Curve Type", "cose.crv.int", FT_INT64, BASE_DEC | BASE_VAL64_STRING, VALS64(crv_vals), 0x0, NULL, HFILL}}, + {&hf_keyparam_crv_tstr, {"Curve Type", "cose.crv.tstr", FT_STRING, STR_UNICODE, NULL, 0x0, NULL, HFILL}}, + {&hf_keyparam_xcoord, {"X-coordinate", "cose.key.xcoord", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_keyparam_ycoord, {"Y-coordinate", "cose.key.ycoord", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_keyparam_dcoord, {"Private Key", "cose.key.dcoord", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_keyparam_k, {"Key", "cose.key.k", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL}}, +}; + +static int ett_msg = -1; +static int ett_sig_list = -1; +static int ett_sig = -1; +static int ett_recip_list = -1; +static int ett_recip = -1; +static int ett_prot_bstr = -1; +static int ett_unprot = -1; +static int ett_hdr_map = -1; +static int ett_hdr_label = -1; +static int ett_hdr_static_key = -1; +static int ett_hdr_ephem_key = -1; +static int ett_hdr_crit_list = -1; +static int ett_hdr_x5cert_list = -1; +static int ett_hdr_x5t_list = -1; +static int ett_key = -1; +static int ett_key_set = -1; +static int ett_keyops_list = -1; +/// Tree structures +static int *ett[] = { + &ett_msg, + &ett_sig_list, + &ett_sig, + &ett_recip_list, + &ett_recip, + &ett_prot_bstr, + &ett_unprot, + &ett_hdr_map, + &ett_hdr_label, + &ett_hdr_static_key, + &ett_hdr_ephem_key, + &ett_hdr_crit_list, + &ett_hdr_x5cert_list, + &ett_hdr_x5t_list, + &ett_key, + &ett_key_set, + &ett_keyops_list, +}; + +static expert_field ei_invalid_tag = EI_INIT; +static expert_field ei_value_partial_decode = EI_INIT; +static ei_register_info expertitems[] = { + {&ei_invalid_tag, { "cose.invalid_tag", PI_UNDECODED, PI_WARN, "COSE dissector did not match any known tag", EXPFILL}}, + {&ei_value_partial_decode, { "cose.partial_decode", PI_MALFORMED, PI_WARN, "Value is only partially decoded", EXPFILL}}, +}; + +guint cose_param_key_hash(gconstpointer ptr) { + const cose_param_key_t *obj = (const cose_param_key_t *)ptr; + guint val = 0; + if (obj->principal) { + val ^= g_int64_hash(obj->principal); + } + if (obj->label) { + val ^= g_variant_hash(obj->label); + } + return val; +} + +gboolean cose_param_key_equal(gconstpointer a, gconstpointer b) { + const cose_param_key_t *aobj = (const cose_param_key_t *)a; + const cose_param_key_t *bobj = (const cose_param_key_t *)b; + + if (aobj->principal && bobj->principal) { + if (!g_variant_equal(aobj->principal, bobj->principal)) { + return FALSE; + } + } + else if ((aobj->principal == NULL) != (bobj->principal == NULL)) { + return FALSE; + } + + gboolean match; + if (aobj->label && bobj->label) { + match = g_variant_equal(aobj->label, bobj->label); + } + else { + // don't care + match = FALSE; + } + return match; +} + +/** Dissect an ID-value pair within a context. + * + * @param dis_table The cose_param_key_t dissector table. + * @param[in,out] ctx The context from other pairs. + */ +static void dissect_header_pair(dissector_table_t dis_table, cose_header_context_t *ctx, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk_label = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + + proto_item *item_label = NULL; + cose_param_key_t *key = g_new0(cose_param_key_t, 1); + + switch (chunk_label->type_major) { + case CBOR_TYPE_UINT: + case CBOR_TYPE_NEGINT: { + gint64 *label = wscbor_require_int64(wmem_packet_scope(), chunk_label); + item_label = proto_tree_add_cbor_int64(tree, hf_hdr_label_int, pinfo, tvb, chunk_label, label); + if (label) { + key->label = ctx->label = + g_variant_new_int64(*label); + } + break; + } + case CBOR_TYPE_STRING: { + const char *label = wscbor_require_tstr(wmem_packet_scope(), tvb, chunk_label); + item_label = proto_tree_add_cbor_tstr(tree, hf_hdr_label_tstr, pinfo, tvb, chunk_label); + if (label) { + key->label = ctx->label = + g_variant_new_string(label); + } + break; + } + default: + break; + } + + // First attempt with context then without + key->principal = ctx->principal; + dissector_handle_t dissector = dissector_get_custom_table_handle(dis_table, key); + if (!dissector) { + key->principal = NULL; + dissector = dissector_get_custom_table_handle(dis_table, key); + } + proto_tree *tree_label = proto_item_add_subtree(item_label, ett_hdr_label); + + // Peek into the value as tvb + const gint offset_value = *offset; + wscbor_skip_next_item(wmem_packet_scope(), tvb, offset); + tvbuff_t *tvb_value = tvb_new_subset_length(tvb, offset_value, *offset - offset_value); + + gint sublen = 0; + if (dissector) { + sublen = call_dissector_with_data(dissector, tvb_value, pinfo, tree_label, ctx); + if ((sublen < 0) || ((guint)sublen < tvb_captured_length(tvb_value))) { + expert_add_info(pinfo, proto_tree_get_parent(tree), &ei_value_partial_decode); + } + } + if (ctx->label) { + g_variant_unref(ctx->label); + ctx->label = NULL; + } + g_free(key); + if (sublen == 0) { + TRY { + sublen = call_dissector(handle_cbor, tvb_value, pinfo, tree_label); + } + CATCH_ALL {} + ENDTRY; + } +} + +/** Dissect an entire header map, either for messages, recipients, or keys. + * + * @param dis_table The cose_param_key_t dissector table. + * @param tvb The source data. + * @param tree The parent of the header map. + * @param[in,out] offset The data offset. + * @return The total length dissected, or -1 if failed. + */ +static void dissect_header_map(dissector_table_t dis_table, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk_hdr_map = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + wscbor_require_map(chunk_hdr_map); + proto_item *item_hdr_map = proto_tree_get_parent(tree); + wscbor_chunk_mark_errors(pinfo, item_hdr_map, chunk_hdr_map); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, offset, chunk_hdr_map)) { + proto_tree *tree_hdr_map = proto_item_add_subtree(item_hdr_map, ett_hdr_map); + + cose_header_context_t *ctx = wmem_new0(wmem_packet_scope(), cose_header_context_t); + + for (guint64 ix = 0; ix < chunk_hdr_map->head_value; ++ix) { + dissect_header_pair(dis_table, ctx, tvb, pinfo, tree_hdr_map, offset); + } + + if (ctx->principal) { + g_variant_unref(ctx->principal); + } + wmem_free(wmem_packet_scope(), ctx); + } + + proto_item_set_len(item_hdr_map, *offset - chunk_hdr_map->start); +} + +int dissect_cose_msg_header_map(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + dissect_header_map(table_header, tvb, pinfo, tree, &offset); + return offset; +} + +/** Indicate the tag which informed the message type. + */ +static void dissect_msg_tag(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree_msg, const wscbor_chunk_t *chunk_msg _U_, const void *data) { + if (!data) { + return; + } + const wscbor_tag_t *tag = (const wscbor_tag_t *)data; + + proto_tree_add_uint64(tree_msg, hf_msg_tag, tvb, tag->start, tag->length, tag->value); +} + +/** Common behavior for pair of header maps. + */ +static void dissect_headers(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + // Protected in bstr + wscbor_chunk_t *chunk_prot_bstr = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + tvbuff_t *tvb_prot = wscbor_require_bstr(tvb, chunk_prot_bstr); + proto_item *item_prot_bstr = proto_tree_add_cbor_bstr(tree, hf_hdr_prot_bstr, pinfo, tvb, chunk_prot_bstr); + if (tvb_prot) { + proto_tree *tree_prot = proto_item_add_subtree(item_prot_bstr, ett_prot_bstr); + + if (tvb_reported_length(tvb_prot) > 0) { + dissect_cose_msg_header_map(tvb_prot, pinfo, tree_prot, NULL); + } + } + + // Unprotected + tvbuff_t *tvb_unprot = tvb_new_subset_remaining(tvb, *offset); + proto_item *item_unprot = proto_tree_add_item(tree, hf_hdr_unprot, tvb_unprot, 0, -1, ENC_NA); + proto_tree *tree_unprot = proto_item_add_subtree(item_unprot, ett_unprot); + const int sublen = dissect_cose_msg_header_map(tvb_unprot, pinfo, tree_unprot, NULL); + *offset += sublen; + proto_item_set_len(item_unprot, sublen); +} + +/** Common behavior for payload. + */ +static void dissect_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + if (chunk->type_major == CBOR_TYPE_FLOAT_CTRL) { + proto_tree_add_cbor_ctrl(tree, hf_payload_null, pinfo, tvb, chunk); + } + else { + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_payload_bstr, pinfo, tvb, chunk); + } +} +static void dissect_signature(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_signature, pinfo, tvb, chunk); +} +static void dissect_cose_signature(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk_sig = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + wscbor_require_array_size(chunk_sig, 3, 3); + proto_item *item_sig = proto_tree_add_cbor_container(tree, hf_cose_signature, pinfo, tvb, chunk_sig); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, offset, chunk_sig)) { + proto_tree *tree_sig = proto_item_add_subtree(item_sig, ett_sig); + + dissect_headers(tvb, pinfo, tree_sig, offset); + dissect_signature(tvb, pinfo, tree_sig, offset); + } + proto_item_set_len(item_sig, *offset - chunk_sig->start); +} +static void dissect_ciphertext(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + if (chunk->type_major == CBOR_TYPE_FLOAT_CTRL) { + proto_tree_add_cbor_ctrl(tree, hf_ciphertext_null, pinfo, tvb, chunk); + } + else { + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_ciphertext_bstr, pinfo, tvb, chunk); + } +} +static void dissect_cose_recipient(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset); +static void dissect_cose_recipient_list(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk_list = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + wscbor_require_array(chunk_list); + proto_item *item_list = proto_tree_add_cbor_container(tree, hf_cose_recipient_list, pinfo, tvb, chunk_list); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, offset, chunk_list)) { + proto_tree *tree_recip_list = proto_item_add_subtree(item_list, ett_recip_list); + + for (guint64 ix = 0; ix < chunk_list->head_value; ++ix) { + dissect_cose_recipient(tvb, pinfo, tree_recip_list, offset); + } + } + proto_item_set_len(item_list, *offset - chunk_list->start); +} +static void dissect_cose_recipient(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk_recip = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + wscbor_require_array_size(chunk_recip, 3, 4); + proto_item *item_recip = proto_tree_add_cbor_container(tree, hf_cose_recipient, pinfo, tvb, chunk_recip); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, offset, chunk_recip)) { + proto_tree *tree_recip = proto_item_add_subtree(item_recip, ett_recip); + + dissect_headers(tvb, pinfo, tree_recip, offset); + dissect_ciphertext(tvb, pinfo, tree_recip, offset); + if (chunk_recip->head_value > 3) { + dissect_cose_recipient_list(tvb, pinfo, tree_recip, offset); + } + } + proto_item_set_len(item_recip, *offset - chunk_recip->start); + +} +static void dissect_tag(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_tag, pinfo, tvb, chunk); +} + +// Top-level protocol dissectors +static int dissect_cose_sign(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + gint offset = 0; + + wscbor_chunk_t *chunk_msg = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array_size(chunk_msg, 4, 4); + proto_item *item_msg = proto_tree_add_cbor_container(tree, proto_cose, pinfo, tvb, chunk_msg); + proto_item_append_text(item_msg, ": COSE_Sign"); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_msg)) { + proto_tree *tree_msg = proto_item_add_subtree(item_msg, ett_msg); + dissect_msg_tag(tvb, pinfo, tree_msg, chunk_msg, data); + + dissect_headers(tvb, pinfo, tree_msg, &offset); + dissect_payload(tvb, pinfo, tree_msg, &offset); + + wscbor_chunk_t *chunk_sig_list = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array(chunk_sig_list); + proto_item *item_sig_list = proto_tree_add_cbor_container(tree_msg, hf_cose_signature_list, pinfo, tvb, chunk_sig_list); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_sig_list)) { + proto_tree *tree_sig_list = proto_item_add_subtree(item_sig_list, ett_sig_list); + + for (guint64 ix = 0; ix < chunk_sig_list->head_value; ++ix) { + dissect_cose_signature(tvb, pinfo, tree_sig_list, &offset); + } + } + proto_item_set_len(item_sig_list, offset - chunk_sig_list->start); + } + + return offset; +} +static int dissect_cose_sign1(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + gint offset = 0; + + wscbor_chunk_t *chunk_msg = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array_size(chunk_msg, 4, 4); + proto_item *item_msg = proto_tree_add_cbor_container(tree, proto_cose, pinfo, tvb, chunk_msg); + proto_item_append_text(item_msg, ": COSE_Sign1"); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_msg)) { + proto_tree *tree_msg = proto_item_add_subtree(item_msg, ett_msg); + dissect_msg_tag(tvb, pinfo, tree_msg, chunk_msg, data); + + dissect_headers(tvb, pinfo, tree_msg, &offset); + dissect_payload(tvb, pinfo, tree_msg, &offset); + dissect_signature(tvb, pinfo, tree_msg, &offset); + } + + return offset; +} +static int dissect_cose_encrypt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + gint offset = 0; + + wscbor_chunk_t *chunk_msg = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array_size(chunk_msg, 4, 4); + proto_item *item_msg = proto_tree_add_cbor_container(tree, proto_cose, pinfo, tvb, chunk_msg); + proto_item_append_text(item_msg, ": COSE_Encrypt"); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_msg)) { + proto_tree *tree_msg = proto_item_add_subtree(item_msg, ett_msg); + dissect_msg_tag(tvb, pinfo, tree_msg, chunk_msg, data); + + dissect_headers(tvb, pinfo, tree_msg, &offset); + dissect_ciphertext(tvb, pinfo, tree_msg, &offset); + dissect_cose_recipient_list(tvb, pinfo, tree_msg, &offset); + } + + return offset; +} +static int dissect_cose_encrypt0(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + gint offset = 0; + + wscbor_chunk_t *chunk_msg = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array_size(chunk_msg, 3, 3); + proto_item *item_msg = proto_tree_add_cbor_container(tree, proto_cose, pinfo, tvb, chunk_msg); + proto_item_append_text(item_msg, ": COSE_Encrypt0"); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_msg)) { + proto_tree *tree_msg = proto_item_add_subtree(item_msg, ett_msg); + dissect_msg_tag(tvb, pinfo, tree_msg, chunk_msg, data); + + dissect_headers(tvb, pinfo, tree_msg, &offset); + dissect_ciphertext(tvb, pinfo, tree_msg, &offset); + } + + return offset; +} +static int dissect_cose_mac(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + gint offset = 0; + + wscbor_chunk_t *chunk_msg = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array_size(chunk_msg, 5, 5); + proto_item *item_msg = proto_tree_add_cbor_container(tree, proto_cose, pinfo, tvb, chunk_msg); + proto_item_append_text(item_msg, ": COSE_Mac"); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_msg)) { + proto_tree *tree_msg = proto_item_add_subtree(item_msg, ett_msg); + dissect_msg_tag(tvb, pinfo, tree_msg, chunk_msg, data); + + dissect_headers(tvb, pinfo, tree_msg, &offset); + dissect_payload(tvb, pinfo, tree_msg, &offset); + dissect_tag(tvb, pinfo, tree_msg, &offset); + dissect_cose_recipient_list(tvb, pinfo, tree_msg, &offset); + } + + return offset; +} +static int dissect_cose_mac0(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + gint offset = 0; + + wscbor_chunk_t *chunk_msg = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array_size(chunk_msg, 4, 4); + proto_item *item_msg = proto_tree_add_cbor_container(tree, proto_cose, pinfo, tvb, chunk_msg); + proto_item_append_text(item_msg, ": COSE_Mac0"); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_msg)) { + proto_tree *tree_msg = proto_item_add_subtree(item_msg, ett_msg); + dissect_msg_tag(tvb, pinfo, tree_msg, chunk_msg, data); + + dissect_headers(tvb, pinfo, tree_msg, &offset); + dissect_payload(tvb, pinfo, tree_msg, &offset); + dissect_tag(tvb, pinfo, tree_msg, &offset); + } + + return offset; +} + +/** Dissect a tagged COSE message. + */ +int dissect_cose_msg_tagged(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + // All messages have the same base structure, attempt all tags present + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + for (wmem_list_frame_t *it = wmem_list_head(chunk->tags); it; + it = wmem_list_frame_next(it)) { + wscbor_tag_t *tag = (wscbor_tag_t *) wmem_list_frame_data(it); + // first usable tag wins + dissector_handle_t dissector = dissector_get_custom_table_handle(table_cose_msg_tag, &(tag->value)); + if (!dissector) { + continue; + } + g_log(LOG_DOMAIN, G_LOG_LEVEL_INFO, "main dissector using tag %" PRIu64, tag->value); + int sublen = call_dissector_with_data(dissector, tvb, pinfo, tree, tag); + if (sublen > 0) { + return sublen; + } + } + + g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "main dissector did not match any known tag"); + proto_item *item_msg = proto_tree_add_item(tree, proto_cose, tvb, 0, -1, 0); + expert_add_info(pinfo, item_msg, &ei_invalid_tag); + return -1; +} + +void dissect_value_alg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset, GVariant **value) { + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + switch (chunk->type_major) { + case CBOR_TYPE_UINT: + case CBOR_TYPE_NEGINT: { + gint64 *val = wscbor_require_int64(wmem_packet_scope(), chunk); + proto_tree_add_cbor_int64(tree, hf_hdr_alg_int, pinfo, tvb, chunk, val); + if (value && val) { + *value = g_variant_new_int64(*val); + } + break; + } + case CBOR_TYPE_STRING: { + const char *val = wscbor_require_tstr(wmem_packet_scope(), tvb, chunk); + proto_tree_add_cbor_tstr(tree, hf_hdr_alg_tstr, pinfo, tvb, chunk); + if (value && val) { + *value = g_variant_new_string(val); + } + break; + } + default: + break; + } +} + +static int dissect_header_salt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_hdr_salt, pinfo, tvb, chunk); + + return offset; +} + +static void dissect_value_cose_key(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + dissect_header_map(table_keyparam, tvb, pinfo, tree, offset); +} + +static int dissect_header_static_key(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + proto_item *item_ctr = proto_tree_add_item(tree, hf_hdr_static_key, tvb, 0, -1, ENC_NA); + proto_tree *tree_ctr = proto_item_add_subtree(item_ctr, ett_hdr_static_key); + dissect_value_cose_key(tvb, pinfo, tree_ctr, &offset); + return offset; +} + +static int dissect_header_ephem_key(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + proto_item *item_ctr = proto_tree_add_item(tree, hf_hdr_ephem_key, tvb, 0, -1, ENC_NA); + proto_tree *tree_ctr = proto_item_add_subtree(item_ctr, ett_hdr_ephem_key); + dissect_value_cose_key(tvb, pinfo, tree_ctr, &offset); + return offset; +} + +static int dissect_header_alg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + cose_header_context_t *ctx = (cose_header_context_t *)data; + gint offset = 0; + + dissect_value_alg(tvb, pinfo, tree, &offset, &(ctx->principal)); + + return offset; +} + +static int dissect_header_crit(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk_list = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array(chunk_list); + proto_item *item_list = proto_tree_add_cbor_container(tree, hf_hdr_crit_list, pinfo, tvb, chunk_list); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_list)) { + proto_tree *tree_list = proto_item_add_subtree(item_list, ett_hdr_crit_list); + + for (guint64 ix = 0; ix < chunk_list->head_value; ++ix) { + wscbor_chunk_t *chunk_label = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + switch (chunk_label->type_major) { + case CBOR_TYPE_UINT: + case CBOR_TYPE_NEGINT: { + gint64 *label = wscbor_require_int64(wmem_packet_scope(), chunk_label); + proto_tree_add_cbor_int64(tree_list, hf_hdr_label_int, pinfo, tvb, chunk_label, label); + break; + } + case CBOR_TYPE_STRING: { + proto_tree_add_cbor_tstr(tree_list, hf_hdr_label_tstr, pinfo, tvb, chunk_label); + break; + } + default: + break; + } + } + } + + proto_item_set_len(item_list, offset); + return offset; +} + +static int dissect_header_ctype(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + switch (chunk->type_major) { + case CBOR_TYPE_UINT: { + guint64 *val = wscbor_require_uint64(wmem_packet_scope(), chunk); + proto_tree_add_cbor_uint64(tree, hf_hdr_ctype_uint, pinfo, tvb, chunk, val); + break; + } + case CBOR_TYPE_STRING: { + proto_tree_add_cbor_tstr(tree, hf_hdr_ctype_tstr, pinfo, tvb, chunk); + break; + } + default: + break; + } + + return offset; +} + +static int dissect_header_kid(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_hdr_kid, pinfo, tvb, chunk); + + return offset; +} + +static int dissect_header_iv(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_hdr_iv, pinfo, tvb, chunk); + + return offset; +} + +static int dissect_header_piv(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_hdr_piv, pinfo, tvb, chunk); + + return offset; +} + +static void dissect_value_x5cert(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint *offset) { + wscbor_chunk_t *chunk_item = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + tvbuff_t *tvb_item = wscbor_require_bstr(tvb, chunk_item); + + if (tvb_item) { + // disallow column text rewrite + gchar *info_text = g_strdup(col_get_text(pinfo->cinfo, COL_INFO)); + + TRY { + dissector_try_string( + table_media, + "application/pkix-cert", + tvb_item, + pinfo, + tree, + NULL + ); + } + CATCH_ALL {} + ENDTRY; + + col_add_str(pinfo->cinfo, COL_INFO, info_text); + g_free(info_text); + } + +} +static void dissect_value_cosex509(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int hfindex, gint *offset) { + proto_item *item_ctr = proto_tree_add_item(tree, hfindex, tvb, 0, -1, ENC_NA); + proto_tree *tree_ctr = proto_item_add_subtree(item_ctr, ett_hdr_x5cert_list); + + wscbor_chunk_t *chunk_ctr = wscbor_chunk_read(wmem_packet_scope(), tvb, offset); + switch (chunk_ctr->type_major) { + case CBOR_TYPE_ARRAY: { + wscbor_require_array(chunk_ctr); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, offset, chunk_ctr)) { + for (guint64 ix = 0; ix < chunk_ctr->head_value; ++ix) { + dissect_value_x5cert(tvb, pinfo, tree_ctr, offset); + } + } + break; + } + case CBOR_TYPE_BYTESTRING: { + // re-read this chunk as cert + *offset = chunk_ctr->start; + dissect_value_x5cert(tvb, pinfo, tree_ctr, offset); + break; + } + default: + break; + } + +} +static int dissect_header_x5bag(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + dissect_value_cosex509(tvb, pinfo, tree, hf_hdr_x5bag, &offset); + return offset; +} +static int dissect_header_x5chain(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + dissect_value_cosex509(tvb, pinfo, tree, hf_hdr_x5chain, &offset); + return offset; +} + +static int dissect_header_x5t(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk_list = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array_size(chunk_list, 2, 2); + proto_item *item_list = proto_tree_add_cbor_container(tree, hf_hdr_x5t, pinfo, tvb, chunk_list); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_list)) { + proto_tree *tree_list = proto_item_add_subtree(item_list, ett_hdr_x5t_list); + + dissect_value_alg(tvb, pinfo, tree_list, &offset, NULL); + + wscbor_chunk_t *chunk_hash = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk_hash); + proto_tree_add_cbor_bstr(tree_list, hf_hdr_x5t_hash, pinfo, tvb, chunk_hash); + + } + + return offset; +} + +static int dissect_header_x5u(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_major_type(chunk, CBOR_TYPE_STRING); + proto_tree_add_cbor_tstr(tree, hf_hdr_x5u, pinfo, tvb, chunk); + + return offset; +} + +static int dissect_cose_key(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + proto_item *item_msg = proto_tree_add_item(tree, proto_cose, tvb, 0, -1, 0); + proto_item_append_text(item_msg, ": COSE_Key"); + + dissect_value_cose_key(tvb, pinfo, tree, &offset); + + return offset; +} + +static int dissect_cose_key_set(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk_set = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array(chunk_set); + proto_item *item_set = proto_tree_add_cbor_container(tree, proto_cose, pinfo, tvb, chunk_set); + proto_item_append_text(item_set, ": COSE_KeySet"); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_set)) { + proto_tree *tree_set = proto_item_add_subtree(item_set, ett_key_set); + + for (guint64 ix = 0; ix < chunk_set->head_value; ++ix) { + proto_item *item_key = proto_tree_add_item(tree_set, hf_key, tvb, offset, -1, ENC_NA); + proto_tree *tree_key = proto_item_add_subtree(item_key, ett_key); + + const gint offset_key = offset; + dissect_value_cose_key(tvb, pinfo, tree_key, &offset); + proto_item_set_len(item_key, offset - offset_key); + } + } + + proto_item_set_len(item_set, offset); + return offset; +} + +static int dissect_keyparam_kty(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + cose_header_context_t *ctx = (cose_header_context_t *)data; + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + switch (chunk->type_major) { + case CBOR_TYPE_UINT: + case CBOR_TYPE_NEGINT: { + gint64 *val = wscbor_require_int64(wmem_packet_scope(), chunk); + proto_tree_add_cbor_int64(tree, hf_keyparam_kty_int, pinfo, tvb, chunk, val); + if (val) { + ctx->principal = g_variant_new_int64(*val); + } + break; + } + case CBOR_TYPE_STRING: { + const char *val = wscbor_require_tstr(wmem_packet_scope(), tvb, chunk); + proto_tree_add_cbor_tstr(tree, hf_keyparam_kty_tstr, pinfo, tvb, chunk); + if (val) { + ctx->principal = g_variant_new_string(val); + } + break; + } + default: + break; + } + + return offset; +} + +static int dissect_keyparam_keyops(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk_list = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_array(chunk_list); + proto_item *item_list = proto_tree_add_cbor_container(tree, hf_keyparam_keyops_list, pinfo, tvb, chunk_list); + if (!wscbor_skip_if_errors(wmem_packet_scope(), tvb, &offset, chunk_list)) { + proto_tree *tree_list = proto_item_add_subtree(item_list, ett_keyops_list); + + for (guint64 ix = 0; ix < chunk_list->head_value; ++ix) { + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + switch (chunk->type_major) { + case CBOR_TYPE_UINT: + case CBOR_TYPE_NEGINT: { + gint64 *val = wscbor_require_int64(wmem_packet_scope(), chunk); + proto_tree_add_cbor_int64(tree_list, hf_keyparam_keyops_int, pinfo, tvb, chunk, val); + break; + } + case CBOR_TYPE_STRING: { + proto_tree_add_cbor_tstr(tree_list, hf_keyparam_keyops_tstr, pinfo, tvb, chunk); + break; + } + default: + break; + } + } + } + proto_item_set_len(item_list, offset - chunk_list->start); + + return offset; +} + +static int dissect_keyparam_baseiv(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_keyparam_baseiv, pinfo, tvb, chunk); + + return offset; +} + +static int dissect_keyparam_crv(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + switch (chunk->type_major) { + case CBOR_TYPE_UINT: + case CBOR_TYPE_NEGINT: { + gint64 *val = wscbor_require_int64(wmem_packet_scope(), chunk); + proto_tree_add_cbor_int64(tree, hf_keyparam_crv_int, pinfo, tvb, chunk, val); + break; + } + case CBOR_TYPE_STRING: { + proto_tree_add_cbor_tstr(tree, hf_keyparam_crv_tstr, pinfo, tvb, chunk); + break; + } + default: + break; + } + + return offset; +} + +static int dissect_keyparam_xcoord(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_keyparam_xcoord, pinfo, tvb, chunk); + + return offset; +} + +static int dissect_keyparam_ycoord(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + switch (chunk->type_major) { + case CBOR_TYPE_FLOAT_CTRL: { + proto_tree_add_item(tree, hf_keyparam_ycoord, tvb, 0, 0, ENC_NA); + break; + } + case CBOR_TYPE_BYTESTRING: { + proto_tree_add_cbor_bstr(tree, hf_keyparam_ycoord, pinfo, tvb, chunk); + break; + } + default: + break; + } + + return offset; +} + +static int dissect_keyparam_dcoord(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_keyparam_dcoord, pinfo, tvb, chunk); + + return offset; +} + +static int dissect_keyparam_k(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { + gint offset = 0; + + wscbor_chunk_t *chunk = wscbor_chunk_read(wmem_packet_scope(), tvb, &offset); + wscbor_require_bstr(tvb, chunk); + proto_tree_add_cbor_bstr(tree, hf_keyparam_k, pinfo, tvb, chunk); + + return offset; +} + + +/** Register a message dissector. + */ +static void register_msg_dissector(dissector_handle_t dis_h, guint64 tag_int, const char *media) { + guint64 *key_int = g_new(guint64, 1); + *key_int = tag_int; + dissector_add_custom_table_handle("cose.msgtag", key_int, dis_h); + + if (media) { + dissector_add_string("media_type", media, dis_h); + } +} + +/** Register a header dissector. + */ +static void register_header_dissector(dissector_t dissector, GVariant *label) { + dissector_handle_t dis_h = create_dissector_handle(dissector, proto_cose); + + cose_param_key_t *key = g_new0(cose_param_key_t, 1); + key->label = label; + + dissector_add_custom_table_handle("cose.header", key, dis_h); +} + +/** Register a key parameter dissector. + * @param dissector The dissector function. + * @param kty The associated key type "kty" or NULL. + */ +static void register_keyparam_dissector(dissector_t dissector, GVariant *kty, GVariant *label) { + dissector_handle_t dis_h = create_dissector_handle(dissector, proto_cose); + + cose_param_key_t *key = g_new0(cose_param_key_t, 1); + if (kty) { + g_variant_ref(kty); + key->principal = kty; + } + key->label = label; + + dissector_add_custom_table_handle("cose.keyparam", key, dis_h); +} + +/// Initialize for a new file load +static void cose_init(void) { +} + +/// Cleanup after a file +static void cose_cleanup(void) { +} + +/// Re-initialize after a configuration change +static void cose_reinit(void) { +} + +/// Overall registration of the protocol +void proto_register_cose(void) { + proto_cose = proto_register_protocol( + "CBOR Object Signing and Encryption", /* name */ + proto_name_cose, /* short name */ + "cose" /* abbrev */ + ); + register_init_routine(&cose_init); + register_cleanup_routine(&cose_cleanup); + + proto_register_field_array(proto_cose, fields, array_length(fields)); + proto_register_subtree_array(ett, array_length(ett)); + expert_module_t *expert = expert_register_protocol(proto_cose); + expert_register_field_array(expert, expertitems, array_length(expertitems)); + + handle_cose_msg_hdr = register_dissector("cose.msg.headers", dissect_cose_msg_header_map, proto_cose); + + table_cose_msg_tag = register_custom_dissector_table("cose.msgtag", "COSE Message Tag", proto_cose, g_int64_hash, g_int64_equal); + handle_cose_msg_tagged = register_dissector("cose", dissect_cose_msg_tagged, proto_cose); + handle_cose_sign = register_dissector("cose_sign", dissect_cose_sign, proto_cose); + handle_cose_sign1 = register_dissector("cose_sign1", dissect_cose_sign1, proto_cose); + handle_cose_encrypt = register_dissector("cose_encrypt", dissect_cose_encrypt, proto_cose); + handle_cose_encrypt0 = register_dissector("cose_encrypt0", dissect_cose_encrypt0, proto_cose); + handle_cose_mac = register_dissector("cose_mac", dissect_cose_mac, proto_cose); + handle_cose_mac0 = register_dissector("cose_mac0", dissect_cose_mac0, proto_cose); + + table_header = register_custom_dissector_table("cose.header", "COSE Header Parameter", proto_cose, cose_param_key_hash, cose_param_key_equal); + + handle_cose_key = register_dissector("cose_key", dissect_cose_key, proto_cose); + handle_cose_key_set = register_dissector("cose_key_set", dissect_cose_key_set, proto_cose); + + table_keyparam = register_custom_dissector_table("cose.keyparam", "COSE Key Parameter", proto_cose, cose_param_key_hash, cose_param_key_equal); + + module_t *module_cose = prefs_register_protocol(proto_cose, cose_reinit); + (void)module_cose; +} + +void proto_reg_handoff_cose(void) { + table_media = find_dissector_table("media_type"); + handle_cbor = find_dissector("cbor"); + + dissector_add_string("media_type", "application/cose", handle_cose_msg_tagged); + // RFC 8152 tags and names (Table 26) + register_msg_dissector(handle_cose_sign, 98, "application/cose; cose-type=\"cose-sign\""); + register_msg_dissector(handle_cose_sign1, 18, "application/cose; cose-type=\"cose-sign1\""); + register_msg_dissector(handle_cose_encrypt, 96, "application/cose; cose-type=\"cose-encrypt\""); + register_msg_dissector(handle_cose_encrypt0, 16, "application/cose; cose-type=\"cose-encrypt0\""); + register_msg_dissector(handle_cose_mac, 97, "application/cose; cose-type=\"cose-mac\""); + register_msg_dissector(handle_cose_mac0, 17, "application/cose; cose-type=\"cose-mac0\""); + + // RFC 8152 header labels + register_header_dissector(dissect_header_salt, g_variant_new_int64(-20)); + register_header_dissector(dissect_header_static_key, g_variant_new_int64(-2)); + register_header_dissector(dissect_header_ephem_key, g_variant_new_int64(-1)); + register_header_dissector(dissect_header_alg, g_variant_new_int64(1)); + register_header_dissector(dissect_header_crit, g_variant_new_int64(2)); + register_header_dissector(dissect_header_ctype, g_variant_new_int64(3)); + register_header_dissector(dissect_header_kid, g_variant_new_int64(4)); + register_header_dissector(dissect_header_iv, g_variant_new_int64(5)); + register_header_dissector(dissect_header_piv, g_variant_new_int64(6)); + // draft-ietf-cose-x509 header labels + register_header_dissector(dissect_header_x5bag, g_variant_new_int64(32)); + register_header_dissector(dissect_header_x5chain, g_variant_new_int64(33)); + register_header_dissector(dissect_header_x5t, g_variant_new_int64(34)); + register_header_dissector(dissect_header_x5u, g_variant_new_int64(35)); + + dissector_add_string("media_type", "application/cose-key", handle_cose_key); + dissector_add_string("media_type", "application/cose-key-set", handle_cose_key_set); + // RFC 8152 key parameter labels + register_keyparam_dissector(dissect_keyparam_kty, NULL, g_variant_new_int64(1)); + register_keyparam_dissector(dissect_header_kid, NULL, g_variant_new_int64(2)); + register_keyparam_dissector(dissect_header_alg, NULL, g_variant_new_int64(3)); + register_keyparam_dissector(dissect_keyparam_keyops, NULL, g_variant_new_int64(4)); + register_keyparam_dissector(dissect_keyparam_baseiv, NULL, g_variant_new_int64(5)); + // kty-specific parameters + { + GVariant *kty = g_variant_new_int64(1); + register_keyparam_dissector(dissect_keyparam_crv, kty, g_variant_new_int64(-1)); + register_keyparam_dissector(dissect_keyparam_xcoord, kty, g_variant_new_int64(-2)); + register_keyparam_dissector(dissect_keyparam_dcoord, kty, g_variant_new_int64(-3)); + } + { + GVariant *kty = g_variant_new_int64(2); + register_keyparam_dissector(dissect_keyparam_crv, kty, g_variant_new_int64(-1)); + register_keyparam_dissector(dissect_keyparam_xcoord, kty, g_variant_new_int64(-2)); + register_keyparam_dissector(dissect_keyparam_ycoord, kty, g_variant_new_int64(-3)); + register_keyparam_dissector(dissect_keyparam_dcoord, kty, g_variant_new_int64(-4)); + } + { + GVariant *kty = g_variant_new_int64(4); + register_keyparam_dissector(dissect_keyparam_k, kty, g_variant_new_int64(-1)); + } + + cose_reinit(); +} diff --git a/epan/dissectors/packet-cose.h b/epan/dissectors/packet-cose.h new file mode 100644 index 0000000000..47d6de8e30 --- /dev/null +++ b/epan/dissectors/packet-cose.h @@ -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 + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef __PACKET_COSE_H__ +#define __PACKET_COSE_H__ + +#include + +/** + * 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__ */ diff --git a/epan/epan.c b/epan/epan.c index fb0ddc0c44..1b4f2aa2df 100644 --- a/epan/epan.c +++ b/epan/epan.c @@ -58,6 +58,7 @@ #include "stats_tree.h" #include "secrets.h" #include "funnel.h" +#include "wscbor.h" #include #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(); diff --git a/epan/wscbor.c b/epan/wscbor.c new file mode 100644 index 0000000000..e303071e36 --- /dev/null +++ b/epan/wscbor.c @@ -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 + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include +#include +#include +#include +#include +#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; +} diff --git a/epan/wscbor.h b/epan/wscbor.h new file mode 100644 index 0000000000..0687afaff5 --- /dev/null +++ b/epan/wscbor.h @@ -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 + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __WSCBOR_H__ +#define __WSCBOR_H__ + +#include +#include +#include +#include +#include + +#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__ */ diff --git a/epan/wscbor_test.c b/epan/wscbor_test.c new file mode 100644 index 0000000000..13a46cd709 --- /dev/null +++ b/epan/wscbor_test.c @@ -0,0 +1,551 @@ +/* wscbor_test.c + * Wireshark CBOR API tests + * Copyright 2021, Brian Sipos + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#undef G_DISABLE_ASSERT + +#include +#include +#include + +#include "wscbor.h" +#include +#include +#include + +#include + +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: + */ diff --git a/test/captures/cose_encrypt0_tagged.cbordiag b/test/captures/cose_encrypt0_tagged.cbordiag new file mode 100644 index 0000000000..36b9d62e53 --- /dev/null +++ b/test/captures/cose_encrypt0_tagged.cbordiag @@ -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' + ] +) diff --git a/test/captures/cose_encrypt0_tagged.pcap b/test/captures/cose_encrypt0_tagged.pcap new file mode 100644 index 0000000000..51d1cbf634 Binary files /dev/null and b/test/captures/cose_encrypt0_tagged.pcap differ diff --git a/test/captures/cose_encrypt_tagged.cbordiag b/test/captures/cose_encrypt_tagged.cbordiag new file mode 100644 index 0000000000..30767f9d8c --- /dev/null +++ b/test/captures/cose_encrypt_tagged.cbordiag @@ -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'' + ] + ] + ] +) diff --git a/test/captures/cose_encrypt_tagged.pcap b/test/captures/cose_encrypt_tagged.pcap new file mode 100644 index 0000000000..71f5ad8cef Binary files /dev/null and b/test/captures/cose_encrypt_tagged.pcap differ diff --git a/test/captures/cose_keyset.cbordiag b/test/captures/cose_keyset.cbordiag new file mode 100644 index 0000000000..11b1df360e --- /dev/null +++ b/test/captures/cose_keyset.cbordiag @@ -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' + } +] diff --git a/test/captures/cose_keyset.pcap b/test/captures/cose_keyset.pcap new file mode 100644 index 0000000000..5b98d31a5a Binary files /dev/null and b/test/captures/cose_keyset.pcap differ diff --git a/test/captures/cose_mac0_tagged.cbordiag b/test/captures/cose_mac0_tagged.cbordiag new file mode 100644 index 0000000000..ead2910d82 --- /dev/null +++ b/test/captures/cose_mac0_tagged.cbordiag @@ -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' + ] +) diff --git a/test/captures/cose_mac0_tagged.pcap b/test/captures/cose_mac0_tagged.pcap new file mode 100644 index 0000000000..0454b19cf4 Binary files /dev/null and b/test/captures/cose_mac0_tagged.pcap differ diff --git a/test/captures/cose_mac_tagged.cbordiag b/test/captures/cose_mac_tagged.cbordiag new file mode 100644 index 0000000000..91d9b89bbb --- /dev/null +++ b/test/captures/cose_mac_tagged.cbordiag @@ -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' + ] + ] + ] +) diff --git a/test/captures/cose_mac_tagged.pcap b/test/captures/cose_mac_tagged.pcap new file mode 100644 index 0000000000..3c685b29e7 Binary files /dev/null and b/test/captures/cose_mac_tagged.pcap differ diff --git a/test/captures/cose_sign1_tagged.cbordiag b/test/captures/cose_sign1_tagged.cbordiag new file mode 100644 index 0000000000..64063320cd --- /dev/null +++ b/test/captures/cose_sign1_tagged.cbordiag @@ -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' + ] +) diff --git a/test/captures/cose_sign1_tagged.pcap b/test/captures/cose_sign1_tagged.pcap new file mode 100644 index 0000000000..f3c844012d Binary files /dev/null and b/test/captures/cose_sign1_tagged.pcap differ diff --git a/test/captures/cose_sign_tagged.cbordiag b/test/captures/cose_sign_tagged.cbordiag new file mode 100644 index 0000000000..92003a4c7e --- /dev/null +++ b/test/captures/cose_sign_tagged.cbordiag @@ -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' + ] + ] + ] +) diff --git a/test/captures/cose_sign_tagged.pcap b/test/captures/cose_sign_tagged.pcap new file mode 100644 index 0000000000..428acb9a2b Binary files /dev/null and b/test/captures/cose_sign_tagged.pcap differ diff --git a/test/suite_dissection.py b/test/suite_dissection.py index ba02c68be9..686a23bf59 100644 --- a/test/suite_dissection.py +++ b/test/suite_dissection.py @@ -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): diff --git a/test/suite_unittests.py b/test/suite_unittests.py index ccffbd3806..ce42697921 100644 --- a/test/suite_unittests.py +++ b/test/suite_unittests.py @@ -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'), diff --git a/tools/generate_cbor_pcap.py b/tools/generate_cbor_pcap.py new file mode 100644 index 0000000000..d515a44556 --- /dev/null +++ b/tools/generate_cbor_pcap.py @@ -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 + +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())