ec-field-test.c: Make the field-element type use internal format.
[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 [11] = { name = "x25519", kind = "dhgroup",
230 desc = "X25519 elliptic curve Diffie--Hellman key agreement" },
231 [12] = { name = "x448", kind = "dhgroup",
232 desc = "X448 elliptic curve Diffie--Hellman key agreement" },
233 [31] = { name = "mobile-priority", kind = "early",
234 desc = "Mobile site takes priority in case of MSG1 crossing" }
235 }
236
237 local function get_algname(kind, cap, dflt)
238 -- Fetch an algorithm of the given KIND, given its capability number CAP;
239 -- if CAP is nil, then return DFLT instead.
240
241 local name
242 if cap == nil then
243 name = dflt
244 else
245 local info = CAPTAB[cap]
246 if info ~= nil and info.kind == kind then name = info.name
247 else name = string.format("Unknown %s #%d", kind, cap)
248 end
249 end
250 return name
251 end
252
253 local function notice_alg_selection(st)
254 -- Record the algorithm selections declared in the packet described by ST.
255
256 local transform = get_algname("transform", st.transform, "serpent256cbc")
257 local dhgroup = get_algname("dhgroup", st.dhgroup, "tradzp")
258 local site = get_site_create(st.sndname)
259 local peer = get_site_create(st.rcvname)
260 local now = st.pinfo.rel_ts
261 local algs = { transform = transform, dhgroup = dhgroup }
262 tl_add(get_timeline_create(site.algs, st.rcvname), now, algs)
263 tl_add(get_timeline_create(peer.algs, st.sndname), now, algs)
264 end
265
266 -----------------------------------------------------------------------------
267 --- Protocol dissection primitives.
268
269 local PF = { } -- The table of protocol fields, filled in later.
270 local F = { } -- A table of field values, also filled in later.
271
272 local function msgcode(major, minor)
273 -- Construct a Secnet message number according to the complicated rules.
274
275 local majlo = bit.band(major, 0x000f)
276 local majhi = bit.band(major, 0xfff0)
277 local minlo = bit.band(minor, 0x000f)
278 local minhi = bit.band(minor, 0xfff0)
279 return bit.bxor(bit.lshift(majlo, 0),
280 bit.lshift(majlo, 8),
281 bit.lshift(majlo, 16),
282 bit.lshift(majlo, 24),
283 bit.lshift(majhi, 4),
284 bit.lshift(minlo, 4),
285 bit.lshift(minlo, 28),
286 bit.lshift(minhi, 16))
287 end
288
289 local function msgmajor(label)
290 -- Return the major message number from a LABEL.
291
292 local lo = bit.band(label, 0x000f)
293 local hi = bit.band(bit.rshift(label, 4), 0xfff0)
294 return bit.bxor(lo, bit.lshift(lo, 4), bit.lshift(lo, 12), hi)
295 end
296
297 local function msgminor(label)
298 -- Return the minor message number from a LABEL.
299
300 return bit.bxor(bit.lshift(bit.band(label, 0x00ff), 8),
301 bit.band(bit.rshift(label, 4), 0x000f),
302 bit.band(bit.rshift(label, 16), 0xfff0))
303 end
304
305 -- Main message-number table.
306 local M = { NAK = msgcode( 0, 0),
307 MSG0 = msgcode(0x2020, 0), -- !
308 MSG1 = msgcode( 1, 0),
309 MSG2 = msgcode( 2, 0),
310 MSG3 = msgcode( 3, 0),
311 MSG3BIS = msgcode( 3, 1),
312 MSG3TER = msgcode( 3, 2),
313 MSG4 = msgcode( 4, 0),
314 MSG5 = msgcode( 5, 0),
315 MSG6 = msgcode( 6, 0),
316 MSG7 = msgcode( 7, 0),
317 MSG8 = msgcode( 8, 0),
318 MSG9 = msgcode( 9, 0),
319 PROD = msgcode( 10, 0)}
320
321 -- The `dissect_*' functions follow a common protocol. They parse a thing
322 -- from a packet buffer BUF, of size SZ, starting from POS, and store
323 -- interesting things in a given TREE; when they're done, they return the
324 -- updated index where the next interesting thing might be, and maybe store
325 -- interesting things in the state ST. As a result, it's usually a simple
326 -- matter to parse a packet by invoking the appropriate primitive dissectors
327 -- in the right order.
328
329 local function dissect_sequence(dissect, st, buf, tree, pos, sz)
330 -- Dissect pieces of the packed in BUF with each of the dissectors in the
331 -- list DISSECT in turn.
332
333 for _, d in ipairs(dissect) do pos = d(st, buf, tree, pos, sz) end
334 return pos
335 end
336
337 local function dissect_wtf(st, buf, tree, pos, sz)
338 -- If POS is not at the end of the buffer, note that there's unexpected
339 -- stuff in the packet.
340
341 if pos < sz then tree:add(PF["secnet.wtf"], buf(pos, sz - pos)) end
342 return sz
343 end
344
345 local dissect_caps
346 do
347 -- This will be a list of the capability protocol field names, in the right
348 -- order. We just have to figure out what that will be.
349 local caplist = { }
350
351 do
352 local caps = { }
353
354 -- Firstly, build, in `caps', a list of the capability names and their
355 -- numbers.
356 local i = 1
357 caps[i] = { i = 15, cap = "explicit" }; i = 1 + 1
358 for j, cap in pairs(CAPTAB) do
359 caps[i] = { i = j, cap = cap.name }
360 i = i + 1
361 end
362
363 -- Sort the list. Now they're in the right order.
364 table.sort(caps, function (v0, v1) return v0.i < v1.i end)
365
366 -- Finally, write the entries to `caplist', with the `user' entry at the
367 -- start and the `unassigned' entry at the end.
368 i = 1
369 caplist[i] = "secnet.cap.user"; i = i + 1
370 for _, v in ipairs(caps) do
371 caplist[i] = "secnet.cap." .. v.cap
372 i = i + 1
373 end
374 caplist[i] = "secnet.cap.unassigned"; i = i + 1
375 end
376
377 function dissect_caps(st, buf, tree, pos, sz)
378 -- Dissect a capabilities word.
379
380 if pos < sz then
381 local cap = tree:add(PF["secnet.cap"], buf(pos, 4))
382 for _, pf in ipairs(caplist) do cap:add(PF[pf], buf(pos, 4)) end
383 pos = pos + 4
384 end
385 return pos
386 end
387 end
388
389 local function dissect_mtu(st, buf, tree, pos, sz)
390 -- Dissect an MTU request.
391
392 if pos < sz then tree:add(PF["secnet.mtu"], buf(pos, 2)); pos = pos + 2 end
393 return pos
394 end
395
396 local function make_dissect_name_xinfo(label, dissect_xinfo, hook)
397 -- Return a dissector function for reading a name and extra information.
398 -- The function will dissect a subtree rooted at the protocol field LABEL;
399 -- it will dissect the extra information using the list DISSECT_XINFO
400 -- (processed using `dissect_sequence'); and finally, if the packet hasn't
401 -- been visited yet, it will call HOOK(ST, NAME), where NAME is the name
402 -- string extracted from the packet.
403
404 return function (st, buf, tree, pos, sz)
405
406 -- Find the length of the whole thing.
407 local len = buf(pos, 2):uint()
408
409 -- Make the subtree root.
410 local sub = tree:add(PF[label], buf(pos, len + 2))
411
412 -- Find the length of the name. This is rather irritating: I'd like to
413 -- get Wireshark to do this, but it seems that `stringz' doesn't pay
414 -- attention to the buffer limits it's given. So read the whole lot and
415 -- find the null by hand.
416 local name = buf(pos + 2, len):string()
417 local z, _ = string.find(name, "\0", 1, true)
418 if z == nil then
419 z = len
420 else
421 z = z - 1
422 name = string.sub(name, 1, z)
423 end
424
425 -- Fill in the subtree.
426 sub:add(PF["secnet.namex.len"], buf(pos, 2)); pos = pos + 2
427 sub:add(PF["secnet.namex.name"], buf(pos, z))
428 if z < len then
429 dissect_sequence(dissect_xinfo, st, buf, sub, pos + z + 1, pos + len)
430 end
431
432 -- Maybe call the hook.
433 if hook ~= nil and not st.pinfo.visited then hook(st, name) end
434
435 -- We're done.
436 return pos + len
437 end
438 end
439
440 local function dissect_sndnonce(st, buf, tree, pos, sz)
441 -- Dissect the sender's nonce.
442
443 tree:add(PF["secnet.kx.sndnonce"], buf(pos, 8)); pos = pos + 8
444 return pos
445 end
446
447 local function dissect_rcvnonce(st, buf, tree, pos, sz)
448 -- Dissect the recipient's nonce.
449
450 tree:add(PF["secnet.kx.rcvnonce"], buf(pos, 8)); pos = pos + 8
451 return pos
452 end
453
454 local function dissect_transform(st, buf, tree, pos, sz)
455 -- Dissect the selected transform. Note this in the packet state for
456 -- later.
457
458 st.transform = buf(pos, 1):uint()
459 tree:add(PF["secnet.kx.transform"], buf(pos, 1)); pos = pos + 1
460 return pos
461 end
462
463 local function dissect_dhgroup(st, buf, tree, pos, sz)
464 -- Dissect the selected DH group. Note this in the packet state for later.
465
466 st.dhgroup = buf(pos, 1):uint()
467 tree:add(PF["secnet.kx.dhgroup"], buf(pos, 1)); pos = pos + 1
468 return pos
469 end
470
471 local function dissect_lenstr(st, buf, tree, label, pos, sz)
472 -- Dissect a simple string given its length.
473 local len = buf(pos, 2):uint()
474 local sub = tree:add(PF[label], buf(pos, len + 2))
475 sub:add(PF[label .. ".len"], buf(pos, 2)); pos = pos + 2
476 sub:add(PF[label .. ".text"], buf(pos, len)); pos = pos + len
477 return pos
478 end
479
480 local function dissect_dhval(st, buf, tree, pos, sz)
481 -- Dissect a Diffie--Hellman public value.
482
483 local len = buf(pos, 2):uint()
484 local sub = tree:add(PF["secnet.kx.dhval"], buf(pos, len + 2))
485 sub:add(PF["secnet.kx.dhval.len"], buf(pos, 2)); pos = pos + 2
486 sub:add(PF["secnet.kx.dhval.bytes"], buf(pos, len)); pos = pos + len
487 return pos
488 end
489
490 local function dissect_sig(st, buf, tree, pos, sz)
491 -- Dissect a signature.
492
493 return dissect_lenstr(st, buf, tree, "secnet.kx.sig", pos, sz)
494 end
495
496 local function find_algs_lookup(map, sock, now, ix)
497 -- Utility for `find_algs': look SOCK up in the address map ADDR, to find a
498 -- site; find its peer with index IX; and return the algorithm selection
499 -- current between the pair at time NOW. If the lookup fails, return nil.
500
501 local name = lookup_timeline(map, sock, now)
502 if name == nil then return nil end
503 local site = SITEMAP[name]
504 if site == nil then return nil end
505 local peername = lookup_timeline(site.index, ix, now)
506 if peername == nil then return nil end
507 return lookup_timeline(site.algs, peername, now)
508 end
509
510 local function find_algs(st)
511 -- Return the algorithm selection which applies to the packet described in
512 -- ST.
513
514 local now = st.pinfo.rel_ts
515 local sock = snd_sockname(st)
516 local algs = find_algs_lookup(ADDRMAP, sock, now, st.sndix)
517 if algs ~= nil then return algs
518 else return find_algs_lookup(GUESSMAP, sock, now, st.rcvix)
519 end
520 end
521
522 -- Transform-specific dissectors...
523 local dissect_ct = { }
524 function dissect_ct.unknown(st, why, buf, tree, pos, sz)
525 tree:add(PF["secnet.ciphertext.unknown"], buf(pos, sz - pos),
526 "Ciphertext with unknown structure: " .. why)
527 return sz
528 end
529 function dissect_ct.serpent256cbc(st, buf, tree, pos, sz)
530 tree:add(PF["secnet.ciphertext.iv"], buf(pos, 4)); pos = pos + 4
531 tree:add(PF["secnet.ciphertext.payload"], buf(pos, sz - pos))
532 return sz
533 end
534 function dissect_ct.eaxserpent(st, buf, tree, pos, sz)
535 local len = sz - pos - 20
536 tree:add(PF["secnet.ciphertext.payload"], buf(pos, len)); pos = pos + len
537 tree:add(PF["secnet.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
538 tree:add(PF["secnet.ciphertext.sequence"], buf(pos, 4)); pos = pos + 4
539 return pos
540 end
541
542 local function dissect_ciphertext(st, buf, tree, pos, sz)
543 -- Dissect a ciphertext.
544
545 local sub = tree:add(PF["secnet.ciphertext"], buf(pos, sz - pos))
546 local algs = find_algs(st)
547 local xform
548 if algs == nil then xform = nil else xform = algs.transform end
549 if xform == nil then
550 pos = dissect_ct.unknown(st, "unable to find negotiated transform",
551 buf, sub, pos, sz)
552 else
553 local func = dissect_ct[xform]
554 if func == nil then
555 pos = dissect_ct.unknown(st, "unsupported transform " .. xform,
556 buf, sub, pos, sz)
557 else
558 pos = func(st, buf, sub, pos, sz)
559 end
560 end
561 return pos
562 end
563
564 -----------------------------------------------------------------------------
565 --- The protocol information table.
566
567 local PKTINFO = {
568 -- This is the main table which describes the protocol. The top level maps
569 -- message labels to structures:
570 --
571 -- * `label' is the category code's symbolic name;
572 --
573 -- * `info' is a prefix for the information column display; and
574 --
575 -- * `dissect' is a sequence of primitive dissectors to run in order to
576 -- parse the rest of the packet.
577
578 [M.NAK] = {
579 label = "NAK",
580 info = "Stimulate fresh key exchange",
581 dissect = { dissect_wtf }
582 },
583 [M.MSG0] = {
584 label = "MSG0",
585 info = "MSG0",
586 dissect = { dissect_ciphertext }
587 },
588 [M.MSG1] = {
589 label = "MSG1",
590 info = "MSG1",
591 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
592 { dissect_caps, dissect_wtf },
593 notice_sndname),
594 make_dissect_name_xinfo("secnet.kx.rcvname",
595 { dissect_wtf },
596 notice_rcvname),
597 dissect_sndnonce,
598 dissect_wtf }
599 },
600 [M.MSG2] = {
601 label = "MSG2",
602 info = "MSG2",
603 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
604 { dissect_caps, dissect_wtf },
605 notice_sndname),
606 make_dissect_name_xinfo("secnet.kx.rcvname",
607 { dissect_wtf },
608 notice_rcvname),
609 dissect_sndnonce, dissect_rcvnonce,
610 dissect_wtf }
611 },
612 [M.MSG3] = {
613 label = "MSG3",
614 info = "MSG3",
615 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
616 { dissect_caps,
617 dissect_mtu,
618 dissect_wtf },
619 notice_sndname),
620 make_dissect_name_xinfo("secnet.kx.rcvname",
621 { dissect_wtf },
622 notice_rcvname),
623 dissect_sndnonce, dissect_rcvnonce,
624 dissect_wtf },
625 hook = notice_alg_selection
626 },
627 [M.MSG3BIS] = {
628 label = "MSG3BIS",
629 info = "MSG3BIS",
630 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
631 { dissect_caps,
632 dissect_mtu,
633 dissect_wtf },
634 notice_sndname),
635 make_dissect_name_xinfo("secnet.kx.rcvname",
636 { dissect_wtf },
637 notice_rcvname),
638 dissect_sndnonce, dissect_rcvnonce,
639 dissect_transform,
640 dissect_dhval, dissect_sig,
641 dissect_wtf },
642 hook = notice_alg_selection
643 },
644 [M.MSG3TER] = {
645 label = "MSG3TER",
646 info = "MSG3TER",
647 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
648 { dissect_caps,
649 dissect_mtu,
650 dissect_wtf },
651 notice_sndname),
652 make_dissect_name_xinfo("secnet.kx.rcvname",
653 { dissect_wtf },
654 notice_rcvname),
655 dissect_sndnonce, dissect_rcvnonce,
656 dissect_transform, dissect_dhgroup,
657 dissect_dhval, dissect_sig,
658 dissect_wtf },
659 hook = notice_alg_selection
660 },
661 [M.MSG4] = {
662 label = "MSG4",
663 info = "MSG4",
664 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
665 { dissect_caps,
666 dissect_mtu,
667 dissect_wtf },
668 notice_sndname),
669 make_dissect_name_xinfo("secnet.kx.rcvname",
670 { dissect_wtf },
671 notice_rcvname),
672 dissect_sndnonce, dissect_rcvnonce,
673 dissect_dhval, dissect_sig,
674 dissect_wtf }
675 },
676 [M.MSG5] = {
677 label = "MSG5",
678 info = "MSG5",
679 dissect = { dissect_ciphertext }
680 },
681 [M.MSG6] = {
682 label = "MSG6",
683 info = "MSG6",
684 dissect = { dissect_ciphertext }
685 },
686 [M.PROD] = {
687 label = "PROD",
688 info = "PROD",
689 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
690 { dissect_caps,
691 dissect_wtf },
692 notice_sndname),
693 make_dissect_name_xinfo("secnet.kx.rcvname",
694 { dissect_wtf },
695 notice_rcvname),
696 dissect_wtf }
697 },
698 }
699
700 do
701 -- Work through the master table and build the `msgtab'' table, mapping
702 -- message codes to their symbolic names for presentation.
703 local msgtab = { }
704 for i, v in pairs(PKTINFO) do msgtab[i] = v.label end
705
706 local capmap = { transform = { }, dhgroup = { }, early = { } }
707 for i, v in pairs(CAPTAB) do capmap[v.kind][i] = v.desc end
708
709 local ftab = {
710 -- The protocol fields. This table maps the field names to structures
711 -- used to build the fields, which are then stored in `PF' (declared way
712 -- above):
713 --
714 -- * `name' is the field name to show in the dissector tree view;
715 --
716 -- * `type' is the field type;
717 --
718 -- * `base' is a tweak describing how the field should be formatted;
719 --
720 -- * `mask' is used to single out a piece of a larger bitfield;
721 --
722 -- * `tab' names a mapping table used to convert numerical values to
723 -- symbolic names; and
724 --
725 -- * `hook' is a hook function to run the first time we see a packet,
726 -- to keep track of things.
727
728 ["secnet.hdr"] = {
729 name = "Common message header", type = ftypes.NONE
730 },
731 ["secnet.hdr.rcvix"] = {
732 name = "Recipient's site index for sender",
733 type = ftypes.UINT32, base = base.DEC
734 },
735 ["secnet.hdr.sndix"] = {
736 name = "Sender's site index for recipient",
737 type = ftypes.UINT32, base = base.DEC
738 },
739 ["secnet.hdr.label"] = {
740 name = "Message label", type = ftypes.UINT32,
741 base = base.HEX, tab = msgtab
742 },
743 ["secnet.kx.sndname"] = {
744 name = "Sender's site name and extended information",
745 type = ftypes.NONE
746 },
747 ["secnet.kx.rcvname"] = {
748 name = "Recipient's site name and extended information",
749 type = ftypes.NONE
750 },
751 ["secnet.namex.len"] = {
752 name = "Name/extended info length",
753 type = ftypes.UINT16, base = base.DEC
754 },
755 ["secnet.namex.name"] = {
756 name = "Site name", type = ftypes.STRING,
757 field = true, base = base.ASCII,
758 },
759 ["secnet.cap"] = {
760 name = "Advertised capability bits",
761 type = ftypes.UINT32, base = base.HEX
762 },
763 ["secnet.cap.user"] = {
764 name = "User-assigned capability bits",
765 type = ftypes.UINT32, mask = 0x000000ff, base = base.HEX
766 },
767 ["secnet.cap.explicit"] = {
768 name = "Transforms listed explicitly; all capability bits used",
769 type = ftypes.BOOLEAN, mask = 0x00008000, base = 32
770 },
771 ["secnet.mtu"] = {
772 name = "Sender's requested MTU", type = ftypes.UINT16, base = base.DEC
773 },
774 ["secnet.kx.sndnonce"] = {
775 name = "Sender's nonce", type = ftypes.BYTES, base = base.SPACE
776 },
777 ["secnet.kx.rcvnonce"] = {
778 name = "Recipient's nonce", type = ftypes.BYTES, base = base.SPACE
779 },
780 ["secnet.kx.transform"] = {
781 name = "Selected bulk-crypto transform", type = ftypes.UINT8,
782 base = base.DEC, tab = capmap.transform
783 },
784 ["secnet.kx.dhgroup"] = {
785 name = "Selected Diffie--Hellman group kind", type = ftypes.UINT8,
786 base = base.DEC, tab = capmap.dhgroup
787 },
788 ["secnet.kx.dhval"] = {
789 name = "Sender's public Diffie--Hellman value", type = ftypes.NONE
790 },
791 ["secnet.kx.dhval.len"] = {
792 name = "Sender's public Diffie--Hellman length",
793 type = ftypes.UINT16, base = base.DEC
794 },
795 ["secnet.kx.dhval.bytes"] = {
796 name = "Sender's public Diffie--Hellman value bytes",
797 type = ftypes.BYTES, base = base.SPACE
798 },
799 ["secnet.kx.sig"] = {
800 name = "Sender's signature", type = ftypes.NONE
801 },
802 ["secnet.kx.sig.len"] = {
803 name = "Sender's signature length",
804 type = ftypes.UINT16, base = base.DEC
805 },
806 ["secnet.kx.sig.text"] = {
807 name = "Sender's signature text", type = ftypes.STRING,
808 base = base.ASCII
809 },
810 ["secnet.ciphertext"] = {
811 name = "Encrypted data", type = ftypes.NONE
812 },
813 ["secnet.ciphertext.unknown"] = {
814 name = "Ciphertext with unknown structure",
815 type = ftypes.BYTES, base = base.SPACE
816 },
817 ["secnet.ciphertext.iv"] = {
818 name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
819 },
820 ["secnet.ciphertext.sequence"] = {
821 name = "Sequence number", type = ftypes.UINT32, base = base.DEC
822 },
823 ["secnet.ciphertext.payload"] = {
824 name = "Encrypted payload", type = ftypes.BYTES, base = base.SPACE
825 },
826 ["secnet.ciphertext.tag"] = {
827 name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
828 },
829 ["secnet.wtf"] = {
830 name = "Unexpected trailing data",
831 type = ftypes.BYTES, base = base.SPACE
832 }
833 }
834
835 -- Add the remaining capability fields. Calculate the unassigned mask
836 -- based on the assigned bits.
837 local unasgn = 0x7fff7f00
838 for i, v in pairs(CAPTAB) do
839 local flag = bit.lshift(1, i)
840 ftab["secnet.cap." .. v.name] = {
841 name = v.desc, type = ftypes.BOOLEAN,
842 mask = flag, base = 32
843 }
844 unasgn = bit.band(unasgn, bit.bnot(flag))
845 end
846 ftab["secnet.cap.unassigned"] = {
847 name = "Unassigned capability bits",
848 type = ftypes.UINT32, mask = unasgn, base = base.HEX
849 }
850
851 -- Convert this table into the protocol fields, and populate `PF'.
852 local ff = { }
853 local i = 1
854
855 -- Figure out whether we can use `none' fields (see below).
856 local use_none_p = rawget(ProtoField, 'none') ~= nil
857 for abbr, args in pairs(ftab) do
858
859 -- An annoying hack. Older versions of Wireshark don't allow setting
860 -- fields with type `none', which is a shame because they're ideal as
861 -- internal tree nodes.
862 ty = args.type
863 b = args.base
864 if ty == ftypes.NONE then
865 if use_none_p then
866 b = base.NONE
867 else
868 ty = ftypes.BYTES
869 b = base.SPACE
870 end
871 end
872
873 -- Go make the field.
874 local f = ProtoField.new(args.name, abbr, ty,
875 args.tab, b, args.mask, args.descr)
876 PF[abbr] = f
877 ff[i] = f; i = i + 1
878 end
879 secnet.fields = PF
880
881 -- Make readable fields corresponding to especially interesting protocol
882 -- fields.
883 for abbr, args in pairs(ftab) do
884 if args.field then F[abbr] = Field.new(abbr) end
885 end
886 end
887
888 -----------------------------------------------------------------------------
889 --- The main dissector.
890
891 function secnet.dissector(buf, pinfo, tree)
892
893 -- Fill in the obvious stuff.
894 pinfo.cols.protocol = "Secnet"
895
896 local sz = buf:reported_length_remaining()
897 local sub = tree:add(secnet, buf(0, sz), "Secnet packet")
898 local p = 12
899
900 -- Decode the message header.
901 hdr = sub:add(PF["secnet.hdr"], buf(0, 12))
902 local rcvix = buf(0, 4):uint(); hdr:add(PF["secnet.hdr.rcvix"], buf(0, 4))
903 local sndix = buf(4, 4):uint(); hdr:add(PF["secnet.hdr.sndix"], buf(4, 4))
904 local label = buf(8, 4):uint()
905 hdr:add(PF["secnet.hdr.label"], buf(8, 4), label,
906 string.format("Message label (major = 0x%04x, minor = 0x%04x)",
907 msgmajor(label), msgminor(label)))
908 local st = { pinfo = pinfo, label = label, rcvix = rcvix, sndix = sndix }
909 local info = PKTINFO[label]
910
911 -- Dispatch using the master protocol table.
912 if info == nil then
913 pinfo.cols.info = string.format("Unknown message label 0x%08x", label)
914 else
915 pinfo.cols.info = info.info
916 p = dissect_sequence(info.dissect, st, buf, sub, p, sz)
917 end
918
919 -- Invoke the hook if necessary.
920 if not pinfo.visited and info.hook ~= nil then info.hook(st) end
921
922 -- Return the final position we reached.
923 return p
924 end
925
926 -- We're done. Register the dissector.
927 DissectorTable.get("udp.port"):add(410, secnet)
928
929 -------- That's all, folks --------------------------------------------------