Coverage Report

Created: 2025-12-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-nts-ke.c
Line
Count
Source
1
/* packet-nts-ke.c
2
 * Dissector for Network Time Security Key Establishment Protocol (RFC 8915)
3
 *
4
 * Copyright (c) 2024 by Martin Mayer <martin.mayer@m2-it-solutions.de>
5
 *
6
 * Wireshark - Network traffic analyzer
7
 * By Gerald Combs <gerald@wireshark.org>
8
 * Copyright 1998 Gerald Combs
9
 *
10
 * SPDX-License-Identifier: GPL-2.0-or-later
11
 */
12
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>
23
24
14
#define TLS_PORT              4460
25
0
#define CRIT_TYPE_BODY_LEN       4
26
14
#define TYPE_MASK           0x7FFF
27
14
#define CRITICAL_MASK       0x8000
28
29
0
#define NTS_KE_EXPORTER_LABEL "EXPORTER-network-time-security"
30
14
#define NTS_KE_ALPN           "ntske/1"
31
32
void proto_register_nts_ke(void);
33
void proto_reg_handoff_nts_ke(void);
34
35
static dissector_handle_t nts_ke_handle;
36
37
static int proto_nts_ke;
38
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;
54
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;
63
64
/* Prefs */
65
static bool nts_ke_extract_keys = true;
66
static bool nts_ke_chrony_compat_mode = false;
67
68
/* Trees */
69
static int ett_nts_ke;
70
static int ett_nts_ke_record;
71
72
0
#define RECORD_TYPE_END             0
73
0
#define RECORD_TYPE_NEXT            1
74
0
#define RECORD_TYPE_ERR             2
75
0
#define RECORD_TYPE_WARN            3
76
0
#define RECORD_TYPE_AEAD            4
77
0
#define RECORD_TYPE_COOKIE          5
78
0
#define RECORD_TYPE_NEG_SRV         6
79
0
#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
87
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 }
103
};
104
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 }
113
};
114
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 }
120
};
121
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 }
128
};
129
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 }
168
};
169
170
/* All supported AEAD
171
 * Note: Key length is limited in NTS_KE_TLS13_KEY_MAX_LEN
172
 *
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.
177
 *
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 successfully without GCRYPT support.
181
 */
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 }
190
};
191
192
const nts_aead *
193
nts_find_aead(uint16_t id)
194
0
{
195
0
    const nts_aead *c;
196
0
    for(c = nts_ke_aead_gcry_map; c->id !=0 ; c++){
197
0
        if(c->id == id){
198
0
            return c;
199
0
        }
200
0
    }
201
0
    return NULL;
202
0
}
203
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;
209
210
/* Cookie map */
211
static wmem_map_t *nts_cookies;
212
213
struct nts_ke_uid_lookup {
214
    uint32_t uid_hash;
215
    nts_cookie_t *cookie;
216
};
217
218
static int
219
nts_find_list_callback(const void *a, const void *b)
220
0
{
221
0
    const uint32_t *x = (const uint32_t*)a;
222
0
    if(*x == GPOINTER_TO_UINT(b)) return 0; else return -1;
223
0
}
224
225
static void
226
nts_uid_lookup_callback(void *key _U_, void *val, void *userdata)
227
0
{
228
0
    nts_cookie_t *current_cookie = (nts_cookie_t *)val;
229
0
    struct nts_ke_uid_lookup *func_data = (struct nts_ke_uid_lookup *)userdata;
230
231
0
    if(wmem_list_find_custom(current_cookie->frames_used_uid, GUINT_TO_POINTER(func_data->uid_hash), nts_find_list_callback))
232
0
        func_data->cookie = current_cookie;
233
0
}
234
235
void
236
nts_append_used_frames_to_tree(void *data, void *user_data)
237
0
{
238
0
    proto_item *ct;
239
240
0
    uint32_t *pnum = (uint32_t *)data;
241
0
    nts_used_frames_lookup_t *func_data = (nts_used_frames_lookup_t *)user_data;
242
243
0
    ct = proto_tree_add_uint(func_data->tree, func_data->hfindex, func_data->tvb, 0, 0, *pnum);
244
0
    proto_item_set_generated(ct);
245
0
}
246
247
nts_cookie_t*
248
nts_new_cookie(tvbuff_t *tvb, uint16_t aead, packet_info *pinfo)
249
0
{
250
0
    unsigned int cookie_len = tvb_reported_length(tvb);
251
0
    uint8_t *key_c2s = (uint8_t *)wmem_alloc0(pinfo->pool, NTS_KE_TLS13_KEY_MAX_LEN);
252
0
    uint8_t *key_s2c = (uint8_t *)wmem_alloc0(pinfo->pool, NTS_KE_TLS13_KEY_MAX_LEN);
253
0
    uint8_t *tvb_bytes;
254
0
    nts_cookie_t *cookie;
255
0
    uint32_t strong_hash;
256
0
    const nts_aead *aead_entry;
257
0
    bool k1, k2;
258
0
    uint8_t ex_context_s2c[5], ex_context_c2s[5];
259
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)
264
     *
265
     * RFC 8915 5.1:
266
     * The per-association context value [RFC5705] SHALL consist of the following five octets:
267
     *
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.
271
     *
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
275
     *
276
     * As this is a breaking compatibility bug, offer a compatibility mode as preference.
277
     * See: https://gitlab.com/chrony/chrony/-/issues/12
278
     */
279
0
    ex_context_c2s[0] = 0x00;
280
0
    ex_context_c2s[1] = 0x00;
281
0
    ex_context_c2s[2] = (uint8_t)(aead >> 8);
282
0
    ex_context_c2s[3] = (uint8_t)aead;
283
0
    ex_context_c2s[4] = 0x00;
284
285
0
    ex_context_s2c[0] = 0x00;
286
0
    ex_context_s2c[1] = 0x00;
287
0
    ex_context_s2c[2] = (uint8_t)(aead >> 8);
288
0
    ex_context_s2c[3] = (uint8_t)aead;
289
0
    ex_context_s2c[4] = 0x01;
290
291
0
    if(nts_ke_chrony_compat_mode && aead == 30) {
292
0
        ex_context_c2s[2] = 0x00;
293
0
        ex_context_c2s[3] = 0x0F;
294
0
        ex_context_s2c[2] = 0x00;
295
0
        ex_context_s2c[3] = 0x0F;
296
0
    }
297
298
0
    aead_entry = nts_find_aead(aead);
299
300
0
    if(cookie_len < 1 || !aead_entry)
301
0
        return NULL;
302
303
0
    tvb_bytes = (uint8_t *)tvb_memdup(pinfo->pool, tvb, 0, cookie_len);
304
0
    strong_hash = wmem_strong_hash(tvb_bytes, cookie_len);
305
306
0
    cookie = (nts_cookie_t*) wmem_map_lookup(nts_cookies, GUINT_TO_POINTER(strong_hash));
307
308
    /* Cookie was not found, add it */
309
0
    if(!cookie) {
310
311
        /* Extract keys */
312
0
        k1 = tls13_exporter(pinfo, false,
313
0
                NTS_KE_EXPORTER_LABEL, ex_context_c2s,
314
0
                sizeof(ex_context_c2s), aead_entry->key_len, &key_c2s);
315
0
        k2 = tls13_exporter(pinfo, false,
316
0
                NTS_KE_EXPORTER_LABEL, ex_context_s2c,
317
0
                sizeof(ex_context_s2c), aead_entry->key_len, &key_s2c);
318
319
0
        cookie = wmem_new(wmem_file_scope(), nts_cookie_t);
320
321
0
        cookie->frame_received  = pinfo->num;
322
0
        cookie->frames_used     = wmem_list_new(wmem_file_scope());
323
0
        cookie->frames_used_uid = wmem_list_new(wmem_file_scope());
324
0
        cookie->aead            = aead;
325
0
        if(k1 && k2) {
326
0
            cookie->keys_present = true;
327
0
            memcpy(cookie->key_c2s, key_c2s, aead_entry->key_len);
328
0
            memcpy(cookie->key_s2c, key_s2c, aead_entry->key_len);
329
0
        } else {
330
0
            cookie->keys_present = false;
331
0
        }
332
333
0
        wmem_map_insert(nts_cookies, GUINT_TO_POINTER(strong_hash), cookie);
334
0
    }
335
336
0
    return cookie;
337
0
}
338
339
nts_cookie_t*
340
nts_new_cookie_copy(tvbuff_t *tvb, nts_cookie_t *ref_cookie, packet_info *pinfo)
341
0
{
342
0
    unsigned int cookie_len = tvb_reported_length(tvb);
343
0
    uint8_t *tvb_bytes;
344
0
    nts_cookie_t *cookie;
345
0
    uint32_t strong_hash;
346
0
    const nts_aead *aead_entry;
347
348
0
    aead_entry = nts_find_aead(ref_cookie->aead);
349
350
0
    if(cookie_len < 1 || !aead_entry)
351
0
        return NULL;
352
353
0
    tvb_bytes = (uint8_t *)tvb_memdup(pinfo->pool, tvb, 0, cookie_len);
354
0
    strong_hash = wmem_strong_hash(tvb_bytes, cookie_len);
355
356
0
    cookie = (nts_cookie_t*) wmem_map_lookup(nts_cookies, GUINT_TO_POINTER(strong_hash));
357
358
    /* Cookie was not found, add it */
359
0
    if(!cookie) {
360
361
0
        cookie = wmem_new(wmem_file_scope(), nts_cookie_t);
362
363
0
        cookie->frame_received  = pinfo->num;
364
0
        cookie->frames_used     = wmem_list_new(wmem_file_scope());
365
0
        cookie->frames_used_uid = wmem_list_new(wmem_file_scope());
366
0
        cookie->aead            = ref_cookie->aead;
367
0
        if(ref_cookie->keys_present) {
368
0
            cookie->keys_present = true;
369
0
            memcpy(cookie->key_c2s, ref_cookie->key_c2s, aead_entry->key_len);
370
0
            memcpy(cookie->key_s2c, ref_cookie->key_s2c, aead_entry->key_len);
371
0
        } else {
372
0
            cookie->keys_present = false;
373
0
        }
374
375
0
        wmem_map_insert(nts_cookies, GUINT_TO_POINTER(strong_hash), cookie);
376
0
    }
377
378
0
    return cookie;
379
0
}
380
381
nts_cookie_t* nts_use_cookie(tvbuff_t *tvb_cookie, tvbuff_t *tvb_uid, packet_info *pinfo)
382
0
{
383
0
    unsigned int cookie_len = tvb_reported_length(tvb_cookie);
384
0
    unsigned int uid_len = tvb_reported_length(tvb_uid);
385
386
0
    uint8_t *tvb_cookie_bytes, *tvb_uid_bytes;
387
0
    nts_cookie_t *cookie;
388
389
0
    uint32_t strong_hash_cookie, strong_hash_uid;
390
0
    uint32_t *pnum, *uid;
391
392
0
    if(cookie_len < 1 || uid_len < 1)
393
0
        return NULL;
394
395
    /* Hash cookie and UID */
396
0
    tvb_cookie_bytes = (uint8_t *)tvb_memdup(pinfo->pool, tvb_cookie, 0, cookie_len);
397
0
    strong_hash_cookie = wmem_strong_hash(tvb_cookie_bytes, cookie_len);
398
399
0
    tvb_uid_bytes = (uint8_t *)tvb_memdup(pinfo->pool, tvb_uid, 0, uid_len);
400
0
    strong_hash_uid = wmem_strong_hash(tvb_uid_bytes, uid_len);
401
402
    /* Find cookie by hash */
403
0
    cookie = (nts_cookie_t*) wmem_map_lookup(nts_cookies, GUINT_TO_POINTER(strong_hash_cookie));
404
405
0
    if(cookie) {
406
        /* In theory a cookie can be used multiple times, so remember all packets which used it */
407
0
        if(!wmem_list_find_custom(cookie->frames_used, GUINT_TO_POINTER(pinfo->num), nts_find_list_callback)) {
408
0
            pnum = wmem_new0(wmem_file_scope(), uint32_t);
409
0
            wmem_list_append(cookie->frames_used, pnum);
410
0
            *pnum = pinfo->num;
411
0
        }
412
413
0
        if(!wmem_list_find_custom(cookie->frames_used_uid, GUINT_TO_POINTER(strong_hash_uid), nts_find_list_callback)) {
414
0
            uid = wmem_new0(wmem_file_scope(), uint32_t);
415
0
            wmem_list_append(cookie->frames_used_uid, uid);
416
0
            *uid = strong_hash_uid;
417
0
        }
418
0
    }
419
420
0
    return cookie;
421
0
}
422
423
nts_cookie_t*
424
nts_find_cookie_by_uid(tvbuff_t *tvb_uid)
425
0
{
426
0
    unsigned int uid_len = tvb_reported_length(tvb_uid);
427
428
0
    uint8_t *tvb_uid_bytes;
429
0
    struct nts_ke_uid_lookup lookup;
430
431
0
    if(uid_len < 1)
432
0
        return NULL;
433
434
    /* Hash UID */
435
0
    tvb_uid_bytes = (uint8_t *)tvb_memdup(NULL, tvb_uid, 0, uid_len);
436
0
    lookup.uid_hash = wmem_strong_hash(tvb_uid_bytes, uid_len);
437
0
    lookup.cookie = NULL;
438
0
    wmem_free(NULL, tvb_uid_bytes);
439
440
    /* Find cookie by UID hash */
441
0
    wmem_map_foreach(nts_cookies, nts_uid_lookup_callback, &lookup);
442
443
0
    return lookup.cookie;
444
0
}
445
446
static int
447
dissect_nts_ke(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
448
0
{
449
0
    int offset;
450
0
    uint16_t critical, type;
451
0
    uint32_t body_length, body_counter, aead = 0;
452
0
    uint32_t counter_next_proto_recs = 0, counter_aead = 0, counter_cookies = 0;
453
0
    uint32_t *next_proto_list_item;
454
0
    proto_item *ti, *ti_record, *rt;
455
0
    proto_tree *nts_ke_tree, *record_tree;
456
0
    bool critical_bool, end_record = false;
457
0
    bool request, direction_determined = false;
458
0
    wmem_list_t *next_protos = wmem_list_new(pinfo->pool);
459
0
    conversation_t *conv;
460
0
    nts_ke_req_resp_t *conv_data;
461
0
    nts_cookie_t *cookie;
462
0
    struct tcp_analysis *tcp_conv;
463
0
    nts_used_frames_lookup_t lookup_data = {.tvb = tvb, .hfindex = hf_nts_ke_cookie_used_frame};
464
465
0
    offset = 0;
466
467
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "NTS-KE");
468
0
    col_clear(pinfo->cinfo,COL_INFO);
469
470
0
    ti = proto_tree_add_item(tree, proto_nts_ke, tvb, 0, 0, ENC_NA);
471
0
    nts_ke_tree = proto_item_add_subtree(ti, ett_nts_ke);
472
473
    /* Error on ALPN mismatch */
474
0
    if(strcmp(tls_get_alpn(pinfo), NTS_KE_ALPN) != 0)
475
0
        expert_add_info(pinfo, nts_ke_tree, &ei_nts_ke_alpn_mismatch);
476
477
    /* Conversation init */
478
0
    conv = find_or_create_conversation(pinfo);
479
0
    conv_data = (nts_ke_req_resp_t *)conversation_get_proto_data(conv, proto_nts_ke);
480
481
    /* As NTS-KE has no client/server distinction. We need to rely on TCP.
482
     * We can be sure that TCP has identified the server port,
483
     * so we just need to compare it with our packet's destination port
484
     * to identify the direction.
485
     */
486
0
    tcp_conv = get_tcp_conversation_data_idempotent(conv);
487
0
    if(tcp_conv && pinfo->destport == tcp_conv->server_port) {
488
0
        direction_determined = true;
489
0
        request = true;
490
0
    } else if (tcp_conv && pinfo->srcport == tcp_conv->server_port) {
491
0
        direction_determined = true;
492
0
        request = false;
493
0
    }
494
495
0
    if (direction_determined) {
496
0
        if (!conv_data) {
497
0
            conv_data = wmem_new(wmem_file_scope(), nts_ke_req_resp_t);
498
0
            conv_data->req_frame = request ? pinfo->num : 0;
499
0
            conv_data->resp_frame = !request ? pinfo->num : 0;
500
0
            conversation_add_proto_data(conv, proto_nts_ke, conv_data);
501
0
        } else {
502
0
            conv_data->req_frame = request ? pinfo->num : conv_data->req_frame;
503
0
            conv_data->resp_frame = !request ? pinfo->num : conv_data->resp_frame;
504
0
        }
505
0
    }
506
507
0
    while(tvb_reported_length_remaining(tvb, offset) >= CRIT_TYPE_BODY_LEN) {
508
509
        /* Reset pointer */
510
0
        cookie = NULL;
511
512
0
        ti_record = proto_tree_add_item(nts_ke_tree, hf_nts_ke_record, tvb, offset, 0, ENC_NA);
513
0
        record_tree = proto_item_add_subtree(ti_record, ett_nts_ke_record);
514
515
0
        critical = tvb_get_uint16(tvb, offset, ENC_BIG_ENDIAN) & CRITICAL_MASK;
516
0
        critical_bool = (bool)(critical >> 15);
517
0
        proto_tree_add_boolean(record_tree, hf_nts_ke_critical_bit, tvb, offset, 2, critical);
518
519
0
        type = tvb_get_uint16(tvb, offset, ENC_BIG_ENDIAN) & TYPE_MASK;
520
0
        proto_tree_add_uint(record_tree, hf_nts_ke_record_type, tvb, offset, 2, type);
521
0
        proto_item_append_text(ti_record, " (%s)", rval_to_str_const(type, nts_ke_record_types, "Unknown Record Type"));
522
0
        offset += 2;
523
524
0
        proto_tree_add_item_ret_uint(record_tree, hf_nts_ke_body_length, tvb, offset, 2, ENC_BIG_ENDIAN, &body_length);
525
0
        offset += 2;
526
527
0
        if(end_record)
528
0
            expert_add_info(pinfo, record_tree, &ei_nts_ke_record_after_end);
529
530
0
        body_counter = 0;
531
532
0
        switch (type) {
533
0
            case RECORD_TYPE_END:
534
535
                /* No body allowed */
536
0
                if(body_length > 0) {
537
0
                    call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
538
0
                    expert_add_info(pinfo, record_tree, &ei_nts_ke_body_illegal);
539
0
                    offset += body_length;
540
0
                }
541
542
                /* Critical bit is mandatory for this type */
543
0
                if(!critical_bool)
544
0
                    expert_add_info(pinfo, record_tree, &ei_nts_ke_critical_bit_missing);
545
546
                /* Mark end record as seen */
547
0
                end_record = true;
548
549
0
                break;
550
551
0
            case RECORD_TYPE_NEXT:
552
553
0
                while(body_counter < body_length) {
554
0
                    uint32_t next_proto;
555
0
                    proto_tree_add_item_ret_uint(record_tree, hf_nts_ke_next_proto, tvb, offset, 2, ENC_BIG_ENDIAN, &next_proto);
556
0
                    offset += 2;
557
0
                    body_counter += 2;
558
559
                    /* Store list of offered/accepted next protocols */
560
0
                    next_proto_list_item = wmem_new0(pinfo->pool, uint32_t);
561
0
                    wmem_list_append(next_protos, next_proto_list_item);
562
0
                    *next_proto_list_item = next_proto;
563
564
0
                    col_append_str(pinfo->cinfo, COL_INFO, rval_to_str_const(next_proto, nts_ke_next_proto_rvals, "Unknown Proto"));
565
0
                }
566
567
                /* Critical bit is mandatory for this type */
568
0
                if(!critical_bool)
569
0
                    expert_add_info(pinfo, record_tree, &ei_nts_ke_critical_bit_missing);
570
571
0
                counter_next_proto_recs++;
572
573
0
                break;
574
575
0
            case RECORD_TYPE_ERR:
576
577
                /* Fixed body length */
578
0
                if(body_length == 2) {
579
0
                    proto_tree_add_item(record_tree, hf_nts_ke_error, tvb, offset, body_length, ENC_BIG_ENDIAN);
580
0
                } else {
581
0
                    call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
582
0
                    expert_add_info(pinfo, record_tree, &ei_nts_ke_body_length_illegal);
583
0
                }
584
0
                offset += body_length;
585
586
                /* Critical bit is mandatory for this type */
587
0
                if(!critical_bool)
588
0
                    expert_add_info(pinfo, record_tree, &ei_nts_ke_critical_bit_missing);
589
590
0
                break;
591
592
0
            case RECORD_TYPE_WARN:
593
594
                /* Fixed body length */
595
0
                if(body_length == 2) {
596
0
                    proto_tree_add_item(record_tree, hf_nts_ke_warning, tvb, offset, body_length, ENC_BIG_ENDIAN);
597
0
                } else {
598
0
                    call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
599
0
                    expert_add_info(pinfo, record_tree, &ei_nts_ke_body_length_illegal);
600
0
                }
601
0
                offset += body_length;
602
603
                /* Critical bit is mandatory for this type */
604
0
                if(!critical_bool)
605
0
                    expert_add_info(pinfo, record_tree, &ei_nts_ke_critical_bit_missing);
606
607
0
                break;
608
609
0
            case RECORD_TYPE_AEAD:
610
611
0
                while(body_counter < body_length) {
612
613
0
                    proto_tree_add_item_ret_uint(record_tree, hf_nts_ke_aead_algo, tvb, offset, 2, ENC_BIG_ENDIAN, &aead);
614
0
                    offset += 2;
615
0
                    body_counter += 2;
616
0
                    counter_aead++;
617
0
                }
618
619
0
                break;
620
621
0
            case RECORD_TYPE_COOKIE:
622
623
                /* Arbitrary body data
624
                 *
625
                 * Other dissectors (e.g. NTP) need to access this data along it's extracted keys.
626
                 * Add NTS cookies if NTP (0x00) is part of next protos
627
                 */
628
0
                if (
629
0
                    nts_ke_extract_keys &&
630
0
                    aead > 0 &&
631
0
                    wmem_list_find_custom(next_protos, GUINT_TO_POINTER(0x00), nts_find_list_callback)
632
0
                ) {
633
0
                    cookie = nts_new_cookie(tvb_new_subset_length(tvb, offset, body_length), (uint16_t)aead, pinfo);
634
0
                }
635
0
                proto_tree_add_item(record_tree, hf_nts_ke_cookie, tvb, offset, body_length, ENC_NA);
636
0
                offset += body_length;
637
0
                counter_cookies++;
638
639
0
                if(cookie) {
640
                    /* List all packets which made use of that cookie */
641
0
                    lookup_data.tree = record_tree;
642
0
                    wmem_list_foreach(cookie->frames_used, nts_append_used_frames_to_tree, &lookup_data);
643
0
                }
644
645
0
                break;
646
647
0
            case RECORD_TYPE_NEG_SRV:
648
649
                /* Arbitrary string */
650
0
                proto_tree_add_item(record_tree, hf_nts_ke_server, tvb, offset, body_length, ENC_ASCII);
651
0
                offset += body_length;
652
653
0
                break;
654
655
0
            case RECORD_TYPE_NEG_PORT:
656
657
                /* Fixed body length */
658
0
                if(body_length == 2) {
659
0
                    proto_tree_add_item(record_tree, hf_nts_ke_port, tvb, offset, body_length, ENC_BIG_ENDIAN);
660
0
                } else {
661
0
                    call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
662
0
                    expert_add_info(pinfo, record_tree, &ei_nts_ke_body_length_illegal);
663
0
                }
664
0
                offset += body_length;
665
666
0
                break;
667
668
0
            default:
669
670
0
                call_data_dissector(tvb_new_subset_length(tvb, offset, body_length), pinfo, record_tree);
671
0
                offset += body_length;
672
673
0
                break;
674
0
        }
675
676
0
        proto_item_set_end(ti_record, tvb, offset);
677
0
    }
678
679
    /* Request/Response */
680
0
    if(conv_data && direction_determined) {
681
0
        if(request && conv_data->resp_frame > 0) {
682
0
            rt = proto_tree_add_uint(nts_ke_tree, hf_nts_ke_response_in, tvb, 0, 0, conv_data->resp_frame);
683
0
            proto_item_set_generated(rt);
684
0
        } else if (!request && conv_data->req_frame > 0) {
685
0
            rt = proto_tree_add_uint(nts_ke_tree, hf_nts_ke_response_to, tvb, 0, 0, conv_data->req_frame);
686
0
            proto_item_set_generated(rt);
687
0
        }
688
0
    }
689
690
    /* Info columns text */
691
0
    if(counter_aead > 0)
692
0
        col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "%u AEAD Algorithm%s", counter_aead, plurality(counter_aead, "", "s"));
693
694
0
    if(counter_cookies > 0)
695
0
        col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "%u Cookie%s", counter_cookies, plurality(counter_cookies, "", "s"));
696
697
    /* No end record found */
698
0
    if(!end_record)
699
0
        expert_add_info(pinfo, nts_ke_tree, &ei_nts_ke_end_missing);
700
701
    /* Illegal AEAD record count */
702
0
    if(counter_next_proto_recs != 1)
703
0
        expert_add_info(pinfo, nts_ke_tree, &ei_nts_ke_next_proto_illegal_count);
704
705
0
    proto_item_set_end(ti, tvb, offset);
706
707
0
    return offset;
708
0
}
709
710
static unsigned
711
get_nts_ke_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
712
0
{
713
714
0
    bool another_record = true;
715
0
    unsigned size = 0;
716
717
    /* Concat multiple records into one protocol tree */
718
0
    while(another_record) {
719
720
        /* Size is body length + 4 byte (CRIT_TYPE_BODY_LEN) */
721
0
        unsigned pdu_size = tvb_get_uint16(tvb, offset + 2, ENC_BIG_ENDIAN) + CRIT_TYPE_BODY_LEN;
722
0
        size += pdu_size;
723
724
0
        if (tvb_captured_length_remaining(tvb, offset + pdu_size) < CRIT_TYPE_BODY_LEN)
725
0
            another_record = false;
726
727
0
        offset += pdu_size;
728
0
    }
729
730
0
    return size;
731
732
0
}
733
734
static int
735
dissect_nts_ke_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
736
0
{
737
0
    if (!tvb_bytes_exist(tvb, 0, CRIT_TYPE_BODY_LEN))
738
0
        return 0;
739
740
0
    tcp_dissect_pdus(tvb, pinfo, tree, true, CRIT_TYPE_BODY_LEN, get_nts_ke_message_len, dissect_nts_ke, data);
741
0
    return tvb_reported_length(tvb);
742
0
}
743
744
void
745
proto_register_nts_ke(void)
746
14
{
747
14
    static hf_register_info hf[] = {
748
14
        { &hf_nts_ke_record,
749
14
            { "NTS-KE Record", "nts-ke.record",
750
14
            FT_NONE, BASE_NONE,
751
14
            NULL, 0x0,
752
14
            NULL, HFILL }
753
14
        },
754
14
        { &hf_nts_ke_critical_bit,
755
14
            { "Critical Bit", "nts-ke.critical_bit",
756
14
            FT_BOOLEAN, 16,
757
14
            TFS(&tfs_set_notset), CRITICAL_MASK,
758
14
            NULL, HFILL }
759
14
        },
760
14
        { &hf_nts_ke_record_type,
761
14
            { "Record Type", "nts-ke.type",
762
14
            FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
763
14
            RVALS(nts_ke_record_types), TYPE_MASK,
764
14
            NULL, HFILL }
765
14
        },
766
14
        { &hf_nts_ke_body_length,
767
14
            { "Body Length", "nts-ke.body_length",
768
14
            FT_UINT16, BASE_DEC | BASE_UNIT_STRING,
769
14
            UNS(&units_byte_bytes), 0x0,
770
14
            NULL, HFILL }
771
14
        },
772
14
        { &hf_nts_ke_next_proto,
773
14
            { "Next Protocol ID", "nts-ke.next_proto",
774
14
            FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
775
14
            RVALS(nts_ke_next_proto_rvals), 0x0,
776
14
            NULL, HFILL }
777
14
        },
778
14
        { &hf_nts_ke_error,
779
14
            { "Error Code", "nts-ke.error",
780
14
            FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
781
14
            RVALS(nts_ke_error_codes), 0x0,
782
14
            NULL, HFILL }
783
14
        },
784
14
        { &hf_nts_ke_warning,
785
14
            { "Warning Code", "nts-ke.warning",
786
14
            FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
787
14
            RVALS(nts_ke_warning_codes), 0x0,
788
14
            NULL, HFILL }
789
14
        },
790
14
        { &hf_nts_ke_aead_algo,
791
14
            { "AEAD Algorithm", "nts-ke.aead_algo",
792
14
            FT_UINT16, BASE_DEC | BASE_RANGE_STRING,
793
14
            RVALS(nts_ke_aead_rvals), 0x0,
794
14
            NULL, HFILL }
795
14
        },
796
14
        { &hf_nts_ke_cookie,
797
14
            { "Cookie Data", "nts-ke.cookie",
798
14
            FT_BYTES, BASE_NONE,
799
14
            NULL, 0x0,
800
14
            NULL, HFILL }
801
14
        },
802
14
        { &hf_nts_ke_cookie_used_frame, {
803
14
            "Used cookie in", "nts-ke.cookie.use_frame",
804
14
            FT_FRAMENUM, BASE_NONE,
805
14
            NULL, 0,
806
14
            NULL, HFILL }},
807
14
        { &hf_nts_ke_server,
808
14
            { "Server", "nts-ke.server",
809
14
            FT_STRING, BASE_NONE,
810
14
            NULL, 0x0,
811
14
            NULL, HFILL }
812
14
        },
813
14
        { &hf_nts_ke_port,
814
14
            { "Port", "nts-ke.port",
815
14
            FT_UINT16, BASE_DEC,
816
14
            NULL, 0x0,
817
14
            NULL, HFILL }
818
14
        },
819
14
        { &hf_nts_ke_response_in,
820
14
            { "Response In", "nts-ke.response_in",
821
14
            FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0,
822
14
            NULL, HFILL }
823
14
        },
824
14
        { &hf_nts_ke_response_to,
825
14
            { "Response To", "nts-ke.response_to",
826
14
            FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0,
827
14
            NULL, HFILL }
828
14
        }
829
14
    };
830
831
14
    static ei_register_info ei[] = {
832
14
        { &ei_nts_ke_critical_bit_missing,
833
14
            { "nts-ke.critical_bit.missing", PI_MALFORMED, PI_ERROR,
834
14
                "Critical bit must be set for this record type", EXPFILL }
835
14
        },
836
14
        { &ei_nts_ke_record_after_end,
837
14
            { "nts-ke.record.after_end", PI_MALFORMED, PI_ERROR,
838
14
                "Illegal record after end of message", EXPFILL }
839
14
        },
840
14
        { &ei_nts_ke_end_missing,
841
14
            { "nts-ke.end.missing", PI_MALFORMED, PI_ERROR,
842
14
                "No end of message present", EXPFILL }
843
14
        },
844
14
        { &ei_nts_ke_body_illegal,
845
14
            { "nts-ke.body.illegal", PI_MALFORMED, PI_ERROR,
846
14
                "Illegal body data present", EXPFILL }
847
14
        },
848
14
        { &ei_nts_ke_body_length_illegal,
849
14
            { "nts-ke.body_length.illegal", PI_MALFORMED, PI_ERROR,
850
14
                "Illegal body length", EXPFILL }
851
14
        },
852
14
        { &ei_nts_ke_next_proto_illegal_count,
853
14
            { "nts-ke.next_proto.illegal_count", PI_MALFORMED, PI_ERROR,
854
14
                "Illegal Next Protocol record count", EXPFILL }
855
14
        },
856
14
        { &ei_nts_ke_alpn_mismatch,
857
14
            { "nts-ke.alpn_mismatch", PI_DECRYPTION, PI_ERROR,
858
14
                "TLS ALPN mismatch", EXPFILL }
859
14
        }
860
14
    };
861
862
14
    static int *ett[] = {
863
14
        &ett_nts_ke,
864
14
        &ett_nts_ke_record
865
14
    };
866
867
14
    expert_module_t* expert_nts_ke;
868
14
    module_t *nts_ke_module;
869
870
14
    nts_cookies = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), g_direct_hash, g_direct_equal);
871
872
14
    proto_nts_ke = proto_register_protocol ("NTS Key Establishment Protocol", "NTS-KE", "nts-ke");
873
874
14
    proto_register_field_array(proto_nts_ke, hf, array_length(hf));
875
14
    proto_register_subtree_array(ett, array_length(ett));
876
877
14
    expert_nts_ke = expert_register_protocol(proto_nts_ke);
878
14
    expert_register_field_array(expert_nts_ke, ei, array_length(ei));
879
880
14
    nts_ke_module = prefs_register_protocol(proto_nts_ke, NULL);
881
14
    prefs_register_bool_preference(nts_ke_module, "extract_keys",
882
14
        "Extract S2C and C2S keys",
883
14
        "Whether to extract client-to-server and server-to-client "
884
14
        "keys for crypto-processing.",
885
14
        &nts_ke_extract_keys);
886
14
    prefs_register_bool_preference(nts_ke_module, "chrony_compat_mode",
887
14
        "Chrony Compatibility Mode",
888
14
        "Allows AEAD_AES_128_GCM_SIV key extraction for Chrony-based "
889
14
        "NTP clients and servers.",
890
14
        &nts_ke_chrony_compat_mode);
891
892
14
    nts_ke_handle = register_dissector("nts-ke", dissect_nts_ke_tcp, proto_nts_ke);
893
14
}
894
895
void
896
proto_reg_handoff_nts_ke(void)
897
14
{
898
14
    dissector_add_uint_with_preference("tls.port", TLS_PORT, nts_ke_handle);
899
14
    dissector_add_string("tls.alpn", NTS_KE_ALPN, nts_ke_handle);
900
14
}
901
902
/*
903
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
904
 *
905
 * Local variables:
906
 * c-basic-offset: 4
907
 * tab-width: 8
908
 * indent-tabs-mode: nil
909
 * End:
910
 *
911
 * vi: set shiftwidth=4 tabstop=8 expandtab:
912
 * :indentSize=4:tabSize=8:noTabs=true:
913
 */