chiark / gitweb /
Upgrade licence to GPLv3+.
[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 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
25 local tripe = Proto("tripe", "TrIPE VPN")
26
27 -----------------------------------------------------------------------------
28 --- Configuration handling.
29
30 local 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
75 local C = { } -- The working values of the configuration variables.
76
77 local 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
122 end
123
124 -- Set up the configuration information.  Configure preferences objects on
125 -- the dissector.  For `enum' variables, build the `map' slots.
126 for 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
140 end
141
142 local 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
148 end
149 tripe.prefs_changed = prefs_changed
150
151 -- Populate the configuration table from the stored preferences or their
152 -- default values.
153 prefs_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.
159 for _, 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)
163 end
164
165 -----------------------------------------------------------------------------
166 --- Protocol dissection primitives.
167
168 local 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
177 local 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
183 end
184
185 -- Dissect a ciphertext of some particular kind.
186 local dissect_ct = { }
187 function 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))
191 end
192 function 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))
196 end
197 function 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))
202 end
203
204 local 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
211 end
212
213 local function dissect_packet(buf, tree, pos, sz)
214   return dissect_ciphertext(buf, tree, "tripe.packet.payload", pos, sz)
215 end
216
217 -- Dissect a group element of some particular kind.
218 local dissect_ge = { }
219 function 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
224 end
225 function 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
233 end
234 function dissect_ge.x25519(buf, tree, pos, sz)
235   tree:add(PF["tripe.x25519.x"], buf(pos, 32))
236   return pos + 32
237 end
238 function dissect_ge.x448(buf, tree, pos, sz)
239   tree:add(PF["tripe.x448.x"], buf(pos, 56))
240   return pos + 56
241 end
242
243 local 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
252 end
253
254 local function dissect_my_cookie(buf, tree, pos, sz)
255   tree:add(PF["tripe.keyexch.mycookie"], buf(pos, C.hashsz))
256   return pos + C.hashsz
257 end
258
259 local function dissect_your_cookie(buf, tree, pos, sz)
260   tree:add(PF["tripe.keyexch.yourcookie"], buf(pos, C.hashsz))
261   return pos + C.hashsz
262 end
263
264 local kx_scsz = { x25519 = 32, x448 = 56 } -- Hardwired scalar sizes.
265 local 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
269 end
270
271 local function dissect_reply(buf, tree, pos, sz)
272   return dissect_ciphertext(buf, tree, "tripe.keyexch.reply", pos, sz)
273 end
274
275 local function dissect_switch(buf, tree, pos, sz)
276   return dissect_ciphertext(buf, tree, "tripe.keyexch.switch", pos, sz)
277 end
278
279 local function dissect_switchok(buf, tree, pos, sz)
280   return dissect_ciphertext(buf, tree, "tripe.keyexch.switchok", pos, sz)
281 end
282
283 local function dissect_misc_payload(buf, tree, pos, sz)
284   tree:add(PF["tripe.misc.payload"], buf(pos, sz - pos))
285   return sz
286 end
287
288 local function dissect_misc_ciphertext(buf, tree, pos, sz)
289   return dissect_ciphertext(buf, tree, "tripe.misc.ciphertext", pos, sz)
290 end
291
292 -----------------------------------------------------------------------------
293 --- The protocol information table.
294
295 local 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
371 do
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
534 end
535
536 -----------------------------------------------------------------------------
537 --- The main dissector.
538
539 function 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
576 end
577
578 -- We're done.  Register the dissector.
579 DissectorTable.get("udp.port"):add(4070, tripe)
580
581 -------- That's all, folks --------------------------------------------------