@@ -29,7 +29,8 @@ | |||
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"; | |||
static const uint8_t shared_domain[] = "com.funkthat.lora.irrigation.shared.v0.0.1"; | |||
static const uint8_t ecdhe_domain[] = "com.funkthat.lora.irrigation.ecdhe.v0.0.1"; | |||
static const uint8_t reqreset[] = "reqreset"; | |||
static const uint8_t confirm[] = "confirm"; | |||
@@ -60,25 +61,55 @@ _strobe_state_size() | |||
return sizeof(strobe_s); | |||
} | |||
void | |||
comms_init(struct comms_state *cs, process_msgfunc_t pmf, struct pktbuf *shared) | |||
int | |||
comms_init(struct comms_state *cs, process_msgfunc_t pmf, | |||
struct pktbuf *shared, struct pktbuf *resp_key, struct pktbuf *init_pubkey) | |||
{ | |||
unsigned char buf[EC_PUBLIC_BYTES * 2]; | |||
strobe_s *crypt; | |||
*cs = (struct comms_state){ | |||
.cs_procmsg = pmf, | |||
}; | |||
strobe_init(&cs->cs_start, domain, sizeof domain - 1); | |||
crypt = &cs->cs_start.cs_crypto; | |||
if (shared != NULL) { | |||
strobe_init(crypt, shared_domain, sizeof shared_domain - 1); | |||
strobe_key(crypt, SYM_KEY, shared->pkt, shared->pktlen); | |||
cs->cs_start.cs_state = COMMS_WAIT_REQUEST_SHARED; | |||
} else if (resp_key != NULL && init_pubkey != NULL) { | |||
strobe_init(crypt, ecdhe_domain, sizeof ecdhe_domain - 1); | |||
if (resp_key->pktlen != EC_PRIVATE_BYTES || | |||
init_pubkey->pktlen != EC_PUBLIC_BYTES) | |||
return 0; | |||
/* store our private key */ | |||
memcpy(cs->cs_respkey, resp_key->pkt, resp_key->pktlen); | |||
x25519_base(cs->cs_resppubkey, resp_key->pkt, 1); | |||
/* store the public key to associate with */ | |||
memcpy(cs->cs_initpubkey, init_pubkey->pkt, init_pubkey->pktlen); | |||
/* public keys */ | |||
memcpy(&buf[0], cs->cs_initpubkey, EC_PUBLIC_BYTES); | |||
memcpy(&buf[EC_PUBLIC_BYTES], cs->cs_resppubkey, EC_PUBLIC_BYTES); | |||
strobe_key(crypt, SYM_KEY, buf, EC_PUBLIC_BYTES * 2); | |||
if (shared != NULL) | |||
strobe_key(&cs->cs_start, SYM_KEY, shared->pkt, shared->pktlen); | |||
cs->cs_start.cs_state = COMMS_WAIT_REQUEST_ECDHE; | |||
} else { | |||
return 0; | |||
} | |||
/* copy starting state over to initial state */ | |||
cs->cs_active = (struct comms_session){ | |||
.cs_crypto = cs->cs_start, | |||
.cs_state = COMMS_WAIT_REQUEST, | |||
}; | |||
cs->cs_active = cs->cs_start; | |||
cs->cs_pending = cs->cs_active; | |||
return 1; | |||
} | |||
#define CONFIRMED_STR_BASE "confirmed" | |||
@@ -128,7 +159,7 @@ badmsg: | |||
ret = 0; | |||
switch (sess->cs_state) { | |||
case COMMS_WAIT_REQUEST: | |||
case COMMS_WAIT_REQUEST_SHARED: | |||
if (msglen != 24 || memcmp(reqreset, &buf[16], | |||
sizeof reqreset - 1) != 0) | |||
goto badmsg; | |||
@@ -143,6 +174,55 @@ badmsg: | |||
sess->cs_state = COMMS_WAIT_CONFIRM; | |||
break; | |||
case COMMS_WAIT_REQUEST_ECDHE: | |||
{ | |||
unsigned char ephkey[EC_PRIVATE_BYTES]; | |||
unsigned char ephpubkey[EC_PUBLIC_BYTES]; | |||
unsigned char keybuf[EC_PUBLIC_BYTES * 2]; | |||
if (msglen != (EC_PUBLIC_BYTES + 8) || memcmp(reqreset, &buf[EC_PUBLIC_BYTES], | |||
sizeof reqreset - 1) != 0) | |||
goto badmsg; | |||
/* DH(s, re) */ | |||
memcpy(&keybuf[0], cs->cs_resppubkey, EC_PUBLIC_BYTES); | |||
if (x25519(&keybuf[0], cs->cs_respkey, buf, 1) != 0) | |||
goto badmsg; | |||
/* DH(s, rs) */ | |||
memcpy(&keybuf[EC_PUBLIC_BYTES], cs->cs_resppubkey, EC_PUBLIC_BYTES); | |||
if (x25519(&keybuf[EC_PUBLIC_BYTES], cs->cs_respkey, cs->cs_initpubkey, 1) != 0) | |||
goto badmsg; | |||
/* key(DH() + DH()) */ | |||
strobe_key(&sess->cs_crypto, SYM_KEY, keybuf, EC_PUBLIC_BYTES * 2); | |||
/* generate ephemeral */ | |||
bare_strobe_randomize(ephkey, EC_PRIVATE_BYTES); | |||
if (x25519_base(ephpubkey, ephkey, 1) != 0) | |||
goto badmsg; | |||
ret = strobe_put(&sess->cs_crypto, APP_CIPHERTEXT, ephpubkey, | |||
EC_PUBLIC_BYTES); | |||
ret += strobe_put(&sess->cs_crypto, MAC, NULL, MAC_LEN); | |||
/* DH(e, re) */ | |||
memcpy(&keybuf[0], ephpubkey, EC_PUBLIC_BYTES); | |||
if (x25519(&keybuf[0], ephkey, buf, 1) != 0) | |||
goto badmsg; | |||
/* DH(e, rs) */ | |||
memcpy(&keybuf[EC_PUBLIC_BYTES], ephpubkey, EC_PUBLIC_BYTES); | |||
if (x25519(&keybuf[EC_PUBLIC_BYTES], ephkey, cs->cs_initpubkey, 1) != 0) | |||
goto badmsg; | |||
/* key(DH() + DH()) */ | |||
strobe_key(&sess->cs_crypto, SYM_KEY, keybuf, EC_PUBLIC_BYTES * 2); | |||
sess->cs_state = COMMS_WAIT_CONFIRM; | |||
break; | |||
} | |||
case COMMS_WAIT_CONFIRM: | |||
if (msglen != 7 || memcmp(confirm, buf, | |||
sizeof confirm - 1) != 0) | |||
@@ -227,20 +307,14 @@ retmsg: | |||
if (cs->cs_pending.cs_state == COMMS_PROCESS_MSGS) { | |||
/* new active state */ | |||
cs->cs_active = cs->cs_pending; | |||
cs->cs_pending = (struct comms_session){ | |||
.cs_crypto = cs->cs_start, | |||
.cs_state = COMMS_WAIT_REQUEST, | |||
}; | |||
cs->cs_pending = cs->cs_start; | |||
goto retmsg; | |||
} | |||
/* pending session didn't work, maybe new */ | |||
struct comms_session tmpsess; | |||
tmpsess = (struct comms_session){ | |||
.cs_crypto = cs->cs_start, | |||
.cs_state = COMMS_WAIT_REQUEST, | |||
}; | |||
tmpsess = cs->cs_start; | |||
pbouttmp = *pbout; | |||
_comms_process_session(cs, &tmpsess, pbin, &pbouttmp); | |||
@@ -28,6 +28,7 @@ | |||
#include <stdint.h> | |||
#include <strobe.h> | |||
#include <x25519.h> | |||
#define COMMS_MAXMSG 64 | |||
@@ -40,7 +41,8 @@ struct pktbuf { | |||
typedef void (*process_msgfunc_t)(struct pktbuf, struct pktbuf *); | |||
enum comm_state { | |||
COMMS_WAIT_REQUEST = 1, | |||
COMMS_WAIT_REQUEST_SHARED = 1, | |||
COMMS_WAIT_REQUEST_ECDHE, | |||
COMMS_WAIT_CONFIRM, | |||
COMMS_PROCESS_MSGS, | |||
}; | |||
@@ -72,7 +74,11 @@ struct comms_state { | |||
struct comms_session cs_active; /* current active session */ | |||
struct comms_session cs_pending; /* current pending session */ | |||
strobe_s cs_start; /* special starting state cache */ | |||
unsigned char cs_respkey[EC_PRIVATE_BYTES]; /* private key for device */ | |||
unsigned char cs_resppubkey[EC_PUBLIC_BYTES]; /* public key for device */ | |||
unsigned char cs_initpubkey[EC_PUBLIC_BYTES]; /* public key for initiator */ | |||
struct comms_session cs_start; /* special starting state cache */ | |||
process_msgfunc_t cs_procmsg; | |||
@@ -86,5 +92,5 @@ struct comms_state { | |||
size_t _strobe_state_size(); | |||
size_t _comms_state_size(); | |||
void comms_init(struct comms_state *, process_msgfunc_t, struct pktbuf *); | |||
int comms_init(struct comms_state *, process_msgfunc_t, struct pktbuf *, struct pktbuf *, struct pktbuf *); | |||
void comms_process(struct comms_state *, struct pktbuf, struct pktbuf *); |
@@ -929,7 +929,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
shared_key = os.urandom(32) | |||
# Initialize everything | |||
lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key)) | |||
lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key), None, None) | |||
# Create test fixture, only use it to init crypto state | |||
tsd = SyncDatagram() | |||
@@ -996,6 +996,103 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
self.assertFalse(out) | |||
@timeout(2) | |||
async def test_ccode_ecdhe(self): | |||
_self = self | |||
from ctypes import 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_PING, [ ]), | |||
(CMD_TERMINATE, [ ]), | |||
] | |||
def procmsg(msg, outbuf): | |||
msgbuf = msg._from() | |||
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 [ 40, 17, 9, 9, 9, 9 ]: | |||
# get message | |||
inmsg = await self.get() | |||
# process the test message | |||
out = lora_comms.comms_process_wrap( | |||
commstate, inmsg) | |||
# make sure the reply matches length | |||
_self.assertEqual(expectlen, len(out)) | |||
# save what was originally replied | |||
origmsg = out | |||
# pretend that the reply didn't make it | |||
out = lora_comms.comms_process_wrap( | |||
commstate, inmsg) | |||
# make sure that the reply matches | |||
# the previous | |||
_self.assertEqual(origmsg, out) | |||
# pass the reply back | |||
await self.put(out) | |||
# Generate keys | |||
initkey = X25519.gen() | |||
respkey = X25519.gen() | |||
# Initialize everything | |||
lora_comms.comms_init(commstate, cb, None, make_pktbuf(respkey.getpriv()), make_pktbuf(initkey.getpub())) | |||
# Create test fixture | |||
tsd = CCodeSD() | |||
l = LORANode(tsd, init_key=initkey, resp_pub=respkey.getpub()) | |||
# Send various messages | |||
await l.start() | |||
await l.waitfor(30) | |||
await l.runfor(1, 50) | |||
await l.ping() | |||
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) | |||
#_debprint('done') | |||
@timeout(2) | |||
async def test_ccode(self): | |||
_self = self | |||
@@ -1064,7 +1161,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
shared_key = os.urandom(32) | |||
# Initialize everything | |||
lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key)) | |||
lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key), None, None) | |||
# Create test fixture | |||
tsd = CCodeSD() | |||
@@ -1193,7 +1290,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
shared_key = os.urandom(32) | |||
# Initialize everything | |||
lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key)) | |||
lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key), None, None) | |||
# Create test fixture | |||
tsd = CCodeSD1() | |||
@@ -1276,7 +1373,7 @@ class TestLoRaNodeMulticast(unittest.IsolatedAsyncioTestCase): | |||
shared_key = os.urandom(32) | |||
# Initialize everything | |||
lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key)) | |||
lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key), None, None) | |||
# create the object we are testing | |||
msd = MulticastSyncDatagram(self.maddr) | |||
@@ -83,12 +83,21 @@ class CommsSession(Structure,StructureRepr): | |||
('cs_state', c_int), | |||
] | |||
EC_PUBLIC_BYTES = 32 | |||
EC_PRIVATE_BYTES = 32 | |||
class CommsState(Structure,StructureRepr): | |||
_fields_ = [ | |||
# The alignment of these may be off | |||
('cs_active', CommsSession), | |||
('cs_pending', CommsSession), | |||
('cs_start', c_uint64 * _strobe_state_u64_cnt), | |||
('cs_respkey', c_uint8 * EC_PRIVATE_BYTES), | |||
('cs_resppubkey', c_uint8 * EC_PUBLIC_BYTES), | |||
('cs_initpubkey', c_uint8 * EC_PUBLIC_BYTES), | |||
('cs_start', CommsSession), | |||
('cs_procmsg', process_msgfunc_t), | |||
('cs_prevmsg', PktBuf), | |||
@@ -98,9 +107,6 @@ class CommsState(Structure,StructureRepr): | |||
('cs_prevmsgrespbuf', c_uint8 * 64), | |||
] | |||
EC_PUBLIC_BYTES = 32 | |||
EC_PRIVATE_BYTES = 32 | |||
if _lib is not None: | |||
_lib._comms_state_size.restype = c_size_t | |||
_lib._comms_state_size.argtypes = () | |||
@@ -111,8 +117,8 @@ if _lib is not None: | |||
X25519_BASE_POINT = (c_uint8 * (256//8)).in_dll(_lib, 'X25519_BASE_POINT') | |||
for func, ret, args in [ | |||
('comms_init', None, (POINTER(CommsState), process_msgfunc_t, | |||
POINTER(PktBuf))), | |||
('comms_init', c_int, (POINTER(CommsState), process_msgfunc_t, | |||
POINTER(PktBuf), POINTER(PktBuf), POINTER(PktBuf))), | |||
('comms_process', None, (POINTER(CommsState), PktBuf, POINTER(PktBuf))), | |||
('strobe_seed_prng', None, (POINTER(c_uint8), c_ssize_t)), | |||
('x25519', c_int, (c_uint8 * EC_PUBLIC_BYTES, c_uint8 * EC_PRIVATE_BYTES, c_uint8 * EC_PUBLIC_BYTES, c_int)), | |||