summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--epan/dissectors/packet-ssl-utils.h7
-rw-r--r--epan/dissectors/packet-ssl.c220
2 files changed, 211 insertions, 16 deletions
diff --git a/epan/dissectors/packet-ssl-utils.h b/epan/dissectors/packet-ssl-utils.h
index 17e539d7f9..50490c40ee 100644
--- a/epan/dissectors/packet-ssl-utils.h
+++ b/epan/dissectors/packet-ssl-utils.h
@@ -421,6 +421,13 @@ typedef struct _SslSession {
dissector_handle_t app_handle;
guint32 last_nontls_frame;
gboolean is_session_resumed;
+
+ /* Tracks handshake reassembly during the first pass. */
+#if 0
+ guint32 hs_seqno;
+#endif
+ guint32 hs_total_length;
+ guint32 hs_current_length;
} SslSession;
/* RFC 5246, section 8.1 says that the master secret is always 48 bytes */
diff --git a/epan/dissectors/packet-ssl.c b/epan/dissectors/packet-ssl.c
index 8b1bad7765..389713da9c 100644
--- a/epan/dissectors/packet-ssl.c
+++ b/epan/dissectors/packet-ssl.c
@@ -343,6 +343,8 @@ void proto_reg_handoff_ssl(void);
/* Desegmentation of SSL streams */
/* table to hold defragmented SSL streams */
static reassembly_table ssl_reassembly_table;
+/* table to hold (reassembled) fragments for handshake messages */
+static reassembly_table ssl_handshake_reassembly_table;
/* initialize/reset per capture state data (ssl sessions cache) */
static void
@@ -2242,6 +2244,13 @@ process_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo,
* A single record can have multiple handshake messages and a single
* handshake message can span multiple records. This function uses a loop to
* handle the former and reassembly to handle the latter.
+ *
+ * Handshake reassembly uses a sequence number to identify a handshake msg.
+ * In the first pass, the sequence number of the first msg is set in the
+ * record.
+ *
+ * If we have a previous partial handshake message, assume plaintext.
+ * Otherwise we should check for encrypted messages that must be rejected.
*/
guint32 offset_end = offset + record_length;
const gchar *msg_type_str;
@@ -2249,6 +2258,38 @@ process_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo,
guint32 length;
gboolean is_first_message = TRUE;
gboolean is_continuation = FALSE;
+ guint32 hs_seqno;
+ guint32 msg_len;
+ fragment_head *fh;
+
+ /*
+ * TODO currently only one fragmented handshake message is supported per
+ * session which works if the only fragmented message is Certificate. But
+ * to support more cases, it should track the actual handshake msg seq no.
+ */
+ hs_seqno = GPOINTER_TO_UINT(session);
+
+ if (!PINFO_FD_VISITED(pinfo)) {
+ /*
+ * First pass: this record is a continuation if the previous handshake
+ * message was not fully seen.
+ */
+ is_continuation = session->hs_total_length && session->hs_current_length != session->hs_total_length;
+ } else {
+ fh = fragment_get_reassembled_id(&ssl_handshake_reassembly_table, pinfo, hs_seqno);
+ if (fh) {
+ /*
+ * Assume that the begin of this record is a continuation when a
+ * reassembled packet exists and when this reassembly was completed
+ * in this frame.
+ * TODO if there are multiple records per frame (possibly due to
+ * reassembled TCP segments), then it should also check the offset
+ * within the frame. Otherwise it might display the same handshake
+ * message multiple times. Perhaps by using reas_in_layer_num?
+ */
+ is_continuation = fh->reassembled_in != pinfo->num;
+ }
+ }
if (!is_continuation) {
/*
@@ -2260,6 +2301,7 @@ process_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo,
* encrypted contents for GCM/CCM cipher suites as used in TLS 1.2.
*/
if (maybe_encrypted) {
+ /* TODO this marks AEAD as encrypted, but also random IV as plaintext */
maybe_encrypted = tvb_bytes_exist(tvb, offset, 5) && tvb_get_ntoh40(tvb, offset) == 0;
}
if (maybe_encrypted) {
@@ -2283,33 +2325,177 @@ process_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo,
}
while (offset < offset_end) {
- msg_type = tvb_get_guint8(tvb, offset);
- length = tvb_get_ntoh24(tvb, offset + 1);
+ guint frag_len = offset_end - offset;
+ tvbuff_t *sub_tvb = NULL;
- /* Check the length in the handshake message. Assume it's an
- * encrypted handshake message if the message would pass
- * the record_length boundary. This is a workaround for the
- * situation where the first octet of the encrypted handshake
- * message is actually a known handshake message type.
+ /*
+ * 1. Read message length (if enough bytes are available).
*/
- if (!maybe_encrypted && offset + 4 + length <= offset_end)
+ if (!is_continuation) {
+ if (offset_end - offset >= 4) {
+ /* Not a continuation, length is available. */
+ length = tvb_get_ntoh24(tvb, offset + 1);
+ msg_len = 4 + length;
+ } else {
+ msg_len = 0; /* length unknown */
+ }
+
+ msg_type = tvb_get_guint8(tvb, offset);
msg_type_str = try_val_to_str(msg_type, ssl_31_handshake_type);
- else
- msg_type_str = NULL;
+ if (!msg_type_str) {
+ /*
+ * Unknown handshake message type, perhaps this is encrypted
+ * data or something else went wrong. Do not try to start
+ * reassembly and just stop here.
+ */
+ return;
+ }
+ } else {
+ if (!PINFO_FD_VISITED(pinfo)) {
+ /*
+ * Continuation, so a previous fragment *must* exist.
+ * This fragment head is also used in the next step.
+ */
+ fh = fragment_get(&ssl_handshake_reassembly_table, pinfo, hs_seqno, NULL);
+ DISSECTOR_ASSERT(fh);
- if (!msg_type_str) {
- /* only dissect further messages if the message type is valid. */
- return;
+ /*
+ * Keep adding fragments until the length can be extracted.
+ */
+ guint needed = 4;
+ tvbuff_t *len_tvb = tvb_new_composite();
+ /* Add head of fragment */
+ tvb_composite_append(len_tvb, fh->tvb_data);
+ needed -= MIN(needed, tvb_reported_length(fh->tvb_data));
+ /* Add remaining fragments. */
+ for (fragment_item *fd = fh->next; needed > 0 && fd; fd = fd->next) {
+ tvb_composite_append(len_tvb, fd->tvb_data);
+ needed -= MIN(needed, tvb_reported_length(fd->tvb_data));
+ }
+ /* Add data from current record. */
+ if (needed > 0) {
+ tvbuff_t *remaining_tvb = tvb_new_subset_remaining(tvb, offset);
+ tvb_composite_append(len_tvb, remaining_tvb);
+ needed -= MIN(needed, tvb_reported_length(remaining_tvb));
+ }
+ tvb_composite_finalize(len_tvb);
+
+ if (needed == 0) {
+ length = tvb_get_ntoh24(len_tvb, 1);
+ msg_len = 4 + length;
+ } else {
+ /* Not enough data to read the length. */
+ msg_len = 0; /* length unknown */
+ }
+ } else {
+ /*
+ * Second pass with continuation, if everything went OK we
+ * should have a reassembled message now.
+ */
+ fh = fragment_get_reassembled_id(&ssl_handshake_reassembly_table, pinfo, hs_seqno);
+ if (fh) {
+ msg_len = tvb_captured_length(fh->tvb_data);
+ } else {
+ /*
+ * Not enough fragments are received for reassembly.
+ */
+ msg_len = 0; /* length unknown */
+ }
+ }
+ }
+
+ /*
+ * 2. Consume the record data, using fragments reassembly if necessary.
+ * If all fragments are available, extract the handshake message TVB.
+ * Set the next state for the "is_continuation" flag.
+ */
+ if (!PINFO_FD_VISITED(pinfo)) {
+ /*
+ * Find the fragment offset (length of previous fragments, or 0 if
+ * this is not a continuation) and the fragment length (the subset
+ * of the current record data that must be added).
+ *
+ * If at this point the length is not yet known, the full record
+ * fragment must be added for reassembly.
+ */
+ guint frag_offset = 0;
+ if (msg_len != 0) {
+ if (!is_continuation) {
+ frag_len = MIN(frag_len, msg_len);
+ } else {
+ /* fh is already set when extracting the length. */
+#if 0
+ fh = fragment_get(&ssl_handshake_reassembly_table, pinfo, hs_seqno, NULL);
+ DISSECTOR_ASSERT(fh);
+#endif
+ frag_offset = fh->len;
+ for (fragment_item *fd = fh->next; fd; fd = fd->next) {
+ frag_offset += fd->len;
+ }
+ if (msg_len != 0) {
+ DISSECTOR_ASSERT(frag_offset < msg_len);
+ frag_len = MIN(frag_len, msg_len - frag_offset);
+ }
+ }
+ }
+
+ /*
+ * Either (1) continuation of previous message or (2) not enough
+ * data for length or (3) begin of new message. Store the current
+ * fragment for reassembly.
+ */
+ if (is_continuation || msg_len == 0 || frag_offset + frag_len < msg_len) {
+ is_continuation = msg_len == 0 || frag_offset + frag_len < msg_len;
+ fh = fragment_add_seq_next(&ssl_handshake_reassembly_table, tvb, offset,
+ pinfo, hs_seqno, NULL, frag_len, is_continuation);
+ if (fh) {
+ /* Fully reassembled. */
+ sub_tvb = fh->tvb_data;
+ }
+ } else {
+ /*
+ * This must be a handshake message which fully fits
+ * in this record and does not need reassembly.
+ */
+ DISSECTOR_ASSERT(!is_continuation);
+ sub_tvb = tvb_new_subset_length(tvb, offset, msg_len);
+ }
+ } else {
+ /*
+ * Second pass, so no need to add new fragments.
+ * XXX multiple fragments in one record are currently not handled
+ * well for second pass since we do not know where the fragment
+ * starts... So we do not know what frag_len should become in case
+ * of reassembly.
+ */
+ if (is_continuation) {
+ fh = fragment_get_reassembled_id(&ssl_handshake_reassembly_table, pinfo, hs_seqno);
+ is_continuation = fh && fh->reassembled_in != pinfo->num;
+ if (fh) {
+ /* Fully reassembled. */
+ sub_tvb = fh->tvb_data;
+ }
+ } else {
+ /*
+ * Not a continuation, but we can dissect only if the full
+ * message is received.
+ */
+ if (msg_len != 0 && msg_len <= offset_end - offset) {
+ sub_tvb = tvb_new_subset_length(tvb, offset, msg_len);
+ }
+ }
}
/*
* We have a valid message type and received all data, start the
* actual handshake dissection.
*/
- dissect_ssl3_handshake(tvb, pinfo, tree, offset, session,
- is_from_server, ssl, version, is_first_message);
+ if (sub_tvb) {
+ dissect_ssl3_handshake(sub_tvb, pinfo, tree, 0, session,
+ is_from_server, ssl, version, is_first_message);
+ }
- offset += 4 + length;
+ offset += frag_len;
is_first_message = FALSE; /* set up for next pass, if any */
}
}
@@ -4465,6 +4651,8 @@ proto_register_ssl(void)
register_cleanup_routine(ssl_cleanup);
reassembly_table_register(&ssl_reassembly_table,
&addresses_ports_reassembly_table_functions);
+ reassembly_table_register(&ssl_handshake_reassembly_table,
+ &addresses_ports_reassembly_table_functions);
register_decode_as(&ssl_da);
/* XXX: this seems unused due to new "Follow SSL" method, remove? */