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