From ee901c58e65bb69d4e4e5b0acfaf76b70b6d86fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 20 Dec 2017 10:03:08 +0100 Subject: [PATCH] OSCORE: Add the new dissector - decrypt and verify the authenticity of requests This change introduces the OSCORE dissector, following draft-ietf-core-object-security-07. It performs decryption and authenticity check on requests. Bug: 14417 Change-Id: I92e45d66d5df51f6d4dbea4ef44e707955b65bee Reviewed-on: https://code.wireshark.org/review/25480 Petri-Dish: Peter Wu Tested-by: Petri Dish Buildbot Reviewed-by: Peter Wu --- docbook/release-notes.asciidoc | 1 + epan/dissectors/CMakeLists.txt | 1 + epan/dissectors/Makefile.am | 1 + epan/dissectors/packet-oscore.c | 855 ++++++++++++++++++++++++++++++++ epan/dissectors/packet-oscore.h | 72 +++ 5 files changed, 930 insertions(+) create mode 100644 epan/dissectors/packet-oscore.c create mode 100644 epan/dissectors/packet-oscore.h diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc index 3bcc081897..cb2cb04eee 100644 --- a/docbook/release-notes.asciidoc +++ b/docbook/release-notes.asciidoc @@ -121,6 +121,7 @@ New Radio Radio Resource Control protocol New Radio Radio Link Control protocol NR (5G) MAC protocol NXP 802.15.4 Sniffer Protocol +Object Security for Constrained RESTful Environments (OSCORE) PFCP (Packet Forwarding Control Protocol) Protobuf (Protocol Buffers) QUIC (IETF) diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index d498bbaab8..02fb8efa44 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -1446,6 +1446,7 @@ set(DISSECTOR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/packet-opsi.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-optommp.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-osc.c + ${CMAKE_CURRENT_SOURCE_DIR}/packet-oscore.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-osi-options.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-osi.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-ositp.c diff --git a/epan/dissectors/Makefile.am b/epan/dissectors/Makefile.am index 5ed965a08f..898645b7bb 100644 --- a/epan/dissectors/Makefile.am +++ b/epan/dissectors/Makefile.am @@ -1066,6 +1066,7 @@ DISSECTOR_SRC = \ packet-opsi.c \ packet-optommp.c \ packet-osc.c \ + packet-oscore.c \ packet-osi-options.c \ packet-osi.c \ packet-ositp.c \ diff --git a/epan/dissectors/packet-oscore.c b/epan/dissectors/packet-oscore.c new file mode 100644 index 0000000000..1cba852809 --- /dev/null +++ b/epan/dissectors/packet-oscore.c @@ -0,0 +1,855 @@ +/* packet-oscore.c + * Routines for Object Security for Constrained RESTful Environments dissection + * Copyright 2017, Malisa Vucinic + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * draft-ietf-core-object-security-07 + */ + +#include + +#include /* Should be first Wireshark include (other than config.h) */ +#include /* Include only as needed */ +#include +#include +#include /* Include only as needed */ +#include + +#include +#include "packet-ieee802154.h" /* We use CCM implementation available as part of 802.15.4 dissector */ +#include "packet-oscore.h" + +/* Prototypes */ +static guint oscore_alg_get_key_len(cose_aead_alg_t); +static guint oscore_alg_get_iv_len(cose_aead_alg_t); +static guint oscore_alg_get_tag_len(cose_aead_alg_t); +static gboolean oscore_context_derive_params(oscore_context_t *); + +/* CBOR encoder prototypes */ +static guint8 cborencoder_put_text(guint8 *buffer, const char *text, guint8 text_len); +static guint8 cborencoder_put_null(guint8 *buffer); +static guint8 cborencoder_put_unsigned(guint8 *buffer, guint8 value); +static guint8 cborencoder_put_bytes(guint8 *buffer, const guint8 *bytes, guint8 bytes_len); +static guint8 cborencoder_put_array(guint8 *buffer, guint8 elements); + +/* (Required to prevent [-Wmissing-prototypes] warnings */ +void proto_reg_handoff_oscore(void); +void proto_register_oscore(void); + +/* Initialize the protocol and registered fields */ +static int proto_oscore = -1; + +static int hf_oscore_coap_data = -1; +static int hf_oscore_tag = -1; + +static expert_field ei_oscore_key_id_not_found = EI_INIT; +static expert_field ei_oscore_partial_iv_not_found = EI_INIT; +static expert_field ei_oscore_context_not_set = EI_INIT; +static expert_field ei_oscore_message_too_small = EI_INIT; +static expert_field ei_oscore_truncated = EI_INIT; +static expert_field ei_oscore_tag_check_failed = EI_INIT; +static expert_field ei_oscore_decrypt_error = EI_INIT; +static expert_field ei_oscore_cbc_mac_failed = EI_INIT; +static expert_field ei_oscore_piv_len_invalid = EI_INIT; + +/* Initialize the subtree pointers */ +static gint ett_oscore = -1; + +/* UAT variables */ +static uat_t *oscore_context_uat = NULL; +static oscore_context_t *oscore_contexts = NULL; +static guint num_oscore_contexts = 0; + +/* Enumeration for COSE algorithms used by OSCORE */ +static const value_string oscore_context_alg_vals[] = { + { COSE_AES_CCM_16_64_128, "AES-CCM-16-64-128 (CCM*)"}, + { 0, NULL } +}; + +/* Field callbacks. */ +UAT_CSTRING_CB_DEF(oscore_context_uat, master_secret_prefs, oscore_context_t) +UAT_CSTRING_CB_DEF(oscore_context_uat, master_salt_prefs, oscore_context_t) +UAT_CSTRING_CB_DEF(oscore_context_uat, sender_id_prefs, oscore_context_t) +UAT_CSTRING_CB_DEF(oscore_context_uat, recipient_id_prefs, oscore_context_t) +UAT_VS_DEF(oscore_context_uat, algorithm, oscore_context_t, cose_aead_alg_t, COSE_AES_CCM_16_64_128, "AES-CCM-16-64-128 (CCM*)") + +#define OSCORE_MIN_LENGTH 9 /* 1 byte for code plus 8 bytes for shortest authentication tag */ +#define OSCORE_VERSION 1 /* draft-ietf-core-object-security-07 */ +#define TAG_MAX_LEN 16 +#define AES_128_BLOCK_LEN 16 +#define NONCE_MAX_LEN 13 /* longest nonce in RFC8152 is 13 bytes */ + +#define OSCORE_PIV_MAX_LEN 5 /* upper bound specified in the draft */ +#define OSCORE_KID_MAX_LEN_CCM_STAR 7 /* upper bound on KID for AES-CCM-16-64-128 (CCM*) */ +#define OSCORE_KID_MAX_LEN OSCORE_KID_MAX_LEN_CCM_STAR /* upper bound on KID coming from the default algorithm implemented */ + +/* Helper macros to correctly size the statically allocated buffers and verify if an overflow occured */ + +#define OSCORE_INFO_MAX_LEN (1 + /* max return of cborencoder_put_array() */ \ + 2 + OSCORE_KID_MAX_LEN + /* max 2 to encode length, KID following */ \ + 2 + /* max return of cborencoder_put_unsigned() */ \ + 2 + 3 + /* max 2 to encode length, "Key" following */ \ + 2 /* max return of cborencoder_put_unsigned() */ ) + +#define OSCORE_EXTERNAL_AAD_MAX_LEN (1 + /* max return of cborencoder_put_array() */ \ + 2 + /* max return of cborencoder_put_unsigned() */ \ + 2 + /* max return of cborencoder_put_unsigned() */ \ + 2 + OSCORE_KID_MAX_LEN + /* max 2 to encode length, KID following */ \ + 2 + OSCORE_PIV_MAX_LEN + /* max 2 to encode length, PIV following */ \ + 1 + 0 /* 1 to encode length, 0 bytes following */ ) + +#define OSCORE_AAD_MAX_LEN (1 + /* max return of cborencoder_put_array() */ \ + 2 + 8 +/* max 2 to encode length, "Encrypt0" following */ \ + 1 + 0 + /* 1 to encode length, 0 bytes following */ \ + 2 + OSCORE_EXTERNAL_AAD_MAX_LEN /* max 2 to encode length, external_aad following */ ) + +static void oscore_context_free_byte_arrays(oscore_context_t *rec) { + + if (rec->master_secret) { + g_byte_array_free(rec->master_secret, TRUE); + } + + if (rec->master_salt) { + g_byte_array_free(rec->master_salt, TRUE); + } + + if (rec->sender_id) { + g_byte_array_free(rec->sender_id, TRUE); + } + + if (rec->recipient_id) { + g_byte_array_free(rec->recipient_id, TRUE); + } + + if (rec->request_decryption_key) { + g_byte_array_free(rec->request_decryption_key, TRUE); + } + + if (rec->response_decryption_key) { + g_byte_array_free(rec->response_decryption_key, TRUE); + } + + if (rec->common_iv) { + g_byte_array_free(rec->common_iv, TRUE); + } +} + +static void oscore_context_post_update_cb(void) { + guint i; + guint key_len; + guint iv_len; + + for (i = 0; i < num_oscore_contexts; i++) { + + /* Make sure to free the memory if it was allocated previously. */ + oscore_context_free_byte_arrays(&oscore_contexts[i]); + + oscore_contexts[i].master_secret = g_byte_array_new(); + oscore_contexts[i].master_salt = g_byte_array_new(); + oscore_contexts[i].sender_id = g_byte_array_new(); + oscore_contexts[i].recipient_id = g_byte_array_new(); + + /* Convert strings to byte arrays */ + hex_str_to_bytes(oscore_contexts[i].sender_id_prefs, oscore_contexts[i].sender_id, FALSE); + hex_str_to_bytes(oscore_contexts[i].recipient_id_prefs, oscore_contexts[i].recipient_id, FALSE); + hex_str_to_bytes(oscore_contexts[i].master_secret_prefs, oscore_contexts[i].master_secret, FALSE); + hex_str_to_bytes(oscore_contexts[i].master_salt_prefs, oscore_contexts[i].master_salt, FALSE); + + /* Algorithm-dependent key and IV length */ + key_len = oscore_alg_get_key_len(oscore_contexts[i].algorithm); + iv_len = oscore_alg_get_iv_len(oscore_contexts[i].algorithm); + + /* Allocate memory for derived parameters */ + oscore_contexts[i].request_decryption_key = g_byte_array_sized_new(key_len); + oscore_contexts[i].response_decryption_key = g_byte_array_sized_new(key_len); + oscore_contexts[i].common_iv = g_byte_array_sized_new(iv_len); + + oscore_context_derive_params(&oscore_contexts[i]); + } +} + +/* Check user input, do not allocate any memory */ +static gboolean oscore_context_update_cb(void *r, char **err) { + oscore_context_t *rec = (oscore_context_t *) r; + GByteArray *bytes; /* temp array to verify each parameter */ + + bytes = g_byte_array_new(); + + if (hex_str_to_bytes(rec->sender_id_prefs, bytes, FALSE) == FALSE) { + *err = g_strdup("Sender ID is invalid."); + g_byte_array_free(bytes, TRUE); + return FALSE; + } + + if (bytes->len == 0 || bytes->len > OSCORE_KID_MAX_LEN) { + *err = g_strdup_printf("Sender ID is mandatory. Should be %u bytes or less.", OSCORE_KID_MAX_LEN); + g_byte_array_free(bytes, TRUE); + return FALSE; + } + + if (hex_str_to_bytes(rec->recipient_id_prefs, bytes, FALSE) == FALSE) { + *err = g_strdup("Recipient ID is invalid."); + g_byte_array_free(bytes, TRUE); + return FALSE; + } + + if (bytes->len == 0 || bytes->len > OSCORE_KID_MAX_LEN) { + *err = g_strdup_printf("Recipient ID is mandatory. Should be %u bytes or less.", OSCORE_KID_MAX_LEN); + g_byte_array_free(bytes, TRUE); + return FALSE; + } + + if (hex_str_to_bytes(rec->master_secret_prefs, bytes, FALSE) == FALSE) { + *err = g_strdup("Master Secret is invalid."); + g_byte_array_free(bytes, TRUE); + return FALSE; + } + + /* No max length check on Master Secret. We use GByteArray to allocate memory + * and pass it to the context derivation routine */ + if (bytes->len == 0) { + *err = g_strdup("Master Secret is mandatory."); + g_byte_array_free(bytes, TRUE); + return FALSE; + } + + if (hex_str_to_bytes(rec->master_salt_prefs, bytes, FALSE) == FALSE) { + *err = g_strdup("Master Salt is invalid."); + g_byte_array_free(bytes, TRUE); + return FALSE; + } + + /* No (max) length check on optional Master Salt. We use GByteArray to allocate memory + * and pass it to the context derivation routine */ + + g_byte_array_free(bytes, TRUE); + return TRUE; +} + +static void* oscore_context_copy_cb(void *n, const void *o, size_t siz _U_) { + oscore_context_t *new_record = (oscore_context_t *) n; + const oscore_context_t *old_record = (const oscore_context_t *) o; + + /* Pre-Shared Parameters */ + new_record->master_secret_prefs = g_strdup(old_record->master_secret_prefs); + new_record->master_salt_prefs = g_strdup(old_record->master_salt_prefs); + new_record->sender_id_prefs = g_strdup(old_record->sender_id_prefs); + new_record->recipient_id_prefs = g_strdup(old_record->recipient_id_prefs); + new_record->algorithm = old_record->algorithm; + + /* Initialize all to NULL, overwrite as needed */ + new_record->master_secret = NULL; + new_record->master_salt = NULL; + new_record->sender_id = NULL; + new_record->recipient_id = NULL; + new_record->request_decryption_key = NULL; + new_record->response_decryption_key = NULL; + new_record->common_iv = NULL; + + /* We rely on oscore_context_post_update_cb() to convert strings to GByteArrays and derive params */ + + return new_record; +} + +static void oscore_context_free_cb(void *r) { + oscore_context_t *rec = (oscore_context_t *) r; + + /* User-configured strings */ + g_free(rec->master_secret_prefs); + g_free(rec->master_salt_prefs); + g_free(rec->sender_id_prefs); + g_free(rec->recipient_id_prefs); + + /* Allocated byte arrays */ + oscore_context_free_byte_arrays(rec); + } + +/* GByteArrays within the oscore_context_t object should be initialized before calling this function */ +static gboolean oscore_context_derive_params(oscore_context_t *context) { + const char *iv_label = "IV"; + const char *key_label = "Key"; + guint8 prk[32]; /* Pseudo-random key from HKDF-Extract step. 32 for SHA256. */ + guint key_len; + guint iv_len; + guint8 info_buf[OSCORE_INFO_MAX_LEN]; + guint info_len; + GByteArray *info; + + key_len = oscore_alg_get_key_len(context->algorithm); + iv_len = oscore_alg_get_iv_len(context->algorithm); + + info = g_byte_array_new(); + + /* Common HKDF-Extract step on master salt */ + hkdf_extract(GCRY_MD_SHA256, context->master_salt->data, context->master_salt->len, context->master_secret->data, context->master_secret->len, prk); + + /* Request Decryption Key */ + info_len = 0; + info_len += cborencoder_put_array(&info_buf[info_len], 4); + info_len += cborencoder_put_bytes(&info_buf[info_len], context->sender_id->data, context->sender_id->len); + info_len += cborencoder_put_unsigned(&info_buf[info_len], context->algorithm); + info_len += cborencoder_put_text(&info_buf[info_len], key_label, 3); + info_len += cborencoder_put_unsigned(&info_buf[info_len], key_len); + /* sender_id->len comes from user input, it is validated by the UAT callback and the max length is accounted for + * in OSCORE_INFO_MAX_LEN */ + DISSECTOR_ASSERT(info_len < OSCORE_INFO_MAX_LEN); + g_byte_array_append(info, info_buf, info_len); + g_byte_array_set_size(context->request_decryption_key, key_len); + hkdf_expand(GCRY_MD_SHA256, prk, sizeof(prk), info->data, info->len, context->request_decryption_key->data, key_len); /* 32 for SHA256 */ + + /* Response Decryption Key */ + info_len = 0; + g_byte_array_set_size(info, 0); + info_len += cborencoder_put_array(&info_buf[info_len], 4); + info_len += cborencoder_put_bytes(&info_buf[info_len], context->recipient_id->data, context->recipient_id->len); + info_len += cborencoder_put_unsigned(&info_buf[info_len], context->algorithm); + info_len += cborencoder_put_text(&info_buf[info_len], key_label, 3); + info_len += cborencoder_put_unsigned(&info_buf[info_len], key_len); + /* recipient_id->len comes from user input, it is validated by the UAT callback and the max length is accounted for + * in OSCORE_INFO_MAX_LEN */ + DISSECTOR_ASSERT(info_len < OSCORE_INFO_MAX_LEN); + g_byte_array_append(info, info_buf, info_len); + g_byte_array_set_size(context->response_decryption_key, key_len); + hkdf_expand(GCRY_MD_SHA256, prk, sizeof(prk), info->data, info->len, context->response_decryption_key->data, key_len); /* 32 for SHA256 */ + + /* Common IV */ + info_len = 0; + g_byte_array_set_size(info, 0); + info_len += cborencoder_put_array(&info_buf[info_len], 4); + info_len += cborencoder_put_null(&info_buf[info_len]); + info_len += cborencoder_put_unsigned(&info_buf[info_len], context->algorithm); + info_len += cborencoder_put_text(&info_buf[info_len], iv_label, 2); + info_len += cborencoder_put_unsigned(&info_buf[info_len], iv_len); + /* all static lengths, accounted for in OSCORE_INFO_MAX_LEN */ + DISSECTOR_ASSERT(info_len < OSCORE_INFO_MAX_LEN); + g_byte_array_append(info, info_buf, info_len); + g_byte_array_set_size(context->common_iv, iv_len); + hkdf_expand(GCRY_MD_SHA256, prk, sizeof(prk), info->data, info->len, context->common_iv->data, iv_len); /* 32 for SHA256 */ + + g_byte_array_free(info, TRUE); + return TRUE; +} + +static guint oscore_alg_get_key_len(cose_aead_alg_t algorithm) { + switch(algorithm) { + case COSE_AES_CCM_16_64_128: + return 16; /* RFC8152 */ + /* unsupported */ + default: + return 0; + } +} + +static guint oscore_alg_get_tag_len(cose_aead_alg_t algorithm) { + switch(algorithm) { + case COSE_AES_CCM_16_64_128: + return 8; /* RFC8152 */ + /* unsupported */ + default: + return 0; + } +} + +static guint oscore_alg_get_iv_len(cose_aead_alg_t algorithm) { + switch(algorithm) { + case COSE_AES_CCM_16_64_128: + return 13; /* RFC8152 */ + /* unsupported */ + default: + return 0; + } +} + +static oscore_context_t * oscore_find_context(oscore_info_t *info) { + guint i; + + for (i = 0; i < num_oscore_contexts; i++) { + if (oscore_contexts[i].sender_id_prefs && info->kid) { + if ((info->kid_len == oscore_contexts[i].sender_id->len) && + memcmp(oscore_contexts[i].sender_id->data, info->kid, info->kid_len) == 0) { + return &oscore_contexts[i]; + } + } + } + return NULL; +} + +/** +CBOR encoding functions needed to construct HKDF info and aad. +Author Martin Gunnarsson +Modified by Malisa Vucinic +*/ +static guint8 +cborencoder_put_text(guint8 *buffer, const char *text, guint8 text_len) { + guint8 ret = 0; + + if(text_len > 23 ){ + buffer[ret++] = 0x78; + buffer[ret++] = text_len; + } else { + buffer[ret++] = (0x60 | text_len); + } + + if (text) { + memcpy(&buffer[ret], text, text_len); + ret += text_len; + } + + return ret; +} + +static guint8 +cborencoder_put_array(guint8 *buffer, guint8 elements) { + guint8 ret = 0; + + if(elements > 15){ + return 0; + } + + buffer[ret++] = (0x80 | elements); + return ret; +} + +static guint8 +cborencoder_put_bytes(guint8 *buffer, const guint8 *bytes, guint8 bytes_len) { + guint8 ret = 0; + + if(bytes_len > 23){ + buffer[ret++] = 0x58; + buffer[ret++] = bytes_len; + } else { + buffer[ret++] = (0x40 | bytes_len); + } + + if (bytes){ + memcpy(&buffer[ret], bytes, bytes_len); + ret += bytes_len; + } + + return ret; +} + +static guint8 +cborencoder_put_unsigned(guint8 *buffer, guint8 value) { + guint8 ret = 0; + + if(value > 0x17 ){ + buffer[ret++] = 0x18; + buffer[ret++] = value; + return ret; + } + + buffer[ret++] = value; + return ret; +} + +static guint8 +cborencoder_put_null(guint8 *buffer) { + guint8 ret = 0; + + buffer[ret++] = 0xf6; + return ret; +} + +/* out should hold NONCE_MAX_LEN bytes at most */ +static void +oscore_create_nonce(guint8 *out, + oscore_context_t *context, + oscore_info_t *info) { + + guint i = 0; + gchar piv_extended[NONCE_MAX_LEN] = { 0 }; + guint nonce_len; + + DISSECTOR_ASSERT(out != NULL); + DISSECTOR_ASSERT(context != NULL); + DISSECTOR_ASSERT(info != NULL); + + nonce_len = oscore_alg_get_iv_len(context->algorithm); + DISSECTOR_ASSERT(nonce_len <= NONCE_MAX_LEN); + + /* AEAD nonce is the XOR of Common IV left-padded to AEAD nonce length and the concatenation of: + * Step 3: Size of Sender ID (1 byte), + * Step 2: Sender ID (left-padded to "AEAD nonce length - 6 bytes"), + * Step 1: Partial IV (left-padded to OSCORE_PIV_MAX_LEN bytes). + */ + + /* Step 1 */ + DISSECTOR_ASSERT(info->piv_len <= OSCORE_PIV_MAX_LEN); + memcpy(&piv_extended[nonce_len - info->piv_len], info->piv, info->piv_len); + + /* Step 2 */ + DISSECTOR_ASSERT(context->sender_id->len <= nonce_len - 6); + memcpy(&piv_extended[nonce_len - OSCORE_PIV_MAX_LEN - context->sender_id->len], context->sender_id->data, context->sender_id->len); + + /* Step 3 */ + piv_extended[0] = context->sender_id->len; + + /* Now XOR with Common IV */ + for (i = 0; i < nonce_len; i++) { + out[i] = piv_extended[i] ^ context->common_iv->data[i]; + } + +} + +static oscore_decryption_status_t +oscore_decrypt_and_verify(tvbuff_t *tvb_ciphertext, + packet_info *pinfo, + gint *offset, + proto_tree *tree, + oscore_context_t *context, + oscore_info_t *info, + tvbuff_t **tvb_plaintext) { + + gboolean have_tag = FALSE; + guint8 nonce[NONCE_MAX_LEN]; + guint8 tmp[AES_128_BLOCK_LEN]; + guint8 *text; + guint8 rx_tag[TAG_MAX_LEN]; + guint tag_len = 0; + guint8 gen_tag[TAG_MAX_LEN]; + guint8 external_aad[OSCORE_EXTERNAL_AAD_MAX_LEN]; + guint8 external_aad_len = 0; + guint8 aad[OSCORE_AAD_MAX_LEN]; + guint8 aad_len = 0; + gint ciphertext_captured_len; + gint ciphertext_reported_len; + const char *encrypt0 = "Encrypt0"; + proto_item *item = NULL; + + tag_len = oscore_alg_get_tag_len(context->algorithm); + + ciphertext_reported_len = tvb_reported_length_remaining(tvb_ciphertext, *offset + tag_len); + + if (ciphertext_reported_len == 0) { + return STATUS_ERROR_MESSAGE_TOO_SMALL; + } + + /* Check if the payload is truncated. */ + if (tvb_bytes_exist(tvb_ciphertext, *offset, ciphertext_reported_len)) { + ciphertext_captured_len = ciphertext_reported_len; + } + else { + ciphertext_captured_len = tvb_captured_length_remaining(tvb_ciphertext, *offset); + } + + /* Check if the tag is present in the captured data. */ + have_tag = tvb_bytes_exist(tvb_ciphertext, *offset + ciphertext_reported_len, tag_len); + if (have_tag) { + DISSECTOR_ASSERT(tag_len <= sizeof(rx_tag)); + tvb_memcpy(tvb_ciphertext, rx_tag, *offset + ciphertext_reported_len, tag_len); + } + + /* Create nonce to use for decryption and authenticity check */ + oscore_create_nonce(nonce, context, info); + + /* + * Create the CCM* initial block for decryption (Adata=0, M=0, counter=0). + * XXX: This only handles AES-CCM-16-64-128, add generic algorithm handling + * */ + ccm_init_block(tmp, FALSE, 0, 0, 0, 0, 0, nonce); + + /* + * Make a copy of the ciphertext in heap memory. + * + * We will decrypt the message in-place and then use the buffer as the + * real data for the new tvb. + */ + text = (guint8 *)tvb_memdup(pinfo->pool, tvb_ciphertext, *offset, ciphertext_captured_len); + + /* + * Perform CTR-mode transformation and decrypt the tag. + * XXX: This only handles AES-CCM-16-64-128, add generic algorithm handling + * */ + if(ccm_ctr_encrypt(context->request_decryption_key->data, tmp, rx_tag, text, ciphertext_captured_len) == FALSE) { + return STATUS_ERROR_DECRYPT_FAILED; + } + + /* Create a tvbuff for the plaintext. */ + *tvb_plaintext = tvb_new_real_data(text, ciphertext_captured_len, ciphertext_reported_len); + tvb_set_child_real_data_tvbuff(tvb_ciphertext, *tvb_plaintext); + add_new_data_source(pinfo, *tvb_plaintext, "Decrypted OSCORE"); + + if (have_tag) { + /* Construct external_aad to be able to verify the tag */ + + /* Note that OSCORE_EXTERNAL_AAD_MAX_LEN calculation depends on the following construct. + * If this is updated - e.g. due to spec changes, added support for Class I options, or added + * support for other algorithms which would change max length of KID - do not forget to update the macro. + * */ + external_aad_len += cborencoder_put_array(&external_aad[external_aad_len], 5); /* 5 elements in the array */ + external_aad_len += cborencoder_put_unsigned(&external_aad[external_aad_len], OSCORE_VERSION); + external_aad_len += cborencoder_put_unsigned(&external_aad[external_aad_len], context->algorithm); + external_aad_len += cborencoder_put_bytes(&external_aad[external_aad_len], info->kid, info->kid_len); + external_aad_len += cborencoder_put_bytes(&external_aad[external_aad_len], info->piv, info->piv_len); + external_aad_len += cborencoder_put_bytes(&external_aad[external_aad_len], NULL, 0); // Class I options not implemented/standardized yet + + /* info->kid_len and info->piv_len come from the lower layer, other parameters are local. + * we end up here only if kid_len is matched to the one from the configured context through oscore_find_context() + * and piv_len is verified in the main dissection routine */ + DISSECTOR_ASSERT(external_aad_len < OSCORE_EXTERNAL_AAD_MAX_LEN); + + /* Note that OSCORE_AAD_MAX_LEN calculation depends on the following construct. + * If the construct below is modified, do not forget to update the macro. + * */ + aad_len += cborencoder_put_array(&aad[aad_len], 3); // COSE Encrypt0 structure with 3 elements + aad_len += cborencoder_put_text(&aad[aad_len], encrypt0, 8); /* Text string "Encrypt0" */ + aad_len += cborencoder_put_bytes(&aad[aad_len], NULL, 0); /* Empty byte string */ + aad_len += cborencoder_put_bytes(&aad[aad_len], external_aad, external_aad_len); /* OSCORE external_aad */ + + DISSECTOR_ASSERT(aad_len < OSCORE_AAD_MAX_LEN); + + /* Compute CBC-MAC authentication tag. */ + + /* + * Create the CCM* initial block for authentication (Adata!=0, M!=0, counter=l(m)). + * XXX: This only handles AES-CCM-16-64-128, add generic algorithm handling + * */ + DISSECTOR_ASSERT(tag_len <= sizeof(gen_tag)); + ccm_init_block(tmp, TRUE, tag_len, 0, 0, 0, ciphertext_captured_len, nonce); + /* text is already a raw buffer containing the plaintext since we just decrypted it in-place */ + if (!ccm_cbc_mac(context->request_decryption_key->data, tmp, aad, aad_len, text, ciphertext_captured_len, gen_tag)) { + return STATUS_ERROR_CBCMAC_FAILED; + } + /* Compare the received tag with the one we generated. */ + else if (memcmp(gen_tag, rx_tag, tag_len) != 0) { + return STATUS_ERROR_TAG_CHECK_FAILED; + } + + /* Display the tag. */ + if (tag_len) { + item = proto_tree_add_bytes(tree, hf_oscore_tag, tvb_ciphertext, ciphertext_captured_len, tag_len, rx_tag); + PROTO_ITEM_SET_GENERATED(item); + } + + return STATUS_SUCCESS_DECRYPTED_TAG_CHECKED; + } /* if (have_tag) */ + + return STATUS_SUCCESS_DECRYPTED_TAG_TRUNCATED; +} + +/* Code to actually dissect the packets */ +static int +oscore_dissect(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + void *data _U_) +{ + /* Set up structures needed to add the protocol subtree and manage it */ + proto_item *ti; + proto_tree *oscore_tree; + /* Other misc. local variables. */ + guint offset = 0; + oscore_info_t *info = (oscore_info_t *) data; + oscore_context_t *context = NULL; + oscore_decryption_status_t status; + tvbuff_t *tvb_decrypted = NULL; + + /* Check that the packet is long enough for it to belong to us. */ + if (tvb_reported_length(tvb) < OSCORE_MIN_LENGTH) { + return 0; + } + + /* Set the Protocol column to the constant string of oscore */ + col_set_str(pinfo->cinfo, COL_PROTOCOL, "OSCORE"); + + /* create display subtree for the protocol */ + ti = proto_tree_add_item(tree, proto_oscore, tvb, 0, -1, ENC_NA); + + oscore_tree = proto_item_add_subtree(ti, ett_oscore); + + if (info->kid == NULL) { + expert_add_info(pinfo, oscore_tree, &ei_oscore_key_id_not_found); + return tvb_reported_length(tvb); + } + + if (info->piv == NULL) { + expert_add_info(pinfo, oscore_tree, &ei_oscore_partial_iv_not_found); + return tvb_reported_length(tvb); + } + + if ((context = oscore_find_context(info)) == NULL) { + expert_add_info(pinfo, oscore_tree, &ei_oscore_context_not_set); + return tvb_reported_length(tvb); + } + + if (info->piv_len > OSCORE_PIV_MAX_LEN) { + expert_add_info(pinfo, oscore_tree, &ei_oscore_piv_len_invalid); + return tvb_reported_length(tvb); + } + + status = oscore_decrypt_and_verify(tvb, pinfo, &offset, oscore_tree, context, info, &tvb_decrypted); + + switch (status) { + case STATUS_ERROR_DECRYPT_FAILED: + expert_add_info(pinfo, oscore_tree, &ei_oscore_decrypt_error); + return tvb_reported_length(tvb); + case STATUS_ERROR_CBCMAC_FAILED: + expert_add_info(pinfo, oscore_tree, &ei_oscore_cbc_mac_failed); + return tvb_reported_length(tvb); + case STATUS_ERROR_TAG_CHECK_FAILED: + expert_add_info(pinfo, oscore_tree, &ei_oscore_tag_check_failed); + return tvb_reported_length(tvb); + case STATUS_ERROR_MESSAGE_TOO_SMALL: + expert_add_info(pinfo, oscore_tree, &ei_oscore_message_too_small); + return tvb_reported_length(tvb); + case STATUS_SUCCESS_DECRYPTED_TAG_TRUNCATED: + expert_add_info(pinfo, oscore_tree, &ei_oscore_truncated); + /* do not return, attempt dissection */ + break; + case STATUS_SUCCESS_DECRYPTED_TAG_CHECKED: + break; + } + + DISSECTOR_ASSERT(tvb_decrypted); + + proto_tree_add_item(oscore_tree, hf_oscore_coap_data, tvb_decrypted, 0, tvb_reported_length(tvb_decrypted), ENC_NA); + + return tvb_captured_length(tvb); +} + +/* Register the protocol with Wireshark. + * + * This format is require because a script is used to build the C function that + * calls all the protocol registration. + */ +void +proto_register_oscore(void) +{ + module_t *oscore_module; + expert_module_t *expert_oscore; + + static hf_register_info hf[] = { + { &hf_oscore_coap_data, + { "Decrypted CoAP Data", "oscore.coap_data", + FT_BYTES, BASE_NONE, NULL, 0x00, + NULL, HFILL } + }, + { &hf_oscore_tag, + { "Decrypted Authentication Tag", "oscore.tag", FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + }; + + /* Setup protocol subtree array */ + static gint *ett[] = { + &ett_oscore + }; + + /* Setup protocol expert items */ + static ei_register_info ei[] = { + { &ei_oscore_key_id_not_found, + { "oscore.key_id_not_found", PI_UNDECODED, PI_WARN, + "Key ID not found - can't decrypt", EXPFILL + } + }, + { &ei_oscore_partial_iv_not_found, + { "oscore.partial_iv_not_found", PI_UNDECODED, PI_WARN, + "Partial IV not found - can't decrypt", EXPFILL + } + }, + { &ei_oscore_context_not_set, + { "oscore.context_not_set", PI_UNDECODED, PI_WARN, + "Security context not set - can't decrypt", EXPFILL + } + }, + { &ei_oscore_message_too_small, + { "oscore.message_too_small", PI_UNDECODED, PI_WARN, + "Message too small", EXPFILL + } + }, + { &ei_oscore_truncated, + { "oscore.truncated", PI_UNDECODED, PI_WARN, + "Message truncated, cannot verify authentication tag, but decryption is attempted", EXPFILL + } + }, + { &ei_oscore_cbc_mac_failed, + { "oscore.cbc_mac_failed", PI_UNDECODED, PI_WARN, + "Call to CBC-MAC failed", EXPFILL + } + }, + { &ei_oscore_tag_check_failed, + { "oscore.tag_check_failed", PI_UNDECODED, PI_WARN, + "Authentication tag check failed", EXPFILL + } + }, + { &ei_oscore_decrypt_error, + { "oscore.decrypt_error", PI_UNDECODED, PI_WARN, + "Decryption error", EXPFILL + } + }, + { &ei_oscore_piv_len_invalid, + { "oscore.piv_len_invalid", PI_UNDECODED, PI_WARN, + "Partial IV length from the lower layer is invalid", EXPFILL + } + }, + }; + + static uat_field_t oscore_context_uat_flds[] = { + UAT_FLD_CSTRING(oscore_context_uat,sender_id_prefs,"Sender ID", + "Sender ID as HEX string. Should be 7 bytes or less. Mandatory."), + UAT_FLD_CSTRING(oscore_context_uat,recipient_id_prefs,"Recipient ID", + "Recipient ID as HEX string. Should be 7 bytes or less. Mandatory."), + UAT_FLD_CSTRING(oscore_context_uat,master_secret_prefs,"Master Secret", + "Master Secret as HEX string. Mandatory."), + UAT_FLD_CSTRING(oscore_context_uat,master_salt_prefs,"Master Salt", + "Master Salt as HEX string. Optional."), + UAT_FLD_VS(oscore_context_uat, algorithm, "Algorithm", oscore_context_alg_vals, "Decryption algorithm."), + UAT_END_FIELDS + }; + + /* Register the protocol name and description */ + proto_oscore = proto_register_protocol("Object Security for Constrained RESTful Environments", + "OSCORE", "oscore"); + + /* Required function calls to register the header fields and subtrees */ + proto_register_field_array(proto_oscore, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + + /* Required function calls to register expert items */ + expert_oscore = expert_register_protocol(proto_oscore); + expert_register_field_array(expert_oscore, ei, array_length(ei)); + + oscore_module = prefs_register_protocol(proto_oscore, NULL); + + /* Create a UAT for security context management. */ + oscore_context_uat = uat_new("Security Contexts", + sizeof(oscore_context_t), /* record size */ + "oscore_contexts", /* filename */ + TRUE, /* from_profile */ + &oscore_contexts, /* data_ptr */ + &num_oscore_contexts, /* numitems_ptr */ + UAT_AFFECTS_DISSECTION, /* affects dissection of packets, but not set of named fields */ + NULL, /* help */ + oscore_context_copy_cb, /* copy callback */ + oscore_context_update_cb, /* update callback */ + oscore_context_free_cb, /* free callback */ + oscore_context_post_update_cb, /* post update callback */ + NULL, /* reset callback */ + oscore_context_uat_flds); /* UAT field definitions */ + + prefs_register_uat_preference(oscore_module, "contexts", + "Security Contexts", + "Security context configuration data", + oscore_context_uat); + + register_dissector("oscore", oscore_dissect, proto_oscore); +} + +/* + * 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/epan/dissectors/packet-oscore.h b/epan/dissectors/packet-oscore.h new file mode 100644 index 0000000000..2eea9f3209 --- /dev/null +++ b/epan/dissectors/packet-oscore.h @@ -0,0 +1,72 @@ +/* packet-oscore.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __PACKET_OSCORE_H__ +#define __PACKET_OSCORE_H__ + +/* OSCORE uses AEAD algorithms defined in RFC8152 (COSE) + * We only implement the default algorithm which corresponds to CCM* + * */ +typedef enum { + COSE_AES_CCM_16_64_128 = 10, +} cose_aead_alg_t; + +typedef enum { + STATUS_ERROR_DECRYPT_FAILED = 0, + STATUS_ERROR_CBCMAC_FAILED, + STATUS_ERROR_TAG_CHECK_FAILED, + STATUS_ERROR_MESSAGE_TOO_SMALL, + STATUS_SUCCESS_DECRYPTED_TAG_TRUNCATED, + STATUS_SUCCESS_DECRYPTED_TAG_CHECKED, +} oscore_decryption_status_t; + +/* Structure containing information regarding all necessary OSCORE message fields. */ +typedef struct oscore_context { + /* Pre-Shared Parameters as Strings */ + gchar *master_secret_prefs; + gchar *master_salt_prefs; + gchar *sender_id_prefs; + gchar *recipient_id_prefs; + cose_aead_alg_t algorithm; + /* Pre-Shared Parameters as Byte Arrays */ + GByteArray *master_secret; + GByteArray *master_salt; + GByteArray *sender_id; + GByteArray *recipient_id; + /* Derived Parameters */ + GByteArray *request_decryption_key; + GByteArray *response_decryption_key; + GByteArray *common_iv; /* IV used to generate the nonce */ +} oscore_context_t; + +/* Data from the lower layer (CoAP/HTTP) necessary for OSCORE to decrypt the packet */ +typedef struct oscore_info { + guint8 *kid; + guint8 kid_len; + guint8 *kid_context; + guint8 kid_context_len; + guint8 *piv; + guint8 piv_len; + gboolean response; +} oscore_info_t; + +#endif /* __PACKET_OSCORE_H__ */ + +/* + * 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: + */