Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-nts-ke.c
blobec23ccc4c8b0a8030fac5c881ee6989cdbef14b0
1 /* packet-nts-ke.c
2 * Dissector for Network Time Security Key Establishment Protocol (RFC 8915)
4 * Copyright (c) 2024 by Martin Mayer <martin.mayer@m2-it-solutions.de>
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
13 #include "config.h"
14 #include "packet-tcp.h"
15 #include "packet-tls.h"
16 #include "packet-nts-ke.h"
17 #include <epan/packet.h>
18 #include <epan/tfs.h>
19 #include <epan/unit_strings.h>
20 #include <wsutil/str_util.h>
21 #include <epan/expert.h>
22 #include <epan/conversation.h>
24 #define TLS_PORT 4460
25 #define CRIT_TYPE_BODY_LEN 4
26 #define TYPE_MASK 0x7FFF
27 #define CRITICAL_MASK 0x8000
29 #define NTS_KE_EXPORTER_LABEL "EXPORTER-network-time-security"
30 #define NTS_KE_ALPN "ntske/1"
32 void proto_register_nts_ke(void);
33 void proto_reg_handoff_nts_ke(void);
35 static dissector_handle_t nts_ke_handle;
37 static int proto_nts_ke;
39 /* Fields */
40 static int hf_nts_ke_record;
41 static int hf_nts_ke_critical_bit;
42 static int hf_nts_ke_record_type;
43 static int hf_nts_ke_body_length;
44 static int hf_nts_ke_next_proto;
45 static int hf_nts_ke_error;
46 static int hf_nts_ke_warning;
47 static int hf_nts_ke_aead_algo;
48 static int hf_nts_ke_cookie;
49 static int hf_nts_ke_cookie_used_frame;
50 static int hf_nts_ke_server;
51 static int hf_nts_ke_port;
52 static int hf_nts_ke_response_in;
53 static int hf_nts_ke_response_to;
55 /* Expert fields */
56 static expert_field ei_nts_ke_critical_bit_missing;
57 static expert_field ei_nts_ke_record_after_end;
58 static expert_field ei_nts_ke_end_missing;
59 static expert_field ei_nts_ke_next_proto_illegal_count;
60 static expert_field ei_nts_ke_body_illegal;
61 static expert_field ei_nts_ke_body_length_illegal;
62 static expert_field ei_nts_ke_alpn_mismatch;
64 /* Prefs */
65 static bool nts_ke_extract_keys = true;
66 static bool nts_ke_chrony_compat_mode = false;
68 /* Trees */
69 static int ett_nts_ke;
70 static int ett_nts_ke_record;
72 #define RECORD_TYPE_END 0
73 #define RECORD_TYPE_NEXT 1
74 #define RECORD_TYPE_ERR 2
75 #define RECORD_TYPE_WARN 3
76 #define RECORD_TYPE_AEAD 4
77 #define RECORD_TYPE_COOKIE 5
78 #define RECORD_TYPE_NEG_SRV 6
79 #define RECORD_TYPE_NEG_PORT 7
80 #define RECORD_TYPE_UA_1_LOW 8
81 #define RECORD_TYPE_UA_1_HIGH 1023
82 #define RECORD_TYPE_COMP_GCM 1024
83 #define RECORD_TYPE_UA_2_LOW 1025
84 #define RECORD_TYPE_UA_2_HIGH 16383
85 #define RECORD_TYPE_RES_LOW 16384
86 #define RECORD_TYPE_RES_HIGH 32767
88 /* https://www.iana.org/assignments/nts/nts.xhtml#nts-key-establishment-record-types */
89 static const range_string nts_ke_record_types[] = {
90 { RECORD_TYPE_END, RECORD_TYPE_END, "End of Message" },
91 { RECORD_TYPE_NEXT, RECORD_TYPE_NEXT, "NTS Next Protocol Negotiation" },
92 { RECORD_TYPE_ERR, RECORD_TYPE_ERR, "Error" },
93 { RECORD_TYPE_WARN, RECORD_TYPE_WARN, "Warning" },
94 { RECORD_TYPE_AEAD, RECORD_TYPE_AEAD, "AEAD Algorithm Negotiation" },
95 { RECORD_TYPE_COOKIE, RECORD_TYPE_COOKIE, "New Cookie for NTPv4" },
96 { RECORD_TYPE_NEG_SRV, RECORD_TYPE_NEG_SRV, "NTPv4 Server Negotiation" },
97 { RECORD_TYPE_NEG_PORT, RECORD_TYPE_NEG_PORT, "NTPv4 Port Negotiation" },
98 { RECORD_TYPE_UA_1_LOW, RECORD_TYPE_UA_1_HIGH, "Unassigned" },
99 { RECORD_TYPE_COMP_GCM, RECORD_TYPE_COMP_GCM, "Compliant AES-128-GCM-SIV Exporter Context" },
100 { RECORD_TYPE_UA_2_LOW, RECORD_TYPE_UA_2_HIGH, "Unassigned" },
101 { RECORD_TYPE_RES_LOW, RECORD_TYPE_RES_HIGH, "Reserved" },
102 { 0, 0, NULL }
105 /* https://www.iana.org/assignments/nts/nts.xhtml#nts-error-codes */
106 static const range_string nts_ke_error_codes[] = {
107 { 0, 0, "Unrecognized Critical Record" },
108 { 1, 1, "Bad Request" },
109 { 2, 2, "Internal Server Error" },
110 { 3, 32767, "Unassigned" },
111 { 32768, 65535, "Reserved" },
112 { 0, 0, NULL }
115 /* https://www.iana.org/assignments/nts/nts.xhtml#nts-warning-codes */
116 static const range_string nts_ke_warning_codes[] = {
117 { 0, 32767, "Unassigned" },
118 { 32768, 65535, "Reserved" },
119 { 0, 0, NULL }
122 /* https://www.iana.org/assignments/nts/nts.xhtml#nts-next-protocols */
123 static const range_string nts_ke_next_proto_rvals[] = {
124 { 0, 0, "NTPv4" },
125 { 1, 32767, "Unassigned" },
126 { 32768, 65535, "Reserved" },
127 { 0, 0, NULL }
130 /* https://www.iana.org/assignments/aead-parameters/ */
131 static const range_string nts_ke_aead_rvals[] = {
132 { 1, 1, "AEAD_AES_128_GCM" },
133 { 2, 2, "AEAD_AES_256_GCM" },
134 { 3, 3, "AEAD_AES_128_CCM" },
135 { 4, 4, "AEAD_AES_256_CCM" },
136 { 5, 5, "AEAD_AES_128_GCM_8" },
137 { 6, 6, "AEAD_AES_256_GCM_8" },
138 { 7, 7, "AEAD_AES_128_GCM_12" },
139 { 8, 8, "AEAD_AES_256_GCM_12" },
140 { 9, 9, "AEAD_AES_128_CCM_SHORT" },
141 { 10, 10, "AEAD_AES_256_CCM_SHORT" },
142 { 11, 11, "AEAD_AES_128_CCM_SHORT_8" },
143 { 12, 12, "AEAD_AES_256_CCM_SHORT_8" },
144 { 13, 13, "AEAD_AES_128_CCM_SHORT_12" },
145 { 14, 14, "AEAD_AES_256_CCM_SHORT_12" },
146 { 15, 15, "AEAD_AES_SIV_CMAC_256" },
147 { 16, 16, "AEAD_AES_SIV_CMAC_384" },
148 { 17, 17, "AEAD_AES_SIV_CMAC_512" },
149 { 18, 18, "AEAD_AES_128_CCM_8" },
150 { 19, 19, "AEAD_AES_256_CCM_8" },
151 { 20, 20, "AEAD_AES_128_OCB_TAGLEN128" },
152 { 21, 21, "AEAD_AES_128_OCB_TAGLEN96" },
153 { 22, 22, "AEAD_AES_128_OCB_TAGLEN64" },
154 { 23, 23, "AEAD_AES_192_OCB_TAGLEN128" },
155 { 24, 24, "AEAD_AES_192_OCB_TAGLEN96" },
156 { 25, 25, "AEAD_AES_192_OCB_TAGLEN64" },
157 { 26, 26, "AEAD_AES_256_OCB_TAGLEN128" },
158 { 27, 27, "AEAD_AES_256_OCB_TAGLEN96" },
159 { 28, 28, "AEAD_AES_256_OCB_TAGLEN64" },
160 { 29, 29, "AEAD_CHACHA20_POLY1305" },
161 { 30, 30, "AEAD_AES_128_GCM_SIV" },
162 { 31, 31, "AEAD_AES_256_GCM_SIV" },
163 { 32, 32, "AEAD_AEGIS128L" },
164 { 33, 33, "AEAD_AEGIS256" },
165 { 34, 32767, "Unassigned" },
166 { 32768, 65535, "Reserved for Private Use" },
167 { 0, 0, NULL }
170 /* All supported AEAD
171 * Note: Key length is limited in NTS_KE_TLS13_KEY_MAX_LEN
173 * Only the following algos have been seen in the wild and were tested.
174 * Extending the supported algos can be easily done by extending this list.
175 * Think of looking at NTP ntp_decrypt_nts() when adding new algos, because
176 * different GCRY modes may require different handling.
178 * All crypto functions will need GCRYPT >= 1.10.0 because
179 * GCRY_CIPHER_MODE_SIV is a mandatory algorithm. If'ing out SIV algos
180 * to compile sucessfully without GCRYPT support.
182 static const nts_aead nts_ke_aead_gcry_map[] = {
183 #if GCRYPT_VERSION_NUMBER >= 0x010a00
184 { 15, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_SIV, 32, 16 },
185 { 16, GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_SIV, 48, 16 },
186 { 17, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_SIV, 64, 16 },
187 { 30, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM_SIV, 16, 16 },
188 #endif
189 { 0, 0, 0, 0, 0 }
192 const nts_aead *
193 nts_find_aead(uint16_t id)
195 const nts_aead *c;
196 for(c = nts_ke_aead_gcry_map; c->id !=0 ; c++){
197 if(c->id == id){
198 return c;
201 return NULL;
204 /* Request/response tracking */
205 typedef struct _nts_ke_req_resp_t {
206 uint32_t req_frame;
207 uint32_t resp_frame;
208 } nts_ke_req_resp_t;
210 /* Cookie map */
211 static wmem_map_t *nts_cookies;
213 struct nts_ke_uid_lookup {
214 uint32_t uid_hash;
215 nts_cookie_t *cookie;
218 static int
219 nts_find_list_callback(const void *a, const void *b)
221 const uint32_t *x = (const uint32_t*)a;
222 if(*x == GPOINTER_TO_UINT(b)) return 0; else return -1;
225 static void
226 nts_uid_lookup_callback(void *key _U_, void *val, void *userdata)
228 nts_cookie_t *current_cookie = (nts_cookie_t *)val;
229 struct nts_ke_uid_lookup *func_data = (struct nts_ke_uid_lookup *)userdata;
231 if(wmem_list_find_custom(current_cookie->frames_used_uid, GUINT_TO_POINTER(func_data->uid_hash), nts_find_list_callback))
232 func_data->cookie = current_cookie;
235 void
236 nts_append_used_frames_to_tree(void *data, void *user_data)
238 proto_item *ct;
240 uint32_t *pnum = (uint32_t *)data;
241 nts_used_frames_lookup_t *func_data = (nts_used_frames_lookup_t *)user_data;
243 ct = proto_tree_add_uint(func_data->tree, func_data->hfindex, func_data->tvb, 0, 0, *pnum);
244 proto_item_set_generated(ct);
247 nts_cookie_t*
248 nts_new_cookie(tvbuff_t *tvb, uint16_t aead, packet_info *pinfo)
250 unsigned int cookie_len = tvb_reported_length(tvb);
251 unsigned char *key_c2s = (char *)wmem_alloc0(pinfo->pool, NTS_KE_TLS13_KEY_MAX_LEN);
252 unsigned char *key_s2c = (char *)wmem_alloc0(pinfo->pool, NTS_KE_TLS13_KEY_MAX_LEN);
253 uint8_t *tvb_bytes;
254 nts_cookie_t *cookie;
255 uint32_t strong_hash;
256 const nts_aead *aead_entry;
257 bool k1, k2;
258 uint8_t ex_context_s2c[5], ex_context_c2s[5];
260 /* Build exporter context
261 * We only support NTPv4 - Context begins with 0x0000 (NTPv4 protocol ID)
262 * Followed by two bytes = AEAD ID
263 * Followed by one byte (0x00 = C2S key, 0x01 = S2C key)
265 * RFC 8915 5.1:
266 * The per-association context value [RFC5705] SHALL consist of the following five octets:
268 * - The first two octets SHALL be zero (the Protocol ID for NTPv4).
269 * - The next two octets SHALL be the Numeric Identifier of the negotiated AEAD algorithm in network byte order.
270 * - The final octet SHALL be 0x00 for the C2S key and 0x01 for the S2C key.
272 * Chrony is using a hard-coded context of AEAD_AES_SIV_CMAC_256 while also supporting AEAD_AES_128_GCM_SIV:
273 * - C2S 0x0000000F00
274 * - S2C 0x0000000F01
276 * As this is a breaking compatibility bug, offer a compatibility mode as preference.
277 * See: https://gitlab.com/chrony/chrony/-/issues/12
279 ex_context_c2s[0] = 0x00;
280 ex_context_c2s[1] = 0x00;
281 ex_context_c2s[2] = (uint8_t)(aead >> 8);
282 ex_context_c2s[3] = (uint8_t)aead;
283 ex_context_c2s[4] = 0x00;
285 ex_context_s2c[0] = 0x00;
286 ex_context_s2c[1] = 0x00;
287 ex_context_s2c[2] = (uint8_t)(aead >> 8);
288 ex_context_s2c[3] = (uint8_t)aead;
289 ex_context_s2c[4] = 0x01;
291 if(nts_ke_chrony_compat_mode && aead == 30) {
292 ex_context_c2s[2] = 0x00;
293 ex_context_c2s[3] = 0x0F;
294 ex_context_s2c[2] = 0x00;
295 ex_context_s2c[3] = 0x0F;
298 aead_entry = nts_find_aead(aead);
300 if(cookie_len < 1 || !aead_entry)
301 return NULL;
303 tvb_bytes = (uint8_t *)tvb_memdup(pinfo->pool, tvb, 0, cookie_len);
304 strong_hash = wmem_strong_hash(tvb_bytes, cookie_len);
306 cookie = (nts_cookie_t*) wmem_map_lookup(nts_cookies, GUINT_TO_POINTER(strong_hash));
308 /* Cookie was not found, add it */
309 if(!cookie) {
311 /* Extract keys */
312 k1 = tls13_exporter(pinfo, false,
313 NTS_KE_EXPORTER_LABEL, ex_context_c2s,
314 sizeof(ex_context_c2s), aead_entry->key_len, &key_c2s);
315 k2 = tls13_exporter(pinfo, false,
316 NTS_KE_EXPORTER_LABEL, ex_context_s2c,
317 sizeof(ex_context_s2c), aead_entry->key_len, &key_s2c);
319 cookie = wmem_new(wmem_file_scope(), nts_cookie_t);
321 cookie->frame_received = pinfo->num;
322 cookie->frames_used = wmem_list_new(wmem_file_scope());
323 cookie->frames_used_uid = wmem_list_new(wmem_file_scope());
324 cookie->aead = aead;
325 if(k1 && k2) {
326 cookie->keys_present = true;
327 memcpy(cookie->key_c2s, key_c2s, aead_entry->key_len);
328 memcpy(cookie->key_s2c, key_s2c, aead_entry->key_len);
329 } else {
330 cookie->keys_present = false;
333 wmem_map_insert(nts_cookies, GUINT_TO_POINTER(strong_hash), cookie);
336 return cookie;
339 nts_cookie_t*
340 nts_new_cookie_copy(tvbuff_t *tvb, nts_cookie_t *ref_cookie, packet_info *pinfo)
342 unsigned int cookie_len = tvb_reported_length(tvb);
343 uint8_t *tvb_bytes;
344 nts_cookie_t *cookie;
345 uint32_t strong_hash;
346 const nts_aead *aead_entry;
348 aead_entry = nts_find_aead(ref_cookie->aead);
350 if(cookie_len < 1 || !aead_entry)
351 return NULL;
353 tvb_bytes = (uint8_t *)tvb_memdup(pinfo->pool, tvb, 0, cookie_len);
354 strong_hash = wmem_strong_hash(tvb_bytes, cookie_len);
356 cookie = (nts_cookie_t*) wmem_map_lookup(nts_cookies, GUINT_TO_POINTER(strong_hash));
358 /* Cookie was not found, add it */
359 if(!cookie) {
361 cookie = wmem_new(wmem_file_scope(), nts_cookie_t);
363 cookie->frame_received = pinfo->num;
364 cookie->frames_used = wmem_list_new(wmem_file_scope());
365 cookie->frames_used_uid = wmem_list_new(wmem_file_scope());
366 cookie->aead = ref_cookie->aead;
367 if(ref_cookie->keys_present) {
368 cookie->keys_present = true;
369 memcpy(cookie->key_c2s, ref_cookie->key_c2s, aead_entry->key_len);
370 memcpy(cookie->key_s2c, ref_cookie->key_s2c, aead_entry->key_len);
371 } else {
372 cookie->keys_present = false;
375 wmem_map_insert(nts_cookies, GUINT_TO_POINTER(strong_hash), cookie);
378 return cookie;
381 nts_cookie_t* nts_use_cookie(tvbuff_t *tvb_cookie, tvbuff_t *tvb_uid, packet_info *pinfo)
383 unsigned int cookie_len = tvb_reported_length(tvb_cookie);
384 unsigned int uid_len = tvb_reported_length(tvb_uid);
386 uint8_t *tvb_cookie_bytes, *tvb_uid_bytes;
387 nts_cookie_t *cookie;
389 uint32_t strong_hash_cookie, strong_hash_uid;
390 uint32_t *pnum, *uid;
392 if(cookie_len < 1 || uid_len < 1)
393 return NULL;
395 /* Hash cookie and UID */
396 tvb_cookie_bytes = (uint8_t *)tvb_memdup(pinfo->pool, tvb_cookie, 0, cookie_len);
397 strong_hash_cookie = wmem_strong_hash(tvb_cookie_bytes, cookie_len);
399 tvb_uid_bytes = (uint8_t *)tvb_memdup(pinfo->pool, tvb_uid, 0, uid_len);
400 strong_hash_uid = wmem_strong_hash(tvb_uid_bytes, uid_len);
402 /* Find cookie by hash */
403 cookie = (nts_cookie_t*) wmem_map_lookup(nts_cookies, GUINT_TO_POINTER(strong_hash_cookie));
405 if(cookie) {
406 /* In theory a cookie can be used multiple times, so remember all packets which used it */
407 if(!wmem_list_find_custom(cookie->frames_used, GUINT_TO_POINTER(pinfo->num), nts_find_list_callback)) {
408 pnum = wmem_new0(wmem_file_scope(), uint32_t);
409 wmem_list_append(cookie->frames_used, pnum);
410 *pnum = pinfo->num;
413 if(!wmem_list_find_custom(cookie->frames_used_uid, GUINT_TO_POINTER(strong_hash_uid), nts_find_list_callback)) {
414 uid = wmem_new0(wmem_file_scope(), uint32_t);
415 wmem_list_append(cookie->frames_used_uid, uid);
416 *uid = strong_hash_uid;
420 return cookie;
423 nts_cookie_t*
424 nts_find_cookie_by_uid(tvbuff_t *tvb_uid)
426 unsigned int uid_len = tvb_reported_length(tvb_uid);
428 uint8_t *tvb_uid_bytes;
429 struct nts_ke_uid_lookup lookup;
431 if(uid_len < 1)
432 return NULL;
434 /* Hash UID */
435 tvb_uid_bytes = (uint8_t *)tvb_memdup(wmem_packet_scope(), tvb_uid, 0, uid_len);
436 lookup.uid_hash = wmem_strong_hash(tvb_uid_bytes, uid_len);
437 lookup.cookie = NULL;
439 /* Find cookie by UID hash */
440 wmem_map_foreach(nts_cookies, nts_uid_lookup_callback, &lookup);
442 return lookup.cookie;
445 static int
446 dissect_nts_ke(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
448 int offset;
449 uint16_t critical, type;
450 uint32_t body_length, body_counter, aead = 0;
451 uint32_t counter_next_proto_recs = 0, counter_aead = 0, counter_cookies = 0;
452 uint32_t *next_proto_list_item;
453 proto_item *ti, *ti_record, *rt;
454 proto_tree *nts_ke_tree, *record_tree;
455 bool critical_bool, end_record = false;
456 bool request, direction_determined = false;
457 wmem_list_t *next_protos = wmem_list_new(pinfo->pool);
458 conversation_t *conv;
459 nts_ke_req_resp_t *conv_data;
460 nts_cookie_t *cookie;
461 struct tcp_analysis *tcp_conv;
462 nts_used_frames_lookup_t lookup_data = {.tvb = tvb, .hfindex = hf_nts_ke_cookie_used_frame};
464 offset = 0;
466 col_set_str(pinfo->cinfo, COL_PROTOCOL, "NTS-KE");
467 col_clear(pinfo->cinfo,COL_INFO);
469 ti = proto_tree_add_item(tree, proto_nts_ke, tvb, 0, 0, ENC_NA);
470 nts_ke_tree = proto_item_add_subtree(ti, ett_nts_ke);
472 /* Error on ALPN mismatch */
473 if(strcmp(tls_get_alpn(pinfo), NTS_KE_ALPN) != 0)
474 expert_add_info(pinfo, nts_ke_tree, &ei_nts_ke_alpn_mismatch);
476 /* Conversation init */
477 conv = find_or_create_conversation(pinfo);
478 conv_data = (nts_ke_req_resp_t *)conversation_get_proto_data(conv, proto_nts_ke);
480 /* As NTS-KE has no client/server distinction. We need to rely on TCP.
481 * We can be sure that TCP has identified the server port,
482 * so we just need to compare it with our packet's destination port
483 * to identify the direction.
485 tcp_conv = get_tcp_conversation_data_idempotent(conv);
486 if(tcp_conv && pinfo->destport == tcp_conv->server_port) {
487 direction_determined = true;
488 request = true;
489 } else if (tcp_conv && pinfo->srcport == tcp_conv->server_port) {
490 direction_determined = true;
491 request = false;
494 if (direction_determined) {
495 if (!conv_data) {
496 conv_data = wmem_new(wmem_file_scope(), nts_ke_req_resp_t);
497 conv_data->req_frame = request ? pinfo->num : 0;
498 conv_data->resp_frame = !request ? pinfo->num : 0;
499 conversation_add_proto_data(conv, proto_nts_ke, conv_data);
500 } else {
501 conv_data->req_frame = request ? pinfo->num : conv_data->req_frame;
502 conv_data->resp_frame = !request ? pinfo->num : conv_data->resp_frame;
506 while(tvb_reported_length_remaining(tvb, offset) >= CRIT_TYPE_BODY_LEN) {
508 /* Reset pointer */
509 cookie = NULL;
511 ti_record = proto_tree_add_item(nts_ke_tree, hf_nts_ke_record, tvb, offset, 0, ENC_NA);
512 record_tree = proto_item_add_subtree(ti_record, ett_nts_ke_record);
514 critical = tvb_get_uint16(tvb, offset, ENC_BIG_ENDIAN) & CRITICAL_MASK;
515 critical_bool = (bool)(critical >> 15);
516 proto_tree_add_boolean(record_tree, hf_nts_ke_critical_bit, tvb, offset, 2, critical);
518 type = tvb_get_uint16(tvb, offset, ENC_BIG_ENDIAN) & TYPE_MASK;
519 proto_tree_add_uint(record_tree, hf_nts_ke_record_type, tvb, offset, 2, type);
520 proto_item_append_text(ti_record, " (%s)", rval_to_str_const(type, nts_ke_record_types, "Unknown Record Type"));
521 offset += 2;
523 proto_tree_add_item_ret_uint(record_tree, hf_nts_ke_body_length, tvb, offset, 2, ENC_BIG_ENDIAN, &body_length);
524 offset += 2;
526 if(end_record)
527 expert_add_info(pinfo, record_tree, &ei_nts_ke_record_after_end);
529 body_counter = 0;
531 switch (type) {
532 case RECORD_TYPE_END:
534 /* No body allowed */
535 if(body_length > 0) {
536 call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
537 expert_add_info(pinfo, record_tree, &ei_nts_ke_body_illegal);
538 offset += body_length;
541 /* Critical bit is mandatory for this type */
542 if(!critical_bool)
543 expert_add_info(pinfo, record_tree, &ei_nts_ke_critical_bit_missing);
545 /* Mark end record as seen */
546 end_record = true;
548 break;
550 case RECORD_TYPE_NEXT:
552 while(body_counter < body_length) {
553 uint32_t next_proto;
554 proto_tree_add_item_ret_uint(record_tree, hf_nts_ke_next_proto, tvb, offset, 2, ENC_BIG_ENDIAN, &next_proto);
555 offset += 2;
556 body_counter += 2;
558 /* Store list of offered/accepted next protocols */
559 next_proto_list_item = wmem_new0(pinfo->pool, uint32_t);
560 wmem_list_append(next_protos, next_proto_list_item);
561 *next_proto_list_item = next_proto;
563 col_append_str(pinfo->cinfo, COL_INFO, rval_to_str_const(next_proto, nts_ke_next_proto_rvals, "Unknown Proto"));
566 /* Critical bit is mandatory for this type */
567 if(!critical_bool)
568 expert_add_info(pinfo, record_tree, &ei_nts_ke_critical_bit_missing);
570 counter_next_proto_recs++;
572 break;
574 case RECORD_TYPE_ERR:
576 /* Fixed body length */
577 if(body_length == 2) {
578 proto_tree_add_item(record_tree, hf_nts_ke_error, tvb, offset, body_length, ENC_BIG_ENDIAN);
579 } else {
580 call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
581 expert_add_info(pinfo, record_tree, &ei_nts_ke_body_length_illegal);
583 offset += body_length;
585 /* Critical bit is mandatory for this type */
586 if(!critical_bool)
587 expert_add_info(pinfo, record_tree, &ei_nts_ke_critical_bit_missing);
589 break;
591 case RECORD_TYPE_WARN:
593 /* Fixed body length */
594 if(body_length == 2) {
595 proto_tree_add_item(record_tree, hf_nts_ke_warning, tvb, offset, body_length, ENC_BIG_ENDIAN);
596 } else {
597 call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
598 expert_add_info(pinfo, record_tree, &ei_nts_ke_body_length_illegal);
600 offset += body_length;
602 /* Critical bit is mandatory for this type */
603 if(!critical_bool)
604 expert_add_info(pinfo, record_tree, &ei_nts_ke_critical_bit_missing);
606 break;
608 case RECORD_TYPE_AEAD:
610 while(body_counter < body_length) {
612 proto_tree_add_item_ret_uint(record_tree, hf_nts_ke_aead_algo, tvb, offset, 2, ENC_BIG_ENDIAN, &aead);
613 offset += 2;
614 body_counter += 2;
615 counter_aead++;
618 break;
620 case RECORD_TYPE_COOKIE:
622 /* Arbitrary body data
624 * Other dissectors (e.g. NTP) need to access this data along it's extracted keys.
625 * Add NTS cookies if NTP (0x00) is part of next protos
627 if (
628 nts_ke_extract_keys &&
629 aead > 0 &&
630 wmem_list_find_custom(next_protos, GUINT_TO_POINTER(0x00), nts_find_list_callback)
632 cookie = nts_new_cookie(tvb_new_subset_length(tvb, offset, body_length), (uint16_t)aead, pinfo);
634 proto_tree_add_item(record_tree, hf_nts_ke_cookie, tvb, offset, body_length, ENC_NA);
635 offset += body_length;
636 counter_cookies++;
638 if(cookie) {
639 /* List all packets which made use of that cookie */
640 lookup_data.tree = record_tree;
641 wmem_list_foreach(cookie->frames_used, nts_append_used_frames_to_tree, &lookup_data);
644 break;
646 case RECORD_TYPE_NEG_SRV:
648 /* Arbitrary string */
649 proto_tree_add_item(record_tree, hf_nts_ke_server, tvb, offset, body_length, ENC_ASCII);
650 offset += body_length;
652 break;
654 case RECORD_TYPE_NEG_PORT:
656 /* Fixed body length */
657 if(body_length == 2) {
658 proto_tree_add_item(record_tree, hf_nts_ke_port, tvb, offset, body_length, ENC_BIG_ENDIAN);
659 } else {
660 call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
661 expert_add_info(pinfo, record_tree, &ei_nts_ke_body_length_illegal);
663 offset += body_length;
665 break;
667 default:
669 call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
670 offset += body_length;
672 break;
675 proto_item_set_end(ti_record, tvb, offset);
678 /* Request/Response */
679 if(conv_data && direction_determined) {
680 if(request && conv_data->resp_frame > 0) {
681 rt = proto_tree_add_uint(nts_ke_tree, hf_nts_ke_response_in, tvb, 0, 0, conv_data->resp_frame);
682 proto_item_set_generated(rt);
683 } else if (!request && conv_data->req_frame > 0) {
684 rt = proto_tree_add_uint(nts_ke_tree, hf_nts_ke_response_to, tvb, 0, 0, conv_data->req_frame);
685 proto_item_set_generated(rt);
689 /* Info columns text */
690 if(counter_aead > 0)
691 col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "%u AEAD Algorithm%s", counter_aead, plurality(counter_aead, "", "s"));
693 if(counter_cookies > 0)
694 col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "%u Cookie%s", counter_cookies, plurality(counter_cookies, "", "s"));
696 /* No end record found */
697 if(!end_record)
698 expert_add_info(pinfo, nts_ke_tree, &ei_nts_ke_end_missing);
700 /* Illegal AEAD record count */
701 if(counter_next_proto_recs != 1)
702 expert_add_info(pinfo, nts_ke_tree, &ei_nts_ke_next_proto_illegal_count);
704 proto_item_set_end(ti, tvb, offset);
706 return offset;
709 static unsigned
710 get_nts_ke_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
713 bool another_record = true;
714 unsigned size = 0;
716 /* Concat multiple records into one protocol tree */
717 while(another_record) {
719 /* Size is body length + 4 byte (CRIT_TYPE_BODY_LEN) */
720 unsigned pdu_size = tvb_get_uint16(tvb, offset + 2, ENC_BIG_ENDIAN) + CRIT_TYPE_BODY_LEN;
721 size += pdu_size;
723 if (tvb_captured_length_remaining(tvb, offset + pdu_size) < CRIT_TYPE_BODY_LEN)
724 another_record = false;
726 offset += pdu_size;
729 return size;
733 static int
734 dissect_nts_ke_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
736 if (!tvb_bytes_exist(tvb, 0, CRIT_TYPE_BODY_LEN))
737 return 0;
739 tcp_dissect_pdus(tvb, pinfo, tree, true, CRIT_TYPE_BODY_LEN, get_nts_ke_message_len, dissect_nts_ke, data);
740 return tvb_reported_length(tvb);
743 void
744 proto_register_nts_ke(void)
746 static hf_register_info hf[] = {
747 { &hf_nts_ke_record,
748 { "NTS-KE Record", "nts-ke.record",
749 FT_NONE, BASE_NONE,
750 NULL, 0x0,
751 NULL, HFILL }
753 { &hf_nts_ke_critical_bit,
754 { "Critical Bit", "nts-ke.critical_bit",
755 FT_BOOLEAN, 16,
756 TFS(&tfs_set_notset), CRITICAL_MASK,
757 NULL, HFILL }
759 { &hf_nts_ke_record_type,
760 { "Record Type", "nts-ke.type",
761 FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
762 RVALS(nts_ke_record_types), TYPE_MASK,
763 NULL, HFILL }
765 { &hf_nts_ke_body_length,
766 { "Body Length", "nts-ke.body_length",
767 FT_UINT16, BASE_DEC | BASE_UNIT_STRING,
768 UNS(&units_byte_bytes), 0x0,
769 NULL, HFILL }
771 { &hf_nts_ke_next_proto,
772 { "Next Protocol ID", "nts-ke.next_proto",
773 FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
774 RVALS(nts_ke_next_proto_rvals), 0x0,
775 NULL, HFILL }
777 { &hf_nts_ke_error,
778 { "Error Code", "nts-ke.error",
779 FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
780 RVALS(nts_ke_error_codes), 0x0,
781 NULL, HFILL }
783 { &hf_nts_ke_warning,
784 { "Warning Code", "nts-ke.warning",
785 FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
786 RVALS(nts_ke_warning_codes), 0x0,
787 NULL, HFILL }
789 { &hf_nts_ke_aead_algo,
790 { "AEAD Algorithm", "nts-ke.aead_algo",
791 FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
792 RVALS(nts_ke_aead_rvals), 0x0,
793 NULL, HFILL }
795 { &hf_nts_ke_cookie,
796 { "Cookie Data", "nts-ke.cookie",
797 FT_BYTES, BASE_NONE,
798 NULL, 0x0,
799 NULL, HFILL }
801 { &hf_nts_ke_cookie_used_frame, {
802 "Used cookie in", "nts-ke.cookie.use_frame",
803 FT_FRAMENUM, BASE_NONE,
804 NULL, 0,
805 NULL, HFILL }},
806 { &hf_nts_ke_server,
807 { "Server", "nts-ke.server",
808 FT_STRING, BASE_NONE,
809 NULL, 0x0,
810 NULL, HFILL }
812 { &hf_nts_ke_port,
813 { "Port", "nts-ke.port",
814 FT_UINT16, BASE_DEC,
815 NULL, 0x0,
816 NULL, HFILL }
818 { &hf_nts_ke_response_in,
819 { "Response In", "nts-ke.response_in",
820 FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0,
821 NULL, HFILL }
823 { &hf_nts_ke_response_to,
824 { "Response To", "nts-ke.response_to",
825 FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0,
826 NULL, HFILL }
830 static ei_register_info ei[] = {
831 { &ei_nts_ke_critical_bit_missing,
832 { "nts-ke.critical_bit.missing", PI_MALFORMED, PI_ERROR,
833 "Critical bit must be set for this record type", EXPFILL }
835 { &ei_nts_ke_record_after_end,
836 { "nts-ke.record.after_end", PI_MALFORMED, PI_ERROR,
837 "Illegal record after end of message", EXPFILL }
839 { &ei_nts_ke_end_missing,
840 { "nts-ke.end.missing", PI_MALFORMED, PI_ERROR,
841 "No end of message present", EXPFILL }
843 { &ei_nts_ke_body_illegal,
844 { "nts-ke.body.illegal", PI_MALFORMED, PI_ERROR,
845 "Illegal body data present", EXPFILL }
847 { &ei_nts_ke_body_length_illegal,
848 { "nts-ke.body_length.illegal", PI_MALFORMED, PI_ERROR,
849 "Illegal body length", EXPFILL }
851 { &ei_nts_ke_next_proto_illegal_count,
852 { "nts-ke.next_proto.illegal_count", PI_MALFORMED, PI_ERROR,
853 "Illegal Next Protocol record count", EXPFILL }
855 { &ei_nts_ke_alpn_mismatch,
856 { "nts-ke.alpn_mismatch", PI_DECRYPTION, PI_ERROR,
857 "TLS ALPN mismatch", EXPFILL }
861 static int *ett[] = {
862 &ett_nts_ke,
863 &ett_nts_ke_record
866 expert_module_t* expert_nts_ke;
867 module_t *nts_ke_module;
869 nts_cookies = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), g_direct_hash, g_direct_equal);
871 proto_nts_ke = proto_register_protocol ("NTS Key Establishment Protocol", "NTS-KE", "nts-ke");
873 proto_register_field_array(proto_nts_ke, hf, array_length(hf));
874 proto_register_subtree_array(ett, array_length(ett));
876 expert_nts_ke = expert_register_protocol(proto_nts_ke);
877 expert_register_field_array(expert_nts_ke, ei, array_length(ei));
879 nts_ke_module = prefs_register_protocol(proto_nts_ke, NULL);
880 prefs_register_bool_preference(nts_ke_module, "extract_keys",
881 "Extract S2C and C2S keys",
882 "Whether to extract client-to-server and server-to-client "
883 "keys for crypto-processing.",
884 &nts_ke_extract_keys);
885 prefs_register_bool_preference(nts_ke_module, "chrony_compat_mode",
886 "Chrony Compatibility Mode",
887 "Allows AEAD_AES_128_GCM_SIV key extraction for Chrony-based "
888 "NTP clients and servers.",
889 &nts_ke_chrony_compat_mode);
891 nts_ke_handle = register_dissector("nts-ke", dissect_nts_ke_tcp, proto_nts_ke);
894 void
895 proto_reg_handoff_nts_ke(void)
897 dissector_add_uint_with_preference("tls.port", TLS_PORT, nts_ke_handle);
898 dissector_add_string("tls.alpn", NTS_KE_ALPN, nts_ke_handle);
902 * Editor modelines - https://www.wireshark.org/tools/modelines.html
904 * Local variables:
905 * c-basic-offset: 4
906 * tab-width: 8
907 * indent-tabs-mode: nil
908 * End:
910 * vi: set shiftwidth=4 tabstop=8 expandtab:
911 * :indentSize=4:tabSize=8:noTabs=true: