chiark / gitweb /
Upgrade licence to GPLv3+.
[tripe] / wireshark / tripe.lua
... / ...
CommitLineData
1--- -*-lua-*-
2---
3--- Wireshark protocol dissector for TrIPE
4---
5--- (c) 2017 Straylight/Edgeware
6---
7
8-------- Licensing notice ---------------------------------------------------
9---
10--- This file is part of Trivial IP Encryption (TrIPE).
11---
12--- TrIPE is free software: you can redistribute it and/or modify it under
13--- the terms of the GNU General Public License as published by the Free
14--- Software Foundation; either version 3 of the License, or (at your
15--- option) any later version.
16---
17--- TrIPE is distributed in the hope that it will be useful, but WITHOUT
18--- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19--- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20--- for more details.
21---
22--- You should have received a copy of the GNU General Public License
23--- along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
24
25local tripe = Proto("tripe", "TrIPE VPN")
26
27-----------------------------------------------------------------------------
28--- Configuration handling.
29
30local CONFIG = {
31 -- Information about the configuration variables. This table, when it's
32 -- set up, maps the internal names, which are used to refer to
33 -- configuration variables in the rest of this code, to a little structure:
34 --
35 -- * `var' names the variable, and is the usual key for lookups;
36 --
37 -- * `name' is the label used in the dialogue box;
38 --
39 -- * `type' is the type of variable, currently either `enum' or `int';
40 --
41 -- * `descr' is a longer (but generally fairly useless) description for
42 -- use in a tooltip;
43 --
44 -- * `allowed' is a sequence of allowed values for an `enum' variable;
45 -- and
46 --
47 -- * `min' and `max' are the limits on permitted values for an `int'
48 -- variable (and may be omitted).
49 --
50 -- More slots are added at runtime:
51 --
52 -- * `map' is a table mapping string values to their integer indices, as
53 -- stored in Wireshark's preferences database.
54 --
55 -- Initially, though, the table is given as a sequence, so that the
56 -- preferences can be populated in a consistent (and approximately logical)
57 -- order.
58
59 { var = "bulk", name = "Bulk transform",
60 type = "enum", allowed = { "v0", "iiv", "naclbox" },
61 descr = "Bulk cryptographic transform", default = "v0" },
62 { var = "hashsz", name = "Hash length", type = "int", min = 0,
63 descr = "Hash length (bytes)", default = 20 },
64 { var = "tagsz", name = "Tag length", type = "int", min = 0,
65 descr = "Authentication tag length (bytes)", default = 10 },
66 { var = "ivsz", name = "IV length", type = "int", min = 0,
67 descr = "Initialization vector length (bytes)", default = 8 },
68 { var = "kx", name = "Key-exchange group",
69 type = "enum", allowed = { "dh", "ec", "x25519", "x448" },
70 descr = "Key-exchange group type", default = "dh" },
71 { var = "scsz", name = "Scalar length", type = "int", min = 1,
72 descr = "Scalar field-element length (bytes)", default = 32 },
73}
74
75local C = { } -- The working values of the configuration variables.
76
77local function set_config(k, v)
78 -- Set configuration variable K to the value V.
79 --
80 -- K is a string naming the variable to set. V is the new value, which may
81 -- be a string or a number.
82 --
83 -- For `int' variables, V is converted to a number if necessary, and then
84 -- checked against the permitted bounds.
85 --
86 -- For `enum' variables, things are more complicated. If V is a string,
87 -- it's checked against the permitted values. If V is a number, it's
88 -- converted back into the corresponding string.
89
90 local info = CONFIG[k]
91
92 if info == nil then error("unknown config key `" .. k .. "'") end
93
94 if info.type == "enum" then
95 if type(v) == "number" then
96 local t = info.allowed[v]
97 if t == nil then
98 error(string.format("bad index %d for `%s'", n, k))
99 end
100 v = t
101 else
102 if info.map[v] == nil then
103 error(string.format("bad value `%s' for `%s'", v, k))
104 end
105 end
106
107 elseif info.type == "int" then
108 local n = tonumber(v)
109 if n == nil then error("bad number `" .. v .. "'") end
110 if n ~= math.floor(n) then
111 error("value `" .. v .. "' is not an integer")
112 end
113 if (info.min ~= nil and n < info.min) or
114 (info.max ~= nil and n > info.max)
115 then
116 error(string.format("value %d out of range for `%s'", n, k))
117 end
118 v = n
119 end
120
121 C[k] = v
122end
123
124-- Set up the configuration information. Configure preferences objects on
125-- the dissector. For `enum' variables, build the `map' slots.
126for i, v in ipairs(CONFIG) do
127 local k = v.var
128 CONFIG[k] = v
129 if v.type == "enum" then
130 local tab = { }
131 v.map = { }
132 for i, t in pairs(v.allowed) do
133 v.map[t] = i
134 tab[i] = { i, t, i }
135 end
136 tripe.prefs[k] = Pref.enum(v.name, v.map[v.default], v.descr, tab)
137 elseif v.type == "int" then
138 tripe.prefs[k] = Pref.uint(v.name, v.default, v.descr)
139 end
140end
141
142local function prefs_changed()
143 -- Notice that the preferences have been changed and update `C'.
144
145 for k, _ in pairs(CONFIG) do
146 if type(k) == "string" then set_config(k, tripe.prefs[k]) end
147 end
148end
149tripe.prefs_changed = prefs_changed
150
151-- Populate the configuration table from the stored preferences or their
152-- default values.
153prefs_changed()
154
155-- Now work through arguments passed in on the command line. Annoyingly,
156-- while one can set preferences on the Wireshark command line, these are
157-- done /before/ Lua scripts are loaded, so the silly thing thinks the
158-- preference slots don't exist. So we have to do it a different way.
159for _, arg in ipairs({...}) do
160 local k, v = arg:match("(.+)=(.+)")
161 if k == nil or v == nil then error("bad option syntax `" .. arg .. "'") end
162 se_config(k, v)
163end
164
165-----------------------------------------------------------------------------
166--- Protocol dissection primitives.
167
168local PF = { } -- The table of protocol fields, filled in later.
169
170-- The `dissect_*' functions follow a common protocol. They parse a thing
171-- from a packet buffer BUF, of size SZ, starting from POS, and store
172-- interesting things in a given TREE; when they're done, they return the
173-- updated index where the next interesting thing might be. As a result,
174-- it's usually a simple matter to parse a packet by invoking the appropriate
175-- primitive dissectors in the right order.
176
177local function dissect_wtf(buf, tree, pos, sz)
178 -- If POS is not at the end of the buffer, note that there's unexpected
179 -- stuff in the packet.
180
181 if pos < sz then tree:add(PF["tripe.wtf"], buf(pos, sz - pos)) end
182 return sz
183end
184
185-- Dissect a ciphertext of some particular kind.
186local dissect_ct = { }
187function dissect_ct.naclbox(buf, tree, pos, sz)
188 tree:add(PF["tripe.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
189 tree:add(PF["tripe.ciphertext.seq"], buf(pos, 4)); pos = pos + 4
190 tree:add(PF["tripe.ciphertext.body"], buf(pos, sz - pos))
191end
192function dissect_ct.iiv(buf, tree, pos, sz)
193 tree:add(PF["tripe.ciphertext.tag"], buf(pos, C.tagsz)); pos = pos + C.tagsz
194 tree:add(PF["tripe.ciphertext.seq"], buf(pos, 4)); pos = pos + 4
195 tree:add(PF["tripe.ciphertext.body"], buf(pos, sz - pos))
196end
197function dissect_ct.v0(buf, tree, pos, sz)
198 tree:add(PF["tripe.ciphertext.tag"], buf(pos, C.tagsz)); pos = pos + C.tagsz
199 tree:add(PF["tripe.ciphertext.seq"], buf(pos, 4)); pos = pos + 4
200 tree:add(PF["tripe.ciphertext.iv"], buf(pos, C.ivsz)); pos = pos + C.ivsz
201 tree:add(PF["tripe.ciphertext.body"], buf(pos, sz - pos))
202end
203
204local function dissect_ciphertext(buf, tree, label, pos, sz)
205 -- Dissect a ciphertext, making the whole thing be a little subtree with
206 -- the given LABEL.
207
208 local t = tree:add(PF[label], buf(pos, sz - pos))
209 dissect_ct[C.bulk](buf, t, pos, sz)
210 return pos
211end
212
213local function dissect_packet(buf, tree, pos, sz)
214 return dissect_ciphertext(buf, tree, "tripe.packet.payload", pos, sz)
215end
216
217-- Dissect a group element of some particular kind.
218local dissect_ge = { }
219function dissect_ge.dh(buf, tree, pos, sz)
220 tree:add(PF["tripe.dh.len"], buf(pos, 2))
221 xsz = buf(pos, 2):uint(); pos = pos + 2
222 tree:add(PF["tripe.dh.x"], buf(pos, xsz)); pos = pos + xsz
223 return pos
224end
225function dissect_ge.ec(buf, tree, pos, sz)
226 tree:add(PF["tripe.ec.xlen"], buf(pos, 2))
227 xsz = buf(pos, 2):uint(); pos = pos + 2
228 tree:add(PF["tripe.ec.x"], buf(pos, xsz)); pos = pos + xsz
229 tree:add(PF["tripe.ec.ylen"], buf(pos, 2))
230 ysz = buf(pos, 2):uint(); pos = pos + 2
231 tree:add(PF["tripe.ec.y"], buf(pos, ysz)); pos = pos + ysz
232 return pos
233end
234function dissect_ge.x25519(buf, tree, pos, sz)
235 tree:add(PF["tripe.x25519.x"], buf(pos, 32))
236 return pos + 32
237end
238function dissect_ge.x448(buf, tree, pos, sz)
239 tree:add(PF["tripe.x448.x"], buf(pos, 56))
240 return pos + 56
241end
242
243local function dissect_my_challenge(buf, tree, pos, sz)
244 -- We don't know how long the group element is going to be. We can set the
245 -- length later, but (at least in older versions) it doesn't work so well
246 -- to increase the length, so make it large to start out, and shrink it
247 -- later.
248 local t = tree:add(PF["tripe.keyexch.mychal"], buf(pos, sz - pos))
249 local q = dissect_ge[C.kx](buf, t, pos, sz)
250 t:set_len(q - pos)
251 return q
252end
253
254local function dissect_my_cookie(buf, tree, pos, sz)
255 tree:add(PF["tripe.keyexch.mycookie"], buf(pos, C.hashsz))
256 return pos + C.hashsz
257end
258
259local function dissect_your_cookie(buf, tree, pos, sz)
260 tree:add(PF["tripe.keyexch.yourcookie"], buf(pos, C.hashsz))
261 return pos + C.hashsz
262end
263
264local kx_scsz = { x25519 = 32, x448 = 56 } -- Hardwired scalar sizes.
265local function dissect_check(buf, tree, pos, sz)
266 local scsz = kx_scsz[C.kx] or C.scsz
267 tree:add(PF["tripe.keyexch.check"], buf(pos, scsz))
268 return pos + scsz
269end
270
271local function dissect_reply(buf, tree, pos, sz)
272 return dissect_ciphertext(buf, tree, "tripe.keyexch.reply", pos, sz)
273end
274
275local function dissect_switch(buf, tree, pos, sz)
276 return dissect_ciphertext(buf, tree, "tripe.keyexch.switch", pos, sz)
277end
278
279local function dissect_switchok(buf, tree, pos, sz)
280 return dissect_ciphertext(buf, tree, "tripe.keyexch.switchok", pos, sz)
281end
282
283local function dissect_misc_payload(buf, tree, pos, sz)
284 tree:add(PF["tripe.misc.payload"], buf(pos, sz - pos))
285 return sz
286end
287
288local function dissect_misc_ciphertext(buf, tree, pos, sz)
289 return dissect_ciphertext(buf, tree, "tripe.misc.ciphertext", pos, sz)
290end
291
292-----------------------------------------------------------------------------
293--- The protocol information table.
294
295local PKTINFO = {
296 -- This is the main table which describes the protocol. The top level maps
297 -- category codes to structures:
298 --
299 -- * `label' is the category code's symbolic name;
300 --
301 -- * `subtype' is the field name for the subtype code;
302 --
303 -- * `info' is a prefix for the information column display; and
304 --
305 -- * `sub' is a table describing the individual subtypes.
306 --
307 -- The subtype table similarly maps subtype codes to structures:
308 --
309 -- * `label' is the subtype code's symbolic name;
310 --
311 -- * `info' is the suffix for the information column display; and
312 --
313 -- * `dissect' is a sequence of primitive dissectors to run in order to
314 -- parse the rest of the packet.
315
316 [0] = {
317 label = "MSG_PACKET", subtype = "tripe.packet.type",
318 info = "Packet data",
319 sub = {
320 [0] = { label = "PACKET_IP", info = "encapsulated IP datagram",
321 dissect = { dissect_packet} }
322 }
323 },
324
325 [1] = {
326 label = "MSG_KEYEXCH", subtype = "tripe.keyexch.type",
327 info = "Key exchange",
328 sub = {
329 [0] = { label = "KX_PRECHAL", info = "pre-challenge",
330 dissect = { dissect_my_challenge,
331 dissect_wtf } },
332 [1] = { label = "KX_CHAL", info = "challenge",
333 dissect = { dissect_my_challenge,
334 dissect_your_cookie,
335 dissect_check,
336 dissect_wtf } },
337 [2] = { label = "KX_REPLY", info = "reply",
338 dissect = { dissect_my_challenge,
339 dissect_your_cookie,
340 dissect_check,
341 dissect_reply } },
342 [3] = { label = "KX_SWITCH", info = "switch",
343 dissect = { dissect_my_cookie,
344 dissect_your_cookie,
345 dissect_switch } },
346 [4] = { label = "KX_SWITCHOK", info = "switch-ok",
347 dissect = { dissect_switchok } },
348 }
349 },
350
351 [2] = {
352 label = "MSG_MISC", subtype = "tripe.misc.type",
353 info = "Miscellaneous",
354 sub = {
355 [0] = { label = "MISC_NOP", info = "no-operation (keepalive)",
356 dissect = { dissect_misc_payload } },
357 [1] = { label = "MISC_PING", info = "transport-level ping",
358 dissect = { dissect_misc_payload } },
359 [2] = { label = "MISC_PONG", info = "transport-level ping reply",
360 dissect = { dissect_misc_payload } },
361 [3] = { label = "MISC_EPING", info = "crypto-level ping",
362 dissect = { dissect_misc_ciphertext } },
363 [4] = { label = "MISC_EPONG", info = "crypto-level ping reply",
364 dissect = { dissect_misc_ciphertext } },
365 [5] = { label = "MISC_GREET", info = "greeting",
366 dissect = { dissect_misc_payload } },
367 }
368 }
369}
370
371do
372 -- Work through the master table and build `cattab' and `subtab' tables,
373 -- mapping category and subtype codes to their symbolic names for
374 -- presentation. The `subtab' is a two-level table, needing two layers of
375 -- indexing.
376 local cattab = { }
377 local subtab = { }
378 for i, v in pairs(PKTINFO) do
379 cattab[i] = v.label
380 if v.sub ~= nil then
381 subtab[i] = { }
382 for j, w in pairs(v.sub) do
383 subtab[i][j] = w.label
384 end
385 end
386 end
387
388 local ftab = {
389 -- The protocol fields. This table maps the field names to structures
390 -- used to build the fields, which are then stored in `PF' (declared way
391 -- above):
392 --
393 -- * `name' is the field name to show in the dissector tree view;
394 --
395 -- * `type' is the field type;
396 --
397 -- * `base' is a tweak describing how the field should be formatted;
398 --
399 -- * `mask' is used to single out a piece of a larger bitfield; and
400 --
401 -- * `tab' names a mapping table used to convert numerical values to
402 -- symbolic names.
403
404 ["tripe.type"] = {
405 name = "Message type", type = ftypes.UINT8, base = base.HEX
406 },
407 ["tripe.cat"] = {
408 name = "Message category", type = ftypes.UINT8, base = base.DEC,
409 mask = 0xf0, tab = cattab
410 },
411 ["tripe.packet.type"] = {
412 name = "Packet subcode", type = ftypes.UINT8, base = base.DEC,
413 mask = 0x0f, tab = subtab[0]
414 },
415 ["tripe.packet.payload"] = {
416 name = "Encrypted packet", type = ftypes.NONE
417 },
418 ["tripe.keyexch.type"] = {
419 name = "Key-exchange subcode", type = ftypes.UINT8, base = base.DEC,
420 mask = 0x0f, tab = subtab[1]
421 },
422 ["tripe.keyexch.mychal"] = {
423 name = "Sender's challenge R = r P", type = ftypes.NONE
424 },
425 ["tripe.keyexch.mycookie"] = {
426 name = "Hash of recipient's challenge = H(R, ...)",
427 type = ftypes.BYTES, base = base.SPACE
428 },
429 ["tripe.keyexch.yourcookie"] = {
430 name = "Hash of sender's challenge = H(R', ...)",
431 type = ftypes.BYTES, base = base.SPACE
432 },
433 ["tripe.keyexch.reply"] = {
434 name = "Encrypted reply = k R'", type = ftypes.NONE
435 },
436 ["tripe.keyexch.switch"] = {
437 name = "Encrypted reply and switch request = k R', H(...)",
438 type = ftypes.NONE
439 },
440 ["tripe.keyexch.switchok"] = {
441 name = "Encrypted switch confirmation = H(...)", type = ftypes.NONE
442 },
443 ["tripe.misc.type"] = {
444 name = "Miscellenaous subcode", type = ftypes.UINT8, base = base.DEC,
445 mask = 0x0f, tab = subtab[2]
446 },
447 ["tripe.misc.payload"] = {
448 name = "Miscellaneous payload",
449 type = ftypes.BYTES, base = base.SPACE
450 },
451 ["tripe.misc.ciphertext"] = {
452 name = "Miscellaneous encrypted payload", type = ftypes.NONE
453 },
454 ["tripe.wtf"] = {
455 name = "Unexpected trailing data",
456 type = ftypes.BYTES, base = base.SPACE
457 },
458 ["tripe.keyexch.check"] = {
459 name = "Sender's challenge check value = r XOR H(r K', ...)",
460 type = ftypes.BYTES, base = base.SPACE
461 },
462 ["tripe.ciphertext.seq"] = {
463 name = "Sequence number", type = ftypes.UINT32, base = base.DEC
464 },
465 ["tripe.ciphertext.iv"] = {
466 name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
467 },
468 ["tripe.ciphertext.tag"] = {
469 name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
470 },
471 ["tripe.ciphertext.body"] = {
472 name = "Encrypted data", type = ftypes.BYTES, base = base.SPACE
473 },
474 ["tripe.dh.len"] = {
475 name = "DH group element length",
476 type = ftypes.UINT16, base = base.DEC
477 },
478 ["tripe.dh.x"] = {
479 name = "DH group element value",
480 type = ftypes.BYTES, base = base.SPACE
481 },
482 ["tripe.ec.xlen"] = {
483 name = "Elliptic curve x-coordinate length",
484 type = ftypes.UINT16, base = base.DEC
485 },
486 ["tripe.ec.x"] = {
487 name = "Elliptic curve x-coordinate value",
488 type = ftypes.BYTES, base = base.SPACE
489 },
490 ["tripe.ec.ylen"] = {
491 name = "Elliptic curve y-coordinate length",
492 type = ftypes.UINT16, base = base.DEC
493 },
494 ["tripe.ec.y"] = {
495 name = "Elliptic curve y-coordinate value",
496 type = ftypes.BYTES, base = base.SPACE
497 },
498 ["tripe.x25519.x"] = {
499 name = "X25519 x-coordinate",
500 type = ftypes.BYTES, base = base.SPACE
501 },
502 ["tripe.x448.x"] = {
503 name = "X448 x-coordinate",
504 type = ftypes.BYTES, base = base.SPACE
505 },
506 }
507
508 -- Convert this table into the protocol fields, and populate `PF'.
509 local ff = { }
510 local i = 1
511
512 -- Figure out whether we can use `none' fields (see below).
513 -- probe for this easily
514 local use_none_p = rawget(ProtoField, 'none') ~= nil
515 for abbr, args in pairs(ftab) do
516
517 -- An annoying hack. Older versions of Wireshark don't allow setting
518 -- fields with type `none', which is a shame because they're ideal as
519 -- internal tree nodes.
520 ty = args.type
521 b = args.base
522 if ty == ftypes.NONE and not use_none_p then
523 ty = ftypes.BYTES
524 b = base.SPACE
525 end
526
527 -- Go make the field.
528 local f = ProtoField.new(args.name, abbr, ty,
529 args.tab, b, args.mask, args.descr)
530 PF[abbr] = f
531 ff[i] = f; i = i + 1
532 end
533 tripe.fields = PF
534end
535
536-----------------------------------------------------------------------------
537--- The main dissector.
538
539function tripe.dissector(buf, pinfo, tree)
540
541 -- Fill in the obvious stuff.
542 pinfo.cols.protocol = "TrIPE"
543
544 local sz = buf:reported_length_remaining()
545 local sub = tree:add(tripe, buf(0, sz), "TrIPE packet")
546 local p = 1
547
548 -- Decode the packet type octet.
549 local tycode = buf(0, 1):uint()
550 local ty = sub:add(PF["tripe.type"], buf(0, 1))
551 ty:add(PF["tripe.cat"], buf(0, 1))
552 local cat = bit.rshift(bit.band(tycode, 0xf0), 4)
553 local subty = bit.band(tycode, 0x0f)
554 local info = PKTINFO[cat]
555
556 -- Dispatch using the master protocol table.
557 if info == nil then
558 pinfo.cols.info = string.format("Unknown category code %u, " ..
559 "unknown type code %u",
560 cat, subty)
561 else
562 ty:add(PF[info.subtype], buf(0, 1))
563 local subinfo = info.sub[subty]
564 if subinfo == nil then
565 pinfo.cols.info = string.format("%s, unknown type code %u",
566 info.info, subty)
567 else
568 pinfo.cols.info = string.format("%s, %s", info.info, subinfo.info)
569 p = 1
570 for _, d in ipairs(subinfo.dissect) do p = d(buf, sub, p, sz) end
571 end
572 end
573
574 -- Return the final position we reached.
575 return p
576end
577
578-- We're done. Register the dissector.
579DissectorTable.get("udp.port"):add(4070, tripe)
580
581-------- That's all, folks --------------------------------------------------