| 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 -------------------------------------------------- |