Turns out there's a bit of code that isn't compatible w/ the Python version, eliminate it... This was to support encoding lengths (via negative length parameters)... Also, the default C version (which we want to use) is Keccak(800) and not Keccak(1600), switch Python to 800, as it'll be faster on the 32-bit uC, and still has plenty of security margin...irr_shared
| @@ -6,6 +6,14 @@ ARMTARGET?= -mcpu=cortex-m3 -mthumb -DSTROBE_SINGLE_THREAD=1 | |||
| #ARMCC?=clang-mp-9.0 | |||
| #ARMTARGET?= -nostdlib -ffreestanding -target arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -mthumb | |||
| PLATFORM != uname -s | |||
| .if $(PLATFORM) == "Darwin" | |||
| SOEXT=dylib | |||
| .else | |||
| .error Unsupported platform: $(PLATFORM) | |||
| .endif | |||
| PROG = lora.irr | |||
| PROGEXT = .elf | |||
| @@ -86,12 +94,28 @@ CFLAGS+= -I$(STM32)/usb | |||
| OBJS = $(SRCS:C/.c$/.o/) | |||
| CFLAGS+= -Werror -Wall | |||
| LIBLORA_TEST_SRCS= comms.c strobe.c x25519.c | |||
| LIBLORA_TEST_OBJS= $(LIBLORA_TEST_SRCS:C/.c$/.no/) | |||
| LIBLORA_TEST = liblora_test.$(SOEXT) | |||
| $(LIBLORA_TEST): $(LIBLORA_TEST_OBJS) | |||
| $(CC) -shared -o $@ $(.ALLSRC) | |||
| .MAIN: all | |||
| .PHONY: all | |||
| all: $(PROG)$(PROGEXT) $(PROG).list | |||
| .PHONY: depend | |||
| depend: $(SRCS) | |||
| $(ARMCC) $(ARMTARGET) $(CFLAGS) $(.ALLSRC) -MM > .depend || rm -f .depend | |||
| depend: .arm_deps .test_deps | |||
| .sinclude ".arm_deps" | |||
| .sinclude ".test_deps" | |||
| .arm_deps: $(SRCS) | |||
| $(ARMCC) $(ARMTARGET) $(CFLAGS) $(.ALLSRC) -MM > $@ || rm -f $@ | |||
| .test_deps: $(LIBLORA_TEST_SRCS) | |||
| $(CC) $(CFLAGS) $(.ALLSRC) -MM | sed -e 's/\.o:/\.no:/' > $@ || rm -f $@ | |||
| $(PROG)$(PROGEXT): $(OBJS) | |||
| $(ARMCC) $(ARMTARGET) -o $@ $(.ALLSRC) -T$(LINKER_SCRIPT) --specs=nosys.specs -Wl,--gc-sections -static --specs=nano.specs -Wl,--start-group -lc -lm -Wl,--end-group | |||
| @@ -104,8 +128,13 @@ runbuild: $(SRCS) | |||
| for i in $(.MAKEFILE_LIST) $(.ALLSRC) $$(gsed ':x; /\\$$/ { N; s/\\\n//; tx }' < .depend | sed -e 's/^[^:]*://'); do if [ "$$i" != ".." ]; then echo $$i; fi; done | entr -d sh -c 'echo starting...; cd $(.CURDIR) && $(MAKE) $(.MAKEFLAGS) depend && $(MAKE) $(.MAKEFLAGS) all' | |||
| .PHONY: runtests | |||
| runtests: lora.py | |||
| ls $(.ALLSRC) | entr sh -c 'PYTHONPATH="$(.CURDIR)" python -m coverage run -m unittest lora && coverage report --omit=p/\* -m -i' | |||
| runtests: Makefile lora_comms.py lora.py $(LIBLORA_TEST) $(LIBLORA_TEST_SRCS) | |||
| ls $(.ALLSRC) | entr sh -c '(cd $(.CURDIR) && $(MAKE) $(.MAKEFLAGS) $(LIBLORA_TEST)) && ((PYTHONPATH="$(.CURDIR)" python -m coverage run -m unittest lora && coverage report --omit=p/\* -m -i) 2>&1 | head -n 20)' | |||
| # native objects | |||
| .SUFFIXES: .no | |||
| .c.no: | |||
| $(CC) $(CFLAGS) -c $< -o $@ | |||
| .c.o: | |||
| $(ARMCC) $(ARMTARGET) $(CFLAGS) -c $< -o $@ | |||
| @@ -0,0 +1,110 @@ | |||
| #include <comms.h> | |||
| #include <strobe_rng_init.h> | |||
| static const size_t MAC_LEN = 8; | |||
| static const size_t CHALLENGE_LEN = 16; | |||
| static const uint8_t domain[] = "com.funkthat.lora.irrigation.shared.v0.0.1"; | |||
| size_t | |||
| _strobe_state_size() | |||
| { | |||
| return sizeof(strobe_s); | |||
| } | |||
| void | |||
| comms_init(struct comms_state *cs, process_msgfunc_t pmf) | |||
| { | |||
| *cs = (struct comms_state){ | |||
| .cs_comm_state = COMMS_WAIT_REQUEST, | |||
| .cs_procmsg = pmf, | |||
| }; | |||
| strobe_init(&cs->cs_start, domain, sizeof domain - 1); | |||
| /* copy starting state over to initial state */ | |||
| cs->cs_state = cs->cs_start; | |||
| } | |||
| #define CONFIRMED_STR_BASE "confirmed" | |||
| #define CONFIRMED_STR ((const uint8_t *)CONFIRMED_STR_BASE) | |||
| #define CONFIRMED_STR_LEN (sizeof(CONFIRMED_STR_BASE) - 1) | |||
| /* | |||
| * encrypted data to be processed is passed in via pbin. | |||
| * | |||
| * The pktbuf pointed to by pbout contains the buffer that a [encrypted] | |||
| * response will be written to. The length needs to be updated, where 0 | |||
| * means no reply. | |||
| */ | |||
| void | |||
| comms_process(struct comms_state *cs, struct pktbuf pbin, struct pktbuf *pbout) | |||
| { | |||
| uint8_t buf[64] = {}; | |||
| struct pktbuf pbmsg, pbrep; | |||
| ssize_t cnt, ret, msglen; | |||
| strobe_attach_buffer(&cs->cs_state, pbin.pkt, pbin.pktlen); | |||
| cnt = strobe_get(&cs->cs_state, APP_CIPHERTEXT, buf, pbin.pktlen - | |||
| MAC_LEN); | |||
| msglen = cnt; | |||
| cnt = strobe_get(&cs->cs_state, MAC, pbin.pkt + | |||
| (pbin.pktlen - MAC_LEN), MAC_LEN); | |||
| /* XXX - cnt != MAC_LEN test case */ | |||
| /* | |||
| * if we have arrived here, MAC has been verified, and buf now | |||
| * contains the data to operate upon. | |||
| */ | |||
| /* attach the buffer for output */ | |||
| strobe_attach_buffer(&cs->cs_state, pbout->pkt, pbout->pktlen); | |||
| ret = 0; | |||
| switch (cs->cs_comm_state) { | |||
| case COMMS_WAIT_REQUEST: | |||
| /* XXX - reqreset check */ | |||
| bare_strobe_randomize(buf, CHALLENGE_LEN); | |||
| ret = strobe_put(&cs->cs_state, APP_CIPHERTEXT, buf, | |||
| CHALLENGE_LEN); | |||
| ret += strobe_put(&cs->cs_state, MAC, NULL, MAC_LEN); | |||
| cs->cs_comm_state = COMMS_WAIT_CONFIRM; | |||
| break; | |||
| case COMMS_WAIT_CONFIRM: | |||
| /* XXX - confirm check */ | |||
| ret = strobe_put(&cs->cs_state, APP_CIPHERTEXT, CONFIRMED_STR, | |||
| CONFIRMED_STR_LEN); | |||
| ret += strobe_put(&cs->cs_state, MAC, NULL, MAC_LEN); | |||
| cs->cs_comm_state = COMMS_PROCESS_MSGS; | |||
| break; | |||
| case COMMS_PROCESS_MSGS: { | |||
| uint8_t repbuf[pbout->pktlen - MAC_LEN]; | |||
| memset(repbuf, '\x00', sizeof repbuf); | |||
| pbmsg.pkt = buf; | |||
| pbmsg.pktlen = msglen; | |||
| pbrep.pkt = repbuf; | |||
| pbrep.pktlen = sizeof repbuf; | |||
| cs->cs_procmsg(pbmsg, &pbrep); | |||
| ret = strobe_put(&cs->cs_state, APP_CIPHERTEXT, repbuf, | |||
| pbrep.pktlen); | |||
| ret += strobe_put(&cs->cs_state, MAC, NULL, MAC_LEN); | |||
| break; | |||
| } | |||
| } | |||
| pbout->pktlen = ret; | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| #include <sys/types.h> | |||
| #include <stdint.h> | |||
| #include <strobe.h> | |||
| struct pktbuf { | |||
| uint8_t *pkt; | |||
| uint16_t pktlen; | |||
| }; | |||
| /* first arg is input buffer, second arg is what will be sent as reply */ | |||
| typedef void (*process_msgfunc_t)(struct pktbuf, struct pktbuf *); | |||
| enum comm_state { | |||
| COMMS_WAIT_REQUEST = 1, | |||
| COMMS_WAIT_CONFIRM, | |||
| COMMS_PROCESS_MSGS, | |||
| }; | |||
| struct comms_state { | |||
| strobe_s cs_state; | |||
| enum comm_state cs_comm_state; | |||
| strobe_s cs_start; /* special starting state cache */ | |||
| process_msgfunc_t cs_procmsg; | |||
| }; | |||
| size_t _strobe_state_size(); | |||
| void comms_init(struct comms_state *, process_msgfunc_t); | |||
| void comms_process(struct comms_state *, struct pktbuf, struct pktbuf *); | |||
| @@ -3,9 +3,12 @@ import functools | |||
| import os | |||
| import unittest | |||
| from Strobe.Strobe import Strobe | |||
| from Strobe.Strobe import Strobe, KeccakF | |||
| from Strobe.Strobe import AuthenticationFailed | |||
| import lora_comms | |||
| from lora_comms import make_pktbuf | |||
| domain = b'com.funkthat.lora.irrigation.shared.v0.0.1' | |||
| # Response to command will be the CMD and any arguments if needed. | |||
| @@ -21,7 +24,7 @@ class LORANode(object): | |||
| def __init__(self, syncdatagram): | |||
| self.sd = syncdatagram | |||
| self.st = Strobe(domain) | |||
| self.st = Strobe(domain, F=KeccakF(800)) | |||
| async def start(self): | |||
| msg = self.st.send_enc(os.urandom(16) + b'reqreset') + \ | |||
| @@ -174,7 +177,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
| async def test_lora(self): | |||
| class TestSD(MockSyncDatagram): | |||
| async def runner(self): | |||
| l = Strobe(domain) | |||
| l = Strobe(domain, F=KeccakF(800)) | |||
| # start handshake | |||
| r = await self.get() | |||
| @@ -243,4 +246,86 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
| self.assertTrue(tsd.sendq.empty()) | |||
| self.assertTrue(tsd.recvq.empty()) | |||
| print('done') | |||
| @timeout(2) | |||
| async def test_ccode(self): | |||
| _self = self | |||
| from ctypes import pointer, sizeof, c_uint8 | |||
| # seed the RNG | |||
| prngseed = b'abc123' | |||
| lora_comms.strobe_seed_prng((c_uint8 * | |||
| len(prngseed))(*prngseed), len(prngseed)) | |||
| # Create the state for testing | |||
| commstate = lora_comms.CommsState() | |||
| # These are the expected messages and their arguments | |||
| exptmsgs = [ | |||
| (CMD_WAITFOR, [ 30 ]), | |||
| (CMD_RUNFOR, [ 1, 50 ]), | |||
| (CMD_TERMINATE, [ ]), | |||
| ] | |||
| def procmsg(msg, outbuf): | |||
| msgbuf = msg._from() | |||
| #print('procmsg:', repr(msg), repr(msgbuf), repr(outbuf)) | |||
| cmd = msgbuf[0] | |||
| args = [ int.from_bytes(msgbuf[x:x + 4], | |||
| byteorder='little') for x in range(1, len(msgbuf), | |||
| 4) ] | |||
| if exptmsgs[0] == (cmd, args): | |||
| exptmsgs.pop(0) | |||
| outbuf[0].pkt[0] = cmd | |||
| outbuf[0].pktlen = 1 | |||
| else: #pragma: no cover | |||
| raise RuntimeError('cmd not found') | |||
| # wrap the callback function | |||
| cb = lora_comms.process_msgfunc_t(procmsg) | |||
| class CCodeSD(MockSyncDatagram): | |||
| async def runner(self): | |||
| for expectlen in [ 24, 17, 9, 9, 9 ]: | |||
| # get message | |||
| gb = await self.get() | |||
| r = make_pktbuf(gb) | |||
| outbytes = bytearray(64) | |||
| outbuf = make_pktbuf(outbytes) | |||
| # process the test message | |||
| lora_comms.comms_process(commstate, r, | |||
| outbuf) | |||
| # make sure the reply matches length | |||
| _self.assertEqual(expectlen, | |||
| outbuf.pktlen) | |||
| # pass the reply back | |||
| await self.put(outbytes[:outbuf.pktlen]) | |||
| # Initialize everything | |||
| lora_comms.comms_init(commstate, cb) | |||
| # Create test fixture | |||
| tsd = CCodeSD() | |||
| l = LORANode(tsd) | |||
| # Send various messages | |||
| await l.start() | |||
| await l.waitfor(30) | |||
| await l.runfor(1, 50) | |||
| await l.terminate() | |||
| await tsd.drain() | |||
| # Make sure all messages have been processed | |||
| self.assertTrue(tsd.sendq.empty()) | |||
| self.assertTrue(tsd.recvq.empty()) | |||
| # Make sure all the expected messages have been | |||
| # processed. | |||
| self.assertFalse(exptmsgs) | |||
| @@ -0,0 +1,59 @@ | |||
| from ctypes import Structure, POINTER, CFUNCTYPE, pointer | |||
| from ctypes import c_uint8, c_uint16, c_ssize_t, c_size_t, c_uint64 | |||
| from ctypes import CDLL | |||
| class PktBuf(Structure): | |||
| _fields_ = [ | |||
| ('pkt', POINTER(c_uint8)), | |||
| ('pktlen', c_uint16), | |||
| ] | |||
| def _from(self): | |||
| return bytes(self.pkt[:self.pktlen]) | |||
| def __repr__(self): | |||
| return 'PktBuf(pkt=%s, pktlen=%s)' % (repr(self._from()), | |||
| self.pktlen) | |||
| def make_pktbuf(s): | |||
| pb = PktBuf() | |||
| if isinstance(s, bytearray): | |||
| obj = s | |||
| pb.pkt = pointer(c_uint8.from_buffer(s)) | |||
| #print('mp:', repr(pb.pkt)) | |||
| else: | |||
| obj = (c_uint8 * len(s))(*s) | |||
| pb.pkt = obj | |||
| pb.pktlen = len(s) | |||
| pb._make_pktbuf_ref = (obj, s) | |||
| return pb | |||
| process_msgfunc_t = CFUNCTYPE(None, PktBuf, POINTER(PktBuf)) | |||
| _lib = CDLL('liblora_test.dylib') | |||
| _lib._strobe_state_size.restype = c_size_t | |||
| _lib._strobe_state_size.argtypes = () | |||
| _strobe_state_u64_cnt = (_lib._strobe_state_size() + 7) // 8 | |||
| class CommsState(Structure): | |||
| _fields_ = [ | |||
| # The alignment of these may be off | |||
| ('cs_state', c_uint64 * _strobe_state_u64_cnt), | |||
| ('cs_start', c_uint64 * _strobe_state_u64_cnt), | |||
| ('cs_procmsg', process_msgfunc_t), | |||
| ] | |||
| for func, ret, args in [ | |||
| ('comms_init', None, (POINTER(CommsState), process_msgfunc_t)), | |||
| ('comms_process', None, (POINTER(CommsState), PktBuf, POINTER(PktBuf))), | |||
| ('strobe_seed_prng', None, (POINTER(c_uint8), c_ssize_t)), | |||
| ]: | |||
| f = getattr(_lib, func) | |||
| f.restype = ret | |||
| f.argtypes = args | |||
| locals()[func] = f | |||
| @@ -224,25 +224,6 @@ static ssize_t strobe_operate_0 ( | |||
| return -1; | |||
| } | |||
| /* Read/write the control word */ | |||
| strobe_serialized_control_t str = { | |||
| GET_CONTROL_TAG(flags), | |||
| receiving_the_length ? 0 : eswap_htole_sl(len) | |||
| }; | |||
| if (!more) { | |||
| TRY(strobe_duplex(strobe, cwf, (uint8_t *)&str, sizeof(str.control) + length_bytes)); | |||
| } | |||
| str.len = eswap_letoh_sl(str.len); | |||
| // Check received control word and length | |||
| if ( str.control != GET_CONTROL_TAG(flags) | |||
| || str.len > INT_MAX | |||
| || ((ssize_t)(len + str.len) > 0 && (ssize_t)str.len != len) | |||
| ) { | |||
| return -1; | |||
| } | |||
| len = str.len; | |||
| if (flags & FLAG_NO_DATA) return 0; | |||
| return strobe_duplex(strobe, flags, inside, len); | |||