2a6761d12c7876279efb1fc69e0915e633462fd1
[secnet] / secnet-wireshark.lua
1 --- -*-lua-*-
2 ---
3 --- This file is part of secnet.
4 --- See README for full list of copyright holders.
5 ---
6 --- secnet is free software; you can redistribute it and/or modify it
7 --- under the terms of the GNU General Public License as published by
8 --- the Free Software Foundation; either version d of the License, or
9 --- (at your option) any later version.
10 ---
11 --- secnet is distributed in the hope that it will be useful, but
12 --- WITHOUT ANY WARRANTY; without even the implied warranty of
13 --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 --- General Public License for more details.
15 ---
16 --- You should have received a copy of the GNU General Public License
17 --- version 3 along with secnet; if not, see
18 --- https://www.gnu.org/licenses/gpl.html.
19
20 local secnet = Proto("secnet", "Secnet VPN")
21
22 -----------------------------------------------------------------------------
23 --- Session tracking.
24 ---
25 --- This is the hardest part of the dissector.
26
27 -- Timelines. A timeline associates pieces of information with times T.
28
29 local function tl_new()
30 -- Return a fresh shiny timeline.
31
32 return { }
33 end
34
35 local function tl__find(tl, t)
36 -- Find and return the earliest association in TL not earlier than T. If
37 -- there is no such entry, return nil.
38
39 local lo = 1
40 local hi = #tl + 1
41
42 -- Plain old binary search. The active interval is half-open, [lo, hi).
43 while true do
44 local w = hi - lo
45 if w == 0 then return nil end
46 local mid = lo + math.floor(w/2)
47 local tv = tl[mid]
48 if tv.t > t then hi = mid
49 elseif tv.t == t or w == 1 then return tv
50 else lo = mid
51 end
52 end
53 end
54
55 local function tl_find(tl, t)
56 -- Find and return the state of the timeline at time T, i.e., the earliest
57 -- value in TL not earlier than T. If there is no such entry, return nil.
58
59 local tv = tl__find(tl, t)
60 if tv == nil then return nil else return tv.v end
61 end
62
63 local function tl_add(tl, t, v)
64 -- Associate the value V with time T in TL.
65
66 local tv = tl__find(tl, t)
67 if tv ~= nil and tv.t == t then
68 tv.v = v
69 else
70 -- Append the new item. If necessary, sort the vector; we expect that
71 -- we'll see everything in the right order, so this won't be a problem.
72 local n = #tl
73 tl[n + 1] = { t = t, v = v }
74 if n > 0 and tl[n].t > t then
75 table.sort(tl, function (tv0, tv1) return tv0.t < tv1.t end)
76 end
77 end
78 end
79
80 local function dump_timeline(tl, cvt)
81 -- Dump a timeline TL, using the function CVT to convert each value to a
82 -- string.
83
84 for _, tv in ipairs(tl) do print("\t" .. tv.t .. ": " .. cvt(tv.v)) end
85 end
86
87 local function get_timeline_create(map, index)
88 -- If MAP[INDEX] exists, return it; otherwise set MAP[INDEX] to a fresh
89 -- timeline and return that.
90
91 local tl = map[index]
92 if tl == nil then tl = tl_new(); map[index] = tl end
93 return tl
94 end
95
96 local function lookup_timeline(map, index, t)
97 -- If it exists, MAP[INDEX] should be a timeline; find its state at time T.
98 -- Return nil if there's nothing there, or T is too early.
99
100 local tl = map[index]
101 if tl == nil then return nil
102 else return tl_find(tl, t)
103 end
104 end
105
106 -- The `SITEMAP' maps site names to little structures.
107 --
108 -- * `algs' is a map from peer site names to a timeline of structures
109 -- described below.
110 --
111 -- * `index' is a map from site indices to a timeline of names, reflecting
112 -- that, at some time T, this site thought that some index I referred to
113 -- a peer site P.
114 --
115 -- The `algs' map contains the following slots, populated during .
116 --
117 -- * `xform' is a timeline of transform names.
118 local SITEMAP = { }
119
120 -- The `ADDRMAP' maps (IPv4 or IPv6) socket addresses in the form
121 -- `[ADDR]:PORT' to a timeline of site names, populated based on claims made
122 -- by senders about themselves. The `GUESSMAP' is similar, but populated
123 -- based on assertions about recipients.
124 local ADDRMAP = { }
125 local GUESSMAP = { }
126
127 local function snd_sockname(st)
128 -- Return the sender's socket name as a thing which can be used as a table
129 -- index.
130
131 local pinfo = st.pinfo
132 return string.format("[%s]:%d", pinfo.net_src, pinfo.src_port)
133 end
134
135 local function rcv_sockname(st)
136 -- Return the recipient's socket name as a thing which can be used as a
137 -- table index.
138
139 local pinfo = st.pinfo
140 return string.format("[%s]:%d", pinfo.net_dst, pinfo.dst_port)
141 end
142
143 local function get_site_create(name)
144 -- If NAME refers to a known site, then return its information structure;
145 -- otherwise create a new one and return that.
146
147 local site = SITEMAP[name]
148 if site == nil then
149 site = { algs = { }, index = { } }
150 SITEMAP[name] = site
151 end
152 return site
153 end
154
155 local function notice_site_name(map, st, sock, name)
156 -- Record in MAP that the packet described in the state ST tells us that,
157 -- at that time, the site NAME appeared to be at address SOCK.
158
159 tl_add(get_timeline_create(map, sock), st.pinfo.rel_ts, name)
160 end
161
162 local function dump_algs(algs)
163 -- Dump the algorithms selection ALGS from a site structure.
164
165 return "xform=" .. algs.transform .. "; dh=" .. algs.dhgroup
166 end
167
168 local function dump_str(str) return str end
169
170 local function dump_addrmap(what, map)
171 -- Dump MAP, which is an address map like `ADDRMAP' or `GUESSMAP'; WHAT is
172 -- a string describing which map it is.
173
174 print(what .. "...")
175 for addr, tl in pairs(map) do
176 print(" " .. addr)
177 dump_timeline(tl, dump_str)
178 end
179 end
180
181 local function dump_tracking_state()
182 -- Dump the entire tracking state to standard output.
183
184 dump_addrmap("Address map", ADDRMAP)
185 dump_addrmap("Guess map", GUESSMAP)
186 print("Site map...")
187 for name, site in pairs(SITEMAP) do
188 print(" " .. name)
189 print(" algs...")
190 for peer, tl in pairs(site.algs) do
191 print(" " .. peer)
192 dump_timeline(tl, dump_algs)
193 end
194 print(" index...")
195 for ix, tl in pairs(site.index) do
196 print(" " .. ix)
197 dump_timeline(tl, dump_str)
198 end
199 end
200 end
201
202 local function notice_sndname(st, name)
203 -- Record that sender of the packet described by state ST is called NAME.
204
205 st.sndname = name
206 notice_site_name(ADDRMAP, st, snd_sockname(st), name)
207 end
208
209 local function notice_rcvname(st, name)
210 -- Record that the sender of the packet described by ST thought that its
211 -- recipient was called NAME.
212
213 st.rcvname = name
214 notice_site_name(GUESSMAP, st, rcv_sockname(st), name)
215 if st.sndname ~= nil then
216 local site = get_site_create(st.sndname)
217 tl_add(get_timeline_create(site.index, st.sndix), st.pinfo.rel_ts, name)
218 end
219 end
220
221 -- Tables describing the kinds of algorithms which can be selected.
222 local CAPTAB = {
223 [8] = { name = "serpent256cbc", kind = "transform",
224 desc = "Deprecated Serpent256-CBC transform" },
225 [9] = { name = "eaxserpent", kind = "transform",
226 desc = "Serpent256-EAX transform" },
227 [10] = { name = "tradzp", kind = "dhgroup",
228 desc = "Traditional Z_p Diffie--Hellman key agreement" },
229 [31] = { name = "mobile-priority", kind = "early",
230 desc = "Mobile site takes priority in case of MSG1 crossing" }
231 }
232
233 local function get_algname(kind, cap, dflt)
234 -- Fetch an algorithm of the given KIND, given its capability number CAP;
235 -- if CAP is nil, then return DFLT instead.
236
237 local name
238 if cap == nil then
239 name = dflt
240 else
241 local info = CAPTAB[cap]
242 if info ~= nil and info.kind == kind then name = info.name
243 else name = string.format("Unknown %s #%d", kind, cap)
244 end
245 end
246 return name
247 end
248
249 local function notice_alg_selection(st)
250 -- Record the algorithm selections declared in the packet described by ST.
251
252 local transform = get_algname("transform", st.transform, "serpent256cbc")
253 local dhgroup = get_algname("dhgroup", st.dhgroup, "tradzp")
254 local site = get_site_create(st.sndname)
255 local peer = get_site_create(st.rcvname)
256 local now = st.pinfo.rel_ts
257 local algs = { transform = transform, dhgroup = dhgroup }
258 tl_add(get_timeline_create(site.algs, st.rcvname), now, algs)
259 tl_add(get_timeline_create(peer.algs, st.sndname), now, algs)
260 end
261
262 -----------------------------------------------------------------------------
263 --- Protocol dissection primitives.
264
265 local PF = { } -- The table of protocol fields, filled in later.
266 local F = { } -- A table of field values, also filled in later.
267
268 local function msgcode(major, minor)
269 -- Construct a Secnet message number according to the complicated rules.
270
271 local majlo = bit.band(major, 0x000f)
272 local majhi = bit.band(major, 0xfff0)
273 local minlo = bit.band(minor, 0x000f)
274 local minhi = bit.band(minor, 0xfff0)
275 return bit.bxor(bit.lshift(majlo, 0),
276 bit.lshift(majlo, 8),
277 bit.lshift(majlo, 16),
278 bit.lshift(majlo, 24),
279 bit.lshift(majhi, 4),
280 bit.lshift(minlo, 4),
281 bit.lshift(minlo, 28),
282 bit.lshift(minhi, 16))
283 end
284
285 local function msgmajor(label)
286 -- Return the major message number from a LABEL.
287
288 local lo = bit.band(label, 0x000f)
289 local hi = bit.band(bit.rshift(label, 4), 0xfff0)
290 return bit.bxor(lo, bit.lshift(lo, 4), bit.lshift(lo, 12), hi)
291 end
292
293 local function msgminor(label)
294 -- Return the minor message number from a LABEL.
295
296 return bit.bxor(bit.lshift(bit.band(label, 0x00ff), 8),
297 bit.band(bit.rshift(label, 4), 0x000f),
298 bit.band(bit.rshift(label, 16), 0xfff0))
299 end
300
301 -- Main message-number table.
302 local M = { NAK = msgcode( 0, 0),
303 MSG0 = msgcode(0x2020, 0), -- !
304 MSG1 = msgcode( 1, 0),
305 MSG2 = msgcode( 2, 0),
306 MSG3 = msgcode( 3, 0),
307 MSG3BIS = msgcode( 3, 1),
308 MSG3TER = msgcode( 3, 2),
309 MSG4 = msgcode( 4, 0),
310 MSG5 = msgcode( 5, 0),
311 MSG6 = msgcode( 6, 0),
312 MSG7 = msgcode( 7, 0),
313 MSG8 = msgcode( 8, 0),
314 MSG9 = msgcode( 9, 0),
315 PROD = msgcode( 10, 0)}
316
317 -- The `dissect_*' functions follow a common protocol. They parse a thing
318 -- from a packet buffer BUF, of size SZ, starting from POS, and store
319 -- interesting things in a given TREE; when they're done, they return the
320 -- updated index where the next interesting thing might be, and maybe store
321 -- interesting things in the state ST. As a result, it's usually a simple
322 -- matter to parse a packet by invoking the appropriate primitive dissectors
323 -- in the right order.
324
325 local function dissect_sequence(dissect, st, buf, tree, pos, sz)
326 -- Dissect pieces of the packed in BUF with each of the dissectors in the
327 -- list DISSECT in turn.
328
329 for _, d in ipairs(dissect) do pos = d(st, buf, tree, pos, sz) end
330 return pos
331 end
332
333 local function dissect_wtf(st, buf, tree, pos, sz)
334 -- If POS is not at the end of the buffer, note that there's unexpected
335 -- stuff in the packet.
336
337 if pos < sz then tree:add(PF["secnet.wtf"], buf(pos, sz - pos)) end
338 return sz
339 end
340
341 local dissect_caps
342 do
343 -- This will be a list of the capability protocol field names, in the right
344 -- order. We just have to figure out what that will be.
345 local caplist = { }
346
347 do
348 local caps = { }
349
350 -- Firstly, build, in `caps', a list of the capability names and their
351 -- numbers.
352 local i = 1
353 caps[i] = { i = 15, cap = "explicit" }; i = 1 + 1
354 for j, cap in pairs(CAPTAB) do
355 caps[i] = { i = j, cap = cap.name }
356 i = i + 1
357 end
358
359 -- Sort the list. Now they're in the right order.
360 table.sort(caps, function (v0, v1) return v0.i < v1.i end)
361
362 -- Finally, write the entries to `caplist', with the `user' entry at the
363 -- start and the `unassigned' entry at the end.
364 i = 1
365 caplist[i] = "secnet.cap.user"; i = i + 1
366 for _, v in ipairs(caps) do
367 caplist[i] = "secnet.cap." .. v.cap
368 i = i + 1
369 end
370 caplist[i] = "secnet.cap.unassigned"; i = i + 1
371 end
372
373 function dissect_caps(st, buf, tree, pos, sz)
374 -- Dissect a capabilities word.
375
376 if pos < sz then
377 local cap = tree:add(PF["secnet.cap"], buf(pos, 4))
378 for _, pf in ipairs(caplist) do cap:add(PF[pf], buf(pos, 4)) end
379 pos = pos + 4
380 end
381 return pos
382 end
383 end
384
385 local function dissect_mtu(st, buf, tree, pos, sz)
386 -- Dissect an MTU request.
387
388 if pos < sz then tree:add(PF["secnet.mtu"], buf(pos, 2)); pos = pos + 2 end
389 return pos
390 end
391
392 local function make_dissect_name_xinfo(label, dissect_xinfo, hook)
393 -- Return a dissector function for reading a name and extra information.
394 -- The function will dissect a subtree rooted at the protocol field LABEL;
395 -- it will dissect the extra information using the list DISSECT_XINFO
396 -- (processed using `dissect_sequence'); and finally, if the packet hasn't
397 -- been visited yet, it will call HOOK(ST, NAME), where NAME is the name
398 -- string extracted from the packet.
399
400 return function (st, buf, tree, pos, sz)
401
402 -- Find the length of the whole thing.
403 local len = buf(pos, 2):uint()
404
405 -- Make the subtree root.
406 local sub = tree:add(PF[label], buf(pos, len + 2))
407
408 -- Find the length of the name. This is rather irritating: I'd like to
409 -- get Wireshark to do this, but it seems that `stringz' doesn't pay
410 -- attention to the buffer limits it's given. So read the whole lot and
411 -- find the null by hand.
412 local name = buf(pos + 2, len):string()
413 local z, _ = string.find(name, "\0", 1, true)
414 if z == nil then
415 z = len
416 else
417 z = z - 1
418 name = string.sub(name, 1, z)
419 end
420
421 -- Fill in the subtree.
422 sub:add(PF["secnet.namex.len"], buf(pos, 2)); pos = pos + 2
423 sub:add(PF["secnet.namex.name"], buf(pos, z))
424 if z < len then
425 dissect_sequence(dissect_xinfo, st, buf, sub, pos + z + 1, pos + len)
426 end
427
428 -- Maybe call the hook.
429 if hook ~= nil and not st.pinfo.visited then hook(st, name) end
430
431 -- We're done.
432 return pos + len
433 end
434 end
435
436 local function dissect_sndnonce(st, buf, tree, pos, sz)
437 -- Dissect the sender's nonce.
438
439 tree:add(PF["secnet.kx.sndnonce"], buf(pos, 8)); pos = pos + 8
440 return pos
441 end
442
443 local function dissect_rcvnonce(st, buf, tree, pos, sz)
444 -- Dissect the recipient's nonce.
445
446 tree:add(PF["secnet.kx.rcvnonce"], buf(pos, 8)); pos = pos + 8
447 return pos
448 end
449
450 local function dissect_transform(st, buf, tree, pos, sz)
451 -- Dissect the selected transform. Note this in the packet state for
452 -- later.
453
454 st.transform = buf(pos, 1):uint()
455 tree:add(PF["secnet.kx.transform"], buf(pos, 1)); pos = pos + 1
456 return pos
457 end
458
459 local function dissect_dhgroup(st, buf, tree, pos, sz)
460 -- Dissect the selected DH group. Note this in the packet state for later.
461
462 st.dhgroup = buf(pos, 1):uint()
463 tree:add(PF["secnet.kx.dhgroup"], buf(pos, 1)); pos = pos + 1
464 return pos
465 end
466
467 local function dissect_lenstr(st, buf, tree, label, pos, sz)
468 -- Dissect a simple string given its length.
469 local len = buf(pos, 2):uint()
470 local sub = tree:add(PF[label], buf(pos, len + 2))
471 sub:add(PF[label .. ".len"], buf(pos, 2)); pos = pos + 2
472 sub:add(PF[label .. ".text"], buf(pos, len)); pos = pos + len
473 return pos
474 end
475
476 local function dissect_dhval(st, buf, tree, pos, sz)
477 -- Dissect a Diffie--Hellman public value.
478
479 local len = buf(pos, 2):uint()
480 local sub = tree:add(PF["secnet.kx.dhval"], buf(pos, len + 2))
481 sub:add(PF["secnet.kx.dhval.len"], buf(pos, 2)); pos = pos + 2
482 sub:add(PF["secnet.kx.dhval.bytes"], buf(pos, len)); pos = pos + len
483 return pos
484 end
485
486 local function dissect_sig(st, buf, tree, pos, sz)
487 -- Dissect a signature.
488
489 return dissect_lenstr(st, buf, tree, "secnet.kx.sig", pos, sz)
490 end
491
492 local function find_algs_lookup(map, sock, now, ix)
493 -- Utility for `find_algs': look SOCK up in the address map ADDR, to find a
494 -- site; find its peer with index IX; and return the algorithm selection
495 -- current between the pair at time NOW. If the lookup fails, return nil.
496
497 local name = lookup_timeline(map, sock, now)
498 if name == nil then return nil end
499 local site = SITEMAP[name]
500 if site == nil then return nil end
501 local peername = lookup_timeline(site.index, ix, now)
502 if peername == nil then return nil end
503 return lookup_timeline(site.algs, peername, now)
504 end
505
506 local function find_algs(st)
507 -- Return the algorithm selection which applies to the packet described in
508 -- ST.
509
510 local now = st.pinfo.rel_ts
511 local sock = snd_sockname(st)
512 local algs = find_algs_lookup(ADDRMAP, sock, now, st.sndix)
513 if algs ~= nil then return algs
514 else return find_algs_lookup(GUESSMAP, sock, now, st.rcvix)
515 end
516 end
517
518 -- Transform-specific dissectors...
519 local dissect_ct = { }
520 function dissect_ct.unknown(st, why, buf, tree, pos, sz)
521 tree:add(PF["secnet.ciphertext.unknown"], buf(pos, sz - pos),
522 "Ciphertext with unknown structure: " .. why)
523 return sz
524 end
525 function dissect_ct.serpent256cbc(st, buf, tree, pos, sz)
526 tree:add(PF["secnet.ciphertext.iv"], buf(pos, 4)); pos = pos + 4
527 tree:add(PF["secnet.ciphertext.payload"], buf(pos, sz - pos))
528 return sz
529 end
530 function dissect_ct.eaxserpent(st, buf, tree, pos, sz)
531 local len = sz - pos - 20
532 tree:add(PF["secnet.ciphertext.payload"], buf(pos, len)); pos = pos + len
533 tree:add(PF["secnet.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
534 tree:add(PF["secnet.ciphertext.sequence"], buf(pos, 4)); pos = pos + 4
535 return pos
536 end
537
538 local function dissect_ciphertext(st, buf, tree, pos, sz)
539 -- Dissect a ciphertext.
540
541 local sub = tree:add(PF["secnet.ciphertext"], buf(pos, sz - pos))
542 local algs = find_algs(st)
543 local xform
544 if algs == nil then xform = nil else xform = algs.transform end
545 if xform == nil then
546 pos = dissect_ct.unknown(st, "unable to find negotiated transform",
547 buf, sub, pos, sz)
548 else
549 local func = dissect_ct[xform]
550 if func == nil then
551 pos = dissect_ct.unknown(st, "unsupported transform " .. xform,
552 buf, sub, pos, sz)
553 else
554 pos = func(st, buf, sub, pos, sz)
555 end
556 end
557 return pos
558 end
559
560 -----------------------------------------------------------------------------
561 --- The protocol information table.
562
563 local PKTINFO = {
564 -- This is the main table which describes the protocol. The top level maps
565 -- message labels to structures:
566 --
567 -- * `label' is the category code's symbolic name;
568 --
569 -- * `info' is a prefix for the information column display; and
570 --
571 -- * `dissect' is a sequence of primitive dissectors to run in order to
572 -- parse the rest of the packet.
573
574 [M.NAK] = {
575 label = "NAK",
576 info = "Stimulate fresh key exchange",
577 dissect = { dissect_wtf }
578 },
579 [M.MSG0] = {
580 label = "MSG0",
581 info = "MSG0",
582 dissect = { dissect_ciphertext }
583 },
584 [M.MSG1] = {
585 label = "MSG1",
586 info = "MSG1",
587 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
588 { dissect_caps, dissect_wtf },
589 notice_sndname),
590 make_dissect_name_xinfo("secnet.kx.rcvname",
591 { dissect_wtf },
592 notice_rcvname),
593 dissect_sndnonce,
594 dissect_wtf }
595 },
596 [M.MSG2] = {
597 label = "MSG2",
598 info = "MSG2",
599 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
600 { dissect_caps, dissect_wtf },
601 notice_sndname),
602 make_dissect_name_xinfo("secnet.kx.rcvname",
603 { dissect_wtf },
604 notice_rcvname),
605 dissect_sndnonce, dissect_rcvnonce,
606 dissect_wtf }
607 },
608 [M.MSG3] = {
609 label = "MSG3",
610 info = "MSG3",
611 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
612 { dissect_caps,
613 dissect_mtu,
614 dissect_wtf },
615 notice_sndname),
616 make_dissect_name_xinfo("secnet.kx.rcvname",
617 { dissect_wtf },
618 notice_rcvname),
619 dissect_sndnonce, dissect_rcvnonce,
620 dissect_wtf },
621 hook = notice_alg_selection
622 },
623 [M.MSG3BIS] = {
624 label = "MSG3BIS",
625 info = "MSG3BIS",
626 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
627 { dissect_caps,
628 dissect_mtu,
629 dissect_wtf },
630 notice_sndname),
631 make_dissect_name_xinfo("secnet.kx.rcvname",
632 { dissect_wtf },
633 notice_rcvname),
634 dissect_sndnonce, dissect_rcvnonce,
635 dissect_transform,
636 dissect_dhval, dissect_sig,
637 dissect_wtf },
638 hook = notice_alg_selection
639 },
640 [M.MSG3TER] = {
641 label = "MSG3TER",
642 info = "MSG3TER",
643 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
644 { dissect_caps,
645 dissect_mtu,
646 dissect_wtf },
647 notice_sndname),
648 make_dissect_name_xinfo("secnet.kx.rcvname",
649 { dissect_wtf },
650 notice_rcvname),
651 dissect_sndnonce, dissect_rcvnonce,
652 dissect_transform, dissect_dhgroup,
653 dissect_dhval, dissect_sig,
654 dissect_wtf },
655 hook = notice_alg_selection
656 },
657 [M.MSG4] = {
658 label = "MSG4",
659 info = "MSG4",
660 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
661 { dissect_caps,
662 dissect_mtu,
663 dissect_wtf },
664 notice_sndname),
665 make_dissect_name_xinfo("secnet.kx.rcvname",
666 { dissect_wtf },
667 notice_rcvname),
668 dissect_sndnonce, dissect_rcvnonce,
669 dissect_dhval, dissect_sig,
670 dissect_wtf }
671 },
672 [M.MSG5] = {
673 label = "MSG5",
674 info = "MSG5",
675 dissect = { dissect_ciphertext }
676 },
677 [M.MSG6] = {
678 label = "MSG6",
679 info = "MSG6",
680 dissect = { dissect_ciphertext }
681 },
682 [M.PROD] = {
683 label = "PROD",
684 info = "PROD",
685 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
686 { dissect_caps,
687 dissect_wtf },
688 notice_sndname),
689 make_dissect_name_xinfo("secnet.kx.rcvname",
690 { dissect_wtf },
691 notice_rcvname),
692 dissect_wtf }
693 },
694 }
695
696 do
697 -- Work through the master table and build the `msgtab'' table, mapping
698 -- message codes to their symbolic names for presentation.
699 local msgtab = { }
700 for i, v in pairs(PKTINFO) do msgtab[i] = v.label end
701
702 local capmap = { transform = { }, dhgroup = { }, early = { } }
703 for i, v in pairs(CAPTAB) do capmap[v.kind][i] = v.desc end
704
705 local ftab = {
706 -- The protocol fields. This table maps the field names to structures
707 -- used to build the fields, which are then stored in `PF' (declared way
708 -- above):
709 --
710 -- * `name' is the field name to show in the dissector tree view;
711 --
712 -- * `type' is the field type;
713 --
714 -- * `base' is a tweak describing how the field should be formatted;
715 --
716 -- * `mask' is used to single out a piece of a larger bitfield;
717 --
718 -- * `tab' names a mapping table used to convert numerical values to
719 -- symbolic names; and
720 --
721 -- * `hook' is a hook function to run the first time we see a packet,
722 -- to keep track of things.
723
724 ["secnet.hdr"] = {
725 name = "Common message header", type = ftypes.NONE
726 },
727 ["secnet.hdr.rcvix"] = {
728 name = "Recipient's site index for sender",
729 type = ftypes.UINT32, base = base.DEC
730 },
731 ["secnet.hdr.sndix"] = {
732 name = "Sender's site index for recipient",
733 type = ftypes.UINT32, base = base.DEC
734 },
735 ["secnet.hdr.label"] = {
736 name = "Message label", type = ftypes.UINT32,
737 base = base.HEX, tab = msgtab
738 },
739 ["secnet.kx.sndname"] = {
740 name = "Sender's site name and extended information",
741 type = ftypes.NONE
742 },
743 ["secnet.kx.rcvname"] = {
744 name = "Recipient's site name and extended information",
745 type = ftypes.NONE
746 },
747 ["secnet.namex.len"] = {
748 name = "Name/extended info length",
749 type = ftypes.UINT16, base = base.DEC
750 },
751 ["secnet.namex.name"] = {
752 name = "Site name", type = ftypes.STRING,
753 field = true, base = base.ASCII,
754 },
755 ["secnet.cap"] = {
756 name = "Advertised capability bits",
757 type = ftypes.UINT32, base = base.HEX
758 },
759 ["secnet.cap.user"] = {
760 name = "User-assigned capability bits",
761 type = ftypes.UINT32, mask = 0x000000ff, base = base.HEX
762 },
763 ["secnet.cap.explicit"] = {
764 name = "Transforms listed explicitly; all capability bits used",
765 type = ftypes.BOOLEAN, mask = 0x00008000, base = 32
766 },
767 ["secnet.mtu"] = {
768 name = "Sender's requested MTU", type = ftypes.UINT16, base = base.DEC
769 },
770 ["secnet.kx.sndnonce"] = {
771 name = "Sender's nonce", type = ftypes.BYTES, base = base.SPACE
772 },
773 ["secnet.kx.rcvnonce"] = {
774 name = "Recipient's nonce", type = ftypes.BYTES, base = base.SPACE
775 },
776 ["secnet.kx.transform"] = {
777 name = "Selected bulk-crypto transform", type = ftypes.UINT8,
778 base = base.DEC, tab = capmap.transform
779 },
780 ["secnet.kx.dhgroup"] = {
781 name = "Selected Diffie--Hellman group kind", type = ftypes.UINT8,
782 base = base.DEC, tab = capmap.dhgroup
783 },
784 ["secnet.kx.dhval"] = {
785 name = "Sender's public Diffie--Hellman value", type = ftypes.NONE
786 },
787 ["secnet.kx.dhval.len"] = {
788 name = "Sender's public Diffie--Hellman length",
789 type = ftypes.UINT16, base = base.DEC
790 },
791 ["secnet.kx.dhval.bytes"] = {
792 name = "Sender's public Diffie--Hellman value bytes",
793 type = ftypes.BYTES, base = base.SPACE
794 },
795 ["secnet.kx.sig"] = {
796 name = "Sender's signature", type = ftypes.NONE
797 },
798 ["secnet.kx.sig.len"] = {
799 name = "Sender's signature length",
800 type = ftypes.UINT16, base = base.DEC
801 },
802 ["secnet.kx.sig.text"] = {
803 name = "Sender's signature text", type = ftypes.STRING,
804 base = base.ASCII
805 },
806 ["secnet.ciphertext"] = {
807 name = "Encrypted data", type = ftypes.NONE
808 },
809 ["secnet.ciphertext.unknown"] = {
810 name = "Ciphertext with unknown structure",
811 type = ftypes.BYTES, base = base.SPACE
812 },
813 ["secnet.ciphertext.iv"] = {
814 name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
815 },
816 ["secnet.ciphertext.sequence"] = {
817 name = "Sequence number", type = ftypes.UINT32, base = base.DEC
818 },
819 ["secnet.ciphertext.payload"] = {
820 name = "Encrypted payload", type = ftypes.BYTES, base = base.SPACE
821 },
822 ["secnet.ciphertext.tag"] = {
823 name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
824 },
825 ["secnet.wtf"] = {
826 name = "Unexpected trailing data",
827 type = ftypes.BYTES, base = base.SPACE
828 }
829 }
830
831 -- Add the remaining capability fields. Calculate the unassigned mask
832 -- based on the assigned bits.
833 local unasgn = 0x7fff7f00
834 for i, v in pairs(CAPTAB) do
835 local flag = bit.lshift(1, i)
836 ftab["secnet.cap." .. v.name] = {
837 name = v.desc, type = ftypes.BOOLEAN,
838 mask = flag, base = 32
839 }
840 unasgn = bit.band(unasgn, bit.bnot(flag))
841 end
842 ftab["secnet.cap.unassigned"] = {
843 name = "Unassigned capability bits",
844 type = ftypes.UINT32, mask = unasgn, base = base.HEX
845 }
846
847 -- Convert this table into the protocol fields, and populate `PF'.
848 local ff = { }
849 local i = 1
850
851 -- Figure out whether we can use `none' fields (see below).
852 local use_none_p = rawget(ProtoField, 'none') ~= nil
853 for abbr, args in pairs(ftab) do
854
855 -- An annoying hack. Older versions of Wireshark don't allow setting
856 -- fields with type `none', which is a shame because they're ideal as
857 -- internal tree nodes.
858 ty = args.type
859 b = args.base
860 if ty == ftypes.NONE then
861 if use_none_p then
862 b = base.NONE
863 else
864 ty = ftypes.BYTES
865 b = base.SPACE
866 end
867 end
868
869 -- Go make the field.
870 local f = ProtoField.new(args.name, abbr, ty,
871 args.tab, b, args.mask, args.descr)
872 PF[abbr] = f
873 ff[i] = f; i = i + 1
874 end
875 secnet.fields = PF
876
877 -- Make readable fields corresponding to especially interesting protocol
878 -- fields.
879 for abbr, args in pairs(ftab) do
880 if args.field then F[abbr] = Field.new(abbr) end
881 end
882 end
883
884 -----------------------------------------------------------------------------
885 --- The main dissector.
886
887 function secnet.dissector(buf, pinfo, tree)
888
889 -- Fill in the obvious stuff.
890 pinfo.cols.protocol = "Secnet"
891
892 local sz = buf:reported_length_remaining()
893 local sub = tree:add(secnet, buf(0, sz), "Secnet packet")
894 local p = 12
895
896 -- Decode the message header.
897 hdr = sub:add(PF["secnet.hdr"], buf(0, 12))
898 local rcvix = buf(0, 4):uint(); hdr:add(PF["secnet.hdr.rcvix"], buf(0, 4))
899 local sndix = buf(4, 4):uint(); hdr:add(PF["secnet.hdr.sndix"], buf(4, 4))
900 local label = buf(8, 4):uint()
901 hdr:add(PF["secnet.hdr.label"], buf(8, 4), label,
902 string.format("Message label (major = 0x%04x, minor = 0x%04x)",
903 msgmajor(label), msgminor(label)))
904 local st = { pinfo = pinfo, label = label, rcvix = rcvix, sndix = sndix }
905 local info = PKTINFO[label]
906
907 -- Dispatch using the master protocol table.
908 if info == nil then
909 pinfo.cols.info = string.format("Unknown message label 0x%08x", label)
910 else
911 pinfo.cols.info = info.info
912 p = dissect_sequence(info.dissect, st, buf, sub, p, sz)
913 end
914
915 -- Invoke the hook if necessary.
916 if not pinfo.visited and info.hook ~= nil then info.hook(st) end
917
918 -- Return the final position we reached.
919 return p
920 end
921
922 -- We're done. Register the dissector.
923 DissectorTable.get("udp.port"):add(410, secnet)
924
925 -------- That's all, folks --------------------------------------------------