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 | #ARMCC?=clang-mp-9.0 | ||||
#ARMTARGET?= -nostdlib -ffreestanding -target arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -mthumb | #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 | PROG = lora.irr | ||||
PROGEXT = .elf | PROGEXT = .elf | ||||
@@ -86,12 +94,28 @@ CFLAGS+= -I$(STM32)/usb | |||||
OBJS = $(SRCS:C/.c$/.o/) | OBJS = $(SRCS:C/.c$/.o/) | ||||
CFLAGS+= -Werror -Wall | 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 | .PHONY: all | ||||
all: $(PROG)$(PROGEXT) $(PROG).list | all: $(PROG)$(PROGEXT) $(PROG).list | ||||
.PHONY: depend | .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) | $(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 | $(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' | 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 | .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: | .c.o: | ||||
$(ARMCC) $(ARMTARGET) $(CFLAGS) -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 os | ||||
import unittest | import unittest | ||||
from Strobe.Strobe import Strobe | |||||
from Strobe.Strobe import Strobe, KeccakF | |||||
from Strobe.Strobe import AuthenticationFailed | from Strobe.Strobe import AuthenticationFailed | ||||
import lora_comms | |||||
from lora_comms import make_pktbuf | |||||
domain = b'com.funkthat.lora.irrigation.shared.v0.0.1' | domain = b'com.funkthat.lora.irrigation.shared.v0.0.1' | ||||
# Response to command will be the CMD and any arguments if needed. | # Response to command will be the CMD and any arguments if needed. | ||||
@@ -21,7 +24,7 @@ class LORANode(object): | |||||
def __init__(self, syncdatagram): | def __init__(self, syncdatagram): | ||||
self.sd = syncdatagram | self.sd = syncdatagram | ||||
self.st = Strobe(domain) | |||||
self.st = Strobe(domain, F=KeccakF(800)) | |||||
async def start(self): | async def start(self): | ||||
msg = self.st.send_enc(os.urandom(16) + b'reqreset') + \ | msg = self.st.send_enc(os.urandom(16) + b'reqreset') + \ | ||||
@@ -174,7 +177,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||||
async def test_lora(self): | async def test_lora(self): | ||||
class TestSD(MockSyncDatagram): | class TestSD(MockSyncDatagram): | ||||
async def runner(self): | async def runner(self): | ||||
l = Strobe(domain) | |||||
l = Strobe(domain, F=KeccakF(800)) | |||||
# start handshake | # start handshake | ||||
r = await self.get() | r = await self.get() | ||||
@@ -243,4 +246,86 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||||
self.assertTrue(tsd.sendq.empty()) | self.assertTrue(tsd.sendq.empty()) | ||||
self.assertTrue(tsd.recvq.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; | 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; | if (flags & FLAG_NO_DATA) return 0; | ||||
return strobe_duplex(strobe, flags, inside, len); | return strobe_duplex(strobe, flags, inside, len); | ||||