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