ADDED rd-key.lua Index: rd-key.lua ================================================================== --- rd-key.lua +++ rd-key.lua @@ -0,0 +1,235 @@ +-- [ʞ] rd-key.lua +-- ~ lexi hale +-- © GNU AGPLv3 +-- ? utility for generating and applying 64bit key +-- schedules to RD-5R "codeplug" provisioning profiles +-- > lua rd-key.lua gen sched.kch +-- > lua rd-key.lua apply sched.kch device.img +-- > dmrconfig -w device.img + +function B(b, ...) + if not b then return "" end + return string.pack("I1", b) .. B(...) +end + +local const = { + kch_header = B(0x9a, 0x1e, 0x2f) .. "kCH"; + maxkeys = 16; + keylen = 64/8; + + plug = { + kch_start = 0x1370; + kch_end = 0x13f8; + bank_chns = 128; + chn_sz = 56; + bank_sz = 16 + (56 * 128); + chn_regions = { + {top = 128, start = 0x3780}; + {top = 1023, start = 0xb1b0}; + }; + + mode = { + analog = 0; + digital = 1; + }; + }; +} + +math.randomseed(os.time()) +function log(...) print(string.format(...)) end +function keymask(num) + local mask = 0 + for i = 0, num-1 do + mask = mask | 1 << i + end + + return string.pack(" r.top then + region = region + 1 + r = const.plug.chn_regions[region] + end + if r == nil then return nil end + local bottom = region > 1 and const.plug.chn_regions[region-1].top or 1 + + local bank = (i - bottom) // const.plug.bank_chns + local bo = r.start + bank * const.plug.bank_sz + local bankpos = (i - bottom) % const.plug.bank_chns + + i = i + 1 + + -- local bitmap = mem:sub(bo, bo + 16) + -- local bitmap_b = string.byte(bitmap, 1 + bankpos // 8) + -- if bitmap_b & (1 << (bankpos % 8)) == 0 then --- doesn't work + -- goto tryagain + -- end + + local chans = bo + 16 + local ent = chans + bankpos * const.plug.chn_sz + + local rec = mem:sub(1+ent,1+ent + const.plug.chn_sz) + + + return rec, i-1, 1+ent + end +end + +function strchg(str, ofs, sz, new) + local head = string.sub(str, 1, ofs-1) + local tail = string.sub(str, ofs+sz) + local n = head .. new .. tail + if #n ~= #str then + print("old str") dumpline(string.sub(str, ofs, ofs + (sz-1))) + print("new str should be", sz, #new) dumpline(new) + error("bad str chg") + end + return n +end + +function dumpline(str) + local function dump(line) + local top="\t" + local bottom="\t" + + for i=1,#line do + local val = string.byte(line, i) + if val >= 0x20 and val <= 0x7e then + top = top .. string.format(" %c ", val) + else + top = top .. " : " + end + bottom = bottom .. string.format("%02x ", val) + end + print(top) + print(bottom) + end + local cpl = 32 + for i = 0, #str // cpl do + local st = 1 + i*cpl + dump(str:sub(st, st+cpl)) + end +end +function applykeychain(chain, file) + local h = io.open(file, 'rb') + if not h then error("could not open file") end + local pl = h:read('a') h:close() + + local keylist = B(1,0) -- basic mode + .. keymask(const.maxkeys) -- number of keys + .. B(0,0, 0,0) -- padding?? + .. table.concat(chain) + pl = strchg(pl, const.plug.kch_start+1, #keylist, keylist) + + for ch, idx, ofs in chans(pl) do + if string.byte(ch, 1) ~= 0xFF then -- is channel defined? + local name = strdec(ch:sub(1,16)) + local mode = string.byte(ch, 25) + local function say(fmt, ...) + log("\x1b[1mchannel [%04u] “%s”\x1b[m: " .. fmt, idx, name, ...) + end + local newch + if mode == const.plug.mode.digital then + local privg = string.byte(ch, 42) + local chkey = idx % const.maxkeys + if privg == 0 then + say("marked clear, comms on this channel will NOT be encrypted") + elseif privg ~= chkey then + say("changing key from #%u to #%u", privg, chkey) + newch = strchg(ch, 42, 1, B(chkey)) + dirty = true + -- else + -- say("already keyed correctly") + end + else + say("analog channel, cannot be encrypted") + end + + if newch then + pl=strchg(pl, ofs, 1+const.plug.chn_sz, newch) + end + end + end + + h = io.open(file, 'w+b') + h:write(pl) + h:close() +end + +function loadkeychain(file) + local f = io.open(file, "r") + if f:read(#const.kch_header) ~= const.kch_header then + error "not a valid keychain file" + end + local ch = {} + for i=1, const.maxkeys do + ch[i] = f:read(const.keylen) + end + local comment = f:read('a') + f:close() + log("using keychain for %s", comment) + return ch +end + +function savekeychain(file, kch) + local f = io.open(file, "w+b") + f:write(const.kch_header) + for i=1, const.maxkeys do + f:write(kch[i]) + end + f:write(os.date()) + f:close() +end + +function genkeychain() + local ch = {} + local rng = io.open("/dev/urandom") or io.open("/dev/random") + function rb(n) + if rng then return rng:read(n) else + log('warning, cannot read /dev/[u]random, random bytes may be low-quality') + local str = "" + for i = 1,n do str = str .. B(math.random(0,0xff)) end + return str + end + end + for i=1,const.maxkeys do + ch[i] = rb(64/8) + end + if rng then rng:close() end + return ch +end + +local cmd, keychain, target = ... +if not cmd then + print("usage: lua rd-key.lua (gen|apply|extract) keychain [codeplug]") +else + if keychain == nil or file == "" then keychain = "chain.kch" end + if target == nil or target == "" then target = "device.img" end + if cmd == "gen" then + log("writing new keychain to %s", keychain) + local ch = genkeychain() + savekeychain(keychain, ch) + elseif cmd == "apply" then + log("applying keychain %s to codeplug %s", keychain, target) + local ch = loadkeychain(keychain) + applykeychain(ch, target) + elseif cmd == "extract" then + log("extracting keychain %s from codeplug %s", keychain, target) + log(" -- unimplemented --") + end +end Index: tenki/make.sh ================================================================== --- tenki/make.sh +++ tenki/make.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash if test -e apikey; then cc -Ofast -Dds_apikey="\"$(cat apikey)\"" tenki.c -lssl -otenki else echo "tenki requires an API key. get one from dark sky and put it in a file in this directory named 'apikey' before you try to build" fi