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
14 #include "packet-tcp.h"
15 #include "packet-tls.h"
16 #include "packet-nts-ke.h"
17 #include <epan/packet.h>
19 #include <epan/unit_strings.h>
20 #include <wsutil/str_util.h>
21 #include <epan/expert.h>
22 #include <epan/conversation.h>
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
;
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
;
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
;
65 static bool nts_ke_extract_keys
= true;
66 static bool nts_ke_chrony_compat_mode
= false;
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" },
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" },
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" },
122 /* https://www.iana.org/assignments/nts/nts.xhtml#nts-next-protocols */
123 static const range_string nts_ke_next_proto_rvals
[] = {
125 { 1, 32767, "Unassigned" },
126 { 32768, 65535, "Reserved" },
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" },
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 },
193 nts_find_aead(uint16_t id
)
196 for(c
= nts_ke_aead_gcry_map
; c
->id
!=0 ; c
++){
204 /* Request/response tracking */
205 typedef struct _nts_ke_req_resp_t
{
211 static wmem_map_t
*nts_cookies
;
213 struct nts_ke_uid_lookup
{
215 nts_cookie_t
*cookie
;
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;
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
;
236 nts_append_used_frames_to_tree(void *data
, void *user_data
)
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
);
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
);
254 nts_cookie_t
*cookie
;
255 uint32_t strong_hash
;
256 const nts_aead
*aead_entry
;
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)
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:
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
)
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 */
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());
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
);
330 cookie
->keys_present
= false;
333 wmem_map_insert(nts_cookies
, GUINT_TO_POINTER(strong_hash
), cookie
);
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
);
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
)
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 */
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
);
372 cookie
->keys_present
= false;
375 wmem_map_insert(nts_cookies
, GUINT_TO_POINTER(strong_hash
), 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)
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
));
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
);
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
;
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
;
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
;
446 dissect_nts_ke(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
)
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
};
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;
489 } else if (tcp_conv
&& pinfo
->srcport
== tcp_conv
->server_port
) {
490 direction_determined
= true;
494 if (direction_determined
) {
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
);
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
) {
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"));
523 proto_tree_add_item_ret_uint(record_tree
, hf_nts_ke_body_length
, tvb
, offset
, 2, ENC_BIG_ENDIAN
, &body_length
);
527 expert_add_info(pinfo
, record_tree
, &ei_nts_ke_record_after_end
);
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 */
543 expert_add_info(pinfo
, record_tree
, &ei_nts_ke_critical_bit_missing
);
545 /* Mark end record as seen */
550 case RECORD_TYPE_NEXT
:
552 while(body_counter
< body_length
) {
554 proto_tree_add_item_ret_uint(record_tree
, hf_nts_ke_next_proto
, tvb
, offset
, 2, ENC_BIG_ENDIAN
, &next_proto
);
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 */
568 expert_add_info(pinfo
, record_tree
, &ei_nts_ke_critical_bit_missing
);
570 counter_next_proto_recs
++;
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
);
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 */
587 expert_add_info(pinfo
, record_tree
, &ei_nts_ke_critical_bit_missing
);
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
);
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 */
604 expert_add_info(pinfo
, record_tree
, &ei_nts_ke_critical_bit_missing
);
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
);
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
628 nts_ke_extract_keys
&&
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
;
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
);
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
;
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
);
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
;
669 call_data_dissector(tvb_new_subset_length(tvb
, offset
, body_length
), pinfo
, record_tree
);
670 offset
+= body_length
;
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 */
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 */
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
);
710 get_nts_ke_message_len(packet_info
*pinfo _U_
, tvbuff_t
*tvb
, int offset
, void *data _U_
)
713 bool another_record
= true;
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
;
723 if (tvb_captured_length_remaining(tvb
, offset
+ pdu_size
) < CRIT_TYPE_BODY_LEN
)
724 another_record
= false;
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
))
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
);
744 proto_register_nts_ke(void)
746 static hf_register_info hf
[] = {
748 { "NTS-KE Record", "nts-ke.record",
753 { &hf_nts_ke_critical_bit
,
754 { "Critical Bit", "nts-ke.critical_bit",
756 TFS(&tfs_set_notset
), CRITICAL_MASK
,
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
,
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,
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,
778 { "Error Code", "nts-ke.error",
779 FT_UINT16
, BASE_DEC
| BASE_RANGE_STRING
,
780 RVALS(nts_ke_error_codes
), 0x0,
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,
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,
796 { "Cookie Data", "nts-ke.cookie",
801 { &hf_nts_ke_cookie_used_frame
, {
802 "Used cookie in", "nts-ke.cookie.use_frame",
803 FT_FRAMENUM
, BASE_NONE
,
807 { "Server", "nts-ke.server",
808 FT_STRING
, BASE_NONE
,
813 { "Port", "nts-ke.port",
818 { &hf_nts_ke_response_in
,
819 { "Response In", "nts-ke.response_in",
820 FT_FRAMENUM
, BASE_NONE
, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE
), 0x0,
823 { &hf_nts_ke_response_to
,
824 { "Response To", "nts-ke.response_to",
825 FT_FRAMENUM
, BASE_NONE
, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST
), 0x0,
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
[] = {
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
);
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
907 * indent-tabs-mode: nil
910 * vi: set shiftwidth=4 tabstop=8 expandtab:
911 * :indentSize=4:tabSize=8:noTabs=true: