summaryrefslogtreecommitdiffstats
path: root/plugins/epan/opcua/opcua.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/epan/opcua/opcua.c')
-rw-r--r--plugins/epan/opcua/opcua.c771
1 files changed, 638 insertions, 133 deletions
diff --git a/plugins/epan/opcua/opcua.c b/plugins/epan/opcua/opcua.c
index 33d966a9..5872a207 100644
--- a/plugins/epan/opcua/opcua.c
+++ b/plugins/epan/opcua/opcua.c
@@ -14,54 +14,82 @@
** Author: Gerhard Gappmeier <gerhard.gappmeier@ascolab.com>
******************************************************************************/
-#include "config.h"
-
+#include <epan/dissectors/packet-tcp.h>
#include <epan/packet.h>
+#include <epan/prefs.h>
+#include <epan/range.h>
#include <epan/reassemble.h>
-#include <epan/dissectors/packet-tcp.h>
-#include "opcua_transport_layer.h"
-#include "opcua_security_layer.h"
+#include <epan/secrets.h>
+#include <epan/tvbuff.h>
+#include <gcrypt.h>
+#include <wiretap/secrets-types.h>
+#include <wsutil/file_util.h>
+
+#include "config.h"
#include "opcua_application_layer.h"
#include "opcua_complextypeparser.h"
-#include "opcua_serviceparser.h"
#include "opcua_enumparser.h"
-#include "opcua_simpletypes.h"
#include "opcua_hfindeces.h"
+#include "opcua_keyset.h"
+#include "opcua_security_layer.h"
+#include "opcua_serviceparser.h"
+#include "opcua_serviceids.h"
+#include "opcua_simpletypes.h"
+#include "opcua_transport_layer.h"
void proto_register_opcua(void);
extern const value_string g_requesttypes[];
extern const int g_NumServices;
+static const char *g_opcua_debug_file_name;
+int g_opcua_default_sig_len;
/* forward reference */
void proto_reg_handoff_opcua(void);
/* declare parse function pointer */
-typedef int (*FctParse)(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, gint *pOffset);
+typedef int (*FctParse)(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int *pOffset, struct ua_metadata *data);
-int proto_opcua = -1;
+int proto_opcua;
static dissector_handle_t opcua_handle;
+static module_t *opcua_module;
+
+/* #define OPCUA_DEBUG */
+#ifdef OPCUA_DEBUG
+# define debugprintf(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__);
+#else
+# define debugprintf(fmt, ...)
+#endif
+
/** Official IANA registered port for OPC UA Binary Protocol. */
+#define OPCUA_DEFAULT_PORT 4840
+/* default port range for preferences */
#define OPCUA_PORT_RANGE "4840"
+/** header length that is needed to compute the pdu length.
+ * @see get_opcua_message_len
+ */
+#define FRAME_HEADER_LEN 8
+/* AES block size: for both AES128 and AES256 the block size is 128 bits */
+#define AES_BLOCK_SIZE 16
/** subtree types used in opcua_transport_layer.c */
-gint ett_opcua_extensionobject = -1;
-gint ett_opcua_nodeid = -1;
+int ett_opcua_extensionobject;
+int ett_opcua_nodeid;
/** subtree types used locally */
-static gint ett_opcua_transport = -1;
-static gint ett_opcua_fragment = -1;
-static gint ett_opcua_fragments = -1;
-
-static int hf_opcua_fragments = -1;
-static int hf_opcua_fragment = -1;
-static int hf_opcua_fragment_overlap = -1;
-static int hf_opcua_fragment_overlap_conflicts = -1;
-static int hf_opcua_fragment_multiple_tails = -1;
-static int hf_opcua_fragment_too_long_fragment = -1;
-static int hf_opcua_fragment_error = -1;
-static int hf_opcua_fragment_count = -1;
-static int hf_opcua_reassembled_in = -1;
-static int hf_opcua_reassembled_length = -1;
+static int ett_opcua_transport;
+static int ett_opcua_fragment;
+static int ett_opcua_fragments;
+
+static int hf_opcua_fragments;
+static int hf_opcua_fragment;
+static int hf_opcua_fragment_overlap;
+static int hf_opcua_fragment_overlap_conflicts;
+static int hf_opcua_fragment_multiple_tails;
+static int hf_opcua_fragment_too_long_fragment;
+static int hf_opcua_fragment_error;
+static int hf_opcua_fragment_count;
+static int hf_opcua_reassembled_in;
+static int hf_opcua_reassembled_length;
static const fragment_items opcua_frag_items = {
/* Fragment subtrees */
@@ -86,7 +114,6 @@ static const fragment_items opcua_frag_items = {
"Message fragments"
};
-
static reassembly_table opcua_reassembly_table;
/** OpcUa Transport Message Types */
@@ -115,23 +142,35 @@ static const char* g_szMessageTypes[] =
"Invalid message"
};
+static const enum_val_t opcua_sig_len_enum[] = {
+ { "None", "Unsigned", 0 },
+ { "20", "20 Bytes", 20 },
+ { "32", "32 Bytes", 32 },
+ { NULL, NULL, 0 }
+};
-
-
-/** header length that is needed to compute
- * the pdu length.
- * @see get_opcua_message_len
- */
-#define FRAME_HEADER_LEN 8
+#ifdef _MSC_VER
+static char *ua_strtok_r(char *str, const char *delim, char **saveptr)
+{
+ /* use MSVC specific strtok_s */
+ return strtok_s(str, delim, saveptr);
+}
+#else
+static char *ua_strtok_r(char *str, const char *delim, char **saveptr)
+{
+ /* use POSIX strtok_r */
+ return strtok_r(str, delim, saveptr);
+}
+#endif
/** returns the length of an OpcUa message.
* This function reads the length information from
* the transport header.
*/
-static guint get_opcua_message_len(packet_info *pinfo _U_, tvbuff_t *tvb,
+static unsigned get_opcua_message_len(packet_info *pinfo _U_, tvbuff_t *tvb,
int offset, void *data _U_)
{
- gint32 plen;
+ int32_t plen;
/* the message length starts at offset 4 */
plen = tvb_get_letohl(tvb, offset + 4);
@@ -139,6 +178,315 @@ static guint get_opcua_message_len(packet_info *pinfo _U_, tvbuff_t *tvb,
return plen;
}
+/* Helper function to convert hex string to binary data */
+unsigned hex_to_bin(const char *hex_string, unsigned char *binary_data, unsigned int binary_size)
+{
+ unsigned length = (unsigned)strlen(hex_string);
+ unsigned i;
+
+ for (i = 0; i < length / 2 && i < binary_size; ++i) {
+ sscanf(hex_string + 2 * i, "%2hhx", &binary_data[i]);
+ }
+
+ return i;
+}
+
+/** Parsing context */
+struct opcua_keylog_parser_ctx {
+ struct ua_keyset *keyset; /**< current keyset */
+ uint64_t last_id; /**< the id of the previous line, this is also the id of the keyset */
+};
+
+/**
+ * Common function for parsing key log line used by opcua_keylog_process_lines and opcua_load_keylog_file.
+ *
+ * @param ctx Parsing context.
+ * @param line Current line to parse.
+ */
+static void opcua_keylog_process_line(struct opcua_keylog_parser_ctx *ctx, const char *line)
+{
+ struct ua_keyset *keyset;
+ char key[33]; /* 32 chars + null terminator */
+ char value[65]; /* 64 hex chars + null terminator */
+ const char *parts[4]; /* for string split */
+ unsigned int num_parts;
+ char *tmp, *saveptr;
+ uint32_t token_id = 0;
+ uint32_t channel_id = 0;
+ uint64_t id = 0;
+ int n;
+
+ /* parse key/value pair */
+ n = sscanf(line, "%32[^:]: %64s\n", key, value);
+ if (n != 2) return;
+
+ debugprintf("%s = %s\n", key, value);
+
+ /* split key into parts */
+ num_parts = 0;
+ tmp = ua_strtok_r(key, "_", &saveptr);
+ while (tmp && num_parts < 4) {
+ parts[num_parts++] = tmp;
+ tmp = ua_strtok_r(NULL, "_", &saveptr);
+ }
+ if (num_parts != 4) return; /* skip invalid enty */
+ channel_id = (uint32_t)strtoul(parts[2], NULL, 10);
+ token_id = (uint32_t)strtoul(parts[3], NULL, 10);
+
+ debugprintf("channel_id = %u\n", channel_id);
+ debugprintf("token_id = %u\n", token_id);
+
+ /* create unique keyset id */
+ id = ua_keyset_id(channel_id, token_id);
+
+ if (ctx->keyset == NULL || id != ctx->last_id) {
+ debugprintf("Adding new keyset for id %lu...\n", id);
+ /* create new keyset for new id */
+ ctx->keyset = ua_keysets_add();
+ ctx->last_id = id;
+ }
+ keyset = ctx->keyset;
+ if (keyset) {
+ keyset->id = id;
+ /* store key material */
+ if (strcmp(parts[0], "client") == 0) {
+ if (strcmp(parts[1], "iv") == 0) {
+ hex_to_bin(value, keyset->client_iv, sizeof(keyset->client_iv));
+ } else if (strcmp(parts[1], "key") == 0) {
+ keyset->client_key_len = (unsigned int)hex_to_bin(value, keyset->client_key, sizeof(keyset->client_key));
+ } else if (strcmp(parts[1], "siglen") == 0) {
+ keyset->client_sig_len = (unsigned int)strtoul(value, NULL, 10);
+ }
+ } else if (strcmp(parts[0], "server") == 0) {
+ if (strcmp(parts[1], "iv") == 0) {
+ hex_to_bin(value, keyset->server_iv, sizeof(keyset->server_iv));
+ } else if (strcmp(parts[1], "key") == 0) {
+ keyset->server_key_len = (unsigned int)hex_to_bin(value, keyset->server_key, sizeof(keyset->server_key));
+ } else if (strcmp(parts[1], "siglen") == 0) {
+ keyset->server_sig_len = (unsigned int)strtoul(value, NULL, 10);
+ }
+ }
+ }
+}
+
+/**
+ * Parses key log data from PCAP file.
+ * This function splits the data by \n and calls opcua_keylog_process_line.
+ */
+static void opcua_keylog_process_lines(char *data)
+{
+ struct opcua_keylog_parser_ctx ctx = { NULL, 0 };
+ char *saveptr;
+ const char *line = ua_strtok_r(data, "\n", &saveptr);
+
+ while (line) {
+ opcua_keylog_process_line(&ctx, line);
+ line = ua_strtok_r(NULL, "\n", &saveptr);
+ }
+
+ /* sort data by id to make lookup working */
+ ua_keysets_sort();
+}
+
+/**
+ * Loads the configured OPCUA Keylog file.
+ */
+static void opcua_load_keylog_file(const char *filename)
+{
+ struct opcua_keylog_parser_ctx ctx = { NULL, 0 };
+ char line[256];
+
+ debugprintf("Loading key file '%s'...\n", filename);
+ FILE *f = ws_fopen(filename, "r");
+ if (f == NULL) {
+ debugprintf("error: '%s' not found\n", filename);
+ return;
+ }
+
+ /* parse file contents */
+ while (fgets(line, sizeof(line), f)) {
+ opcua_keylog_process_line(&ctx, line);
+ }
+ fclose(f);
+
+ /* sort data by id to make lookup working */
+ ua_keysets_sort();
+}
+
+/**
+ * Checks the padding of a symetric signed message.
+ * A message always contains a padding_len byte, which tells us the length of
+ * the padding. All following padding bytes contain the same value. This makes it
+ * possible the padding from the end of the message.
+ * Example Paddings:
+ * - 00
+ * - 01 01
+ * - 02 02 02
+ * @param padding Pointer to last padding byte.
+ * @return padding length on success, -1 if the paddding is invalid.
+ */
+static int verify_padding(const uint8_t *padding)
+{
+ uint8_t pad_len;
+ uint8_t i;
+
+ pad_len = *padding;
+
+ for (i = 0; i < pad_len; ++i) {
+ if (padding[-pad_len + i] != pad_len) return -1;
+ }
+
+ return pad_len;
+}
+/**
+ * Gets security footer info.
+ *
+ * @param channel_id SecureChannelId for keyset lookup.
+ * @param token_id TokenId for keyset lookup.
+ * @param sig_len Returns the length of the signature.
+ * @param from_server True of the message is sent from the server, false when sent from the client.
+ *
+ * @return Returns 0 on success, -1 if parsing failed.
+ */
+static int opcua_get_footer_info(uint32_t channel_id, uint32_t token_id, uint8_t *sig_len, bool from_server)
+{
+ struct ua_keyset *keyset;
+ uint64_t id;
+
+ id = ua_keyset_id(channel_id, token_id);
+
+ /* try to get correct signature length from key log file */
+ keyset = ua_keysets_lookup(id);
+ if (keyset) {
+ /* The Client keys are used to secure Messages sent by the Client. The Server keys are used to
+ * secure Messages sent by the Server.
+ */
+ if (from_server) {
+ *sig_len = keyset->server_sig_len;
+ } else {
+ *sig_len = keyset->client_sig_len;
+ }
+ }
+
+ debugprintf("no keyset found for channel_id=%u and token_id=%u\n", channel_id, token_id);
+ /* we use sig_len set from OpenSecurehChannel Policy in this case.
+ * this requires to have the OPN in the capture file, otherwise we are out of luck.
+ */
+
+ return 0;
+}
+
+/**
+ * This function to perform AES decryption on service data in-place.
+ * Add also determines the payload length by removing the padding and signature.
+ *
+ * @param channel_id SecureChannelId for keyset lookup.
+ * @param token_id TokenId for keyset lookup.
+ * @param cipher The cipher text.
+ * @param cipher_len The cipher test length in bytes.
+ * @param plaintext The plaintext to return.
+ * @param plaintext_len The plaintext in bytes, should be the same as cipher_len.
+ * @param padding_len Returns the length of the padding.
+ * @param sig_len Returns the length of the signature.
+ * @param from_server True of the message is sent from the server, false when sent from the client.
+ *
+ * @return Returns 0 on success, -1 if decryption failed.
+ */
+static int decrypt_opcua(
+ uint32_t channel_id, uint32_t token_id,
+ const uint8_t *cipher, unsigned cipher_len,
+ uint8_t *plaintext, unsigned plaintext_len,
+ uint8_t *padding_len, uint8_t *sig_len, bool from_server)
+{
+ struct ua_keyset *keyset;
+ uint64_t id;
+ unsigned int keylen, ivlen;
+ unsigned char *keydata, *ivdata;
+ int cipher_mode;
+ gcry_error_t res;
+ int ret = 0;
+
+ id = ua_keyset_id(channel_id, token_id);
+
+ keyset = ua_keysets_lookup(id);
+ if (keyset == NULL) {
+ debugprintf("no keyset found for channel_id=%u and token_id=%u\n", channel_id, token_id);
+ /* col_append_fstr(pinfo->cinfo, COL_INFO, " (encrypted)"); */
+ return -1;
+ }
+ debugprintf("found keyset for channel_id=%u and token_id=%u\n", channel_id, token_id);
+
+ /* The Client keys are used to secure Messages sent by the Client. The Server keys are used to
+ * secure Messages sent by the Server.
+ */
+ if (from_server) {
+ ivlen = sizeof(keyset->server_iv);
+ ivdata = keyset->server_iv;
+ keylen = keyset->server_key_len;
+ keydata = keyset->server_key;
+ *sig_len = keyset->server_sig_len;
+ } else {
+ ivlen = sizeof(keyset->client_iv);
+ ivdata = keyset->client_iv;
+ keylen = keyset->client_key_len;
+ keydata = keyset->client_key;
+ *sig_len = keyset->client_sig_len;
+ }
+ /* derive AES mode from key length */
+ switch (keylen) {
+ case 16:
+ debugprintf("using AES-128-CBC\n");
+ cipher_mode = GCRY_CIPHER_AES128;
+ break;
+ case 32:
+ debugprintf("using AES-256-CBC\n");
+ cipher_mode = GCRY_CIPHER_AES256;
+ break;
+ default:
+ debugprintf("invalid AES key length: %u bytes\n", keylen);
+ /* col_append_fstr(pinfo->cinfo, COL_INFO, " (encrypted)"); */
+ return -1;
+ }
+
+ debugprintf("cipher_len=%u\n", cipher_len);
+ if (cipher_len % 16 != 0) {
+ debugprintf("warning: cipher_len not a multiple of 16.\n");
+ }
+
+ gcry_cipher_hd_t handle;
+ gcry_cipher_open(&handle, cipher_mode, GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_CBC_CTS);
+ gcry_cipher_setkey(handle, keydata, keylen);
+ gcry_cipher_setiv(handle, ivdata, ivlen);
+
+ /* Decrypt the data in-place */
+ res = gcry_cipher_decrypt(handle, plaintext, plaintext_len, cipher, cipher_len);
+ if (res == 0) {
+ /* col_append_fstr(pinfo->cinfo, COL_INFO, " (decrypted)"); */
+ debugprintf("decryption succeeded.\n");
+ } else {
+ /* col_append_fstr(pinfo->cinfo, COL_INFO, " (encrypted)"); */
+ debugprintf("decryption failed.\n");
+ ret = -1;
+ }
+ gcry_cipher_close(handle);
+ /* it makes no sense to continue and verify the padding if decryption failed */
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = verify_padding(&plaintext[plaintext_len - *sig_len - 1]);
+ if (ret < 0) {
+ debugprintf("padding is invalid.\n");
+ }
+
+ /* return padding length */
+ *padding_len = plaintext[plaintext_len - *sig_len - 1];
+ debugprintf("sig_len=%u\n", *sig_len);
+ debugprintf("pad_len=%u\n", *padding_len);
+
+ return 0;
+}
+
/** The OpcUa message dissector.
* This method dissects full OpcUa messages.
* It gets only called with reassembled data
@@ -148,41 +496,64 @@ static int dissect_opcua_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *
{
FctParse pfctParse = NULL;
enum MessageType msgtype = MSG_INVALID;
+ uint16_t src_port = pinfo->srcport;
+ range_t *port_range;
+ bool from_server = false;
+ bool decrypted = false; /* successfully decrypted secure message */
+ enum ua_message_mode mode = UA_MessageMode_None;
+ uint8_t sig_len = 0;
+ struct ua_metadata metadata;
+ tvbuff_t *decrypted_tvb = NULL;
+ int ret;
+
+ /* determine if telegram is from server or from client by checking the port number */
+ if (src_port == OPCUA_DEFAULT_PORT) {
+ from_server = true;
+ } else {
+ port_range = prefs_get_range_value("opcua", "tcp.port");
+ if (port_range && value_is_in_range(port_range, src_port)) {
+ from_server = true;
+ }
+ }
+
+ metadata.encrypted = false;
+ get_encryption_info(pinfo, &mode, &sig_len);
col_set_str(pinfo->cinfo, COL_PROTOCOL, "OpcUa");
+
/* parse message type */
- if (tvb_memeql(tvb, 0, (const guint8 * )"HEL", 3) == 0)
+ if (tvb_memeql(tvb, 0, (const uint8_t * )"HEL", 3) == 0)
{
msgtype = MSG_HELLO;
pfctParse = parseHello;
}
- else if (tvb_memeql(tvb, 0, (const guint8*)"ACK", 3) == 0)
+ else if (tvb_memeql(tvb, 0, (const uint8_t*)"ACK", 3) == 0)
{
msgtype = MSG_ACKNOWLEDGE;
pfctParse = parseAcknowledge;
}
- else if (tvb_memeql(tvb, 0, (const guint8*)"ERR", 3) == 0)
+ else if (tvb_memeql(tvb, 0, (const uint8_t*)"ERR", 3) == 0)
{
msgtype = MSG_ERROR;
pfctParse = parseError;
}
- else if (tvb_memeql(tvb, 0, (const guint8*)"RHE", 3) == 0)
+ else if (tvb_memeql(tvb, 0, (const uint8_t*)"RHE", 3) == 0)
{
msgtype = MSG_REVERSEHELLO;
pfctParse = parseReverseHello;
}
- else if (tvb_memeql(tvb, 0, (const guint8*)"MSG", 3) == 0)
+ else if (tvb_memeql(tvb, 0, (const uint8_t*)"MSG", 3) == 0)
{
msgtype = MSG_MESSAGE;
pfctParse = parseMessage;
}
- else if (tvb_memeql(tvb, 0, (const guint8*)"OPN", 3) == 0)
+ else if (tvb_memeql(tvb, 0, (const uint8_t*)"OPN", 3) == 0)
{
msgtype = MSG_OPENSECURECHANNEL;
pfctParse = parseOpenSecureChannel;
}
- else if (tvb_memeql(tvb, 0, (const guint8*)"CLO", 3) == 0)
+ else if (tvb_memeql(tvb, 0, (const uint8_t*)"CLO", 3) == 0)
{
msgtype = MSG_CLOSESECURECHANNEL;
pfctParse = parseCloseSecureChannel;
@@ -205,11 +576,12 @@ static int dissect_opcua_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *
if (pfctParse)
{
- gint offset = 0;
+ int offset = 0;
int iServiceId = -1;
- tvbuff_t *next_tvb = tvb;
- gboolean bParseService = TRUE;
- gboolean bIsLastFragment = FALSE;
+ bool bParseService = false; /* Only MSG, OPN and CLO have a service payload */
+ bool bIsFinalChunk = false;
+ unsigned payload_len = 0;
+ uint8_t pad_len = 0;
/* we are being asked for details */
proto_item *ti = NULL;
@@ -218,83 +590,149 @@ static int dissect_opcua_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *
ti = proto_tree_add_item(tree, proto_opcua, tvb, 0, -1, ENC_NA);
transport_tree = proto_item_add_subtree(ti, ett_opcua_transport);
- /* MSG_MESSAGE might be fragmented, check for that */
- if (msgtype == MSG_MESSAGE)
+ /* call the transport message dissector */
+ (*pfctParse)(transport_tree, tvb, pinfo, &offset, &metadata);
+
+ /* MSG_MESSAGE and MSG_CLOSESECURECHANNEL can be decrypted.
+ * Also handle chunked message reassembly for MSG_MESSAGE.
+ */
+ if (msgtype == MSG_MESSAGE || msgtype == MSG_CLOSESECURECHANNEL)
{
- guint8 chunkType = 0;
- guint32 opcua_seqid = 0;
- guint32 opcua_num = 0;
- guint32 opcua_seqnum;
+ uint8_t chunkType = 0;
+ uint32_t opcua_seqno = 0; /* OPCUA sequence number */
+ uint32_t opcua_reqid = 0; /* OPCUA request id */
fragment_head *frag_msg = NULL;
- fragment_item *frag_i = NULL;
+ bParseService = true;
offset = 3;
+ chunkType = tvb_get_uint8(tvb, offset); offset += 1;
+ offset += 4; /* message size */
+ offset += 4; /* skip secure channel_id */
+ parseSecurityHeader(transport_tree, tvb, &offset, &metadata); /* only token_id (4 byte) */
+
+ if (mode == UA_MessageMode_MaybeEncrypted) {
+ /* try to parse ServiceId */
+ iServiceId = getServiceNodeId(tvb, offset + 8); /* skip 4 byte SeqNo and 4 byte RequestId */
+ const char *szServiceName = val_to_str((uint32_t)iServiceId, g_requesttypes, "not found");
+ if (strcmp(szServiceName, "not found") == 0) {
+ mode = UA_MessageMode_SignAndEncrypt;
+ } else {
+ mode = UA_MessageMode_Sign;
+ }
+ store_encryption_info(pinfo, mode, sig_len);
+ }
- chunkType = tvb_get_guint8(tvb, offset); offset += 1;
-
- offset += 4; /* Message Size */
- offset += 4; /* SecureChannelId */
- offset += 4; /* Security Token Id */
+ /* Message Structure:
+ * +-----------------+
+ * / | Message Header | MSGF, MessageSize
+ * | +-----------------+
+ * | | Security Header | SecureChannelId, TokenId
+ * | +-----------------+
+ * Signed < | Sequence Header | \ SequenceNumber, RequestId
+ * | +-----------------+ |
+ * | | Body | |
+ * | +-----------------+ > Encrypted
+ * \ | Padding | |
+ * +-----------------+ |
+ * | Signature | /
+ * +-----------------+
+ */
+ if (mode == UA_MessageMode_SignAndEncrypt) {
+ uint32_t channel_id = tvb_get_letohl(tvb, 8);
+ uint32_t token_id = tvb_get_letohl(tvb, 12);
+ unsigned cipher_len = tvb_ensure_captured_length_remaining(tvb, 16);
+ unsigned plaintext_len = cipher_len;
+ const uint8_t *cipher = tvb_get_ptr(tvb, 16, (int)cipher_len);
+ unsigned char *plaintext = (unsigned char*)wmem_alloc(pinfo->pool, plaintext_len);
+
+ ret = decrypt_opcua(channel_id, token_id, cipher, cipher_len, plaintext, plaintext_len, &pad_len, &sig_len, from_server);
+ if (ret == 0) {
+ /* decrypted */
+ /* to get the payload length we need to subtract the sequence header (8) byte,
+ * the padding (paddin_len+1), and the signature from the plaintext */
+ payload_len = plaintext_len - pad_len - sig_len - 9; /* pad_len 2 = 02 02 02 */
+ /* Now re-setup the tvb buffer to have the new data */
+ decrypted_tvb = tvb_new_child_real_data(tvb, plaintext, (unsigned)plaintext_len, (int)plaintext_len);
+ add_new_data_source(pinfo, decrypted_tvb, "Decrypted Data");
+ /* process decrypted_tvb from here */
+ tvb = decrypted_tvb;
+ offset = 0;
+ decrypted = true;
+ } else {
+ /* decryption failed */
+ metadata.encrypted = true;
+ }
+ } else if (mode == UA_MessageMode_Sign) {
+ uint32_t channel_id = tvb_get_letohl(tvb, 8);
+ uint32_t token_id = tvb_get_letohl(tvb, 12);
+ payload_len = tvb_ensure_captured_length_remaining(tvb, 24); /* subtract header */
+
+ ret = opcua_get_footer_info(channel_id, token_id, &sig_len, from_server);
+ if (ret != 0) {
+ debugprintf("Processing security footer of signed message failed.\n");
+ } else {
+ /* signed only messages have no padding, so the payload is the message size
+ * without 24 byte header and without signature */
+ payload_len -= sig_len;
+ }
+ /* store the current tvb as decrypted tvb, because we need this to parse the signature
+ * at the end, and tvb gets replaces with the reassembled UA message if the message was chunked.
+ */
+ decrypted_tvb = tvb;
+ } else {
+ /* no padding, no signature, just payload */
+ payload_len = tvb_ensure_captured_length_remaining(tvb, 24); /* subtract header */
+ pad_len= 0;
+ sig_len = 0;
+ }
- opcua_num = tvb_get_letohl(tvb, offset); offset += 4; /* Security Sequence Number */
- opcua_seqid = tvb_get_letohl(tvb, offset); offset += 4; /* Security RequestId */
+ opcua_seqno = tvb_get_letohl(tvb, offset); /* Sequence.Sequence Number */
+ opcua_reqid = tvb_get_letohl(tvb, offset + 4); /* Sequence.RequestId */
+ parseSequenceHeader(transport_tree, tvb, &offset, &metadata);
if (chunkType == 'A')
{
- fragment_delete(&opcua_reassembly_table, pinfo, opcua_seqid, NULL);
+ /* cancel chunk reassembly */
+ fragment_delete(&opcua_reassembly_table, pinfo, opcua_reqid, NULL);
col_clear_fence(pinfo->cinfo, COL_INFO);
col_set_str(pinfo->cinfo, COL_INFO, "Abort message");
offset = 0;
- (*pfctParse)(transport_tree, tvb, pinfo, &offset);
- parseAbort(transport_tree, tvb, pinfo, &offset);
+ (*pfctParse)(transport_tree, tvb, pinfo, &offset, &metadata);
+ parseAbort(transport_tree, tvb, pinfo, &offset, &metadata);
return tvb_reported_length(tvb);
}
/* check if tvb is part of a chunked message:
the UA protocol does not tell us that, so we look into
- opcua_reassembly_table if the opcua_seqid belongs to a
+ opcua_reassembly_table if the opcua_reqid belongs to a
chunked message */
- frag_msg = fragment_get(&opcua_reassembly_table, pinfo, opcua_seqid, NULL);
+ frag_msg = fragment_get(&opcua_reassembly_table, pinfo, opcua_reqid, NULL);
if (frag_msg == NULL)
{
- frag_msg = fragment_get_reassembled_id(&opcua_reassembly_table, pinfo, opcua_seqid);
+ frag_msg = fragment_get_reassembled_id(&opcua_reassembly_table, pinfo, opcua_reqid);
}
- if (frag_msg != NULL || chunkType != 'F')
+ if (frag_msg != NULL || chunkType == 'C')
{
- gboolean bSaveFragmented = pinfo->fragmented;
- gboolean bMoreFragments = TRUE;
- tvbuff_t *new_tvb = NULL;
+ bool bSaveFragmented = pinfo->fragmented;
+ bool bMoreFragments = true;
+ tvbuff_t *reassembled_tvb = NULL;
+ bool first_frag = false;
- pinfo->fragmented = TRUE;
+ pinfo->fragmented = true;
if (frag_msg == NULL)
{
- /* first fragment */
- opcua_seqnum = 0;
+ first_frag = true;
}
else
{
- /* the UA protocol does not number the chunks beginning from 0 but from a
- arbitrary value, so we have to fake the numbers in the stored fragments.
- this way Wireshark reassembles the message, as it expects the fragment
- sequence numbers to start at 0 */
- for (frag_i = frag_msg->next; frag_i && frag_i->next; frag_i = frag_i->next) {}
- if (frag_i) {
- opcua_seqnum = frag_i->offset + 1;
- } else {
- /* We should never have a fragment head with no fragment items, but
- * just in case.
- */
- opcua_seqnum = 0;
- }
-
if (chunkType == 'F')
{
- bMoreFragments = FALSE;
+ bMoreFragments = false;
}
}
@@ -302,77 +740,100 @@ static int dissect_opcua_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *
tvb,
offset,
pinfo,
- opcua_seqid, /* ID for fragments belonging together */
+ opcua_reqid, /* ID for fragments belonging together */
NULL,
- opcua_seqnum, /* fragment sequence number */
- tvb_captured_length_remaining(tvb, offset), /* fragment length - to the end */
+ first_frag ? 0 : opcua_seqno, /* fragment sequence number */
+ payload_len,
bMoreFragments); /* More fragments? */
- new_tvb = process_reassembled_data(tvb,
+ if (first_frag) {
+ /* the UA protocol does not number the chunks beginning
+ * from 0 but uses the common sequence number. We
+ * handle that in Wireshark by setting the sequence
+ * offset here, after passing in 0 for the first
+ * fragment. For later fragments we can use the
+ * sequence number as contained in the protocol.
+ */
+
+ fragment_add_seq_offset(&opcua_reassembly_table, pinfo, opcua_reqid, NULL, opcua_seqno);
+ }
+ reassembled_tvb = process_reassembled_data(tvb,
offset,
pinfo,
- "Reassembled Message",
+ "Reassembled UA Message",
frag_msg,
&opcua_frag_items,
NULL,
transport_tree);
- if (new_tvb)
+ if (reassembled_tvb)
{
/* Reassembled */
- bIsLastFragment = TRUE;
- }
- else
- {
- /* Not last packet of reassembled UA message */
- col_append_fstr(pinfo->cinfo, COL_INFO, " (Message fragment %u)", opcua_num);
- }
-
- if (new_tvb)
- {
+ bIsFinalChunk = true;
/* take it all */
- next_tvb = new_tvb;
+ tvb = reassembled_tvb;
+ /* new tvb starts at payload */
+ offset = 0;
}
else
{
+ /* Not last packet of reassembled UA message */
+ col_append_fstr(pinfo->cinfo, COL_INFO, " (Message fragment %u)", opcua_seqno);
/* only show transport header */
- bParseService = FALSE;
- next_tvb = tvb_new_subset_remaining(tvb, 0);
+ bParseService = false;
+ tvb = tvb_new_subset_remaining(tvb, 0);
}
pinfo->fragmented = bSaveFragmented;
}
}
- offset = 0;
-
- /* call the transport message dissector */
- iServiceId = (*pfctParse)(transport_tree, tvb, pinfo, &offset);
-
- /* parse the service if not chunked or last chunk */
- if (msgtype == MSG_MESSAGE && bParseService)
- {
- if (bIsLastFragment != FALSE)
- {
- offset = 0;
- }
- iServiceId = parseService(transport_tree, next_tvb, pinfo, &offset);
- }
+ /* parse payload if not encrypted */
+ if (!metadata.encrypted && bParseService) {
+ if (msgtype == MSG_CLOSESECURECHANNEL) {
+ iServiceId = parseService(transport_tree, tvb, pinfo, &offset, &metadata);
+ if (iServiceId == OpcUaId_CloseSecureChannelRequest_Encoding_DefaultBinary) {
+ col_append_str(pinfo->cinfo, COL_INFO, ": CloseSecureChannelRequest");
+ } else if (iServiceId == OpcUaId_CloseSecureChannelResponse_Encoding_DefaultBinary) {
+ col_append_str(pinfo->cinfo, COL_INFO, ": CloseSecureChannelResponse");
+ } else {
+ const char *szServiceName = val_to_str((uint32_t)iServiceId, g_requesttypes, "ServiceId %d");
+ col_append_fstr(pinfo->cinfo, COL_INFO, ": %s (Wrong ServiceId)", szServiceName);
+ }
+ } else if (msgtype == MSG_MESSAGE) {
+ /* parse the service if not chunked or message was reassembled */
+ iServiceId = parseService(transport_tree, tvb, pinfo, &offset, &metadata);
- /* display the service type in addition to the message type */
- if (iServiceId != -1)
- {
- const gchar *szServiceName = val_to_str((guint32)iServiceId, g_requesttypes, "ServiceId %d");
+ /* display the service type in addition to the message type */
+ if (iServiceId != -1)
+ {
+ const char *szServiceName = val_to_str((uint32_t)iServiceId, g_requesttypes, "ServiceId %d");
- if (bIsLastFragment == FALSE)
- {
- col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s", g_szMessageTypes[msgtype], szServiceName);
+ if (bIsFinalChunk == false)
+ {
+ /* normal message in one chunk */
+ col_append_fstr(pinfo->cinfo, COL_INFO, ": %s", szServiceName);
+ }
+ else
+ {
+ /* reassembled message from multiple chunks */
+ col_append_fstr(pinfo->cinfo, COL_INFO, ": %s (Message Reassembled)", szServiceName);
+ }
+ }
}
- else
- {
- col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s (Message Reassembled)", g_szMessageTypes[msgtype], szServiceName);
+ if (mode == UA_MessageMode_SignAndEncrypt && decrypted) {
+ /* parse padding and signature */
+ parseSecurityFooterSAE(transport_tree, decrypted_tvb, 8 + payload_len, pad_len, sig_len);
+ } else if (mode == UA_MessageMode_Sign) {
+ /* parse signature */
+ parseSecurityFooterSO(transport_tree, decrypted_tvb, 24 + payload_len, sig_len);
}
}
+ if (metadata.encrypted) {
+ col_append_str(pinfo->cinfo, COL_INFO, " (encrypted)");
+ } else if (mode == UA_MessageMode_SignAndEncrypt) {
+ col_append_str(pinfo->cinfo, COL_INFO, " (decrypted)");
+ }
}
return tvb_reported_length(tvb);
@@ -384,11 +845,41 @@ static int dissect_opcua_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *
*/
static int dissect_opcua(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
{
- tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN,
+ tcp_dissect_pdus(tvb, pinfo, tree, true, FRAME_HEADER_LEN,
get_opcua_message_len, dissect_opcua_message, data);
return tvb_reported_length(tvb);
}
+/** Init plugin resources */
+void proto_init_opcua(void)
+{
+ debugprintf("proto_init_opcua called.\n");
+ ua_keysets_init();
+ opcua_load_keylog_file(g_opcua_debug_file_name);
+}
+
+/** Cleanup plugin resources */
+void proto_cleanup_opcua(void)
+{
+ debugprintf("proto_cleanup_opcua called.\n");
+ ua_keysets_clear();
+}
+
+/** secrets callback called from Wireshark when loading a capture file with OPC UA Keylog File. */
+static void opcua_secrets_block_callback(const void *secrets, unsigned size)
+{
+ char *tmp = g_memdup2(secrets, size + 1);
+ if (tmp == NULL) return; /* OOM */
+
+ debugprintf("Loading secrets block '%s'...\n", (const char*)secrets);
+ debugprintf("size = %u\n", size);
+ /* ensure data is zero terminated */
+ tmp[size] = 0;
+ /* parse data */
+ opcua_keylog_process_lines(tmp);
+ g_free(tmp);
+}
+
/** plugin entry functions.
* This registers the OpcUa protocol.
*/
@@ -410,7 +901,7 @@ void proto_register_opcua(void)
};
/** Setup protocol subtree array */
- static gint *ett[] =
+ static int *ett[] =
{
&ett_opcua_extensionobject,
&ett_opcua_nodeid,
@@ -422,8 +913,21 @@ void proto_register_opcua(void)
proto_opcua = proto_register_protocol("OpcUa Binary Protocol", "OpcUa", "opcua");
opcua_handle = register_dissector("opcua", dissect_opcua, proto_opcua);
+ register_init_routine(proto_init_opcua);
+ register_cleanup_routine(proto_cleanup_opcua);
+
+ opcua_module = prefs_register_protocol(proto_opcua, proto_reg_handoff_opcua);
+ prefs_register_filename_preference(opcua_module, "debug_file", "OPCUA debug file",
+ "Redirect OPC UA Secure Conversion session keys to the file specified to enable decryption.",
+ &g_opcua_debug_file_name, false);
+
+ prefs_register_enum_preference(opcua_module, "signature_length", "Default signature length",
+ "Default signature length to use if the OpenSecureChannel message is missing.",
+ &g_opcua_default_sig_len, opcua_sig_len_enum, false);
+
registerTransportLayerTypes(proto_opcua);
registerSecurityLayerTypes(proto_opcua);
+ registerSequenceLayerTypes(proto_opcua);
registerApplicationLayerTypes(proto_opcua);
registerSimpleTypes(proto_opcua);
registerEnumTypes(proto_opcua);
@@ -436,6 +940,7 @@ void proto_register_opcua(void)
reassembly_table_register(&opcua_reassembly_table,
&addresses_reassembly_table_functions);
+ secrets_register_type(SECRETS_TYPE_OPCUA, opcua_secrets_block_callback);
}
void proto_reg_handoff_opcua(void)